Merge "Revert "Force DIRECT flag on direct output thread."" into rvc-dev
diff --git a/MainlineFiles.cfg b/MainlineFiles.cfg
new file mode 100644
index 0000000..37d714c
--- /dev/null
+++ b/MainlineFiles.cfg
@@ -0,0 +1,34 @@
+#
+# mainline files for frameworks/av
+#
+# ignore comment (#) lines and blank lines
+# rest are path prefixes starting at root of the project
+# (so OWNERS, not frameworks/av/OWNERS)
+#
+# path
+# INCLUDE path
+# EXCLUDE path
+#
+# 'path' and 'INCLUDE path' are identical -- they both indicate that this path
+# is part of mainline
+# EXCLUDE indicates that this is not part of mainline,
+# so 'foo/' and 'EXCLUDE foo/nope'
+# means everything under foo/ is part of mainline EXCEPT foo/nope.
+# INCLUDE/EXCLUDE/INCLUDE nested structuring is not supported
+#
+# matching is purely prefix
+# so 'foo' will match 'foo', 'foo.c', 'foo/bar/baz'
+# if you want to exclude a directory, best to use a pattern like "foo/"
+#
+
+media/codec2/components/
+media/codecs/
+media/extractors/
+media/libstagefright/codecs/amrnb/
+media/libstagefright/codecs/amrwb/
+media/libstagefright/codecs/amrwbenc/
+media/libstagefright/codecs/common/
+media/libstagefright/codecs/flac/
+media/libstagefright/codecs/m4v_h263/
+media/libstagefright/codecs/mp3dec/
+media/libstagefright/mpeg2ts
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index e63185d..ae920c0 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,2 @@
[Hook Scripts]
-mainline_hook = tools/mainline_hook.sh ${PREUPLOAD_COMMIT} "."
+mainline_hook = ${REPO_ROOT}/frameworks/av/tools/mainline_hook_partial.sh ${REPO_ROOT} ${PREUPLOAD_FILES}
diff --git a/apex/TEST_MAPPING b/apex/TEST_MAPPING
index a2e98cc..f036516 100644
--- a/apex/TEST_MAPPING
+++ b/apex/TEST_MAPPING
@@ -3,5 +3,30 @@
{
"path": "system/apex/tests"
}
+ ],
+ "presubmit": [
+ // The following tests validate codec and drm path.
+ {
+ "name": "GtsMediaTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
+ }
+ ]
+ },
+ {
+ "name": "GtsExoPlayerTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.SocPresubmit"
+ },
+ {
+ "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+ }
+ ]
+ }
]
}
diff --git a/apex/testing/Android.bp b/apex/testing/Android.bp
index 376d3e4..a04ab3f 100644
--- a/apex/testing/Android.bp
+++ b/apex/testing/Android.bp
@@ -17,6 +17,7 @@
manifest: "test_manifest.json",
file_contexts: ":com.android.media-file_contexts",
defaults: ["com.android.media-defaults"],
+ prebuilts: ["sdkinfo_45"],
installable: false,
}
diff --git a/apex/testing/test_manifest.json b/apex/testing/test_manifest.json
index ddd642e..e1295a2 100644
--- a/apex/testing/test_manifest.json
+++ b/apex/testing/test_manifest.json
@@ -1,4 +1,4 @@
{
"name": "com.android.media",
- "version": 300000000
+ "version": 2147483647
}
diff --git a/camera/CameraMetadata.cpp b/camera/CameraMetadata.cpp
index 4745ee8..135384a 100644
--- a/camera/CameraMetadata.cpp
+++ b/camera/CameraMetadata.cpp
@@ -17,9 +17,7 @@
// #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>
@@ -40,13 +38,11 @@
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);
}
@@ -117,7 +113,6 @@
}
void CameraMetadata::clear() {
- ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return;
@@ -129,7 +124,6 @@
}
void CameraMetadata::acquire(camera_metadata_t *buffer) {
- ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return;
@@ -143,7 +137,6 @@
}
void CameraMetadata::acquire(CameraMetadata &other) {
- ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return;
@@ -152,12 +145,10 @@
}
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;
@@ -179,7 +170,6 @@
}
status_t CameraMetadata::sort() {
- ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return INVALID_OPERATION;
@@ -310,7 +300,6 @@
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__);
@@ -368,13 +357,11 @@
}
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) {
@@ -391,7 +378,6 @@
}
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);
@@ -403,7 +389,6 @@
}
status_t CameraMetadata::erase(uint32_t tag) {
- ATRACE_CALL();
camera_metadata_entry_t entry;
status_t res;
if (mLocked) {
@@ -434,7 +419,6 @@
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;
@@ -511,7 +495,6 @@
}
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) {
@@ -551,7 +534,7 @@
status_t CameraMetadata::readFromParcel(const Parcel& data,
camera_metadata_t** out) {
- ATRACE_CALL();
+
status_t err = OK;
camera_metadata_t* metadata = NULL;
@@ -642,7 +625,6 @@
status_t CameraMetadata::writeToParcel(Parcel& data,
const camera_metadata_t* metadata) {
- ATRACE_CALL();
status_t res = OK;
/**
@@ -737,7 +719,7 @@
}
status_t CameraMetadata::readFromParcel(const Parcel *parcel) {
- ATRACE_CALL();
+
ALOGV("%s: parcel = %p", __FUNCTION__, parcel);
status_t res = OK;
@@ -769,7 +751,7 @@
}
status_t CameraMetadata::writeToParcel(Parcel *parcel) const {
- ATRACE_CALL();
+
ALOGV("%s: parcel = %p", __FUNCTION__, parcel);
if (parcel == NULL) {
@@ -798,7 +780,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/CaptureResult.cpp b/camera/CaptureResult.cpp
index 9cbfdb0..755051c 100644
--- a/camera/CaptureResult.cpp
+++ b/camera/CaptureResult.cpp
@@ -49,6 +49,9 @@
}
errorPhysicalCameraId = cameraId;
}
+ parcel->readInt64(&lastCompletedRegularFrameNumber);
+ parcel->readInt64(&lastCompletedReprocessFrameNumber);
+ parcel->readInt64(&lastCompletedZslFrameNumber);
return OK;
}
@@ -76,6 +79,9 @@
} else {
parcel->writeBool(false);
}
+ parcel->writeInt64(lastCompletedRegularFrameNumber);
+ parcel->writeInt64(lastCompletedReprocessFrameNumber);
+ parcel->writeInt64(lastCompletedZslFrameNumber);
return OK;
}
diff --git a/camera/ICameraClient.cpp b/camera/ICameraClient.cpp
index 487b8b0..c02c81b 100644
--- a/camera/ICameraClient.cpp
+++ b/camera/ICameraClient.cpp
@@ -139,20 +139,18 @@
CHECK_INTERFACE(ICameraClient, data, reply);
int32_t msgType = data.readInt32();
sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
- camera_frame_metadata_t *metadata = NULL;
+ camera_frame_metadata_t metadata;
if (data.dataAvail() > 0) {
- metadata = new camera_frame_metadata_t;
- metadata->number_of_faces = data.readInt32();
- if (metadata->number_of_faces <= 0 ||
- metadata->number_of_faces > (int32_t)(INT32_MAX / sizeof(camera_face_t))) {
- ALOGE("%s: Too large face count: %d", __FUNCTION__, metadata->number_of_faces);
+ metadata.number_of_faces = data.readInt32();
+ if (metadata.number_of_faces <= 0 ||
+ metadata.number_of_faces > (int32_t)(INT32_MAX / sizeof(camera_face_t))) {
+ ALOGE("%s: Too large face count: %d", __FUNCTION__, metadata.number_of_faces);
return BAD_VALUE;
}
- metadata->faces = (camera_face_t *) data.readInplace(
- sizeof(camera_face_t) * metadata->number_of_faces);
+ metadata.faces = (camera_face_t *) data.readInplace(
+ sizeof(camera_face_t) * metadata.number_of_faces);
}
- dataCallback(msgType, imageData, metadata);
- if (metadata) delete metadata;
+ dataCallback(msgType, imageData, &metadata);
return NO_ERROR;
} break;
case DATA_CALLBACK_TIMESTAMP: {
diff --git a/camera/include/camera/CaptureResult.h b/camera/include/camera/CaptureResult.h
index dc3d282..f163c1e 100644
--- a/camera/include/camera/CaptureResult.h
+++ b/camera/include/camera/CaptureResult.h
@@ -76,6 +76,34 @@
*/
String16 errorPhysicalCameraId;
+ // The last completed frame numbers shouldn't be checked in onResultReceived() and notifyError()
+ // because the output buffers could be arriving after onResultReceived() and
+ // notifyError(). Given this constraint, we check it for each
+ // onCaptureStarted, and if there is no further onCaptureStarted(),
+ // check for onDeviceIdle() to clear out all pending frame numbers.
+
+ /**
+ * The latest regular request frameNumber for which all buffers and capture result have been
+ * returned or notified as an BUFFER_ERROR/RESULT_ERROR/REQUEST_ERROR. -1 if
+ * none has completed.
+ */
+ int64_t lastCompletedRegularFrameNumber;
+
+ /**
+ * The latest reprocess request frameNumber for which all buffers and capture result have been
+ * returned or notified as an BUFFER_ERROR/RESULT_ERROR/REQUEST_ERROR. -1 if
+ * none has completed.
+ */
+ int64_t lastCompletedReprocessFrameNumber;
+
+ /**
+ * The latest Zsl request frameNumber for which all buffers and capture result have been
+ * returned or notified as an BUFFER_ERROR/RESULT_ERROR/REQUEST_ERROR. -1 if
+ * none has completed.
+ */
+ int64_t lastCompletedZslFrameNumber;
+
+
/**
* Constructor initializes object as invalid by setting requestId to be -1.
*/
@@ -87,7 +115,10 @@
frameNumber(0),
partialResultCount(0),
errorStreamId(-1),
- errorPhysicalCameraId() {
+ errorPhysicalCameraId(),
+ lastCompletedRegularFrameNumber(-1),
+ lastCompletedReprocessFrameNumber(-1),
+ lastCompletedZslFrameNumber(-1) {
}
/**
diff --git a/camera/ndk/impl/ACameraDevice.cpp b/camera/ndk/impl/ACameraDevice.cpp
index 0d7180a..c15c5a5 100644
--- a/camera/ndk/impl/ACameraDevice.cpp
+++ b/camera/ndk/impl/ACameraDevice.cpp
@@ -1336,56 +1336,97 @@
void
CameraDevice::checkAndFireSequenceCompleteLocked() {
int64_t completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
- //std::map<int, int64_t> mSequenceLastFrameNumberMap;
auto it = mSequenceLastFrameNumberMap.begin();
while (it != mSequenceLastFrameNumberMap.end()) {
int sequenceId = it->first;
- int64_t lastFrameNumber = it->second;
- bool seqCompleted = false;
- bool hasCallback = true;
+ int64_t lastFrameNumber = it->second.lastFrameNumber;
+ bool hasCallback = true;
+
+ if (mRemote == nullptr) {
+ ALOGW("Camera %s closed while checking sequence complete", getId());
+ return;
+ }
+ ALOGV("%s: seq %d's last frame number %" PRId64 ", completed %" PRId64,
+ __FUNCTION__, sequenceId, lastFrameNumber, completedFrameNumber);
+ if (!it->second.isSequenceCompleted) {
+ // Check if there is callback for this sequence
+ // This should not happen because we always register callback (with nullptr inside)
+ if (mSequenceCallbackMap.count(sequenceId) == 0) {
+ ALOGW("No callback found for sequenceId %d", sequenceId);
+ hasCallback = false;
+ }
+
+ if (lastFrameNumber <= completedFrameNumber) {
+ ALOGV("Mark sequenceId %d as sequence completed", sequenceId);
+ it->second.isSequenceCompleted = true;
+ }
+
+ if (it->second.isSequenceCompleted && hasCallback) {
+ auto cbIt = mSequenceCallbackMap.find(sequenceId);
+ CallbackHolder cbh = cbIt->second;
+
+ // send seq complete callback
+ sp<AMessage> msg = new AMessage(kWhatCaptureSeqEnd, mHandler);
+ msg->setPointer(kContextKey, cbh.mContext);
+ msg->setObject(kSessionSpKey, cbh.mSession);
+ msg->setPointer(kCallbackFpKey, (void*) cbh.mOnCaptureSequenceCompleted);
+ msg->setInt32(kSequenceIdKey, sequenceId);
+ msg->setInt64(kFrameNumberKey, lastFrameNumber);
+
+ // Clear the session sp before we send out the message
+ // This will guarantee the rare case where the message is processed
+ // before cbh goes out of scope and causing we call the session
+ // destructor while holding device lock
+ cbh.mSession.clear();
+ postSessionMsgAndCleanup(msg);
+ }
+ }
+
+ if (it->second.isSequenceCompleted && it->second.isInflightCompleted) {
+ if (mSequenceCallbackMap.find(sequenceId) != mSequenceCallbackMap.end()) {
+ mSequenceCallbackMap.erase(sequenceId);
+ }
+ it = mSequenceLastFrameNumberMap.erase(it);
+ ALOGV("%s: Remove holder for sequenceId %d", __FUNCTION__, sequenceId);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void
+CameraDevice::removeCompletedCallbackHolderLocked(int64_t lastCompletedRegularFrameNumber) {
+ auto it = mSequenceLastFrameNumberMap.begin();
+ while (it != mSequenceLastFrameNumberMap.end()) {
+ int sequenceId = it->first;
+ int64_t lastFrameNumber = it->second.lastFrameNumber;
if (mRemote == nullptr) {
ALOGW("Camera %s closed while checking sequence complete", getId());
return;
}
- // Check if there is callback for this sequence
- // This should not happen because we always register callback (with nullptr inside)
- if (mSequenceCallbackMap.count(sequenceId) == 0) {
- ALOGW("No callback found for sequenceId %d", sequenceId);
- hasCallback = false;
- }
+ ALOGV("%s: seq %d's last frame number %" PRId64
+ ", completed inflight frame number %" PRId64,
+ __FUNCTION__, sequenceId, lastFrameNumber,
+ lastCompletedRegularFrameNumber);
+ if (lastFrameNumber <= lastCompletedRegularFrameNumber) {
+ if (it->second.isSequenceCompleted) {
+ // Check if there is callback for this sequence
+ // This should not happen because we always register callback (with nullptr inside)
+ if (mSequenceCallbackMap.count(sequenceId) == 0) {
+ ALOGW("No callback found for sequenceId %d", sequenceId);
+ } else {
+ mSequenceCallbackMap.erase(sequenceId);
+ }
- if (lastFrameNumber <= completedFrameNumber) {
- ALOGV("seq %d reached last frame %" PRId64 ", completed %" PRId64,
- sequenceId, lastFrameNumber, completedFrameNumber);
- seqCompleted = true;
- }
-
- if (seqCompleted && hasCallback) {
- // remove callback holder from callback map
- auto cbIt = mSequenceCallbackMap.find(sequenceId);
- CallbackHolder cbh = cbIt->second;
- mSequenceCallbackMap.erase(cbIt);
- // send seq complete callback
- sp<AMessage> msg = new AMessage(kWhatCaptureSeqEnd, mHandler);
- msg->setPointer(kContextKey, cbh.mContext);
- msg->setObject(kSessionSpKey, cbh.mSession);
- msg->setPointer(kCallbackFpKey, (void*) cbh.mOnCaptureSequenceCompleted);
- msg->setInt32(kSequenceIdKey, sequenceId);
- msg->setInt64(kFrameNumberKey, lastFrameNumber);
-
- // Clear the session sp before we send out the message
- // This will guarantee the rare case where the message is processed
- // before cbh goes out of scope and causing we call the session
- // destructor while holding device lock
- cbh.mSession.clear();
- postSessionMsgAndCleanup(msg);
- }
-
- // No need to track sequence complete if there is no callback registered
- if (seqCompleted || !hasCallback) {
- it = mSequenceLastFrameNumberMap.erase(it);
+ it = mSequenceLastFrameNumberMap.erase(it);
+ ALOGV("%s: Remove holder for sequenceId %d", __FUNCTION__, sequenceId);
+ } else {
+ ALOGV("Mark sequenceId %d as inflight completed", sequenceId);
+ it->second.isInflightCompleted = true;
+ ++it;
+ }
} else {
++it;
}
@@ -1480,6 +1521,9 @@
return ret;
}
+ dev->removeCompletedCallbackHolderLocked(
+ std::numeric_limits<int64_t>::max()/*lastCompletedRegularFrameNumber*/);
+
if (dev->mIdle) {
// Already in idle state. Possibly other thread did waitUntilIdle
return ret;
@@ -1522,6 +1566,9 @@
return ret;
}
+ dev->removeCompletedCallbackHolderLocked(
+ resultExtras.lastCompletedRegularFrameNumber);
+
int sequenceId = resultExtras.requestId;
int32_t burstId = resultExtras.burstId;
diff --git a/camera/ndk/impl/ACameraDevice.h b/camera/ndk/impl/ACameraDevice.h
index 6c2ceb3..d937865 100644
--- a/camera/ndk/impl/ACameraDevice.h
+++ b/camera/ndk/impl/ACameraDevice.h
@@ -267,8 +267,23 @@
static const int REQUEST_ID_NONE = -1;
int mRepeatingSequenceId = REQUEST_ID_NONE;
- // sequence id -> last frame number map
- std::map<int, int64_t> mSequenceLastFrameNumberMap;
+ // sequence id -> last frame number holder map
+ struct RequestLastFrameNumberHolder {
+ int64_t lastFrameNumber;
+ // Whether the current sequence is completed (capture results are
+ // generated). May be set to true, but
+ // not removed from the map if not all inflight requests in the sequence
+ // have been completed.
+ bool isSequenceCompleted = false;
+ // Whether all inflight requests in the sequence are completed
+ // (capture results and buffers are generated). May be
+ // set to true, but not removed from the map yet if the capture results
+ // haven't been delivered to the app yet.
+ bool isInflightCompleted = false;
+ RequestLastFrameNumberHolder(int64_t lastFN) :
+ lastFrameNumber(lastFN) {}
+ };
+ std::map<int, RequestLastFrameNumberHolder> mSequenceLastFrameNumberMap;
struct CallbackHolder {
CallbackHolder(sp<ACameraCaptureSession> session,
@@ -338,6 +353,7 @@
void checkRepeatingSequenceCompleteLocked(const int sequenceId, const int64_t lastFrameNumber);
void checkAndFireSequenceCompleteLocked();
+ void removeCompletedCallbackHolderLocked(int64_t lastCompletedRegularFrameNumber);
// Misc variables
int32_t mShadingMapSize[2]; // const after constructor
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 16457ac..7d78571 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -1897,11 +1897,13 @@
* <p>By using this control, the application gains a simpler way to control zoom, which can
* be a combination of optical and digital zoom. For example, a multi-camera system may
* contain more than one lens with different focal lengths, and the user can use optical
- * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:
- * <em> Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
- * better precision compared to an integer value of ACAMERA_SCALER_CROP_REGION.
- * </em> Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
- * ACAMERA_SCALER_CROP_REGION doesn't.</p>
+ * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:</p>
+ * <ul>
+ * <li>Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
+ * better precision compared to an integer value of ACAMERA_SCALER_CROP_REGION.</li>
+ * <li>Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
+ * ACAMERA_SCALER_CROP_REGION doesn't.</li>
+ * </ul>
* <p>To illustrate, here are several scenarios of different zoom ratios, crop regions,
* and output streams, for a hypothetical camera device with an active array of size
* <code>(2000,1500)</code>.</p>
@@ -4381,8 +4383,8 @@
ACAMERA_SENSOR_AVAILABLE_TEST_PATTERN_MODES = // int32[n]
ACAMERA_SENSOR_START + 25,
/**
- * <p>Duration between the start of first row exposure
- * and the start of last row exposure.</p>
+ * <p>Duration between the start of exposure for the first row of the image sensor,
+ * and the start of exposure for one past the last row of the image sensor.</p>
*
* <p>Type: int64</p>
*
@@ -4391,12 +4393,22 @@
* <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
* </ul></p>
*
- * <p>This is the exposure time skew between the first and last
- * row exposure start times. The first row and the last row are
- * the first and last rows inside of the
+ * <p>This is the exposure time skew between the first and <code>(last+1)</code> row exposure start times. The
+ * first row and the last row are the first and last rows inside of the
* ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.</p>
- * <p>For typical camera sensors that use rolling shutters, this is also equivalent
- * to the frame readout time.</p>
+ * <p>For typical camera sensors that use rolling shutters, this is also equivalent to the frame
+ * readout time.</p>
+ * <p>If the image sensor is operating in a binned or cropped mode due to the current output
+ * target resolutions, it's possible this skew is reported to be larger than the exposure
+ * time, for example, since it is based on the full array even if a partial array is read
+ * out. Be sure to scale the number to cover the section of the sensor actually being used
+ * for the outputs you care about. So if your output covers N rows of the active array of
+ * height H, scale this value by N/H to get the total skew for that viewport.</p>
+ * <p><em>Note:</em> Prior to Android 11, this field was described as measuring duration from
+ * first to last row of the image sensor, which is not equal to the frame readout time for a
+ * rolling shutter sensor. Implementations generally reported the latter value, so to resolve
+ * the inconsistency, the description has been updated to range from (first, last+1) row
+ * exposure start, instead.</p>
*
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
@@ -6042,10 +6054,11 @@
* </ul></p>
*
* <p>The accuracy of the frame timestamp synchronization determines the physical cameras'
- * ability to start exposure at the same time. If the sensorSyncType is CALIBRATED,
- * the physical camera sensors usually run in master-slave mode so that their shutter
- * time is synchronized. For APPROXIMATE sensorSyncType, the camera sensors usually run in
- * master-master mode, and there could be offset between their start of exposure.</p>
+ * ability to start exposure at the same time. If the sensorSyncType is CALIBRATED, the
+ * physical camera sensors usually run in leader/follower mode where one sensor generates a
+ * timing signal for the other, so that their shutter time is synchronized. For APPROXIMATE
+ * sensorSyncType, the camera sensors usually run in leader/leader mode, where both sensors
+ * use their own timing generator, and there could be offset between their start of exposure.</p>
* <p>In both cases, all images generated for a particular capture request still carry the same
* timestamps, so that they can be used to look up the matching frame number and
* onCaptureStarted callback.</p>
@@ -8076,19 +8089,35 @@
* <li>ACAMERA_LENS_POSE_REFERENCE</li>
* <li>ACAMERA_LENS_DISTORTION</li>
* </ul>
- * <p>The field of view of all non-RAW physical streams must be the same or as close as
- * possible to that of non-RAW logical streams. If the requested FOV is outside of the
- * range supported by the physical camera, the physical stream for that physical camera
- * will use either the maximum or minimum scaler crop region, depending on which one is
- * closer to the requested FOV. For example, for a logical camera with wide-tele lens
- * configuration where the wide lens is the default, if the logical camera's crop region
- * is set to maximum, the physical stream for the tele lens will be configured to its
- * maximum crop region. On the other hand, if the logical camera has a normal-wide lens
- * configuration where the normal lens is the default, when the logical camera's crop
- * region is set to maximum, the FOV of the logical streams will be that of the normal
- * lens. The FOV of the physical streams for the wide lens will be the same as the
- * logical stream, by making the crop region smaller than its active array size to
- * compensate for the smaller focal length.</p>
+ * <p>The field of view of non-RAW physical streams must not be smaller than that of the
+ * non-RAW logical streams, or the maximum field-of-view of the physical camera,
+ * whichever is smaller. The application should check the physical capture result
+ * metadata for how the physical streams are cropped or zoomed. More specifically, given
+ * the physical camera result metadata, the effective horizontal field-of-view of the
+ * physical camera is:</p>
+ * <pre><code>fov = 2 * atan2(cropW * sensorW / (2 * zoomRatio * activeArrayW), focalLength)
+ * </code></pre>
+ * <p>where the equation parameters are the physical camera's crop region width, physical
+ * sensor width, zoom ratio, active array width, and focal length respectively. Typically
+ * the physical stream of active physical camera has the same field-of-view as the
+ * logical streams. However, the same may not be true for physical streams from
+ * non-active physical cameras. For example, if the logical camera has a wide-ultrawide
+ * configuration where the wide lens is the default, when the crop region is set to the
+ * logical camera's active array size, (and the zoom ratio set to 1.0 starting from
+ * Android 11), a physical stream for the ultrawide camera may prefer outputing images
+ * with larger field-of-view than that of the wide camera for better stereo matching
+ * margin or more robust motion tracking. At the same time, the physical non-RAW streams'
+ * field of view must not be smaller than the requested crop region and zoom ratio, as
+ * long as it's within the physical lens' capability. For example, for a logical camera
+ * with wide-tele lens configuration where the wide lens is the default, if the logical
+ * camera's crop region is set to maximum size, and zoom ratio set to 1.0, the physical
+ * stream for the tele lens will be configured to its maximum size crop region (no zoom).</p>
+ * <p><em>Deprecated:</em> Prior to Android 11, the field of view of all non-RAW physical streams
+ * cannot be larger than that of non-RAW logical streams. If the logical camera has a
+ * wide-ultrawide lens configuration where the wide lens is the default, when the logical
+ * camera's crop region is set to maximum size, the FOV of the physical streams for the
+ * ultrawide lens will be the same as the logical stream, by making the crop region
+ * smaller than its active array size to compensate for the smaller focal length.</p>
* <p>Even if the underlying physical cameras have different RAW characteristics (such as
* size or CFA pattern), a logical camera can still advertise RAW capability. In this
* case, when the application configures a RAW stream, the camera device will make sure
diff --git a/drm/TEST_MAPPING b/drm/TEST_MAPPING
new file mode 100644
index 0000000..2595e3e
--- /dev/null
+++ b/drm/TEST_MAPPING
@@ -0,0 +1,27 @@
+{
+ "presubmit": [
+ // The following tests validate codec and drm path.
+ {
+ "name": "GtsMediaTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
+ }
+ ]
+ },
+ {
+ "name": "GtsExoPlayerTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.SocPresubmit"
+ },
+ {
+ "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+ }
+ ]
+ }
+ ]
+}
diff --git a/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp
index 3ecf6d5..1495703 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp
+++ b/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp
@@ -148,14 +148,17 @@
// Calculate the output buffer size and determine if any subsamples are
// encrypted.
size_t destSize = 0;
+ size_t srcSize = 0;
bool haveEncryptedSubsamples = false;
for (size_t i = 0; i < subSamples.size(); i++) {
const SubSample &subSample = subSamples[i];
- if (__builtin_add_overflow(destSize, subSample.numBytesOfClearData, &destSize)) {
+ if (__builtin_add_overflow(destSize, subSample.numBytesOfClearData, &destSize) ||
+ __builtin_add_overflow(srcSize, subSample.numBytesOfClearData, &srcSize)) {
_hidl_cb(Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE, 0, "subsample clear size overflow");
return Void();
}
- if (__builtin_add_overflow(destSize, subSample.numBytesOfEncryptedData, &destSize)) {
+ if (__builtin_add_overflow(destSize, subSample.numBytesOfEncryptedData, &destSize) ||
+ __builtin_add_overflow(srcSize, subSample.numBytesOfEncryptedData, &srcSize)) {
_hidl_cb(Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE, 0, "subsample encrypted size overflow");
return Void();
}
@@ -164,7 +167,7 @@
}
}
- if (destSize > destBuffer.size) {
+ if (destSize > destBuffer.size || srcSize > source.size) {
_hidl_cb(Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE, 0, "subsample sum too large");
return Void();
}
diff --git a/include/drm/TEST_MAPPING b/include/drm/TEST_MAPPING
new file mode 100644
index 0000000..28e432e
--- /dev/null
+++ b/include/drm/TEST_MAPPING
@@ -0,0 +1,26 @@
+{
+ "presubmit": [
+ {
+ "name": "GtsMediaTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
+ }
+ ]
+ },
+ {
+ "name": "GtsExoPlayerTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.SocPresubmit"
+ },
+ {
+ "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+ }
+ ]
+ }
+ ]
+}
diff --git a/include/media/TEST_MAPPING b/include/media/TEST_MAPPING
new file mode 100644
index 0000000..ac697da
--- /dev/null
+++ b/include/media/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/av/include/drm"
+ }
+ ]
+}
diff --git a/include/private/media/TEST_MAPPING b/include/private/media/TEST_MAPPING
new file mode 100644
index 0000000..ac697da
--- /dev/null
+++ b/include/private/media/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/av/include/drm"
+ }
+ ]
+}
diff --git a/media/audioserver/audioserver.rc b/media/audioserver/audioserver.rc
index 6cb0c73..f05c2d2 100644
--- a/media/audioserver/audioserver.rc
+++ b/media/audioserver/audioserver.rc
@@ -20,6 +20,14 @@
# Keep the original service names for backward compatibility
stop vendor.audio-hal-2-0
stop audio-hal-2-0
+ # See b/155364397. Need to have HAL service running for VTS.
+ # Can't use 'restart' because then HAL service would restart
+ # audioserver bringing it back into running state.
+ start vendor.audio-hal
+ start vendor.audio-hal-4-0-msd
+ # Keep the original service names for backward compatibility
+ start vendor.audio-hal-2-0
+ start audio-hal-2-0
on property:init.svc.audioserver=running
start vendor.audio-hal
diff --git a/media/bufferpool/2.0/Android.bp b/media/bufferpool/2.0/Android.bp
index 557b7ef..536f75e 100644
--- a/media/bufferpool/2.0/Android.bp
+++ b/media/bufferpool/2.0/Android.bp
@@ -30,6 +30,7 @@
name: "libstagefright_bufferpool@2.0.1",
defaults: ["libstagefright_bufferpool@2.0-default"],
vendor_available: true,
+ min_sdk_version: "29",
// TODO: b/147147992
double_loadable: true,
cflags: [
diff --git a/media/codec2/components/aac/C2SoftAacDec.cpp b/media/codec2/components/aac/C2SoftAacDec.cpp
index f39620e..677f316 100644
--- a/media/codec2/components/aac/C2SoftAacDec.cpp
+++ b/media/codec2/components/aac/C2SoftAacDec.cpp
@@ -89,11 +89,18 @@
addParameter(
DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT)
.withDefault(new C2StreamChannelCountInfo::output(0u, 1))
- .withFields({C2F(mChannelCount, value).inRange(1, 8)})
+ .withFields({C2F(mChannelCount, value).inRange(1, MAX_CHANNEL_COUNT)})
.withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps)
.build());
addParameter(
+ DefineParam(mMaxChannelCount, C2_PARAMKEY_MAX_CHANNEL_COUNT)
+ .withDefault(new C2StreamMaxChannelCountInfo::input(0u, MAX_CHANNEL_COUNT))
+ .withFields({C2F(mMaxChannelCount, value).inRange(1, MAX_CHANNEL_COUNT)})
+ .withSetter(Setter<decltype(*mMaxChannelCount)>::StrictValueWithNoDeps)
+ .build());
+
+ addParameter(
DefineParam(mBitrate, C2_PARAMKEY_BITRATE)
.withDefault(new C2StreamBitrateInfo::input(0u, 64000))
.withFields({C2F(mBitrate, value).inRange(8000, 960000)})
@@ -225,6 +232,7 @@
int32_t getDrcAttenuationFactor() const { return mDrcAttenuationFactor->value * 127. + 0.5; }
int32_t getDrcEffectType() const { return mDrcEffectType->value; }
int32_t getDrcAlbumMode() const { return mDrcAlbumMode->value; }
+ u_int32_t getMaxChannelCount() const { return mMaxChannelCount->value; }
int32_t getDrcOutputLoudness() const { return (mDrcOutputLoudness->value <= 0 ? -mDrcOutputLoudness->value * 4. + 0.5 : -1); }
private:
@@ -241,6 +249,7 @@
std::shared_ptr<C2StreamDrcAttenuationFactorTuning::input> mDrcAttenuationFactor;
std::shared_ptr<C2StreamDrcEffectTypeTuning::input> mDrcEffectType;
std::shared_ptr<C2StreamDrcAlbumModeTuning::input> mDrcAlbumMode;
+ std::shared_ptr<C2StreamMaxChannelCountInfo::input> mMaxChannelCount;
std::shared_ptr<C2StreamDrcOutputLoudnessTuning::output> mDrcOutputLoudness;
// TODO Add : C2StreamAacSbrModeTuning
};
@@ -366,9 +375,10 @@
ALOGV("AAC decoder using MPEG-D DRC album mode %d", albumMode);
aacDecoder_SetParam(mAACDecoder, AAC_UNIDRC_ALBUM_MODE, albumMode);
- // By default, the decoder creates a 5.1 channel downmix signal.
- // For seven and eight channel input streams, enable 6.1 and 7.1 channel output
- aacDecoder_SetParam(mAACDecoder, AAC_PCM_MAX_OUTPUT_CHANNELS, -1);
+ // AAC_PCM_MAX_OUTPUT_CHANNELS
+ u_int32_t maxChannelCount = mIntf->getMaxChannelCount();
+ ALOGV("AAC decoder using maximum output channel count %d", maxChannelCount);
+ aacDecoder_SetParam(mAACDecoder, AAC_PCM_MAX_OUTPUT_CHANNELS, maxChannelCount);
return status;
}
@@ -707,6 +717,11 @@
ALOGV("AAC decoder using MPEG-D DRC album mode %d", albumMode);
aacDecoder_SetParam(mAACDecoder, AAC_UNIDRC_ALBUM_MODE, albumMode);
+ // AAC_PCM_MAX_OUTPUT_CHANNELS
+ int32_t maxChannelCount = mIntf->getMaxChannelCount();
+ ALOGV("AAC decoder using maximum output channel count %d", maxChannelCount);
+ aacDecoder_SetParam(mAACDecoder, AAC_PCM_MAX_OUTPUT_CHANNELS, maxChannelCount);
+
mDrcWrap.update();
UINT inBufferUsedLength = inBufferLength[0] - bytesValid[0];
@@ -776,7 +791,6 @@
// After an error, replace bufferSize with the sum of the
// decodedSizes to resynchronize the in/out lists.
- inInfo.decodedSizes.pop_back();
inInfo.bufferSize = std::accumulate(
inInfo.decodedSizes.begin(), inInfo.decodedSizes.end(), 0);
@@ -847,6 +861,51 @@
ALOGE("Getting output loudness failed");
}
}
+
+ // update config with values used for decoding:
+ // Album mode, target reference level, DRC effect type, DRC attenuation and boost
+ // factor, DRC compression mode, encoder target level and max channel count
+ // with input values as they were not modified by decoder
+
+ C2StreamDrcAttenuationFactorTuning::input currentAttenuationFactor(0u,
+ (C2FloatValue) (attenuationFactor/127.));
+ work->worklets.front()->output.configUpdate.push_back(
+ C2Param::Copy(currentAttenuationFactor));
+
+ C2StreamDrcBoostFactorTuning::input currentBoostFactor(0u,
+ (C2FloatValue) (boostFactor/127.));
+ work->worklets.front()->output.configUpdate.push_back(
+ C2Param::Copy(currentBoostFactor));
+
+ C2StreamDrcCompressionModeTuning::input currentCompressMode(0u,
+ (C2Config::drc_compression_mode_t) compressMode);
+ work->worklets.front()->output.configUpdate.push_back(
+ C2Param::Copy(currentCompressMode));
+
+ C2StreamDrcEncodedTargetLevelTuning::input currentEncodedTargetLevel(0u,
+ (C2FloatValue) (encTargetLevel*-0.25));
+ work->worklets.front()->output.configUpdate.push_back(
+ C2Param::Copy(currentEncodedTargetLevel));
+
+ C2StreamDrcAlbumModeTuning::input currentAlbumMode(0u,
+ (C2Config::drc_album_mode_t) albumMode);
+ work->worklets.front()->output.configUpdate.push_back(
+ C2Param::Copy(currentAlbumMode));
+
+ C2StreamDrcTargetReferenceLevelTuning::input currentTargetRefLevel(0u,
+ (float) (targetRefLevel*-0.25));
+ work->worklets.front()->output.configUpdate.push_back(
+ C2Param::Copy(currentTargetRefLevel));
+
+ C2StreamDrcEffectTypeTuning::input currentEffectype(0u,
+ (C2Config::drc_effect_type_t) effectType);
+ work->worklets.front()->output.configUpdate.push_back(
+ C2Param::Copy(currentEffectype));
+
+ C2StreamMaxChannelCountInfo::input currentMaxChannelCnt(0u, maxChannelCount);
+ work->worklets.front()->output.configUpdate.push_back(
+ C2Param::Copy(currentMaxChannelCnt));
+
} while (decoderErr == AAC_DEC_OK);
}
diff --git a/media/codec2/components/aac/C2SoftAacEnc.cpp b/media/codec2/components/aac/C2SoftAacEnc.cpp
index 4db94f5..2e85915 100644
--- a/media/codec2/components/aac/C2SoftAacEnc.cpp
+++ b/media/codec2/components/aac/C2SoftAacEnc.cpp
@@ -293,6 +293,30 @@
return OK;
}
+static void MaybeLogTimestampWarning(
+ long long lastFrameEndTimestampUs, long long inputTimestampUs) {
+ using Clock = std::chrono::steady_clock;
+ thread_local Clock::time_point sLastLogTimestamp{};
+ thread_local int32_t sOverlapCount = -1;
+ if (Clock::now() - sLastLogTimestamp > std::chrono::minutes(1) || sOverlapCount < 0) {
+ AString countMessage = "";
+ if (sOverlapCount > 0) {
+ countMessage = AStringPrintf(
+ "(%d overlapping timestamp detected since last log)", sOverlapCount);
+ }
+ ALOGI("Correcting overlapping timestamp: last frame ended at %lldus but "
+ "current frame is starting at %lldus. Using the last frame's end timestamp %s",
+ lastFrameEndTimestampUs, inputTimestampUs, countMessage.c_str());
+ sLastLogTimestamp = Clock::now();
+ sOverlapCount = 0;
+ } else {
+ ALOGV("Correcting overlapping timestamp: last frame ended at %lldus but "
+ "current frame is starting at %lldus. Using the last frame's end timestamp",
+ lastFrameEndTimestampUs, inputTimestampUs);
+ ++sOverlapCount;
+ }
+}
+
void C2SoftAacEnc::process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) {
@@ -366,9 +390,7 @@
}
c2_cntr64_t inputTimestampUs = work->input.ordinal.timestamp;
if (inputTimestampUs < mLastFrameEndTimestampUs.value_or(inputTimestampUs)) {
- ALOGW("Correcting overlapping timestamp: last frame ended at %lldus but "
- "current frame is starting at %lldus. Using the last frame's end timestamp",
- mLastFrameEndTimestampUs->peekll(), inputTimestampUs.peekll());
+ MaybeLogTimestampWarning(mLastFrameEndTimestampUs->peekll(), inputTimestampUs.peekll());
inputTimestampUs = *mLastFrameEndTimestampUs;
}
if (capacity > 0) {
diff --git a/media/codec2/components/amr_nb_wb/C2SoftAmrDec.cpp b/media/codec2/components/amr_nb_wb/C2SoftAmrDec.cpp
index 6578ad2..f7943be 100644
--- a/media/codec2/components/amr_nb_wb/C2SoftAmrDec.cpp
+++ b/media/codec2/components/amr_nb_wb/C2SoftAmrDec.cpp
@@ -336,11 +336,10 @@
memset(output, 0, outSamples * sizeof(int16_t));
} else {
int16_t FT;
- RX_State_wb rx_state;
int16_t numRecSamples;
mime_unsorting(const_cast<uint8_t *>(&input[1]),
- mInputSampleBuffer, &FT, &FM, 1, &rx_state);
+ mInputSampleBuffer, &FT, &FM, 1, &mRxState);
pvDecoder_AmrWb(FM, mInputSampleBuffer, output, &numRecSamples,
mDecoderBuf, FT, mDecoderCookie);
if (numRecSamples != outSamples) {
diff --git a/media/codec2/components/amr_nb_wb/C2SoftAmrDec.h b/media/codec2/components/amr_nb_wb/C2SoftAmrDec.h
index 6384450..afe1537 100644
--- a/media/codec2/components/amr_nb_wb/C2SoftAmrDec.h
+++ b/media/codec2/components/amr_nb_wb/C2SoftAmrDec.h
@@ -18,6 +18,8 @@
#define ANDROID_C2_SOFT_AMR_DEC_H_
#include <SimpleC2Component.h>
+#include "gsmamr_dec.h"
+#include "pvamrwbdecoder.h"
namespace android {
@@ -51,6 +53,7 @@
void *mAmrHandle;
void *mDecoderBuf;
int16_t *mDecoderCookie;
+ RX_State_wb mRxState{};
int16_t mInputSampleBuffer[477];
diff --git a/media/codec2/components/base/SimpleC2Interface.cpp b/media/codec2/components/base/SimpleC2Interface.cpp
index 5c019f3..29740d1 100644
--- a/media/codec2/components/base/SimpleC2Interface.cpp
+++ b/media/codec2/components/base/SimpleC2Interface.cpp
@@ -39,6 +39,16 @@
setDerivedInstance(this);
addParameter(
+ DefineParam(mApiFeatures, C2_PARAMKEY_API_FEATURES)
+ .withConstValue(new C2ApiFeaturesSetting(C2Config::api_feature_t(
+ API_REFLECTION |
+ API_VALUES |
+ API_CURRENT_VALUES |
+ API_DEPENDENCY |
+ API_SAME_INPUT_BUFFER)))
+ .build());
+
+ addParameter(
DefineParam(mName, C2_PARAMKEY_COMPONENT_NAME)
.withConstValue(AllocSharedString<C2ComponentNameSetting>(name.c_str()))
.build());
@@ -305,7 +315,6 @@
Clients need to handle the following base params due to custom dependency.
std::shared_ptr<C2ApiLevelSetting> mApiLevel;
- std::shared_ptr<C2ApiFeaturesSetting> mApiFeatures;
std::shared_ptr<C2ComponentAttributesSetting> mAttrib;
std::shared_ptr<C2PortSuggestedBufferCountTuning::input> mSuggestedInputBufferCount;
diff --git a/media/codec2/components/vpx/C2SoftVpxEnc.cpp b/media/codec2/components/vpx/C2SoftVpxEnc.cpp
index 74e105e..7e9090f 100644
--- a/media/codec2/components/vpx/C2SoftVpxEnc.cpp
+++ b/media/codec2/components/vpx/C2SoftVpxEnc.cpp
@@ -460,8 +460,8 @@
const C2ConstGraphicBlock inBuffer =
inputBuffer->data().graphicBlocks().front();
- if (inBuffer.width() != mSize->width ||
- inBuffer.height() != mSize->height) {
+ if (inBuffer.width() < mSize->width ||
+ inBuffer.height() < mSize->height) {
ALOGE("unexpected Input buffer attributes %d(%d) x %d(%d)",
inBuffer.width(), mSize->width, inBuffer.height(),
mSize->height);
@@ -472,8 +472,8 @@
bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
vpx_image_t raw_frame;
const C2PlanarLayout &layout = rView->layout();
- uint32_t width = rView->width();
- uint32_t height = rView->height();
+ uint32_t width = mSize->width;
+ uint32_t height = mSize->height;
if (width > 0x8000 || height > 0x8000) {
ALOGE("Image too big: %u x %u", width, height);
work->result = C2_BAD_VALUE;
diff --git a/media/codec2/core/Android.bp b/media/codec2/core/Android.bp
index 1f9d7ab..ce1c9ac 100644
--- a/media/codec2/core/Android.bp
+++ b/media/codec2/core/Android.bp
@@ -1,12 +1,14 @@
cc_library_headers {
name: "libcodec2_headers",
vendor_available: true,
+ min_sdk_version: "29",
export_include_dirs: ["include"],
}
cc_library_shared {
name: "libcodec2",
vendor_available: true,
+ min_sdk_version: "29",
vndk: {
enabled: true,
},
diff --git a/media/codec2/core/include/C2Config.h b/media/codec2/core/include/C2Config.h
index 9fc0e17..29bccd5 100644
--- a/media/codec2/core/include/C2Config.h
+++ b/media/codec2/core/include/C2Config.h
@@ -278,16 +278,19 @@
C2ApiLevelSetting;
constexpr char C2_PARAMKEY_API_LEVEL[] = "api.level";
-enum C2Config::api_feature_t : uint64_t {
+C2ENUM(C2Config::api_feature_t, uint64_t,
API_REFLECTION = (1U << 0), ///< ability to list supported parameters
API_VALUES = (1U << 1), ///< ability to list supported values for each parameter
API_CURRENT_VALUES = (1U << 2), ///< ability to list currently supported values for each parameter
API_DEPENDENCY = (1U << 3), ///< have a defined parameter dependency
+ API_SAME_INPUT_BUFFER = (1U << 16), ///< supporting multiple input buffers
+ ///< backed by the same allocation
+
API_STREAMS = (1ULL << 32), ///< supporting variable number of streams
- API_TUNNELING = (1ULL << 48), ///< tunneling API
-};
+ API_TUNNELING = (1ULL << 48) ///< tunneling API
+)
// read-only
typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Config::api_feature_t>, kParamIndexApiFeatures>
diff --git a/media/codec2/hidl/1.0/utils/Android.bp b/media/codec2/hidl/1.0/utils/Android.bp
index 75c9424..3b73350 100644
--- a/media/codec2/hidl/1.0/utils/Android.bp
+++ b/media/codec2/hidl/1.0/utils/Android.bp
@@ -48,6 +48,7 @@
cc_library {
name: "libcodec2_hidl@1.0",
vendor_available: true,
+ min_sdk_version: "29",
defaults: ["hidl_defaults"],
diff --git a/media/codec2/hidl/1.0/vts/functional/Android.bp b/media/codec2/hidl/1.0/vts/functional/Android.bp
index cd3be81..5ea4825 100644
--- a/media/codec2/hidl/1.0/vts/functional/Android.bp
+++ b/media/codec2/hidl/1.0/vts/functional/Android.bp
@@ -91,6 +91,14 @@
"res/bbb_av1_176_144.info",
"res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9",
"res/bbb_vp9_704x480_280kbps_24fps_altref_2.info",
+ "res/bbb_avc_176x144_300kbps_60fps_chksum.md5",
+ "res/bbb_avc_640x360_768kbps_30fps_chksum.md5",
+ "res/bbb_hevc_176x144_176kbps_60fps_chksum.md5",
+ "res/bbb_hevc_640x360_1600kbps_30fps_chksum.md5",
+ "res/bbb_vp8_640x360_2mbps_30fps_chksm.md5",
+ "res/bbb_vp9_640x360_1600kbps_30fps_chksm.md5",
+ "res/bbb_av1_640_360_chksum.md5",
+ "res/bbb_av1_176_144_chksm.md5",
],
}
diff --git a/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioDecTest.cpp b/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioDecTest.cpp
index 264abba..3a47ae9 100644
--- a/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioDecTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioDecTest.cpp
@@ -22,7 +22,6 @@
#include <hidl/GtestPrinter.h>
#include <stdio.h>
#include <algorithm>
-#include <fstream>
#include <C2AllocatorIon.h>
#include <C2Buffer.h>
@@ -35,15 +34,11 @@
#include "media_c2_hidl_test_common.h"
-struct FrameInfo {
- int bytesCount;
- uint32_t flags;
- int64_t timestamp;
-};
-
static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
kDecodeTestParameters;
+static std::vector<std::tuple<std::string, std::string, std::string>> kCsdFlushTestParameters;
+
// Resource directory
static std::string sResourceDir = "";
@@ -105,6 +100,7 @@
mEos = false;
mFramesReceived = 0;
mTimestampUs = 0u;
+ mWorkResult = C2_OK;
mTimestampDevTest = false;
if (mCompName == unknown_comp) mDisableTest = true;
if (mDisableTest) std::cout << "[ WARN ] Test Disabled \n";
@@ -121,6 +117,8 @@
// Get the test parameters from GetParam call.
virtual void getParams() {}
+ virtual void validateTimestampList(int32_t* bitStreamInfo);
+
struct outputMetaData {
uint64_t timestampUs;
uint32_t rangeLength;
@@ -131,6 +129,7 @@
if (!work->worklets.empty()) {
// For decoder components current timestamp always exceeds
// previous timestamp
+ mWorkResult |= work->result;
bool codecConfig = ((work->worklets.front()->output.flags &
C2FrameData::FLAG_CODEC_CONFIG) != 0);
if (!codecConfig && !work->worklets.front()->output.buffers.empty()) {
@@ -182,6 +181,8 @@
bool mDisableTest;
bool mTimestampDevTest;
standardComp mCompName;
+
+ int32_t mWorkResult;
uint64_t mTimestampUs;
uint32_t mFramesReceived;
std::list<uint64_t> mFlushedIndices;
@@ -457,6 +458,31 @@
}
}
+void Codec2AudioDecHidlTestBase::validateTimestampList(int32_t* bitStreamInfo) {
+ uint32_t samplesReceived = 0;
+ // Update SampleRate and ChannelCount
+ ASSERT_NO_FATAL_FAILURE(getInputChannelInfo(mComponent, mCompName, bitStreamInfo));
+ int32_t nSampleRate = bitStreamInfo[0];
+ int32_t nChannels = bitStreamInfo[1];
+ std::list<uint64_t>::iterator itIn = mTimestampUslist.begin();
+ auto itOut = oBufferMetaData.begin();
+ EXPECT_EQ(*itIn, itOut->timestampUs);
+ uint64_t expectedTimeStamp = *itIn;
+ while (itOut != oBufferMetaData.end()) {
+ EXPECT_EQ(expectedTimeStamp, itOut->timestampUs);
+ if (expectedTimeStamp != itOut->timestampUs) break;
+ // buffer samples = ((total bytes) / (ac * (bits per sample / 8))
+ samplesReceived += ((itOut->rangeLength) / (nChannels * 2));
+ expectedTimeStamp = samplesReceived * 1000000ll / nSampleRate;
+ itOut++;
+ }
+ itIn = mTimestampUslist.end();
+ --itIn;
+ EXPECT_GT(expectedTimeStamp, *itIn);
+ oBufferMetaData.clear();
+ mTimestampUslist.clear();
+}
+
TEST_P(Codec2AudioDecHidlTest, validateCompName) {
if (mDisableTest) GTEST_SKIP() << "Test is disabled";
ALOGV("Checks if the given component is a valid audio component");
@@ -493,7 +519,7 @@
bool signalEOS = !std::get<3>(GetParam()).compare("true");
mTimestampDevTest = true;
char mURL[512], info[512];
- std::ifstream eleStream, eleInfo;
+ android::Vector<FrameInfo> Info;
strcpy(mURL, sResourceDir.c_str());
strcpy(info, sResourceDir.c_str());
@@ -503,21 +529,9 @@
return;
}
- eleInfo.open(info);
- ASSERT_EQ(eleInfo.is_open(), true);
- android::Vector<FrameInfo> Info;
- int bytesCount = 0;
- uint32_t flags = 0;
- uint32_t timestamp = 0;
- while (1) {
- if (!(eleInfo >> bytesCount)) break;
- eleInfo >> flags;
- eleInfo >> timestamp;
- bool codecConfig = ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0;
- if (mTimestampDevTest && !codecConfig) mTimestampUslist.push_back(timestamp);
- Info.push_back({bytesCount, flags, timestamp});
- }
- eleInfo.close();
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file: " << info;
+
// Reset total no of frames received
mFramesReceived = 0;
mTimestampUs = 0;
@@ -534,6 +548,7 @@
}
ASSERT_EQ(mComponent->start(), C2_OK);
ALOGV("mURL : %s", mURL);
+ std::ifstream eleStream;
eleStream.open(mURL, std::ifstream::binary);
ASSERT_EQ(eleStream.is_open(), true);
ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
@@ -550,7 +565,7 @@
}
// blocking call to ensures application to Wait till all the inputs are
// consumed
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
eleStream.close();
if (mFramesReceived != infoSize) {
ALOGE("Input buffer count and Output buffer count mismatch");
@@ -558,32 +573,12 @@
ASSERT_TRUE(false);
}
ASSERT_EQ(mEos, true);
+
if (mTimestampDevTest) {
- uint64_t expTs;
- uint32_t samplesReceived = 0;
- // Update SampleRate and ChannelCount
- ASSERT_NO_FATAL_FAILURE(getInputChannelInfo(mComponent, mCompName, bitStreamInfo));
- int nSampleRate = bitStreamInfo[0];
- int nChannels = bitStreamInfo[1];
- std::list<uint64_t>::iterator itIn = mTimestampUslist.begin();
- auto itOut = oBufferMetaData.begin();
- EXPECT_EQ(*itIn, itOut->timestampUs);
- expTs = *itIn;
- while (itOut != oBufferMetaData.end()) {
- EXPECT_EQ(expTs, itOut->timestampUs);
- if (expTs != itOut->timestampUs) break;
- // buffer samples = ((total bytes) / (ac * (bits per sample / 8))
- samplesReceived += ((itOut->rangeLength) / (nChannels * 2));
- expTs = samplesReceived * 1000000ll / nSampleRate;
- itOut++;
- }
- itIn = mTimestampUslist.end();
- --itIn;
- EXPECT_GT(expTs, *itIn);
- oBufferMetaData.clear();
- mTimestampUslist.clear();
+ validateTimestampList(bitStreamInfo);
}
ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
// thumbnail test
@@ -592,25 +587,15 @@
if (mDisableTest) GTEST_SKIP() << "Test is disabled";
char mURL[512], info[512];
- std::ifstream eleStream, eleInfo;
+ android::Vector<FrameInfo> Info;
strcpy(mURL, sResourceDir.c_str());
strcpy(info, sResourceDir.c_str());
GetURLForComponent(mCompName, mURL, info);
- eleInfo.open(info);
- ASSERT_EQ(eleInfo.is_open(), true);
- android::Vector<FrameInfo> Info;
- int bytesCount = 0;
- uint32_t flags = 0;
- uint32_t timestamp = 0;
- while (1) {
- if (!(eleInfo >> bytesCount)) break;
- eleInfo >> flags;
- eleInfo >> timestamp;
- Info.push_back({bytesCount, flags, timestamp});
- }
- eleInfo.close();
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file: " << info;
+
int32_t bitStreamInfo[2] = {0};
if (mCompName == raw) {
bitStreamInfo[0] = 8000;
@@ -628,22 +613,25 @@
// request EOS for thumbnail
// signal EOS flag with last frame
size_t i = -1;
+ uint32_t flags;
do {
i++;
flags = 0;
if (Info[i].flags) flags = 1u << (Info[i].flags - 1);
} while (!(flags & SYNC_FRAME));
+ std::ifstream eleStream;
eleStream.open(mURL, std::ifstream::binary);
ASSERT_EQ(eleStream.is_open(), true);
ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
mFlushedIndices, mLinearPool, eleStream, &Info, 0,
i + 1));
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
eleStream.close();
EXPECT_GE(mFramesReceived, 1U);
ASSERT_EQ(mEos, true);
ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
TEST_P(Codec2AudioDecHidlTest, EOSTest) {
@@ -684,33 +672,22 @@
ASSERT_EQ(mEos, true);
ASSERT_EQ(mWorkQueue.size(), (size_t)MAX_INPUT_BUFFERS);
ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
TEST_P(Codec2AudioDecHidlTest, FlushTest) {
description("Tests Flush calls");
if (mDisableTest) GTEST_SKIP() << "Test is disabled";
- typedef std::unique_lock<std::mutex> ULock;
char mURL[512], info[512];
- std::ifstream eleStream, eleInfo;
+ android::Vector<FrameInfo> Info;
strcpy(mURL, sResourceDir.c_str());
strcpy(info, sResourceDir.c_str());
GetURLForComponent(mCompName, mURL, info);
- eleInfo.open(info);
- ASSERT_EQ(eleInfo.is_open(), true);
- android::Vector<FrameInfo> Info;
- int bytesCount = 0;
- uint32_t flags = 0;
- uint32_t timestamp = 0;
- mFlushedIndices.clear();
- while (1) {
- if (!(eleInfo >> bytesCount)) break;
- eleInfo >> flags;
- eleInfo >> timestamp;
- Info.push_back({bytesCount, flags, timestamp});
- }
- eleInfo.close();
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file: " << info;
+
int32_t bitStreamInfo[2] = {0};
if (mCompName == raw) {
bitStreamInfo[0] = 8000;
@@ -723,44 +700,37 @@
return;
}
ASSERT_EQ(mComponent->start(), C2_OK);
- ALOGV("mURL : %s", mURL);
- eleStream.open(mURL, std::ifstream::binary);
- ASSERT_EQ(eleStream.is_open(), true);
- // Decode 128 frames and flush. here 128 is chosen to ensure there is a key
- // frame after this so that the below section can be covered for all
- // components
- uint32_t numFramesFlushed = 128;
- ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
- mFlushedIndices, mLinearPool, eleStream, &Info, 0,
- numFramesFlushed, false));
// flush
std::list<std::unique_ptr<C2Work>> flushedWork;
c2_status_t err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
ASSERT_EQ(err, C2_OK);
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
- (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
- uint64_t frameIndex;
- {
- // Update mFlushedIndices based on the index received from flush()
- ULock l(mQueueLock);
- for (std::unique_ptr<C2Work>& work : flushedWork) {
- ASSERT_NE(work, nullptr);
- frameIndex = work->input.ordinal.frameIndex.peeku();
- std::list<uint64_t>::iterator frameIndexIt =
- std::find(mFlushedIndices.begin(), mFlushedIndices.end(), frameIndex);
- if (!mFlushedIndices.empty() && (frameIndexIt != mFlushedIndices.end())) {
- mFlushedIndices.erase(frameIndexIt);
- work->input.buffers.clear();
- work->worklets.clear();
- mWorkQueue.push_back(std::move(work));
- }
- }
- }
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+
+ ALOGV("mURL : %s", mURL);
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+ // Decode 30 frames and flush.
+ uint32_t numFramesFlushed = FLUSH_INTERVAL;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info, 0,
+ numFramesFlushed, false));
+ // flush
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+
// Seek to next key frame and start decoding till the end
mFlushedIndices.clear();
int index = numFramesFlushed;
bool keyFrame = false;
- flags = 0;
+ uint32_t flags = 0;
while (index < (int)Info.size()) {
if (Info[index].flags) flags = 1u << (Info[index].flags - 1);
if ((flags & SYNC_FRAME) == SYNC_FRAME) {
@@ -779,25 +749,13 @@
eleStream.close();
err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
ASSERT_EQ(err, C2_OK);
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
- (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
- {
- // Update mFlushedIndices based on the index received from flush()
- ULock l(mQueueLock);
- for (std::unique_ptr<C2Work>& work : flushedWork) {
- ASSERT_NE(work, nullptr);
- frameIndex = work->input.ordinal.frameIndex.peeku();
- std::list<uint64_t>::iterator frameIndexIt =
- std::find(mFlushedIndices.begin(), mFlushedIndices.end(), frameIndex);
- if (!mFlushedIndices.empty() && (frameIndexIt != mFlushedIndices.end())) {
- mFlushedIndices.erase(frameIndexIt);
- work->input.buffers.clear();
- work->worklets.clear();
- mWorkQueue.push_back(std::move(work));
- }
- }
- }
- ASSERT_EQ(mFlushedIndices.empty(), true);
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ // TODO: (b/154671521)
+ // Add assert for mWorkResult
ASSERT_EQ(mComponent->stop(), C2_OK);
}
@@ -862,7 +820,7 @@
// consumed
if (!mEos) {
ALOGV("Waiting for input consumption");
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
}
eleStream.close();
@@ -875,6 +833,109 @@
ASSERT_EQ(mComponent->stop(), C2_OK);
}
+class Codec2AudioDecCsdInputTests
+ : public Codec2AudioDecHidlTestBase,
+ public ::testing::WithParamInterface<std::tuple<std::string, std::string, std::string>> {
+ void getParams() {
+ mInstanceName = std::get<0>(GetParam());
+ mComponentName = std::get<1>(GetParam());
+ }
+};
+
+// Test the codecs for the following
+// start - csd - data… - (with/without)flush - data… - flush - data…
+TEST_P(Codec2AudioDecCsdInputTests, CSDFlushTest) {
+ description("Tests codecs for flush at different states");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512], info[512];
+ android::Vector<FrameInfo> Info;
+
+ strcpy(mURL, sResourceDir.c_str());
+ strcpy(info, sResourceDir.c_str());
+ GetURLForComponent(mCompName, mURL, info);
+ if (!strcmp(mURL, sResourceDir.c_str())) {
+ ALOGV("EMPTY INPUT sResourceDir.c_str() %s mURL %s ", sResourceDir.c_str(), mURL);
+ return;
+ }
+ ALOGV("mURL : %s", mURL);
+
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file";
+
+ int32_t bitStreamInfo[2] = {0};
+ if (mCompName == raw) {
+ bitStreamInfo[0] = 8000;
+ bitStreamInfo[1] = 1;
+ } else {
+ ASSERT_NO_FATAL_FAILURE(getInputChannelInfo(mComponent, mCompName, bitStreamInfo));
+ }
+ if (!setupConfigParam(mComponent, bitStreamInfo)) {
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ ASSERT_EQ(mComponent->start(), C2_OK);
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+
+ bool signalEOS = false;
+ bool flushCsd = !std::get<2>(GetParam()).compare("true");
+ ALOGV("sending %d csd data ", numCsds);
+ int framesToDecode = numCsds;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info, 0,
+ framesToDecode, false));
+
+ c2_status_t err = C2_OK;
+ std::list<std::unique_ptr<C2Work>> flushedWork;
+ if (numCsds && flushCsd) {
+ // We wait for all the CSD buffers to get consumed.
+ // Once we have received all CSD work back, we call flush
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ oBufferMetaData.clear();
+ }
+
+ int offset = framesToDecode;
+ while (1) {
+ framesToDecode = c2_min(FLUSH_INTERVAL, (int)Info.size() - offset);
+ if (framesToDecode < FLUSH_INTERVAL) signalEOS = true;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info,
+ offset, framesToDecode, signalEOS));
+ offset += framesToDecode;
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ // blocking call to ensures application to Wait till remaining
+ // 'non-flushed' inputs are consumed
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ if (signalEOS || offset >= (int)Info.size()) {
+ break;
+ }
+ }
+ if (!signalEOS) {
+ ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
+ C2FrameData::FLAG_END_OF_STREAM, false));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+ }
+ eleStream.close();
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+}
+
INSTANTIATE_TEST_SUITE_P(PerInstance, Codec2AudioDecHidlTest, testing::ValuesIn(kTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
@@ -883,6 +944,10 @@
testing::ValuesIn(kDecodeTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
+INSTANTIATE_TEST_SUITE_P(CsdInputs, Codec2AudioDecCsdInputTests,
+ testing::ValuesIn(kCsdFlushTestParameters),
+ android::hardware::PrintInstanceTupleNameToString<>);
+
} // anonymous namespace
int main(int argc, char** argv) {
@@ -896,6 +961,11 @@
std::make_tuple(std::get<0>(params), std::get<1>(params), "1", "false"));
kDecodeTestParameters.push_back(
std::make_tuple(std::get<0>(params), std::get<1>(params), "1", "true"));
+
+ kCsdFlushTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "true"));
+ kCsdFlushTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "false"));
}
// Set the resource directory based on command line args.
@@ -909,4 +979,4 @@
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
-}
\ No newline at end of file
+}
diff --git a/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioEncTest.cpp b/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioEncTest.cpp
index 5f3ae41..e3a4f68 100644
--- a/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioEncTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioEncTest.cpp
@@ -95,6 +95,8 @@
mEos = false;
mCsd = false;
mFramesReceived = 0;
+ mWorkResult = C2_OK;
+ mOutputSize = 0u;
if (mCompName == unknown_comp) mDisableTest = true;
if (mDisableTest) std::cout << "[ WARN ] Test Disabled \n";
getInputMaxBufSize();
@@ -115,6 +117,17 @@
void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) {
for (std::unique_ptr<C2Work>& work : workItems) {
if (!work->worklets.empty()) {
+ mWorkResult |= work->result;
+ if (!work->worklets.front()->output.buffers.empty()) {
+ mOutputSize += work->worklets.front()
+ ->output.buffers[0]
+ ->data()
+ .linearBlocks()
+ .front()
+ .map()
+ .get()
+ .capacity();
+ }
workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue,
mEos, mCsd, mFramesReceived);
}
@@ -135,8 +148,11 @@
bool mCsd;
bool mDisableTest;
standardComp mCompName;
+
+ int32_t mWorkResult;
uint32_t mFramesReceived;
int32_t mInputMaxBufSize;
+ uint64_t mOutputSize;
std::list<uint64_t> mFlushedIndices;
C2BlockPool::local_id_t mBlockPoolId;
@@ -236,6 +252,41 @@
return false;
}
+// Get config params for a component
+bool getConfigParams(Codec2AudioEncHidlTest::standardComp compName, int32_t* nChannels,
+ int32_t* nSampleRate, int32_t* samplesPerFrame) {
+ switch (compName) {
+ case Codec2AudioEncHidlTest::aac:
+ *nChannels = 2;
+ *nSampleRate = 48000;
+ *samplesPerFrame = 1024;
+ break;
+ case Codec2AudioEncHidlTest::flac:
+ *nChannels = 2;
+ *nSampleRate = 48000;
+ *samplesPerFrame = 1152;
+ break;
+ case Codec2AudioEncHidlTest::opus:
+ *nChannels = 2;
+ *nSampleRate = 48000;
+ *samplesPerFrame = 960;
+ break;
+ case Codec2AudioEncHidlTest::amrnb:
+ *nChannels = 1;
+ *nSampleRate = 8000;
+ *samplesPerFrame = 160;
+ break;
+ case Codec2AudioEncHidlTest::amrwb:
+ *nChannels = 1;
+ *nSampleRate = 16000;
+ *samplesPerFrame = 160;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
// LookUpTable of clips and metadata for component testing
void GetURLForComponent(Codec2AudioEncHidlTest::standardComp comp, char* mURL) {
struct CompToURL {
@@ -367,36 +418,18 @@
bool signalEOS = !std::get<2>(GetParam()).compare("true");
// Ratio w.r.t to mInputMaxBufSize
int32_t inputMaxBufRatio = std::stoi(std::get<3>(GetParam()));
- ;
- // Setting default sampleRate
- int32_t nChannels = 2;
- int32_t nSampleRate = 44100;
- switch (mCompName) {
- case aac:
- nChannels = 2;
- nSampleRate = 48000;
- break;
- case flac:
- nChannels = 2;
- nSampleRate = 48000;
- break;
- case opus:
- nChannels = 2;
- nSampleRate = 48000;
- break;
- case amrnb:
- nChannels = 1;
- nSampleRate = 8000;
- break;
- case amrwb:
- nChannels = 1;
- nSampleRate = 16000;
- break;
- default:
- ASSERT_TRUE(false);
+ int32_t nChannels;
+ int32_t nSampleRate;
+ int32_t samplesPerFrame;
+
+ if (!getConfigParams(mCompName, &nChannels, &nSampleRate, &samplesPerFrame)) {
+ std::cout << "Failed to get the config params for " << mCompName << " component\n";
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
}
- int32_t samplesPerFrame = ((mInputMaxBufSize / inputMaxBufRatio) / (nChannels * 2));
+
+ samplesPerFrame = ((mInputMaxBufSize / inputMaxBufRatio) / (nChannels * 2));
ALOGV("signalEOS %d mInputMaxBufSize %d samplesPerFrame %d", signalEOS, mInputMaxBufSize,
samplesPerFrame);
@@ -416,7 +449,7 @@
// If EOS is not sent, sending empty input with EOS flag
if (!signalEOS) {
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, 1));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, 1);
ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
C2FrameData::FLAG_END_OF_STREAM, false));
numFrames += 1;
@@ -424,7 +457,7 @@
// blocking call to ensures application to Wait till all the inputs are
// consumed
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
eleStream.close();
if (mFramesReceived != numFrames) {
ALOGE("Input buffer count and Output buffer count mismatch");
@@ -439,6 +472,7 @@
}
ASSERT_EQ(mEos, true);
ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
TEST_P(Codec2AudioEncHidlTest, EOSTest) {
@@ -479,50 +513,26 @@
}
ASSERT_EQ(mEos, true);
ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
TEST_P(Codec2AudioEncHidlTest, FlushTest) {
description("Test Request for flush");
if (mDisableTest) GTEST_SKIP() << "Test is disabled";
- typedef std::unique_lock<std::mutex> ULock;
char mURL[512];
strcpy(mURL, sResourceDir.c_str());
GetURLForComponent(mCompName, mURL);
- // Setting default configuration
mFlushedIndices.clear();
- int32_t nChannels = 2;
- int32_t nSampleRate = 44100;
- int32_t samplesPerFrame = 1024;
- switch (mCompName) {
- case aac:
- nChannels = 2;
- nSampleRate = 48000;
- samplesPerFrame = 1024;
- break;
- case flac:
- nChannels = 2;
- nSampleRate = 48000;
- samplesPerFrame = 1152;
- break;
- case opus:
- nChannels = 2;
- nSampleRate = 48000;
- samplesPerFrame = 960;
- break;
- case amrnb:
- nChannels = 1;
- nSampleRate = 8000;
- samplesPerFrame = 160;
- break;
- case amrwb:
- nChannels = 1;
- nSampleRate = 16000;
- samplesPerFrame = 160;
- break;
- default:
- ASSERT_TRUE(false);
+ int32_t nChannels;
+ int32_t nSampleRate;
+ int32_t samplesPerFrame;
+
+ if (!getConfigParams(mCompName, &nChannels, &nSampleRate, &samplesPerFrame)) {
+ std::cout << "Failed to get the config params for " << mCompName << " component\n";
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
}
if (!setupConfigParam(mComponent, nChannels, nSampleRate)) {
@@ -536,33 +546,24 @@
uint32_t numFrames = 128;
eleStream.open(mURL, std::ifstream::binary);
ASSERT_EQ(eleStream.is_open(), true);
+ // flush
+ std::list<std::unique_ptr<C2Work>> flushedWork;
+ c2_status_t err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
ALOGV("mURL : %s", mURL);
ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
mFlushedIndices, mLinearPool, eleStream, numFramesFlushed,
samplesPerFrame, nChannels, nSampleRate));
- std::list<std::unique_ptr<C2Work>> flushedWork;
- c2_status_t err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
ASSERT_EQ(err, C2_OK);
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
- (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
- uint64_t frameIndex;
- {
- // Update mFlushedIndices based on the index received from flush()
- ULock l(mQueueLock);
- for (std::unique_ptr<C2Work>& work : flushedWork) {
- ASSERT_NE(work, nullptr);
- frameIndex = work->input.ordinal.frameIndex.peeku();
- std::list<uint64_t>::iterator frameIndexIt =
- std::find(mFlushedIndices.begin(), mFlushedIndices.end(), frameIndex);
- if (!mFlushedIndices.empty() && (frameIndexIt != mFlushedIndices.end())) {
- mFlushedIndices.erase(frameIndexIt);
- work->input.buffers.clear();
- work->worklets.clear();
- mWorkQueue.push_back(std::move(work));
- }
- }
- }
- mFlushedIndices.clear();
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
mFlushedIndices, mLinearPool, eleStream,
numFrames - numFramesFlushed, samplesPerFrame, nChannels,
@@ -570,28 +571,220 @@
eleStream.close();
err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
ASSERT_EQ(err, C2_OK);
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
- (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
- {
- // Update mFlushedIndices based on the index received from flush()
- ULock l(mQueueLock);
- for (std::unique_ptr<C2Work>& work : flushedWork) {
- ASSERT_NE(work, nullptr);
- frameIndex = work->input.ordinal.frameIndex.peeku();
- std::list<uint64_t>::iterator frameIndexIt =
- std::find(mFlushedIndices.begin(), mFlushedIndices.end(), frameIndex);
- if (!mFlushedIndices.empty() && (frameIndexIt != mFlushedIndices.end())) {
- mFlushedIndices.erase(frameIndexIt);
- work->input.buffers.clear();
- work->worklets.clear();
- mWorkQueue.push_back(std::move(work));
- }
- }
- }
- ASSERT_EQ(mFlushedIndices.empty(), true);
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ // TODO: (b/154671521)
+ // Add assert for mWorkResult
ASSERT_EQ(mComponent->stop(), C2_OK);
}
+TEST_P(Codec2AudioEncHidlTest, MultiChannelCountTest) {
+ description("Encodes input file for different channel count");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512];
+ strcpy(mURL, sResourceDir.c_str());
+ GetURLForComponent(mCompName, mURL);
+
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true) << mURL << " file not found";
+ ALOGV("mURL : %s", mURL);
+
+ int32_t nSampleRate;
+ int32_t samplesPerFrame;
+ int32_t nChannels;
+ int32_t numFrames = 16;
+ int32_t maxChannelCount = 8;
+
+ if (!getConfigParams(mCompName, &nChannels, &nSampleRate, &samplesPerFrame)) {
+ std::cout << "Failed to get the config params for " << mCompName << " component\n";
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ uint64_t prevOutputSize = 0u;
+ uint32_t prevChannelCount = 0u;
+
+ // Looping through the maximum number of channel count supported by encoder
+ for (nChannels = 1; nChannels < maxChannelCount; nChannels++) {
+ ALOGV("Configuring %u encoder for channel count = %d", mCompName, nChannels);
+ if (!setupConfigParam(mComponent, nChannels, nSampleRate)) {
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ std::vector<std::unique_ptr<C2Param>> inParams;
+ c2_status_t c2_status = mComponent->query({}, {C2StreamChannelCountInfo::input::PARAM_TYPE},
+ C2_DONT_BLOCK, &inParams);
+ ASSERT_TRUE(!c2_status && inParams.size())
+ << "Query configured channelCount failed => %d" << c2_status;
+
+ size_t offset = sizeof(C2Param);
+ C2Param* param = inParams[0].get();
+ int32_t channelCount = *(int32_t*)((uint8_t*)param + offset);
+ if (channelCount != nChannels) {
+ std::cout << "[ WARN ] Test Skipped for ChannelCount " << nChannels << "\n";
+ continue;
+ }
+
+ // To check if the input stream is sufficient to encode for the higher channel count
+ int32_t bytesCount = (samplesPerFrame * nChannels * 2) * numFrames;
+ if (eleStream.gcount() < bytesCount) {
+ std::cout << "[ WARN ] Test Skipped for ChannelCount " << nChannels
+ << " because of insufficient input data\n";
+ continue;
+ }
+
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, numFrames,
+ samplesPerFrame, nChannels, nSampleRate));
+
+ // mDisableTest will be set if buffer was not fetched properly.
+ // This may happen when config params is not proper but config succeeded
+ // In this cases, we skip encoding the input stream
+ if (mDisableTest) {
+ std::cout << "[ WARN ] Test Disabled for ChannelCount " << nChannels << "\n";
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ return;
+ }
+
+ // blocking call to ensures application to Wait till all the inputs are consumed
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ // Validate output size based on chosen ChannelCount
+ EXPECT_GE(mOutputSize, prevOutputSize);
+
+ prevChannelCount = nChannels;
+ prevOutputSize = mOutputSize;
+
+ if (mFramesReceived != numFrames) {
+ ALOGE("Input buffer count and Output buffer count mismatch");
+ ALOGE("framesReceived : %d inputFrames : %u", mFramesReceived, numFrames);
+ ASSERT_TRUE(false);
+ }
+ if ((mCompName == flac || mCompName == opus || mCompName == aac)) {
+ ASSERT_TRUE(mCsd) << "CSD buffer missing";
+ }
+ ASSERT_TRUE(mEos);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ mFramesReceived = 0;
+ mOutputSize = 0;
+ mEos = false;
+ mCsd = false;
+ eleStream.seekg(0, eleStream.beg);
+ }
+}
+
+TEST_P(Codec2AudioEncHidlTest, MultiSampleRateTest) {
+ description("Encodes input file for different SampleRate");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512];
+ strcpy(mURL, sResourceDir.c_str());
+ GetURLForComponent(mCompName, mURL);
+
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true) << mURL << " file not found";
+ ALOGV("mURL : %s", mURL);
+
+ int32_t nSampleRate;
+ int32_t samplesPerFrame;
+ int32_t nChannels;
+ int32_t numFrames = 16;
+
+ if (!getConfigParams(mCompName, &nChannels, &nSampleRate, &samplesPerFrame)) {
+ std::cout << "Failed to get the config params for " << mCompName << " component\n";
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ int32_t sampleRateValues[] = {1000, 8000, 16000, 24000, 48000, 96000, 192000};
+
+ uint64_t prevOutputSize = 0u;
+ uint32_t prevSampleRate = 0u;
+
+ for (int32_t nSampleRate : sampleRateValues) {
+ ALOGV("Configuring %u encoder for SampleRate = %d", mCompName, nSampleRate);
+ if (!setupConfigParam(mComponent, nChannels, nSampleRate)) {
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ std::vector<std::unique_ptr<C2Param>> inParams;
+ c2_status_t c2_status = mComponent->query({}, {C2StreamSampleRateInfo::input::PARAM_TYPE},
+ C2_DONT_BLOCK, &inParams);
+
+ ASSERT_TRUE(!c2_status && inParams.size())
+ << "Query configured SampleRate failed => %d" << c2_status;
+ size_t offset = sizeof(C2Param);
+ C2Param* param = inParams[0].get();
+ int32_t configuredSampleRate = *(int32_t*)((uint8_t*)param + offset);
+
+ if (configuredSampleRate != nSampleRate) {
+ std::cout << "[ WARN ] Test Skipped for SampleRate " << nSampleRate << "\n";
+ continue;
+ }
+
+ // To check if the input stream is sufficient to encode for the higher SampleRate
+ int32_t bytesCount = (samplesPerFrame * nChannels * 2) * numFrames;
+ if (eleStream.gcount() < bytesCount) {
+ std::cout << "[ WARN ] Test Skipped for SampleRate " << nSampleRate
+ << " because of insufficient input data\n";
+ continue;
+ }
+
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, numFrames,
+ samplesPerFrame, nChannels, nSampleRate));
+
+ // mDisableTest will be set if buffer was not fetched properly.
+ // This may happen when config params is not proper but config succeeded
+ // In this case, we skip encoding the input stream
+ if (mDisableTest) {
+ std::cout << "[ WARN ] Test Disabled for SampleRate" << nSampleRate << "\n";
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ return;
+ }
+
+ // blocking call to ensures application to Wait till all the inputs are consumed
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ // Validate output size based on chosen samplerate
+ if (prevSampleRate >= nSampleRate) {
+ EXPECT_LE(mOutputSize, prevOutputSize);
+ } else {
+ EXPECT_GT(mOutputSize, prevOutputSize);
+ }
+ prevSampleRate = nSampleRate;
+ prevOutputSize = mOutputSize;
+
+ if (mFramesReceived != numFrames) {
+ ALOGE("Input buffer count and Output buffer count mismatch");
+ ALOGE("framesReceived : %d inputFrames : %u", mFramesReceived, numFrames);
+ ASSERT_TRUE(false);
+ }
+ if ((mCompName == flac || mCompName == opus || mCompName == aac)) {
+ ASSERT_TRUE(mCsd) << "CSD buffer missing";
+ }
+ ASSERT_TRUE(mEos);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ mFramesReceived = 0;
+ mOutputSize = 0;
+ mEos = false;
+ mCsd = false;
+ eleStream.seekg(0, eleStream.beg);
+ }
+}
+
INSTANTIATE_TEST_SUITE_P(PerInstance, Codec2AudioEncHidlTest, testing::ValuesIn(kTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
diff --git a/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp b/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
index da8225c..0251ec2 100644
--- a/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
@@ -92,7 +92,10 @@
for (size_t i = 0; i < updates.size(); ++i) {
C2Param* param = updates[i].get();
if (param->index() == C2StreamInitDataInfo::output::PARAM_TYPE) {
- csd = true;
+ C2StreamInitDataInfo::output* csdBuffer =
+ (C2StreamInitDataInfo::output*)(param);
+ size_t csdSize = csdBuffer->flexCount();
+ if (csdSize > 0) csd = true;
} else if ((param->index() == C2StreamSampleRateInfo::output::PARAM_TYPE) ||
(param->index() == C2StreamChannelCountInfo::output::PARAM_TYPE) ||
(param->index() == C2StreamPictureSizeInfo::output::PARAM_TYPE)) {
@@ -160,4 +163,57 @@
}
return parameters;
-}
\ No newline at end of file
+}
+
+// Populate Info vector and return number of CSDs
+int32_t populateInfoVector(std::string info, android::Vector<FrameInfo>* frameInfo,
+ bool timestampDevTest, std::list<uint64_t>* timestampUslist) {
+ std::ifstream eleInfo;
+ eleInfo.open(info);
+ if (!eleInfo.is_open()) {
+ ALOGE("Can't open info file");
+ return -1;
+ }
+ int32_t numCsds = 0;
+ int32_t bytesCount = 0;
+ uint32_t flags = 0;
+ uint32_t timestamp = 0;
+ while (1) {
+ if (!(eleInfo >> bytesCount)) break;
+ eleInfo >> flags;
+ eleInfo >> timestamp;
+ bool codecConfig = flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0;
+ if (codecConfig) numCsds++;
+ bool nonDisplayFrame = ((flags & FLAG_NON_DISPLAY_FRAME) != 0);
+ if (timestampDevTest && !codecConfig && !nonDisplayFrame) {
+ timestampUslist->push_back(timestamp);
+ }
+ frameInfo->push_back({bytesCount, flags, timestamp});
+ }
+ ALOGV("numCsds : %d", numCsds);
+ eleInfo.close();
+ return numCsds;
+}
+
+void verifyFlushOutput(std::list<std::unique_ptr<C2Work>>& flushedWork,
+ std::list<std::unique_ptr<C2Work>>& workQueue,
+ std::list<uint64_t>& flushedIndices, std::mutex& queueLock) {
+ // Update mFlushedIndices based on the index received from flush()
+ typedef std::unique_lock<std::mutex> ULock;
+ uint64_t frameIndex;
+ ULock l(queueLock);
+ for (std::unique_ptr<C2Work>& work : flushedWork) {
+ ASSERT_NE(work, nullptr);
+ frameIndex = work->input.ordinal.frameIndex.peeku();
+ std::list<uint64_t>::iterator frameIndexIt =
+ std::find(flushedIndices.begin(), flushedIndices.end(), frameIndex);
+ if (!flushedIndices.empty() && (frameIndexIt != flushedIndices.end())) {
+ flushedIndices.erase(frameIndexIt);
+ work->input.buffers.clear();
+ work->worklets.clear();
+ workQueue.push_back(std::move(work));
+ }
+ }
+ ASSERT_EQ(flushedIndices.empty(), true);
+ flushedWork.clear();
+}
diff --git a/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.h b/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.h
index 4b5e0a6..50e3ac5 100644
--- a/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.h
+++ b/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.h
@@ -25,10 +25,13 @@
#include <gtest/gtest.h>
#include <hidl/HidlSupport.h>
#include <chrono>
+#include <fstream>
+#define FLAG_NON_DISPLAY_FRAME (1 << 4)
#define MAX_RETRY 20
#define TIME_OUT 400ms
#define MAX_INPUT_BUFFERS 8
+#define FLUSH_INTERVAL 30
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
@@ -39,6 +42,12 @@
static std::vector<std::tuple<std::string, std::string>> kTestParameters;
+struct FrameInfo {
+ int bytesCount;
+ uint32_t flags;
+ int64_t timestamp;
+};
+
/*
* Handle Callback functions onWorkDone(), onTripped(),
* onError(), onDeath(), onFramesRendered()
@@ -123,4 +132,10 @@
int64_t getNowUs();
+int32_t populateInfoVector(std::string info, android::Vector<FrameInfo>* frameInfo,
+ bool timestampDevTest, std::list<uint64_t>* timestampUslist);
+
+void verifyFlushOutput(std::list<std::unique_ptr<C2Work>>& flushedWork,
+ std::list<std::unique_ptr<C2Work>>& workQueue,
+ std::list<uint64_t>& flushedIndices, std::mutex& queueLock);
#endif // MEDIA_C2_HIDL_TEST_COMMON_H
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_176_144_chksm.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_176_144_chksm.md5
new file mode 100644
index 0000000..cb69709
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_176_144_chksm.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_640_360_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_640_360_chksum.md5
new file mode 100644
index 0000000..2693071
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_640_360_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_176x144_300kbps_60fps_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_176x144_300kbps_60fps_chksum.md5
new file mode 100644
index 0000000..5c802d9
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_176x144_300kbps_60fps_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_640x360_768kbps_30fps_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_640x360_768kbps_30fps_chksum.md5
new file mode 100644
index 0000000..073f8eb
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_640x360_768kbps_30fps_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_176x144_176kbps_60fps_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_176x144_176kbps_60fps_chksum.md5
new file mode 100644
index 0000000..83f11c0
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_176x144_176kbps_60fps_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_640x360_1600kbps_30fps_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_640x360_1600kbps_30fps_chksum.md5
new file mode 100644
index 0000000..3344881
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_640x360_1600kbps_30fps_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_vp8_640x360_2mbps_30fps_chksm.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp8_640x360_2mbps_30fps_chksm.md5
new file mode 100644
index 0000000..738b1da
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp8_640x360_2mbps_30fps_chksm.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_640x360_1600kbps_30fps_chksm.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_640x360_1600kbps_30fps_chksm.md5
new file mode 100644
index 0000000..a52faf2
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_640x360_1600kbps_30fps_chksm.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/video/Android.bp b/media/codec2/hidl/1.0/vts/functional/video/Android.bp
index 760f4da..c7b0c12 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/Android.bp
+++ b/media/codec2/hidl/1.0/vts/functional/video/Android.bp
@@ -26,6 +26,7 @@
"libbinder",
"libgui",
"libutils",
+ "libcrypto",
],
data: [":media_c2_v1_video_decode_res"],
test_config: "VtsHalMediaC2V1_0TargetVideoDecTest.xml",
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
index f216429..12ed725 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
@@ -21,7 +21,8 @@
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <stdio.h>
-#include <fstream>
+
+#include <openssl/md5.h>
#include <C2AllocatorIon.h>
#include <C2Buffer.h>
@@ -39,15 +40,11 @@
#include "media_c2_hidl_test_common.h"
#include "media_c2_video_hidl_test_common.h"
-struct FrameInfo {
- int bytesCount;
- uint32_t flags;
- int64_t timestamp;
-};
-
static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
kDecodeTestParameters;
+static std::vector<std::tuple<std::string, std::string, std::string>> kCsdFlushTestParameters;
+
// Resource directory
static std::string sResourceDir = "";
@@ -111,7 +108,12 @@
mEos = false;
mFramesReceived = 0;
mTimestampUs = 0u;
+ mWorkResult = C2_OK;
+ mReorderDepth = -1;
mTimestampDevTest = false;
+ mMd5Offset = 0;
+ mMd5Enable = false;
+ mRefMd5 = nullptr;
if (mCompName == unknown_comp) mDisableTest = true;
C2SecureModeTuning secureModeTuning{};
@@ -134,43 +136,130 @@
// Get the test parameters from GetParam call.
virtual void getParams() {}
+ /* Calculate the CKSUM for the data in inbuf */
+ void calc_md5_cksum(uint8_t* pu1_inbuf, uint32_t u4_stride, uint32_t u4_width,
+ uint32_t u4_height, uint8_t* pu1_cksum_p) {
+ int32_t row;
+ MD5_CTX s_md5_context;
+ MD5_Init(&s_md5_context);
+ for (row = 0; row < u4_height; row++) {
+ MD5_Update(&s_md5_context, pu1_inbuf, u4_width);
+ pu1_inbuf += u4_stride;
+ }
+ MD5_Final(pu1_cksum_p, &s_md5_context);
+ }
+
+ void compareMd5Chksm(std::unique_ptr<C2Work>& work) {
+ uint8_t chksum[48];
+ uint8_t* au1_y_chksum = chksum;
+ uint8_t* au1_u_chksum = chksum + 16;
+ uint8_t* au1_v_chksum = chksum + 32;
+ const C2GraphicView output = work->worklets.front()
+ ->output.buffers[0]
+ ->data()
+ .graphicBlocks()
+ .front()
+ .map()
+ .get();
+ uint8_t* yPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_Y]);
+ uint8_t* uPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_U]);
+ uint8_t* vPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_V]);
+ C2PlanarLayout layout = output.layout();
+
+ size_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc;
+ size_t uvStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc;
+ size_t colInc = layout.planes[C2PlanarLayout::PLANE_U].colInc;
+ size_t bitDepth = layout.planes[C2PlanarLayout::PLANE_Y].bitDepth;
+ uint32_t layoutType = layout.type;
+ size_t cropWidth = output.crop().width;
+ size_t cropHeight = output.crop().height;
+
+ if (bitDepth == 8 && layoutType == C2PlanarLayout::TYPE_YUV && colInc == 1) {
+ calc_md5_cksum(yPlane, yStride, cropWidth, cropHeight, au1_y_chksum);
+ calc_md5_cksum(uPlane, uvStride, cropWidth / 2, cropHeight / 2, au1_u_chksum);
+ calc_md5_cksum(vPlane, uvStride, cropWidth / 2, cropHeight / 2, au1_v_chksum);
+ } else if (bitDepth == 8 && layoutType == C2PlanarLayout::TYPE_YUV && colInc == 2) {
+ uint8_t* cbPlane = (uint8_t*)malloc(cropWidth * cropHeight / 4);
+ uint8_t* crPlane = (uint8_t*)malloc(cropWidth * cropHeight / 4);
+ ASSERT_NE(cbPlane, nullptr);
+ ASSERT_NE(crPlane, nullptr);
+ size_t count = 0;
+ for (size_t k = 0; k < (cropHeight / 2); k++) {
+ for (size_t l = 0; l < (cropWidth); l = l + 2) {
+ cbPlane[count] = uPlane[k * uvStride + l];
+ crPlane[count] = vPlane[k * uvStride + l];
+ count++;
+ }
+ }
+ calc_md5_cksum(yPlane, yStride, cropWidth, cropHeight, au1_y_chksum);
+ calc_md5_cksum(cbPlane, cropWidth / 2, cropWidth / 2, cropHeight / 2, au1_u_chksum);
+ calc_md5_cksum(crPlane, cropWidth / 2, cropWidth / 2, cropHeight / 2, au1_v_chksum);
+ free(cbPlane);
+ free(crPlane);
+ } else {
+ mMd5Enable = false;
+ ALOGV("Disabling MD5 chksm flag");
+ return;
+ }
+ if (memcmp(mRefMd5 + mMd5Offset, chksum, 48)) ASSERT_TRUE(false);
+ mMd5Offset += 48;
+ return;
+ }
+ bool configPixelFormat(uint32_t format);
+
// callback function to process onWorkDone received by Listener
void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) {
for (std::unique_ptr<C2Work>& work : workItems) {
if (!work->worklets.empty()) {
// For decoder components current timestamp always exceeds
- // previous timestamp
+ // previous timestamp if output is in display order
typedef std::unique_lock<std::mutex> ULock;
+ mWorkResult |= work->result;
bool codecConfig = ((work->worklets.front()->output.flags &
C2FrameData::FLAG_CODEC_CONFIG) != 0);
if (!codecConfig && !work->worklets.front()->output.buffers.empty()) {
- EXPECT_GE((work->worklets.front()->output.ordinal.timestamp.peeku()),
- mTimestampUs);
- mTimestampUs = work->worklets.front()->output.ordinal.timestamp.peeku();
-
- ULock l(mQueueLock);
- if (mTimestampDevTest) {
- bool tsHit = false;
- std::list<uint64_t>::iterator it = mTimestampUslist.begin();
- while (it != mTimestampUslist.end()) {
- if (*it == mTimestampUs) {
- mTimestampUslist.erase(it);
- tsHit = true;
- break;
- }
- it++;
+ if (mReorderDepth < 0) {
+ C2PortReorderBufferDepthTuning::output reorderBufferDepth;
+ mComponent->query({&reorderBufferDepth}, {}, C2_MAY_BLOCK,
+ nullptr);
+ mReorderDepth = reorderBufferDepth.value;
+ if (mReorderDepth > 0) {
+ // TODO: Add validation for reordered output
+ mTimestampDevTest = false;
}
- if (tsHit == false) {
- if (mTimestampUslist.empty() == false) {
- EXPECT_EQ(tsHit, true) << "TimeStamp not recognized";
- } else {
- std::cout << "[ INFO ] Received non-zero "
- "output / TimeStamp not recognized \n";
+ }
+ if (mTimestampDevTest) {
+ EXPECT_GE((work->worklets.front()->output.ordinal.timestamp.peeku()),
+ mTimestampUs);
+ mTimestampUs = work->worklets.front()->output.ordinal.timestamp.peeku();
+
+ ULock l(mQueueLock);
+ {
+ bool tsHit = false;
+ std::list<uint64_t>::iterator it = mTimestampUslist.begin();
+ while (it != mTimestampUslist.end()) {
+ if (*it == mTimestampUs) {
+ mTimestampUslist.erase(it);
+ tsHit = true;
+ break;
+ }
+ it++;
+ }
+ if (tsHit == false) {
+ if (mTimestampUslist.empty() == false) {
+ EXPECT_EQ(tsHit, true) << "TimeStamp not recognized";
+ } else {
+ std::cout << "[ INFO ] Received non-zero "
+ "output / TimeStamp not recognized \n";
+ }
}
}
}
+ if (mMd5Enable) {
+ compareMd5Chksm(work);
+ }
}
- bool mCsd;
+ bool mCsd = false;
workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue,
mEos, mCsd, mFramesReceived);
(void)mCsd;
@@ -195,11 +284,17 @@
bool mEos;
bool mDisableTest;
+ bool mMd5Enable;
bool mTimestampDevTest;
uint64_t mTimestampUs;
+ uint64_t mMd5Offset;
+ char* mRefMd5;
std::list<uint64_t> mTimestampUslist;
std::list<uint64_t> mFlushedIndices;
standardComp mCompName;
+
+ int32_t mWorkResult;
+ int32_t mReorderDepth;
uint32_t mFramesReceived;
C2BlockPool::local_id_t mBlockPoolId;
std::shared_ptr<C2BlockPool> mLinearPool;
@@ -268,54 +363,73 @@
// number of elementary streams per component
#define STREAM_COUNT 3
-// LookUpTable of clips and metadata for component testing
-void GetURLForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, char* info,
- size_t streamIndex = 1) {
+// LookUpTable of clips, metadata and chksum for component testing
+void GetURLChksmForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, char* info,
+ char* chksum, size_t streamIndex = 1) {
struct CompToURL {
Codec2VideoDecHidlTest::standardComp comp;
const char mURL[STREAM_COUNT][512];
const char info[STREAM_COUNT][512];
+ const char chksum[STREAM_COUNT][512];
};
ASSERT_TRUE(streamIndex < STREAM_COUNT);
static const CompToURL kCompToURL[] = {
{Codec2VideoDecHidlTest::standardComp::avc,
{"bbb_avc_176x144_300kbps_60fps.h264", "bbb_avc_640x360_768kbps_30fps.h264", ""},
- {"bbb_avc_176x144_300kbps_60fps.info", "bbb_avc_640x360_768kbps_30fps.info", ""}},
+ {"bbb_avc_176x144_300kbps_60fps.info", "bbb_avc_640x360_768kbps_30fps.info", ""},
+ {"bbb_avc_176x144_300kbps_60fps_chksum.md5",
+ "bbb_avc_640x360_768kbps_30fps_chksum.md5", ""}},
{Codec2VideoDecHidlTest::standardComp::hevc,
{"bbb_hevc_176x144_176kbps_60fps.hevc", "bbb_hevc_640x360_1600kbps_30fps.hevc", ""},
- {"bbb_hevc_176x144_176kbps_60fps.info", "bbb_hevc_640x360_1600kbps_30fps.info", ""}},
+ {"bbb_hevc_176x144_176kbps_60fps.info", "bbb_hevc_640x360_1600kbps_30fps.info", ""},
+ {"bbb_hevc_176x144_176kbps_60fps_chksum.md5",
+ "bbb_hevc_640x360_1600kbps_30fps_chksum.md5", ""}},
{Codec2VideoDecHidlTest::standardComp::mpeg2,
{"bbb_mpeg2_176x144_105kbps_25fps.m2v", "bbb_mpeg2_352x288_1mbps_60fps.m2v", ""},
- {"bbb_mpeg2_176x144_105kbps_25fps.info", "bbb_mpeg2_352x288_1mbps_60fps.info", ""}},
+ {"bbb_mpeg2_176x144_105kbps_25fps.info", "bbb_mpeg2_352x288_1mbps_60fps.info", ""},
+ {"", "", ""}},
{Codec2VideoDecHidlTest::standardComp::h263,
{"", "bbb_h263_352x288_300kbps_12fps.h263", ""},
- {"", "bbb_h263_352x288_300kbps_12fps.info", ""}},
+ {"", "bbb_h263_352x288_300kbps_12fps.info", ""},
+ {"", "", ""}},
{Codec2VideoDecHidlTest::standardComp::mpeg4,
{"", "bbb_mpeg4_352x288_512kbps_30fps.m4v", ""},
- {"", "bbb_mpeg4_352x288_512kbps_30fps.info", ""}},
+ {"", "bbb_mpeg4_352x288_512kbps_30fps.info", ""},
+ {"", "", ""}},
{Codec2VideoDecHidlTest::standardComp::vp8,
{"bbb_vp8_176x144_240kbps_60fps.vp8", "bbb_vp8_640x360_2mbps_30fps.vp8", ""},
- {"bbb_vp8_176x144_240kbps_60fps.info", "bbb_vp8_640x360_2mbps_30fps.info", ""}},
+ {"bbb_vp8_176x144_240kbps_60fps.info", "bbb_vp8_640x360_2mbps_30fps.info", ""},
+ {"", "bbb_vp8_640x360_2mbps_30fps_chksm.md5", ""}},
{Codec2VideoDecHidlTest::standardComp::vp9,
{"bbb_vp9_176x144_285kbps_60fps.vp9", "bbb_vp9_640x360_1600kbps_30fps.vp9",
"bbb_vp9_704x480_280kbps_24fps_altref_2.vp9"},
{"bbb_vp9_176x144_285kbps_60fps.info", "bbb_vp9_640x360_1600kbps_30fps.info",
- "bbb_vp9_704x480_280kbps_24fps_altref_2.info"}},
+ "bbb_vp9_704x480_280kbps_24fps_altref_2.info"},
+ {"", "bbb_vp9_640x360_1600kbps_30fps_chksm.md5", ""}},
{Codec2VideoDecHidlTest::standardComp::av1,
{"bbb_av1_640_360.av1", "bbb_av1_176_144.av1", ""},
- {"bbb_av1_640_360.info", "bbb_av1_176_144.info", ""}},
+ {"bbb_av1_640_360.info", "bbb_av1_176_144.info", ""},
+ {"bbb_av1_640_360_chksum.md5", "bbb_av1_176_144_chksm.md5", ""}},
};
for (size_t i = 0; i < sizeof(kCompToURL) / sizeof(kCompToURL[0]); ++i) {
if (kCompToURL[i].comp == comp) {
strcat(mURL, kCompToURL[i].mURL[streamIndex]);
strcat(info, kCompToURL[i].info[streamIndex]);
+ strcat(chksum, kCompToURL[i].chksum[streamIndex]);
return;
}
}
}
+void GetURLForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, char* info,
+ size_t streamIndex = 1) {
+ char chksum[512];
+ strcpy(chksum, sResourceDir.c_str());
+ GetURLChksmForComponent(comp, mURL, info, chksum, streamIndex);
+}
+
void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component,
std::mutex& queueLock, std::condition_variable& queueCondition,
std::list<std::unique_ptr<C2Work>>& workQueue,
@@ -446,6 +560,19 @@
ASSERT_EQ(producer->setSidebandStream(nativeHandle), NO_ERROR);
}
+// Config output pixel format
+bool Codec2VideoDecHidlTestBase::configPixelFormat(uint32_t format) {
+ std::vector<std::unique_ptr<C2SettingResult>> failures;
+ C2StreamPixelFormatInfo::output pixelformat(0u, format);
+
+ std::vector<C2Param*> configParam{&pixelformat};
+ c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
+ if (status == C2_OK && failures.size() == 0u) {
+ return true;
+ }
+ return false;
+}
+
class Codec2VideoDecDecodeTest
: public Codec2VideoDecHidlTestBase,
public ::testing::WithParamInterface<
@@ -463,40 +590,61 @@
uint32_t streamIndex = std::stoi(std::get<2>(GetParam()));
bool signalEOS = !std::get<2>(GetParam()).compare("true");
- char mURL[512], info[512];
- std::ifstream eleStream, eleInfo;
+ mTimestampDevTest = true;
+
+ char mURL[512], info[512], chksum[512];
+ android::Vector<FrameInfo> Info;
+
strcpy(mURL, sResourceDir.c_str());
strcpy(info, sResourceDir.c_str());
- GetURLForComponent(mCompName, mURL, info, streamIndex);
+ strcpy(chksum, sResourceDir.c_str());
- eleInfo.open(info);
- ASSERT_EQ(eleInfo.is_open(), true) << mURL << " - file not found";
- android::Vector<FrameInfo> Info;
- int bytesCount = 0;
- uint32_t flags = 0;
- uint32_t timestamp = 0;
- mTimestampDevTest = true;
+ GetURLChksmForComponent(mCompName, mURL, info, chksum, streamIndex);
+ if (!(strcmp(mURL, sResourceDir.c_str())) || !(strcmp(info, sResourceDir.c_str()))) {
+ ALOGV("Skipping Test, Stream not available");
+ return;
+ }
+ mMd5Enable = true;
+ if (!strcmp(chksum, sResourceDir.c_str())) mMd5Enable = false;
+
+ uint32_t format = HAL_PIXEL_FORMAT_YCBCR_420_888;
+ if (!configPixelFormat(format)) {
+ std::cout << "[ WARN ] Test Skipped PixelFormat not configured\n";
+ return;
+ }
+
mFlushedIndices.clear();
mTimestampUslist.clear();
- while (1) {
- if (!(eleInfo >> bytesCount)) break;
- eleInfo >> flags;
- eleInfo >> timestamp;
- bool codecConfig = flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0;
- bool nonDisplayFrame = ((flags & FLAG_NON_DISPLAY_FRAME) != 0);
- if (mTimestampDevTest && !codecConfig && !nonDisplayFrame)
- mTimestampUslist.push_back(timestamp);
- Info.push_back({bytesCount, flags, timestamp});
- }
- eleInfo.close();
+
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file: " << info;
ASSERT_EQ(mComponent->start(), C2_OK);
// Reset total no of frames received
mFramesReceived = 0;
mTimestampUs = 0;
ALOGV("mURL : %s", mURL);
+ std::ifstream eleStream;
eleStream.open(mURL, std::ifstream::binary);
ASSERT_EQ(eleStream.is_open(), true);
+
+ size_t refChksmSize = 0;
+ std::ifstream refChksum;
+ if (mMd5Enable) {
+ ALOGV("chksum file name: %s", chksum);
+ refChksum.open(chksum, std::ifstream::binary | std::ifstream::ate);
+ ASSERT_EQ(refChksum.is_open(), true);
+ refChksmSize = refChksum.tellg();
+ refChksum.seekg(0, std::ifstream::beg);
+
+ ALOGV("chksum Size %zu ", refChksmSize);
+ mRefMd5 = (char*)malloc(refChksmSize);
+ ASSERT_NE(mRefMd5, nullptr);
+ refChksum.read(mRefMd5, refChksmSize);
+ ASSERT_EQ(refChksum.gcount(), refChksmSize);
+ refChksum.close();
+ }
+
ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
mFlushedIndices, mLinearPool, eleStream, &Info, 0,
(int)Info.size(), signalEOS));
@@ -504,7 +652,7 @@
// If EOS is not sent, sending empty input with EOS flag
size_t infoSize = Info.size();
if (!signalEOS) {
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, 1));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, 1);
ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
C2FrameData::FLAG_END_OF_STREAM, false));
infoSize += 1;
@@ -513,7 +661,7 @@
// consumed
if (!mEos) {
ALOGV("Waiting for input consumption");
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
}
eleStream.close();
@@ -523,8 +671,17 @@
ASSERT_TRUE(false);
}
+ if (mRefMd5 != nullptr) free(mRefMd5);
+ if (mMd5Enable && refChksmSize != mMd5Offset) {
+ ALOGE("refChksum size and generated chksum size mismatch refChksum size %zu generated "
+ "chksum size %" PRId64 "",
+ refChksmSize, mMd5Offset);
+ ASSERT_TRUE(false);
+ }
+
if (mTimestampDevTest) EXPECT_EQ(mTimestampUslist.empty(), true);
ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
// Adaptive Test
@@ -622,7 +779,7 @@
// blocking call to ensures application to Wait till all the inputs are
// consumed
ALOGV("Waiting for input consumption");
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
if (mFramesReceived != ((Info.size()) + 1)) {
ALOGE("Input buffer count and Output buffer count mismatch");
@@ -631,6 +788,7 @@
}
if (mTimestampDevTest) EXPECT_EQ(mTimestampUslist.empty(), true);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
// thumbnail test
@@ -639,26 +797,16 @@
if (mDisableTest) GTEST_SKIP() << "Test is disabled";
char mURL[512], info[512];
- std::ifstream eleStream, eleInfo;
+ android::Vector<FrameInfo> Info;
strcpy(mURL, sResourceDir.c_str());
strcpy(info, sResourceDir.c_str());
GetURLForComponent(mCompName, mURL, info);
- eleInfo.open(info);
- ASSERT_EQ(eleInfo.is_open(), true);
- android::Vector<FrameInfo> Info;
- int bytesCount = 0;
- uint32_t flags = 0;
- uint32_t timestamp = 0;
- while (1) {
- if (!(eleInfo >> bytesCount)) break;
- eleInfo >> flags;
- eleInfo >> timestamp;
- Info.push_back({bytesCount, flags, timestamp});
- }
- eleInfo.close();
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file: " << info;
+ uint32_t flags = 0;
for (size_t i = 0; i < MAX_ITERATIONS; i++) {
ASSERT_EQ(mComponent->start(), C2_OK);
@@ -671,18 +819,21 @@
if (Info[j].flags) flags = 1u << (Info[j].flags - 1);
} while (!(flags & SYNC_FRAME));
+
+ std::ifstream eleStream;
eleStream.open(mURL, std::ifstream::binary);
ASSERT_EQ(eleStream.is_open(), true);
ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
mFlushedIndices, mLinearPool, eleStream, &Info, 0,
j + 1));
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
eleStream.close();
EXPECT_GE(mFramesReceived, 1U);
ASSERT_EQ(mEos, true);
ASSERT_EQ(mComponent->stop(), C2_OK);
}
ASSERT_EQ(mComponent->release(), C2_OK);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
TEST_P(Codec2VideoDecHidlTest, EOSTest) {
@@ -723,72 +874,59 @@
ASSERT_EQ(mEos, true);
ASSERT_EQ(mWorkQueue.size(), (size_t)MAX_INPUT_BUFFERS);
ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mWorkResult, C2_OK);
}
TEST_P(Codec2VideoDecHidlTest, FlushTest) {
description("Tests Flush calls");
if (mDisableTest) GTEST_SKIP() << "Test is disabled";
- typedef std::unique_lock<std::mutex> ULock;
+
ASSERT_EQ(mComponent->start(), C2_OK);
+
char mURL[512], info[512];
- std::ifstream eleStream, eleInfo;
+ android::Vector<FrameInfo> Info;
strcpy(mURL, sResourceDir.c_str());
strcpy(info, sResourceDir.c_str());
GetURLForComponent(mCompName, mURL, info);
- eleInfo.open(info);
- ASSERT_EQ(eleInfo.is_open(), true);
- android::Vector<FrameInfo> Info;
- int bytesCount = 0;
- uint32_t flags = 0;
- uint32_t timestamp = 0;
mFlushedIndices.clear();
- while (1) {
- if (!(eleInfo >> bytesCount)) break;
- eleInfo >> flags;
- eleInfo >> timestamp;
- Info.push_back({bytesCount, flags, timestamp});
- }
- eleInfo.close();
+
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file: " << info;
ALOGV("mURL : %s", mURL);
- eleStream.open(mURL, std::ifstream::binary);
- ASSERT_EQ(eleStream.is_open(), true);
- // Decode 128 frames and flush. here 128 is chosen to ensure there is a key
- // frame after this so that the below section can be covered for all
- // components
- uint32_t numFramesFlushed = 128;
- ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
- mFlushedIndices, mLinearPool, eleStream, &Info, 0,
- numFramesFlushed, false));
+
// flush
std::list<std::unique_ptr<C2Work>> flushedWork;
c2_status_t err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
ASSERT_EQ(err, C2_OK);
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
- (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
- {
- // Update mFlushedIndices based on the index received from flush()
- ULock l(mQueueLock);
- for (std::unique_ptr<C2Work>& work : flushedWork) {
- ASSERT_NE(work, nullptr);
- auto frameIndexIt = std::find(mFlushedIndices.begin(), mFlushedIndices.end(),
- work->input.ordinal.frameIndex.peeku());
- if (!mFlushedIndices.empty() && (frameIndexIt != mFlushedIndices.end())) {
- mFlushedIndices.erase(frameIndexIt);
- work->input.buffers.clear();
- work->worklets.clear();
- mWorkQueue.push_back(std::move(work));
- }
- }
- }
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+ // Decode 30 frames and flush. here 30 is chosen to ensure there is a key
+ // frame after this so that the below section can be covered for all
+ // components
+ uint32_t numFramesFlushed = FLUSH_INTERVAL;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info, 0,
+ numFramesFlushed, false));
+ // flush
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
// Seek to next key frame and start decoding till the end
- mFlushedIndices.clear();
int index = numFramesFlushed;
bool keyFrame = false;
- flags = 0;
+ uint32_t flags = 0;
while (index < (int)Info.size()) {
if (Info[index].flags) flags = 1u << (Info[index].flags - 1);
if ((flags & SYNC_FRAME) == SYNC_FRAME) {
@@ -807,25 +945,13 @@
eleStream.close();
err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
ASSERT_EQ(err, C2_OK);
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
- (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
- {
- // Update mFlushedIndices based on the index received from flush()
- ULock l(mQueueLock);
- for (std::unique_ptr<C2Work>& work : flushedWork) {
- ASSERT_NE(work, nullptr);
- uint64_t frameIndex = work->input.ordinal.frameIndex.peeku();
- std::list<uint64_t>::iterator frameIndexIt =
- std::find(mFlushedIndices.begin(), mFlushedIndices.end(), frameIndex);
- if (!mFlushedIndices.empty() && (frameIndexIt != mFlushedIndices.end())) {
- mFlushedIndices.erase(frameIndexIt);
- work->input.buffers.clear();
- work->worklets.clear();
- mWorkQueue.push_back(std::move(work));
- }
- }
- }
- ASSERT_EQ(mFlushedIndices.empty(), true);
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ // TODO: (b/154671521)
+ // Add assert for mWorkResult
ASSERT_EQ(mComponent->stop(), C2_OK);
}
@@ -880,7 +1006,7 @@
// consumed
if (!mEos) {
ALOGV("Waiting for input consumption");
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
}
eleStream.close();
@@ -891,6 +1017,110 @@
}
}
+class Codec2VideoDecCsdInputTests
+ : public Codec2VideoDecHidlTestBase,
+ public ::testing::WithParamInterface<std::tuple<std::string, std::string, std::string>> {
+ void getParams() {
+ mInstanceName = std::get<0>(GetParam());
+ mComponentName = std::get<1>(GetParam());
+ }
+};
+
+// Test the codecs for the following
+// start - csd - data… - (with/without)flush - data… - flush - data…
+TEST_P(Codec2VideoDecCsdInputTests, CSDFlushTest) {
+ description("Tests codecs for flush at different states");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512], info[512];
+
+ android::Vector<FrameInfo> Info;
+
+ strcpy(mURL, sResourceDir.c_str());
+ strcpy(info, sResourceDir.c_str());
+ GetURLForComponent(mCompName, mURL, info);
+
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file";
+
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ ALOGV("mURL : %s", mURL);
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+ bool flushedDecoder = false;
+ bool signalEOS = false;
+ bool keyFrame = false;
+ bool flushCsd = !std::get<2>(GetParam()).compare("true");
+
+ ALOGV("sending %d csd data ", numCsds);
+ int framesToDecode = numCsds;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info, 0,
+ framesToDecode, false));
+ c2_status_t err = C2_OK;
+ std::list<std::unique_ptr<C2Work>> flushedWork;
+ if (numCsds && flushCsd) {
+ // We wait for all the CSD buffers to get consumed.
+ // Once we have received all CSD work back, we call flush
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ flushedDecoder = true;
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ }
+
+ int offset = framesToDecode;
+ uint32_t flags = 0;
+ while (1) {
+ while (offset < (int)Info.size()) {
+ flags = 0;
+ if (Info[offset].flags) flags = 1u << (Info[offset].flags - 1);
+ if (flags & SYNC_FRAME) {
+ keyFrame = true;
+ break;
+ }
+ eleStream.ignore(Info[offset].bytesCount);
+ offset++;
+ }
+ if (keyFrame) {
+ framesToDecode = c2_min(FLUSH_INTERVAL, (int)Info.size() - offset);
+ if (framesToDecode < FLUSH_INTERVAL) signalEOS = true;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(
+ mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices,
+ mLinearPool, eleStream, &Info, offset, framesToDecode, signalEOS));
+ offset += framesToDecode;
+ }
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ keyFrame = false;
+ // blocking call to ensures application to Wait till remaining
+ // 'non-flushed' inputs are consumed
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ if (signalEOS || offset >= (int)Info.size()) {
+ break;
+ }
+ }
+ if (!signalEOS) {
+ ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
+ C2FrameData::FLAG_END_OF_STREAM, false));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+ }
+ eleStream.close();
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+}
+
INSTANTIATE_TEST_SUITE_P(PerInstance, Codec2VideoDecHidlTest, testing::ValuesIn(kTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
@@ -899,6 +1129,10 @@
testing::ValuesIn(kDecodeTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
+INSTANTIATE_TEST_SUITE_P(CsdInputs, Codec2VideoDecCsdInputTests,
+ testing::ValuesIn(kCsdFlushTestParameters),
+ android::hardware::PrintInstanceTupleNameToString<>);
+
} // anonymous namespace
// TODO : Video specific configuration Test
@@ -917,6 +1151,11 @@
std::make_tuple(std::get<0>(params), std::get<1>(params), "2", "false"));
kDecodeTestParameters.push_back(
std::make_tuple(std::get<0>(params), std::get<1>(params), "2", "true"));
+
+ kCsdFlushTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "true"));
+ kCsdFlushTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "false"));
}
// Set the resource directory based on command line args.
@@ -930,4 +1169,4 @@
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
-}
\ No newline at end of file
+}
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
index 63e7a69..a1049df 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
@@ -51,6 +51,14 @@
<option name="push-file" key="bbb_av1_176_144.info" value="/data/local/tmp/media/bbb_av1_176_144.info" />
<option name="push-file" key="bbb_vp9_704x480_280kbps_24fps_altref_2.vp9" value="/data/local/tmp/media/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9" />
<option name="push-file" key="bbb_vp9_704x480_280kbps_24fps_altref_2.info" value="/data/local/tmp/media/bbb_vp9_704x480_280kbps_24fps_altref_2.info" />
+ <option name="push-file" key="bbb_avc_176x144_300kbps_60fps_chksum.md5" value="/data/local/tmp/media/bbb_avc_176x144_300kbps_60fps_chksum.md5" />
+ <option name="push-file" key="bbb_avc_640x360_768kbps_30fps_chksum.md5" value="/data/local/tmp/media/bbb_avc_640x360_768kbps_30fps_chksum.md5" />
+ <option name="push-file" key="bbb_hevc_176x144_176kbps_60fps_chksum.md5" value="/data/local/tmp/media/bbb_hevc_176x144_176kbps_60fps_chksum.md5" />
+ <option name="push-file" key="bbb_hevc_640x360_1600kbps_30fps_chksum.md5" value="/data/local/tmp/media/bbb_hevc_640x360_1600kbps_30fps_chksum.md5" />
+ <option name="push-file" key="bbb_vp8_640x360_2mbps_30fps_chksm.md5" value="/data/local/tmp/media/bbb_vp8_640x360_2mbps_30fps_chksm.md5" />
+ <option name="push-file" key="bbb_vp9_640x360_1600kbps_30fps_chksm.md5" value="/data/local/tmp/media/bbb_vp9_640x360_1600kbps_30fps_chksm.md5" />
+ <option name="push-file" key="bbb_av1_640_360_chksum.md5" value="/data/local/tmp/media/bbb_av1_640_360_chksum.md5" />
+ <option name="push-file" key="bbb_av1_176_144_chksm.md5" value="/data/local/tmp/media/bbb_av1_176_144_chksm.md5" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
index ecc56f5..ecaf3a8 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
@@ -41,7 +41,7 @@
: C2Buffer({block->share(C2Rect(block->width(), block->height()), ::C2Fence())}) {}
};
-static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
+static std::vector<std::tuple<std::string, std::string, std::string, std::string, std::string>>
kEncodeTestParameters;
static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
kEncodeResolutionTestParameters;
@@ -100,6 +100,7 @@
}
mEos = false;
mCsd = false;
+ mConfigBPictures = false;
mFramesReceived = 0;
mFailedWorkReceived = 0;
mTimestampUs = 0u;
@@ -120,7 +121,7 @@
// Get the test parameters from GetParam call.
virtual void getParams() {}
- bool setupConfigParam(int32_t nWidth, int32_t nHeight);
+ bool setupConfigParam(int32_t nWidth, int32_t nHeight, int32_t nBFrame = 0);
// callback function to process onWorkDone received by Listener
void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) {
@@ -130,8 +131,10 @@
// previous timestamp
typedef std::unique_lock<std::mutex> ULock;
if (!mTimestampUslist.empty()) {
- EXPECT_GE((work->worklets.front()->output.ordinal.timestamp.peeku()),
- mTimestampUs);
+ if (!mConfigBPictures) {
+ EXPECT_GE((work->worklets.front()->output.ordinal.timestamp.peeku()),
+ mTimestampUs);
+ }
mTimestampUs = work->worklets.front()->output.ordinal.timestamp.peeku();
// Currently this lock is redundant as no mTimestampUslist is only initialized
// before queuing any work to component. Once AdaptiveTest is added similar to
@@ -192,7 +195,7 @@
bool mEos;
bool mCsd;
bool mDisableTest;
- bool mConfig;
+ bool mConfigBPictures;
bool mTimestampDevTest;
standardComp mCompName;
uint32_t mFramesReceived;
@@ -269,13 +272,27 @@
}
// Set Default config param.
-bool Codec2VideoEncHidlTestBase::setupConfigParam(int32_t nWidth, int32_t nHeight) {
+bool Codec2VideoEncHidlTestBase::setupConfigParam(int32_t nWidth, int32_t nHeight,
+ int32_t nBFrame) {
+ c2_status_t status = C2_OK;
+ std::vector<std::unique_ptr<C2Param>> configParam;
std::vector<std::unique_ptr<C2SettingResult>> failures;
- C2StreamPictureSizeInfo::input inputSize(0u, nWidth, nHeight);
- std::vector<C2Param*> configParam{&inputSize};
- c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
- if (status == C2_OK && failures.size() == 0u) return true;
- return false;
+
+ configParam.push_back(std::make_unique<C2StreamPictureSizeInfo::input>(0u, nWidth, nHeight));
+
+ if (nBFrame > 0) {
+ std::unique_ptr<C2StreamGopTuning::output> gop =
+ C2StreamGopTuning::output::AllocUnique(2 /* flexCount */, 0u /* stream */);
+ gop->m.values[0] = {P_FRAME, UINT32_MAX};
+ gop->m.values[1] = {C2Config::picture_type_t(P_FRAME | B_FRAME), uint32_t(nBFrame)};
+ configParam.push_back(std::move(gop));
+ }
+
+ for (const std::unique_ptr<C2Param>& param : configParam) {
+ status = mComponent->config({param.get()}, C2_DONT_BLOCK, &failures);
+ if (status != C2_OK || failures.size() != 0u) return false;
+ }
+ return true;
}
// LookUpTable of clips for component testing
@@ -388,7 +405,7 @@
class Codec2VideoEncEncodeTest
: public Codec2VideoEncHidlTestBase,
public ::testing::WithParamInterface<
- std::tuple<std::string, std::string, std::string, std::string>> {
+ std::tuple<std::string, std::string, std::string, std::string, std::string>> {
void getParams() {
mInstanceName = std::get<0>(GetParam());
mComponentName = std::get<1>(GetParam());
@@ -405,6 +422,7 @@
bool signalEOS = !std::get<2>(GetParam()).compare("true");
// Send an empty frame to receive CSD data from encoder.
bool sendEmptyFirstFrame = !std::get<3>(GetParam()).compare("true");
+ mConfigBPictures = !std::get<4>(GetParam()).compare("true");
strcpy(mURL, sResourceDir.c_str());
GetURLForComponent(mURL);
@@ -428,10 +446,30 @@
inputFrames--;
}
- if (!setupConfigParam(nWidth, nHeight)) {
+ if (!setupConfigParam(nWidth, nHeight, mConfigBPictures ? 1 : 0)) {
std::cout << "[ WARN ] Test Skipped \n";
return;
}
+ std::vector<std::unique_ptr<C2Param>> inParams;
+ c2_status_t c2_status = mComponent->query({}, {C2StreamGopTuning::output::PARAM_TYPE},
+ C2_DONT_BLOCK, &inParams);
+
+ if (c2_status != C2_OK || inParams.size() == 0) {
+ std::cout << "[ WARN ] Bframe not supported for " << mComponentName
+ << " resetting num BFrames to 0\n";
+ mConfigBPictures = false;
+ } else {
+ size_t offset = sizeof(C2Param);
+ C2Param* param = inParams[0].get();
+ int32_t numBFrames = *(int32_t*)((uint8_t*)param + offset);
+
+ if (!numBFrames) {
+ std::cout << "[ WARN ] Bframe not supported for " << mComponentName
+ << " resetting num BFrames to 0\n";
+ mConfigBPictures = false;
+ }
+ }
+
ASSERT_EQ(mComponent->start(), C2_OK);
if (sendEmptyFirstFrame) {
@@ -454,7 +492,7 @@
// If EOS is not sent, sending empty input with EOS flag
inputFrames += ENC_NUM_FRAMES;
if (!signalEOS) {
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, 1));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, 1);
ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
C2FrameData::FLAG_END_OF_STREAM, false));
inputFrames += 1;
@@ -463,7 +501,7 @@
// blocking call to ensures application to Wait till all the inputs are
// consumed
ALOGD("Waiting for input consumption");
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
eleStream.close();
if (mFramesReceived != inputFrames) {
@@ -472,16 +510,17 @@
ASSERT_TRUE(false);
}
- if (!mCsd && (mCompName != vp8 && mCompName != vp9)) {
- ASSERT_TRUE(false) << "CSD Buffer not received";
- }
-
- if (mCsd && (mCompName == vp8 || mCompName == vp9)) {
- ASSERT_TRUE(false) << "CSD Buffer not expected";
+ if (mCompName == vp8 || mCompName == h263) {
+ ASSERT_FALSE(mCsd) << "CSD Buffer not expected";
+ } else if (mCompName != vp9) {
+ ASSERT_TRUE(mCsd) << "CSD Buffer not received";
}
if (mTimestampDevTest) EXPECT_EQ(mTimestampUslist.empty(), true);
ASSERT_EQ(mComponent->stop(), C2_OK);
+
+ // TODO: (b/155534991)
+ // Add assert for mFailedWorkReceived
}
TEST_P(Codec2VideoEncHidlTest, EOSTest) {
@@ -522,13 +561,13 @@
}
ASSERT_EQ(mEos, true);
ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mFailedWorkReceived, 0);
}
TEST_P(Codec2VideoEncHidlTest, FlushTest) {
description("Test Request for flush");
if (mDisableTest) GTEST_SKIP() << "Test is disabled";
- typedef std::unique_lock<std::mutex> ULock;
char mURL[512];
int32_t nWidth = ENC_DEFAULT_FRAME_WIDTH;
int32_t nHeight = ENC_DEFAULT_FRAME_HEIGHT;
@@ -549,9 +588,17 @@
eleStream.open(mURL, std::ifstream::binary);
ASSERT_EQ(eleStream.is_open(), true);
ALOGV("mURL : %s", mURL);
+ // flush
+ std::list<std::unique_ptr<C2Work>> flushedWork;
+ c2_status_t err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+
ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
mFlushedIndices, mGraphicPool, eleStream, mDisableTest, 0,
- numFramesFlushed, nWidth, nHeight));
+ numFramesFlushed, nWidth, nHeight, false, false));
// mDisableTest will be set if buffer was not fetched properly.
// This may happen when resolution is not proper but config succeeded
// In this cases, we skip encoding the input stream
@@ -561,29 +608,14 @@
return;
}
- std::list<std::unique_ptr<C2Work>> flushedWork;
- c2_status_t err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ // flush
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
ASSERT_EQ(err, C2_OK);
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
- (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
- uint64_t frameIndex;
- {
- // Update mFlushedIndices based on the index received from flush()
- ULock l(mQueueLock);
- for (std::unique_ptr<C2Work>& work : flushedWork) {
- ASSERT_NE(work, nullptr);
- frameIndex = work->input.ordinal.frameIndex.peeku();
- std::list<uint64_t>::iterator frameIndexIt =
- std::find(mFlushedIndices.begin(), mFlushedIndices.end(), frameIndex);
- if (!mFlushedIndices.empty() && (frameIndexIt != mFlushedIndices.end())) {
- mFlushedIndices.erase(frameIndexIt);
- work->input.buffers.clear();
- work->worklets.clear();
- mWorkQueue.push_back(std::move(work));
- }
- }
- }
- mFlushedIndices.clear();
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
mFlushedIndices, mGraphicPool, eleStream, mDisableTest,
numFramesFlushed, numFrames - numFramesFlushed, nWidth,
@@ -600,25 +632,13 @@
err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
ASSERT_EQ(err, C2_OK);
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
- (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
- {
- // Update mFlushedIndices based on the index received from flush()
- ULock l(mQueueLock);
- for (std::unique_ptr<C2Work>& work : flushedWork) {
- ASSERT_NE(work, nullptr);
- frameIndex = work->input.ordinal.frameIndex.peeku();
- std::list<uint64_t>::iterator frameIndexIt =
- std::find(mFlushedIndices.begin(), mFlushedIndices.end(), frameIndex);
- if (!mFlushedIndices.empty() && (frameIndexIt != mFlushedIndices.end())) {
- mFlushedIndices.erase(frameIndexIt);
- work->input.buffers.clear();
- work->worklets.clear();
- mWorkQueue.push_back(std::move(work));
- }
- }
- }
- ASSERT_EQ(mFlushedIndices.empty(), true);
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ // TODO: (b/154671521)
+ // Add assert for mFailedWorkReceived
ASSERT_EQ(mComponent->stop(), C2_OK);
}
@@ -653,7 +673,7 @@
// blocking call to ensures application to Wait till all the inputs are
// consumed
ALOGD("Waiting for input consumption");
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
if (mFramesReceived != 3) {
std::cout << "[ WARN ] Component didn't receive all buffers back \n";
@@ -708,7 +728,7 @@
}
ALOGD("Waiting for input consumption");
- ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
ASSERT_EQ(mEos, true);
ASSERT_EQ(mComponent->stop(), C2_OK);
@@ -816,14 +836,14 @@
int main(int argc, char** argv) {
kTestParameters = getTestParameters(C2Component::DOMAIN_VIDEO, C2Component::KIND_ENCODER);
for (auto params : kTestParameters) {
- kEncodeTestParameters.push_back(
- std::make_tuple(std::get<0>(params), std::get<1>(params), "true", "true"));
- kEncodeTestParameters.push_back(
- std::make_tuple(std::get<0>(params), std::get<1>(params), "true", "false"));
- kEncodeTestParameters.push_back(
- std::make_tuple(std::get<0>(params), std::get<1>(params), "false", "true"));
- kEncodeTestParameters.push_back(
- std::make_tuple(std::get<0>(params), std::get<1>(params), "false", "false"));
+ constexpr char const* kBoolString[] = { "false", "true" };
+ for (size_t i = 0; i < 1 << 3; ++i) {
+ kEncodeTestParameters.push_back(std::make_tuple(
+ std::get<0>(params), std::get<1>(params),
+ kBoolString[i & 1],
+ kBoolString[(i >> 1) & 1],
+ kBoolString[(i >> 2) & 1]));
+ }
kEncodeResolutionTestParameters.push_back(
std::make_tuple(std::get<0>(params), std::get<1>(params), "52", "18"));
diff --git a/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h b/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
index 9c1a5cb..d3a693b 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
+++ b/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
@@ -22,7 +22,6 @@
#define ENC_DEFAULT_FRAME_WIDTH 352
#define ENC_DEFAULT_FRAME_HEIGHT 288
#define MAX_ITERATIONS 128
-#define FLAG_NON_DISPLAY_FRAME (1 << 4)
#define ALIGN(_sz, _align) ((_sz + (_align - 1)) & ~(_align - 1))
diff --git a/media/codec2/hidl/1.1/utils/Android.bp b/media/codec2/hidl/1.1/utils/Android.bp
index 8fddf98..386f6e2 100644
--- a/media/codec2/hidl/1.1/utils/Android.bp
+++ b/media/codec2/hidl/1.1/utils/Android.bp
@@ -52,6 +52,7 @@
cc_library {
name: "libcodec2_hidl@1.1",
vendor_available: true,
+ min_sdk_version: "29",
defaults: ["hidl_defaults"],
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index 1e4560c..1972d3f 100644
--- a/media/codec2/sfplugin/CCodec.cpp
+++ b/media/codec2/sfplugin/CCodec.cpp
@@ -1379,7 +1379,7 @@
state->set(STOPPING);
}
- mChannel->stop();
+ mChannel->reset();
(new AMessage(kWhatStop, this))->post();
}
@@ -1406,9 +1406,6 @@
// TODO: convert err into status_t
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
}
- // Assure buffers are not owned when stop() was called without flush().
- std::list<std::unique_ptr<C2Work>> flushedWork;
- mChannel->flush(flushedWork);
{
Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
@@ -1468,7 +1465,7 @@
}
}
- mChannel->stop();
+ mChannel->reset();
// thiz holds strong ref to this while the thread is running.
sp<CCodec> thiz(this);
std::thread([thiz, sendCallback] { thiz->release(sendCallback); }).detach();
@@ -1495,6 +1492,7 @@
state->set(RELEASED);
state->comp.reset();
}
+ (new AMessage(kWhatRelease, this))->post();
if (sendCallback) {
mCallback->onReleaseCompleted();
}
@@ -1759,6 +1757,12 @@
flush();
break;
}
+ case kWhatRelease: {
+ mChannel->release();
+ mClient.reset();
+ mClientListener.reset();
+ break;
+ }
case kWhatCreateInputSurface: {
// Surface operations may be briefly blocking.
setDeadline(now, 1500ms, "createInputSurface");
@@ -1861,7 +1865,7 @@
config->mInputSurface->onInputBufferDone(work->input.ordinal.frameIndex);
}
mChannel->onWorkDone(
- std::move(work), changed ? config->mOutputFormat : nullptr,
+ std::move(work), changed ? config->mOutputFormat->dup() : nullptr,
initData.hasChanged() ? initData.update().get() : nullptr);
break;
}
@@ -1951,11 +1955,98 @@
inputSurface->getHalInterface()));
}
-static void MaybeLogUnrecognizedName(const char *func, const std::string &name) {
- thread_local std::set<std::string> sLogged{};
- if (sLogged.insert(name).second) {
- ALOGW("%s: Unrecognized interface name: %s", func, name.c_str());
+class IntfCache {
+public:
+ IntfCache() = default;
+
+ status_t init(const std::string &name) {
+ std::shared_ptr<Codec2Client::Interface> intf{
+ Codec2Client::CreateInterfaceByName(name.c_str())};
+ if (!intf) {
+ ALOGW("IntfCache [%s]: Unrecognized interface name", name.c_str());
+ mInitStatus = NO_INIT;
+ return NO_INIT;
+ }
+ const static C2StreamUsageTuning::input sUsage{0u /* stream id */};
+ mFields.push_back(C2FieldSupportedValuesQuery::Possible(
+ C2ParamField{&sUsage, &sUsage.value}));
+ c2_status_t err = intf->querySupportedValues(mFields, C2_MAY_BLOCK);
+ if (err != C2_OK) {
+ ALOGW("IntfCache [%s]: failed to query usage supported value (err=%d)",
+ name.c_str(), err);
+ mFields[0].status = err;
+ }
+ std::vector<std::unique_ptr<C2Param>> params;
+ err = intf->query(
+ {&mApiFeatures},
+ {C2PortAllocatorsTuning::input::PARAM_TYPE},
+ C2_MAY_BLOCK,
+ ¶ms);
+ if (err != C2_OK && err != C2_BAD_INDEX) {
+ ALOGW("IntfCache [%s]: failed to query api features (err=%d)",
+ name.c_str(), err);
+ }
+ while (!params.empty()) {
+ C2Param *param = params.back().release();
+ params.pop_back();
+ if (!param) {
+ continue;
+ }
+ if (param->type() == C2PortAllocatorsTuning::input::PARAM_TYPE) {
+ mInputAllocators.reset(
+ C2PortAllocatorsTuning::input::From(params[0].get()));
+ }
+ }
+ mInitStatus = OK;
+ return OK;
}
+
+ status_t initCheck() const { return mInitStatus; }
+
+ const C2FieldSupportedValuesQuery &getUsageSupportedValues() const {
+ CHECK_EQ(1u, mFields.size());
+ return mFields[0];
+ }
+
+ const C2ApiFeaturesSetting &getApiFeatures() const {
+ return mApiFeatures;
+ }
+
+ const C2PortAllocatorsTuning::input &getInputAllocators() const {
+ static std::unique_ptr<C2PortAllocatorsTuning::input> sInvalidated = []{
+ std::unique_ptr<C2PortAllocatorsTuning::input> param =
+ C2PortAllocatorsTuning::input::AllocUnique(0);
+ param->invalidate();
+ return param;
+ }();
+ return mInputAllocators ? *mInputAllocators : *sInvalidated;
+ }
+
+private:
+ status_t mInitStatus{NO_INIT};
+
+ std::vector<C2FieldSupportedValuesQuery> mFields;
+ C2ApiFeaturesSetting mApiFeatures;
+ std::unique_ptr<C2PortAllocatorsTuning::input> mInputAllocators;
+};
+
+static const IntfCache &GetIntfCache(const std::string &name) {
+ static IntfCache sNullIntfCache;
+ static std::mutex sMutex;
+ static std::map<std::string, IntfCache> sCache;
+ std::unique_lock<std::mutex> lock{sMutex};
+ auto it = sCache.find(name);
+ if (it == sCache.end()) {
+ lock.unlock();
+ IntfCache intfCache;
+ status_t err = intfCache.init(name);
+ if (err != OK) {
+ return sNullIntfCache;
+ }
+ lock.lock();
+ it = sCache.insert({name, std::move(intfCache)}).first;
+ }
+ return it->second;
}
static status_t GetCommonAllocatorIds(
@@ -1973,24 +2064,16 @@
}
bool firstIteration = true;
for (const std::string &name : names) {
- std::shared_ptr<Codec2Client::Interface> intf{
- Codec2Client::CreateInterfaceByName(name.c_str())};
- if (!intf) {
- MaybeLogUnrecognizedName(__FUNCTION__, name);
+ const IntfCache &intfCache = GetIntfCache(name);
+ if (intfCache.initCheck() != OK) {
continue;
}
- std::vector<std::unique_ptr<C2Param>> params;
- c2_status_t err = intf->query(
- {}, {C2PortAllocatorsTuning::input::PARAM_TYPE}, C2_MAY_BLOCK, ¶ms);
+ const C2PortAllocatorsTuning::input &allocators = intfCache.getInputAllocators();
if (firstIteration) {
firstIteration = false;
- if (err == C2_OK && params.size() == 1u) {
- C2PortAllocatorsTuning::input *allocators =
- C2PortAllocatorsTuning::input::From(params[0].get());
- if (allocators && allocators->flexCount() > 0) {
- ids->insert(allocators->m.values,
- allocators->m.values + allocators->flexCount());
- }
+ if (allocators && allocators.flexCount() > 0) {
+ ids->insert(allocators.m.values,
+ allocators.m.values + allocators.flexCount());
}
if (ids->empty()) {
// The component does not advertise allocators. Use default.
@@ -1999,24 +2082,20 @@
continue;
}
bool filtered = false;
- if (err == C2_OK && params.size() == 1u) {
- C2PortAllocatorsTuning::input *allocators =
- C2PortAllocatorsTuning::input::From(params[0].get());
- if (allocators && allocators->flexCount() > 0) {
- filtered = true;
- for (auto it = ids->begin(); it != ids->end(); ) {
- bool found = false;
- for (size_t j = 0; j < allocators->flexCount(); ++j) {
- if (allocators->m.values[j] == *it) {
- found = true;
- break;
- }
+ if (allocators && allocators.flexCount() > 0) {
+ filtered = true;
+ for (auto it = ids->begin(); it != ids->end(); ) {
+ bool found = false;
+ for (size_t j = 0; j < allocators.flexCount(); ++j) {
+ if (allocators.m.values[j] == *it) {
+ found = true;
+ break;
}
- if (found) {
- ++it;
- } else {
- it = ids->erase(it);
- }
+ }
+ if (found) {
+ ++it;
+ } else {
+ it = ids->erase(it);
}
}
}
@@ -2048,23 +2127,16 @@
*minUsage = 0;
*maxUsage = ~0ull;
for (const std::string &name : names) {
- std::shared_ptr<Codec2Client::Interface> intf{
- Codec2Client::CreateInterfaceByName(name.c_str())};
- if (!intf) {
- MaybeLogUnrecognizedName(__FUNCTION__, name);
+ const IntfCache &intfCache = GetIntfCache(name);
+ if (intfCache.initCheck() != OK) {
continue;
}
- std::vector<C2FieldSupportedValuesQuery> fields;
- fields.push_back(C2FieldSupportedValuesQuery::Possible(
- C2ParamField{&sUsage, &sUsage.value}));
- c2_status_t err = intf->querySupportedValues(fields, C2_MAY_BLOCK);
- if (err != C2_OK) {
+ const C2FieldSupportedValuesQuery &usageSupportedValues =
+ intfCache.getUsageSupportedValues();
+ if (usageSupportedValues.status != C2_OK) {
continue;
}
- if (fields[0].status != C2_OK) {
- continue;
- }
- const C2FieldSupportedValues &supported = fields[0].values;
+ const C2FieldSupportedValues &supported = usageSupportedValues.values;
if (supported.type != C2FieldSupportedValues::FLAGS) {
continue;
}
@@ -2085,6 +2157,17 @@
// static
status_t CCodec::CanFetchLinearBlock(
const std::vector<std::string> &names, const C2MemoryUsage &usage, bool *isCompatible) {
+ for (const std::string &name : names) {
+ const IntfCache &intfCache = GetIntfCache(name);
+ if (intfCache.initCheck() != OK) {
+ continue;
+ }
+ const C2ApiFeaturesSetting &features = intfCache.getApiFeatures();
+ if (features && !(features.value & API_SAME_INPUT_BUFFER)) {
+ *isCompatible = false;
+ return OK;
+ }
+ }
uint64_t minUsage = usage.expected;
uint64_t maxUsage = ~0ull;
std::set<C2Allocator::id_t> allocators;
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index 3773528..0626c8d 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -18,6 +18,8 @@
#define LOG_TAG "CCodecBufferChannel"
#include <utils/Log.h>
+#include <algorithm>
+#include <list>
#include <numeric>
#include <C2AllocatorGralloc.h>
@@ -128,97 +130,6 @@
count->value = -1;
}
-// CCodecBufferChannel::ReorderStash
-
-CCodecBufferChannel::ReorderStash::ReorderStash() {
- clear();
-}
-
-void CCodecBufferChannel::ReorderStash::clear() {
- mPending.clear();
- mStash.clear();
- mDepth = 0;
- mKey = C2Config::ORDINAL;
-}
-
-void CCodecBufferChannel::ReorderStash::flush() {
- mPending.clear();
- mStash.clear();
-}
-
-void CCodecBufferChannel::ReorderStash::setDepth(uint32_t depth) {
- mPending.splice(mPending.end(), mStash);
- mDepth = depth;
-}
-
-void CCodecBufferChannel::ReorderStash::setKey(C2Config::ordinal_key_t key) {
- mPending.splice(mPending.end(), mStash);
- mKey = key;
-}
-
-bool CCodecBufferChannel::ReorderStash::pop(Entry *entry) {
- if (mPending.empty()) {
- return false;
- }
- entry->buffer = mPending.front().buffer;
- entry->timestamp = mPending.front().timestamp;
- entry->flags = mPending.front().flags;
- entry->ordinal = mPending.front().ordinal;
- mPending.pop_front();
- return true;
-}
-
-void CCodecBufferChannel::ReorderStash::emplace(
- const std::shared_ptr<C2Buffer> &buffer,
- int64_t timestamp,
- int32_t flags,
- const C2WorkOrdinalStruct &ordinal) {
- bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
- if (!buffer && eos) {
- // TRICKY: we may be violating ordering of the stash here. Because we
- // don't expect any more emplace() calls after this, the ordering should
- // not matter.
- mStash.emplace_back(buffer, timestamp, flags, ordinal);
- } else {
- flags = flags & ~MediaCodec::BUFFER_FLAG_EOS;
- auto it = mStash.begin();
- for (; it != mStash.end(); ++it) {
- if (less(ordinal, it->ordinal)) {
- break;
- }
- }
- mStash.emplace(it, buffer, timestamp, flags, ordinal);
- if (eos) {
- mStash.back().flags = mStash.back().flags | MediaCodec::BUFFER_FLAG_EOS;
- }
- }
- while (!mStash.empty() && mStash.size() > mDepth) {
- mPending.push_back(mStash.front());
- mStash.pop_front();
- }
-}
-
-void CCodecBufferChannel::ReorderStash::defer(
- const CCodecBufferChannel::ReorderStash::Entry &entry) {
- mPending.push_front(entry);
-}
-
-bool CCodecBufferChannel::ReorderStash::hasPending() const {
- return !mPending.empty();
-}
-
-bool CCodecBufferChannel::ReorderStash::less(
- const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) {
- switch (mKey) {
- case C2Config::ORDINAL: return o1.frameIndex < o2.frameIndex;
- case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
- case C2Config::CUSTOM: return o1.customOrdinal < o2.customOrdinal;
- default:
- ALOGD("Unrecognized key; default to timestamp");
- return o1.frameIndex < o2.frameIndex;
- }
-}
-
// Input
CCodecBufferChannel::Input::Input() : extraBuffers("extra") {}
@@ -707,13 +618,14 @@
}
void CCodecBufferChannel::feedInputBufferIfAvailableInternal() {
- if (mInputMetEos ||
- mReorderStash.lock()->hasPending() ||
- mPipelineWatcher.lock()->pipelineFull()) {
+ if (mInputMetEos || mPipelineWatcher.lock()->pipelineFull()) {
return;
- } else {
+ }
+ {
Mutexed<Output>::Locked output(mOutput);
- if (output->buffers->numClientBuffers() >= output->numSlots) {
+ if (!output->buffers ||
+ output->buffers->hasPending() ||
+ output->buffers->numClientBuffers() >= output->numSlots) {
return;
}
}
@@ -820,6 +732,9 @@
std::shared_ptr<const C2StreamHdr10PlusInfo::output> hdr10PlusInfo =
std::static_pointer_cast<const C2StreamHdr10PlusInfo::output>(
c2Buffer->getInfo(C2StreamHdr10PlusInfo::output::PARAM_TYPE));
+ if (hdr10PlusInfo && hdr10PlusInfo->flexCount() == 0) {
+ hdr10PlusInfo.reset();
+ }
{
Mutexed<OutputSurface>::Locked output(mOutputSurface);
@@ -851,31 +766,39 @@
if (hdrStaticInfo || hdr10PlusInfo) {
HdrMetadata hdr;
if (hdrStaticInfo) {
- struct android_smpte2086_metadata smpte2086_meta = {
- .displayPrimaryRed = {
- hdrStaticInfo->mastering.red.x, hdrStaticInfo->mastering.red.y
- },
- .displayPrimaryGreen = {
- hdrStaticInfo->mastering.green.x, hdrStaticInfo->mastering.green.y
- },
- .displayPrimaryBlue = {
- hdrStaticInfo->mastering.blue.x, hdrStaticInfo->mastering.blue.y
- },
- .whitePoint = {
- hdrStaticInfo->mastering.white.x, hdrStaticInfo->mastering.white.y
- },
- .maxLuminance = hdrStaticInfo->mastering.maxLuminance,
- .minLuminance = hdrStaticInfo->mastering.minLuminance,
- };
-
- struct android_cta861_3_metadata cta861_meta = {
- .maxContentLightLevel = hdrStaticInfo->maxCll,
- .maxFrameAverageLightLevel = hdrStaticInfo->maxFall,
- };
-
- hdr.validTypes = HdrMetadata::SMPTE2086 | HdrMetadata::CTA861_3;
- hdr.smpte2086 = smpte2086_meta;
- hdr.cta8613 = cta861_meta;
+ // If mastering max and min luminance fields are 0, do not use them.
+ // It indicates the value may not be present in the stream.
+ if (hdrStaticInfo->mastering.maxLuminance > 0.0f &&
+ hdrStaticInfo->mastering.minLuminance > 0.0f) {
+ struct android_smpte2086_metadata smpte2086_meta = {
+ .displayPrimaryRed = {
+ hdrStaticInfo->mastering.red.x, hdrStaticInfo->mastering.red.y
+ },
+ .displayPrimaryGreen = {
+ hdrStaticInfo->mastering.green.x, hdrStaticInfo->mastering.green.y
+ },
+ .displayPrimaryBlue = {
+ hdrStaticInfo->mastering.blue.x, hdrStaticInfo->mastering.blue.y
+ },
+ .whitePoint = {
+ hdrStaticInfo->mastering.white.x, hdrStaticInfo->mastering.white.y
+ },
+ .maxLuminance = hdrStaticInfo->mastering.maxLuminance,
+ .minLuminance = hdrStaticInfo->mastering.minLuminance,
+ };
+ hdr.validTypes |= HdrMetadata::SMPTE2086;
+ hdr.smpte2086 = smpte2086_meta;
+ }
+ // If the content light level fields are 0, do not use them, it
+ // indicates the value may not be present in the stream.
+ if (hdrStaticInfo->maxCll > 0.0f && hdrStaticInfo->maxFall > 0.0f) {
+ struct android_cta861_3_metadata cta861_meta = {
+ .maxContentLightLevel = hdrStaticInfo->maxCll,
+ .maxFrameAverageLightLevel = hdrStaticInfo->maxFall,
+ };
+ hdr.validTypes |= HdrMetadata::CTA861_3;
+ hdr.cta8613 = cta861_meta;
+ }
}
if (hdr10PlusInfo) {
hdr.validTypes |= HdrMetadata::HDR10PLUS;
@@ -981,17 +904,6 @@
return UNKNOWN_ERROR;
}
- {
- Mutexed<ReorderStash>::Locked reorder(mReorderStash);
- reorder->clear();
- if (reorderDepth) {
- reorder->setDepth(reorderDepth.value);
- }
- if (reorderKey) {
- reorder->setKey(reorderKey.value);
- }
- }
-
uint32_t inputDelayValue = inputDelay ? inputDelay.value : 0;
uint32_t pipelineDelayValue = pipelineDelay ? pipelineDelay.value : 0;
uint32_t outputDelayValue = outputDelay ? outputDelay.value : 0;
@@ -1008,6 +920,12 @@
if (inputFormat != nullptr) {
bool graphic = (iStreamFormat.value == C2BufferData::GRAPHIC);
+ C2Config::api_feature_t apiFeatures = C2Config::api_feature_t(
+ API_REFLECTION |
+ API_VALUES |
+ API_CURRENT_VALUES |
+ API_DEPENDENCY |
+ API_SAME_INPUT_BUFFER);
std::shared_ptr<C2BlockPool> pool;
{
Mutexed<BlockPools>::Locked pools(mBlockPools);
@@ -1019,14 +937,15 @@
// query C2PortAllocatorsTuning::input from component. If an allocator ID is obtained
// from component, create the input block pool with given ID. Otherwise, use default IDs.
std::vector<std::unique_ptr<C2Param>> params;
- err = mComponent->query({ },
+ C2ApiFeaturesSetting featuresSetting{apiFeatures};
+ err = mComponent->query({ &featuresSetting },
{ C2PortAllocatorsTuning::input::PARAM_TYPE },
C2_DONT_BLOCK,
¶ms);
if ((err != C2_OK && err != C2_BAD_INDEX) || params.size() != 1) {
ALOGD("[%s] Query input allocators returned %zu params => %s (%u)",
mName, params.size(), asString(err), err);
- } else if (err == C2_OK && params.size() == 1) {
+ } else if (params.size() == 1) {
C2PortAllocatorsTuning::input *inputAllocators =
C2PortAllocatorsTuning::input::From(params[0].get());
if (inputAllocators && inputAllocators->flexCount() > 0) {
@@ -1041,6 +960,9 @@
}
}
}
+ if (featuresSetting) {
+ apiFeatures = featuresSetting.value;
+ }
// TODO: use C2Component wrapper to associate this pool with ourselves
if ((poolMask >> pools->inputAllocatorId) & 1) {
@@ -1074,7 +996,10 @@
input->numSlots = numInputSlots;
input->extraBuffers.flush();
input->numExtraSlots = 0u;
- if (!buffersBoundToCodec) {
+ bool conforming = (apiFeatures & API_SAME_INPUT_BUFFER);
+ // For encrypted content, framework decrypts source buffer (ashmem) into
+ // C2Buffers. Thus non-conforming codecs can process these.
+ if (!buffersBoundToCodec && (hasCryptoOrDescrambler() || conforming)) {
input->buffers.reset(new SlotInputBuffers(mName));
} else if (graphic) {
if (mInputSurface) {
@@ -1260,6 +1185,13 @@
}
output->buffers->setFormat(outputFormat);
+ output->buffers->clearStash();
+ if (reorderDepth) {
+ output->buffers->setReorderDepth(reorderDepth.value);
+ }
+ if (reorderKey) {
+ output->buffers->setReorderKey(reorderKey.value);
+ }
// Try to set output surface to created block pool if given.
if (outputSurface) {
@@ -1329,62 +1261,98 @@
return UNKNOWN_ERROR;
}
size_t numInputSlots = mInput.lock()->numSlots;
- std::vector<sp<MediaCodecBuffer>> toBeQueued;
- for (size_t i = 0; i < numInputSlots; ++i) {
+
+ struct ClientInputBuffer {
size_t index;
sp<MediaCodecBuffer> buffer;
- {
- Mutexed<Input>::Locked input(mInput);
- if (!input->buffers->requestNewBuffer(&index, &buffer)) {
- if (i == 0) {
- ALOGW("[%s] start: cannot allocate memory at all", mName);
- return NO_MEMORY;
- } else {
- ALOGV("[%s] start: cannot allocate memory, only %zu buffers allocated",
- mName, i);
- }
+ size_t capacity;
+ };
+ std::list<ClientInputBuffer> clientInputBuffers;
+
+ {
+ Mutexed<Input>::Locked input(mInput);
+ while (clientInputBuffers.size() < numInputSlots) {
+ ClientInputBuffer clientInputBuffer;
+ if (!input->buffers->requestNewBuffer(&clientInputBuffer.index,
+ &clientInputBuffer.buffer)) {
break;
}
+ clientInputBuffer.capacity = clientInputBuffer.buffer->capacity();
+ clientInputBuffers.emplace_back(std::move(clientInputBuffer));
}
- if (buffer) {
- Mutexed<std::list<sp<ABuffer>>>::Locked configs(mFlushedConfigs);
- ALOGV("[%s] input buffer %zu available", mName, index);
- bool post = true;
- if (!configs->empty()) {
+ }
+ if (clientInputBuffers.empty()) {
+ ALOGW("[%s] start: cannot allocate memory at all", mName);
+ return NO_MEMORY;
+ } else if (clientInputBuffers.size() < numInputSlots) {
+ ALOGD("[%s] start: cannot allocate memory for all slots, "
+ "only %zu buffers allocated",
+ mName, clientInputBuffers.size());
+ } else {
+ ALOGV("[%s] %zu initial input buffers available",
+ mName, clientInputBuffers.size());
+ }
+ // Sort input buffers by their capacities in increasing order.
+ clientInputBuffers.sort(
+ [](const ClientInputBuffer& a, const ClientInputBuffer& b) {
+ return a.capacity < b.capacity;
+ });
+
+ {
+ Mutexed<std::list<sp<ABuffer>>>::Locked configs(mFlushedConfigs);
+ if (!configs->empty()) {
+ while (!configs->empty()) {
sp<ABuffer> config = configs->front();
configs->pop_front();
- if (buffer->capacity() >= config->size()) {
- memcpy(buffer->base(), config->data(), config->size());
- buffer->setRange(0, config->size());
- buffer->meta()->clear();
- buffer->meta()->setInt64("timeUs", 0);
- buffer->meta()->setInt32("csd", 1);
- post = false;
- } else {
- ALOGD("[%s] buffer capacity too small for the config (%zu < %zu)",
- mName, buffer->capacity(), config->size());
+ // Find the smallest input buffer that can fit the config.
+ auto i = std::find_if(
+ clientInputBuffers.begin(),
+ clientInputBuffers.end(),
+ [cfgSize = config->size()](const ClientInputBuffer& b) {
+ return b.capacity >= cfgSize;
+ });
+ if (i == clientInputBuffers.end()) {
+ ALOGW("[%s] no input buffer large enough for the config "
+ "(%zu bytes)",
+ mName, config->size());
+ return NO_MEMORY;
}
- } else if (oStreamFormat.value == C2BufferData::LINEAR && i == 0
- && (!prepend || prepend.value == PREPEND_HEADER_TO_NONE)) {
- // WORKAROUND: Some apps expect CSD available without queueing
- // any input. Queue an empty buffer to get the CSD.
- buffer->setRange(0, 0);
+ sp<MediaCodecBuffer> buffer = i->buffer;
+ memcpy(buffer->base(), config->data(), config->size());
+ buffer->setRange(0, config->size());
buffer->meta()->clear();
buffer->meta()->setInt64("timeUs", 0);
- post = false;
+ buffer->meta()->setInt32("csd", 1);
+ if (queueInputBufferInternal(buffer) != OK) {
+ ALOGW("[%s] Error while queueing a flushed config",
+ mName);
+ return UNKNOWN_ERROR;
+ }
+ clientInputBuffers.erase(i);
}
- if (post) {
- mCallback->onInputBufferAvailable(index, buffer);
- } else {
- toBeQueued.emplace_back(buffer);
+ } else if (oStreamFormat.value == C2BufferData::LINEAR &&
+ (!prepend || prepend.value == PREPEND_HEADER_TO_NONE)) {
+ sp<MediaCodecBuffer> buffer = clientInputBuffers.front().buffer;
+ // WORKAROUND: Some apps expect CSD available without queueing
+ // any input. Queue an empty buffer to get the CSD.
+ buffer->setRange(0, 0);
+ buffer->meta()->clear();
+ buffer->meta()->setInt64("timeUs", 0);
+ if (queueInputBufferInternal(buffer) != OK) {
+ ALOGW("[%s] Error while queueing an empty buffer to get CSD",
+ mName);
+ return UNKNOWN_ERROR;
}
+ clientInputBuffers.pop_front();
}
}
- for (const sp<MediaCodecBuffer> &buffer : toBeQueued) {
- if (queueInputBufferInternal(buffer) != OK) {
- ALOGV("[%s] Error while queueing initial buffers", mName);
- }
+
+ for (const ClientInputBuffer& clientInputBuffer: clientInputBuffers) {
+ mCallback->onInputBufferAvailable(
+ clientInputBuffer.index,
+ clientInputBuffer.buffer);
}
+
return OK;
}
@@ -1394,8 +1362,36 @@
if (mInputSurface != nullptr) {
mInputSurface.reset();
}
+ mPipelineWatcher.lock()->flush();
}
+void CCodecBufferChannel::reset() {
+ stop();
+ {
+ Mutexed<Input>::Locked input(mInput);
+ input->buffers.reset(new DummyInputBuffers(""));
+ input->extraBuffers.flush();
+ }
+ {
+ Mutexed<Output>::Locked output(mOutput);
+ output->buffers.reset();
+ }
+}
+
+void CCodecBufferChannel::release() {
+ mComponent.reset();
+ mInputAllocator.reset();
+ mOutputSurface.lock()->surface.clear();
+ {
+ Mutexed<BlockPools>::Locked blockPools{mBlockPools};
+ blockPools->inputPool.reset();
+ blockPools->outputPoolIntf.reset();
+ }
+ setCrypto(nullptr);
+ setDescrambler(nullptr);
+}
+
+
void CCodecBufferChannel::flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) {
ALOGV("[%s] flush", mName);
{
@@ -1426,9 +1422,11 @@
}
{
Mutexed<Output>::Locked output(mOutput);
- output->buffers->flush(flushedWork);
+ if (output->buffers) {
+ output->buffers->flush(flushedWork);
+ output->buffers->flushStash();
+ }
}
- mReorderStash.lock()->flush();
mPipelineWatcher.lock()->flush();
}
@@ -1464,45 +1462,41 @@
std::unique_ptr<C2Work> work,
const sp<AMessage> &outputFormat,
const C2StreamInitDataInfo::output *initData) {
- if (outputFormat != nullptr) {
+ {
Mutexed<Output>::Locked output(mOutput);
- ALOGD("[%s] onWorkDone: output format changed to %s",
- mName, outputFormat->debugString().c_str());
- output->buffers->setFormat(outputFormat);
-
- AString mediaType;
- if (outputFormat->findString(KEY_MIME, &mediaType)
- && mediaType == MIMETYPE_AUDIO_RAW) {
- int32_t channelCount;
- int32_t sampleRate;
- if (outputFormat->findInt32(KEY_CHANNEL_COUNT, &channelCount)
- && outputFormat->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
- output->buffers->updateSkipCutBuffer(sampleRate, channelCount);
- }
+ if (!output->buffers) {
+ return false;
}
}
- if ((work->input.ordinal.frameIndex - mFirstValidFrameIndex.load()).peek() < 0) {
+ // Whether the output buffer should be reported to the client or not.
+ bool notifyClient = false;
+
+ if (work->result == C2_OK){
+ notifyClient = true;
+ } else if (work->result == C2_NOT_FOUND) {
+ ALOGD("[%s] flushed work; ignored.", mName);
+ } else {
+ // C2_OK and C2_NOT_FOUND are the only results that we accept for processing
+ // the config update.
+ ALOGD("[%s] work failed to complete: %d", mName, work->result);
+ mCCodecCallback->onError(work->result, ACTION_CODE_FATAL);
+ return false;
+ }
+
+ if ((work->input.ordinal.frameIndex -
+ mFirstValidFrameIndex.load()).peek() < 0) {
// Discard frames from previous generation.
ALOGD("[%s] Discard frames from previous generation.", mName);
- return false;
+ notifyClient = false;
}
if (mInputSurface == nullptr && (work->worklets.size() != 1u
|| !work->worklets.front()
- || !(work->worklets.front()->output.flags & C2FrameData::FLAG_INCOMPLETE))) {
- mPipelineWatcher.lock()->onWorkDone(work->input.ordinal.frameIndex.peeku());
- }
-
- if (work->result == C2_NOT_FOUND) {
- ALOGD("[%s] flushed work; ignored.", mName);
- return true;
- }
-
- if (work->result != C2_OK) {
- ALOGD("[%s] work failed to complete: %d", mName, work->result);
- mCCodecCallback->onError(work->result, ACTION_CODE_FATAL);
- return false;
+ || !(work->worklets.front()->output.flags &
+ C2FrameData::FLAG_INCOMPLETE))) {
+ mPipelineWatcher.lock()->onWorkDone(
+ work->input.ordinal.frameIndex.peeku());
}
// NOTE: MediaCodec usage supposedly have only one worklet
@@ -1538,8 +1532,10 @@
case C2PortReorderBufferDepthTuning::CORE_INDEX: {
C2PortReorderBufferDepthTuning::output reorderDepth;
if (reorderDepth.updateFrom(*param)) {
- bool secure = mComponent->getName().find(".secure") != std::string::npos;
- mReorderStash.lock()->setDepth(reorderDepth.value);
+ bool secure = mComponent->getName().find(".secure") !=
+ std::string::npos;
+ mOutput.lock()->buffers->setReorderDepth(
+ reorderDepth.value);
ALOGV("[%s] onWorkDone: updated reorder depth to %u",
mName, reorderDepth.value);
size_t numOutputSlots = mOutput.lock()->numSlots;
@@ -1551,17 +1547,19 @@
output->maxDequeueBuffers += numInputSlots;
}
if (output->surface) {
- output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
+ output->surface->setMaxDequeuedBufferCount(
+ output->maxDequeueBuffers);
}
} else {
- ALOGD("[%s] onWorkDone: failed to read reorder depth", mName);
+ ALOGD("[%s] onWorkDone: failed to read reorder depth",
+ mName);
}
break;
}
case C2PortReorderKeySetting::CORE_INDEX: {
C2PortReorderKeySetting::output reorderKey;
if (reorderKey.updateFrom(*param)) {
- mReorderStash.lock()->setKey(reorderKey.value);
+ mOutput.lock()->buffers->setReorderKey(reorderKey.value);
ALOGV("[%s] onWorkDone: updated reorder key to %u",
mName, reorderKey.value);
} else {
@@ -1576,7 +1574,8 @@
ALOGV("[%s] onWorkDone: updating pipeline delay %u",
mName, pipelineDelay.value);
newPipelineDelay = pipelineDelay.value;
- (void)mPipelineWatcher.lock()->pipelineDelay(pipelineDelay.value);
+ (void)mPipelineWatcher.lock()->pipelineDelay(
+ pipelineDelay.value);
}
}
if (param->forInput()) {
@@ -1585,7 +1584,8 @@
ALOGV("[%s] onWorkDone: updating input delay %u",
mName, inputDelay.value);
newInputDelay = inputDelay.value;
- (void)mPipelineWatcher.lock()->inputDelay(inputDelay.value);
+ (void)mPipelineWatcher.lock()->inputDelay(
+ inputDelay.value);
}
}
if (param->forOutput()) {
@@ -1593,16 +1593,22 @@
if (outputDelay.updateFrom(*param)) {
ALOGV("[%s] onWorkDone: updating output delay %u",
mName, outputDelay.value);
- bool secure = mComponent->getName().find(".secure") != std::string::npos;
- (void)mPipelineWatcher.lock()->outputDelay(outputDelay.value);
+ bool secure = mComponent->getName().find(".secure") !=
+ std::string::npos;
+ (void)mPipelineWatcher.lock()->outputDelay(
+ outputDelay.value);
bool outputBuffersChanged = false;
size_t numOutputSlots = 0;
size_t numInputSlots = mInput.lock()->numSlots;
{
Mutexed<Output>::Locked output(mOutput);
+ if (!output->buffers) {
+ return false;
+ }
output->outputDelay = outputDelay.value;
- numOutputSlots = outputDelay.value + kSmoothnessFactor;
+ numOutputSlots = outputDelay.value +
+ kSmoothnessFactor;
if (output->numSlots < numOutputSlots) {
output->numSlots = numOutputSlots;
if (output->buffers->isArrayMode()) {
@@ -1621,7 +1627,7 @@
mCCodecCallback->onOutputBuffersChanged();
}
- uint32_t depth = mReorderStash.lock()->depth();
+ uint32_t depth = mOutput.lock()->buffers->getReorderDepth();
Mutexed<OutputSurface>::Locked output(mOutputSurface);
output->maxDequeueBuffers = numOutputSlots + depth + kRenderingDepth;
if (!secure) {
@@ -1665,9 +1671,6 @@
ALOGV("[%s] onWorkDone: output EOS", mName);
}
- sp<MediaCodecBuffer> outBuffer;
- size_t index;
-
// WORKAROUND: adjust output timestamp based on client input timestamp and codec
// input timestamp. Codec output timestamp (in the timestamp field) shall correspond to
// the codec input timestamp, but client output timestamp should (reported in timeUs)
@@ -1688,9 +1691,19 @@
worklet->output.ordinal.timestamp.peekll(),
timestamp.peekll());
+ // csd cannot be re-ordered and will always arrive first.
if (initData != nullptr) {
Mutexed<Output>::Locked output(mOutput);
- if (output->buffers->registerCsd(initData, &index, &outBuffer) == OK) {
+ if (output->buffers && outputFormat) {
+ output->buffers->updateSkipCutBuffer(outputFormat);
+ output->buffers->setFormat(outputFormat);
+ }
+ if (!notifyClient) {
+ return false;
+ }
+ size_t index;
+ sp<MediaCodecBuffer> outBuffer;
+ if (output->buffers && output->buffers->registerCsd(initData, &index, &outBuffer) == OK) {
outBuffer->meta()->setInt64("timeUs", timestamp.peek());
outBuffer->meta()->setInt32("flags", MediaCodec::BUFFER_FLAG_CODECCONFIG);
ALOGV("[%s] onWorkDone: csd index = %zu [%p]", mName, index, outBuffer.get());
@@ -1705,10 +1718,10 @@
}
}
- if (!buffer && !flags && outputFormat == nullptr) {
- ALOGV("[%s] onWorkDone: nothing to report from the work (%lld)",
+ if (notifyClient && !buffer && !flags) {
+ ALOGV("[%s] onWorkDone: Not reporting output buffer (%lld)",
mName, work->input.ordinal.frameIndex.peekull());
- return true;
+ notifyClient = false;
}
if (buffer) {
@@ -1727,63 +1740,65 @@
}
{
- Mutexed<ReorderStash>::Locked reorder(mReorderStash);
- reorder->emplace(buffer, timestamp.peek(), flags, worklet->output.ordinal);
- if (flags & MediaCodec::BUFFER_FLAG_EOS) {
- // Flush reorder stash
- reorder->setDepth(0);
+ Mutexed<Output>::Locked output(mOutput);
+ if (!output->buffers) {
+ return false;
}
+ output->buffers->pushToStash(
+ buffer,
+ notifyClient,
+ timestamp.peek(),
+ flags,
+ outputFormat,
+ worklet->output.ordinal);
}
sendOutputBuffers();
return true;
}
void CCodecBufferChannel::sendOutputBuffers() {
- ReorderStash::Entry entry;
- sp<MediaCodecBuffer> outBuffer;
+ OutputBuffers::BufferAction action;
size_t index;
+ sp<MediaCodecBuffer> outBuffer;
+ std::shared_ptr<C2Buffer> c2Buffer;
while (true) {
- Mutexed<ReorderStash>::Locked reorder(mReorderStash);
- if (!reorder->hasPending()) {
- break;
- }
- if (!reorder->pop(&entry)) {
- break;
- }
-
Mutexed<Output>::Locked output(mOutput);
- status_t err = output->buffers->registerBuffer(entry.buffer, &index, &outBuffer);
- if (err != OK) {
- bool outputBuffersChanged = false;
- if (err != WOULD_BLOCK) {
- if (!output->buffers->isArrayMode()) {
- output->buffers = output->buffers->toArrayMode(output->numSlots);
- }
- OutputBuffersArray *array = (OutputBuffersArray *)output->buffers.get();
- array->realloc(entry.buffer);
- outputBuffersChanged = true;
- }
- ALOGV("[%s] sendOutputBuffers: unable to register output buffer", mName);
- reorder->defer(entry);
-
- output.unlock();
- reorder.unlock();
-
- if (outputBuffersChanged) {
- mCCodecCallback->onOutputBuffersChanged();
- }
+ if (!output->buffers) {
return;
}
- output.unlock();
- reorder.unlock();
-
- outBuffer->meta()->setInt64("timeUs", entry.timestamp);
- outBuffer->meta()->setInt32("flags", entry.flags);
- ALOGV("[%s] sendOutputBuffers: out buffer index = %zu [%p] => %p + %zu (%lld)",
- mName, index, outBuffer.get(), outBuffer->data(), outBuffer->size(),
- (long long)entry.timestamp);
- mCallback->onOutputBufferAvailable(index, outBuffer);
+ action = output->buffers->popFromStashAndRegister(
+ &c2Buffer, &index, &outBuffer);
+ switch (action) {
+ case OutputBuffers::SKIP:
+ return;
+ case OutputBuffers::DISCARD:
+ break;
+ case OutputBuffers::NOTIFY_CLIENT:
+ output.unlock();
+ mCallback->onOutputBufferAvailable(index, outBuffer);
+ break;
+ case OutputBuffers::REALLOCATE:
+ if (!output->buffers->isArrayMode()) {
+ output->buffers =
+ output->buffers->toArrayMode(output->numSlots);
+ }
+ static_cast<OutputBuffersArray*>(output->buffers.get())->
+ realloc(c2Buffer);
+ output.unlock();
+ mCCodecCallback->onOutputBuffersChanged();
+ break;
+ case OutputBuffers::RETRY:
+ ALOGV("[%s] sendOutputBuffers: unable to register output buffer",
+ mName);
+ return;
+ default:
+ LOG_ALWAYS_FATAL("[%s] sendOutputBuffers: "
+ "corrupted BufferAction value (%d) "
+ "returned from popFromStashAndRegister.",
+ mName, int(action));
+ return;
+ }
}
}
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.h b/media/codec2/sfplugin/CCodecBufferChannel.h
index 0263211..046c5c3 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.h
+++ b/media/codec2/sfplugin/CCodecBufferChannel.h
@@ -138,6 +138,16 @@
*/
void stop();
+ /**
+ * Stop queueing buffers to the component and release all buffers.
+ */
+ void reset();
+
+ /**
+ * Release all resources.
+ */
+ void release();
+
void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork);
/**
@@ -296,48 +306,6 @@
Mutexed<PipelineWatcher> mPipelineWatcher;
- class ReorderStash {
- public:
- struct Entry {
- inline Entry() : buffer(nullptr), timestamp(0), flags(0), ordinal({0, 0, 0}) {}
- inline Entry(
- const std::shared_ptr<C2Buffer> &b,
- int64_t t,
- int32_t f,
- const C2WorkOrdinalStruct &o)
- : buffer(b), timestamp(t), flags(f), ordinal(o) {}
- std::shared_ptr<C2Buffer> buffer;
- int64_t timestamp;
- int32_t flags;
- C2WorkOrdinalStruct ordinal;
- };
-
- ReorderStash();
-
- void clear();
- void flush();
- void setDepth(uint32_t depth);
- void setKey(C2Config::ordinal_key_t key);
- bool pop(Entry *entry);
- void emplace(
- const std::shared_ptr<C2Buffer> &buffer,
- int64_t timestamp,
- int32_t flags,
- const C2WorkOrdinalStruct &ordinal);
- void defer(const Entry &entry);
- bool hasPending() const;
- uint32_t depth() const { return mDepth; }
-
- private:
- std::list<Entry> mPending;
- std::list<Entry> mStash;
- uint32_t mDepth;
- C2Config::ordinal_key_t mKey;
-
- bool less(const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2);
- };
- Mutexed<ReorderStash> mReorderStash;
-
std::atomic_bool mInputMetEos;
std::once_flag mRenderWarningFlag;
diff --git a/media/codec2/sfplugin/CCodecBuffers.cpp b/media/codec2/sfplugin/CCodecBuffers.cpp
index a9120c4..e58a1e4 100644
--- a/media/codec2/sfplugin/CCodecBuffers.cpp
+++ b/media/codec2/sfplugin/CCodecBuffers.cpp
@@ -21,6 +21,7 @@
#include <C2PlatformSupport.h>
#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/SkipCutBuffer.h>
#include <mediadrm/ICrypto.h>
@@ -155,16 +156,29 @@
setSkipCutBuffer(delay, padding);
}
+void OutputBuffers::updateSkipCutBuffer(
+ const sp<AMessage> &format, bool notify) {
+ AString mediaType;
+ if (format->findString(KEY_MIME, &mediaType)
+ && mediaType == MIMETYPE_AUDIO_RAW) {
+ int32_t channelCount;
+ int32_t sampleRate;
+ if (format->findInt32(KEY_CHANNEL_COUNT, &channelCount)
+ && format->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
+ updateSkipCutBuffer(sampleRate, channelCount);
+ }
+ }
+ if (notify) {
+ mUnreportedFormat = nullptr;
+ }
+}
+
void OutputBuffers::submit(const sp<MediaCodecBuffer> &buffer) {
if (mSkipCutBuffer != nullptr) {
mSkipCutBuffer->submit(buffer);
}
}
-void OutputBuffers::transferSkipCutBuffer(const sp<SkipCutBuffer> &scb) {
- mSkipCutBuffer = scb;
-}
-
void OutputBuffers::setSkipCutBuffer(int32_t skip, int32_t cut) {
if (mSkipCutBuffer != nullptr) {
size_t prevSize = mSkipCutBuffer->size();
@@ -175,6 +189,175 @@
mSkipCutBuffer = new SkipCutBuffer(skip, cut, mChannelCount);
}
+void OutputBuffers::clearStash() {
+ mPending.clear();
+ mReorderStash.clear();
+ mDepth = 0;
+ mKey = C2Config::ORDINAL;
+ mUnreportedFormat = nullptr;
+}
+
+void OutputBuffers::flushStash() {
+ for (StashEntry& e : mPending) {
+ e.notify = false;
+ }
+ for (StashEntry& e : mReorderStash) {
+ e.notify = false;
+ }
+}
+
+uint32_t OutputBuffers::getReorderDepth() const {
+ return mDepth;
+}
+
+void OutputBuffers::setReorderDepth(uint32_t depth) {
+ mPending.splice(mPending.end(), mReorderStash);
+ mDepth = depth;
+}
+
+void OutputBuffers::setReorderKey(C2Config::ordinal_key_t key) {
+ mPending.splice(mPending.end(), mReorderStash);
+ mKey = key;
+}
+
+void OutputBuffers::pushToStash(
+ const std::shared_ptr<C2Buffer>& buffer,
+ bool notify,
+ int64_t timestamp,
+ int32_t flags,
+ const sp<AMessage>& format,
+ const C2WorkOrdinalStruct& ordinal) {
+ bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
+ if (!buffer && eos) {
+ // TRICKY: we may be violating ordering of the stash here. Because we
+ // don't expect any more emplace() calls after this, the ordering should
+ // not matter.
+ mReorderStash.emplace_back(
+ buffer, notify, timestamp, flags, format, ordinal);
+ } else {
+ flags = flags & ~MediaCodec::BUFFER_FLAG_EOS;
+ auto it = mReorderStash.begin();
+ for (; it != mReorderStash.end(); ++it) {
+ if (less(ordinal, it->ordinal)) {
+ break;
+ }
+ }
+ mReorderStash.emplace(it,
+ buffer, notify, timestamp, flags, format, ordinal);
+ if (eos) {
+ mReorderStash.back().flags =
+ mReorderStash.back().flags | MediaCodec::BUFFER_FLAG_EOS;
+ }
+ }
+ while (!mReorderStash.empty() && mReorderStash.size() > mDepth) {
+ mPending.push_back(mReorderStash.front());
+ mReorderStash.pop_front();
+ }
+ ALOGV("[%s] %s: pushToStash -- pending size = %zu", mName, __func__, mPending.size());
+}
+
+OutputBuffers::BufferAction OutputBuffers::popFromStashAndRegister(
+ std::shared_ptr<C2Buffer>* c2Buffer,
+ size_t* index,
+ sp<MediaCodecBuffer>* outBuffer) {
+ if (mPending.empty()) {
+ return SKIP;
+ }
+
+ // Retrieve the first entry.
+ StashEntry &entry = mPending.front();
+
+ *c2Buffer = entry.buffer;
+ sp<AMessage> outputFormat = entry.format;
+
+ // The output format can be processed without a registered slot.
+ if (outputFormat) {
+ ALOGD("[%s] popFromStashAndRegister: output format changed to %s",
+ mName, outputFormat->debugString().c_str());
+ updateSkipCutBuffer(outputFormat, entry.notify);
+ }
+
+ if (entry.notify) {
+ if (outputFormat) {
+ setFormat(outputFormat);
+ } else if (mUnreportedFormat) {
+ outputFormat = mUnreportedFormat;
+ setFormat(outputFormat);
+ }
+ mUnreportedFormat = nullptr;
+ } else {
+ if (outputFormat) {
+ mUnreportedFormat = outputFormat;
+ } else if (!mUnreportedFormat) {
+ mUnreportedFormat = mFormat;
+ }
+ }
+
+ // Flushing mReorderStash because no other buffers should come after output
+ // EOS.
+ if (entry.flags & MediaCodec::BUFFER_FLAG_EOS) {
+ // Flush reorder stash
+ setReorderDepth(0);
+ }
+
+ if (!entry.notify) {
+ mPending.pop_front();
+ return DISCARD;
+ }
+
+ // Try to register the buffer.
+ status_t err = registerBuffer(*c2Buffer, index, outBuffer);
+ if (err != OK) {
+ if (err != WOULD_BLOCK) {
+ return REALLOCATE;
+ }
+ return RETRY;
+ }
+
+ // Append information from the front stash entry to outBuffer.
+ (*outBuffer)->meta()->setInt64("timeUs", entry.timestamp);
+ (*outBuffer)->meta()->setInt32("flags", entry.flags);
+ ALOGV("[%s] popFromStashAndRegister: "
+ "out buffer index = %zu [%p] => %p + %zu (%lld)",
+ mName, *index, outBuffer->get(),
+ (*outBuffer)->data(), (*outBuffer)->size(),
+ (long long)entry.timestamp);
+
+ // The front entry of mPending will be removed now that the registration
+ // succeeded.
+ mPending.pop_front();
+ return NOTIFY_CLIENT;
+}
+
+bool OutputBuffers::popPending(StashEntry *entry) {
+ if (mPending.empty()) {
+ return false;
+ }
+ *entry = mPending.front();
+ mPending.pop_front();
+ return true;
+}
+
+void OutputBuffers::deferPending(const OutputBuffers::StashEntry &entry) {
+ mPending.push_front(entry);
+}
+
+bool OutputBuffers::hasPending() const {
+ return !mPending.empty();
+}
+
+bool OutputBuffers::less(
+ const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) const {
+ switch (mKey) {
+ case C2Config::ORDINAL: return o1.frameIndex < o2.frameIndex;
+ case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
+ case C2Config::CUSTOM: return o1.customOrdinal < o2.customOrdinal;
+ default:
+ ALOGD("Unrecognized key; default to timestamp");
+ return o1.frameIndex < o2.frameIndex;
+ }
+}
+
// LocalBufferPool
constexpr size_t kInitialPoolCapacity = kMaxLinearBufferSize;
@@ -981,6 +1164,16 @@
mImpl.grow(newSize, mAlloc);
}
+void OutputBuffersArray::transferFrom(OutputBuffers* source) {
+ mFormat = source->mFormat;
+ mSkipCutBuffer = source->mSkipCutBuffer;
+ mUnreportedFormat = source->mUnreportedFormat;
+ mPending = std::move(source->mPending);
+ mReorderStash = std::move(source->mReorderStash);
+ mDepth = source->mDepth;
+ mKey = source->mKey;
+}
+
// FlexOutputBuffers
status_t FlexOutputBuffers::registerBuffer(
@@ -1023,13 +1216,12 @@
// track of the flushed work.
}
-std::unique_ptr<OutputBuffers> FlexOutputBuffers::toArrayMode(size_t size) {
+std::unique_ptr<OutputBuffersArray> FlexOutputBuffers::toArrayMode(size_t size) {
std::unique_ptr<OutputBuffersArray> array(new OutputBuffersArray(mComponentName.c_str()));
- array->setFormat(mFormat);
- array->transferSkipCutBuffer(mSkipCutBuffer);
+ array->transferFrom(this);
std::function<sp<Codec2Buffer>()> alloc = getAlloc();
array->initialize(mImpl, size, alloc);
- return std::move(array);
+ return array;
}
size_t FlexOutputBuffers::numClientBuffers() const {
diff --git a/media/codec2/sfplugin/CCodecBuffers.h b/media/codec2/sfplugin/CCodecBuffers.h
index 6244acd..0d4fa81 100644
--- a/media/codec2/sfplugin/CCodecBuffers.h
+++ b/media/codec2/sfplugin/CCodecBuffers.h
@@ -156,6 +156,8 @@
DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
};
+class OutputBuffersArray;
+
class OutputBuffers : public CCodecBuffers {
public:
OutputBuffers(const char *componentName, const char *name = "Output");
@@ -163,8 +165,12 @@
/**
* Register output C2Buffer from the component and obtain corresponding
- * index and MediaCodecBuffer object. Returns false if registration
- * fails.
+ * index and MediaCodecBuffer object.
+ *
+ * Returns:
+ * OK if registration succeeds.
+ * NO_MEMORY if all buffers are available but not compatible.
+ * WOULD_BLOCK if there are compatible buffers, but they are all in use.
*/
virtual status_t registerBuffer(
const std::shared_ptr<C2Buffer> &buffer,
@@ -199,7 +205,7 @@
* shall retain the internal state so that it will honor index and
* buffer from previous calls of registerBuffer().
*/
- virtual std::unique_ptr<OutputBuffers> toArrayMode(size_t size) = 0;
+ virtual std::unique_ptr<OutputBuffersArray> toArrayMode(size_t size) = 0;
/**
* Initialize SkipCutBuffer object.
@@ -208,6 +214,164 @@
int32_t delay, int32_t padding, int32_t sampleRate, int32_t channelCount);
/**
+ * Update SkipCutBuffer from format. The @p format must not be null.
+ * @p notify determines whether the format comes with a buffer that should
+ * be reported to the client or not.
+ */
+ void updateSkipCutBuffer(const sp<AMessage> &format, bool notify = true);
+
+ /**
+ * Output Stash
+ * ============
+ *
+ * The output stash is a place to hold output buffers temporarily before
+ * they are registered to output slots. It has 2 main functions:
+ * 1. Allow reordering of output frames as the codec may produce frames in a
+ * different order.
+ * 2. Act as a "buffer" between the codec and the client because the codec
+ * may produce more buffers than available slots. This excess of codec's
+ * output buffers should be registered to slots later, after the client
+ * has released some slots.
+ *
+ * The stash consists of 2 lists of buffers: mPending and mReorderStash.
+ * mPending is a normal FIFO queue with not size limit, while mReorderStash
+ * is a sorted list with size limit mDepth.
+ *
+ * The normal flow of a non-csd output buffer is as follows:
+ *
+ * |----------------OutputBuffers---------------|
+ * |----------Output stash----------| |
+ * Codec --|-> mReorderStash --> mPending --|-> slots --|-> client
+ * | | |
+ * pushToStash() popFromStashAndRegister()
+ *
+ * The buffer that comes from the codec first enters mReorderStash. The
+ * first buffer in mReorderStash gets moved to mPending when mReorderStash
+ * overflows. Buffers in mPending are registered to slots and given to the
+ * client as soon as slots are available.
+ *
+ * Every output buffer that is not a csd buffer should be put on the stash
+ * by calling pushToStash(), then later registered to a slot by calling
+ * popFromStashAndRegister() before notifying the client with
+ * onOutputBufferAvailable().
+ *
+ * Reordering
+ * ==========
+ *
+ * mReorderStash is a sorted list with a specified size limit. The size
+ * limit can be set by calling setReorderDepth().
+ *
+ * Every buffer in mReorderStash has a C2WorkOrdinalStruct, which contains 3
+ * members, all of which are comparable. Which member of C2WorkOrdinalStruct
+ * should be used for reordering can be chosen by calling setReorderKey().
+ */
+
+ /**
+ * Return the reorder depth---the size of mReorderStash.
+ */
+ uint32_t getReorderDepth() const;
+
+ /**
+ * Set the reorder depth.
+ */
+ void setReorderDepth(uint32_t depth);
+
+ /**
+ * Set the type of "key" to use in comparisons.
+ */
+ void setReorderKey(C2Config::ordinal_key_t key);
+
+ /**
+ * Return whether the output stash has any pending buffers.
+ */
+ bool hasPending() const;
+
+ /**
+ * Flush the stash and reset the depth and the key to their default values.
+ */
+ void clearStash();
+
+ /**
+ * Flush the stash.
+ */
+ void flushStash();
+
+ /**
+ * Push a buffer to the reorder stash.
+ *
+ * @param buffer C2Buffer object from the returned work.
+ * @param notify Whether the returned work contains a buffer that should
+ * be reported to the client. This may be false if the
+ * caller wants to process the buffer without notifying the
+ * client.
+ * @param timestamp Buffer timestamp to report to the client.
+ * @param flags Buffer flags to report to the client.
+ * @param format Buffer format to report to the client.
+ * @param ordinal Ordinal used in reordering. This determines when the
+ * buffer will be popped from the output stash by
+ * `popFromStashAndRegister()`.
+ */
+ void pushToStash(
+ const std::shared_ptr<C2Buffer>& buffer,
+ bool notify,
+ int64_t timestamp,
+ int32_t flags,
+ const sp<AMessage>& format,
+ const C2WorkOrdinalStruct& ordinal);
+
+ enum BufferAction : int {
+ SKIP,
+ DISCARD,
+ NOTIFY_CLIENT,
+ REALLOCATE,
+ RETRY,
+ };
+
+ /**
+ * Try to atomically pop the first buffer from the reorder stash and
+ * register it to an output slot. The function returns a value that
+ * indicates a recommended course of action for the caller.
+ *
+ * If the stash is empty, the function will return `SKIP`.
+ *
+ * If the stash is not empty, the function will peek at the first (oldest)
+ * entry in mPending process the buffer in the entry as follows:
+ * - If the buffer should not be sent to the client, the function will
+ * return `DISCARD`. The stash entry will be removed.
+ * - If the buffer should be sent to the client, the function will attempt
+ * to register the buffer to a slot. The registration may have 3 outcomes
+ * corresponding to the following return values:
+ * - `NOTIFY_CLIENT`: The buffer is successfully registered to a slot. The
+ * output arguments @p index and @p outBuffer will contain valid values
+ * that the caller can use to call onOutputBufferAvailable(). The stash
+ * entry will be removed.
+ * - `REALLOCATE`: The buffer is not registered because it is not
+ * compatible with the current slots (which are available). The caller
+ * should reallocate the OutputBuffers with slots that can fit the
+ * returned @p c2Buffer. The stash entry will not be removed
+ * - `RETRY`: All slots are currently occupied by the client. The caller
+ * should try to call this function again after the client has released
+ * some slots.
+ *
+ * @return What the caller should do afterwards.
+ *
+ * @param[out] c2Buffer Underlying C2Buffer associated to the first buffer
+ * on the stash. This value is guaranteed to be valid
+ * unless the return value is `SKIP`.
+ * @param[out] index Slot index. This value is valid only if the return
+ * value is `NOTIFY_CLIENT`.
+ * @param[out] outBuffer Registered buffer. This value is valid only if the
+ * return valu is `NOTIFY_CLIENT`.
+ */
+ BufferAction popFromStashAndRegister(
+ std::shared_ptr<C2Buffer>* c2Buffer,
+ size_t* index,
+ sp<MediaCodecBuffer>* outBuffer);
+
+protected:
+ sp<SkipCutBuffer> mSkipCutBuffer;
+
+ /**
* Update the SkipCutBuffer object. No-op if it's never initialized.
*/
void updateSkipCutBuffer(int32_t sampleRate, int32_t channelCount);
@@ -217,15 +381,8 @@
*/
void submit(const sp<MediaCodecBuffer> &buffer);
- /**
- * Transfer SkipCutBuffer object to the other Buffers object.
- */
- void transferSkipCutBuffer(const sp<SkipCutBuffer> &scb);
-
-protected:
- sp<SkipCutBuffer> mSkipCutBuffer;
-
private:
+ // SkipCutBuffer
int32_t mDelay;
int32_t mPadding;
int32_t mSampleRate;
@@ -233,7 +390,78 @@
void setSkipCutBuffer(int32_t skip, int32_t cut);
+ // Output stash
+
+ // Output format that has not been made available to the client.
+ sp<AMessage> mUnreportedFormat;
+
+ // Struct for an entry in the output stash (mPending and mReorderStash)
+ struct StashEntry {
+ inline StashEntry()
+ : buffer(nullptr),
+ notify(false),
+ timestamp(0),
+ flags(0),
+ format(),
+ ordinal({0, 0, 0}) {}
+ inline StashEntry(
+ const std::shared_ptr<C2Buffer> &b,
+ bool n,
+ int64_t t,
+ int32_t f,
+ const sp<AMessage> &fmt,
+ const C2WorkOrdinalStruct &o)
+ : buffer(b),
+ notify(n),
+ timestamp(t),
+ flags(f),
+ format(fmt),
+ ordinal(o) {}
+ std::shared_ptr<C2Buffer> buffer;
+ bool notify;
+ int64_t timestamp;
+ int32_t flags;
+ sp<AMessage> format;
+ C2WorkOrdinalStruct ordinal;
+ };
+
+ /**
+ * FIFO queue of stash entries.
+ */
+ std::list<StashEntry> mPending;
+ /**
+ * Sorted list of stash entries.
+ */
+ std::list<StashEntry> mReorderStash;
+ /**
+ * Size limit of mReorderStash.
+ */
+ uint32_t mDepth{0};
+ /**
+ * Choice of key to use in ordering of stash entries in mReorderStash.
+ */
+ C2Config::ordinal_key_t mKey{C2Config::ORDINAL};
+
+ /**
+ * Return false if mPending is empty; otherwise, pop the first entry from
+ * mPending and return true.
+ */
+ bool popPending(StashEntry *entry);
+
+ /**
+ * Push an entry as the first entry of mPending.
+ */
+ void deferPending(const StashEntry &entry);
+
+ /**
+ * Comparison of C2WorkOrdinalStruct based on mKey.
+ */
+ bool less(const C2WorkOrdinalStruct &o1,
+ const C2WorkOrdinalStruct &o2) const;
+
DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
+
+ friend OutputBuffersArray;
};
/**
@@ -416,7 +644,7 @@
size_t *index,
sp<Codec2Buffer> *buffer,
std::function<bool(const sp<Codec2Buffer> &)> match =
- [](const sp<Codec2Buffer> &) { return true; });
+ [](const sp<Codec2Buffer> &buffer) { return (buffer != nullptr); });
/**
* Return the buffer from the client, and get the C2Buffer object back from
@@ -770,7 +998,7 @@
bool isArrayMode() const final { return true; }
- std::unique_ptr<OutputBuffers> toArrayMode(size_t) final {
+ std::unique_ptr<OutputBuffersArray> toArrayMode(size_t) final {
return nullptr;
}
@@ -809,6 +1037,12 @@
*/
void grow(size_t newSize);
+ /**
+ * Transfer the SkipCutBuffer and the output stash from another
+ * OutputBuffers.
+ */
+ void transferFrom(OutputBuffers* source);
+
private:
BuffersArrayImpl mImpl;
std::function<sp<Codec2Buffer>()> mAlloc;
@@ -837,7 +1071,7 @@
void flush(
const std::list<std::unique_ptr<C2Work>> &flushedWork) override;
- std::unique_ptr<OutputBuffers> toArrayMode(size_t size) override;
+ std::unique_ptr<OutputBuffersArray> toArrayMode(size_t size) override;
size_t numClientBuffers() const final;
diff --git a/media/codec2/sfplugin/utils/Android.bp b/media/codec2/sfplugin/utils/Android.bp
index 205abdc..6287221 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,
+ min_sdk_version: "29",
double_loadable: true,
srcs: [
diff --git a/media/codec2/vndk/Android.bp b/media/codec2/vndk/Android.bp
index f3e37e0..6f7acce 100644
--- a/media/codec2/vndk/Android.bp
+++ b/media/codec2/vndk/Android.bp
@@ -7,6 +7,8 @@
// TODO: Remove this when this module is moved back to frameworks/av.
vendor_available: true,
+
+ min_sdk_version: "29",
}
// !!!DO NOT DEPEND ON THIS SHARED LIBRARY DIRECTLY!!!
@@ -14,6 +16,7 @@
cc_library_shared {
name: "libcodec2_vndk",
vendor_available: true,
+ min_sdk_version: "29",
// TODO: b/147147883
double_loadable: true,
@@ -87,6 +90,8 @@
"libcodec2_vndk",
"libutils",
],
+
+ min_sdk_version: "29",
}
// public dependency for implementing Codec 2 framework utilities
diff --git a/media/extractors/Android.bp b/media/extractors/Android.bp
index bb42580..7c4e62f 100644
--- a/media/extractors/Android.bp
+++ b/media/extractors/Android.bp
@@ -24,6 +24,9 @@
"libmediandk#29",
],
+ // extractors are supposed to work on Q(29)
+ min_sdk_version: "29",
+
relative_install_path: "extractors",
compile_multilib: "first",
diff --git a/media/extractors/TEST_MAPPING b/media/extractors/TEST_MAPPING
new file mode 100644
index 0000000..abefb0f
--- /dev/null
+++ b/media/extractors/TEST_MAPPING
@@ -0,0 +1,17 @@
+{
+ "presubmit": [
+ // TODO(b/153661591) enable test once the bug is fixed
+ // This tests the extractor path
+ // {
+ // "name": "GtsYouTubeTestCases",
+ // "options" : [
+ // {
+ // "include-annotation": "android.platform.test.annotations.Presubmit"
+ // },
+ // {
+ // "include-filter": "com.google.android.youtube.gts.SimultaneousClearAndProtectedDecodeTest#testSimultaneousClearAndProtectedDecode"
+ // }
+ // ]
+ // }
+ ]
+}
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index a051648..b88e4e8 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -814,11 +814,13 @@
int32_t sampleRate;
if (!AMediaFormat_getInt32(trackInfo->mMeta, AMEDIAFORMAT_KEY_SAMPLE_RATE,
&sampleRate)) {
+ mbuf->release();
return AMEDIA_ERROR_MALFORMED;
}
int64_t durationUs;
if (!AMediaFormat_getInt64(trackInfo->mMeta, AMEDIAFORMAT_KEY_DURATION,
&durationUs)) {
+ mbuf->release();
return AMEDIA_ERROR_MALFORMED;
}
// TODO: Explore if this can be handled similar to MPEG4 extractor where padding is
@@ -981,6 +983,7 @@
while (mPendingFrames.empty()) {
media_status_t err = readBlock();
if (err != OK) {
+ buffer->release();
clearPendingFrames();
return err;
}
@@ -1000,6 +1003,7 @@
while (mPendingFrames.empty()) {
media_status_t err = readBlock();
if (err != OK) {
+ buffer->release();
clearPendingFrames();
return err;
}
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 54f1fa2..ba24274 100755
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -20,6 +20,7 @@
#include <ctype.h>
#include <inttypes.h>
#include <algorithm>
+#include <map>
#include <memory>
#include <stdint.h>
#include <stdlib.h>
@@ -149,6 +150,7 @@
bool mIsHeif;
bool mIsAudio;
+ bool mIsUsac = false;
sp<ItemTable> mItemTable;
/* Shift start offset (move to earlier time) when media_time > 0,
@@ -204,6 +206,7 @@
Vector<size_t> encryptedsizes;
};
Vector<Sample> mCurrentSamples;
+ std::map<off64_t, uint32_t> mDrmOffsets;
MPEG4Source(const MPEG4Source &);
MPEG4Source &operator=(const MPEG4Source &);
@@ -4454,7 +4457,7 @@
//AOT_SLS = 38, /**< SLS */
//AOT_ER_AAC_ELD = 39, /**< AAC Enhanced Low Delay */
- //AOT_USAC = 42, /**< USAC */
+ AOT_USAC = 42, /**< USAC */
//AOT_SAOC = 43, /**< SAOC */
//AOT_LD_MPEGS = 44, /**< Low Delay MPEG Surround */
@@ -4602,7 +4605,7 @@
ABitReader br(csd, csd_size);
uint32_t objectType = br.getBits(5);
- if (objectType == 31) { // AAC-ELD => additional 6 bits
+ if (objectType == AOT_ESCAPE) { // AAC-ELD => additional 6 bits
objectType = 32 + br.getBits(6);
}
@@ -4979,6 +4982,12 @@
mIsPcm = !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW);
mIsAudio = !strncasecmp(mime, "audio/", 6);
+ int32_t aacObjectType = -1;
+
+ if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_AAC_PROFILE, &aacObjectType)) {
+ mIsUsac = (aacObjectType == AOT_USAC);
+ }
+
if (mIsPcm) {
int32_t numChannels = 0;
int32_t bitsPerSample = 0;
@@ -5153,6 +5162,9 @@
if (chunk_type == FOURCC("moof")) {
mNextMoofOffset = *offset;
break;
+ } else if (chunk_type == FOURCC("mdat")) {
+ parseChunk(offset);
+ continue;
} else if (chunk_size == 0) {
break;
}
@@ -5214,6 +5226,22 @@
// parse DRM info if present
ALOGV("MPEG4Source::parseChunk mdat");
// if saiz/saoi was previously observed, do something with the sampleinfos
+ status_t err = OK;
+ auto kv = mDrmOffsets.lower_bound(*offset);
+ if (kv != mDrmOffsets.end()) {
+ auto drmoffset = kv->first;
+ auto flags = kv->second;
+ mDrmOffsets.erase(kv);
+ ALOGV("mdat chunk_size %" PRIu64 " drmoffset %" PRId64 " offset %" PRId64,
+ chunk_size, drmoffset, *offset);
+ if (chunk_size >= drmoffset - *offset) {
+ err = parseClearEncryptedSizes(drmoffset, false, flags,
+ chunk_size - (drmoffset - *offset));
+ }
+ }
+ if (err != OK) {
+ return err;
+ }
*offset += chunk_size;
break;
}
@@ -5395,8 +5423,10 @@
off64_t drmoffset = mCurrentSampleInfoOffsets[0]; // from moof
drmoffset += mCurrentMoofOffset;
+ mDrmOffsets[drmoffset] = flags;
+ ALOGV("saio drmoffset %" PRId64 " flags %u", drmoffset, flags);
- return parseClearEncryptedSizes(drmoffset, false, 0, mCurrentMoofSize);
+ return OK;
}
status_t MPEG4Source::parseClearEncryptedSizes(
@@ -5951,10 +5981,10 @@
}
uint32_t syncSampleIndex = sampleIndex;
- // assume every audio sample is a sync sample. This works around
+ // assume every non-USAC audio sample is a sync sample. This works around
// seek issues with files that were incorrectly written with an
// empty or single-sample stss block for the audio track
- if (err == OK && !mIsAudio) {
+ if (err == OK && (!mIsAudio || mIsUsac)) {
err = mSampleTable->findSyncSampleNear(
sampleIndex, &syncSampleIndex, findFlags);
}
diff --git a/media/libaaudio/src/binding/AAudioServiceMessage.h b/media/libaaudio/src/binding/AAudioServiceMessage.h
index 3981454..62927a0 100644
--- a/media/libaaudio/src/binding/AAudioServiceMessage.h
+++ b/media/libaaudio/src/binding/AAudioServiceMessage.h
@@ -44,6 +44,8 @@
struct AAudioMessageEvent {
aaudio_service_event_t event;
union {
+ // Align so that 32 and 64-bit code can exchange messages through shared memory.
+ alignas(8)
double dataDouble;
int64_t dataLong;
};
@@ -57,8 +59,10 @@
EVENT,
};
- code what;
+ code what;
union {
+ // Align so that 32 and 64-bit code can exchange messages through shared memory.
+ alignas(8)
AAudioMessageTimestamp timestamp; // what == TIMESTAMP
AAudioMessageEvent event; // what == EVENT
};
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 79fa5ed..4520823 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -288,7 +288,7 @@
requestStop();
}
- logBufferState();
+ logReleaseBufferState();
setState(AAUDIO_STREAM_STATE_CLOSING);
aaudio_handle_t serviceStreamHandle = mServiceStreamHandle;
@@ -783,6 +783,14 @@
adjustedFrames = std::min(actualFrames, adjustedFrames);
}
+ if (adjustedFrames != mBufferSizeInFrames) {
+ android::mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_SETBUFFERSIZE)
+ .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, adjustedFrames)
+ .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getXRunCount())
+ .record();
+ }
+
mBufferSizeInFrames = adjustedFrames;
ALOGV("%s(%d) returns %d", __func__, requestedFrames, adjustedFrames);
return (aaudio_result_t) adjustedFrames;
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 0644368..f5c75ca 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -116,9 +116,10 @@
}
}
-void AudioStream::logBufferState() {
+void AudioStream::logReleaseBufferState() {
if (mMetricsId.size() > 0) {
android::mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_RELEASE)
.set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t) getBufferSize())
.set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getXRunCount())
.record();
@@ -140,6 +141,7 @@
case AAUDIO_STREAM_STATE_PAUSED:
case AAUDIO_STREAM_STATE_STOPPING:
case AAUDIO_STREAM_STATE_STOPPED:
+ case AAUDIO_STREAM_STATE_FLUSHING:
case AAUDIO_STREAM_STATE_FLUSHED:
break; // Proceed with starting.
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index 613a092..fb71c36 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -115,7 +115,7 @@
// log to MediaMetrics
virtual void logOpen();
- void logBufferState();
+ void logReleaseBufferState();
/**
* Free any hardware or system resources from the open() call.
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index 853c0db..b0dc59e 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -292,7 +292,7 @@
// Then call it from here
if (getState() != AAUDIO_STREAM_STATE_CLOSING) {
mAudioRecord->removeAudioDeviceCallback(mDeviceCallback);
- logBufferState();
+ logReleaseBufferState();
mAudioRecord.clear();
mFixedBlockWriter.close();
return AudioStream::release_l();
@@ -345,13 +345,17 @@
// Enable callback before starting AudioRecord to avoid shutting
// down because of a race condition.
mCallbackEnabled.store(true);
+ aaudio_stream_state_t originalState = getState();
+ // Set before starting the callback so that we are in the correct state
+ // before updateStateMachine() can be called by the callback.
+ setState(AAUDIO_STREAM_STATE_STARTING);
mFramesWritten.reset32(); // service writes frames
mTimestampPosition.reset32();
status_t err = mAudioRecord->start(); // resets position to zero
if (err != OK) {
+ mCallbackEnabled.store(false);
+ setState(originalState);
return AAudioConvert_androidToAAudioResult(err);
- } else {
- setState(AAUDIO_STREAM_STATE_STARTING);
}
return AAUDIO_OK;
}
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 1120f05..4869480 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -254,7 +254,7 @@
aaudio_result_t AudioStreamTrack::release_l() {
if (getState() != AAUDIO_STREAM_STATE_CLOSING) {
mAudioTrack->removeAudioDeviceCallback(mDeviceCallback);
- logBufferState();
+ logReleaseBufferState();
// TODO Investigate why clear() causes a hang in test_various.cpp
// if I call close() from a data callback.
// But the same thing in AudioRecord is OK!
@@ -307,11 +307,15 @@
// Enable callback before starting AudioTrack to avoid shutting
// down because of a race condition.
mCallbackEnabled.store(true);
+ aaudio_stream_state_t originalState = getState();
+ // Set before starting the callback so that we are in the correct state
+ // before updateStateMachine() can be called by the callback.
+ setState(AAUDIO_STREAM_STATE_STARTING);
err = mAudioTrack->start();
if (err != OK) {
+ mCallbackEnabled.store(false);
+ setState(originalState);
return AAudioConvert_androidToAAudioResult(err);
- } else {
- setState(AAUDIO_STREAM_STATE_STARTING);
}
return AAUDIO_OK;
}
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp
index a6e5f70..8935d57 100644
--- a/media/libaaudio/tests/Android.bp
+++ b/media/libaaudio/tests/Android.bp
@@ -233,6 +233,7 @@
srcs: ["test_steal_exclusive.cpp"],
shared_libs: [
"libaaudio",
+ "liblog",
"libbinder",
"libcutils",
"libutils",
diff --git a/media/libaaudio/tests/test_steal_exclusive.cpp b/media/libaaudio/tests/test_steal_exclusive.cpp
index 2a05910..05c560d 100644
--- a/media/libaaudio/tests/test_steal_exclusive.cpp
+++ b/media/libaaudio/tests/test_steal_exclusive.cpp
@@ -47,137 +47,271 @@
*/
#include <atomic>
+#include <mutex>
#include <stdio.h>
#include <thread>
#include <unistd.h>
+#include <android/log.h>
+
#include <aaudio/AAudio.h>
+#include <aaudio/AAudioTesting.h>
#define DEFAULT_TIMEOUT_NANOS ((int64_t)1000000000)
#define SOLO_DURATION_MSEC 2000
#define DUET_DURATION_MSEC 8000
#define SLEEP_DURATION_MSEC 500
+#define MODULE_NAME "stealAudio"
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
+
static const char * s_sharingModeToText(aaudio_sharing_mode_t mode) {
return (mode == AAUDIO_SHARING_MODE_EXCLUSIVE) ? "EXCLUSIVE"
: ((mode == AAUDIO_SHARING_MODE_SHARED) ? "SHARED"
: AAudio_convertResultToText(mode));
}
+static const char * s_performanceModeToText(aaudio_performance_mode_t mode) {
+ return (mode == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) ? "LOWLAT"
+ : ((mode == AAUDIO_PERFORMANCE_MODE_NONE) ? "NONE"
+ : AAudio_convertResultToText(mode));
+}
+
+static aaudio_data_callback_result_t s_myDataCallbackProc(
+ AAudioStream * /* stream */,
+ void *userData,
+ void *audioData,
+ int32_t numFrames);
+
static void s_myErrorCallbackProc(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
-struct AudioEngine {
- AAudioStream *stream = nullptr;
- std::thread *thread = nullptr;
- aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT;
+class AudioEngine {
+public:
+
+ AudioEngine(const char *name) {
+ mName = name;
+ }
// These counters are read and written by the callback and the main thread.
- std::atomic<int32_t> framesRead{};
std::atomic<int32_t> framesCalled{};
std::atomic<int32_t> callbackCount{};
+ std::atomic<aaudio_sharing_mode_t> sharingMode{};
+ std::atomic<aaudio_performance_mode_t> performanceMode{};
+ std::atomic<bool> isMMap{false};
+ void setMaxRetries(int maxRetries) {
+ mMaxRetries = maxRetries;
+ }
+
+ void setOpenDelayMillis(int openDelayMillis) {
+ mOpenDelayMillis = openDelayMillis;
+ }
+
+ void restartStream() {
+ int retriesLeft = mMaxRetries;
+ aaudio_result_t result;
+ do {
+ closeAudioStream();
+ if (mOpenDelayMillis) usleep(mOpenDelayMillis * 1000);
+ openAudioStream(mDirection, mRequestedSharingMode);
+ // It is possible for the stream to be disconnected, or stolen between the time
+ // it is opened and when it is started. If that happens then try again.
+ // If it was stolen then it should succeed the second time because there will already be
+ // a SHARED stream, which will not get stolen.
+ result = AAudioStream_requestStart(mStream);
+ printf("%s: AAudioStream_requestStart() returns %s\n",
+ mName.c_str(),
+ AAudio_convertResultToText(result));
+ } while (retriesLeft-- > 0 && result != AAUDIO_OK);
+ }
+
+ aaudio_data_callback_result_t onAudioReady(
+ void * /*audioData */,
+ int32_t numFrames) {
+ callbackCount++;
+ framesCalled += numFrames;
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ aaudio_result_t openAudioStream(aaudio_direction_t direction,
+ aaudio_sharing_mode_t requestedSharingMode) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ AAudioStreamBuilder *builder = nullptr;
+ mDirection = direction;
+ mRequestedSharingMode = requestedSharingMode;
+
+ // Use an AAudioStreamBuilder to contain requested parameters.
+ aaudio_result_t result = AAudio_createStreamBuilder(&builder);
+ if (result != AAUDIO_OK) {
+ printf("AAudio_createStreamBuilder returned %s",
+ AAudio_convertResultToText(result));
+ return result;
+ }
+
+ // Request stream properties.
+ AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
+ AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+ AAudioStreamBuilder_setSharingMode(builder, mRequestedSharingMode);
+ AAudioStreamBuilder_setDirection(builder, direction);
+ AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, this);
+ AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, this);
+
+ // Create an AAudioStream using the Builder.
+ result = AAudioStreamBuilder_openStream(builder, &mStream);
+ AAudioStreamBuilder_delete(builder);
+ builder = nullptr;
+ if (result != AAUDIO_OK) {
+ printf("AAudioStreamBuilder_openStream returned %s",
+ AAudio_convertResultToText(result));
+ }
+
+ // See what kind of stream we actually opened.
+ int32_t deviceId = AAudioStream_getDeviceId(mStream);
+ sharingMode = AAudioStream_getSharingMode(mStream);
+ performanceMode = AAudioStream_getPerformanceMode(mStream);
+ isMMap = AAudioStream_isMMapUsed(mStream);
+ printf("%s: opened: deviceId = %3d, sharingMode = %s, perf = %s, %s --------\n",
+ mName.c_str(),
+ deviceId,
+ s_sharingModeToText(sharingMode),
+ s_performanceModeToText(performanceMode),
+ (isMMap ? "MMAP" : "Legacy")
+ );
+
+ return result;
+ }
+
+ aaudio_result_t closeAudioStream() {
+ std::lock_guard<std::mutex> lock(mLock);
+ aaudio_result_t result = AAUDIO_OK;
+ if (mStream != nullptr) {
+ result = AAudioStream_close(mStream);
+ if (result != AAUDIO_OK) {
+ printf("AAudioStream_close returned %s\n",
+ AAudio_convertResultToText(result));
+ }
+ mStream = nullptr;
+ }
+ return result;
+ }
+
+ /**
+ * @return 0 is OK, -1 for error
+ */
+ int checkEnginePositions() {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (mStream == nullptr) return 0;
+
+ const int64_t framesRead = AAudioStream_getFramesRead(mStream);
+ const int64_t framesWritten = AAudioStream_getFramesWritten(mStream);
+ const int32_t delta = (int32_t)(framesWritten - framesRead);
+ printf("%s: playing framesRead = %7d, framesWritten = %7d"
+ ", delta = %4d, framesCalled = %6d, callbackCount = %4d\n",
+ mName.c_str(),
+ (int32_t) framesRead,
+ (int32_t) framesWritten,
+ delta,
+ framesCalled.load(),
+ callbackCount.load()
+ );
+ if (delta > AAudioStream_getBufferCapacityInFrames(mStream)) {
+ printf("ERROR - delta > capacity\n");
+ return -1;
+ }
+ return 0;
+ }
+
+ aaudio_result_t start() {
+ std::lock_guard<std::mutex> lock(mLock);
+ reset();
+ if (mStream == nullptr) return 0;
+ return AAudioStream_requestStart(mStream);
+ }
+
+ aaudio_result_t stop() {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (mStream == nullptr) return 0;
+ return AAudioStream_requestStop(mStream);
+ }
+
+ bool hasAdvanced() {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (mStream == nullptr) return 0;
+ if (mDirection == AAUDIO_DIRECTION_OUTPUT) {
+ return AAudioStream_getFramesRead(mStream) > 0;
+ } else {
+ return AAudioStream_getFramesWritten(mStream) > 0;
+ }
+ }
+
+ aaudio_result_t verify() {
+ int errorCount = 0;
+ if (hasAdvanced()) {
+ printf("%s: stream is running => PASS\n", mName.c_str());
+ } else {
+ errorCount++;
+ printf("%s: stream should be running => FAIL!!\n", mName.c_str());
+ }
+
+ if (isMMap) {
+ printf("%s: data path is MMAP => PASS\n", mName.c_str());
+ } else {
+ errorCount++;
+ printf("%s: data path is Legacy! => FAIL\n", mName.c_str());
+ }
+
+ // Check for PASS/FAIL
+ if (sharingMode == AAUDIO_SHARING_MODE_SHARED) {
+ printf("%s: mode is SHARED => PASS\n", mName.c_str());
+ } else {
+ errorCount++;
+ printf("%s: modes is EXCLUSIVE => FAIL!!\n", mName.c_str());
+ }
+ return errorCount ? AAUDIO_ERROR_INVALID_FORMAT : AAUDIO_OK;
+ }
+
+private:
void reset() {
- framesRead.store(0);
framesCalled.store(0);
callbackCount.store(0);
}
+
+ AAudioStream *mStream = nullptr;
+ aaudio_direction_t mDirection = AAUDIO_DIRECTION_OUTPUT;
+ aaudio_sharing_mode_t mRequestedSharingMode = AAUDIO_UNSPECIFIED;
+ std::mutex mLock;
+ std::string mName;
+ int mMaxRetries = 1;
+ int mOpenDelayMillis = 0;
};
// Callback function that fills the audio output buffer.
static aaudio_data_callback_result_t s_myDataCallbackProc(
- AAudioStream *stream,
+ AAudioStream * /* stream */,
void *userData,
void *audioData,
int32_t numFrames
) {
- (void) audioData;
- (void) numFrames;
- AudioEngine *engine = (struct AudioEngine *)userData;
- engine->callbackCount++;
-
- engine->framesRead = (int32_t)AAudioStream_getFramesRead(stream);
- engine->framesCalled += numFrames;
- return AAUDIO_CALLBACK_RESULT_CONTINUE;
-}
-
-static aaudio_result_t s_OpenAudioStream(struct AudioEngine *engine,
- aaudio_direction_t direction) {
- AAudioStreamBuilder *builder = nullptr;
- engine->direction = direction;
-
- // Use an AAudioStreamBuilder to contain requested parameters.
- aaudio_result_t result = AAudio_createStreamBuilder(&builder);
- if (result != AAUDIO_OK) {
- printf("AAudio_createStreamBuilder returned %s",
- AAudio_convertResultToText(result));
- return result;
- }
-
- // Request stream properties.
- AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
- AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
- AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE);
- AAudioStreamBuilder_setDirection(builder, direction);
- AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, engine);
- AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, engine);
-
- // Create an AAudioStream using the Builder.
- result = AAudioStreamBuilder_openStream(builder, &engine->stream);
- AAudioStreamBuilder_delete(builder);
- builder = nullptr;
- if (result != AAUDIO_OK) {
- printf("AAudioStreamBuilder_openStream returned %s",
- AAudio_convertResultToText(result));
- }
-
- // See see what kind of stream we actually opened.
- int32_t deviceId = AAudioStream_getDeviceId(engine->stream);
- aaudio_sharing_mode_t actualSharingMode = AAudioStream_getSharingMode(engine->stream);
- printf("-------- opened: deviceId = %3d, actualSharingMode = %s\n",
- deviceId,
- s_sharingModeToText(actualSharingMode));
-
- return result;
-}
-
-static aaudio_result_t s_CloseAudioStream(struct AudioEngine *engine) {
- aaudio_result_t result = AAUDIO_OK;
- if (engine->stream != nullptr) {
- result = AAudioStream_close(engine->stream);
- if (result != AAUDIO_OK) {
- printf("AAudioStream_close returned %s\n",
- AAudio_convertResultToText(result));
- }
- engine->stream = nullptr;
- }
- return result;
+ AudioEngine *engine = (AudioEngine *)userData;
+ return engine->onAudioReady(audioData, numFrames);
}
static void s_myRestartStreamProc(void *userData) {
+ LOGI("%s() called", __func__);
printf("%s() - restart in separate thread\n", __func__);
AudioEngine *engine = (AudioEngine *) userData;
- int retriesLeft = 1;
- aaudio_result_t result;
- do {
- s_CloseAudioStream(engine);
- s_OpenAudioStream(engine, engine->direction);
- // It is possible for the stream to be disconnected, or stolen between the time
- // it is opened and when it is started. If that happens then try again.
- // If it was stolen then it should succeed the second time because there will already be
- // a SHARED stream, which will not get stolen.
- result = AAudioStream_requestStart(engine->stream);
- printf("%s() - AAudioStream_requestStart() returns %s\n", __func__,
- AAudio_convertResultToText(result));
- } while (retriesLeft-- > 0 && result != AAUDIO_OK);
+ engine->restartStream();
}
static void s_myErrorCallbackProc(
AAudioStream * /* stream */,
void *userData,
aaudio_result_t error) {
+ LOGI("%s() called", __func__);
printf("%s() - error = %s\n", __func__, AAudio_convertResultToText(error));
// Handle error on a separate thread.
std::thread t(s_myRestartStreamProc, userData);
@@ -185,48 +319,28 @@
}
static void s_usage() {
- printf("test_steal_exclusive [-i]\n");
+ printf("test_steal_exclusive [-i] [-r{maxRetries}] [-d{delay}] -s\n");
printf(" -i direction INPUT, otherwise OUTPUT\n");
+ printf(" -d delay open by milliseconds, default = 0\n");
+ printf(" -r max retries in the error callback, default = 1\n");
+ printf(" -s try to open in SHARED mode\n");
}
-/**
- * @return 0 is OK, -1 for error
- */
-static int s_checkEnginePositions(AudioEngine *engine) {
- if (engine->stream == nullptr) return 0; // race condition with onError procs!
-
- const int64_t framesRead = AAudioStream_getFramesRead(engine->stream);
- const int64_t framesWritten = AAudioStream_getFramesWritten(engine->stream);
- const int32_t delta = (int32_t)(framesWritten - framesRead);
- printf("playing framesRead = %7d, framesWritten = %7d"
- ", delta = %4d, framesCalled = %6d, callbackCount = %4d\n",
- (int32_t) framesRead,
- (int32_t) framesWritten,
- delta,
- engine->framesCalled.load(),
- engine->callbackCount.load()
- );
- if (delta > AAudioStream_getBufferCapacityInFrames(engine->stream)) {
- printf("ERROR - delta > capacity\n");
- return -1;
- }
- return 0;
-}
-
-int main(int argc, char **argv) {
- (void) argc;
- (void) argv;
- struct AudioEngine victim;
- struct AudioEngine thief;
+int main(int argc, char ** argv) {
+ AudioEngine victim("victim");
+ AudioEngine thief("thief");
aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT;
aaudio_result_t result = AAUDIO_OK;
int errorCount = 0;
+ int maxRetries = 1;
+ int openDelayMillis = 0;
+ aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
// Make printf print immediately so that debug info is not stuck
// in a buffer if we hang or crash.
setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
- printf("Test Stealing an EXCLUSIVE stream V1.0\n");
+ printf("Test interaction between streams V1.1\n");
printf("\n");
for (int i = 1; i < argc; i++) {
@@ -234,9 +348,18 @@
if (arg[0] == '-') {
char option = arg[1];
switch (option) {
+ case 'd':
+ openDelayMillis = atoi(&arg[2]);
+ break;
case 'i':
direction = AAUDIO_DIRECTION_INPUT;
break;
+ case 'r':
+ maxRetries = atoi(&arg[2]);
+ break;
+ case 's':
+ requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
+ break;
default:
s_usage();
exit(EXIT_FAILURE);
@@ -249,16 +372,35 @@
}
}
- result = s_OpenAudioStream(&victim, direction);
+ victim.setOpenDelayMillis(openDelayMillis);
+ thief.setOpenDelayMillis(openDelayMillis);
+ victim.setMaxRetries(maxRetries);
+ thief.setMaxRetries(maxRetries);
+
+ result = victim.openAudioStream(direction, requestedSharingMode);
if (result != AAUDIO_OK) {
printf("s_OpenAudioStream victim returned %s\n",
AAudio_convertResultToText(result));
errorCount++;
}
- victim.reset();
+
+ if (victim.sharingMode == requestedSharingMode) {
+ printf("Victim modes is %s => OK\n", s_sharingModeToText(requestedSharingMode));
+ } else {
+ printf("Victim modes should be %s => test not valid!\n",
+ s_sharingModeToText(requestedSharingMode));
+ goto onerror;
+ }
+
+ if (victim.isMMap) {
+ printf("Victim data path is MMAP => OK\n");
+ } else {
+ printf("Victim data path is Legacy! => test not valid\n");
+ goto onerror;
+ }
// Start stream.
- result = AAudioStream_requestStart(victim.stream);
+ result = victim.start();
printf("AAudioStream_requestStart(VICTIM) returned %d >>>>>>>>>>>>>>>>>>>>>>\n", result);
if (result != AAUDIO_OK) {
errorCount++;
@@ -267,77 +409,69 @@
if (result == AAUDIO_OK) {
const int watchLoops = SOLO_DURATION_MSEC / SLEEP_DURATION_MSEC;
for (int i = watchLoops; i > 0; i--) {
- errorCount += s_checkEnginePositions(&victim) ? 1 : 0;
+ errorCount += victim.checkEnginePositions() ? 1 : 0;
usleep(SLEEP_DURATION_MSEC * 1000);
}
}
- printf("Try to start the THIEF stream that may steal the VICTIM MMAP resource -----\n");
- result = s_OpenAudioStream(&thief, direction);
+ printf("Trying to start the THIEF stream, which may steal the VICTIM MMAP resource -----\n");
+ result = thief.openAudioStream(direction, requestedSharingMode);
if (result != AAUDIO_OK) {
printf("s_OpenAudioStream victim returned %s\n",
AAudio_convertResultToText(result));
errorCount++;
}
- thief.reset();
// Start stream.
- result = AAudioStream_requestStart(thief.stream);
+ result = thief.start();
printf("AAudioStream_requestStart(THIEF) returned %d >>>>>>>>>>>>>>>>>>>>>>\n", result);
if (result != AAUDIO_OK) {
errorCount++;
}
- printf("You might enjoy plugging in a headset now to see what happens...\n");
+
+ // Give stream time to advance.
+ usleep(SLEEP_DURATION_MSEC * 1000);
+
+ if (victim.verify()) {
+ errorCount++;
+ goto onerror;
+ }
+ if (thief.verify()) {
+ errorCount++;
+ goto onerror;
+ }
+
+ LOGI("Both streams running. Ask user to plug in headset. ====");
+ printf("\n====\nPlease PLUG IN A HEADSET now!\n====\n\n");
if (result == AAUDIO_OK) {
const int watchLoops = DUET_DURATION_MSEC / SLEEP_DURATION_MSEC;
for (int i = watchLoops; i > 0; i--) {
- printf("victim: ");
- errorCount += s_checkEnginePositions(&victim) ? 1 : 0;
- printf(" thief: ");
- errorCount += s_checkEnginePositions(&thief) ? 1 : 0;
+ errorCount += victim.checkEnginePositions() ? 1 : 0;
+ errorCount += thief.checkEnginePositions() ? 1 : 0;
usleep(SLEEP_DURATION_MSEC * 1000);
}
}
- // Check for PASS/FAIL
- aaudio_sharing_mode_t victimSharingMode = AAudioStream_getSharingMode(victim.stream);
- aaudio_sharing_mode_t thiefSharingMode = AAudioStream_getSharingMode(thief.stream);
- printf("victimSharingMode = %s, thiefSharingMode = %s, - ",
- s_sharingModeToText(victimSharingMode),
- s_sharingModeToText(thiefSharingMode));
- if ((victimSharingMode == AAUDIO_SHARING_MODE_SHARED)
- && (thiefSharingMode == AAUDIO_SHARING_MODE_SHARED)) {
- printf("Both modes are SHARED => PASS\n");
- } else {
- errorCount++;
- printf("Both modes should be SHARED => FAIL!!\n");
- }
+ errorCount += victim.verify() ? 1 : 0;
+ errorCount += thief.verify() ? 1 : 0;
- const int64_t victimFramesRead = AAudioStream_getFramesRead(victim.stream);
- const int64_t thiefFramesRead = AAudioStream_getFramesRead(thief.stream);
- printf("victimFramesRead = %d, thiefFramesRead = %d, - ",
- (int)victimFramesRead, (int)thiefFramesRead);
- if (victimFramesRead > 0 && thiefFramesRead > 0) {
- printf("Both streams are running => PASS\n");
- } else {
- errorCount++;
- printf("Both streams should be running => FAIL!!\n");
- }
-
- result = AAudioStream_requestStop(victim.stream);
+ result = victim.stop();
printf("AAudioStream_requestStop() returned %d <<<<<<<<<<<<<<<<<<<<<\n", result);
if (result != AAUDIO_OK) {
+ printf("stop result = %d = %s\n", result, AAudio_convertResultToText(result));
errorCount++;
}
- result = AAudioStream_requestStop(thief.stream);
+ result = thief.stop();
printf("AAudioStream_requestStop() returned %d <<<<<<<<<<<<<<<<<<<<<\n", result);
if (result != AAUDIO_OK) {
+ printf("stop result = %d = %s\n", result, AAudio_convertResultToText(result));
errorCount++;
}
- s_CloseAudioStream(&victim);
- s_CloseAudioStream(&thief);
+onerror:
+ victim.closeAudioStream();
+ thief.closeAudioStream();
printf("aaudio result = %d = %s\n", result, AAudio_convertResultToText(result));
printf("test %s\n", errorCount ? "FAILED" : "PASSED");
diff --git a/media/libaaudio/tests/test_various.cpp b/media/libaaudio/tests/test_various.cpp
index 1c26615..a20c799 100644
--- a/media/libaaudio/tests/test_various.cpp
+++ b/media/libaaudio/tests/test_various.cpp
@@ -124,7 +124,7 @@
}
enum FunctionToCall {
- CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH
+ CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH, CALL_RELEASE
};
void checkStateTransition(aaudio_performance_mode_t perfMode,
@@ -177,11 +177,27 @@
} else if (originalState == AAUDIO_STREAM_STATE_PAUSED) {
ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
inputState = AAUDIO_STREAM_STATE_PAUSING;
+ } else if (originalState == AAUDIO_STREAM_STATE_FLUSHING) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+ // We can only flush() after pause is complete.
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ AAUDIO_STREAM_STATE_PAUSING,
+ &state,
+ 1000 * NANOS_PER_MILLISECOND));
+ ASSERT_EQ(AAUDIO_STREAM_STATE_PAUSED, state);
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestFlush(aaudioStream));
+ // That will put the stream into the FLUSHING state.
+ // The FLUSHING state will persist until we process functionToCall.
+ // That is because the transition to FLUSHED is caused by the callback,
+ // or by calling write() or waitForStateChange(). But those will not
+ // occur.
+ } else if (originalState == AAUDIO_STREAM_STATE_CLOSING) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_release(aaudioStream));
}
}
}
- // Wait until past transitional state.
+ // Wait until we get past the transitional state if requested.
if (inputState != AAUDIO_STREAM_STATE_UNINITIALIZED) {
ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
inputState,
@@ -208,12 +224,20 @@
EXPECT_EQ(expectedResult, AAudioStream_requestFlush(aaudioStream));
transitionalState = AAUDIO_STREAM_STATE_FLUSHING;
break;
+ case FunctionToCall::CALL_RELEASE:
+ EXPECT_EQ(expectedResult, AAudioStream_release(aaudioStream));
+ // Set to UNINITIALIZED so the waitForStateChange() below will
+ // will return immediately with the current state.
+ transitionalState = AAUDIO_STREAM_STATE_UNINITIALIZED;
+ break;
}
- EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- transitionalState,
- &state,
- 1000 * NANOS_PER_MILLISECOND));
+ EXPECT_EQ(AAUDIO_OK,
+ AAudioStream_waitForStateChange(aaudioStream,
+ transitionalState,
+ &state,
+ 1000 * NANOS_PER_MILLISECOND));
+
// We should not change state when a function fails.
if (expectedResult != AAUDIO_OK) {
ASSERT_EQ(originalState, expectedState);
@@ -493,6 +517,88 @@
AAUDIO_STREAM_STATE_FLUSHED);
}
+// FLUSHING ================================================================
+TEST(test_various, aaudio_state_lowlat_flushing_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_FLUSHING,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_flushing_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_FLUSHING,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_flushing_release) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_FLUSHING,
+ FunctionToCall::CALL_RELEASE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_CLOSING);
+}
+
+TEST(test_various, aaudio_state_none_flushing_release) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_FLUSHING,
+ FunctionToCall::CALL_RELEASE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_CLOSING);
+}
+
+TEST(test_various, aaudio_state_lowlat_starting_release) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTING,
+ FunctionToCall::CALL_RELEASE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_CLOSING);
+}
+
+TEST(test_various, aaudio_state_none_starting_release) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTING,
+ FunctionToCall::CALL_RELEASE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_CLOSING);
+}
+
+// CLOSING ================================================================
+TEST(test_various, aaudio_state_lowlat_closing_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_CLOSING,
+ FunctionToCall::CALL_START,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_CLOSING);
+}
+
+TEST(test_various, aaudio_state_none_closing_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_CLOSING,
+ FunctionToCall::CALL_START,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_CLOSING);
+}
+
+TEST(test_various, aaudio_state_lowlat_closing_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_CLOSING,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_CLOSING);
+}
+
+TEST(test_various, aaudio_state_none_closing_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_CLOSING,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_CLOSING);
+}
+
// ==========================================================================
TEST(test_various, aaudio_set_buffer_size) {
diff --git a/media/libaudioclient/Android.bp b/media/libaudioclient/Android.bp
index 0d20f20..0c40cbb 100644
--- a/media/libaudioclient/Android.bp
+++ b/media/libaudioclient/Android.bp
@@ -1,6 +1,8 @@
cc_library_headers {
name: "libaudioclient_headers",
vendor_available: true,
+ min_sdk_version: "29",
+
header_libs: [
"libaudiofoundation_headers",
],
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index 0bbceef..df47def 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -405,7 +405,7 @@
? AMEDIAMETRICS_PROP_CALLERNAME_VALUE_UNKNOWN
: mCallerName.c_str())
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_START)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mActive))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)status)
.record(); });
@@ -481,7 +481,7 @@
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_STOP)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mActive))
.record(); });
@@ -771,7 +771,7 @@
// use case 3: obtain/release mode
(mTransfer == TRANSFER_OBTAIN);
if (!useCaseAllowed) {
- ALOGW("%s(%d): AUDIO_INPUT_FLAG_FAST denied, incompatible transfer = %s",
+ ALOGD("%s(%d): AUDIO_INPUT_FLAG_FAST denied, incompatible transfer = %s",
__func__, mPortId,
convertTransferToText(mTransfer));
mFlags = (audio_input_flags_t) (mFlags & ~(AUDIO_INPUT_FLAG_FAST |
@@ -923,10 +923,10 @@
mMetricsId = std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_RECORD) + std::to_string(mPortId);
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATE)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
// the following are immutable (at least until restore)
- .set(AMEDIAMETRICS_PROP_FLAGS, (int32_t)mFlags)
- .set(AMEDIAMETRICS_PROP_ORIGINALFLAGS, (int32_t)mOrigFlags)
+ .set(AMEDIAMETRICS_PROP_FLAGS, toString(mFlags).c_str())
+ .set(AMEDIAMETRICS_PROP_ORIGINALFLAGS, toString(mOrigFlags).c_str())
.set(AMEDIAMETRICS_PROP_SESSIONID, (int32_t)mSessionId)
.set(AMEDIAMETRICS_PROP_TRACKID, mPortId)
.set(AMEDIAMETRICS_PROP_SOURCE, toString(mAttributes.source).c_str())
@@ -1387,7 +1387,7 @@
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_RESTORE)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mActive))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)result)
.set(AMEDIAMETRICS_PROP_WHERE, from)
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index ca80dc4..32129f0 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -639,9 +639,17 @@
status_t AudioTrack::start()
{
- const int64_t beginNs = systemTime();
AutoMutex lock(mLock);
+ if (mState == STATE_ACTIVE) {
+ return INVALID_OPERATION;
+ }
+
+ ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
+
+ // Defer logging here due to OpenSL ES repeated start calls.
+ // TODO(b/154868033) after fix, restore this logging back to the beginning of start().
+ const int64_t beginNs = systemTime();
status_t status = NO_ERROR; // logged: make sure to set this before returning.
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
@@ -650,17 +658,11 @@
? AMEDIAMETRICS_PROP_CALLERNAME_VALUE_UNKNOWN
: mCallerName.c_str())
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_START)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mState))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)status)
.record(); });
- ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
-
- if (mState == STATE_ACTIVE) {
- status = INVALID_OPERATION;
- return status;
- }
mInUnderrun = true;
@@ -783,10 +785,11 @@
mediametrics::Defer defer([&]() {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_STOP)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mState))
+ .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t)mProxy->getBufferSizeInFrames())
+ .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getUnderrunCount_l())
.record();
- logBufferSizeUnderruns();
});
ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
@@ -845,7 +848,7 @@
mediametrics::Defer defer([&]() {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_FLUSH)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mState))
.record(); });
@@ -886,7 +889,7 @@
mediametrics::Defer defer([&]() {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_PAUSE)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mState))
.record(); });
@@ -1139,16 +1142,6 @@
return NO_ERROR;
}
-void AudioTrack::logBufferSizeUnderruns() {
- LOG_ALWAYS_FATAL_IF(mMetricsId.size() == 0, "mMetricsId is empty!");
- ALOGD("%s(), mMetricsId = %s", __func__, mMetricsId.c_str());
- // FIXME THis hangs! Why?
-// android::mediametrics::LogItem(mMetricsId)
-// .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t) getBufferSizeInFrames())
-// .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getUnderrunCount())
-// .record();
-}
-
ssize_t AudioTrack::setBufferSizeInFrames(size_t bufferSizeInFrames)
{
AutoMutex lock(mLock);
@@ -1163,7 +1156,11 @@
ssize_t originalBufferSize = mProxy->getBufferSizeInFrames();
ssize_t finalBufferSize = mProxy->setBufferSizeInFrames((uint32_t) bufferSizeInFrames);
if (originalBufferSize != finalBufferSize) {
- logBufferSizeUnderruns();
+ android::mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_SETBUFFERSIZE)
+ .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t)mProxy->getBufferSizeInFrames())
+ .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t)getUnderrunCount_l())
+ .record();
}
return finalBufferSize;
}
@@ -1632,7 +1629,7 @@
mAwaitBoost = true;
}
} else {
- ALOGW("%s(%d): AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %zu -> %zu",
+ ALOGD("%s(%d): AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %zu -> %zu",
__func__, mPortId, mReqFrameCount, mFrameCount);
}
}
@@ -1715,15 +1712,15 @@
// The creation of the audio track by AudioFlinger (in the code above)
// is the first log of the AudioTrack and must be present before
// any AudioTrack client logs will be accepted.
+
mMetricsId = std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK) + std::to_string(mPortId);
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATE)
// the following are immutable
- .set(AMEDIAMETRICS_PROP_FLAGS, (int32_t)mFlags)
- .set(AMEDIAMETRICS_PROP_ORIGINALFLAGS, (int32_t)mOrigFlags)
+ .set(AMEDIAMETRICS_PROP_FLAGS, toString(mFlags).c_str())
+ .set(AMEDIAMETRICS_PROP_ORIGINALFLAGS, toString(mOrigFlags).c_str())
.set(AMEDIAMETRICS_PROP_SESSIONID, (int32_t)mSessionId)
.set(AMEDIAMETRICS_PROP_TRACKID, mPortId) // dup from key
- .set(AMEDIAMETRICS_PROP_STREAMTYPE, toString(mStreamType).c_str())
.set(AMEDIAMETRICS_PROP_CONTENTTYPE, toString(mAttributes.content_type).c_str())
.set(AMEDIAMETRICS_PROP_USAGE, toString(mAttributes.usage).c_str())
.set(AMEDIAMETRICS_PROP_THREADID, (int32_t)output.outputId)
@@ -2440,7 +2437,7 @@
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_RESTORE)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(systemTime() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mState))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)result)
.set(AMEDIAMETRICS_PROP_WHERE, from)
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index 17af7d4..0dbd842 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -1274,8 +1274,6 @@
std::string mMetricsId; // GUARDED_BY(mLock), could change in createTrack_l().
std::string mCallerName; // for example "aaudio"
- void logBufferSizeUnderruns();
-
private:
class AudioTrackCallback : public media::BnAudioTrackCallback {
public:
diff --git a/media/libaudiofoundation/Android.bp b/media/libaudiofoundation/Android.bp
index 93bc4d9..548b080 100644
--- a/media/libaudiofoundation/Android.bp
+++ b/media/libaudiofoundation/Android.bp
@@ -1,6 +1,8 @@
cc_library_headers {
name: "libaudiofoundation_headers",
vendor_available: true,
+ min_sdk_version: "29",
+
export_include_dirs: ["include"],
header_libs: [
"libaudio_system_headers",
diff --git a/media/libaudiofoundation/DeviceDescriptorBase.cpp b/media/libaudiofoundation/DeviceDescriptorBase.cpp
index ef7576e..3dbe37d 100644
--- a/media/libaudiofoundation/DeviceDescriptorBase.cpp
+++ b/media/libaudiofoundation/DeviceDescriptorBase.cpp
@@ -80,9 +80,28 @@
toAudioPortConfig(&port->active_config);
port->id = mId;
port->ext.device.type = mDeviceTypeAddr.mType;
+ port->ext.device.encapsulation_modes = mEncapsulationModes;
+ port->ext.device.encapsulation_metadata_types = mEncapsulationMetadataTypes;
(void)audio_utils_strlcpy_zerofill(port->ext.device.address, mDeviceTypeAddr.getAddress());
}
+status_t DeviceDescriptorBase::setEncapsulationModes(uint32_t encapsulationModes) {
+ if ((encapsulationModes & ~AUDIO_ENCAPSULATION_MODE_ALL_POSITION_BITS) != 0) {
+ return BAD_VALUE;
+ }
+ mEncapsulationModes = encapsulationModes & ~(1 << AUDIO_ENCAPSULATION_MODE_NONE);
+ return NO_ERROR;
+}
+
+status_t DeviceDescriptorBase::setEncapsulationMetadataTypes(uint32_t encapsulationMetadataTypes) {
+ if ((encapsulationMetadataTypes & ~AUDIO_ENCAPSULATION_METADATA_TYPE_ALL_POSITION_BITS) != 0) {
+ return BAD_VALUE;
+ }
+ mEncapsulationMetadataTypes =
+ encapsulationMetadataTypes & ~(1 << AUDIO_ENCAPSULATION_METADATA_TYPE_NONE);
+ return NO_ERROR;
+}
+
void DeviceDescriptorBase::dump(std::string *dst, int spaces, int index,
const char* extraInfo, bool verbose) const
{
@@ -98,6 +117,12 @@
dst->append(base::StringPrintf("%*s- type: %-48s\n",
spaces, "", ::android::toString(mDeviceTypeAddr.mType).c_str()));
+ dst->append(base::StringPrintf(
+ "%*s- supported encapsulation modes: %u", spaces, "", mEncapsulationModes));
+ dst->append(base::StringPrintf(
+ "%*s- supported encapsulation metadata types: %u",
+ spaces, "", mEncapsulationMetadataTypes));
+
if (mDeviceTypeAddr.mAddress.size() != 0) {
dst->append(base::StringPrintf(
"%*s- address: %-32s\n", spaces, "", mDeviceTypeAddr.getAddress()));
@@ -135,6 +160,8 @@
if ((status = AudioPort::writeToParcel(parcel)) != NO_ERROR) return status;
if ((status = AudioPortConfig::writeToParcel(parcel)) != NO_ERROR) return status;
if ((status = parcel->writeParcelable(mDeviceTypeAddr)) != NO_ERROR) return status;
+ if ((status = parcel->writeUint32(mEncapsulationModes)) != NO_ERROR) return status;
+ if ((status = parcel->writeUint32(mEncapsulationMetadataTypes)) != NO_ERROR) return status;
return status;
}
@@ -144,6 +171,8 @@
if ((status = AudioPort::readFromParcel(parcel)) != NO_ERROR) return status;
if ((status = AudioPortConfig::readFromParcel(parcel)) != NO_ERROR) return status;
if ((status = parcel->readParcelable(&mDeviceTypeAddr)) != NO_ERROR) return status;
+ if ((status = parcel->readUint32(&mEncapsulationModes)) != NO_ERROR) return status;
+ if ((status = parcel->readUint32(&mEncapsulationMetadataTypes)) != NO_ERROR) return status;
return status;
}
diff --git a/media/libaudiofoundation/include/media/DeviceDescriptorBase.h b/media/libaudiofoundation/include/media/DeviceDescriptorBase.h
index 4c03667..af04721 100644
--- a/media/libaudiofoundation/include/media/DeviceDescriptorBase.h
+++ b/media/libaudiofoundation/include/media/DeviceDescriptorBase.h
@@ -55,6 +55,9 @@
// AudioPort
virtual void toAudioPort(struct audio_port *port) const;
+ status_t setEncapsulationModes(uint32_t encapsulationModes);
+ status_t setEncapsulationMetadataTypes(uint32_t encapsulationMetadataTypes);
+
void dump(std::string *dst, int spaces, int index,
const char* extraInfo = nullptr, bool verbose = true) const;
void log() const;
@@ -67,6 +70,8 @@
protected:
AudioDeviceTypeAddr mDeviceTypeAddr;
+ uint32_t mEncapsulationModes = 0;
+ uint32_t mEncapsulationMetadataTypes = 0;
};
using DeviceDescriptorBaseVector = std::vector<sp<DeviceDescriptorBase>>;
diff --git a/media/libaudiofoundation/tests/audiofoundation_parcelable_test.cpp b/media/libaudiofoundation/tests/audiofoundation_parcelable_test.cpp
index 5baa072..068b5d8 100644
--- a/media/libaudiofoundation/tests/audiofoundation_parcelable_test.cpp
+++ b/media/libaudiofoundation/tests/audiofoundation_parcelable_test.cpp
@@ -131,6 +131,9 @@
desc->setAudioProfiles(getAudioProfileVectorForTest());
desc->applyAudioPortConfig(&TEST_AUDIO_PORT_CONFIG);
desc->setAddress("DeviceDescriptorBaseTestAddress");
+ ASSERT_EQ(desc->setEncapsulationModes(1 << AUDIO_ENCAPSULATION_MODE_HANDLE), NO_ERROR);
+ ASSERT_EQ(desc->setEncapsulationMetadataTypes(
+ AUDIO_ENCAPSULATION_METADATA_TYPE_ALL_POSITION_BITS), NO_ERROR);
ASSERT_EQ(data.writeParcelable(*desc), NO_ERROR);
data.setDataPosition(0);
diff --git a/media/libaudiohal/impl/ConversionHelperHidl.cpp b/media/libaudiohal/impl/ConversionHelperHidl.cpp
index f29b0f3..ebed5fd 100644
--- a/media/libaudiohal/impl/ConversionHelperHidl.cpp
+++ b/media/libaudiohal/impl/ConversionHelperHidl.cpp
@@ -41,16 +41,25 @@
bool keepFormatValue = halKeys.size() == 2 &&
(halKeys.get(String8(AudioParameter::keyStreamSupportedChannels), value) == NO_ERROR ||
halKeys.get(String8(AudioParameter::keyStreamSupportedSamplingRates), value) == NO_ERROR);
+ // When querying encapsulation capabilities, "keyRouting=<value>" pair is used to identify
+ // the device. We need to transform it into a single key string so that it is carried over to
+ // the legacy HAL via HIDL.
+ bool keepRoutingValue =
+ halKeys.get(String8(AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_MODES),
+ value) == NO_ERROR ||
+ halKeys.get(String8(AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_METADATA_TYPES),
+ value) == NO_ERROR;
for (size_t i = 0; i < halKeys.size(); ++i) {
String8 key;
status_t status = halKeys.getAt(i, key);
if (status != OK) return status;
- if (keepFormatValue && key == AudioParameter::keyFormat) {
- AudioParameter formatParam;
+ if ((keepFormatValue && key == AudioParameter::keyFormat) ||
+ (keepRoutingValue && key == AudioParameter::keyRouting)) {
+ AudioParameter keepValueParam;
halKeys.getAt(i, key, value);
- formatParam.add(key, value);
- key = formatParam.toString();
+ keepValueParam.add(key, value);
+ key = keepValueParam.toString();
}
(*hidlKeys)[i] = key.string();
}
diff --git a/media/libaudioprocessing/AudioMixerBase.cpp b/media/libaudioprocessing/AudioMixerBase.cpp
index 64f91fe..a54e22f 100644
--- a/media/libaudioprocessing/AudioMixerBase.cpp
+++ b/media/libaudioprocessing/AudioMixerBase.cpp
@@ -1500,7 +1500,7 @@
ALOGVV("track__Resample\n");
mResampler->setSampleRate(sampleRate);
const bool ramp = needsRamp();
- if (MIXTYPE == MIXTYPE_MONOEXPAND || MIXTYPE == MIXTYPE_STEREOEXPAND
+ if (MIXTYPE == MIXTYPE_MONOEXPAND || MIXTYPE == MIXTYPE_STEREOEXPAND // custom volume handling
|| ramp || aux != NULL) {
// if ramp: resample with unity gain to temp buffer and scale/mix in 2nd step.
// if aux != NULL: resample with unity gain to temp buffer then apply send level.
diff --git a/media/libaudioprocessing/AudioMixerOps.h b/media/libaudioprocessing/AudioMixerOps.h
index 2748182..80bd093 100644
--- a/media/libaudioprocessing/AudioMixerOps.h
+++ b/media/libaudioprocessing/AudioMixerOps.h
@@ -236,7 +236,8 @@
|| MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
|| MIXTYPE == MIXTYPE_STEREOEXPAND);
auto proc = [](auto& a, const auto& b) {
- if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL) {
+ if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
+ || MIXTYPE == MIXTYPE_STEREOEXPAND) {
a += b;
} else {
a = b;
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index be3f995..3fd3fc3 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -1,6 +1,8 @@
cc_library_headers {
name: "libmedia_headers",
vendor_available: true,
+ min_sdk_version: "29",
+
export_include_dirs: ["include"],
header_libs: [
"libbase_headers",
@@ -31,10 +33,8 @@
path: "aidl",
}
-aidl_interface {
- name: "resourcemanager_aidl_interface",
- unstable: true,
- local_include_dir: "aidl",
+filegroup {
+ name: "resourcemanager_aidl",
srcs: [
"aidl/android/media/IResourceManagerClient.aidl",
"aidl/android/media/IResourceManagerService.aidl",
@@ -43,6 +43,16 @@
"aidl/android/media/MediaResourceParcel.aidl",
"aidl/android/media/MediaResourcePolicyParcel.aidl",
],
+ path: "aidl",
+}
+
+aidl_interface {
+ name: "resourcemanager_aidl_interface",
+ unstable: true,
+ local_include_dir: "aidl",
+ srcs: [
+ ":resourcemanager_aidl",
+ ],
}
cc_library_shared {
@@ -184,6 +194,8 @@
cc_library_static {
name: "libmedia_midiiowrapper",
+ min_sdk_version: "29",
+
srcs: ["MidiIoWrapper.cpp"],
static_libs: [
diff --git a/media/libmediahelper/Android.bp b/media/libmediahelper/Android.bp
index 72edeec..6fcbc7b 100644
--- a/media/libmediahelper/Android.bp
+++ b/media/libmediahelper/Android.bp
@@ -1,6 +1,7 @@
cc_library_headers {
name: "libmedia_helper_headers",
vendor_available: true,
+ min_sdk_version: "29",
export_include_dirs: ["include"],
}
diff --git a/media/libmediahelper/AudioParameter.cpp b/media/libmediahelper/AudioParameter.cpp
index 9f34035..fc8306c 100644
--- a/media/libmediahelper/AudioParameter.cpp
+++ b/media/libmediahelper/AudioParameter.cpp
@@ -53,6 +53,10 @@
const char * const AudioParameter::valueListSeparator = AUDIO_PARAMETER_VALUE_LIST_SEPARATOR;
const char * const AudioParameter::keyReconfigA2dp = AUDIO_PARAMETER_RECONFIG_A2DP;
const char * const AudioParameter::keyReconfigA2dpSupported = AUDIO_PARAMETER_A2DP_RECONFIG_SUPPORTED;
+// const char * const AudioParameter::keyDeviceSupportedEncapsulationModes =
+// AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_MODES;
+// const char * const AudioParameter::keyDeviceSupportedEncapsulationMetadataTypes =
+// AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_METADATA_TYPES;
AudioParameter::AudioParameter(const String8& keyValuePairs)
{
diff --git a/media/libmediahelper/include/media/AudioParameter.h b/media/libmediahelper/include/media/AudioParameter.h
index 3c190f2..66d8dfb 100644
--- a/media/libmediahelper/include/media/AudioParameter.h
+++ b/media/libmediahelper/include/media/AudioParameter.h
@@ -92,6 +92,18 @@
static const char * const keyReconfigA2dp;
static const char * const keyReconfigA2dpSupported;
+ // For querying device supported encapsulation capabilities. All returned values are integer,
+ // which are bit fields composed from using encapsulation capability values as position bits.
+ // Encapsulation capability values are defined in audio_encapsulation_mode_t and
+ // audio_encapsulation_metadata_type_t. For instance, if the supported encapsulation mode is
+ // AUDIO_ENCAPSULATION_MODE_ELEMENTARY_STREAM, the returned value is
+ // "supEncapsulationModes=1 << AUDIO_ENCAPSULATION_MODE_HANDLE".
+ // When querying device supported encapsulation capabilities, the key should use with device
+ // type and address so that it is able to identify the device. The device will be a key. The
+ // device type will be the value of key AUDIO_PARAMETER_STREAM_ROUTING.
+ // static const char * const keyDeviceSupportedEncapsulationModes;
+ // static const char * const keyDeviceSupportedEncapsulationMetadataTypes;
+
String8 toString() const { return toStringImpl(true); }
String8 keysToString() const { return toStringImpl(false); }
diff --git a/media/libmediametrics/include/MediaMetricsConstants.h b/media/libmediametrics/include/MediaMetricsConstants.h
index ec0e133..84388c9 100644
--- a/media/libmediametrics/include/MediaMetricsConstants.h
+++ b/media/libmediametrics/include/MediaMetricsConstants.h
@@ -37,6 +37,9 @@
// They must be appended with another value to make a key.
#define AMEDIAMETRICS_KEY_PREFIX_AUDIO "audio."
+// Device related key prefix.
+#define AMEDIAMETRICS_KEY_PREFIX_AUDIO_DEVICE AMEDIAMETRICS_KEY_PREFIX_AUDIO "device."
+
// The AudioMmap key appends the "trackId" to the prefix.
// This is the AudioFlinger equivalent of the AAudio Stream.
// TODO: unify with AMEDIAMETRICS_KEY_PREFIX_AUDIO_STREAM
@@ -92,6 +95,7 @@
#define AMEDIAMETRICS_PROP_SUFFIX_CHAR_DUPLICATES_ALLOWED '#'
#define AMEDIAMETRICS_PROP_ALLOWUID "_allowUid" // int32_t, allow client uid to post
+#define AMEDIAMETRICS_PROP_AUDIOMODE "audioMode" // string (audio.flinger)
#define AMEDIAMETRICS_PROP_AUXEFFECTID "auxEffectId" // int32 (AudioTrack)
#define AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES "bufferSizeFrames" // int32
#define AMEDIAMETRICS_PROP_BUFFERCAPACITYFRAMES "bufferCapacityFrames" // int32
@@ -112,6 +116,7 @@
#define AMEDIAMETRICS_PROP_DURATIONNS "durationNs" // int64 duration time span
#define AMEDIAMETRICS_PROP_ENCODING "encoding" // string value of format
#define AMEDIAMETRICS_PROP_EVENT "event#" // string value (often func name)
+#define AMEDIAMETRICS_PROP_EXECUTIONTIMENS "executionTimeNs" // time to execute the event
// TODO: fix inconsistency in flags: AudioRecord / AudioTrack int32, AudioThread string
#define AMEDIAMETRICS_PROP_FLAGS "flags"
@@ -142,10 +147,12 @@
#define AMEDIAMETRICS_PROP_THREADID "threadId" // int32 value io handle
#define AMEDIAMETRICS_PROP_THROTTLEMS "throttleMs" // double
#define AMEDIAMETRICS_PROP_TRACKID "trackId" // int32 port id of track/record
+#define AMEDIAMETRICS_PROP_TRAITS "traits" // string
#define AMEDIAMETRICS_PROP_TYPE "type" // string (thread type)
#define AMEDIAMETRICS_PROP_UNDERRUN "underrun" // int32
#define AMEDIAMETRICS_PROP_UNDERRUNFRAMES "underrunFrames" // int64_t from Thread
#define AMEDIAMETRICS_PROP_USAGE "usage" // string attributes (ATrack)
+#define AMEDIAMETRICS_PROP_VOICEVOLUME "voiceVolume" // double (audio.flinger)
#define AMEDIAMETRICS_PROP_VOLUME_LEFT "volume.left" // double (AudioTrack)
#define AMEDIAMETRICS_PROP_VOLUME_RIGHT "volume.right" // double (AudioTrack)
#define AMEDIAMETRICS_PROP_WHERE "where" // string value
@@ -169,8 +176,12 @@
#define AMEDIAMETRICS_PROP_EVENT_VALUE_OPEN "open"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_PAUSE "pause" // AudioTrack
#define AMEDIAMETRICS_PROP_EVENT_VALUE_READPARAMETERS "readParameters" // Thread
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_RELEASE "release"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_RESTORE "restore"
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETMODE "setMode" // AudioFlinger
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETBUFFERSIZE "setBufferSize" // AudioTrack
#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETPLAYBACKPARAM "setPlaybackParam" // AudioTrack
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOICEVOLUME "setVoiceVolume" // AudioFlinger
#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOLUME "setVolume" // AudioTrack
#define AMEDIAMETRICS_PROP_EVENT_VALUE_START "start" // AudioTrack, AudioRecord
#define AMEDIAMETRICS_PROP_EVENT_VALUE_STOP "stop" // AudioTrack, AudioRecord
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index cf7f423..7897959 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -2011,6 +2011,7 @@
}
if (mOutputFormat == OUTPUT_FORMAT_MPEG_4 || mOutputFormat == OUTPUT_FORMAT_THREE_GPP) {
(*meta)->setInt32(kKeyEmptyTrackMalFormed, true);
+ (*meta)->setInt32(kKey4BitTrackIds, true);
}
}
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index 0eaa503..439dbe8 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -432,7 +432,7 @@
}
if (mDataSource == nullptr) {
ALOGD("FileSource local");
- mDataSource = new PlayerServiceFileSource(mFd.get(), mOffset, mLength);
+ mDataSource = new PlayerServiceFileSource(dup(mFd.get()), mOffset, mLength);
}
}
@@ -1159,7 +1159,7 @@
readBuffer(MEDIA_TRACK_TYPE_VIDEO, seekTimeUs, mode, &actualTimeUs);
if (mode != MediaPlayerSeekMode::SEEK_CLOSEST) {
- seekTimeUs = actualTimeUs;
+ seekTimeUs = std::max<int64_t>(0, actualTimeUs);
}
mVideoLastDequeueTimeUs = actualTimeUs;
}
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 24afd43..dc144b2 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -116,6 +116,7 @@
updateMetrics("destructor");
logMetrics("destructor");
+ Mutex::Autolock autoLock(mMetricsLock);
if (mMetricsItem != NULL) {
delete mMetricsItem;
mMetricsItem = NULL;
@@ -129,6 +130,8 @@
status_t NuPlayerDriver::setUID(uid_t uid) {
mPlayer->setUID(uid);
mClientUid = uid;
+
+ Mutex::Autolock autoLock(mMetricsLock);
if (mMetricsItem) {
mMetricsItem->setUid(mClientUid);
}
@@ -542,10 +545,50 @@
}
ALOGV("updateMetrics(%p) from %s at state %d", this, where, mState);
- // gather the final stats for this record
+ // avoid nested locks by gathering our data outside of the metrics lock.
+
+ // final track statistics for this record
Vector<sp<AMessage>> trackStats;
mPlayer->getStats(&trackStats);
+ // getDuration() uses mLock
+ int duration_ms = -1;
+ getDuration(&duration_ms);
+
+ mPlayer->updateInternalTimers();
+
+ int64_t playingTimeUs;
+ int64_t rebufferingTimeUs;
+ int32_t rebufferingEvents;
+ bool rebufferingAtExit;
+ {
+ Mutex::Autolock autoLock(mLock);
+
+ playingTimeUs = mPlayingTimeUs;
+ rebufferingTimeUs = mRebufferingTimeUs;
+ rebufferingEvents = mRebufferingEvents;
+ rebufferingAtExit = mRebufferingAtExit;
+ }
+
+ // finish the rest of the gathering under our mutex to avoid metrics races.
+ // some of the fields we read are updated under mLock.
+ Mutex::Autolock autoLock(mMetricsLock);
+
+ if (mMetricsItem == NULL) {
+ return;
+ }
+
+ mMetricsItem->setInt64(kPlayerDuration, duration_ms);
+ mMetricsItem->setInt64(kPlayerPlaying, (playingTimeUs+500)/1000 );
+
+ if (rebufferingEvents != 0) {
+ mMetricsItem->setInt64(kPlayerRebuffering, (rebufferingTimeUs+500)/1000 );
+ mMetricsItem->setInt32(kPlayerRebufferingCount, rebufferingEvents);
+ mMetricsItem->setInt32(kPlayerRebufferingAtExit, rebufferingAtExit);
+ }
+
+ mMetricsItem->setCString(kPlayerDataSourceType, mPlayer->getDataSourceType());
+
if (trackStats.size() > 0) {
for (size_t i = 0; i < trackStats.size(); ++i) {
const sp<AMessage> &stats = trackStats.itemAt(i);
@@ -590,26 +633,6 @@
}
}
}
-
- // always provide duration and playing time, even if they have 0/unknown values.
-
- // getDuration() uses mLock for mutex -- careful where we use it.
- int duration_ms = -1;
- getDuration(&duration_ms);
- mMetricsItem->setInt64(kPlayerDuration, duration_ms);
-
- mPlayer->updateInternalTimers();
-
- mMetricsItem->setInt64(kPlayerPlaying, (mPlayingTimeUs+500)/1000 );
-
- if (mRebufferingEvents != 0) {
- mMetricsItem->setInt64(kPlayerRebuffering, (mRebufferingTimeUs+500)/1000 );
- mMetricsItem->setInt32(kPlayerRebufferingCount, mRebufferingEvents);
- mMetricsItem->setInt32(kPlayerRebufferingAtExit, mRebufferingAtExit);
-
- }
-
- mMetricsItem->setCString(kPlayerDataSourceType, mPlayer->getDataSourceType());
}
@@ -619,6 +642,9 @@
}
ALOGV("logMetrics(%p) from %s at state %d", this, where, mState);
+ // ensure mMetricsItem stability while we write it out
+ Mutex::Autolock autoLock(mMetricsLock);
+
if (mMetricsItem == NULL || mMetricsItem->isEnabled() == false) {
return;
}
@@ -777,11 +803,16 @@
status_t NuPlayerDriver::getParameter(int key, Parcel *reply) {
- if (key == FOURCC('m','t','r','X') && mMetricsItem != NULL) {
+ if (key == FOURCC('m','t','r','X')) {
// mtrX -- a play on 'metrics' (not matrix)
// gather current info all together, parcel it, and send it back
updateMetrics("api");
- mMetricsItem->writeToParcel(reply);
+
+ // ensure mMetricsItem stability while writing to parcel
+ Mutex::Autolock autoLock(mMetricsLock);
+ if (mMetricsItem != NULL) {
+ mMetricsItem->writeToParcel(reply);
+ }
return OK;
}
@@ -1005,12 +1036,15 @@
// 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 && mMetricsItem != NULL) {
- mMetricsItem->setInt32(kPlayerError, ext1);
- if (ext2 != 0) {
- mMetricsItem->setInt32(kPlayerErrorCode, ext2);
+ if (msg == MEDIA_ERROR) {
+ Mutex::Autolock autoLock(mMetricsLock);
+ if (mMetricsItem != NULL) {
+ mMetricsItem->setInt32(kPlayerError, ext1);
+ if (ext2 != 0) {
+ mMetricsItem->setInt32(kPlayerErrorCode, ext2);
+ }
+ mMetricsItem->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 7001f4a..f4b1968 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -142,6 +142,7 @@
uint32_t mPlayerFlags;
mediametrics::Item *mMetricsItem;
+ mutable Mutex mMetricsLock;
uid_t mClientUid;
bool mAtEOS;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp
index b5142ed..a532603 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp
@@ -34,8 +34,6 @@
mTargetHandler(targetHandler),
mEOS(false),
mSendDataNotification(true) {
- mSource->setListener(this);
-
mMemoryDealer = new MemoryDealer(kNumBuffers * kBufferSize);
for (size_t i = 0; i < kNumBuffers; ++i) {
sp<IMemory> mem = mMemoryDealer->allocate(kBufferSize);
diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
index 14f1323..bec27d3 100644
--- a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
@@ -79,6 +79,7 @@
void NuPlayer::StreamingSource::start() {
mStreamListener = new NuPlayerStreamListener(mSource, NULL);
+ mSource->setListener(mStreamListener);
uint32_t sourceFlags = mSource->flags();
diff --git a/media/libstagefright/ACodecBufferChannel.cpp b/media/libstagefright/ACodecBufferChannel.cpp
index fa13f32..88b15ae 100644
--- a/media/libstagefright/ACodecBufferChannel.cpp
+++ b/media/libstagefright/ACodecBufferChannel.cpp
@@ -22,11 +22,14 @@
#include <C2Buffer.h>
+#include <Codec2BufferUtils.h>
+
#include <android/hardware/cas/native/1.0/IDescrambler.h>
#include <android/hardware/drm/1.0/types.h>
#include <binder/MemoryDealer.h>
#include <hidlmemory/FrameworkUtils.h>
#include <media/openmax/OMX_Core.h>
+#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/MediaCodec.h>
@@ -91,15 +94,27 @@
}
status_t ACodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
- if (mDealer != nullptr) {
- return -ENOSYS;
- }
std::shared_ptr<const std::vector<const BufferInfo>> array(
std::atomic_load(&mInputBuffers));
BufferInfoIterator it = findClientBuffer(array, buffer);
if (it == array->end()) {
return -ENOENT;
}
+ if (it->mClientBuffer != it->mCodecBuffer) {
+ // Copy metadata from client to codec buffer.
+ it->mCodecBuffer->meta()->clear();
+ int64_t timeUs;
+ CHECK(it->mClientBuffer->meta()->findInt64("timeUs", &timeUs));
+ it->mCodecBuffer->meta()->setInt64("timeUs", timeUs);
+ int32_t eos;
+ if (it->mClientBuffer->meta()->findInt32("eos", &eos)) {
+ it->mCodecBuffer->meta()->setInt32("eos", eos);
+ }
+ int32_t csd;
+ if (it->mClientBuffer->meta()->findInt32("csd", &csd)) {
+ it->mCodecBuffer->meta()->setInt32("csd", csd);
+ }
+ }
ALOGV("queueInputBuffer #%d", it->mBufferId);
sp<AMessage> msg = mInputBufferFilled->dup();
msg->setObject("buffer", it->mCodecBuffer);
@@ -267,16 +282,30 @@
}
C2ConstLinearBlock block{c2Buffer->data().linearBlocks().front()};
C2ReadView view{block.map().get()};
- if (view.capacity() > buffer->capacity()) {
- return -ENOSYS;
- }
- memcpy(buffer->base(), view.data(), view.capacity());
- buffer->setRange(0, view.capacity());
+ size_t copyLength = std::min(size_t(view.capacity()), buffer->capacity());
+ ALOGV_IF(view.capacity() > buffer->capacity(),
+ "view.capacity() = %zu, buffer->capacity() = %zu",
+ view.capacity(), buffer->capacity());
+ memcpy(buffer->base(), view.data(), copyLength);
+ buffer->setRange(0, copyLength);
break;
}
case C2BufferData::GRAPHIC: {
- // TODO
- return -ENOSYS;
+ sp<ABuffer> imageData;
+ if (!buffer->format()->findBuffer("image-data", &imageData)) {
+ return -ENOSYS;
+ }
+ if (c2Buffer->data().graphicBlocks().size() != 1u) {
+ return -ENOSYS;
+ }
+ C2ConstGraphicBlock block{c2Buffer->data().graphicBlocks().front()};
+ const C2GraphicView view{block.map().get()};
+ status_t err = ImageCopy(
+ buffer->base(), (const MediaImage2 *)(imageData->base()), view);
+ if (err != OK) {
+ return err;
+ }
+ break;
}
case C2BufferData::LINEAR_CHUNKS: [[fallthrough]];
case C2BufferData::GRAPHIC_CHUNKS: [[fallthrough]];
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index a8fea90..3bccb7b 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -2,10 +2,21 @@
name: "libstagefright_headers",
export_include_dirs: ["include"],
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
}
cc_library_static {
name: "libstagefright_esds",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: ["ESDS.cpp"],
@@ -27,6 +38,11 @@
cc_library_static {
name: "libstagefright_metadatautils",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: ["MetaDataUtils.cpp"],
@@ -94,6 +110,11 @@
cc_library_static {
name: "libstagefright_mpeg2extractor",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: [
"Utils.cpp",
@@ -252,6 +273,7 @@
"libutils",
"libmedia_helper",
"libsfplugin_ccodec",
+ "libsfplugin_ccodec_utils",
"libstagefright_codecbase",
"libstagefright_foundation",
"libstagefright_omx_utils",
diff --git a/media/libstagefright/HevcUtils.cpp b/media/libstagefright/HevcUtils.cpp
index 482a1a7..b347453 100644
--- a/media/libstagefright/HevcUtils.cpp
+++ b/media/libstagefright/HevcUtils.cpp
@@ -83,6 +83,7 @@
}
if (err != OK) {
+ ALOGE("error parsing VPS or SPS or PPS");
return err;
}
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index af8096d..90a0777 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -103,8 +103,40 @@
//#define SHOW_MODEL_BUILD 1
class MPEG4Writer::Track {
+ struct TrackId {
+ TrackId(uint32_t aId)
+ :mId(aId),
+ mTrackIdValid(false) {
+ }
+ bool isValid(bool akKey4BitTrackIds) {
+ // trackId cannot be zero, ISO/IEC 14496-12 8.3.2.3
+ if (mId == 0) {
+ return false;
+ }
+ /* MediaRecorder uses only 4 bit to represent track ids during notifying clients.
+ * MediaMuxer's track ids are restricted by container allowed size only.
+ * MPEG4 Container defines unsigned int (32), ISO/IEC 14496-12 8.3.2.2
+ */
+ if (akKey4BitTrackIds && mId > 15) {
+ return false;
+ }
+ mTrackIdValid = true;
+ return true;
+ }
+ uint32_t getId() const {
+ CHECK(mTrackIdValid);
+ return mId;
+ }
+ TrackId() = delete;
+ DISALLOW_EVIL_CONSTRUCTORS(TrackId);
+ private:
+ // unsigned int (32), ISO/IEC 14496-12 8.3.2.2
+ uint32_t mId;
+ bool mTrackIdValid;
+ };
+
public:
- Track(MPEG4Writer *owner, const sp<MediaSource> &source, size_t trackId);
+ Track(MPEG4Writer *owner, const sp<MediaSource> &source, uint32_t aTrackId);
~Track();
@@ -129,7 +161,7 @@
void addChunkOffset(off64_t offset);
void addItemOffsetAndSize(off64_t offset, size_t size, bool isExif);
void flushItemRefs();
- int32_t getTrackId() const { return mTrackId; }
+ TrackId& getTrackId() { return mTrackId; }
status_t dump(int fd, const Vector<String16>& args) const;
static const char *getFourCCForMime(const char *mime);
const char *getTrackType() const;
@@ -290,7 +322,7 @@
bool mIsMPEG4;
bool mGotStartKeyFrame;
bool mIsMalformed;
- int32_t mTrackId;
+ TrackId mTrackId;
int64_t mTrackDurationUs;
int64_t mMaxChunkDurationUs;
int64_t mLastDecodingTimeUs;
@@ -413,7 +445,7 @@
void addOneElstTableEntry(uint32_t segmentDuration, int32_t mediaTime,
int16_t mediaRate, int16_t mediaRateFraction);
- bool isTrackMalFormed() const;
+ bool isTrackMalFormed();
void sendTrackSummary(bool hasMultipleTracks);
// Write the boxes
@@ -534,7 +566,7 @@
release();
}
- if (fallocate(mFd, 0, 0, 1) == 0) {
+ if (fallocate64(mFd, FALLOC_FL_KEEP_SIZE, 0, 1) == 0) {
ALOGD("PreAllocation enabled");
mPreAllocationEnabled = true;
} else {
@@ -744,8 +776,7 @@
// where 1MB is the common file size limit for MMS application.
// The default MAX _MOOV_BOX_SIZE value is based on about 3
// minute video recording with a bit rate about 3 Mbps, because
- // statistics also show that most of the video captured are going
- // to be less than 3 minutes.
+ // statistics show that most captured videos are less than 3 minutes.
// If the estimation is wrong, we will pay the price of wasting
// some reserved space. This should not happen so often statistically.
@@ -796,6 +827,15 @@
return size;
}
+status_t MPEG4Writer::validateAllTracksId(bool akKey4BitTrackIds) {
+ for (List<Track *>::iterator it = mTracks.begin(); it != mTracks.end(); ++it) {
+ if (!(*it)->getTrackId().isValid(akKey4BitTrackIds)) {
+ return BAD_VALUE;
+ }
+ }
+ return OK;
+}
+
status_t MPEG4Writer::start(MetaData *param) {
if (mInitCheck != OK) {
return UNKNOWN_ERROR;
@@ -810,6 +850,9 @@
mIsFileSizeLimitExplicitlyRequested = true;
}
+ /* mMaxFileSizeLimitBytes has to be set everytime fd is switched, hence the following code is
+ * appropriate in start() method.
+ */
int32_t fileSizeBits = fpathconf(mFd, _PC_FILESIZEBITS);
ALOGD("fpathconf _PC_FILESIZEBITS:%" PRId32, fileSizeBits);
fileSizeBits = std::min(fileSizeBits, 52 /* cap it below 4 peta bytes */);
@@ -902,10 +945,30 @@
mInMemoryCache = NULL;
mInMemoryCacheOffset = 0;
+ status_t err = OK;
+ int32_t is4bitTrackId = false;
+ if (param && param->findInt32(kKey4BitTrackIds, &is4bitTrackId) && is4bitTrackId) {
+ err = validateAllTracksId(true);
+ } else {
+ err = validateAllTracksId(false);
+ }
+ if (err != OK) {
+ return err;
+ }
ALOGV("muxer starting: mHasMoovBox %d, mHasFileLevelMeta %d",
mHasMoovBox, mHasFileLevelMeta);
+ err = startWriterThread();
+ if (err != OK) {
+ return err;
+ }
+
+ err = setupAndStartLooper();
+ if (err != OK) {
+ return err;
+ }
+
writeFtypBox(param);
mFreeBoxOffset = mOffset;
@@ -937,13 +1000,24 @@
seekOrPostError(mFd, mMdatOffset, SEEK_SET);
write("\x00\x00\x00\x01mdat????????", 16);
- status_t err = startWriterThread();
- if (err != OK) {
- return err;
+ /* Confirm whether the writing of the initial file atoms, ftyp and free,
+ * are written to the file properly by posting kWhatNoIOErrorSoFar to the
+ * MP4WtrCtrlHlpLooper that's handling write and seek errors also. If there
+ * was kWhatIOError, the following two scenarios should be handled.
+ * 1) If kWhatIOError was delivered and processed, MP4WtrCtrlHlpLooper
+ * would have stopped all threads gracefully already and posting
+ * kWhatNoIOErrorSoFar would fail.
+ * 2) If kWhatIOError wasn't delivered or getting processed,
+ * kWhatNoIOErrorSoFar should get posted successfully. Wait for
+ * response from MP4WtrCtrlHlpLooper.
+ */
+ sp<AMessage> msg = new AMessage(kWhatNoIOErrorSoFar, mReflector);
+ sp<AMessage> response;
+ err = msg->postAndAwaitResponse(&response);
+ if (err != OK || !response->findInt32("err", &err) || err != OK) {
+ return ERROR_IO;
}
- setupAndStartLooper();
-
err = startTracks(param);
if (err != OK) {
return err;
@@ -961,6 +1035,7 @@
status_t MPEG4Writer::stopWriterThread() {
ALOGV("Stopping writer thread");
if (!mWriterThreadStarted) {
+ ALOGD("Writer thread not started");
return OK;
}
{
@@ -970,12 +1045,16 @@
}
void *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));
+ status_t err = OK;
+ int retVal = pthread_join(mThread, &dummy);
+ if (retVal == 0) {
+ err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
+ ALOGD("WriterThread stopped. Status:%d", err);
+ } else {
+ ALOGE("stopWriterThread pthread_join status:%d", retVal);
+ err = UNKNOWN_ERROR;
+ }
mWriterThreadStarted = false;
- WARN_UNLESS(err == 0, "stopWriterThread pthread_join retVal: %d, writer thread stopped", err);
return err;
}
@@ -1031,26 +1110,35 @@
writeInt32(0x40000000); // w
}
-void MPEG4Writer::release() {
+status_t MPEG4Writer::release() {
ALOGD("release()");
- if (mPreAllocationEnabled) {
- truncatePreAllocation();
+ status_t err = OK;
+ if (!truncatePreAllocation()) {
+ if (err == OK) { err = ERROR_IO; }
}
- int retVal = fsync(mFd);
- WARN_UNLESS(retVal == 0, "fsync retVal:%d", retVal);
- retVal = close(mFd);
- WARN_UNLESS(retVal == 0, "close mFd retVal :%d", retVal);
+ if (fsync(mFd) != 0) {
+ ALOGW("(ignored)fsync err:%s(%d)", std::strerror(errno), errno);
+ // Don't bubble up fsync error, b/157291505.
+ // if (err == OK) { err = ERROR_IO; }
+ }
+ if (close(mFd) != 0) {
+ ALOGE("close err:%s(%d)", std::strerror(errno), errno);
+ if (err == OK) { err = ERROR_IO; }
+ }
mFd = -1;
if (mNextFd != -1) {
- retVal = close(mNextFd);
+ if (close(mNextFd) != 0) {
+ ALOGE("close(mNextFd) error:%s(%d)", std::strerror(errno), errno);
+ }
+ if (err == OK) { err = ERROR_IO; }
mNextFd = -1;
- WARN_UNLESS(retVal == 0, "close mNextFd retVal :%d", retVal);
}
stopAndReleaseLooper();
mInitCheck = NO_INIT;
mStarted = false;
free(mInMemoryCache);
mInMemoryCache = NULL;
+ return err;
}
void MPEG4Writer::finishCurrentSession() {
@@ -1065,7 +1153,7 @@
}
if (mNextFd == -1) {
- ALOGW("No FileDescripter for next recording");
+ ALOGW("No FileDescriptor for next recording");
return INVALID_OPERATION;
}
@@ -1084,12 +1172,15 @@
} else {
if (!mWriterThreadStarted ||
!mStarted) {
- status_t err = OK;
+ status_t writerErr = OK;
if (mWriterThreadStarted) {
- err = stopWriterThread();
+ writerErr = stopWriterThread();
}
- release();
- return err;
+ status_t retErr = release();
+ if (writerErr != OK) {
+ retErr = writerErr;
+ }
+ return retErr;
}
}
@@ -1100,8 +1191,9 @@
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
status_t trackErr = (*it)->stop(stopSource);
+ WARN_UNLESS(trackErr == OK, "%s track stopped with an error",
+ (*it)->getTrackType());
if (err == OK && trackErr != OK) {
- ALOGW("%s track stopped with an error", (*it)->getTrackType());
err = trackErr;
}
@@ -1118,7 +1210,6 @@
}
}
-
if (nonImageTrackCount > 1) {
ALOGD("Duration from tracks range is [%" PRId64 ", %" PRId64 "] us",
minDurationUs, maxDurationUs);
@@ -1126,13 +1217,15 @@
status_t writerErr = stopWriterThread();
- // TODO: which error to propagage, writerErr or trackErr?
+ // Propagating writer error
if (err == OK && writerErr != OK) {
err = writerErr;
}
// Do not write out movie header on error except malformed track.
+ // TODO: Remove samples of malformed tracks added in mdat.
if (err != OK && err != ERROR_MALFORMED) {
+ // Ignoring release() return value as there was an "err" already.
release();
return err;
}
@@ -1174,6 +1267,7 @@
} else {
ALOGI("The mp4 file will not be streamable.");
}
+ ALOGI("MOOV atom was written to the file");
}
mWriteBoxToMemory = false;
@@ -1186,7 +1280,11 @@
CHECK(mBoxes.empty());
- release();
+ status_t errRelease = release();
+ // Prioritize the error that occurred before release().
+ if (err == OK) {
+ err = errRelease;
+ }
return err;
}
@@ -1354,7 +1452,7 @@
for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
it != mChunkInfos.end(); ++it) {
- int trackNum = it->mTrack->getTrackId() << 28;
+ uint32_t trackNum = (it->mTrack->getTrackId().getId() << 28);
notify(MEDIA_RECORDER_TRACK_EVENT_INFO,
trackNum | MEDIA_RECORDER_TRACK_INTER_CHUNK_TIME_MS,
it->mMaxInterChunkDurUs);
@@ -1438,25 +1536,21 @@
void MPEG4Writer::addLengthPrefixedSample_l(MediaBuffer *buffer) {
size_t length = buffer->range_length();
if (mUse4ByteNalLength) {
- uint8_t x = length >> 24;
- writeOrPostError(mFd, &x, 1);
- x = (length >> 16) & 0xff;
- writeOrPostError(mFd, &x, 1);
- x = (length >> 8) & 0xff;
- writeOrPostError(mFd, &x, 1);
- x = length & 0xff;
- writeOrPostError(mFd, &x, 1);
-
+ uint8_t x[4];
+ x[0] = length >> 24;
+ x[1] = (length >> 16) & 0xff;
+ x[2] = (length >> 8) & 0xff;
+ x[3] = length & 0xff;
+ writeOrPostError(mFd, &x, 4);
writeOrPostError(mFd, (const uint8_t*)buffer->data() + buffer->range_offset(), length);
-
mOffset += length + 4;
} else {
CHECK_LT(length, 65536u);
- uint8_t x = length >> 8;
- writeOrPostError(mFd, &x, 1);
- x = length & 0xff;
- writeOrPostError(mFd, &x, 1);
+ uint8_t x[2];
+ x[0] = length >> 8;
+ x[1] = length & 0xff;
+ writeOrPostError(mFd, &x, 2);
writeOrPostError(mFd, (const uint8_t*)buffer->data() + buffer->range_offset(), length);
mOffset += length + 2;
}
@@ -1512,9 +1606,9 @@
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);
+ sp<AMessage> msg = new AMessage(kWhatIOError, mReflector);
+ msg->setInt32("err", ERROR_IO);
+ WARN_UNLESS(msg->post() == OK, "writeOrPostError:error posting ERROR_IO");
}
void MPEG4Writer::seekOrPostError(int fd, off64_t offset, int whence) {
@@ -1531,9 +1625,9 @@
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);
+ sp<AMessage> msg = new AMessage(kWhatIOError, mReflector);
+ msg->setInt32("err", ERROR_IO);
+ WARN_UNLESS(msg->post() == OK, "seekOrPostError:error posting ERROR_IO");
}
void MPEG4Writer::beginBox(uint32_t id) {
@@ -1768,10 +1862,11 @@
ALOGV("preAllocateSize :%" PRIu64 " lastFileEndOffset:%" PRIu64, preAllocateSize,
lastFileEndOffset);
- int res = fallocate(mFd, 0, lastFileEndOffset, preAllocateSize);
+ int res = fallocate64(mFd, FALLOC_FL_KEEP_SIZE, lastFileEndOffset, preAllocateSize);
if (res == -1) {
ALOGE("fallocate err:%s, %d, fd:%d", strerror(errno), errno, mFd);
- sp<AMessage> msg = new AMessage(kWhatHandleFallocateError, mReflector);
+ sp<AMessage> msg = new AMessage(kWhatFallocateError, mReflector);
+ msg->setInt32("err", ERROR_IO);
status_t err = msg->post();
mFallocateErr = true;
ALOGD("preAllocation post:%d", err);
@@ -1783,6 +1878,9 @@
}
bool MPEG4Writer::truncatePreAllocation() {
+ if (!mPreAllocationEnabled)
+ return true;
+
bool status = true;
off64_t endOffset = std::max(mMdatEndOffset, mOffset);
/* if mPreAllocateFileEndOffset >= endOffset, then preallocation logic works good. (diff >= 0).
@@ -1791,9 +1889,13 @@
ALOGD("ftruncate mPreAllocateFileEndOffset:%" PRId64 " mOffset:%" PRIu64
" mMdatEndOffset:%" PRIu64 " diff:%" PRId64, mPreAllocateFileEndOffset, mOffset,
mMdatEndOffset, mPreAllocateFileEndOffset - endOffset);
- if(ftruncate(mFd, endOffset) == -1) {
+ if (ftruncate64(mFd, endOffset) == -1) {
ALOGE("ftruncate err:%s, %d, fd:%d", strerror(errno), errno, mFd);
status = false;
+ /* No need to post and handle(stop & notify client) error like it's done in preAllocate(),
+ * because ftruncate() is called during release() only and the error here would be
+ * reported from there as this function is returning false on any error in ftruncate().
+ */
}
return status;
}
@@ -1899,7 +2001,7 @@
////////////////////////////////////////////////////////////////////////////////
MPEG4Writer::Track::Track(
- MPEG4Writer *owner, const sp<MediaSource> &source, size_t trackId)
+ MPEG4Writer *owner, const sp<MediaSource> &source, uint32_t aTrackId)
: mOwner(owner),
mMeta(source->getFormat()),
mSource(source),
@@ -1909,7 +2011,7 @@
mStarted(false),
mGotStartKeyFrame(false),
mIsMalformed(false),
- mTrackId(trackId),
+ mTrackId(aTrackId),
mTrackDurationUs(0),
mEstimatedTrackSizeBytes(0),
mSamplesHaveSameSize(true),
@@ -2086,25 +2188,28 @@
mElstTableEntries->add(htonl((((uint32_t)mediaRate) << 16) | (uint32_t)mediaRateFraction));
}
-void MPEG4Writer::setupAndStartLooper() {
+status_t MPEG4Writer::setupAndStartLooper() {
+ status_t err = OK;
if (mLooper == nullptr) {
mLooper = new ALooper;
- mLooper->setName("MP4WriterLooper");
- mLooper->start();
+ mLooper->setName("MP4WtrCtrlHlpLooper");
+ err = mLooper->start();
mReflector = new AHandlerReflector<MPEG4Writer>(this);
mLooper->registerHandler(mReflector);
}
+ ALOGD("MP4WtrCtrlHlpLooper Started");
+ return err;
}
void MPEG4Writer::stopAndReleaseLooper() {
if (mLooper != nullptr) {
if (mReflector != nullptr) {
- ALOGD("unregisterHandler");
mLooper->unregisterHandler(mReflector->id());
mReflector.clear();
}
mLooper->stop();
mLooper.clear();
+ ALOGD("MP4WtrCtrlHlpLooper stopped");
}
}
@@ -2329,19 +2434,36 @@
break;
}
// ::write() or lseek64() wasn't a success, file could be malformed
- case kWhatHandleIOError: {
- ALOGE("kWhatHandleIOError");
+ case kWhatIOError: {
+ ALOGE("kWhatIOError");
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
// Stop tracks' threads and main writer thread.
- notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_RECORDER_ERROR_UNKNOWN, ERROR_MALFORMED);
stop();
+ notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_RECORDER_ERROR_UNKNOWN, err);
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);
+ // fallocate() failed, hence stop() and notify app.
+ case kWhatFallocateError: {
+ ALOGE("kWhatFallocateError");
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+ // Stop tracks' threads and main writer thread.
stop();
+ //TODO: introduce a suitable MEDIA_RECORDER_ERROR_* instead MEDIA_RECORDER_ERROR_UNKNOWN?
+ notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_RECORDER_ERROR_UNKNOWN, err);
+ break;
+ }
+ /* Response to kWhatNoIOErrorSoFar would be OK always as of now.
+ * Responding with other options could be added later if required.
+ */
+ case kWhatNoIOErrorSoFar: {
+ ALOGD("kWhatNoIOErrorSoFar");
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", OK);
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+ response->postReply(replyID);
break;
}
default:
@@ -2706,11 +2828,16 @@
mDone = true;
void *dummy;
- 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");
+ status_t err = OK;
+ int retVal = pthread_join(mThread, &dummy);
+ if (retVal == 0) {
+ err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
+ ALOGD("%s track stopped. Status:%d. %s source",
+ getTrackType(), err, stopSource ? "Stop" : "Not Stop");
+ } else {
+ ALOGE("track::stop: pthread_join retVal:%d", retVal);
+ err = UNKNOWN_ERROR;
+ }
mStarted = false;
return err;
}
@@ -2849,6 +2976,7 @@
}
if (nextStartCode == NULL) {
+ ALOGE("nextStartCode is null");
return ERROR_MALFORMED;
}
@@ -3126,11 +3254,11 @@
int64_t lastSampleDurationTicks = -1; // Timescale based ticks
if (mIsAudio) {
- prctl(PR_SET_NAME, (unsigned long)"AudioTrackWriterThread", 0, 0, 0);
+ prctl(PR_SET_NAME, (unsigned long)"MP4WtrAudTrkThread", 0, 0, 0);
} else if (mIsVideo) {
- prctl(PR_SET_NAME, (unsigned long)"VideoTrackWriterThread", 0, 0, 0);
+ prctl(PR_SET_NAME, (unsigned long)"MP4WtrVidTrkThread", 0, 0, 0);
} else {
- prctl(PR_SET_NAME, (unsigned long)"MetadataTrackWriterThread", 0, 0, 0);
+ prctl(PR_SET_NAME, (unsigned long)"MP4WtrMetaTrkThread", 0, 0, 0);
}
if (mOwner->isRealTimeRecording()) {
@@ -3181,6 +3309,7 @@
}
++count;
+
int32_t isCodecConfig;
if (buffer->meta_data().findInt32(kKeyIsCodecConfig, &isCodecConfig)
&& isCodecConfig) {
@@ -3204,7 +3333,7 @@
+ buffer->range_offset(),
buffer->range_length());
} else if (mIsMPEG4) {
- copyCodecSpecificData((const uint8_t *)buffer->data() + buffer->range_offset(),
+ err = copyCodecSpecificData((const uint8_t *)buffer->data() + buffer->range_offset(),
buffer->range_length());
}
}
@@ -3213,8 +3342,10 @@
buffer = NULL;
if (OK != err) {
mSource->stop();
+ mIsMalformed = true;
+ uint32_t trackNum = (mTrackId.getId() << 28);
mOwner->notify(MEDIA_RECORDER_TRACK_EVENT_ERROR,
- mTrackId | MEDIA_RECORDER_TRACK_ERROR_GENERAL, err);
+ trackNum | MEDIA_RECORDER_TRACK_ERROR_GENERAL, err);
break;
}
@@ -3251,7 +3382,7 @@
* 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.
+ * Release the current buffer/sample here.
*/
if (!mOwner->preAllocate(buffer->range_length())) {
buffer->release();
@@ -3291,6 +3422,7 @@
updateTrackSizeEstimate();
if (mOwner->exceedsFileSizeLimit()) {
+ copy->release();
if (mOwner->switchFd() != OK) {
ALOGW("Recorded file size exceeds limit %" PRId64 "bytes",
mOwner->mMaxFileSizeLimitBytes);
@@ -3301,16 +3433,15 @@
ALOGV("%s Current recorded file size exceeds limit %" PRId64 "bytes. Switching output",
getTrackType(), mOwner->mMaxFileSizeLimitBytes);
}
- copy->release();
break;
}
if (mOwner->exceedsFileDurationLimit()) {
ALOGW("Recorded file duration exceeds limit %" PRId64 "microseconds",
mOwner->mMaxFileDurationLimitUs);
- mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0);
copy->release();
mSource->stop();
+ mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0);
break;
}
@@ -3592,13 +3723,13 @@
}
}
}
+
if (isTrackMalFormed()) {
- mIsMalformed = true;
dumpTimeStamps();
err = ERROR_MALFORMED;
}
- mOwner->trackProgressStatus(mTrackId, -1, err);
+ mOwner->trackProgressStatus(mTrackId.getId(), -1, err);
// Add final entries only for non-empty tracks.
if (mStszTableEntries->count() > 0) {
@@ -3665,7 +3796,7 @@
return err;
}
-bool MPEG4Writer::Track::isTrackMalFormed() const {
+bool MPEG4Writer::Track::isTrackMalFormed() {
if (mIsMalformed) {
return true;
}
@@ -3674,23 +3805,29 @@
if (mOwner->mStartMeta &&
mOwner->mStartMeta->findInt32(kKeyEmptyTrackMalFormed, &emptyTrackMalformed) &&
emptyTrackMalformed) {
+ // MediaRecorder(sets kKeyEmptyTrackMalFormed by default) report empty tracks as malformed.
if (!mIsHeic && mStszTableEntries->count() == 0) { // no samples written
ALOGE("The number of recorded samples is 0");
+ mIsMalformed = true;
return true;
}
if (mIsVideo && mStssTableEntries->count() == 0) { // no sync frames for video
ALOGE("There are no sync frames for video track");
+ mIsMalformed = true;
return true;
}
} else {
- // No sync frames for video.
+ // Through MediaMuxer, empty tracks can be added. No sync frames for video.
if (mIsVideo && mStszTableEntries->count() > 0 && mStssTableEntries->count() == 0) {
ALOGE("There are no sync frames for video track");
+ mIsMalformed = true;
return true;
}
}
-
- if (OK != checkCodecSpecificData()) { // no codec specific data
+ // Don't check for CodecSpecificData when track is empty.
+ if (mStszTableEntries->count() > 0 && OK != checkCodecSpecificData()) {
+ // No codec specific data.
+ mIsMalformed = true;
return true;
}
@@ -3704,7 +3841,7 @@
return;
}
- int trackNum = (mTrackId << 28);
+ uint32_t trackNum = (mTrackId.getId() << 28);
mOwner->notify(MEDIA_RECORDER_TRACK_EVENT_INFO,
trackNum | MEDIA_RECORDER_TRACK_INFO_TYPE,
@@ -3758,15 +3895,15 @@
if (mTrackEveryTimeDurationUs > 0 &&
timeUs - mPreviousTrackTimeUs >= mTrackEveryTimeDurationUs) {
ALOGV("Fire time tracking progress status at %" PRId64 " us", timeUs);
- mOwner->trackProgressStatus(mTrackId, timeUs - mPreviousTrackTimeUs, err);
+ mOwner->trackProgressStatus(mTrackId.getId(), timeUs - mPreviousTrackTimeUs, err);
mPreviousTrackTimeUs = timeUs;
}
}
void MPEG4Writer::trackProgressStatus(
- size_t trackId, int64_t timeUs, status_t err) {
+ uint32_t trackId, int64_t timeUs, status_t err) {
Mutex::Autolock lock(mLock);
- int32_t trackNum = (trackId << 28);
+ uint32_t trackNum = (trackId << 28);
// Error notification
// Do not consider ERROR_END_OF_STREAM an error
@@ -3936,8 +4073,8 @@
void MPEG4Writer::Track::writeStblBox() {
mOwner->beginBox("stbl");
- // Add subboxes only for non-empty tracks.
- if (mStszTableEntries->count() > 0) {
+ // Add subboxes for only non-empty and well-formed tracks.
+ if (mStszTableEntries->count() > 0 && !isTrackMalFormed()) {
mOwner->beginBox("stsd");
mOwner->writeInt32(0); // version=0, flags=0
mOwner->writeInt32(1); // entry count
@@ -4242,7 +4379,7 @@
mOwner->writeInt32(0x07); // version=0, flags=7
mOwner->writeInt32(now); // creation time
mOwner->writeInt32(now); // modification time
- mOwner->writeInt32(mTrackId); // track id starts with 1
+ mOwner->writeInt32(mTrackId.getId()); // track id starts with 1
mOwner->writeInt32(0); // reserved
int64_t trakDurationUs = getDurationUs();
int32_t mvhdTimeScale = mOwner->getTimeScale();
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 11f2f38..5d17f97 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -15,6 +15,7 @@
*/
//#define LOG_NDEBUG 0
+#include "hidl/HidlSupport.h"
#define LOG_TAG "MediaCodec"
#include <utils/Log.h>
@@ -37,6 +38,7 @@
#include <cutils/properties.h>
#include <gui/BufferQueue.h>
#include <gui/Surface.h>
+#include <hidlmemory/FrameworkUtils.h>
#include <mediadrm/ICrypto.h>
#include <media/IOMX.h>
#include <media/MediaCodecBuffer.h>
@@ -55,6 +57,7 @@
#include <media/stagefright/BufferProducerWrapper.h>
#include <media/stagefright/CCodec.h>
#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/MediaCodecList.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
@@ -93,9 +96,12 @@
static const char *kCodecCrypto = "android.media.mediacodec.crypto"; /* 0,1 */
static const char *kCodecProfile = "android.media.mediacodec.profile"; /* 0..n */
static const char *kCodecLevel = "android.media.mediacodec.level"; /* 0..n */
+static const char *kCodecBitrateMode = "android.media.mediacodec.bitrate_mode"; /* CQ/VBR/CBR */
+static const char *kCodecBitrate = "android.media.mediacodec.bitrate"; /* 0..n */
static const char *kCodecMaxWidth = "android.media.mediacodec.maxwidth"; /* 0..n */
static const char *kCodecMaxHeight = "android.media.mediacodec.maxheight"; /* 0..n */
static const char *kCodecError = "android.media.mediacodec.errcode";
+static const char *kCodecLifetimeMs = "android.media.mediacodec.lifetimeMs"; /* 0..n ms*/
static const char *kCodecErrorState = "android.media.mediacodec.errstate";
static const char *kCodecLatencyMax = "android.media.mediacodec.latency.max"; /* in us */
static const char *kCodecLatencyMin = "android.media.mediacodec.latency.min"; /* in us */
@@ -617,7 +623,6 @@
mFlags(0),
mStickyError(OK),
mSoftRenderer(NULL),
- mMetricsHandle(0),
mIsVideo(false),
mVideoWidth(0),
mVideoHeight(0),
@@ -677,6 +682,8 @@
mIndexOfFirstFrameWhenLowLatencyOn = -1;
mInputBufferCounter = 0;
}
+
+ mLifetimeStartNs = systemTime(SYSTEM_TIME_MONOTONIC);
}
void MediaCodec::updateMediametrics() {
@@ -685,7 +692,6 @@
return;
}
-
if (mLatencyHist.getCount() != 0 ) {
mediametrics_setInt64(mMetricsHandle, kCodecLatencyMax, mLatencyHist.getMax());
mediametrics_setInt64(mMetricsHandle, kCodecLatencyMin, mLatencyHist.getMin());
@@ -701,6 +707,11 @@
if (mLatencyUnknown > 0) {
mediametrics_setInt64(mMetricsHandle, kCodecLatencyUnknown, mLatencyUnknown);
}
+ if (mLifetimeStartNs > 0) {
+ nsecs_t lifetime = systemTime(SYSTEM_TIME_MONOTONIC) - mLifetimeStartNs;
+ lifetime = lifetime / (1000 * 1000); // emitted in ms, truncated not rounded
+ mediametrics_setInt64(mMetricsHandle, kCodecLifetimeMs, lifetime);
+ }
{
Mutex::Autolock al(mLatencyLock);
@@ -738,7 +749,6 @@
}
}
-
// spit the data (if any) into the supplied analytics record
if (recentHist.getCount()!= 0 ) {
mediametrics_setInt64(item, kCodecRecentLatencyMax, recentHist.getMax());
@@ -1472,9 +1482,9 @@
return PostAndAwaitResponse(msg, &response);
}
-status_t MediaCodec::releaseAsync() {
+status_t MediaCodec::releaseAsync(const sp<AMessage> ¬ify) {
sp<AMessage> msg = new AMessage(kWhatRelease, this);
- msg->setInt32("async", 1);
+ msg->setMessage("async", notify);
sp<AMessage> response;
return PostAndAwaitResponse(msg, &response);
}
@@ -2307,6 +2317,8 @@
// meaningful and confusing for an encoder in a transcoder scenario
mInputFormat->setInt32("allow-frame-drop", mAllowFrameDroppingBySurface);
}
+ sp<AMessage> interestingFormat =
+ (mFlags & kFlagIsEncoder) ? mOutputFormat : mInputFormat;
ALOGV("[%s] configured as input format: %s, output format: %s",
mComponentName.c_str(),
mInputFormat->debugString(4).c_str(),
@@ -2320,6 +2332,7 @@
(new AMessage)->postReply(mReplyID);
// augment our media metrics info, now that we know more things
+ // such as what the codec extracted from any CSD passed in.
if (mMetricsHandle != 0) {
sp<AMessage> format;
if (mConfigureMsg != NULL &&
@@ -2331,6 +2344,30 @@
mime.c_str());
}
}
+ // perhaps video only?
+ int32_t profile = 0;
+ if (interestingFormat->findInt32("profile", &profile)) {
+ mediametrics_setInt32(mMetricsHandle, kCodecProfile, profile);
+ }
+ int32_t level = 0;
+ if (interestingFormat->findInt32("level", &level)) {
+ mediametrics_setInt32(mMetricsHandle, kCodecLevel, level);
+ }
+ // bitrate and bitrate mode, encoder only
+ if (mFlags & kFlagIsEncoder) {
+ // encoder specific values
+ int32_t bitrate_mode = -1;
+ if (mOutputFormat->findInt32(KEY_BITRATE_MODE, &bitrate_mode)) {
+ mediametrics_setCString(mMetricsHandle, kCodecBitrateMode,
+ asString_BitrateMode(bitrate_mode));
+ }
+ int32_t bitrate = -1;
+ if (mOutputFormat->findInt32(KEY_BIT_RATE, &bitrate)) {
+ mediametrics_setInt32(mMetricsHandle, kCodecBitrate, bitrate);
+ }
+ } else {
+ // decoder specific values
+ }
}
break;
}
@@ -2459,6 +2496,18 @@
}
break;
}
+ if (!mLeftover.empty()) {
+ ssize_t index = dequeuePortBuffer(kPortIndexInput);
+ CHECK_GE(index, 0);
+
+ status_t err = handleLeftover(index);
+ if (err != OK) {
+ setStickyError(err);
+ postActivityNotificationIfPossible();
+ cancelPendingDequeueOperations();
+ }
+ break;
+ }
if (mFlags & kFlagIsAsync) {
if (!mHaveInputSurface) {
@@ -2641,10 +2690,16 @@
}
mResourceManagerProxy->removeClient();
+ mReleaseSurface.reset();
if (mReplyID != nullptr) {
(new AMessage)->postReply(mReplyID);
}
+ if (mAsyncReleaseCompleteNotification != nullptr) {
+ flushMediametrics();
+ mAsyncReleaseCompleteNotification->post();
+ mAsyncReleaseCompleteNotification.clear();
+ }
break;
}
@@ -3031,22 +3086,23 @@
break;
}
- int32_t async = 0;
- if (msg->findInt32("async", &async) && async) {
- if ((mState == CONFIGURED || mState == STARTED || mState == FLUSHED)
- && mSurface != NULL) {
+ sp<AMessage> asyncNotify;
+ if (msg->findMessage("async", &asyncNotify) && asyncNotify != nullptr) {
+ if (mSurface != NULL) {
if (!mReleaseSurface) {
mReleaseSurface.reset(new ReleaseSurface);
}
- status_t err = connectToSurface(mReleaseSurface->getSurface());
- ALOGW_IF(err != OK, "error connecting to release surface: err = %d", err);
- if (err == OK && !(mFlags & kFlagUsesSoftwareRenderer)) {
- err = mCodec->setSurface(mReleaseSurface->getSurface());
- ALOGW_IF(err != OK, "error setting release surface: err = %d", err);
- }
- if (err == OK) {
- (void)disconnectFromSurface();
- mSurface = mReleaseSurface->getSurface();
+ if (mSurface != mReleaseSurface->getSurface()) {
+ status_t err = connectToSurface(mReleaseSurface->getSurface());
+ ALOGW_IF(err != OK, "error connecting to release surface: err = %d", err);
+ if (err == OK && !(mFlags & kFlagUsesSoftwareRenderer)) {
+ err = mCodec->setSurface(mReleaseSurface->getSurface());
+ ALOGW_IF(err != OK, "error setting release surface: err = %d", err);
+ }
+ if (err == OK) {
+ (void)disconnectFromSurface();
+ mSurface = mReleaseSurface->getSurface();
+ }
}
}
}
@@ -3063,10 +3119,11 @@
pushBlankBuffersToNativeWindow(mSurface.get());
}
- if (async) {
+ if (asyncNotify != nullptr) {
mResourceManagerProxy->markClientForPendingRemoval();
(new AMessage)->postReply(mReplyID);
mReplyID = 0;
+ mAsyncReleaseCompleteNotification = asyncNotify;
}
break;
@@ -3146,7 +3203,15 @@
break;
}
- status_t err = onQueueInputBuffer(msg);
+ status_t err = UNKNOWN_ERROR;
+ if (!mLeftover.empty()) {
+ mLeftover.push_back(msg);
+ size_t index;
+ msg->findSize("index", &index);
+ err = handleLeftover(index);
+ } else {
+ err = onQueueInputBuffer(msg);
+ }
PostReplyWithError(replyID, err);
break;
@@ -3430,19 +3495,42 @@
sp<ABuffer> csd = *mCSD.begin();
mCSD.erase(mCSD.begin());
std::shared_ptr<C2Buffer> c2Buffer;
+ sp<hardware::HidlMemory> memory;
+ size_t offset = 0;
- if ((mFlags & kFlagUseBlockModel) && mOwnerName.startsWith("codec2::")) {
- std::shared_ptr<C2LinearBlock> block =
- FetchLinearBlock(csd->size(), {std::string{mComponentName.c_str()}});
- C2WriteView view{block->map().get()};
- if (view.error() != C2_OK) {
- return -EINVAL;
+ if (mFlags & kFlagUseBlockModel) {
+ if (hasCryptoOrDescrambler()) {
+ constexpr size_t kInitialDealerCapacity = 1048576; // 1MB
+ thread_local sp<MemoryDealer> sDealer = new MemoryDealer(
+ kInitialDealerCapacity, "CSD(1MB)");
+ sp<IMemory> mem = sDealer->allocate(csd->size());
+ if (mem == nullptr) {
+ size_t newDealerCapacity = sDealer->getMemoryHeap()->getSize() * 2;
+ while (csd->size() * 2 > newDealerCapacity) {
+ newDealerCapacity *= 2;
+ }
+ sDealer = new MemoryDealer(
+ newDealerCapacity,
+ AStringPrintf("CSD(%dMB)", newDealerCapacity / 1048576).c_str());
+ mem = sDealer->allocate(csd->size());
+ }
+ memcpy(mem->unsecurePointer(), csd->data(), csd->size());
+ ssize_t heapOffset;
+ memory = hardware::fromHeap(mem->getMemory(&heapOffset, nullptr));
+ offset += heapOffset;
+ } else {
+ std::shared_ptr<C2LinearBlock> block =
+ FetchLinearBlock(csd->size(), {std::string{mComponentName.c_str()}});
+ C2WriteView view{block->map().get()};
+ if (view.error() != C2_OK) {
+ return -EINVAL;
+ }
+ if (csd->size() > view.capacity()) {
+ return -EINVAL;
+ }
+ memcpy(view.base(), csd->data(), csd->size());
+ c2Buffer = C2Buffer::CreateLinearBuffer(block->share(0, csd->size(), C2Fence{}));
}
- if (csd->size() > view.capacity()) {
- return -EINVAL;
- }
- memcpy(view.base(), csd->data(), csd->size());
- c2Buffer = C2Buffer::CreateLinearBuffer(block->share(0, csd->size(), C2Fence{}));
} else {
const BufferInfo &info = mPortBuffers[kPortIndexInput][bufferIndex];
const sp<MediaCodecBuffer> &codecInputData = info.mData;
@@ -3472,6 +3560,11 @@
new WrapperObject<std::shared_ptr<C2Buffer>>{c2Buffer}};
msg->setObject("c2buffer", obj);
msg->setMessage("tunings", new AMessage);
+ } else if (memory) {
+ sp<WrapperObject<sp<hardware::HidlMemory>>> obj{
+ new WrapperObject<sp<hardware::HidlMemory>>{memory}};
+ msg->setObject("memory", obj);
+ msg->setMessage("tunings", new AMessage);
}
return onQueueInputBuffer(msg);
@@ -3531,6 +3624,9 @@
CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
Mutex::Autolock al(mBufferLock);
+ if (portIndex == kPortIndexInput) {
+ mLeftover.clear();
+ }
for (size_t i = 0; i < mPortBuffers[portIndex].size(); ++i) {
BufferInfo *info = &mPortBuffers[portIndex][i];
@@ -3589,6 +3685,7 @@
} else if (msg->findObject("memory", &obj)) {
CHECK(obj);
memory = static_cast<WrapperObject<sp<hardware::HidlMemory>> *>(obj.get())->value;
+ CHECK(msg->findSize("offset", &offset));
} else {
CHECK(msg->findSize("offset", &offset));
}
@@ -3660,7 +3757,26 @@
err = mBufferChannel->attachEncryptedBuffer(
memory, (mFlags & kFlagIsSecure), key, iv, mode, pattern,
offset, subSamples, numSubSamples, buffer);
+ } else {
+ err = UNKNOWN_ERROR;
}
+
+ if (err == OK && !buffer->asC2Buffer()
+ && c2Buffer && c2Buffer->data().type() == C2BufferData::LINEAR) {
+ C2ConstLinearBlock block{c2Buffer->data().linearBlocks().front()};
+ if (block.size() > buffer->size()) {
+ C2ConstLinearBlock leftover = block.subBlock(
+ block.offset() + buffer->size(), block.size() - buffer->size());
+ sp<WrapperObject<std::shared_ptr<C2Buffer>>> obj{
+ new WrapperObject<std::shared_ptr<C2Buffer>>{
+ C2Buffer::CreateLinearBuffer(leftover)}};
+ msg->setObject("c2buffer", obj);
+ mLeftover.push_front(msg);
+ // Not sending EOS if we have leftovers
+ flags &= ~BUFFER_FLAG_EOS;
+ }
+ }
+
offset = buffer->offset();
size = buffer->size();
if (err != OK) {
@@ -3687,7 +3803,7 @@
}
status_t err = OK;
- if (hasCryptoOrDescrambler()) {
+ if (hasCryptoOrDescrambler() && !c2Buffer && !memory) {
AString *errorDetailMsg;
CHECK(msg->findPointer("errorDetailMsg", (void **)&errorDetailMsg));
@@ -3725,6 +3841,16 @@
return err;
}
+status_t MediaCodec::handleLeftover(size_t index) {
+ if (mLeftover.empty()) {
+ return OK;
+ }
+ sp<AMessage> msg = mLeftover.front();
+ mLeftover.pop_front();
+ msg->setSize("index", index);
+ return onQueueInputBuffer(msg);
+}
+
//static
size_t MediaCodec::CreateFramesRenderedMessage(
const std::list<FrameRenderTracker::Info> &done, sp<AMessage> &msg) {
diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index 1cb45ac..cab4ebd 100644
--- a/media/libstagefright/MediaMuxer.cpp
+++ b/media/libstagefright/MediaMuxer.cpp
@@ -48,8 +48,7 @@
MediaMuxer::MediaMuxer(int fd, OutputFormat format)
: mFormat(format),
- mState(UNINITIALIZED),
- mError(OK) {
+ mState(UNINITIALIZED) {
if (isMp4Format(format)) {
mWriter = new MPEG4Writer(fd);
} else if (format == OUTPUT_FORMAT_WEBM) {
@@ -59,7 +58,6 @@
}
if (mWriter != NULL) {
- mWriter->setMuxerListener(this);
mFileMeta = new MetaData;
if (format == OUTPUT_FORMAT_HEIF) {
// Note that the key uses recorder file types.
@@ -157,23 +155,16 @@
status_t MediaMuxer::stop() {
Mutex::Autolock autoLock(mMuxerLock);
- if (mState == STARTED || mState == ERROR) {
+ if (mState == STARTED) {
mState = STOPPED;
for (size_t i = 0; i < mTrackList.size(); i++) {
if (mTrackList[i]->stop() != OK) {
return INVALID_OPERATION;
}
}
- // Unlock this mutex to allow notify to be called during stop process.
- mMuxerLock.unlock();
status_t err = mWriter->stop();
- mMuxerLock.lock();
- if (err != OK || mError != OK) {
- ALOGE("stop err: %d, mError:%d", err, mError);
- }
- // Prioritize mError over err.
- if (mError != OK) {
- err = mError;
+ if (err != OK) {
+ ALOGE("stop() err: %d", err);
}
return err;
} else {
@@ -229,29 +220,4 @@
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/bqhelper/Android.bp b/media/libstagefright/bqhelper/Android.bp
index 37e842a..8698d33 100644
--- a/media/libstagefright/bqhelper/Android.bp
+++ b/media/libstagefright/bqhelper/Android.bp
@@ -63,6 +63,8 @@
vndk: {
enabled: true,
},
+ min_sdk_version: "29",
+
shared_libs: [ "libgui" ],
target: {
vendor: {
diff --git a/media/libstagefright/codecs/amrnb/common/Android.bp b/media/libstagefright/codecs/amrnb/common/Android.bp
index bcf63d5..59a791d 100644
--- a/media/libstagefright/codecs/amrnb/common/Android.bp
+++ b/media/libstagefright/codecs/amrnb/common/Android.bp
@@ -2,6 +2,7 @@
name: "libstagefright_amrnb_common",
vendor_available: true,
host_supported: true,
+ min_sdk_version: "29",
srcs: [
"src/add.cpp",
diff --git a/media/libstagefright/codecs/amrnb/dec/Android.bp b/media/libstagefright/codecs/amrnb/dec/Android.bp
index 3381d2e..b8e00b3 100644
--- a/media/libstagefright/codecs/amrnb/dec/Android.bp
+++ b/media/libstagefright/codecs/amrnb/dec/Android.bp
@@ -2,6 +2,7 @@
name: "libstagefright_amrnbdec",
vendor_available: true,
host_supported: true,
+ min_sdk_version: "29",
srcs: [
"src/a_refl.cpp",
diff --git a/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp b/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp
index 34dd011..cdfc03a 100644
--- a/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp
+++ b/media/libstagefright/codecs/amrnb/dec/SoftAMR.cpp
@@ -20,9 +20,6 @@
#include "SoftAMR.h"
-#include "gsmamr_dec.h"
-#include "pvamrwbdecoder.h"
-
#include <media/stagefright/foundation/ADebug.h>
namespace android {
@@ -470,11 +467,10 @@
memset(outPtr, 0, kNumSamplesPerFrameWB * sizeof(int16_t));
} else if (mode < 9) {
int16 frameType;
- RX_State_wb rx_state;
mime_unsorting(
const_cast<uint8_t *>(&inputPtr[1]),
mInputSampleBuffer,
- &frameType, &mode, 1, &rx_state);
+ &frameType, &mode, 1, &mRxState);
int16_t numSamplesOutput;
pvDecoder_AmrWb(
diff --git a/media/libstagefright/codecs/amrnb/dec/SoftAMR.h b/media/libstagefright/codecs/amrnb/dec/SoftAMR.h
index 869b81d..d5aaed3 100644
--- a/media/libstagefright/codecs/amrnb/dec/SoftAMR.h
+++ b/media/libstagefright/codecs/amrnb/dec/SoftAMR.h
@@ -19,6 +19,8 @@
#define SOFT_AMR_H_
#include <media/stagefright/omx/SimpleSoftOMXComponent.h>
+#include "gsmamr_dec.h"
+#include "pvamrwbdecoder.h"
namespace android {
@@ -60,6 +62,7 @@
void *mState;
void *mDecoderBuf;
int16_t *mDecoderCookie;
+ RX_State_wb mRxState{};
size_t mInputBufferCount;
int64_t mAnchorTimeUs;
diff --git a/media/libstagefright/codecs/amrnb/enc/Android.bp b/media/libstagefright/codecs/amrnb/enc/Android.bp
index 438ed04..73a1d4b 100644
--- a/media/libstagefright/codecs/amrnb/enc/Android.bp
+++ b/media/libstagefright/codecs/amrnb/enc/Android.bp
@@ -1,6 +1,7 @@
cc_library_static {
name: "libstagefright_amrnbenc",
vendor_available: true,
+ min_sdk_version: "29",
srcs: [
"src/amrencode.cpp",
diff --git a/media/libstagefright/codecs/amrwb/Android.bp b/media/libstagefright/codecs/amrwb/Android.bp
index d8cb568..204cbe3 100644
--- a/media/libstagefright/codecs/amrwb/Android.bp
+++ b/media/libstagefright/codecs/amrwb/Android.bp
@@ -2,6 +2,7 @@
name: "libstagefright_amrwbdec",
vendor_available: true,
host_supported: true,
+ min_sdk_version: "29",
srcs: [
"src/agc2_amr_wb.cpp",
diff --git a/media/libstagefright/codecs/amrwb/fuzzer/amrwb_dec_fuzzer.cpp b/media/libstagefright/codecs/amrwb/fuzzer/amrwb_dec_fuzzer.cpp
index 6dc0270..592a6ec 100644
--- a/media/libstagefright/codecs/amrwb/fuzzer/amrwb_dec_fuzzer.cpp
+++ b/media/libstagefright/codecs/amrwb/fuzzer/amrwb_dec_fuzzer.cpp
@@ -65,6 +65,7 @@
}
void Codec::decodeFrames(const uint8_t *data, size_t size) {
+ RX_State_wb rx_state{};
while (size > 0) {
uint8_t modeByte = *data;
bool quality = modeByte & 0x01;
@@ -81,7 +82,6 @@
memcpy(inputBuf, data, minSize);
int16 frameMode = mode;
int16 frameType;
- RX_State_wb rx_state;
mime_unsorting(inputBuf, inputSampleBuf, &frameType, &frameMode, quality, &rx_state);
int16_t numSamplesOutput;
diff --git a/media/libstagefright/codecs/amrwb/test/AmrwbDecoderTest.cpp b/media/libstagefright/codecs/amrwb/test/AmrwbDecoderTest.cpp
index 2aad81b..7221b92 100644
--- a/media/libstagefright/codecs/amrwb/test/AmrwbDecoderTest.cpp
+++ b/media/libstagefright/codecs/amrwb/test/AmrwbDecoderTest.cpp
@@ -74,6 +74,7 @@
uint8_t inputBuf[kInputBufferSize];
int16_t inputSampleBuf[kMaxSourceDataUnitSize];
int16_t outputBuf[kOutputBufferSize];
+ RX_State_wb rx_state{};
while (frameCount > 0) {
uint8_t modeByte;
@@ -98,7 +99,6 @@
int16 frameMode = mode;
int16 frameType;
- RX_State_wb rx_state;
mime_unsorting(inputBuf, inputSampleBuf, &frameType, &frameMode, 1, &rx_state);
int16_t numSamplesOutput;
diff --git a/media/libstagefright/codecs/amrwb/test/amrwbdec_test.cpp b/media/libstagefright/codecs/amrwb/test/amrwbdec_test.cpp
index b04bafd..1bc90e8 100644
--- a/media/libstagefright/codecs/amrwb/test/amrwbdec_test.cpp
+++ b/media/libstagefright/codecs/amrwb/test/amrwbdec_test.cpp
@@ -110,6 +110,7 @@
// Decode loop.
int retVal = EXIT_SUCCESS;
+ RX_State_wb rx_state{};
while (1) {
// Read mode.
uint8_t modeByte;
@@ -134,7 +135,6 @@
if (bytesRead != frameSize) break;
int16 frameType, frameMode;
- RX_State_wb rx_state;
frameMode = mode;
mime_unsorting(
(uint8_t *)inputBuf,
diff --git a/media/libstagefright/codecs/amrwbenc/Android.bp b/media/libstagefright/codecs/amrwbenc/Android.bp
index 084be0a..64f302c 100644
--- a/media/libstagefright/codecs/amrwbenc/Android.bp
+++ b/media/libstagefright/codecs/amrwbenc/Android.bp
@@ -1,6 +1,7 @@
cc_library_static {
name: "libstagefright_amrwbenc",
vendor_available: true,
+ min_sdk_version: "29",
srcs: [
"src/autocorr.c",
diff --git a/media/libstagefright/codecs/common/Android.bp b/media/libstagefright/codecs/common/Android.bp
index c5a076a..260a60a 100644
--- a/media/libstagefright/codecs/common/Android.bp
+++ b/media/libstagefright/codecs/common/Android.bp
@@ -1,6 +1,7 @@
cc_library {
name: "libstagefright_enc_common",
vendor_available: true,
+ min_sdk_version: "29",
srcs: ["cmnMemory.c"],
diff --git a/media/libstagefright/codecs/m4v_h263/dec/Android.bp b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
index c67dc2b..4303565 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
@@ -1,6 +1,11 @@
cc_library_static {
name: "libstagefright_m4vh263dec",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
shared_libs: ["liblog"],
srcs: [
diff --git a/media/libstagefright/codecs/m4v_h263/enc/Android.bp b/media/libstagefright/codecs/m4v_h263/enc/Android.bp
index 846f614..b8bc24e 100644
--- a/media/libstagefright/codecs/m4v_h263/enc/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/enc/Android.bp
@@ -1,6 +1,11 @@
cc_library_static {
name: "libstagefright_m4vh263enc",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
srcs: [
"src/bitstream_io.cpp",
diff --git a/media/libstagefright/codecs/mp3dec/Android.bp b/media/libstagefright/codecs/mp3dec/Android.bp
index b630524..96106f1 100644
--- a/media/libstagefright/codecs/mp3dec/Android.bp
+++ b/media/libstagefright/codecs/mp3dec/Android.bp
@@ -1,6 +1,7 @@
cc_library_static {
name: "libstagefright_mp3dec",
vendor_available: true,
+ min_sdk_version: "29",
srcs: [
"src/pvmp3_normalize.cpp",
diff --git a/media/libstagefright/data/media_codecs_sw.xml b/media/libstagefright/data/media_codecs_sw.xml
index dd2eed3..6571162 100644
--- a/media/libstagefright/data/media_codecs_sw.xml
+++ b/media/libstagefright/data/media_codecs_sw.xml
@@ -295,12 +295,12 @@
<Feature name="bitrate-modes" value="VBR,CBR" />
</MediaCodec>
<MediaCodec name="c2.android.hevc.encoder" type="video/hevc" variant="!slow-cpu">
- <!-- profiles and levels: ProfileMain : MainTierLevel51 -->
- <Limit name="size" min="2x2" max="512x512" />
+ <!-- profiles and levels: ProfileMain : MainTierLevel3 -->
+ <Limit name="size" min="2x2" max="960x544" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="8x8" />
- <Limit name="block-count" range="1-4096" /> <!-- max 512x512 -->
- <Limit name="blocks-per-second" range="1-122880" />
+ <Limit name="block-count" range="1-8160" /> <!-- max 960x544 -->
+ <Limit name="blocks-per-second" range="1-244880" />
<Limit name="frame-rate" range="1-120" />
<Limit name="bitrate" range="1-10000000" />
<Limit name="complexity" range="0-10" default="0" />
diff --git a/media/libstagefright/flac/dec/Android.bp b/media/libstagefright/flac/dec/Android.bp
index d65a663..32b2075 100644
--- a/media/libstagefright/flac/dec/Android.bp
+++ b/media/libstagefright/flac/dec/Android.bp
@@ -1,6 +1,7 @@
cc_library {
name: "libstagefright_flacdec",
vendor_available: true,
+ min_sdk_version: "29",
srcs: [
"FLACDecoder.cpp",
diff --git a/media/libstagefright/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index 9fe879e..f440e00 100644
--- a/media/libstagefright/foundation/Android.bp
+++ b/media/libstagefright/foundation/Android.bp
@@ -3,6 +3,7 @@
export_include_dirs: ["include"],
vendor_available: true,
host_supported: true,
+ min_sdk_version: "29",
}
cc_defaults {
@@ -101,11 +102,13 @@
cc_library {
name: "libstagefright_foundation",
defaults: ["libstagefright_foundation_defaults"],
+ min_sdk_version: "29",
}
cc_library_static {
name: "libstagefright_foundation_without_imemory",
defaults: ["libstagefright_foundation_defaults"],
+ min_sdk_version: "29",
cflags: [
"-Wno-multichar",
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index e0324e3..4324c45 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -615,7 +615,7 @@
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
}
- err = parseCipherInfo(line, &itemMeta, mBaseURI);
+ err = parseCipherInfo(line, &itemMeta);
} else if (line.startsWith("#EXT-X-ENDLIST")) {
mIsComplete = true;
} else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:EVENT")) {
@@ -936,7 +936,7 @@
// static
status_t M3UParser::parseCipherInfo(
- const AString &line, sp<AMessage> *meta, const AString &baseURI) {
+ const AString &line, sp<AMessage> *meta) {
ssize_t colonPos = line.find(":");
if (colonPos < 0) {
@@ -985,13 +985,9 @@
val = tmp;
}
- AString absURI;
- if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) {
- val = absURI;
- } else {
- ALOGE("failed to make absolute url for %s.",
- uriDebugString(baseURI).c_str());
- }
+ // To save space, we only store the partial Uri here
+ // The full Uri will be constructed from this plus
+ // the base Uri as needed by PlaylistFetcher
}
key.insert(AString("cipher-"), 0);
@@ -1003,6 +999,14 @@
return OK;
}
+AString M3UParser::getFullCipherUri(const AString &partial) {
+ AString full;
+ if (MakeURL(mBaseURI.c_str(), partial.c_str(), &full)) {
+ return full;
+ }
+ return AString("");
+}
+
// static
status_t M3UParser::parseByteRange(
const AString &line, uint64_t curOffset,
diff --git a/media/libstagefright/httplive/M3UParser.h b/media/libstagefright/httplive/M3UParser.h
index c85335a..9f7a66a 100644
--- a/media/libstagefright/httplive/M3UParser.h
+++ b/media/libstagefright/httplive/M3UParser.h
@@ -55,6 +55,8 @@
bool getTypeURI(size_t index, const char *key, AString *uri) const;
bool hasType(size_t index, const char *key) const;
+ AString getFullCipherUri(const AString &partial);
+
protected:
virtual ~M3UParser();
@@ -99,7 +101,7 @@
const AString &line, sp<AMessage> *meta) const;
static status_t parseCipherInfo(
- const AString &line, sp<AMessage> *meta, const AString &baseURI);
+ const AString &line, sp<AMessage> *meta);
static status_t parseByteRange(
const AString &line, uint64_t curOffset,
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
index fdcde29..b23aa8a 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.cpp
+++ b/media/libstagefright/httplive/PlaylistFetcher.cpp
@@ -345,6 +345,7 @@
ALOGE("Missing key uri");
return ERROR_MALFORMED;
}
+ keyURI = mPlaylist->getFullCipherUri(keyURI);
ssize_t index = mAESKeyForURI.indexOfKey(keyURI);
diff --git a/media/libstagefright/id3/Android.bp b/media/libstagefright/id3/Android.bp
index c8173cf..db37fe9 100644
--- a/media/libstagefright/id3/Android.bp
+++ b/media/libstagefright/id3/Android.bp
@@ -1,5 +1,6 @@
cc_library_static {
name: "libstagefright_id3",
+ min_sdk_version: "29",
srcs: ["ID3.cpp"],
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 34a7d55..a1fe57c 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -85,9 +85,10 @@
friend struct AHandlerReflector<MPEG4Writer>;
enum {
- kWhatSwitch = 'swch',
- kWhatHandleIOError = 'ioer',
- kWhatHandleFallocateError = 'faer'
+ kWhatSwitch = 'swch',
+ kWhatIOError = 'ioer',
+ kWhatFallocateError = 'faer',
+ kWhatNoIOErrorSoFar = 'noie'
};
int mFd;
@@ -231,7 +232,7 @@
status_t stopWriterThread();
static void *ThreadWrapper(void *me);
void threadFunc();
- void setupAndStartLooper();
+ status_t setupAndStartLooper();
void stopAndReleaseLooper();
// Buffer a single chunk to be written out later.
@@ -287,7 +288,8 @@
bool exceedsFileDurationLimit();
bool approachingFileSizeLimit();
bool isFileStreamable() const;
- void trackProgressStatus(size_t trackId, int64_t timeUs, status_t err = OK);
+ void trackProgressStatus(uint32_t trackId, int64_t timeUs, status_t err = OK);
+ status_t validateAllTracksId(bool akKey4BitTrackIds);
void writeCompositionMatrix(int32_t degrees);
void writeMvhdBox(int64_t durationUs);
void writeMoovBox(int64_t durationUs);
@@ -310,7 +312,7 @@
*/
bool preAllocate(uint64_t wantSize);
/*
- * Truncate file as per the size used for meta data and actual data in a session.
+ * Truncate file as per the size used for metadata and actual data in a session.
*/
bool truncatePreAllocation();
@@ -327,7 +329,7 @@
void writeFileLevelMetaBox();
void sendSessionSummary();
- void release();
+ status_t release();
status_t switchFd();
status_t reset(bool stopSource = true);
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 63a9dad..f7e6c27 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -139,7 +139,7 @@
// object.
status_t release();
- status_t releaseAsync();
+ status_t releaseAsync(const sp<AMessage> ¬ify);
status_t flush();
@@ -371,7 +371,8 @@
sp<Surface> mSurface;
SoftwareRenderer *mSoftRenderer;
- mediametrics_handle_t mMetricsHandle;
+ mediametrics_handle_t mMetricsHandle = 0;
+ nsecs_t mLifetimeStartNs = 0;
void initMediametrics();
void updateMediametrics();
void flushMediametrics();
@@ -382,6 +383,7 @@
sp<AMessage> mInputFormat;
sp<AMessage> mCallback;
sp<AMessage> mOnFrameRenderedNotification;
+ sp<AMessage> mAsyncReleaseCompleteNotification;
sp<ResourceManagerServiceProxy> mResourceManagerProxy;
@@ -514,6 +516,9 @@
class ReleaseSurface;
std::unique_ptr<ReleaseSurface> mReleaseSurface;
+ std::list<sp<AMessage>> mLeftover;
+ status_t handleLeftover(size_t index);
+
sp<BatteryChecker> mBatteryChecker;
void statsBufferSent(int64_t presentationUs);
diff --git a/media/libstagefright/include/media/stagefright/MediaMuxer.h b/media/libstagefright/include/media/stagefright/MediaMuxer.h
index 7c75f74..a1b9465 100644
--- a/media/libstagefright/include/media/stagefright/MediaMuxer.h
+++ b/media/libstagefright/include/media/stagefright/MediaMuxer.h
@@ -117,8 +117,6 @@
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;
@@ -130,11 +128,9 @@
UNINITIALIZED,
INITIALIZED,
STARTED,
- STOPPED,
- ERROR
+ STOPPED
};
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 08e54b3..1f4fbcb 100644
--- a/media/libstagefright/include/media/stagefright/MediaWriter.h
+++ b/media/libstagefright/include/media/stagefright/MediaWriter.h
@@ -21,7 +21,7 @@
#include <utils/RefBase.h>
#include <media/stagefright/MediaSource.h>
#include <media/IMediaRecorderClient.h>
-#include <media/stagefright/MediaMuxer.h>
+#include <media/mediarecorder.h>
namespace android {
@@ -46,7 +46,6 @@
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;
@@ -61,17 +60,20 @@
int64_t mMaxFileSizeLimitBytes;
int64_t mMaxFileDurationLimitUs;
sp<IMediaRecorderClient> mListener;
- wp<MediaMuxer> mMuxer;
void notify(int msg, int ext1, int ext2) {
- ALOG(LOG_VERBOSE, "MediaWriter", "notify msg:%d, ext1:%d, ext2:%d", msg, ext1, ext2);
+ if (msg == MEDIA_RECORDER_TRACK_EVENT_INFO || msg == MEDIA_RECORDER_TRACK_EVENT_ERROR) {
+ uint32_t trackId = (ext1 >> 28) & 0xf;
+ int type = ext1 & 0xfffffff;
+ ALOG(LOG_VERBOSE, "MediaWriter", "Track event err/info msg:%d, trackId:%u, type:%d,"
+ "val:%d", msg, trackId, type, ext2);
+ } else {
+ ALOG(LOG_VERBOSE, "MediaWriter", "Recorder event 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 3b701f6..64eb8b4 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataBase.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataBase.h
@@ -239,6 +239,12 @@
kKeyHapticChannelCount = 'hapC',
+ /* MediaRecorder.h, error notifications can represent track ids with 4 bits only.
+ * | track id | reserved | error or info type |
+ * 31 28 16 0
+ */
+ kKey4BitTrackIds = '4bid',
+
// Treat empty track as malformed for MediaRecorder.
kKeyEmptyTrackMalFormed = 'nemt', // bool (int32_t)
};
diff --git a/media/libstagefright/mpeg2ts/Android.bp b/media/libstagefright/mpeg2ts/Android.bp
index 42afea3..fbb2d0c 100644
--- a/media/libstagefright/mpeg2ts/Android.bp
+++ b/media/libstagefright/mpeg2ts/Android.bp
@@ -46,4 +46,6 @@
whole_static_libs: [
"libstagefright_metadatautils",
],
+
+ min_sdk_version: "29",
}
diff --git a/media/libstagefright/omx/OMXMaster.cpp b/media/libstagefright/omx/OMXMaster.cpp
index 8c5ca6e..094b1f5 100644
--- a/media/libstagefright/omx/OMXMaster.cpp
+++ b/media/libstagefright/omx/OMXMaster.cpp
@@ -16,6 +16,7 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "OMXMaster"
+#include <android-base/properties.h>
#include <utils/Log.h>
#include <media/stagefright/omx/OMXMaster.h>
@@ -67,6 +68,10 @@
}
void OMXMaster::addPlugin(const char *libname) {
+ if (::android::base::GetIntProperty("vendor.media.omx", int64_t(1)) == 0) {
+ return;
+ }
+
void *libHandle = android_load_sphal_library(libname, RTLD_NOW);
if (libHandle == NULL) {
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index 5b2f6de..ac42373 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -214,6 +214,80 @@
}
}
+template <typename T>
+inline static T *asSetting(void *setting /* nonnull */, size_t size) {
+ // no need to check internally stored size as that is outside of sanitizing
+ // the underlying buffer's size is the one passed into this method.
+ if (size < sizeof(T)) {
+ return nullptr;
+ }
+
+ return (T *)setting;
+}
+
+inline static void sanitize(OMX_CONFIG_CONTAINERNODEIDTYPE *s) {
+ s->cNodeName = 0;
+}
+
+inline static void sanitize(OMX_CONFIG_METADATAITEMTYPE *s) {
+ s->sLanguageCountry = 0;
+}
+
+inline static void sanitize(OMX_PARAM_PORTDEFINITIONTYPE *s) {
+ switch (s->eDomain) {
+ case OMX_PortDomainAudio:
+ s->format.audio.cMIMEType = 0;
+ break;
+ case OMX_PortDomainVideo:
+ s->format.video.cMIMEType = 0;
+ break;
+ case OMX_PortDomainImage:
+ s->format.image.cMIMEType = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+template <typename T>
+static bool sanitizeAs(void *setting, size_t size) {
+ T *s = asSetting<T>(setting, size);
+ if (s) {
+ sanitize(s);
+ return true;
+ }
+ return false;
+}
+
+static void sanitizeSetting(OMX_INDEXTYPE index, void *setting, size_t size) {
+ if (size < 8 || setting == nullptr) {
+ return;
+ }
+
+ bool ok = true;
+
+ // there are 3 standard OMX settings that contain pointer members
+ switch ((OMX_U32)index) {
+ case OMX_IndexConfigCounterNodeID:
+ ok = sanitizeAs<OMX_CONFIG_CONTAINERNODEIDTYPE>(setting, size);
+ break;
+ case OMX_IndexConfigMetadataItem:
+ ok = sanitizeAs<OMX_CONFIG_METADATAITEMTYPE>(setting, size);
+ break;
+ case OMX_IndexParamPortDefinition:
+ ok = sanitizeAs<OMX_PARAM_PORTDEFINITIONTYPE>(setting, size);
+ break;
+ }
+
+ if (!ok) {
+ // cannot effectively sanitize - we should not be here as IOMX.cpp
+ // should guard against size being too small. Nonetheless, log and
+ // clear result.
+ android_errorWriteLog(0x534e4554, "120781925");
+ memset(setting, 0, size);
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// This provides the underlying Thread used by CallbackDispatcher.
@@ -608,7 +682,7 @@
}
status_t OMXNodeInstance::getParameter(
- OMX_INDEXTYPE index, void *params, size_t /* size */) {
+ OMX_INDEXTYPE index, void *params, size_t size) {
Mutex::Autolock autoLock(mLock);
if (mHandle == NULL) {
return DEAD_OBJECT;
@@ -625,6 +699,7 @@
if (err != OMX_ErrorNoMore) {
CLOG_IF_ERROR(getParameter, err, "%s(%#x)", asString(extIndex), index);
}
+ sanitizeSetting(index, params, size);
return StatusFromOMXError(err);
}
@@ -650,11 +725,12 @@
OMX_ERRORTYPE err = OMX_SetParameter(
mHandle, index, const_cast<void *>(params));
CLOG_IF_ERROR(setParameter, err, "%s(%#x)", asString(extIndex), index);
+ sanitizeSetting(index, const_cast<void *>(params), size);
return StatusFromOMXError(err);
}
status_t OMXNodeInstance::getConfig(
- OMX_INDEXTYPE index, void *params, size_t /* size */) {
+ OMX_INDEXTYPE index, void *params, size_t size) {
Mutex::Autolock autoLock(mLock);
if (mHandle == NULL) {
return DEAD_OBJECT;
@@ -671,6 +747,8 @@
if (err != OMX_ErrorNoMore) {
CLOG_IF_ERROR(getConfig, err, "%s(%#x)", asString(extIndex), index);
}
+
+ sanitizeSetting(index, params, size);
return StatusFromOMXError(err);
}
@@ -692,6 +770,7 @@
OMX_ERRORTYPE err = OMX_SetConfig(
mHandle, index, const_cast<void *>(params));
CLOG_IF_ERROR(setConfig, err, "%s(%#x)", asString(extIndex), index);
+ sanitizeSetting(index, const_cast<void *>(params), size);
return StatusFromOMXError(err);
}
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
index 7893148..039991c 100644
--- a/media/libstagefright/omx/tests/OMXHarness.cpp
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -211,6 +211,17 @@
status_t err = getPortDefinition(portIndex, &def);
EXPECT_SUCCESS(err, "getPortDefinition");
+ switch (def.eDomain) {
+ case OMX_PortDomainVideo:
+ EXPECT(def.format.video.cMIMEType == 0, "portDefinition video MIME");
+ break;
+ case OMX_PortDomainAudio:
+ EXPECT(def.format.audio.cMIMEType == 0, "portDefinition audio MIME");
+ break;
+ default:
+ break;
+ }
+
for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
Buffer buffer;
buffer.mFlags = 0;
diff --git a/media/libwatchdog/Android.bp b/media/libwatchdog/Android.bp
index 2aefa7d..1a87824 100644
--- a/media/libwatchdog/Android.bp
+++ b/media/libwatchdog/Android.bp
@@ -31,4 +31,5 @@
},
},
apex_available: ["com.android.media"],
+ min_sdk_version: "29",
}
diff --git a/media/mediaserver/Android.bp b/media/mediaserver/Android.bp
index a968890..afca7c4 100644
--- a/media/mediaserver/Android.bp
+++ b/media/mediaserver/Android.bp
@@ -15,13 +15,14 @@
srcs: ["main_mediaserver.cpp"],
shared_libs: [
- "libresourcemanagerservice",
+ "android.hardware.media.omx@1.0",
+ "libandroidicu",
+ "libbinder",
+ "libhidlbase",
"liblog",
"libmediaplayerservice",
+ "libresourcemanagerservice",
"libutils",
- "libbinder",
- "libandroidicu",
- "android.hardware.media.omx@1.0",
],
static_libs: [
diff --git a/media/mediaserver/main_mediaserver.cpp b/media/mediaserver/main_mediaserver.cpp
index 7b22b05..316732b 100644
--- a/media/mediaserver/main_mediaserver.cpp
+++ b/media/mediaserver/main_mediaserver.cpp
@@ -22,6 +22,7 @@
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
+#include <hidl/HidlTransportSupport.h>
#include <utils/Log.h>
#include "RegisterExtensions.h"
@@ -42,6 +43,8 @@
MediaPlayerService::instantiate();
ResourceManagerService::instantiate();
registerExtensions();
+ ::android::hardware::configureRpcThreadpool(16, false);
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
+ ::android::hardware::joinRpcThreadpool();
}
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index a04a962..a2f8230 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -35,6 +35,12 @@
cc_library_headers {
name: "media_ndk_headers",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
export_include_dirs: ["include"]
}
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index de7ae40..f014209 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1176,6 +1176,10 @@
mPlaybackThreads.valueAt(i)->setMode(mode);
}
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_SETMODE)
+ .set(AMEDIAMETRICS_PROP_AUDIOMODE, toString(mode))
+ .record();
return ret;
}
@@ -1741,6 +1745,10 @@
ret = dev->setVoiceVolume(value);
mHardwareStatus = AUDIO_HW_IDLE;
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOICEVOLUME)
+ .set(AMEDIAMETRICS_PROP_VOICEVOLUME, (double)value)
+ .record();
return ret;
}
diff --git a/services/audioflinger/AudioStreamOut.cpp b/services/audioflinger/AudioStreamOut.cpp
index d13cb8f..7e06096 100644
--- a/services/audioflinger/AudioStreamOut.cpp
+++ b/services/audioflinger/AudioStreamOut.cpp
@@ -164,6 +164,10 @@
stream = outStream;
mHalFormatHasProportionalFrames = audio_has_proportional_frames(config->format);
status = stream->getFrameSize(&mHalFrameSize);
+ LOG_ALWAYS_FATAL_IF(status != OK, "Error retrieving frame size from HAL: %d", status);
+ LOG_ALWAYS_FATAL_IF(mHalFrameSize <= 0, "Error frame size was %zu but must be greater than"
+ " zero", mHalFrameSize);
+
}
return status;
diff --git a/services/audioflinger/DeviceEffectManager.cpp b/services/audioflinger/DeviceEffectManager.cpp
index a3c3b84..5ff7215 100644
--- a/services/audioflinger/DeviceEffectManager.cpp
+++ b/services/audioflinger/DeviceEffectManager.cpp
@@ -144,7 +144,8 @@
write(fd, result.string(), result.size());
}
- write(fd, "\nDevice Effects:\n", sizeof("\nDevice Effects:\n"));
+ String8 heading("\nDevice Effects:\n");
+ write(fd, heading.string(), heading.size());
for (const auto& iter : mDeviceEffects) {
String8 outStr;
outStr.appendFormat("%*sEffect for device %s address %s:\n", 2, "",
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index 55f2952..3dfeb83 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -2812,7 +2812,7 @@
if (t == nullptr) {
return false;
}
- return t->type() == ThreadBase::OFFLOAD || t->type() == ThreadBase::MMAP;
+ return t->isOffloadOrMmap();
}
uint32_t AudioFlinger::EffectChain::EffectCallback::sampleRate() const {
diff --git a/services/audioflinger/FastCapture.cpp b/services/audioflinger/FastCapture.cpp
index dd84bf2..d6d6e25 100644
--- a/services/audioflinger/FastCapture.cpp
+++ b/services/audioflinger/FastCapture.cpp
@@ -154,7 +154,7 @@
mReadBufferState = -1;
dumpState->mFrameCount = frameCount;
}
-
+ dumpState->mSilenced = current->mSilenceCapture;
}
void FastCapture::onWork()
@@ -208,6 +208,9 @@
mReadBufferState = frameCount;
}
if (mReadBufferState > 0) {
+ if (current->mSilenceCapture) {
+ memset(mReadBuffer, 0, mReadBufferState * Format_frameSize(mFormat));
+ }
ssize_t framesWritten = mPipeSink->write(mReadBuffer, mReadBufferState);
audio_track_cblk_t* cblk = current->mCblk;
if (fastPatchRecordBufferProvider != 0) {
diff --git a/services/audioflinger/FastCaptureDumpState.cpp b/services/audioflinger/FastCaptureDumpState.cpp
index 53eeba5..b8b3866 100644
--- a/services/audioflinger/FastCaptureDumpState.cpp
+++ b/services/audioflinger/FastCaptureDumpState.cpp
@@ -44,10 +44,11 @@
double periodSec = (double) mFrameCount / mSampleRate;
dprintf(fd, " FastCapture command=%s readSequence=%u framesRead=%u\n"
" readErrors=%u sampleRate=%u frameCount=%zu\n"
- " measuredWarmup=%.3g ms, warmupCycles=%u period=%.2f ms\n",
+ " measuredWarmup=%.3g ms, warmupCycles=%u period=%.2f ms\n"
+ " silenced: %s\n",
FastCaptureState::commandToString(mCommand), mReadSequence, mFramesRead,
mReadErrors, mSampleRate, mFrameCount, measuredWarmupMs, mWarmupCycles,
- periodSec * 1e3);
+ periodSec * 1e3, mSilenced ? "true" : "false");
}
} // android
diff --git a/services/audioflinger/FastCaptureDumpState.h b/services/audioflinger/FastCaptureDumpState.h
index 6f9c4c3..a1b8706 100644
--- a/services/audioflinger/FastCaptureDumpState.h
+++ b/services/audioflinger/FastCaptureDumpState.h
@@ -35,6 +35,7 @@
uint32_t mReadErrors; // total number of read() errors
uint32_t mSampleRate;
size_t mFrameCount;
+ bool mSilenced = false; // capture is silenced
};
} // android
diff --git a/services/audioflinger/FastCaptureState.h b/services/audioflinger/FastCaptureState.h
index d287232..f949275 100644
--- a/services/audioflinger/FastCaptureState.h
+++ b/services/audioflinger/FastCaptureState.h
@@ -41,6 +41,8 @@
audio_format_t mFastPatchRecordFormat = AUDIO_FORMAT_INVALID;
AudioBufferProvider* mFastPatchRecordBufferProvider = nullptr; // a reference to a patch
// record in fast mode
+ bool mSilenceCapture = false; // request to silence capture for fast track.
+ // note: this also silences the normal mixer pipe
// Extends FastThreadState::Command
static const Command
diff --git a/services/audioflinger/ThreadMetrics.h b/services/audioflinger/ThreadMetrics.h
index 7989de1..6526655 100644
--- a/services/audioflinger/ThreadMetrics.h
+++ b/services/audioflinger/ThreadMetrics.h
@@ -58,9 +58,11 @@
// 2) We come out of standby
void logBeginInterval() {
std::lock_guard l(mLock);
- if (mDevices != mCreatePatchDevices) {
+ // The devices we look for change depend on whether the Thread is input or output.
+ const std::string& patchDevices = mIsOut ? mCreatePatchOutDevices : mCreatePatchInDevices;
+ if (mDevices != patchDevices) {
deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
- mDevices = mCreatePatchDevices; // set after endAudioIntervalGroup
+ mDevices = patchDevices; // set after endAudioIntervalGroup
resetIntervalGroupMetrics();
deliverDeviceMetrics(
AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP, mDevices.c_str());
@@ -80,12 +82,14 @@
.record();
}
- void logCreatePatch(const std::string& devices) {
+ void logCreatePatch(const std::string& inDevices, const std::string& outDevices) {
std::lock_guard l(mLock);
- mCreatePatchDevices = devices;
+ mCreatePatchInDevices = inDevices;
+ mCreatePatchOutDevices = outDevices;
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH)
- .set(AMEDIAMETRICS_PROP_OUTPUTDEVICES, devices)
+ .set(AMEDIAMETRICS_PROP_INPUTDEVICES, inDevices)
+ .set(AMEDIAMETRICS_PROP_OUTPUTDEVICES, outDevices)
.record();
}
@@ -115,11 +119,13 @@
mDeviceLatencyMs.add(latencyMs);
}
- // TODO: further implement this.
- void logUnderrunFrames(size_t count, size_t frames) {
+ void logUnderrunFrames(size_t frames) {
std::lock_guard l(mLock);
- mUnderrunCount = count;
- mUnderrunFrames = frames;
+ if (mLastUnderrun == false && frames > 0) {
+ ++mUnderrunCount; // count non-continguous underrun sequences.
+ }
+ mLastUnderrun = (frames > 0);
+ mUnderrunFrames += frames;
}
const std::string& getMetricsId() const {
@@ -164,6 +170,7 @@
mDeviceLatencyMs.reset();
+ mLastUnderrun = false;
mUnderrunCount = 0;
mUnderrunFrames = 0;
}
@@ -174,8 +181,9 @@
mutable std::mutex mLock;
// Devices in the interval group.
- std::string mDevices GUARDED_BY(mLock);
- std::string mCreatePatchDevices GUARDED_BY(mLock);
+ std::string mDevices GUARDED_BY(mLock); // last input or output devices based on mIsOut.
+ std::string mCreatePatchInDevices GUARDED_BY(mLock);
+ std::string mCreatePatchOutDevices GUARDED_BY(mLock);
// Number of intervals and playing time
int32_t mIntervalCount GUARDED_BY(mLock) = 0;
@@ -187,8 +195,9 @@
audio_utils::Statistics<double> mDeviceLatencyMs GUARDED_BY(mLock);
// underrun count and frames
- int64_t mUnderrunCount GUARDED_BY(mLock) = 0;
- int64_t mUnderrunFrames GUARDED_BY(mLock) = 0;
+ bool mLastUnderrun GUARDED_BY(mLock) = false; // checks consecutive underruns
+ int64_t mUnderrunCount GUARDED_BY(mLock) = 0; // number of consecutive underruns
+ int64_t mUnderrunFrames GUARDED_BY(mLock) = 0; // total estimated frames underrun
};
} // namespace android
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 594baf8..2af27d8 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -484,8 +484,10 @@
return "RECORD";
case OFFLOAD:
return "OFFLOAD";
- case MMAP:
- return "MMAP";
+ case MMAP_PLAYBACK:
+ return "MMAP_PLAYBACK";
+ case MMAP_CAPTURE:
+ return "MMAP_CAPTURE";
default:
return "unknown";
}
@@ -967,8 +969,10 @@
return String16("AudioIn");
case OFFLOAD:
return String16("AudioOffload");
- case MMAP:
- return String16("Mmap");
+ case MMAP_PLAYBACK:
+ return String16("MmapPlayback");
+ case MMAP_CAPTURE:
+ return String16("MmapCapture");
default:
ALOG_ASSERT(false);
return String16("AudioUnknown");
@@ -1477,7 +1481,7 @@
}
void AudioFlinger::ThreadBase::onEffectEnable(const sp<EffectModule>& effect) {
- if (mType == OFFLOAD || mType == MMAP) {
+ if (isOffloadOrMmap()) {
Mutex::Autolock _l(mLock);
broadcast_l();
}
@@ -1493,7 +1497,7 @@
}
void AudioFlinger::ThreadBase::onEffectDisable() {
- if (mType == OFFLOAD || mType == MMAP) {
+ if (isOffloadOrMmap()) {
Mutex::Autolock _l(mLock);
broadcast_l();
}
@@ -4260,7 +4264,7 @@
const std::string patchSinksAsString = patchSinksToString(patch);
mThreadMetrics.logEndInterval();
- mThreadMetrics.logCreatePatch(patchSinksAsString);
+ mThreadMetrics.logCreatePatch(/* inDevices */ {}, patchSinksAsString);
mThreadMetrics.logBeginInterval();
// also dispatch to active AudioTracks for MediaMetrics
for (const auto &track : mActiveTracks) {
@@ -4809,19 +4813,24 @@
// DeferredOperations handles statistics after setting mixerStatus.
class DeferredOperations {
public:
- explicit DeferredOperations(mixer_state *mixerStatus)
- : mMixerStatus(mixerStatus) {}
+ DeferredOperations(mixer_state *mixerStatus, ThreadMetrics *threadMetrics)
+ : mMixerStatus(mixerStatus)
+ , mThreadMetrics(threadMetrics) {}
// when leaving scope, tally frames properly.
~DeferredOperations() {
// Tally underrun frames only if we are actually mixing (MIXER_TRACKS_READY)
// because that is when the underrun occurs.
// We do not distinguish between FastTracks and NormalTracks here.
+ size_t maxUnderrunFrames = 0;
if (*mMixerStatus == MIXER_TRACKS_READY && mUnderrunFrames.size() > 0) {
for (const auto &underrun : mUnderrunFrames) {
underrun.first->tallyUnderrunFrames(underrun.second);
+ maxUnderrunFrames = max(underrun.second, maxUnderrunFrames);
}
}
+ // send the max underrun frames for this mixer period
+ mThreadMetrics->logUnderrunFrames(maxUnderrunFrames);
}
// tallyUnderrunFrames() is called to update the track counters
@@ -4833,8 +4842,9 @@
private:
const mixer_state * const mMixerStatus;
+ ThreadMetrics * const mThreadMetrics;
std::vector<std::pair<sp<Track>, size_t>> mUnderrunFrames;
- } deferredOperations(&mixerStatus);
+ } deferredOperations(&mixerStatus, &mThreadMetrics);
// implicit nested scope for variable capture
bool noFastHapticTrack = true;
@@ -6046,10 +6056,6 @@
bool trackPaused = false;
bool trackStopped = false;
- if ((mType == DIRECT) && audio_is_linear_pcm(mFormat) && !usesHwAvSync()) {
- return !mStandby;
- }
-
// do not put the HAL in standby when paused. AwesomePlayer clear the offloaded AudioTrack
// after a timeout and we will enter standby then.
if (mTracks.size() > 0) {
@@ -7073,6 +7079,8 @@
// reference to a fast track which is about to be removed
sp<RecordTrack> fastTrackToRemove;
+ bool silenceFastCapture = false;
+
{ // scope for mLock
Mutex::Autolock _l(mLock);
@@ -7160,14 +7168,33 @@
__func__, activeTrackState, activeTrack->id(), size);
}
- activeTracks.add(activeTrack);
- i++;
-
if (activeTrack->isFastTrack()) {
ALOG_ASSERT(!mFastTrackAvail);
ALOG_ASSERT(fastTrack == 0);
+ // if the active fast track is silenced either:
+ // 1) silence the whole capture from fast capture buffer if this is
+ // the only active track
+ // 2) invalidate this track: this will cause the client to reconnect and possibly
+ // be invalidated again until unsilenced
+ if (activeTrack->isSilenced()) {
+ if (size > 1) {
+ activeTrack->invalidate();
+ ALOG_ASSERT(fastTrackToRemove == 0);
+ fastTrackToRemove = activeTrack;
+ removeTrack_l(activeTrack);
+ mActiveTracks.remove(activeTrack);
+ size--;
+ continue;
+ } else {
+ silenceFastCapture = true;
+ }
+ }
fastTrack = activeTrack;
}
+
+ activeTracks.add(activeTrack);
+ i++;
+
}
mActiveTracks.updatePowerState(this);
@@ -7241,6 +7268,10 @@
AUDIO_FORMAT_INVALID : fastTrack->format();
didModify = true;
}
+ if (state->mSilenceCapture != silenceFastCapture) {
+ state->mSilenceCapture = silenceFastCapture;
+ didModify = true;
+ }
sq->end(didModify);
if (didModify) {
sq->push(block);
@@ -7325,8 +7356,10 @@
// Update server timestamp with server stats
// systemTime() is optional if the hardware supports timestamps.
- mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER] += framesRead;
- mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] = lastIoEndNs;
+ if (framesRead >= 0) {
+ mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER] += framesRead;
+ mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] = lastIoEndNs;
+ }
// Update server timestamp with kernel stats
if (mPipeSource.get() == nullptr /* don't obtain for FastCapture, could block */) {
@@ -8393,13 +8426,14 @@
}
result = mInput->stream->getFrameSize(&mFrameSize);
LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving frame size from HAL: %d", result);
+ LOG_ALWAYS_FATAL_IF(mFrameSize <= 0, "Error frame size was %zu but must be greater than zero",
+ mFrameSize);
result = mInput->stream->getBufferSize(&mBufferSize);
LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving buffer size from HAL: %d", result);
mFrameCount = mBufferSize / mFrameSize;
- ALOGV("%p RecordThread params: mChannelCount=%u, mFormat=%#x, mFrameSize=%lld, "
- "mBufferSize=%lld, mFrameCount=%lld",
- this, mChannelCount, mFormat, (long long)mFrameSize, (long long)mBufferSize,
- (long long)mFrameCount);
+ ALOGV("%p RecordThread params: mChannelCount=%u, mFormat=%#x, mFrameSize=%zu, "
+ "mBufferSize=%zu, mFrameCount=%zu",
+ this, mChannelCount, mFormat, mFrameSize, mBufferSize, mFrameCount);
// This is the formula for calculating the temporary buffer size.
// With 7 HAL buffers, we can guarantee ability to down-sample the input by ratio of 6:1 to
// 1 full output buffer, regardless of the alignment of the available input.
@@ -8428,6 +8462,17 @@
// AudioRecord mSampleRate and mChannelCount are constant due to AudioRecord API constraints.
// But if thread's mSampleRate or mChannelCount changes, how will that affect active tracks?
+
+ audio_input_flags_t flags = mInput->flags;
+ mediametrics::LogItem item(mThreadMetrics.getMetricsId());
+ item.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_READPARAMETERS)
+ .set(AMEDIAMETRICS_PROP_ENCODING, formatToString(mFormat).c_str())
+ .set(AMEDIAMETRICS_PROP_FLAGS, toString(flags).c_str())
+ .set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
+ .set(AMEDIAMETRICS_PROP_CHANNELMASK, (int32_t)mChannelMask)
+ .set(AMEDIAMETRICS_PROP_CHANNELCOUNT, (int32_t)mChannelCount)
+ .set(AMEDIAMETRICS_PROP_FRAMECOUNT, (int32_t)mFrameCount)
+ .record();
}
uint32_t AudioFlinger::RecordThread::getInputFramesLost()
@@ -8558,7 +8603,7 @@
const std::string pathSourcesAsString = patchSourcesToString(patch);
mThreadMetrics.logEndInterval();
- mThreadMetrics.logCreatePatch(pathSourcesAsString);
+ mThreadMetrics.logCreatePatch(pathSourcesAsString, /* outDevices */ {});
mThreadMetrics.logBeginInterval();
// also dispatch to active AudioRecords
for (const auto &track : mActiveTracks) {
@@ -8672,7 +8717,7 @@
AudioFlinger::MmapThread::MmapThread(
const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady, bool isOut)
- : ThreadBase(audioFlinger, id, MMAP, systemReady, isOut),
+ : ThreadBase(audioFlinger, id, (isOut ? MMAP_PLAYBACK : MMAP_CAPTURE), systemReady, isOut),
mSessionId(AUDIO_SESSION_NONE),
mPortId(AUDIO_PORT_HANDLE_NONE),
mHalStream(stream), mHalDevice(hwDev->hwDevice()), mAudioHwDev(hwDev),
@@ -8964,6 +9009,8 @@
LOG_ALWAYS_FATAL_IF(!audio_is_linear_pcm(mFormat), "HAL format %#x is not linear pcm", mFormat);
result = mHalStream->getFrameSize(&mFrameSize);
LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving frame size from HAL: %d", result);
+ LOG_ALWAYS_FATAL_IF(mFrameSize <= 0, "Error frame size was %zu but must be greater than zero",
+ mFrameSize);
result = mHalStream->getBufferSize(&mBufferSize);
LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving buffer size from HAL: %d", result);
mFrameCount = mBufferSize / mFrameSize;
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 5b8c081..c1ac2e4 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -30,7 +30,8 @@
DUPLICATING, // Thread class is DuplicatingThread
RECORD, // Thread class is RecordThread
OFFLOAD, // Thread class is OffloadThread
- MMAP // control thread for MMAP stream
+ MMAP_PLAYBACK, // Thread class for MMAP playback stream
+ MMAP_CAPTURE, // Thread class for MMAP capture stream
// If you add any values here, also update ThreadBase::threadTypeToString()
};
@@ -332,6 +333,17 @@
bool isOutput() const { return mIsOut; }
+ bool isOffloadOrMmap() const {
+ switch (mType) {
+ case OFFLOAD:
+ case MMAP_PLAYBACK:
+ case MMAP_CAPTURE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
virtual sp<StreamHalInterface> stream() const = 0;
sp<EffectHandle> createEffect_l(
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 15c66fb..01d5345 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -373,10 +373,15 @@
bool mIsInvalid; // non-resettable latch, set by invalidate()
// It typically takes 5 threadloop mix iterations for latency to stabilize.
- static inline constexpr int32_t LOG_START_COUNTDOWN = 8;
- int32_t mLogStartCountdown = 0;
- int64_t mLogStartTimeNs = 0;
- int64_t mLogStartFrames = 0;
+ // However, this can be 12+ iterations for BT.
+ // To be sure, we wait for latency to dip (it usually increases at the start)
+ // to assess stability and then log to MediaMetrics.
+ // Rapid start / pause calls may cause inaccurate numbers.
+ static inline constexpr int32_t LOG_START_COUNTDOWN = 12;
+ int32_t mLogStartCountdown = 0; // Mixer period countdown
+ int64_t mLogStartTimeNs = 0; // Monotonic time at start()
+ int64_t mLogStartFrames = 0; // Timestamp frames at start()
+ double mLogLatencyMs = 0.; // Track the last log latency
TrackMetrics mTrackMetrics;
diff --git a/services/audioflinger/TrackMetrics.h b/services/audioflinger/TrackMetrics.h
index 399c788..af16448 100644
--- a/services/audioflinger/TrackMetrics.h
+++ b/services/audioflinger/TrackMetrics.h
@@ -67,16 +67,23 @@
mIntervalStartTimeNs = systemTime();
}
- void logConstructor(pid_t creatorPid, uid_t creatorUid) const {
+ void logConstructor(pid_t creatorPid, uid_t creatorUid,
+ const std::string& traits = {},
+ audio_stream_type_t streamType = AUDIO_STREAM_DEFAULT) const {
// Once this item is logged by the server, the client can add properties.
// no lock required, all local or const variables.
- mediametrics::LogItem(mMetricsId)
- .setPid(creatorPid)
+ mediametrics::LogItem item(mMetricsId);
+ item.setPid(creatorPid)
.setUid(creatorUid)
.set(AMEDIAMETRICS_PROP_ALLOWUID, (int32_t)creatorUid)
.set(AMEDIAMETRICS_PROP_EVENT,
AMEDIAMETRICS_PROP_PREFIX_SERVER AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
- .record();
+ .set(AMEDIAMETRICS_PROP_TRAITS, traits);
+ // log streamType from the service, since client doesn't know chosen streamType.
+ if (streamType != AUDIO_STREAM_DEFAULT) {
+ item.set(AMEDIAMETRICS_PROP_STREAMTYPE, toString(streamType).c_str());
+ }
+ item.record();
}
// Called when we are removed from the Thread.
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 58c61c9..126015f 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -602,7 +602,8 @@
}
// Once this item is logged by the server, the client can add properties.
- mTrackMetrics.logConstructor(creatorPid, uid);
+ const char * const traits = sharedBuffer == 0 ? "" : "static";
+ mTrackMetrics.logConstructor(creatorPid, uid, traits, streamType);
}
AudioFlinger::PlaybackThread::Track::~Track()
@@ -804,7 +805,7 @@
status_t status = mServerProxy->obtainBuffer(&buf);
buffer->frameCount = buf.mFrameCount;
buffer->raw = buf.mRaw;
- if (buf.mFrameCount == 0 && !isStopping() && !isStopped() && !isPaused()) {
+ if (buf.mFrameCount == 0 && !isStopping() && !isStopped() && !isPaused() && !isOffloaded()) {
ALOGV("%s(%d): underrun, framesReady(%zu) < framesDesired(%zd), state: %d",
__func__, mId, buf.mFrameCount, desiredFrames, mState);
mAudioTrackServerProxy->tallyUnderrunFrames(desiredFrames);
@@ -978,7 +979,8 @@
mLogStartCountdown = LOG_START_COUNTDOWN;
mLogStartTimeNs = systemTime();
mLogStartFrames = mAudioTrackServerProxy->getTimestamp()
- .mPosition[ExtendedTimestamp::LOCATION_SERVER];
+ .mPosition[ExtendedTimestamp::LOCATION_KERNEL];
+ mLogLatencyMs = 0.;
}
if (status == NO_ERROR || status == ALREADY_EXISTS) {
@@ -1514,23 +1516,31 @@
mServerLatencyFromTrack.store(useTrackTimestamp);
mServerLatencyMs.store(latencyMs);
- if (mLogStartCountdown > 0) {
- if (--mLogStartCountdown == 0) {
+ if (mLogStartCountdown > 0
+ && local.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] > 0
+ && local.mPosition[ExtendedTimestamp::LOCATION_KERNEL] > 0)
+ {
+ if (mLogStartCountdown > 1) {
+ --mLogStartCountdown;
+ } else if (latencyMs < mLogLatencyMs) { // wait for latency to stabilize (dip)
+ mLogStartCountdown = 0;
// startup is the difference in times for the current timestamp and our start
double startUpMs =
- (local.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] - mLogStartTimeNs) * 1e-6;
+ (local.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] - mLogStartTimeNs) * 1e-6;
// adjust for frames played.
- startUpMs -= (local.mPosition[ExtendedTimestamp::LOCATION_SERVER] - mLogStartFrames)
- * 1e3 / mSampleRate;
- ALOGV("%s: logging localTime:%lld, startTime:%lld"
- " localPosition:%lld, startPosition:%lld",
- __func__,
- (long long)local.mTimeNs[ExtendedTimestamp::LOCATION_SERVER],
+ startUpMs -= (local.mPosition[ExtendedTimestamp::LOCATION_KERNEL] - mLogStartFrames)
+ * 1e3 / mSampleRate;
+ ALOGV("%s: latencyMs:%lf startUpMs:%lf"
+ " localTime:%lld startTime:%lld"
+ " localPosition:%lld startPosition:%lld",
+ __func__, latencyMs, startUpMs,
+ (long long)local.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL],
(long long)mLogStartTimeNs,
- (long long)local.mPosition[ExtendedTimestamp::LOCATION_SERVER],
+ (long long)local.mPosition[ExtendedTimestamp::LOCATION_KERNEL],
(long long)mLogStartFrames);
mTrackMetrics.logLatencyAndStartup(latencyMs, startUpMs);
}
+ mLogLatencyMs = latencyMs;
}
}
diff --git a/services/audiopolicy/common/managerdefinitions/Android.bp b/services/audiopolicy/common/managerdefinitions/Android.bp
index fad3c5b..57f0b5b 100644
--- a/services/audiopolicy/common/managerdefinitions/Android.bp
+++ b/services/audiopolicy/common/managerdefinitions/Android.bp
@@ -25,12 +25,14 @@
"libhidlbase",
"liblog",
"libmedia",
+ "libmedia_helper",
"libutils",
"libxml2",
],
export_shared_lib_headers: [
"libaudiofoundation",
"libmedia",
+ "libmedia_helper",
],
static_libs: [
"libaudioutils",
diff --git a/services/audiopolicy/common/managerdefinitions/include/ClientDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/ClientDescriptor.h
index 0c5d1d0..923310c 100644
--- a/services/audiopolicy/common/managerdefinitions/include/ClientDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/ClientDescriptor.h
@@ -35,6 +35,7 @@
namespace android {
+class AudioPolicyMix;
class DeviceDescriptor;
class HwAudioOutputDescriptor;
class SwAudioOutputDescriptor;
@@ -90,11 +91,12 @@
product_strategy_t strategy, VolumeSource volumeSource,
audio_output_flags_t flags,
bool isPreferredDeviceForExclusiveUse,
- std::vector<wp<SwAudioOutputDescriptor>> secondaryOutputs) :
+ std::vector<wp<SwAudioOutputDescriptor>> secondaryOutputs,
+ wp<AudioPolicyMix> primaryMix) :
ClientDescriptor(portId, uid, sessionId, attributes, config, preferredDeviceId,
isPreferredDeviceForExclusiveUse),
mStream(stream), mStrategy(strategy), mVolumeSource(volumeSource), mFlags(flags),
- mSecondaryOutputs(std::move(secondaryOutputs)) {}
+ mSecondaryOutputs(std::move(secondaryOutputs)), mPrimaryMix(primaryMix) {}
~TrackClientDescriptor() override = default;
using ClientDescriptor::dump;
@@ -108,6 +110,9 @@
return mSecondaryOutputs;
};
VolumeSource volumeSource() const { return mVolumeSource; }
+ const sp<AudioPolicyMix> getPrimaryMix() const {
+ return mPrimaryMix.promote();
+ };
void setActive(bool active) override
{
@@ -136,7 +141,7 @@
const VolumeSource mVolumeSource;
const audio_output_flags_t mFlags;
const std::vector<wp<SwAudioOutputDescriptor>> mSecondaryOutputs;
-
+ const wp<AudioPolicyMix> mPrimaryMix;
/**
* required for duplicating thread, prevent from removing active client from an output
* involved in a duplication.
diff --git a/services/audiopolicy/common/managerdefinitions/include/DeviceDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/DeviceDescriptor.h
index a6562d7..dd1499c 100644
--- a/services/audiopolicy/common/managerdefinitions/include/DeviceDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/DeviceDescriptor.h
@@ -28,6 +28,8 @@
namespace android {
+class AudioPolicyClientInterface;
+
class DeviceDescriptor : public DeviceDescriptorBase,
public PolicyAudioPort, public PolicyAudioPortConfig
{
@@ -87,6 +89,8 @@
void importAudioPortAndPickAudioProfile(const sp<PolicyAudioPort>& policyPort,
bool force = false);
+ void setEncapsulationInfoFromHal(AudioPolicyClientInterface *clientInterface);
+
void dump(String8 *dst, int spaces, int index, bool verbose = true) const;
private:
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index d5272bc..d6d472b 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -690,7 +690,9 @@
const sp<SwAudioOutputDescriptor> outputDesc = this->valueAt(i);
if (outputDesc->isActive(volumeSource, inPastMs, sysTime)
&& (!(outputDesc->devices()
- .containsDeviceAmongTypes(getAllOutRemoteDevices())))) {
+ .containsDeviceAmongTypes(getAllOutRemoteDevices())
+ || outputDesc->devices()
+ .onlyContainsDevicesWithType(AUDIO_DEVICE_OUT_TELEPHONY_TX)))) {
return true;
}
}
@@ -722,7 +724,11 @@
const sp<SwAudioOutputDescriptor> otherDesc = valueAt(i);
if (desc->sharesHwModuleWith(otherDesc) &&
otherDesc->isStrategyActive(ps, inPastMs, sysTime)) {
- return true;
+ if (desc == otherDesc
+ || !otherDesc->devices()
+ .onlyContainsDevicesWithType(AUDIO_DEVICE_OUT_TELEPHONY_TX)) {
+ return true;
+ }
}
}
return false;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
index fc1a59f..b6de4be 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
@@ -28,7 +28,7 @@
void AudioPolicyMix::dump(String8 *dst, int spaces, int index) const
{
- dst->appendFormat("%*sAudio Policy Mix %d:\n", spaces, "", index + 1);
+ dst->appendFormat("%*sAudio Policy Mix %d (%p):\n", spaces, "", index + 1, this);
std::string mixTypeLiteral;
if (!MixTypeConverter::toString(mMixType, mixTypeLiteral)) {
ALOGE("%s: failed to convert mix type %d", __FUNCTION__, mMixType);
@@ -44,6 +44,9 @@
dst->appendFormat("%*s- device address: %s\n", spaces, "", mDeviceAddress.string());
+ dst->appendFormat("%*s- output: %d\n", spaces, "",
+ mOutput == nullptr ? 0 : mOutput->mIoHandle);
+
int indexCriterion = 0;
for (const auto &criterion : mCriteria) {
dst->appendFormat("%*s- Criterion %d: ", spaces + 2, "", indexCriterion++);
diff --git a/services/audiopolicy/common/managerdefinitions/src/ClientDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/ClientDescriptor.cpp
index 95822b9..afc4d01 100644
--- a/services/audiopolicy/common/managerdefinitions/src/ClientDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/ClientDescriptor.cpp
@@ -23,6 +23,7 @@
#include <TypeConverter.h>
#include "AudioOutputDescriptor.h"
#include "AudioPatch.h"
+#include "AudioPolicyMix.h"
#include "ClientDescriptor.h"
#include "DeviceDescriptor.h"
#include "HwModule.h"
@@ -55,6 +56,12 @@
ClientDescriptor::dump(dst, spaces, index);
dst->appendFormat("%*s- Stream: %d flags: %08x\n", spaces, "", mStream, mFlags);
dst->appendFormat("%*s- Refcount: %d\n", spaces, "", mActivityCount);
+ dst->appendFormat("%*s- DAP Primary Mix: %p\n", spaces, "", mPrimaryMix.promote().get());
+ dst->appendFormat("%*s- DAP Secondary Outputs:\n", spaces, "");
+ for (auto desc : mSecondaryOutputs) {
+ dst->appendFormat("%*s - %d\n", spaces, "",
+ desc.promote() == nullptr ? 0 : desc.promote()->mIoHandle);
+ }
}
std::string TrackClientDescriptor::toShortString() const
@@ -88,7 +95,7 @@
TrackClientDescriptor::TrackClientDescriptor(portId, uid, AUDIO_SESSION_NONE, attributes,
{config.sample_rate, config.channel_mask, config.format}, AUDIO_PORT_HANDLE_NONE,
stream, strategy, volumeSource, AUDIO_OUTPUT_FLAG_NONE, false,
- {} /* Sources do not support secondary outputs*/), mSrcDevice(srcDevice)
+ {} /* Sources do not support secondary outputs*/, nullptr), mSrcDevice(srcDevice)
{
}
diff --git a/services/audiopolicy/common/managerdefinitions/src/DeviceDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/DeviceDescriptor.cpp
index 86dbba8..a29e60e 100644
--- a/services/audiopolicy/common/managerdefinitions/src/DeviceDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/DeviceDescriptor.cpp
@@ -17,9 +17,12 @@
#define LOG_TAG "APM::Devices"
//#define LOG_NDEBUG 0
-#include <audio_utils/string.h>
-#include <media/TypeConverter.h>
#include <set>
+
+#include <AudioPolicyInterface.h>
+#include <audio_utils/string.h>
+#include <media/AudioParameter.h>
+#include <media/TypeConverter.h>
#include "DeviceDescriptor.h"
#include "TypeConverter.h"
#include "HwModule.h"
@@ -165,6 +168,29 @@
policyPort->pickAudioProfile(mSamplingRate, mChannelMask, mFormat);
}
+void DeviceDescriptor::setEncapsulationInfoFromHal(
+ AudioPolicyClientInterface *clientInterface) {
+ AudioParameter param(String8(mDeviceTypeAddr.getAddress()));
+ param.addInt(String8(AudioParameter::keyRouting), mDeviceTypeAddr.mType);
+ param.addKey(String8(AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_MODES));
+ param.addKey(String8(AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_METADATA_TYPES));
+ String8 reply = clientInterface->getParameters(AUDIO_IO_HANDLE_NONE, param.toString());
+ AudioParameter repliedParameters(reply);
+ int value;
+ if (repliedParameters.getInt(
+ String8(AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_MODES), value) == NO_ERROR) {
+ if (setEncapsulationModes(value) != NO_ERROR) {
+ ALOGE("Failed to set encapsulation mode(%d)", value);
+ }
+ }
+ if (repliedParameters.getInt(
+ String8(AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_METADATA_TYPES), value) == NO_ERROR) {
+ if (setEncapsulationMetadataTypes(value) != NO_ERROR) {
+ ALOGE("Failed to set encapsulation metadata types(%d)", value);
+ }
+ }
+}
+
void DeviceDescriptor::dump(String8 *dst, int spaces, int index, bool verbose) const
{
String8 extraInfo;
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index bcf6f38..c5c13e9 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -178,6 +178,9 @@
return INVALID_OPERATION;
}
+ // Populate encapsulation information when a output device is connected.
+ device->setEncapsulationInfoFromHal(mpClientInterface);
+
// outputs should never be empty here
ALOG_ASSERT(outputs.size() != 0, "setDeviceConnectionState():"
"checkOutputsForDevice() returned no outputs but status OK");
@@ -1099,14 +1102,15 @@
};
*portId = PolicyAudioPort::getNextUniqueId();
+ sp<SwAudioOutputDescriptor> outputDesc = mOutputs.valueFor(*output);
sp<TrackClientDescriptor> clientDesc =
new TrackClientDescriptor(*portId, uid, session, resultAttr, clientConfig,
sanitizedRequestedPortId, *stream,
mEngine->getProductStrategyForAttributes(resultAttr),
toVolumeSource(resultAttr),
*flags, isRequestedDeviceForExclusiveUse,
- std::move(weakSecondaryOutputDescs));
- sp<SwAudioOutputDescriptor> outputDesc = mOutputs.valueFor(*output);
+ std::move(weakSecondaryOutputDescs),
+ outputDesc->mPolicyMix);
outputDesc->addClient(clientDesc);
ALOGV("%s() returns output %d requestedPortId %d selectedDeviceId %d for port ID %d", __func__,
@@ -2874,7 +2878,7 @@
{
ALOGV("registerPolicyMixes() %zu mix(es)", mixes.size());
status_t res = NO_ERROR;
-
+ bool checkOutputs = false;
sp<HwModule> rSubmixModule;
// examine each mix's route type
for (size_t i = 0; i < mixes.size(); i++) {
@@ -2993,11 +2997,16 @@
i, type, address.string());
res = INVALID_OPERATION;
break;
+ } else {
+ checkOutputs = true;
}
}
}
if (res != NO_ERROR) {
unregisterPolicyMixes(mixes);
+ } else if (checkOutputs) {
+ checkForDeviceAndOutputChanges();
+ updateCallAndOutputRouting();
}
return res;
}
@@ -3006,6 +3015,7 @@
{
ALOGV("unregisterPolicyMixes() num mixes %zu", mixes.size());
status_t res = NO_ERROR;
+ bool checkOutputs = false;
sp<HwModule> rSubmixModule;
// examine each mix's route type
for (const auto& mix : mixes) {
@@ -3046,9 +3056,15 @@
if (mPolicyMixes.unregisterMix(mix) != NO_ERROR) {
res = INVALID_OPERATION;
continue;
+ } else {
+ checkOutputs = true;
}
}
}
+ if (res == NO_ERROR && checkOutputs) {
+ checkForDeviceAndOutputChanges();
+ updateCallAndOutputRouting();
+ }
return res;
}
@@ -3817,7 +3833,11 @@
ALOGE("%s output not found for id %d", __func__, patch->sources[0].id);
return BAD_VALUE;
}
- // Reset handle so that setOutputDevice will force new AF patch to reach the sink
+ if (patchDesc->getHandle() != outputDesc->getPatchHandle()) {
+ // force SwOutput patch removal as AF counter part patch has already gone.
+ ALOGV("%s reset patch handle on Output as different from SWBridge", __func__);
+ removeAudioPatch(outputDesc->getPatchHandle());
+ }
outputDesc->setPatchHandle(AUDIO_PATCH_HANDLE_NONE);
setOutputDevices(outputDesc,
getNewOutputDevices(outputDesc, true /*fromCache*/),
@@ -4635,6 +4655,7 @@
if (!device->isAttached()) {
device->attach(hwModule);
mAvailableOutputDevices.add(device);
+ device->setEncapsulationInfoFromHal(mpClientInterface);
if (newDevices) newDevices->add(device);
setEngineDeviceConnectionState(device, AUDIO_POLICY_DEVICE_STATE_AVAILABLE);
}
@@ -5218,32 +5239,38 @@
SortedVector<audio_io_handle_t> srcOutputs = getOutputsForDevices(oldDevices, mPreviousOutputs);
SortedVector<audio_io_handle_t> dstOutputs = getOutputsForDevices(newDevices, mOutputs);
- // also take into account external policy-related changes: add all outputs which are
- // associated with policies in the "before" and "after" output vectors
- ALOGVV("%s(): policy related outputs", __func__);
- bool hasDynamicPolicy = false;
- for (size_t i = 0 ; i < mPreviousOutputs.size() ; i++) {
- const sp<SwAudioOutputDescriptor> desc = mPreviousOutputs.valueAt(i);
- if (desc != 0 && desc->mPolicyMix != NULL) {
- srcOutputs.add(desc->mIoHandle);
- hasDynamicPolicy = true;
- ALOGVV(" previous outputs: adding %d", desc->mIoHandle);
+ uint32_t maxLatency = 0;
+ bool invalidate = false;
+ // take into account dynamic audio policies related changes: if a client is now associated
+ // to a different policy mix than at creation time, invalidate corresponding stream
+ for (size_t i = 0; i < mPreviousOutputs.size() && !invalidate; i++) {
+ const sp<SwAudioOutputDescriptor>& desc = mPreviousOutputs.valueAt(i);
+ if (desc->isDuplicated()) {
+ continue;
}
- }
- for (size_t i = 0 ; i < mOutputs.size() ; i++) {
- const sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);
- if (desc != 0 && desc->mPolicyMix != NULL) {
- dstOutputs.add(desc->mIoHandle);
- hasDynamicPolicy = true;
- ALOGVV(" new outputs: adding %d", desc->mIoHandle);
+ for (const sp<TrackClientDescriptor>& client : desc->getClientIterable()) {
+ if (mEngine->getProductStrategyForAttributes(client->attributes()) != psId) {
+ continue;
+ }
+ sp<AudioPolicyMix> primaryMix;
+ status_t status = mPolicyMixes.getOutputForAttr(client->attributes(), client->uid(),
+ client->flags(), primaryMix, nullptr);
+ if (status != OK) {
+ continue;
+ }
+ if (client->getPrimaryMix() != primaryMix) {
+ invalidate = true;
+ if (desc->isStrategyActive(psId)) {
+ maxLatency = desc->latency();
+ }
+ break;
+ }
}
}
- if (srcOutputs != dstOutputs) {
+ if (srcOutputs != dstOutputs || invalidate) {
// get maximum latency of all source outputs to determine the minimum mute time guaranteeing
// audio from invalidated tracks will be rendered when unmuting
- uint32_t maxLatency = 0;
- bool invalidate = hasDynamicPolicy;
for (audio_io_handle_t srcOut : srcOutputs) {
sp<SwAudioOutputDescriptor> desc = mPreviousOutputs.valueFor(srcOut);
if (desc == nullptr) continue;
@@ -5743,15 +5770,6 @@
DeviceVector filteredDevices = outputDesc->filterSupportedDevices(devices);
DeviceVector prevDevices = outputDesc->devices();
- // no need to proceed if new device is not AUDIO_DEVICE_NONE and not supported by current
- // output profile or if new device is not supported AND previous device(s) is(are) still
- // available (otherwise reset device must be done on the output)
- if (!devices.isEmpty() && filteredDevices.isEmpty() &&
- !mAvailableOutputDevices.filter(prevDevices).empty()) {
- ALOGV("%s: unsupported device %s for output", __func__, devices.toString().c_str());
- return 0;
- }
-
ALOGV("setOutputDevices() prevDevice %s", prevDevices.toString().c_str());
if (!filteredDevices.isEmpty()) {
@@ -5766,6 +5784,17 @@
muteWaitMs = 0;
}
+ // no need to proceed if new device is not AUDIO_DEVICE_NONE and not supported by current
+ // output profile or if new device is not supported AND previous device(s) is(are) still
+ // available (otherwise reset device must be done on the output)
+ if (!devices.isEmpty() && filteredDevices.isEmpty() &&
+ !mAvailableOutputDevices.filter(prevDevices).empty()) {
+ ALOGV("%s: unsupported device %s for output", __func__, devices.toString().c_str());
+ // restore previous device after evaluating strategy mute state
+ outputDesc->setDevices(prevDevices);
+ return muteWaitMs;
+ }
+
// Do not change the routing if:
// the requested device is AUDIO_DEVICE_NONE
// OR the requested device is the same as current device
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 9577160..34d07b6 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -593,7 +593,7 @@
}
// including successes gets very verbose
- // but once we cut over to westworld, log them all.
+ // but once we cut over to statsd, log them all.
if (status != NO_ERROR) {
static constexpr char kAudioPolicy[] = "audiopolicy";
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index 9b61e74..e847f9f 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -566,11 +566,13 @@
auto canCaptureIfInCallOrCommunication = [&](const auto &recordClient) REQUIRES(mLock) {
bool canCaptureCall = recordClient->canCaptureOutput;
- bool canCaptureCommunication = recordClient->canCaptureOutput
- || recordClient->uid == mPhoneStateOwnerUid
- || isServiceUid(mPhoneStateOwnerUid);
- return !(isInCall && !canCaptureCall)
- && !(isInCommunication && !canCaptureCommunication);
+ return !(isInCall && !canCaptureCall);
+//TODO(b/160260850): restore restriction to mode owner once fix for misbehaving apps is merged
+// bool canCaptureCommunication = recordClient->canCaptureOutput
+// || recordClient->uid == mPhoneStateOwnerUid
+// || isServiceUid(mPhoneStateOwnerUid);
+// return !(isInCall && !canCaptureCall)
+// && !(isInCommunication && !canCaptureCommunication);
};
// By default allow capture if:
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index c9d2c68..af1e01d 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -467,10 +467,21 @@
logDeviceRemoved(idCombo,
String8::format("Device status changed to %d", newStatus));
}
-
+ // Avoid calling getSystemCameraKind() with mStatusListenerLock held (b/141756275)
+ SystemCameraKind deviceKind = SystemCameraKind::PUBLIC;
+ if (getSystemCameraKind(id, &deviceKind) != OK) {
+ ALOGE("%s: Invalid camera id %s, skipping", __FUNCTION__, id.string());
+ return;
+ }
String16 id16(id), physicalId16(physicalId);
Mutex::Autolock lock(mStatusListenerLock);
for (auto& listener : mListenerList) {
+ if (shouldSkipStatusUpdates(deviceKind, listener->isVendorListener(),
+ listener->getListenerPid(), listener->getListenerUid())) {
+ ALOGV("Skipping discovery callback for system-only camera device %s",
+ id.c_str());
+ continue;
+ }
listener->getListener()->onPhysicalCameraStatusChanged(mapToInterface(newStatus),
id16, physicalId16);
}
@@ -485,9 +496,6 @@
clientToDisconnect->notifyError(
hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_DISCONNECTED,
CaptureResultExtras{});
- // Ensure not in binder RPC so client disconnect PID checks work correctly
- LOG_ALWAYS_FATAL_IF(CameraThreadState::getCallingPid() != getpid(),
- "onDeviceStatusChanged must be called from the camera service process!");
clientToDisconnect->disconnect();
}
}
@@ -1366,7 +1374,12 @@
Mutex::Autolock l(mLogLock);
mEventLog.add(msg);
- return -EBUSY;
+ auto current = mActiveClientManager.get(cameraId);
+ if (current != nullptr) {
+ return -EBUSY; // CAMERA_IN_USE
+ } else {
+ return -EUSERS; // MAX_CAMERAS_IN_USE
+ }
}
for (auto& i : evicted) {
@@ -1627,7 +1640,7 @@
cameraId.string(), clientName8.string(), clientPid);
}
- // Enforce client permissions and do basic sanity checks
+ // Enforce client permissions and do basic validity checks
if(!(ret = validateConnectLocked(cameraId, clientName8,
/*inout*/clientUid, /*inout*/clientPid, /*out*/originalClientPid)).isOk()) {
return ret;
@@ -1658,6 +1671,10 @@
return STATUS_ERROR_FMT(ERROR_CAMERA_IN_USE,
"Higher-priority client using camera, ID \"%s\" currently unavailable",
cameraId.string());
+ case -EUSERS:
+ return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,
+ "Too many cameras already open, cannot open camera \"%s\"",
+ cameraId.string());
default:
return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
"Unexpected error %s (%d) opening camera \"%s\"",
@@ -3757,13 +3774,13 @@
Mutex::Autolock lock(mStatusListenerLock);
- notifyPhysicalCameraStatusLocked(mapToInterface(status), cameraId);
+ notifyPhysicalCameraStatusLocked(mapToInterface(status), cameraId, deviceKind);
for (auto& listener : mListenerList) {
bool isVendorListener = listener->isVendorListener();
if (shouldSkipStatusUpdates(deviceKind, isVendorListener,
listener->getListenerPid(), listener->getListenerUid()) ||
- (isVendorListener && !supportsHAL3)) {
+ (isVendorListener && !supportsHAL3)) {
ALOGV("Skipping discovery callback for system-only camera/HAL1 device %s",
cameraId.c_str());
continue;
@@ -3875,7 +3892,8 @@
return OK;
}
-void CameraService::notifyPhysicalCameraStatusLocked(int32_t status, const String8& cameraId) {
+void CameraService::notifyPhysicalCameraStatusLocked(int32_t status, const String8& cameraId,
+ SystemCameraKind deviceKind) {
Mutex::Autolock lock(mCameraStatesLock);
for (const auto& state : mCameraStates) {
std::vector<std::string> physicalCameraIds;
@@ -3891,6 +3909,12 @@
String16 id16(state.first), physicalId16(cameraId);
for (auto& listener : mListenerList) {
+ if (shouldSkipStatusUpdates(deviceKind, listener->isVendorListener(),
+ listener->getListenerPid(), listener->getListenerUid())) {
+ ALOGV("Skipping discovery callback for system-only camera device %s",
+ cameraId.c_str());
+ continue;
+ }
listener->getListener()->onPhysicalCameraStatusChanged(status,
id16, physicalId16);
}
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 18cf77a..4321201 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -1005,7 +1005,8 @@
hardware::camera::common::V1_0::TorchModeStatus status);
// notify physical camera status when the physical camera is public.
- void notifyPhysicalCameraStatusLocked(int32_t status, const String8& cameraId);
+ void notifyPhysicalCameraStatusLocked(int32_t status, const String8& cameraId,
+ SystemCameraKind deviceKind);
// IBinder::DeathRecipient implementation
virtual void binderDied(const wp<IBinder> &who);
diff --git a/services/camera/libcameraservice/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index ebb0555..b043c0b 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -1777,6 +1777,14 @@
case hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_BUFFER:
ALOGW("%s: Received recoverable error %d from HAL - ignoring, requestId %" PRId32,
__FUNCTION__, errorCode, resultExtras.requestId);
+
+ if ((hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_REQUEST == errorCode) ||
+ (hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_RESULT == errorCode)) {
+ Mutex::Autolock al(mLatestRequestMutex);
+
+ mLatestFailedRequestId = resultExtras.requestId;
+ mLatestRequestSignal.signal();
+ }
mCaptureSequencer->notifyError(errorCode, resultExtras);
return;
default:
@@ -2303,7 +2311,7 @@
status_t Camera2Client::waitUntilRequestIdApplied(int32_t requestId, nsecs_t timeout) {
Mutex::Autolock l(mLatestRequestMutex);
- while (mLatestRequestId != requestId) {
+ while ((mLatestRequestId != requestId) && (mLatestFailedRequestId != requestId)) {
nsecs_t startTime = systemTime();
auto res = mLatestRequestSignal.waitRelative(mLatestRequestMutex, timeout);
@@ -2312,7 +2320,7 @@
timeout -= (systemTime() - startTime);
}
- return OK;
+ return (mLatestRequestId == requestId) ? OK : DEAD_OBJECT;
}
void Camera2Client::notifyRequestId(int32_t requestId) {
diff --git a/services/camera/libcameraservice/api1/Camera2Client.h b/services/camera/libcameraservice/api1/Camera2Client.h
index 3144e0e..03ca44a 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.h
+++ b/services/camera/libcameraservice/api1/Camera2Client.h
@@ -237,6 +237,7 @@
mutable Mutex mLatestRequestMutex;
Condition mLatestRequestSignal;
int32_t mLatestRequestId = -1;
+ int32_t mLatestFailedRequestId = -1;
status_t waitUntilRequestIdApplied(int32_t requestId, nsecs_t timeout);
status_t waitUntilCurrentRequestIdLocked();
};
diff --git a/services/camera/libcameraservice/api1/client2/Parameters.cpp b/services/camera/libcameraservice/api1/client2/Parameters.cpp
index 20333d1..dbc863b 100644
--- a/services/camera/libcameraservice/api1/client2/Parameters.cpp
+++ b/services/camera/libcameraservice/api1/client2/Parameters.cpp
@@ -230,7 +230,7 @@
previewFpsRange[1] = fastInfo.bestStillCaptureFpsRange[1];
// PREVIEW_FRAME_RATE / SUPPORTED_PREVIEW_FRAME_RATES are deprecated, but
- // still have to do something sane for them
+ // still have to do something reasonable for them
// NOTE: Not scaled like FPS range values are.
int previewFps = fpsFromRange(previewFpsRange[0], previewFpsRange[1]);
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.h b/services/camera/libcameraservice/api2/CameraDeviceClient.h
index 964c96a..9d3874f 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.h
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.h
@@ -205,7 +205,7 @@
virtual void notifyRepeatingRequestError(long lastFrameNumber);
// utility function to convert AIDL SessionConfiguration to HIDL
- // streamConfiguration. Also checks for sanity of SessionConfiguration and
+ // streamConfiguration. Also checks for validity of SessionConfiguration and
// returns a non-ok binder::Status if the passed in session configuration
// isn't valid.
static binder::Status
diff --git a/services/camera/libcameraservice/api2/CompositeStream.cpp b/services/camera/libcameraservice/api2/CompositeStream.cpp
index b47ee2e..a61dac7 100644
--- a/services/camera/libcameraservice/api2/CompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/CompositeStream.cpp
@@ -28,19 +28,19 @@
namespace android {
namespace camera3 {
-CompositeStream::CompositeStream(wp<CameraDeviceBase> device,
+CompositeStream::CompositeStream(sp<CameraDeviceBase> device,
wp<hardware::camera2::ICameraDeviceCallbacks> cb) :
mDevice(device),
mRemoteCallback(cb),
mNumPartialResults(1),
mErrorState(false) {
- sp<CameraDeviceBase> cameraDevice = device.promote();
- if (cameraDevice.get() != nullptr) {
- CameraMetadata staticInfo = cameraDevice->info();
+ if (device != nullptr) {
+ CameraMetadata staticInfo = device->info();
camera_metadata_entry_t entry = staticInfo.find(ANDROID_REQUEST_PARTIAL_RESULT_COUNT);
if (entry.count > 0) {
mNumPartialResults = entry.data.i32[0];
}
+ mStatusTracker = device->getStatusTracker();
}
}
@@ -174,7 +174,7 @@
ret = onStreamBufferError(resultExtras);
break;
case hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_REQUEST:
- // Invalid request, this shouldn't affect composite streams.
+ onRequestError(resultExtras);
break;
default:
ALOGE("%s: Unrecoverable error: %d detected!", __FUNCTION__, errorCode);
@@ -186,7 +186,7 @@
return ret;
}
-void CompositeStream::notifyError(int64_t frameNumber) {
+void CompositeStream::notifyError(int64_t frameNumber, int32_t requestId) {
sp<hardware::camera2::ICameraDeviceCallbacks> remoteCb =
mRemoteCallback.promote();
@@ -194,6 +194,7 @@
CaptureResultExtras extras;
extras.errorStreamId = getStreamId();
extras.frameNumber = frameNumber;
+ extras.requestId = requestId;
remoteCb->onDeviceError(
hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_BUFFER,
extras);
diff --git a/services/camera/libcameraservice/api2/CompositeStream.h b/services/camera/libcameraservice/api2/CompositeStream.h
index e5baf1a..5f62d47 100644
--- a/services/camera/libcameraservice/api2/CompositeStream.h
+++ b/services/camera/libcameraservice/api2/CompositeStream.h
@@ -38,7 +38,7 @@
class CompositeStream : public camera3::Camera3StreamBufferListener {
public:
- CompositeStream(wp<CameraDeviceBase> device, wp<hardware::camera2::ICameraDeviceCallbacks> cb);
+ CompositeStream(sp<CameraDeviceBase> device, wp<hardware::camera2::ICameraDeviceCallbacks> cb);
virtual ~CompositeStream() {}
status_t createStream(const std::vector<sp<Surface>>& consumers,
@@ -95,7 +95,7 @@
status_t registerCompositeStreamListener(int32_t streamId);
void eraseResult(int64_t frameNumber);
void flagAnErrorFrameNumber(int64_t frameNumber);
- void notifyError(int64_t frameNumber);
+ void notifyError(int64_t frameNumber, int32_t requestId);
// Subclasses should check for buffer errors from internal streams and return 'true' in
// case the error notification should remain within camera service.
@@ -105,11 +105,16 @@
// internal processing needs result data.
virtual void onResultError(const CaptureResultExtras& resultExtras) = 0;
+ // Subclasses can decide how to handle request errors depending on whether
+ // or not the internal processing needs clean up.
+ virtual void onRequestError(const CaptureResultExtras& /*resultExtras*/) {}
+
// Device and/or service is in unrecoverable error state.
// Composite streams should behave accordingly.
void enableErrorState();
wp<CameraDeviceBase> mDevice;
+ wp<camera3::StatusTracker> mStatusTracker;
wp<hardware::camera2::ICameraDeviceCallbacks> mRemoteCallback;
mutable Mutex mMutex;
diff --git a/services/camera/libcameraservice/api2/DepthCompositeStream.cpp b/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
index 16ce52c..c6859be 100644
--- a/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
@@ -29,7 +29,7 @@
namespace android {
namespace camera3 {
-DepthCompositeStream::DepthCompositeStream(wp<CameraDeviceBase> device,
+DepthCompositeStream::DepthCompositeStream(sp<CameraDeviceBase> device,
wp<hardware::camera2::ICameraDeviceCallbacks> cb) :
CompositeStream(device, cb),
mBlobStreamId(-1),
@@ -43,9 +43,8 @@
mProducerListener(new ProducerListener()),
mMaxJpegSize(-1),
mIsLogicalCamera(false) {
- sp<CameraDeviceBase> cameraDevice = device.promote();
- if (cameraDevice.get() != nullptr) {
- CameraMetadata staticInfo = cameraDevice->info();
+ if (device != nullptr) {
+ CameraMetadata staticInfo = device->info();
auto entry = staticInfo.find(ANDROID_JPEG_MAX_SIZE);
if (entry.count > 0) {
mMaxJpegSize = entry.data.i32[0];
@@ -385,7 +384,8 @@
}
if ((inputFrame->error || mErrorState) && !inputFrame->errorNotified) {
- notifyError(inputFrame->frameNumber);
+ //TODO: Figure out correct requestId
+ notifyError(inputFrame->frameNumber, -1 /*requestId*/);
inputFrame->errorNotified = true;
}
}
diff --git a/services/camera/libcameraservice/api2/DepthCompositeStream.h b/services/camera/libcameraservice/api2/DepthCompositeStream.h
index 1bf714d..cab52b6 100644
--- a/services/camera/libcameraservice/api2/DepthCompositeStream.h
+++ b/services/camera/libcameraservice/api2/DepthCompositeStream.h
@@ -41,7 +41,7 @@
public CpuConsumer::FrameAvailableListener {
public:
- DepthCompositeStream(wp<CameraDeviceBase> device,
+ DepthCompositeStream(sp<CameraDeviceBase> device,
wp<hardware::camera2::ICameraDeviceCallbacks> cb);
~DepthCompositeStream() override;
@@ -80,8 +80,9 @@
bool error;
bool errorNotified;
int64_t frameNumber;
+ int32_t requestId;
- InputFrame() : error(false), errorNotified(false), frameNumber(-1) { }
+ InputFrame() : error(false), errorNotified(false), frameNumber(-1), requestId(-1) { }
};
// Helper methods
diff --git a/services/camera/libcameraservice/api2/HeicCompositeStream.cpp b/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
index f335c20..a63f402 100644
--- a/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
@@ -45,7 +45,7 @@
namespace android {
namespace camera3 {
-HeicCompositeStream::HeicCompositeStream(wp<CameraDeviceBase> device,
+HeicCompositeStream::HeicCompositeStream(sp<CameraDeviceBase> device,
wp<hardware::camera2::ICameraDeviceCallbacks> cb) :
CompositeStream(device, cb),
mUseHeic(false),
@@ -68,7 +68,8 @@
mLockedAppSegmentBufferCnt(0),
mCodecOutputCounter(0),
mQuality(-1),
- mGridTimestampUs(0) {
+ mGridTimestampUs(0),
+ mStatusId(StatusTracker::NO_STATUS_ID) {
}
HeicCompositeStream::~HeicCompositeStream() {
@@ -188,9 +189,17 @@
}
mOutputSurface = consumers[0];
- res = registerCompositeStreamListener(getStreamId());
+ res = registerCompositeStreamListener(mMainImageStreamId);
if (res != OK) {
- ALOGE("%s: Failed to register HAL main image stream", __FUNCTION__);
+ ALOGE("%s: Failed to register HAL main image stream: %s (%d)", __FUNCTION__,
+ strerror(-res), res);
+ return res;
+ }
+
+ res = registerCompositeStreamListener(mAppSegmentStreamId);
+ if (res != OK) {
+ ALOGE("%s: Failed to register HAL app segment stream: %s (%d)", __FUNCTION__,
+ strerror(-res), res);
return res;
}
@@ -224,6 +233,19 @@
mOutputSurface->disconnect(NATIVE_WINDOW_API_CAMERA);
mOutputSurface.clear();
}
+
+ sp<StatusTracker> statusTracker = mStatusTracker.promote();
+ if (statusTracker != nullptr && mStatusId != StatusTracker::NO_STATUS_ID) {
+ statusTracker->removeComponent(mStatusId);
+ mStatusId = StatusTracker::NO_STATUS_ID;
+ }
+
+ if (mPendingInputFrames.size() > 0) {
+ ALOGW("%s: mPendingInputFrames has %zu stale entries",
+ __FUNCTION__, mPendingInputFrames.size());
+ mPendingInputFrames.clear();
+ }
+
return res;
}
@@ -232,9 +254,16 @@
if (bufferInfo.mError) return;
- mCodecOutputBufferTimestamps.push(bufferInfo.mTimestamp);
- ALOGV("%s: [%" PRId64 "]: Adding codecOutputBufferTimestamp (%zu timestamps in total)",
- __FUNCTION__, bufferInfo.mTimestamp, mCodecOutputBufferTimestamps.size());
+ if (bufferInfo.mStreamId == mMainImageStreamId) {
+ mMainImageFrameNumbers.push(bufferInfo.mFrameNumber);
+ mCodecOutputBufferFrameNumbers.push(bufferInfo.mFrameNumber);
+ ALOGV("%s: [%" PRId64 "]: Adding main image frame number (%zu frame numbers in total)",
+ __FUNCTION__, bufferInfo.mFrameNumber, mMainImageFrameNumbers.size());
+ } else if (bufferInfo.mStreamId == mAppSegmentStreamId) {
+ mAppSegmentFrameNumbers.push(bufferInfo.mFrameNumber);
+ ALOGV("%s: [%" PRId64 "]: Adding app segment frame number (%zu frame numbers in total)",
+ __FUNCTION__, bufferInfo.mFrameNumber, mAppSegmentFrameNumbers.size());
+ }
}
// We need to get the settings early to handle the case where the codec output
@@ -264,7 +293,7 @@
quality = entry.data.i32[0];
}
- mSettingsByFrameNumber[frameNumber] = std::make_pair(orientation, quality);
+ mSettingsByFrameNumber[frameNumber] = {orientation, quality};
}
void HeicCompositeStream::onFrameAvailable(const BufferItem& item) {
@@ -479,6 +508,11 @@
return res;
}
+ sp<camera3::StatusTracker> statusTracker = mStatusTracker.promote();
+ if (statusTracker != nullptr) {
+ mStatusId = statusTracker->addComponent();
+ }
+
run("HeicCompositeStreamProc");
return NO_ERROR;
@@ -524,30 +558,44 @@
}
if (mSettingsByFrameNumber.find(resultExtras.frameNumber) != mSettingsByFrameNumber.end()) {
- ALOGV("%s: [%" PRId64 "]: frameNumber %" PRId64, __FUNCTION__,
- timestamp, resultExtras.frameNumber);
- mFrameNumberMap.emplace(resultExtras.frameNumber, timestamp);
- mSettingsByTimestamp[timestamp] = mSettingsByFrameNumber[resultExtras.frameNumber];
- mSettingsByFrameNumber.erase(resultExtras.frameNumber);
+ ALOGV("%s: [%" PRId64 "]: timestamp %" PRId64 ", requestId %d", __FUNCTION__,
+ resultExtras.frameNumber, timestamp, resultExtras.requestId);
+ mSettingsByFrameNumber[resultExtras.frameNumber].shutterNotified = true;
+ mSettingsByFrameNumber[resultExtras.frameNumber].timestamp = timestamp;
+ mSettingsByFrameNumber[resultExtras.frameNumber].requestId = resultExtras.requestId;
mInputReadyCondition.signal();
}
}
void HeicCompositeStream::compilePendingInputLocked() {
- while (!mSettingsByTimestamp.empty()) {
- auto it = mSettingsByTimestamp.begin();
- mPendingInputFrames[it->first].orientation = it->second.first;
- mPendingInputFrames[it->first].quality = it->second.second;
- mSettingsByTimestamp.erase(it);
+ auto i = mSettingsByFrameNumber.begin();
+ while (i != mSettingsByFrameNumber.end()) {
+ if (i->second.shutterNotified) {
+ mPendingInputFrames[i->first].orientation = i->second.orientation;
+ mPendingInputFrames[i->first].quality = i->second.quality;
+ mPendingInputFrames[i->first].timestamp = i->second.timestamp;
+ mPendingInputFrames[i->first].requestId = i->second.requestId;
+ ALOGV("%s: [%" PRId64 "]: timestamp is %" PRId64, __FUNCTION__,
+ i->first, i->second.timestamp);
+ i = mSettingsByFrameNumber.erase(i);
- // Set encoder quality if no inflight encoding
- if (mPendingInputFrames.size() == 1) {
- int32_t newQuality = mPendingInputFrames.begin()->second.quality;
- updateCodecQualityLocked(newQuality);
+ // Set encoder quality if no inflight encoding
+ if (mPendingInputFrames.size() == 1) {
+ sp<StatusTracker> statusTracker = mStatusTracker.promote();
+ if (statusTracker != nullptr) {
+ statusTracker->markComponentActive(mStatusId);
+ ALOGV("%s: Mark component as active", __FUNCTION__);
+ }
+
+ int32_t newQuality = mPendingInputFrames.begin()->second.quality;
+ updateCodecQualityLocked(newQuality);
+ }
+ } else {
+ i++;
}
}
- while (!mInputAppSegmentBuffers.empty()) {
+ while (!mInputAppSegmentBuffers.empty() && mAppSegmentFrameNumbers.size() > 0) {
CpuConsumer::LockedBuffer imgBuffer;
auto it = mInputAppSegmentBuffers.begin();
auto res = mAppSegmentConsumer->lockNextBuffer(&imgBuffer);
@@ -569,17 +617,30 @@
continue;
}
- if ((mPendingInputFrames.find(imgBuffer.timestamp) != mPendingInputFrames.end()) &&
- (mPendingInputFrames[imgBuffer.timestamp].error)) {
+ if (mPendingInputFrames.find(mAppSegmentFrameNumbers.front()) == mPendingInputFrames.end()) {
+ ALOGE("%s: mPendingInputFrames doesn't contain frameNumber %" PRId64, __FUNCTION__,
+ mAppSegmentFrameNumbers.front());
+ mInputAppSegmentBuffers.erase(it);
+ mAppSegmentFrameNumbers.pop();
+ continue;
+ }
+
+ int64_t frameNumber = mAppSegmentFrameNumbers.front();
+ // If mPendingInputFrames doesn't contain the expected frame number, the captured
+ // input app segment frame must have been dropped via a buffer error. Simply
+ // return the buffer to the buffer queue.
+ if ((mPendingInputFrames.find(frameNumber) == mPendingInputFrames.end()) ||
+ (mPendingInputFrames[frameNumber].error)) {
mAppSegmentConsumer->unlockBuffer(imgBuffer);
} else {
- mPendingInputFrames[imgBuffer.timestamp].appSegmentBuffer = imgBuffer;
+ mPendingInputFrames[frameNumber].appSegmentBuffer = imgBuffer;
mLockedAppSegmentBufferCnt++;
}
mInputAppSegmentBuffers.erase(it);
+ mAppSegmentFrameNumbers.pop();
}
- while (!mInputYuvBuffers.empty() && !mYuvBufferAcquired) {
+ while (!mInputYuvBuffers.empty() && !mYuvBufferAcquired && mMainImageFrameNumbers.size() > 0) {
CpuConsumer::LockedBuffer imgBuffer;
auto it = mInputYuvBuffers.begin();
auto res = mMainImageConsumer->lockNextBuffer(&imgBuffer);
@@ -600,59 +661,68 @@
continue;
}
- if ((mPendingInputFrames.find(imgBuffer.timestamp) != mPendingInputFrames.end()) &&
- (mPendingInputFrames[imgBuffer.timestamp].error)) {
+ if (mPendingInputFrames.find(mMainImageFrameNumbers.front()) == mPendingInputFrames.end()) {
+ ALOGE("%s: mPendingInputFrames doesn't contain frameNumber %" PRId64, __FUNCTION__,
+ mMainImageFrameNumbers.front());
+ mInputYuvBuffers.erase(it);
+ mMainImageFrameNumbers.pop();
+ continue;
+ }
+
+ int64_t frameNumber = mMainImageFrameNumbers.front();
+ // If mPendingInputFrames doesn't contain the expected frame number, the captured
+ // input main image must have been dropped via a buffer error. Simply
+ // return the buffer to the buffer queue.
+ if ((mPendingInputFrames.find(frameNumber) == mPendingInputFrames.end()) ||
+ (mPendingInputFrames[frameNumber].error)) {
mMainImageConsumer->unlockBuffer(imgBuffer);
} else {
- mPendingInputFrames[imgBuffer.timestamp].yuvBuffer = imgBuffer;
+ mPendingInputFrames[frameNumber].yuvBuffer = imgBuffer;
mYuvBufferAcquired = true;
}
mInputYuvBuffers.erase(it);
+ mMainImageFrameNumbers.pop();
}
while (!mCodecOutputBuffers.empty()) {
auto it = mCodecOutputBuffers.begin();
- // Bitstream buffer timestamp doesn't necessarily directly correlate with input
- // buffer timestamp. Assume encoder input to output is FIFO, use a queue
- // to look up timestamp.
- int64_t bufferTime = -1;
- if (mCodecOutputBufferTimestamps.empty()) {
- ALOGV("%s: Failed to find buffer timestamp for codec output buffer!", __FUNCTION__);
+ // Assume encoder input to output is FIFO, use a queue to look up
+ // frameNumber when handling codec outputs.
+ int64_t bufferFrameNumber = -1;
+ if (mCodecOutputBufferFrameNumbers.empty()) {
+ ALOGV("%s: Failed to find buffer frameNumber for codec output buffer!", __FUNCTION__);
break;
} else {
- // Direct mapping between camera timestamp (in ns) and codec timestamp (in us).
- bufferTime = mCodecOutputBufferTimestamps.front();
+ // Direct mapping between camera frame number and codec timestamp (in us).
+ bufferFrameNumber = mCodecOutputBufferFrameNumbers.front();
mCodecOutputCounter++;
if (mCodecOutputCounter == mNumOutputTiles) {
- mCodecOutputBufferTimestamps.pop();
+ mCodecOutputBufferFrameNumbers.pop();
mCodecOutputCounter = 0;
}
- mPendingInputFrames[bufferTime].codecOutputBuffers.push_back(*it);
- ALOGV("%s: [%" PRId64 "]: Pushing codecOutputBuffers (time %" PRId64 " us)",
- __FUNCTION__, bufferTime, it->timeUs);
+ mPendingInputFrames[bufferFrameNumber].codecOutputBuffers.push_back(*it);
+ ALOGV("%s: [%" PRId64 "]: Pushing codecOutputBuffers (frameNumber %" PRId64 ")",
+ __FUNCTION__, bufferFrameNumber, it->timeUs);
}
mCodecOutputBuffers.erase(it);
}
- while (!mFrameNumberMap.empty()) {
- auto it = mFrameNumberMap.begin();
- mPendingInputFrames[it->second].frameNumber = it->first;
- ALOGV("%s: [%" PRId64 "]: frameNumber is %" PRId64, __FUNCTION__, it->second, it->first);
- mFrameNumberMap.erase(it);
- }
-
while (!mCaptureResults.empty()) {
auto it = mCaptureResults.begin();
- // Negative timestamp indicates that something went wrong during the capture result
+ // Negative frame number indicates that something went wrong during the capture result
// collection process.
- if (it->first >= 0) {
- if (mPendingInputFrames[it->first].frameNumber == std::get<0>(it->second)) {
- mPendingInputFrames[it->first].result =
+ int64_t frameNumber = std::get<0>(it->second);
+ if (it->first >= 0 &&
+ mPendingInputFrames.find(frameNumber) != mPendingInputFrames.end()) {
+ if (mPendingInputFrames[frameNumber].timestamp == it->first) {
+ mPendingInputFrames[frameNumber].result =
std::make_unique<CameraMetadata>(std::get<1>(it->second));
} else {
ALOGE("%s: Capture result frameNumber/timestamp mapping changed between "
- "shutter and capture result!", __FUNCTION__);
+ "shutter and capture result! before: %" PRId64 ", after: %" PRId64,
+ __FUNCTION__, mPendingInputFrames[frameNumber].timestamp,
+ it->first);
}
}
mCaptureResults.erase(it);
@@ -661,22 +731,24 @@
// mErrorFrameNumbers stores frame number of dropped buffers.
auto it = mErrorFrameNumbers.begin();
while (it != mErrorFrameNumbers.end()) {
- bool frameFound = false;
- for (auto &inputFrame : mPendingInputFrames) {
- if (inputFrame.second.frameNumber == *it) {
- inputFrame.second.error = true;
- frameFound = true;
- break;
- }
- }
-
- if (frameFound) {
- it = mErrorFrameNumbers.erase(it);
+ if (mPendingInputFrames.find(*it) != mPendingInputFrames.end()) {
+ mPendingInputFrames[*it].error = true;
} else {
+ //Error callback is guaranteed to arrive after shutter notify, which
+ //results in mPendingInputFrames being populated.
ALOGW("%s: Not able to find failing input with frame number: %" PRId64, __FUNCTION__,
*it);
- it++;
}
+ it = mErrorFrameNumbers.erase(it);
+ }
+
+ // mExifErrorFrameNumbers stores the frame number of dropped APP_SEGMENT buffers
+ it = mExifErrorFrameNumbers.begin();
+ while (it != mExifErrorFrameNumbers.end()) {
+ if (mPendingInputFrames.find(*it) != mPendingInputFrames.end()) {
+ mPendingInputFrames[*it].exifError = true;
+ }
+ it = mExifErrorFrameNumbers.erase(it);
}
// Distribute codec input buffers to be filled out from YUV output
@@ -701,8 +773,8 @@
}
}
-bool HeicCompositeStream::getNextReadyInputLocked(int64_t *currentTs /*out*/) {
- if (currentTs == nullptr) {
+bool HeicCompositeStream::getNextReadyInputLocked(int64_t *frameNumber /*out*/) {
+ if (frameNumber == nullptr) {
return false;
}
@@ -715,7 +787,8 @@
// This makes sure that muxer gets created only when an output tile is
// generated, because right now we only handle 1 HEIC output buffer at a
// time (max dequeued buffer count is 1).
- bool appSegmentReady = (it.second.appSegmentBuffer.data != nullptr) &&
+ bool appSegmentReady =
+ (it.second.appSegmentBuffer.data != nullptr || it.second.exifError) &&
!it.second.appSegmentWritten && it.second.result != nullptr &&
it.second.muxer != nullptr;
bool codecOutputReady = !it.second.codecOutputBuffers.empty();
@@ -724,9 +797,8 @@
bool hasOutputBuffer = it.second.muxer != nullptr ||
(mDequeuedOutputBufferCnt < kMaxOutputSurfaceProducerCount);
if ((!it.second.error) &&
- (it.first < *currentTs) &&
(appSegmentReady || (codecOutputReady && hasOutputBuffer) || codecInputReady)) {
- *currentTs = it.first;
+ *frameNumber = it.first;
if (it.second.format == nullptr && mFormat != nullptr) {
it.second.format = mFormat->dup();
}
@@ -738,16 +810,12 @@
return newInputAvailable;
}
-int64_t HeicCompositeStream::getNextFailingInputLocked(int64_t *currentTs /*out*/) {
+int64_t HeicCompositeStream::getNextFailingInputLocked() {
int64_t res = -1;
- if (currentTs == nullptr) {
- return res;
- }
for (const auto& it : mPendingInputFrames) {
- if (it.second.error && !it.second.errorNotified && (it.first < *currentTs)) {
- *currentTs = it.first;
- res = it.second.frameNumber;
+ if (it.second.error) {
+ res = it.first;
break;
}
}
@@ -755,12 +823,13 @@
return res;
}
-status_t HeicCompositeStream::processInputFrame(nsecs_t timestamp,
+status_t HeicCompositeStream::processInputFrame(int64_t frameNumber,
InputFrame &inputFrame) {
ATRACE_CALL();
status_t res = OK;
- bool appSegmentReady = inputFrame.appSegmentBuffer.data != nullptr &&
+ bool appSegmentReady =
+ (inputFrame.appSegmentBuffer.data != nullptr || inputFrame.exifError) &&
!inputFrame.appSegmentWritten && inputFrame.result != nullptr &&
inputFrame.muxer != nullptr;
bool codecOutputReady = inputFrame.codecOutputBuffers.size() > 0;
@@ -770,8 +839,9 @@
(mDequeuedOutputBufferCnt < kMaxOutputSurfaceProducerCount);
ALOGV("%s: [%" PRId64 "]: appSegmentReady %d, codecOutputReady %d, codecInputReady %d,"
- " dequeuedOutputBuffer %d", __FUNCTION__, timestamp, appSegmentReady,
- codecOutputReady, codecInputReady, mDequeuedOutputBufferCnt);
+ " dequeuedOutputBuffer %d, timestamp %" PRId64, __FUNCTION__, frameNumber,
+ appSegmentReady, codecOutputReady, codecInputReady, mDequeuedOutputBufferCnt,
+ inputFrame.timestamp);
// Handle inputs for Hevc tiling
if (codecInputReady) {
@@ -791,7 +861,7 @@
// codecOutputReady must be true. Otherwise, appSegmentReady is guaranteed
// to be false, and the function must have returned early.
if (inputFrame.muxer == nullptr) {
- res = startMuxerForInputFrame(timestamp, inputFrame);
+ res = startMuxerForInputFrame(frameNumber, inputFrame);
if (res != OK) {
ALOGE("%s: Failed to create and start muxer: %s (%d)", __FUNCTION__,
strerror(-res), res);
@@ -801,7 +871,7 @@
// Write JPEG APP segments data to the muxer.
if (appSegmentReady) {
- res = processAppSegment(timestamp, inputFrame);
+ res = processAppSegment(frameNumber, inputFrame);
if (res != OK) {
ALOGE("%s: Failed to process JPEG APP segments: %s (%d)", __FUNCTION__,
strerror(-res), res);
@@ -811,7 +881,7 @@
// Write media codec bitstream buffers to muxer.
while (!inputFrame.codecOutputBuffers.empty()) {
- res = processOneCodecOutputFrame(timestamp, inputFrame);
+ res = processOneCodecOutputFrame(frameNumber, inputFrame);
if (res != OK) {
ALOGE("%s: Failed to process codec output frame: %s (%d)", __FUNCTION__,
strerror(-res), res);
@@ -821,7 +891,7 @@
if (inputFrame.pendingOutputTiles == 0) {
if (inputFrame.appSegmentWritten) {
- res = processCompletedInputFrame(timestamp, inputFrame);
+ res = processCompletedInputFrame(frameNumber, inputFrame);
if (res != OK) {
ALOGE("%s: Failed to process completed input frame: %s (%d)", __FUNCTION__,
strerror(-res), res);
@@ -837,7 +907,7 @@
return res;
}
-status_t HeicCompositeStream::startMuxerForInputFrame(nsecs_t timestamp, InputFrame &inputFrame) {
+status_t HeicCompositeStream::startMuxerForInputFrame(int64_t frameNumber, InputFrame &inputFrame) {
sp<ANativeWindow> outputANW = mOutputSurface;
auto res = outputANW->dequeueBuffer(mOutputSurface.get(), &inputFrame.anb, &inputFrame.fenceFd);
@@ -851,7 +921,7 @@
// Combine current thread id, stream id and timestamp to uniquely identify image.
std::ostringstream tempOutputFile;
tempOutputFile << "HEIF-" << pthread_self() << "-"
- << getStreamId() << "-" << timestamp;
+ << getStreamId() << "-" << frameNumber;
inputFrame.fileFd = syscall(__NR_memfd_create, tempOutputFile.str().c_str(), MFD_CLOEXEC);
if (inputFrame.fileFd < 0) {
ALOGE("%s: Failed to create file %s. Error no is %d", __FUNCTION__,
@@ -889,22 +959,27 @@
}
ALOGV("%s: [%" PRId64 "]: Muxer started for inputFrame", __FUNCTION__,
- timestamp);
+ frameNumber);
return OK;
}
-status_t HeicCompositeStream::processAppSegment(nsecs_t timestamp, InputFrame &inputFrame) {
+status_t HeicCompositeStream::processAppSegment(int64_t frameNumber, InputFrame &inputFrame) {
size_t app1Size = 0;
- auto appSegmentSize = findAppSegmentsSize(inputFrame.appSegmentBuffer.data,
- inputFrame.appSegmentBuffer.width * inputFrame.appSegmentBuffer.height,
- &app1Size);
- if (appSegmentSize == 0) {
- ALOGE("%s: Failed to find JPEG APP segment size", __FUNCTION__);
- return NO_INIT;
+ size_t appSegmentSize = 0;
+ if (!inputFrame.exifError) {
+ appSegmentSize = findAppSegmentsSize(inputFrame.appSegmentBuffer.data,
+ inputFrame.appSegmentBuffer.width * inputFrame.appSegmentBuffer.height,
+ &app1Size);
+ if (appSegmentSize == 0) {
+ ALOGE("%s: Failed to find JPEG APP segment size", __FUNCTION__);
+ return NO_INIT;
+ }
}
std::unique_ptr<ExifUtils> exifUtils(ExifUtils::create());
- auto exifRes = exifUtils->initialize(inputFrame.appSegmentBuffer.data, app1Size);
+ auto exifRes = inputFrame.exifError ?
+ exifUtils->initializeEmpty() :
+ exifUtils->initialize(inputFrame.appSegmentBuffer.data, app1Size);
if (!exifRes) {
ALOGE("%s: Failed to initialize ExifUtils object!", __FUNCTION__);
return BAD_VALUE;
@@ -945,7 +1020,7 @@
sp<ABuffer> aBuffer = new ABuffer(appSegmentBuffer, appSegmentBufferSize);
auto res = inputFrame.muxer->writeSampleData(aBuffer, inputFrame.trackIndex,
- timestamp, MediaCodec::BUFFER_FLAG_MUXER_DATA);
+ inputFrame.timestamp, MediaCodec::BUFFER_FLAG_MUXER_DATA);
delete[] appSegmentBuffer;
if (res != OK) {
@@ -955,13 +1030,14 @@
}
ALOGV("%s: [%" PRId64 "]: appSegmentSize is %zu, width %d, height %d, app1Size %zu",
- __FUNCTION__, timestamp, appSegmentSize, inputFrame.appSegmentBuffer.width,
+ __FUNCTION__, frameNumber, appSegmentSize, inputFrame.appSegmentBuffer.width,
inputFrame.appSegmentBuffer.height, app1Size);
inputFrame.appSegmentWritten = true;
// Release the buffer now so any pending input app segments can be processed
mAppSegmentConsumer->unlockBuffer(inputFrame.appSegmentBuffer);
inputFrame.appSegmentBuffer.data = nullptr;
+ inputFrame.exifError = false;
mLockedAppSegmentBufferCnt--;
return OK;
@@ -1010,7 +1086,7 @@
return OK;
}
-status_t HeicCompositeStream::processOneCodecOutputFrame(nsecs_t timestamp,
+status_t HeicCompositeStream::processOneCodecOutputFrame(int64_t frameNumber,
InputFrame &inputFrame) {
auto it = inputFrame.codecOutputBuffers.begin();
sp<MediaCodecBuffer> buffer;
@@ -1028,7 +1104,7 @@
sp<ABuffer> aBuffer = new ABuffer(buffer->data(), buffer->size());
res = inputFrame.muxer->writeSampleData(
- aBuffer, inputFrame.trackIndex, timestamp, 0 /*flags*/);
+ aBuffer, inputFrame.trackIndex, inputFrame.timestamp, 0 /*flags*/);
if (res != OK) {
ALOGE("%s: Failed to write buffer index %d to muxer: %s (%d)",
__FUNCTION__, it->index, strerror(-res), res);
@@ -1045,11 +1121,11 @@
inputFrame.codecOutputBuffers.erase(inputFrame.codecOutputBuffers.begin());
ALOGV("%s: [%" PRId64 "]: Output buffer index %d",
- __FUNCTION__, timestamp, it->index);
+ __FUNCTION__, frameNumber, it->index);
return OK;
}
-status_t HeicCompositeStream::processCompletedInputFrame(nsecs_t timestamp,
+status_t HeicCompositeStream::processCompletedInputFrame(int64_t frameNumber,
InputFrame &inputFrame) {
sp<ANativeWindow> outputANW = mOutputSurface;
inputFrame.muxer->stop();
@@ -1088,7 +1164,7 @@
blobHeader->blobId = static_cast<CameraBlobId>(0x00FE);
blobHeader->blobSize = fSize;
- res = native_window_set_buffers_timestamp(mOutputSurface.get(), timestamp);
+ res = native_window_set_buffers_timestamp(mOutputSurface.get(), inputFrame.timestamp);
if (res != OK) {
ALOGE("%s: Stream %d: Error setting timestamp: %s (%d)",
__FUNCTION__, getStreamId(), strerror(-res), res);
@@ -1104,13 +1180,14 @@
inputFrame.anb = nullptr;
mDequeuedOutputBufferCnt--;
- ALOGV("%s: [%" PRId64 "]", __FUNCTION__, timestamp);
- ATRACE_ASYNC_END("HEIC capture", inputFrame.frameNumber);
+ ALOGV("%s: [%" PRId64 "]", __FUNCTION__, frameNumber);
+ ATRACE_ASYNC_END("HEIC capture", frameNumber);
return OK;
}
-void HeicCompositeStream::releaseInputFrameLocked(InputFrame *inputFrame /*out*/) {
+void HeicCompositeStream::releaseInputFrameLocked(int64_t frameNumber,
+ InputFrame *inputFrame /*out*/) {
if (inputFrame == nullptr) {
return;
}
@@ -1138,9 +1215,9 @@
inputFrame->codecInputBuffers.erase(it);
}
- if ((inputFrame->error || mErrorState) && !inputFrame->errorNotified) {
- notifyError(inputFrame->frameNumber);
- inputFrame->errorNotified = true;
+ if (inputFrame->error || mErrorState) {
+ ALOGV("%s: notifyError called for frameNumber %" PRId64, __FUNCTION__, frameNumber);
+ notifyError(frameNumber, inputFrame->requestId);
}
if (inputFrame->fileFd >= 0) {
@@ -1152,6 +1229,8 @@
sp<ANativeWindow> outputANW = mOutputSurface;
outputANW->cancelBuffer(mOutputSurface.get(), inputFrame->anb, /*fence*/ -1);
inputFrame->anb = nullptr;
+
+ mDequeuedOutputBufferCnt--;
}
}
@@ -1161,8 +1240,8 @@
while (it != mPendingInputFrames.end()) {
auto& inputFrame = it->second;
if (inputFrame.error ||
- (inputFrame.appSegmentWritten && inputFrame.pendingOutputTiles == 0)) {
- releaseInputFrameLocked(&inputFrame);
+ (inputFrame.appSegmentWritten && inputFrame.pendingOutputTiles == 0)) {
+ releaseInputFrameLocked(it->first, &inputFrame);
it = mPendingInputFrames.erase(it);
inputFrameDone = true;
} else {
@@ -1179,6 +1258,8 @@
auto firstPendingFrame = mPendingInputFrames.begin();
if (firstPendingFrame != mPendingInputFrames.end()) {
updateCodecQualityLocked(firstPendingFrame->second.quality);
+ } else {
+ markTrackerIdle();
}
}
}
@@ -1397,20 +1478,6 @@
return expectedSize;
}
-int64_t HeicCompositeStream::findTimestampInNsLocked(int64_t timeInUs) {
- for (const auto& fn : mFrameNumberMap) {
- if (timeInUs == ns2us(fn.second)) {
- return fn.second;
- }
- }
- for (const auto& inputFrame : mPendingInputFrames) {
- if (timeInUs == ns2us(inputFrame.first)) {
- return inputFrame.first;
- }
- }
- return -1;
-}
-
status_t HeicCompositeStream::copyOneYuvTile(sp<MediaCodecBuffer>& codecBuffer,
const CpuConsumer::LockedBuffer& yuvBuffer,
size_t top, size_t left, size_t width, size_t height) {
@@ -1584,7 +1651,7 @@
}
bool HeicCompositeStream::threadLoop() {
- int64_t currentTs = INT64_MAX;
+ int64_t frameNumber = -1;
bool newInputAvailable = false;
{
@@ -1600,19 +1667,25 @@
while (!newInputAvailable) {
compilePendingInputLocked();
- newInputAvailable = getNextReadyInputLocked(¤tTs);
+ newInputAvailable = getNextReadyInputLocked(&frameNumber);
if (!newInputAvailable) {
- auto failingFrameNumber = getNextFailingInputLocked(¤tTs);
+ auto failingFrameNumber = getNextFailingInputLocked();
if (failingFrameNumber >= 0) {
- // We cannot erase 'mPendingInputFrames[currentTs]' at this point because it is
- // possible for two internal stream buffers to fail. In such scenario the
- // composite stream should notify the client about a stream buffer error only
- // once and this information is kept within 'errorNotified'.
- // Any present failed input frames will be removed on a subsequent call to
- // 'releaseInputFramesLocked()'.
- releaseInputFrameLocked(&mPendingInputFrames[currentTs]);
- currentTs = INT64_MAX;
+ releaseInputFrameLocked(failingFrameNumber,
+ &mPendingInputFrames[failingFrameNumber]);
+
+ // It's okay to remove the entry from mPendingInputFrames
+ // because:
+ // 1. Only one internal stream (main input) is critical in
+ // backing the output stream.
+ // 2. If captureResult/appSegment arrives after the entry is
+ // removed, they are simply skipped.
+ mPendingInputFrames.erase(failingFrameNumber);
+ if (mPendingInputFrames.size() == 0) {
+ markTrackerIdle();
+ }
+ return true;
}
auto ret = mInputReadyCondition.waitRelative(mMutex, kWaitDuration);
@@ -1627,12 +1700,13 @@
}
}
- auto res = processInputFrame(currentTs, mPendingInputFrames[currentTs]);
+ auto res = processInputFrame(frameNumber, mPendingInputFrames[frameNumber]);
Mutex::Autolock l(mMutex);
if (res != OK) {
- ALOGE("%s: Failed processing frame with timestamp: %" PRIu64 ": %s (%d)",
- __FUNCTION__, currentTs, strerror(-res), res);
- mPendingInputFrames[currentTs].error = true;
+ ALOGE("%s: Failed processing frame with timestamp: %" PRIu64 ", frameNumber: %"
+ PRId64 ": %s (%d)", __FUNCTION__, mPendingInputFrames[frameNumber].timestamp,
+ frameNumber, strerror(-res), res);
+ mPendingInputFrames[frameNumber].error = true;
}
releaseInputFramesLocked();
@@ -1640,14 +1714,26 @@
return true;
}
+void HeicCompositeStream::flagAnExifErrorFrameNumber(int64_t frameNumber) {
+ Mutex::Autolock l(mMutex);
+ mExifErrorFrameNumbers.emplace(frameNumber);
+ mInputReadyCondition.signal();
+}
+
bool HeicCompositeStream::onStreamBufferError(const CaptureResultExtras& resultExtras) {
bool res = false;
+ int64_t frameNumber = resultExtras.frameNumber;
+
// Buffer errors concerning internal composite streams should not be directly visible to
// camera clients. They must only receive a single buffer error with the public composite
// stream id.
- if ((resultExtras.errorStreamId == mAppSegmentStreamId) ||
- (resultExtras.errorStreamId == mMainImageStreamId)) {
- flagAnErrorFrameNumber(resultExtras.frameNumber);
+ if (resultExtras.errorStreamId == mAppSegmentStreamId) {
+ ALOGV("%s: APP_SEGMENT frameNumber: %" PRId64, __FUNCTION__, frameNumber);
+ flagAnExifErrorFrameNumber(frameNumber);
+ res = true;
+ } else if (resultExtras.errorStreamId == mMainImageStreamId) {
+ ALOGV("%s: YUV frameNumber: %" PRId64, __FUNCTION__, frameNumber);
+ flagAnErrorFrameNumber(frameNumber);
res = true;
}
@@ -1660,16 +1746,16 @@
Mutex::Autolock l(mMutex);
int64_t timestamp = -1;
- for (const auto& fn : mFrameNumberMap) {
+ for (const auto& fn : mSettingsByFrameNumber) {
if (fn.first == resultExtras.frameNumber) {
- timestamp = fn.second;
+ timestamp = fn.second.timestamp;
break;
}
}
if (timestamp == -1) {
for (const auto& inputFrame : mPendingInputFrames) {
- if (inputFrame.second.frameNumber == resultExtras.frameNumber) {
- timestamp = inputFrame.first;
+ if (inputFrame.first == resultExtras.frameNumber) {
+ timestamp = inputFrame.second.timestamp;
break;
}
}
@@ -1681,9 +1767,33 @@
}
mCaptureResults.emplace(timestamp, std::make_tuple(resultExtras.frameNumber, CameraMetadata()));
+ ALOGV("%s: timestamp %" PRId64 ", frameNumber %" PRId64, __FUNCTION__,
+ timestamp, resultExtras.frameNumber);
mInputReadyCondition.signal();
}
+void HeicCompositeStream::onRequestError(const CaptureResultExtras& resultExtras) {
+ auto frameNumber = resultExtras.frameNumber;
+ ALOGV("%s: frameNumber: %" PRId64, __FUNCTION__, frameNumber);
+ Mutex::Autolock l(mMutex);
+ auto numRequests = mSettingsByFrameNumber.erase(frameNumber);
+ if (numRequests == 0) {
+ // Pending request has been populated into mPendingInputFrames
+ mErrorFrameNumbers.emplace(frameNumber);
+ mInputReadyCondition.signal();
+ } else {
+ // REQUEST_ERROR was received without onShutter.
+ }
+}
+
+void HeicCompositeStream::markTrackerIdle() {
+ sp<StatusTracker> statusTracker = mStatusTracker.promote();
+ if (statusTracker != nullptr) {
+ statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
+ ALOGV("%s: Mark component as idle", __FUNCTION__);
+ }
+}
+
void HeicCompositeStream::CodecCallbackHandler::onMessageReceived(const sp<AMessage> &msg) {
sp<HeicCompositeStream> parent = mParent.promote();
if (parent == nullptr) return;
diff --git a/services/camera/libcameraservice/api2/HeicCompositeStream.h b/services/camera/libcameraservice/api2/HeicCompositeStream.h
index 8fc521e..33ca69a 100644
--- a/services/camera/libcameraservice/api2/HeicCompositeStream.h
+++ b/services/camera/libcameraservice/api2/HeicCompositeStream.h
@@ -37,7 +37,7 @@
class HeicCompositeStream : public CompositeStream, public Thread,
public CpuConsumer::FrameAvailableListener {
public:
- HeicCompositeStream(wp<CameraDeviceBase> device,
+ HeicCompositeStream(sp<CameraDeviceBase> device,
wp<hardware::camera2::ICameraDeviceCallbacks> cb);
~HeicCompositeStream() override;
@@ -81,6 +81,7 @@
bool threadLoop() override;
bool onStreamBufferError(const CaptureResultExtras& resultExtras) override;
void onResultError(const CaptureResultExtras& resultExtras) override;
+ void onRequestError(const CaptureResultExtras& resultExtras) override;
private:
//
@@ -156,9 +157,10 @@
CpuConsumer::LockedBuffer yuvBuffer;
std::vector<CodecInputBufferInfo> codecInputBuffers;
- bool error;
- bool errorNotified;
- int64_t frameNumber;
+ bool error; // Main input image buffer error
+ bool exifError; // Exif/APP_SEGMENT buffer error
+ int64_t timestamp;
+ int32_t requestId;
sp<AMessage> format;
sp<MediaMuxer> muxer;
@@ -172,30 +174,29 @@
size_t codecInputCounter;
InputFrame() : orientation(0), quality(kDefaultJpegQuality), error(false),
- errorNotified(false), frameNumber(-1), fenceFd(-1), fileFd(-1),
- trackIndex(-1), anb(nullptr), appSegmentWritten(false),
+ exifError(false), timestamp(-1), requestId(-1), fenceFd(-1),
+ fileFd(-1), trackIndex(-1), anb(nullptr), appSegmentWritten(false),
pendingOutputTiles(0), codecInputCounter(0) { }
};
void compilePendingInputLocked();
- // Find first complete and valid frame with smallest timestamp
- bool getNextReadyInputLocked(int64_t *currentTs /*out*/);
- // Find next failing frame number with smallest timestamp and return respective frame number
- int64_t getNextFailingInputLocked(int64_t *currentTs /*out*/);
+ // Find first complete and valid frame with smallest frame number
+ bool getNextReadyInputLocked(int64_t *frameNumber /*out*/);
+ // Find next failing frame number with smallest frame number and return respective frame number
+ int64_t getNextFailingInputLocked();
- status_t processInputFrame(nsecs_t timestamp, InputFrame &inputFrame);
+ status_t processInputFrame(int64_t frameNumber, InputFrame &inputFrame);
status_t processCodecInputFrame(InputFrame &inputFrame);
- status_t startMuxerForInputFrame(nsecs_t timestamp, InputFrame &inputFrame);
- status_t processAppSegment(nsecs_t timestamp, InputFrame &inputFrame);
- status_t processOneCodecOutputFrame(nsecs_t timestamp, InputFrame &inputFrame);
- status_t processCompletedInputFrame(nsecs_t timestamp, InputFrame &inputFrame);
+ status_t startMuxerForInputFrame(int64_t frameNumber, InputFrame &inputFrame);
+ status_t processAppSegment(int64_t frameNumber, InputFrame &inputFrame);
+ status_t processOneCodecOutputFrame(int64_t frameNumber, InputFrame &inputFrame);
+ status_t processCompletedInputFrame(int64_t frameNumber, InputFrame &inputFrame);
- void releaseInputFrameLocked(InputFrame *inputFrame /*out*/);
+ void releaseInputFrameLocked(int64_t frameNumber, InputFrame *inputFrame /*out*/);
void releaseInputFramesLocked();
size_t findAppSegmentsSize(const uint8_t* appSegmentBuffer, size_t maxSize,
size_t* app1SegmentSize);
- int64_t findTimestampInNsLocked(int64_t timeInUs);
status_t copyOneYuvTile(sp<MediaCodecBuffer>& codecBuffer,
const CpuConsumer::LockedBuffer& yuvBuffer,
size_t top, size_t left, size_t width, size_t height);
@@ -218,12 +219,14 @@
sp<CpuConsumer> mAppSegmentConsumer;
sp<Surface> mAppSegmentSurface;
size_t mAppSegmentMaxSize;
+ std::queue<int64_t> mAppSegmentFrameNumbers;
CameraMetadata mStaticInfo;
int mMainImageStreamId, mMainImageSurfaceId;
sp<Surface> mMainImageSurface;
sp<CpuConsumer> mMainImageConsumer; // Only applicable for HEVC codec.
bool mYuvBufferAcquired; // Only applicable to HEVC codec
+ std::queue<int64_t> mMainImageFrameNumbers;
static const int32_t kMaxOutputSurfaceProducerCount = 1;
sp<Surface> mOutputSurface;
@@ -231,9 +234,22 @@
int32_t mDequeuedOutputBufferCnt;
// Map from frame number to JPEG setting of orientation+quality
- std::map<int64_t, std::pair<int32_t, int32_t>> mSettingsByFrameNumber;
- // Map from timestamp to JPEG setting of orientation+quality
- std::map<int64_t, std::pair<int32_t, int32_t>> mSettingsByTimestamp;
+ struct HeicSettings {
+ int32_t orientation;
+ int32_t quality;
+ int64_t timestamp;
+ int32_t requestId;
+ bool shutterNotified;
+
+ HeicSettings() : orientation(0), quality(95), timestamp(0),
+ requestId(-1), shutterNotified(false) {}
+ HeicSettings(int32_t _orientation, int32_t _quality) :
+ orientation(_orientation),
+ quality(_quality), timestamp(0),
+ requestId(-1), shutterNotified(false) {}
+
+ };
+ std::map<int64_t, HeicSettings> mSettingsByFrameNumber;
// Keep all incoming APP segment Blob buffer pending further processing.
std::vector<int64_t> mInputAppSegmentBuffers;
@@ -241,7 +257,7 @@
// Keep all incoming HEIC blob buffer pending further processing.
std::vector<CodecOutputBufferInfo> mCodecOutputBuffers;
- std::queue<int64_t> mCodecOutputBufferTimestamps;
+ std::queue<int64_t> mCodecOutputBufferFrameNumbers;
size_t mCodecOutputCounter;
int32_t mQuality;
@@ -253,11 +269,19 @@
// Artificial strictly incremental YUV grid timestamp to make encoder happy.
int64_t mGridTimestampUs;
- // In most common use case, entries are accessed in order.
+ // Indexed by frame number. In most common use case, entries are accessed in order.
std::map<int64_t, InputFrame> mPendingInputFrames;
// Function pointer of libyuv row copy.
void (*mFnCopyRow)(const uint8_t* src, uint8_t* dst, int width);
+
+ // A set of APP_SEGMENT error frame numbers
+ std::set<int64_t> mExifErrorFrameNumbers;
+ void flagAnExifErrorFrameNumber(int64_t frameNumber);
+
+ // The status id for tracking the active/idle status of this composite stream
+ int mStatusId;
+ void markTrackerIdle();
};
}; // namespace camera3
diff --git a/services/camera/libcameraservice/common/CameraDeviceBase.h b/services/camera/libcameraservice/common/CameraDeviceBase.h
index 3662a65..a537ef5 100644
--- a/services/camera/libcameraservice/common/CameraDeviceBase.h
+++ b/services/camera/libcameraservice/common/CameraDeviceBase.h
@@ -33,6 +33,7 @@
#include "camera/CaptureResult.h"
#include "gui/IGraphicBufferProducer.h"
#include "device3/Camera3StreamInterface.h"
+#include "device3/StatusTracker.h"
#include "binder/Status.h"
#include "FrameProducer.h"
@@ -362,6 +363,10 @@
virtual status_t setRotateAndCropAutoBehavior(
camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue) = 0;
+ /**
+ * Get the status tracker of the camera device
+ */
+ virtual wp<camera3::StatusTracker> getStatusTracker() = 0;
};
}; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index b00a2d9..4a509aa 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -60,6 +60,7 @@
#include "device3/Camera3SharedOutputStream.h"
#include "CameraService.h"
#include "utils/CameraThreadState.h"
+#include "utils/TraceHFR.h"
#include <algorithm>
#include <tuple>
@@ -1043,8 +1044,9 @@
}
CaptureOutputStates states {
mId,
- mInFlightLock, mInFlightMap,
- mOutputLock, mResultQueue, mResultSignal,
+ mInFlightLock, mLastCompletedRegularFrameNumber,
+ mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+ mInFlightMap, mOutputLock, mResultQueue, mResultSignal,
mNextShutterFrameNumber,
mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
mNextResultFrameNumber,
@@ -1100,8 +1102,9 @@
CaptureOutputStates states {
mId,
- mInFlightLock, mInFlightMap,
- mOutputLock, mResultQueue, mResultSignal,
+ mInFlightLock, mLastCompletedRegularFrameNumber,
+ mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+ mInFlightMap, mOutputLock, mResultQueue, mResultSignal,
mNextShutterFrameNumber,
mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
mNextResultFrameNumber,
@@ -1139,8 +1142,9 @@
CaptureOutputStates states {
mId,
- mInFlightLock, mInFlightMap,
- mOutputLock, mResultQueue, mResultSignal,
+ mInFlightLock, mLastCompletedRegularFrameNumber,
+ mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+ mInFlightMap, mOutputLock, mResultQueue, mResultSignal,
mNextShutterFrameNumber,
mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
mNextResultFrameNumber,
@@ -1777,13 +1781,6 @@
mStatusChanged.broadcast();
}
-void Camera3Device::pauseStateNotify(bool enable) {
- Mutex::Autolock il(mInterfaceLock);
- Mutex::Autolock l(mLock);
-
- mPauseStateNotify = enable;
-}
-
// Pause to reconfigure
status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration) {
if (mRequestThread.get() != nullptr) {
@@ -2359,7 +2356,7 @@
return false;
}
-bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams) {
+bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams, int clientStatusId) {
ATRACE_CALL();
bool ret = false;
@@ -2373,7 +2370,16 @@
return true;
}
- auto rc = internalPauseAndWaitLocked(maxExpectedDuration);
+ status_t rc = NO_ERROR;
+ bool markClientActive = false;
+ if (mStatus == STATUS_ACTIVE) {
+ markClientActive = true;
+ mPauseStateNotify = true;
+ mStatusTracker->markComponentIdle(clientStatusId, Fence::NO_FENCE);
+
+ rc = internalPauseAndWaitLocked(maxExpectedDuration);
+ }
+
if (rc == NO_ERROR) {
mNeedConfig = true;
rc = configureStreamsLocked(mOperatingMode, sessionParams, /*notifyRequestThread*/ false);
@@ -2401,6 +2407,10 @@
ALOGE("%s: Failed to pause streaming: %d", __FUNCTION__, rc);
}
+ if (markClientActive) {
+ mStatusTracker->markComponentActive(clientStatusId);
+ }
+
return ret;
}
@@ -2825,7 +2835,7 @@
}
void Camera3Device::removeInFlightMapEntryLocked(int idx) {
- ATRACE_CALL();
+ ATRACE_HFR_CALL();
nsecs_t duration = mInFlightMap.valueAt(idx).maxExpectedDuration;
mInFlightMap.removeItemsAt(idx, 1);
@@ -4277,22 +4287,11 @@
}
if (res == OK) {
- sp<StatusTracker> statusTracker = mStatusTracker.promote();
- if (statusTracker != 0) {
- sp<Camera3Device> parent = mParent.promote();
- if (parent != nullptr) {
- parent->pauseStateNotify(true);
- }
-
- statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
-
- if (parent != nullptr) {
- mReconfigured |= parent->reconfigureCamera(mLatestSessionParams);
- }
-
- statusTracker->markComponentActive(mStatusId);
- setPaused(false);
+ sp<Camera3Device> parent = mParent.promote();
+ if (parent != nullptr) {
+ mReconfigured |= parent->reconfigureCamera(mLatestSessionParams, mStatusId);
}
+ setPaused(false);
if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
mNextRequests[0].captureRequest->mInputStream->restoreConfiguredState();
@@ -5906,11 +5905,13 @@
// though technically no other thread should be talking to Camera3Device at this point
Camera3OfflineStates offlineStates(
mTagMonitor, mVendorTagId, mUseHalBufManager, mNeedFixupMonochromeTags,
- mUsePartialResult, mNumPartialResults, mNextResultFrameNumber,
- mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
- mNextShutterFrameNumber, mNextReprocessShutterFrameNumber,
- mNextZslStillShutterFrameNumber, mDeviceInfo, mPhysicalDeviceInfoMap,
- mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers);
+ mUsePartialResult, mNumPartialResults, mLastCompletedRegularFrameNumber,
+ mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+ mNextResultFrameNumber, mNextReprocessResultFrameNumber,
+ mNextZslStillResultFrameNumber, mNextShutterFrameNumber,
+ mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
+ mDeviceInfo, mPhysicalDeviceInfoMap, mDistortionMappers,
+ mZoomRatioMappers, mRotateAndCropMappers);
*session = new Camera3OfflineSession(mId, inputStream, offlineStreamSet,
std::move(bufferRecords), offlineReqs, offlineStates, offlineSession);
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 19ecf4b..408f1f9 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -231,6 +231,9 @@
status_t setRotateAndCropAutoBehavior(
camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue);
+ // Get the status trackeer for the camera device
+ wp<camera3::StatusTracker> getStatusTracker() { return mStatusTracker; }
+
/**
* Helper functions to map between framework and HIDL values
*/
@@ -635,17 +638,10 @@
const SurfaceMap &surfaceMap);
/**
- * Pause state updates to the client application. Needed to mask out idle/active
- * transitions during internal reconfigure
- */
- void pauseStateNotify(bool enable);
-
- /**
* Internally re-configure camera device using new session parameters.
- * This will get triggered by the request thread. Be sure to call
- * pauseStateNotify(true) before going idle in the requesting location.
+ * This will get triggered by the request thread.
*/
- bool reconfigureCamera(const CameraMetadata& sessionParams);
+ bool reconfigureCamera(const CameraMetadata& sessionParams, int clientStatusId);
/**
* Return true in case of any output or input abandoned streams,
@@ -1014,6 +1010,9 @@
std::mutex mInFlightLock;
camera3::InFlightRequestMap mInFlightMap;
nsecs_t mExpectedInflightDuration = 0;
+ int64_t mLastCompletedRegularFrameNumber = -1;
+ int64_t mLastCompletedReprocessFrameNumber = -1;
+ int64_t mLastCompletedZslFrameNumber = -1;
// End of mInFlightLock protection scope
int mInFlightStatusId; // const after initialize
diff --git a/services/camera/libcameraservice/device3/Camera3IOStreamBase.cpp b/services/camera/libcameraservice/device3/Camera3IOStreamBase.cpp
index ef0d919..bda2961 100644
--- a/services/camera/libcameraservice/device3/Camera3IOStreamBase.cpp
+++ b/services/camera/libcameraservice/device3/Camera3IOStreamBase.cpp
@@ -269,8 +269,6 @@
}
}
- mBufferReturnedSignal.signal();
-
if (output) {
mLastTimestamp = timestamp;
}
diff --git a/services/camera/libcameraservice/device3/Camera3IOStreamBase.h b/services/camera/libcameraservice/device3/Camera3IOStreamBase.h
index 750f64d..448379c 100644
--- a/services/camera/libcameraservice/device3/Camera3IOStreamBase.h
+++ b/services/camera/libcameraservice/device3/Camera3IOStreamBase.h
@@ -55,7 +55,6 @@
// number of output buffers that are currently acquired by HAL. This will be
// Redundant when camera3 streams are no longer bidirectional streams.
size_t mHandoutOutputBufferCount;
- Condition mBufferReturnedSignal;
uint32_t mFrameCount;
// Last received output buffer's timestamp
nsecs_t mLastTimestamp;
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp b/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
index 5942868..95f9633 100644
--- a/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
@@ -61,6 +61,9 @@
mNeedFixupMonochromeTags(offlineStates.mNeedFixupMonochromeTags),
mUsePartialResult(offlineStates.mUsePartialResult),
mNumPartialResults(offlineStates.mNumPartialResults),
+ mLastCompletedRegularFrameNumber(offlineStates.mLastCompletedRegularFrameNumber),
+ mLastCompletedReprocessFrameNumber(offlineStates.mLastCompletedReprocessFrameNumber),
+ mLastCompletedZslFrameNumber(offlineStates.mLastCompletedZslFrameNumber),
mNextResultFrameNumber(offlineStates.mNextResultFrameNumber),
mNextReprocessResultFrameNumber(offlineStates.mNextReprocessResultFrameNumber),
mNextZslStillResultFrameNumber(offlineStates.mNextZslStillResultFrameNumber),
@@ -247,8 +250,9 @@
CaptureOutputStates states {
mId,
- mOfflineReqsLock, mOfflineReqs,
- mOutputLock, mResultQueue, mResultSignal,
+ mOfflineReqsLock, mLastCompletedRegularFrameNumber,
+ mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+ mOfflineReqs, mOutputLock, mResultQueue, mResultSignal,
mNextShutterFrameNumber,
mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
mNextResultFrameNumber,
@@ -285,8 +289,9 @@
CaptureOutputStates states {
mId,
- mOfflineReqsLock, mOfflineReqs,
- mOutputLock, mResultQueue, mResultSignal,
+ mOfflineReqsLock, mLastCompletedRegularFrameNumber,
+ mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+ mOfflineReqs, mOutputLock, mResultQueue, mResultSignal,
mNextShutterFrameNumber,
mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
mNextResultFrameNumber,
@@ -318,8 +323,9 @@
CaptureOutputStates states {
mId,
- mOfflineReqsLock, mOfflineReqs,
- mOutputLock, mResultQueue, mResultSignal,
+ mOfflineReqsLock, mLastCompletedRegularFrameNumber,
+ mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+ mOfflineReqs, mOutputLock, mResultQueue, mResultSignal,
mNextShutterFrameNumber,
mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
mNextResultFrameNumber,
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.h b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
index 208f70d..c4c7a85 100644
--- a/services/camera/libcameraservice/device3/Camera3OfflineSession.h
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
@@ -57,10 +57,11 @@
const TagMonitor& tagMonitor, const metadata_vendor_id_t vendorTagId,
const bool useHalBufManager, const bool needFixupMonochromeTags,
const bool usePartialResult, const uint32_t numPartialResults,
- const uint32_t nextResultFN, const uint32_t nextReprocResultFN,
- const uint32_t nextZslResultFN, const uint32_t nextShutterFN,
- const uint32_t nextReprocShutterFN, const uint32_t nextZslShutterFN,
- const CameraMetadata& deviceInfo,
+ const int64_t lastCompletedRegularFN, const int64_t lastCompletedReprocessFN,
+ const int64_t lastCompletedZslFN, const uint32_t nextResultFN,
+ const uint32_t nextReprocResultFN, const uint32_t nextZslResultFN,
+ const uint32_t nextShutterFN, const uint32_t nextReprocShutterFN,
+ const uint32_t nextZslShutterFN, const CameraMetadata& deviceInfo,
const std::unordered_map<std::string, CameraMetadata>& physicalDeviceInfoMap,
const std::unordered_map<std::string, camera3::DistortionMapper>& distortionMappers,
const std::unordered_map<std::string, camera3::ZoomRatioMapper>& zoomRatioMappers,
@@ -69,6 +70,9 @@
mTagMonitor(tagMonitor), mVendorTagId(vendorTagId),
mUseHalBufManager(useHalBufManager), mNeedFixupMonochromeTags(needFixupMonochromeTags),
mUsePartialResult(usePartialResult), mNumPartialResults(numPartialResults),
+ mLastCompletedRegularFrameNumber(lastCompletedRegularFN),
+ mLastCompletedReprocessFrameNumber(lastCompletedReprocessFN),
+ mLastCompletedZslFrameNumber(lastCompletedZslFN),
mNextResultFrameNumber(nextResultFN),
mNextReprocessResultFrameNumber(nextReprocResultFN),
mNextZslStillResultFrameNumber(nextZslResultFN),
@@ -90,6 +94,15 @@
const bool mUsePartialResult;
const uint32_t mNumPartialResults;
+ // The last completed (buffers, result metadata, and error notify) regular
+ // request frame number
+ const int64_t mLastCompletedRegularFrameNumber;
+ // The last completed (buffers, result metadata, and error notify) reprocess
+ // request frame number
+ const int64_t mLastCompletedReprocessFrameNumber;
+ // The last completed (buffers, result metadata, and error notify) zsl
+ // request frame number
+ const int64_t mLastCompletedZslFrameNumber;
// the minimal frame number of the next non-reprocess result
const uint32_t mNextResultFrameNumber;
// the minimal frame number of the next reprocess result
@@ -214,6 +227,12 @@
std::mutex mOutputLock;
std::list<CaptureResult> mResultQueue;
std::condition_variable mResultSignal;
+ // the last completed frame number of regular requests
+ int64_t mLastCompletedRegularFrameNumber;
+ // the last completed frame number of reprocess requests
+ int64_t mLastCompletedReprocessFrameNumber;
+ // the last completed frame number of ZSL still capture requests
+ int64_t mLastCompletedZslFrameNumber;
// the minimal frame number of the next non-reprocess result
uint32_t mNextResultFrameNumber;
// the minimal frame number of the next reprocess result
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
index e1d35e8..01ca006 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
@@ -21,6 +21,7 @@
#include <utils/Log.h>
#include <utils/Trace.h>
#include "Camera3OutputStream.h"
+#include "utils/TraceHFR.h"
#ifndef container_of
#define container_of(ptr, type, member) \
@@ -160,7 +161,7 @@
status_t Camera3OutputStream::getBufferLocked(camera3_stream_buffer *buffer,
const std::vector<size_t>&) {
- ATRACE_CALL();
+ ATRACE_HFR_CALL();
ANativeWindowBuffer* anb;
int fenceFd = -1;
@@ -190,7 +191,7 @@
status_t Camera3OutputStream::returnBufferLocked(
const camera3_stream_buffer &buffer,
nsecs_t timestamp, const std::vector<size_t>& surface_ids) {
- ATRACE_CALL();
+ ATRACE_HFR_CALL();
status_t res = returnAnyBufferLocked(buffer, timestamp, /*output*/true, surface_ids);
@@ -516,7 +517,7 @@
}
status_t Camera3OutputStream::getBufferLockedCommon(ANativeWindowBuffer** anb, int* fenceFd) {
- ATRACE_CALL();
+ ATRACE_HFR_CALL();
status_t res;
if ((res = getBufferPreconditionCheckLocked()) != OK) {
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
index 603f516..eea5ef1 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
@@ -405,8 +405,8 @@
// In the case of a successful request:
// all input and output buffers, all result metadata, shutter callback
// arrived.
- // In the case of a unsuccessful request:
- // all input and output buffers arrived.
+ // In the case of an unsuccessful request:
+ // all input and output buffers, as well as request/result error notifications, arrived.
if (request.numBuffersLeft == 0 &&
(request.skipResultMetadata ||
(request.haveResultMetadata && shutterTimestamp != 0))) {
@@ -434,7 +434,17 @@
states.useHalBufManager, states.listener,
request.pendingOutputBuffers.array(),
request.pendingOutputBuffers.size(), 0, /*timestampIncreasing*/true,
- request.outputSurfaces, request.resultExtras);
+ request.outputSurfaces, request.resultExtras,
+ request.errorBufStrategy);
+
+ // Note down the just completed frame number
+ if (request.hasInputBuffer) {
+ states.lastCompletedReprocessFrameNumber = frameNumber;
+ } else if (request.zslCapture) {
+ states.lastCompletedZslFrameNumber = frameNumber;
+ } else {
+ states.lastCompletedRegularFrameNumber = frameNumber;
+ }
removeInFlightMapEntryLocked(states, idx);
ALOGVV("%s: removed frame %d from InFlightMap", __FUNCTION__, frameNumber);
@@ -487,10 +497,13 @@
InFlightRequest &request = states.inflightMap.editValueAt(idx);
ALOGVV("%s: got InFlightRequest requestId = %" PRId32
", frameNumber = %" PRId64 ", burstId = %" PRId32
- ", partialResultCount = %d, hasCallback = %d",
+ ", partialResultCount = %d/%d, hasCallback = %d, num_output_buffers %d"
+ ", usePartialResult = %d",
__FUNCTION__, request.resultExtras.requestId,
request.resultExtras.frameNumber, request.resultExtras.burstId,
- result->partial_result, request.hasCallback);
+ result->partial_result, states.numPartialResults,
+ request.hasCallback, result->num_output_buffers,
+ states.usePartialResult);
// Always update the partial count to the latest one if it's not 0
// (buffers only). When framework aggregates adjacent partial results
// into one, the latest partial count will be used.
@@ -555,6 +568,7 @@
request.collectedPartialResult);
}
request.haveResultMetadata = true;
+ request.errorBufStrategy = ERROR_BUF_RETURN_NOTIFY;
}
uint32_t numBuffersReturned = result->num_output_buffers;
@@ -581,18 +595,14 @@
request.sensorTimestamp = entry.data.i64[0];
}
- // If shutter event isn't received yet, append the output buffers to
- // the in-flight request. Otherwise, return the output buffers to
- // streams.
- if (shutterTimestamp == 0) {
- request.pendingOutputBuffers.appendArray(result->output_buffers,
+ // If shutter event isn't received yet, do not return the pending output
+ // buffers.
+ request.pendingOutputBuffers.appendArray(result->output_buffers,
result->num_output_buffers);
- } else {
- bool timestampIncreasing = !(request.zslCapture || request.hasInputBuffer);
- returnOutputBuffers(states.useHalBufManager, states.listener,
- result->output_buffers, result->num_output_buffers,
- shutterTimestamp, timestampIncreasing,
- request.outputSurfaces, request.resultExtras);
+ if (shutterTimestamp != 0) {
+ returnAndRemovePendingOutputBuffers(
+ states.useHalBufManager, states.listener,
+ request);
}
if (result->result != NULL && !isPartialResult) {
@@ -791,10 +801,26 @@
const camera3_stream_buffer_t *outputBuffers, size_t numBuffers,
nsecs_t timestamp, bool timestampIncreasing,
const SurfaceMap& outputSurfaces,
- const CaptureResultExtras &inResultExtras) {
+ const CaptureResultExtras &inResultExtras,
+ ERROR_BUF_STRATEGY errorBufStrategy) {
for (size_t i = 0; i < numBuffers; i++)
{
+ Camera3StreamInterface *stream = Camera3Stream::cast(outputBuffers[i].stream);
+ int streamId = stream->getId();
+
+ // Call notify(ERROR_BUFFER) if necessary.
+ if (outputBuffers[i].status == CAMERA3_BUFFER_STATUS_ERROR &&
+ errorBufStrategy == ERROR_BUF_RETURN_NOTIFY) {
+ if (listener != nullptr) {
+ CaptureResultExtras extras = inResultExtras;
+ extras.errorStreamId = streamId;
+ listener->notifyError(
+ hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_BUFFER,
+ extras);
+ }
+ }
+
if (outputBuffers[i].buffer == nullptr) {
if (!useHalBufManager) {
// With HAL buffer management API, HAL sometimes will have to return buffers that
@@ -805,20 +831,23 @@
continue;
}
- Camera3StreamInterface *stream = Camera3Stream::cast(outputBuffers[i].stream);
- int streamId = stream->getId();
const auto& it = outputSurfaces.find(streamId);
status_t res = OK;
- if (it != outputSurfaces.end()) {
- res = stream->returnBuffer(
- outputBuffers[i], timestamp, timestampIncreasing, it->second,
- inResultExtras.frameNumber);
- } else {
- res = stream->returnBuffer(
- outputBuffers[i], timestamp, timestampIncreasing, std::vector<size_t> (),
- inResultExtras.frameNumber);
- }
+ // Do not return the buffer if the buffer status is error, and the error
+ // buffer strategy is CACHE.
+ if (outputBuffers[i].status != CAMERA3_BUFFER_STATUS_ERROR ||
+ errorBufStrategy != ERROR_BUF_CACHE) {
+ if (it != outputSurfaces.end()) {
+ res = stream->returnBuffer(
+ outputBuffers[i], timestamp, timestampIncreasing, it->second,
+ inResultExtras.frameNumber);
+ } else {
+ res = stream->returnBuffer(
+ outputBuffers[i], timestamp, timestampIncreasing, std::vector<size_t> (),
+ inResultExtras.frameNumber);
+ }
+ }
// Note: stream may be deallocated at this point, if this buffer was
// the last reference to it.
if (res == NO_INIT || res == DEAD_OBJECT) {
@@ -848,6 +877,28 @@
}
}
+void returnAndRemovePendingOutputBuffers(bool useHalBufManager,
+ sp<NotificationListener> listener, InFlightRequest& request) {
+ bool timestampIncreasing = !(request.zslCapture || request.hasInputBuffer);
+ returnOutputBuffers(useHalBufManager, listener,
+ request.pendingOutputBuffers.array(),
+ request.pendingOutputBuffers.size(),
+ request.shutterTimestamp, timestampIncreasing,
+ request.outputSurfaces, request.resultExtras,
+ request.errorBufStrategy);
+
+ // Remove error buffers that are not cached.
+ for (auto iter = request.pendingOutputBuffers.begin();
+ iter != request.pendingOutputBuffers.end(); ) {
+ if (request.errorBufStrategy != ERROR_BUF_CACHE ||
+ iter->status != CAMERA3_BUFFER_STATUS_ERROR) {
+ iter = request.pendingOutputBuffers.erase(iter);
+ } else {
+ iter++;
+ }
+ }
+}
+
void notifyShutter(CaptureOutputStates& states, const camera3_shutter_msg_t &msg) {
ATRACE_CALL();
ssize_t idx;
@@ -899,6 +950,12 @@
msg.frame_number, r.resultExtras.requestId, msg.timestamp);
// Call listener, if any
if (states.listener != nullptr) {
+ r.resultExtras.lastCompletedRegularFrameNumber =
+ states.lastCompletedRegularFrameNumber;
+ r.resultExtras.lastCompletedReprocessFrameNumber =
+ states.lastCompletedReprocessFrameNumber;
+ r.resultExtras.lastCompletedZslFrameNumber =
+ states.lastCompletedZslFrameNumber;
states.listener->notifyShutter(r.resultExtras, msg.timestamp);
}
// send pending result and buffers
@@ -908,13 +965,8 @@
r.hasInputBuffer, r.zslCapture && r.stillCapture,
r.rotateAndCropAuto, r.cameraIdsWithZoom, r.physicalMetadatas);
}
- bool timestampIncreasing = !(r.zslCapture || r.hasInputBuffer);
- returnOutputBuffers(
- states.useHalBufManager, states.listener,
- r.pendingOutputBuffers.array(),
- r.pendingOutputBuffers.size(), r.shutterTimestamp, timestampIncreasing,
- r.outputSurfaces, r.resultExtras);
- r.pendingOutputBuffers.clear();
+ returnAndRemovePendingOutputBuffers(
+ states.useHalBufManager, states.listener, r);
removeInFlightRequestIfReadyLocked(states, idx);
}
@@ -968,7 +1020,6 @@
break;
case hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_REQUEST:
case hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_RESULT:
- case hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_BUFFER:
{
std::lock_guard<std::mutex> l(states.inflightLock);
ssize_t idx = states.inflightMap.indexOfKey(msg.frame_number);
@@ -976,7 +1027,7 @@
InFlightRequest &r = states.inflightMap.editValueAt(idx);
r.requestStatus = msg.error_code;
resultExtras = r.resultExtras;
- bool logicalDeviceResultError = false;
+ bool physicalDeviceResultError = false;
if (hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_RESULT ==
errorCode) {
if (physicalCameraId.size() > 0) {
@@ -990,23 +1041,22 @@
}
r.physicalCameraIds.erase(iter);
resultExtras.errorPhysicalCameraId = physicalCameraId;
- } else {
- logicalDeviceResultError = true;
+ physicalDeviceResultError = true;
}
}
- if (logicalDeviceResultError
- || hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_REQUEST ==
- errorCode) {
+ if (!physicalDeviceResultError) {
r.skipResultMetadata = true;
- }
- if (logicalDeviceResultError) {
- // In case of missing result check whether the buffers
- // returned. If they returned, then remove inflight
- // request.
- // TODO: should we call this for ERROR_CAMERA_REQUEST as well?
- // otherwise we are depending on HAL to send the buffers back after
- // calling notifyError. Not sure if that's in the spec.
+ if (hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_RESULT
+ == errorCode) {
+ r.errorBufStrategy = ERROR_BUF_RETURN_NOTIFY;
+ } else {
+ // errorCode is ERROR_CAMERA_REQUEST
+ r.errorBufStrategy = ERROR_BUF_RETURN;
+ }
+
+ // Check whether the buffers returned. If they returned,
+ // remove inflight request.
removeInFlightRequestIfReadyLocked(states, idx);
}
} else {
@@ -1024,6 +1074,10 @@
states.cameraId.string(), __FUNCTION__);
}
break;
+ case hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_BUFFER:
+ // Do not depend on HAL ERROR_CAMERA_BUFFER to send buffer error
+ // callback to the app. Rather, use STATUS_ERROR of image buffers.
+ break;
default:
// SET_ERR calls notifyError
SET_ERR("Unknown error message from HAL: %d", msg.error_code);
@@ -1338,8 +1392,14 @@
request.pendingOutputBuffers.array(),
request.pendingOutputBuffers.size(), 0,
/*timestampIncreasing*/true, request.outputSurfaces,
- request.resultExtras);
+ request.resultExtras, request.errorBufStrategy);
+ ALOGW("%s: Frame %d | Timestamp: %" PRId64 ", metadata"
+ " arrived: %s, buffers left: %d.\n", __FUNCTION__,
+ states.inflightMap.keyAt(idx), request.shutterTimestamp,
+ request.haveResultMetadata ? "true" : "false",
+ request.numBuffersLeft);
}
+
states.inflightMap.clear();
states.inflightIntf.onInflightMapFlushedLocked();
}
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.h b/services/camera/libcameraservice/device3/Camera3OutputUtils.h
index fbb47f8..9946312 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.h
@@ -44,7 +44,9 @@
/**
* Helper methods shared between Camera3Device/Camera3OfflineSession for HAL callbacks
*/
- // helper function to return the output buffers to output streams.
+
+ // helper function to return the output buffers to output streams. The
+ // function also optionally calls notify(ERROR_BUFFER).
void returnOutputBuffers(
bool useHalBufManager,
sp<NotificationListener> listener, // Only needed when outputSurfaces is not empty
@@ -53,13 +55,25 @@
// The following arguments are only meant for surface sharing use case
const SurfaceMap& outputSurfaces = SurfaceMap{},
// Used to send buffer error callback when failing to return buffer
- const CaptureResultExtras &resultExtras = CaptureResultExtras{});
+ const CaptureResultExtras &resultExtras = CaptureResultExtras{},
+ ERROR_BUF_STRATEGY errorBufStrategy = ERROR_BUF_RETURN);
+
+ // helper function to return the output buffers to output streams, and
+ // remove the returned buffers from the inflight request's pending buffers
+ // vector.
+ void returnAndRemovePendingOutputBuffers(
+ bool useHalBufManager,
+ sp<NotificationListener> listener, // Only needed when outputSurfaces is not empty
+ InFlightRequest& request);
// Camera3Device/Camera3OfflineSession internal states used in notify/processCaptureResult
// callbacks
struct CaptureOutputStates {
const String8& cameraId;
std::mutex& inflightLock;
+ int64_t& lastCompletedRegularFrameNumber;
+ int64_t& lastCompletedZslFrameNumber;
+ int64_t& lastCompletedReprocessFrameNumber;
InFlightRequestMap& inflightMap; // end of inflightLock scope
std::mutex& outputLock;
std::list<CaptureResult>& resultQueue;
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.cpp b/services/camera/libcameraservice/device3/Camera3Stream.cpp
index 7916ddb..20f6168 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Stream.cpp
@@ -22,6 +22,7 @@
#include <utils/Trace.h>
#include "device3/Camera3Stream.h"
#include "device3/StatusTracker.h"
+#include "utils/TraceHFR.h"
#include <cutils/properties.h>
@@ -601,7 +602,7 @@
status_t Camera3Stream::getBuffer(camera3_stream_buffer *buffer,
nsecs_t waitBufferTimeout,
const std::vector<size_t>& surface_ids) {
- ATRACE_CALL();
+ ATRACE_HFR_CALL();
Mutex::Autolock l(mLock);
status_t res = OK;
@@ -682,7 +683,7 @@
status_t Camera3Stream::returnBuffer(const camera3_stream_buffer &buffer,
nsecs_t timestamp, bool timestampIncreasing,
const std::vector<size_t>& surface_ids, uint64_t frameNumber) {
- ATRACE_CALL();
+ ATRACE_HFR_CALL();
Mutex::Autolock l(mLock);
// Check if this buffer is outstanding.
@@ -814,6 +815,8 @@
info.mError = (buffer.status == CAMERA3_BUFFER_STATUS_ERROR);
info.mFrameNumber = frameNumber;
info.mTimestamp = timestamp;
+ info.mStreamId = getId();
+
// TODO: rest of fields
for (it = mBufferListenerList.begin(), end = mBufferListenerList.end();
diff --git a/services/camera/libcameraservice/device3/Camera3StreamBufferListener.h b/services/camera/libcameraservice/device3/Camera3StreamBufferListener.h
index d0aee27..170da5a 100644
--- a/services/camera/libcameraservice/device3/Camera3StreamBufferListener.h
+++ b/services/camera/libcameraservice/device3/Camera3StreamBufferListener.h
@@ -29,6 +29,7 @@
public:
struct BufferInfo {
+ int mStreamId;
bool mOutput; // if false then input buffer
Rect mCrop;
uint32_t mTransform;
diff --git a/services/camera/libcameraservice/device3/InFlightRequest.h b/services/camera/libcameraservice/device3/InFlightRequest.h
index 424043b..da4f228 100644
--- a/services/camera/libcameraservice/device3/InFlightRequest.h
+++ b/services/camera/libcameraservice/device3/InFlightRequest.h
@@ -32,7 +32,18 @@
namespace camera3 {
+typedef enum {
+ // Cache the buffers with STATUS_ERROR within InFlightRequest
+ ERROR_BUF_CACHE,
+ // Return the buffers with STATUS_ERROR to the buffer queue
+ ERROR_BUF_RETURN,
+ // Return the buffers with STATUS_ERROR to the buffer queue, and call
+ // notify(ERROR_BUFFER) as well
+ ERROR_BUF_RETURN_NOTIFY
+} ERROR_BUF_STRATEGY;
+
struct InFlightRequest {
+
// Set by notify() SHUTTER call.
nsecs_t shutterTimestamp;
// Set by process_capture_result().
@@ -43,6 +54,9 @@
// Decremented by calls to process_capture_result with valid output
// and input buffers
int numBuffersLeft;
+
+ // The inflight request is considered complete if all buffers are returned
+
CaptureResultExtras resultExtras;
// If this request has any input buffer
bool hasInputBuffer;
@@ -79,6 +93,10 @@
// REQUEST/RESULT error.
bool skipResultMetadata;
+ // Whether the buffers with STATUS_ERROR should be cached as pending buffers,
+ // returned to the buffer queue, or returned to the buffer queue and notify with ERROR_BUFFER.
+ ERROR_BUF_STRATEGY errorBufStrategy;
+
// The physical camera ids being requested.
std::set<String8> physicalCameraIds;
@@ -114,6 +132,7 @@
hasCallback(true),
maxExpectedDuration(kDefaultExpectedDuration),
skipResultMetadata(false),
+ errorBufStrategy(ERROR_BUF_CACHE),
stillCapture(false),
zslCapture(false),
rotateAndCropAuto(false) {
@@ -134,6 +153,7 @@
hasCallback(hasAppCallback),
maxExpectedDuration(maxDuration),
skipResultMetadata(false),
+ errorBufStrategy(ERROR_BUF_CACHE),
physicalCameraIds(physicalCameraIdSet),
stillCapture(isStillCapture),
zslCapture(isZslCapture),
diff --git a/services/camera/libcameraservice/tests/ClientManagerTest.cpp b/services/camera/libcameraservice/tests/ClientManagerTest.cpp
new file mode 100644
index 0000000..6a38427
--- /dev/null
+++ b/services/camera/libcameraservice/tests/ClientManagerTest.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "ClientManagerTest"
+
+#include "../utils/ClientManager.h"
+#include <gtest/gtest.h>
+
+using namespace android::resource_policy;
+
+struct TestClient {
+ TestClient(int id, int32_t cost, const std::set<int>& conflictingKeys, int32_t ownerId,
+ int32_t score, int32_t state, bool isVendorClient) :
+ mId(id), mCost(cost), mConflictingKeys(conflictingKeys),
+ mOwnerId(ownerId), mScore(score), mState(state), mIsVendorClient(isVendorClient) {};
+ int mId;
+ int32_t mCost; // Int 0..100
+ std::set<int> mConflictingKeys;
+ int32_t mOwnerId; // PID
+ int32_t mScore; // Priority
+ int32_t mState; // Foreground/background etc
+ bool mIsVendorClient;
+};
+
+using TestClientDescriptor = ClientDescriptor<int, TestClient>;
+using TestDescriptorPtr = std::shared_ptr<TestClientDescriptor>;
+
+TestDescriptorPtr makeDescFromTestClient(const TestClient& tc) {
+ return std::make_shared<TestClientDescriptor>(/*ID*/tc.mId, tc, tc.mCost, tc.mConflictingKeys,
+ tc.mScore, tc.mOwnerId, tc.mState, tc.mIsVendorClient);
+}
+
+class TestClientManager : public ClientManager<int, TestClient> {
+public:
+ TestClientManager() {}
+ virtual ~TestClientManager() {}
+};
+
+
+// Test ClientMager behavior when there is only one single owner
+// The expected behavior is that if one owner (application or vendor) is trying
+// to open second camera, it may succeed or not, but the first opened camera
+// should never be evicted.
+TEST(ClientManagerTest, SingleOwnerMultipleCamera) {
+
+ TestClientManager cm;
+ TestClient cam0Client(/*ID*/0, /*cost*/100, /*conflicts*/{1},
+ /*ownerId*/ 1000, /*score*/50, /*state*/ 1, /*isVendorClient*/ false);
+ auto cam0Desc = makeDescFromTestClient(cam0Client);
+ auto evicted = cm.addAndEvict(cam0Desc);
+ ASSERT_EQ(evicted.size(), 0u) << "Evicted list must be empty";
+
+ TestClient cam1Client(/*ID*/1, /*cost*/100, /*conflicts*/{0},
+ /*ownerId*/ 1000, /*score*/50, /*state*/ 1, /*isVendorClient*/ false);
+ auto cam1Desc = makeDescFromTestClient(cam1Client);
+
+ // 1. Check with conflicting devices, new client would be evicted
+ auto wouldBeEvicted = cm.wouldEvict(cam1Desc);
+ ASSERT_EQ(wouldBeEvicted.size(), 1u) << "Evicted list length must be 1";
+ ASSERT_EQ(wouldBeEvicted[0]->getKey(), cam1Desc->getKey()) << "cam1 must be evicted";
+
+ cm.removeAll();
+
+ TestClient cam2Client(/*ID*/2, /*cost*/100, /*conflicts*/{},
+ /*ownerId*/ 1000, /*score*/50, /*state*/ 1, /*isVendorClient*/ false);
+ auto cam2Desc = makeDescFromTestClient(cam2Client);
+ evicted = cm.addAndEvict(cam2Desc);
+ ASSERT_EQ(evicted.size(), 0u) << "Evicted list must be empty";
+
+ TestClient cam3Client(/*ID*/3, /*cost*/100, /*conflicts*/{},
+ /*ownerId*/ 1000, /*score*/50, /*state*/ 1, /*isVendorClient*/ false);
+ auto cam3Desc = makeDescFromTestClient(cam3Client);
+
+ // 2. Check without conflicting devices, the pre-existing client won't be evicted
+ // In this case, the new client would be granted, but could later be rejected by HAL due to
+ // resource cost.
+ wouldBeEvicted = cm.wouldEvict(cam3Desc);
+ ASSERT_EQ(wouldBeEvicted.size(), 0u) << "Evicted list must be empty";
+
+ cm.removeAll();
+
+ evicted = cm.addAndEvict(cam0Desc);
+ ASSERT_EQ(evicted.size(), 0u) << "Evicted list must be empty";
+
+ TestClient cam0ClientNew(/*ID*/0, /*cost*/100, /*conflicts*/{1},
+ /*ownerId*/ 1000, /*score*/50, /*state*/ 1, /*isVendorClient*/ false);
+ auto cam0DescNew = makeDescFromTestClient(cam0ClientNew);
+ wouldBeEvicted = cm.wouldEvict(cam0DescNew);
+
+ // 3. Check opening the same camera twice will evict the older client
+ ASSERT_EQ(wouldBeEvicted.size(), 1u) << "Evicted list length must be 1";
+ ASSERT_EQ(wouldBeEvicted[0], cam0Desc) << "cam0 (old) must be evicted";
+}
+
diff --git a/services/camera/libcameraservice/utils/ClientManager.h b/services/camera/libcameraservice/utils/ClientManager.h
index 35d25bf..64be6c5 100644
--- a/services/camera/libcameraservice/utils/ClientManager.h
+++ b/services/camera/libcameraservice/utils/ClientManager.h
@@ -496,6 +496,20 @@
evictList.clear();
evictList.push_back(client);
return evictList;
+ } else if (conflicting && owner == curOwner) {
+ // Pre-existing conflicting client with the same client owner exists
+ // Open the same device twice -> most recent open wins
+ // Otherwise let the existing client wins to avoid behaviors difference
+ // due to how HAL advertising conflicting devices (which is hidden from
+ // application)
+ if (curKey == key) {
+ evictList.push_back(i);
+ totalCost -= curCost;
+ } else {
+ evictList.clear();
+ evictList.push_back(client);
+ return evictList;
+ }
} else if (conflicting || ((totalCost > mMaxCost && curCost > 0) &&
(curPriority >= priority) &&
!(highestPriorityOwner == owner && owner == curOwner))) {
diff --git a/services/camera/libcameraservice/utils/SessionConfigurationUtils.h b/services/camera/libcameraservice/utils/SessionConfigurationUtils.h
index fb519d9..cfb9f17 100644
--- a/services/camera/libcameraservice/utils/SessionConfigurationUtils.h
+++ b/services/camera/libcameraservice/utils/SessionConfigurationUtils.h
@@ -32,7 +32,7 @@
class SessionConfigurationUtils {
public:
// utility function to convert AIDL SessionConfiguration to HIDL
- // streamConfiguration. Also checks for sanity of SessionConfiguration and
+ // streamConfiguration. Also checks for validity of SessionConfiguration and
// returns a non-ok binder::Status if the passed in session configuration
// isn't valid.
static binder::Status
diff --git a/services/camera/libcameraservice/utils/TraceHFR.h b/services/camera/libcameraservice/utils/TraceHFR.h
new file mode 100644
index 0000000..3a1900f
--- /dev/null
+++ b/services/camera/libcameraservice/utils/TraceHFR.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_SERVERS_ENABLE_HFR_TRACES_H_
+#define ANDROID_SERVERS_ENABLE_HFR_TRACES_H_
+
+#include <utils/Trace.h>
+
+#ifdef HFR_ENABLE_TRACING
+#define ATRACE_HFR_CALL() ATRACE_CALL()
+#else
+#define ATRACE_HFR_CALL()
+#endif
+
+#endif
diff --git a/services/mediacodec/Android.bp b/services/mediacodec/Android.bp
index 4bf103c..c4efbaa 100644
--- a/services/mediacodec/Android.bp
+++ b/services/mediacodec/Android.bp
@@ -1,6 +1,7 @@
cc_binary {
name: "mediaswcodec",
vendor_available: true,
+ min_sdk_version: "29",
srcs: [
"main_swcodecservice.cpp",
@@ -32,8 +33,6 @@
init_rc: ["mediaswcodec.rc"],
- required: ["mediaswcodec.policy"],
-
cflags: [
"-Werror",
"-Wall",
diff --git a/services/mediametrics/AnalyticsState.h b/services/mediametrics/AnalyticsState.h
index b648947..09c0b4c 100644
--- a/services/mediametrics/AnalyticsState.h
+++ b/services/mediametrics/AnalyticsState.h
@@ -93,7 +93,7 @@
int32_t ll = lines;
if (ll > 0) {
- ss << "TransactionLog:\n";
+ ss << "TransactionLog: gc(" << mTransactionLog.getGarbageCollectionCount() << ")\n";
--ll;
}
if (ll > 0) {
@@ -102,7 +102,7 @@
ll -= l;
}
if (ll > 0) {
- ss << "TimeMachine:\n";
+ ss << "TimeMachine: gc(" << mTimeMachine.getGarbageCollectionCount() << ")\n";
--ll;
}
if (ll > 0) {
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
index 8e5bc79..f033d5c 100644
--- a/services/mediametrics/Android.bp
+++ b/services/mediametrics/Android.bp
@@ -1,8 +1,94 @@
// Media Statistics service
//
+tidy_errors = [
+ // https://clang.llvm.org/extra/clang-tidy/checks/list.html
+ // For many categories, the checks are too many to specify individually.
+ // Feel free to disable as needed - as warnings are generally ignored,
+ // we treat warnings as errors.
+ "android-*",
+ "bugprone-*",
+ "cert-*",
+ "clang-analyzer-security*",
+ "google-*",
+ "misc-*",
+ //"modernize-*", // explicitly list the modernize as they can be subjective.
+ "modernize-avoid-bind",
+ //"modernize-avoid-c-arrays", // std::array<> can be verbose
+ "modernize-concat-nested-namespaces",
+ //"modernize-deprecated-headers", // C headers still ok even if there is C++ equivalent.
+ "modernize-deprecated-ios-base-aliases",
+ "modernize-loop-convert",
+ "modernize-make-shared",
+ "modernize-make-unique",
+ "modernize-pass-by-value",
+ "modernize-raw-string-literal",
+ "modernize-redundant-void-arg",
+ "modernize-replace-auto-ptr",
+ "modernize-replace-random-shuffle",
+ "modernize-return-braced-init-list",
+ "modernize-shrink-to-fit",
+ "modernize-unary-static-assert",
+ "modernize-use-auto", // debatable - auto can obscure type
+ "modernize-use-bool-literals",
+ "modernize-use-default-member-init",
+ "modernize-use-emplace",
+ "modernize-use-equals-default",
+ "modernize-use-equals-delete",
+ "modernize-use-nodiscard",
+ "modernize-use-noexcept",
+ "modernize-use-nullptr",
+ "modernize-use-override",
+ //"modernize-use-trailing-return-type", // not necessarily more readable
+ "modernize-use-transparent-functors",
+ "modernize-use-uncaught-exceptions",
+ "modernize-use-using",
+ "performance-*",
+
+ // Remove some pedantic stylistic requirements.
+ "-google-readability-casting", // C++ casts not always necessary and may be verbose
+ "-google-readability-todo", // do not require TODO(info)
+]
+
+cc_defaults {
+ name: "mediametrics_flags_defaults",
+ // https://clang.llvm.org/docs/UsersManual.html#command-line-options
+ // https://clang.llvm.org/docs/DiagnosticsReference.html
+ cflags: [
+ "-Wall",
+ "-Wdeprecated",
+ "-Werror",
+ "-Werror=implicit-fallthrough",
+ "-Werror=sometimes-uninitialized",
+ "-Werror=conditional-uninitialized",
+ "-Wextra",
+ "-Wredundant-decls",
+ "-Wshadow",
+ "-Wstrict-aliasing",
+ "-fstrict-aliasing",
+ "-Wthread-safety",
+ //"-Wthread-safety-negative", // experimental - looks broken in R.
+ "-Wunreachable-code",
+ "-Wunreachable-code-break",
+ "-Wunreachable-code-return",
+ "-Wunused",
+ "-Wused-but-marked-unused",
+ ],
+ // https://clang.llvm.org/extra/clang-tidy/
+ tidy: true,
+ tidy_checks: tidy_errors,
+ tidy_checks_as_errors: tidy_errors,
+ tidy_flags: [
+ "-format-style='file'",
+ "--header-filter='frameworks/av/services/mediametrics/'",
+ ],
+}
+
cc_binary {
name: "mediametrics",
+ defaults: [
+ "mediametrics_flags_defaults",
+ ],
srcs: [
"main_mediametrics.cpp",
@@ -16,26 +102,26 @@
"libutils",
],
header_libs: [
+ "libaudioutils_headers",
"libmediametrics_headers",
],
init_rc: [
"mediametrics.rc",
],
-
- cflags: [
- "-Wall",
- "-Werror",
- "-Wextra",
- "-Wthread-safety",
- ],
}
cc_library_shared {
name: "libmediametricsservice",
+ defaults: [
+ "mediametrics_flags_defaults",
+ ],
srcs: [
"AudioAnalytics.cpp",
+ "AudioPowerUsage.cpp",
+ "AudioTypes.cpp",
+ "cleaner.cpp",
"iface_statsd.cpp",
"MediaMetricsService.cpp",
"statsd_audiopolicy.cpp",
@@ -47,6 +133,7 @@
"statsd_extractor.cpp",
"statsd_nuplayer.cpp",
"statsd_recorder.cpp",
+ "StringUtils.cpp"
],
proto: {
@@ -54,8 +141,11 @@
},
shared_libs: [
+ "libbase", // android logging
"libbinder",
+ "libcutils",
"liblog",
+ "libmedia_helper",
"libmediametrics",
"libmediautils",
"libmemunreachable",
@@ -71,11 +161,4 @@
include_dirs: [
"system/media/audio_utils/include",
],
-
- cflags: [
- "-Wall",
- "-Werror",
- "-Wextra",
- "-Wthread-safety",
- ],
}
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index fe3a34d..29801a4 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -16,20 +16,148 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioAnalytics"
+#include <android-base/logging.h>
#include <utils/Log.h>
#include "AudioAnalytics.h"
-#include "MediaMetricsService.h" // package info
+
#include <audio_utils/clock.h> // clock conversions
+#include <cutils/properties.h>
#include <statslog.h> // statsd
-// Enable for testing of delivery to statsd
-// #define STATSD
+#include "AudioTypes.h" // string to int conversions
+#include "MediaMetricsService.h" // package info
+#include "StringUtils.h"
+
+#define PROP_AUDIO_ANALYTICS_CLOUD_ENABLED "persist.audio.analytics.cloud.enabled"
namespace android::mediametrics {
-AudioAnalytics::AudioAnalytics()
+// Enable for testing of delivery to statsd. Caution if this is enabled, all protos MUST exist.
+#define STATSD_ENABLE
+
+#ifdef STATSD_ENABLE
+#define CONDITION(INT_VALUE) (INT_VALUE) // allow value
+#else
+#define CONDITION(INT_VALUE) (int(0)) // mask value since the proto may not be defined yet.
+#endif
+
+// Maximum length of a device name.
+static constexpr size_t STATSD_DEVICE_NAME_MAX_LENGTH = 32;
+
+// Transmit Enums to statsd in integer or strings (this must match the atoms.proto)
+static constexpr bool STATSD_USE_INT_FOR_ENUM = false;
+
+// derive types based on integer or strings.
+using short_enum_type_t = std::conditional_t<STATSD_USE_INT_FOR_ENUM, int32_t, std::string>;
+using long_enum_type_t = std::conditional_t<STATSD_USE_INT_FOR_ENUM, int64_t, std::string>;
+
+// Convert std::string to char *
+template <typename T>
+auto ENUM_EXTRACT(const T& x) {
+ if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
+ return x.c_str();
+ } else {
+ return x;
+ }
+}
+
+static constexpr const auto LOG_LEVEL = android::base::VERBOSE;
+
+static constexpr int PREVIOUS_STATE_EXPIRE_SEC = 60 * 60; // 1 hour.
+
+/*
+ * For logging purposes, we list all of the MediaMetrics atom fields,
+ * which can then be associated with consecutive arguments to the statsd write.
+ */
+
+static constexpr const char * const AudioRecordDeviceUsageFields[] = {
+ "mediametrics_audiorecorddeviceusage_reported", // proto number
+ "devices",
+ "device_names",
+ "device_time_nanos",
+ "encoding",
+ "frame_count",
+ "interval_count",
+ "sample_rate",
+ "flags",
+ "package_name",
+ "selected_device_id",
+ "caller",
+ "source",
+};
+
+static constexpr const char * const AudioThreadDeviceUsageFields[] = {
+ "mediametrics_audiothreaddeviceusage_reported",
+ "devices",
+ "device_names",
+ "device_time_nanos",
+ "encoding",
+ "frame_count",
+ "interval_count",
+ "sample_rate",
+ "flags",
+ "xruns",
+ "type",
+};
+
+static constexpr const char * const AudioTrackDeviceUsageFields[] = {
+ "mediametrics_audiotrackdeviceusage_reported",
+ "devices",
+ "device_names",
+ "device_time_nanos",
+ "encoding",
+ "frame_count",
+ "interval_count",
+ "sample_rate",
+ "flags",
+ "xruns",
+ "package_name",
+ "device_latency_millis",
+ "device_startup_millis",
+ "device_volume",
+ "selected_device_id",
+ "stream_type",
+ "usage",
+ "content_type",
+ "caller",
+ "traits",
+};
+
+static constexpr const char * const AudioDeviceConnectionFields[] = {
+ "mediametrics_audiodeviceconnection_reported",
+ "input_devices",
+ "output_devices",
+ "device_names",
+ "result",
+ "time_to_connect_millis",
+ "connection_count",
+};
+
+/**
+ * sendToStatsd is a helper method that sends the arguments to statsd
+ * and returns a pair { result, summary_string }.
+ */
+template <size_t N, typename ...Types>
+std::pair<int, std::string> sendToStatsd(const char * const (& fields)[N], Types ... args)
{
+ int result = 0;
+ std::stringstream ss;
+
+#ifdef STATSD_ENABLE
+ result = android::util::stats_write(args...);
+ ss << "result:" << result;
+#endif
+ ss << " { ";
+ stringutils::fieldPrint(ss, fields, args...);
+ ss << "}";
+ return { result, ss.str() };
+}
+
+AudioAnalytics::AudioAnalytics()
+ : mDeliverStatistics(property_get_bool(PROP_AUDIO_ANALYTICS_CLOUD_ENABLED, true))
+{
+ SetMinimumLogSeverity(android::base::DEBUG); // for LOG().
ALOGD("%s", __func__);
// Add action to save AnalyticsState if audioserver is restarted.
@@ -47,49 +175,28 @@
// to end of full expression.
mAnalyticsState->clear(); // TODO: filter the analytics state.
// Perhaps report this.
+
+ // Set up a timer to expire the previous audio state to save space.
+ // Use the transaction log size as a cookie to see if it is the
+ // same as before. A benign race is possible where a state is cleared early.
+ const size_t size = mPreviousAnalyticsState->transactionLog().size();
+ mTimedAction.postIn(
+ std::chrono::seconds(PREVIOUS_STATE_EXPIRE_SEC), [this, size](){
+ if (mPreviousAnalyticsState->transactionLog().size() == size) {
+ ALOGD("expiring previous audio state after %d seconds.",
+ PREVIOUS_STATE_EXPIRE_SEC);
+ mPreviousAnalyticsState->clear(); // removes data from the state.
+ }
+ });
}));
- // Check underruns
+ // Handle device use record statistics
mActions.addAction(
- AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT,
- std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_UNDERRUN),
+ AMEDIAMETRICS_KEY_PREFIX_AUDIO_RECORD "*." AMEDIAMETRICS_PROP_EVENT,
+ std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
std::make_shared<AnalyticsActions::Function>(
[this](const std::shared_ptr<const android::mediametrics::Item> &item){
- std::string threadId = item->getKey().substr(
- sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) - 1);
- std::string outputDevices;
- mAnalyticsState->timeMachine().get(
- item->getKey(), AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
- ALOGD("(key=%s) Thread underrun event detected on io handle:%s device:%s",
- item->getKey().c_str(), threadId.c_str(), outputDevices.c_str());
- if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
- // report this for Bluetooth
- }
- }));
-
- // Check latencies, playback and startup
- mActions.addAction(
- AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK "*." AMEDIAMETRICS_PROP_LATENCYMS,
- std::monostate{}, // accept any value
- std::make_shared<AnalyticsActions::Function>(
- [this](const std::shared_ptr<const android::mediametrics::Item> &item){
- double latencyMs{};
- double startupMs{};
- if (!item->get(AMEDIAMETRICS_PROP_LATENCYMS, &latencyMs)
- || !item->get(AMEDIAMETRICS_PROP_STARTUPMS, &startupMs)) return;
-
- std::string trackId = item->getKey().substr(
- sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK) - 1);
- std::string thread = getThreadFromTrack(item->getKey());
- std::string outputDevices;
- mAnalyticsState->timeMachine().get(
- thread, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
- ALOGD("(key=%s) Track latencyMs:%lf startupMs:%lf detected on port:%s device:%s",
- item->getKey().c_str(), latencyMs, startupMs,
- trackId.c_str(), outputDevices.c_str());
- if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
- // report this for Bluetooth
- }
+ mDeviceUse.endAudioIntervalGroup(item, DeviceUse::RECORD);
}));
// Handle device use thread statistics
@@ -98,7 +205,7 @@
std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
std::make_shared<AnalyticsActions::Function>(
[this](const std::shared_ptr<const android::mediametrics::Item> &item){
- mDeviceUse.endAudioIntervalGroup(item, false /* isTrack */);
+ mDeviceUse.endAudioIntervalGroup(item, DeviceUse::THREAD);
}));
// Handle device use track statistics
@@ -107,10 +214,11 @@
std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
std::make_shared<AnalyticsActions::Function>(
[this](const std::shared_ptr<const android::mediametrics::Item> &item){
- mDeviceUse.endAudioIntervalGroup(item, true /* isTrack */);
+ mDeviceUse.endAudioIntervalGroup(item, DeviceUse::TRACK);
}));
- // Handle device routing statistics
+
+ // Handle device connection statistics
// We track connections (not disconnections) for the time to connect.
// TODO: consider BT requests in their A2dp service
@@ -119,7 +227,7 @@
// AudioDeviceBroker.postA2dpActiveDeviceChange
mActions.addAction(
"audio.device.a2dp.state",
- std::string("connected"),
+ "connected",
std::make_shared<AnalyticsActions::Function>(
[this](const std::shared_ptr<const android::mediametrics::Item> &item){
mDeviceConnection.a2dpConnected(item);
@@ -132,6 +240,60 @@
[this](const std::shared_ptr<const android::mediametrics::Item> &item){
mDeviceConnection.createPatch(item);
}));
+
+ // Called from BT service
+ mActions.addAction(
+ AMEDIAMETRICS_KEY_PREFIX_AUDIO_DEVICE
+ "postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent"
+ "." AMEDIAMETRICS_PROP_STATE,
+ "connected",
+ std::make_shared<AnalyticsActions::Function>(
+ [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+ mDeviceConnection.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(item);
+ }));
+
+ // Handle power usage
+ mActions.addAction(
+ AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK "*." AMEDIAMETRICS_PROP_EVENT,
+ std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
+ std::make_shared<AnalyticsActions::Function>(
+ [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+ mAudioPowerUsage.checkTrackRecord(item, true /* isTrack */);
+ }));
+
+ mActions.addAction(
+ AMEDIAMETRICS_KEY_PREFIX_AUDIO_RECORD "*." AMEDIAMETRICS_PROP_EVENT,
+ std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
+ std::make_shared<AnalyticsActions::Function>(
+ [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+ mAudioPowerUsage.checkTrackRecord(item, false /* isTrack */);
+ }));
+
+ mActions.addAction(
+ AMEDIAMETRICS_KEY_AUDIO_FLINGER "." AMEDIAMETRICS_PROP_EVENT,
+ std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_SETMODE),
+ std::make_shared<AnalyticsActions::Function>(
+ [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+ // ALOGD("(key=%s) Audioflinger setMode", item->getKey().c_str());
+ mAudioPowerUsage.checkMode(item);
+ }));
+
+ mActions.addAction(
+ AMEDIAMETRICS_KEY_AUDIO_FLINGER "." AMEDIAMETRICS_PROP_EVENT,
+ std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOICEVOLUME),
+ std::make_shared<AnalyticsActions::Function>(
+ [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+ // ALOGD("(key=%s) Audioflinger setVoiceVolume", item->getKey().c_str());
+ mAudioPowerUsage.checkVoiceVolume(item);
+ }));
+
+ mActions.addAction(
+ AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT,
+ std::string("createAudioPatch"),
+ std::make_shared<AnalyticsActions::Function>(
+ [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+ mAudioPowerUsage.checkCreatePatch(item);
+ }));
}
AudioAnalytics::~AudioAnalytics()
@@ -173,6 +335,27 @@
ss << s;
ll -= l;
}
+
+ if (ll > 0) {
+ // Print the statsd atoms we sent out.
+ const std::string statsd = mStatsdLog.dumpToString(" " /* prefix */, ll - 1);
+ const size_t n = std::count(statsd.begin(), statsd.end(), '\n') + 1; // we control this.
+ if ((size_t)ll >= n) {
+ if (n == 1) {
+ ss << "Statsd atoms: empty or truncated\n";
+ } else {
+ ss << "Statsd atoms:\n" << statsd;
+ }
+ ll -= n;
+ }
+ }
+
+ if (ll > 0 && prefix == nullptr) {
+ auto [s, l] = mAudioPowerUsage.dump(ll);
+ ss << s;
+ ll -= l;
+ }
+
return { ss.str(), lines - ll };
}
@@ -199,11 +382,12 @@
// DeviceUse helper class.
void AudioAnalytics::DeviceUse::endAudioIntervalGroup(
- const std::shared_ptr<const android::mediametrics::Item> &item, bool isTrack) const {
+ const std::shared_ptr<const android::mediametrics::Item> &item, ItemType itemType) const {
const std::string& key = item->getKey();
const std::string id = key.substr(
- (isTrack ? sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK)
- : sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD))
+ (itemType == THREAD ? sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD)
+ : itemType == TRACK ? sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK)
+ : sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_RECORD))
- 1);
// deliver statistics
int64_t deviceTimeNs = 0;
@@ -215,41 +399,182 @@
int32_t frameCount = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_FRAMECOUNT, &frameCount);
+ std::string inputDevicePairs;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_INPUTDEVICES, &inputDevicePairs);
int32_t intervalCount = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_INTERVALCOUNT, &intervalCount);
- std::string outputDevices;
+ std::string outputDevicePairs;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
- key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
+ key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevicePairs);
int32_t sampleRate = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_SAMPLERATE, &sampleRate);
- int32_t underrun = 0;
+ std::string flags;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
- key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun);
+ key, AMEDIAMETRICS_PROP_FLAGS, &flags);
+
+ // We may have several devices.
+ // Accumulate the bit flags for input and output devices.
+ std::stringstream oss;
+ long_enum_type_t outputDeviceBits{};
+ { // compute outputDevices
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(outputDevicePairs);
+ for (const auto& [device, addr] : devaddrvec) {
+ if (oss.tellp() > 0) oss << "|"; // delimit devices with '|'.
+ oss << device;
+ outputDeviceBits += types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(device);
+ }
+ }
+ const std::string outputDevices = oss.str();
+
+ std::stringstream iss;
+ long_enum_type_t inputDeviceBits{};
+ { // compute inputDevices
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(inputDevicePairs);
+ for (const auto& [device, addr] : devaddrvec) {
+ if (iss.tellp() > 0) iss << "|"; // delimit devices with '|'.
+ iss << device;
+ inputDeviceBits += types::lookup<types::INPUT_DEVICE, long_enum_type_t>(device);
+ }
+ }
+ const std::string inputDevices = iss.str();
// Get connected device name if from bluetooth.
bool isBluetooth = false;
- std::string name;
+
+ std::string inputDeviceNames; // not filled currently.
+ std::string outputDeviceNames;
if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
isBluetooth = true;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
- "audio.device.bt_a2dp", AMEDIAMETRICS_PROP_NAME, &name);
+ "audio.device.bt_a2dp", AMEDIAMETRICS_PROP_NAME, &outputDeviceNames);
+ // Remove | if present
+ stringutils::replace(outputDeviceNames, "|", '?');
+ if (outputDeviceNames.size() > STATSD_DEVICE_NAME_MAX_LENGTH) {
+ outputDeviceNames.resize(STATSD_DEVICE_NAME_MAX_LENGTH); // truncate
+ }
}
- // We may have several devices. We only list the first device.
- // TODO: consider whether we should list all the devices separated by |
- std::string firstDevice = "unknown";
- auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(outputDevices);
- if (devaddrvec.size() != 0) {
- firstDevice = devaddrvec[0].first;
- // DO NOT show the address.
- }
-
- if (isTrack) {
+ switch (itemType) {
+ case RECORD: {
std::string callerName;
+ const bool clientCalled = mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_CALLERNAME, &callerName) == OK;
+
+ std::string packageName;
+ int64_t versionCode = 0;
+ int32_t uid = -1;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
- key, AMEDIAMETRICS_PROP_CALLERNAME, &callerName);
+ key, AMEDIAMETRICS_PROP_ALLOWUID, &uid);
+ if (uid != -1) {
+ std::tie(packageName, versionCode) =
+ MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid);
+ }
+
+ int32_t selectedDeviceId = 0;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_SELECTEDDEVICEID, &selectedDeviceId);
+ std::string source;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_SOURCE, &source);
+
+ const auto callerNameForStats =
+ types::lookup<types::CALLER_NAME, short_enum_type_t>(callerName);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats = types::lookup<types::INPUT_FLAG, short_enum_type_t>(flags);
+ const auto sourceForStats = types::lookup<types::SOURCE_TYPE, short_enum_type_t>(source);
+
+ LOG(LOG_LEVEL) << "key:" << key
+ << " id:" << id
+ << " inputDevices:" << inputDevices << "(" << inputDeviceBits
+ << ") inputDeviceNames:" << inputDeviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " flags:" << flags << "(" << flagsForStats
+ << ") packageName:" << packageName
+ << " selectedDeviceId:" << selectedDeviceId
+ << " callerName:" << callerName << "(" << callerNameForStats
+ << ") source:" << source << "(" << sourceForStats << ")";
+ if (clientCalled // only log if client app called AudioRecord.
+ && mAudioAnalytics.mDeliverStatistics) {
+ const auto [ result, str ] = sendToStatsd(AudioRecordDeviceUsageFields,
+ CONDITION(android::util::MEDIAMETRICS_AUDIORECORDDEVICEUSAGE_REPORTED)
+ , ENUM_EXTRACT(inputDeviceBits)
+ , inputDeviceNames.c_str()
+ , deviceTimeNs
+ , ENUM_EXTRACT(encodingForStats)
+ , frameCount
+ , intervalCount
+ , sampleRate
+ , ENUM_EXTRACT(flagsForStats)
+
+ , packageName.c_str()
+ , selectedDeviceId
+ , ENUM_EXTRACT(callerNameForStats)
+ , ENUM_EXTRACT(sourceForStats)
+ );
+ ALOGV("%s: statsd %s", __func__, str.c_str());
+ mAudioAnalytics.mStatsdLog.log("%s", str.c_str());
+ }
+ } break;
+ case THREAD: {
+ std::string type;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_TYPE, &type);
+ int32_t underrun = 0; // zero for record types
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun);
+
+ const bool isInput = types::isInputThreadType(type);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats =
+ (isInput ? types::lookup<types::INPUT_FLAG, short_enum_type_t>(flags)
+ : types::lookup<types::OUTPUT_FLAG, short_enum_type_t>(flags));
+ const auto typeForStats = types::lookup<types::THREAD_TYPE, short_enum_type_t>(type);
+
+ LOG(LOG_LEVEL) << "key:" << key
+ << " id:" << id
+ << " inputDevices:" << inputDevices << "(" << inputDeviceBits
+ << ") outputDevices:" << outputDevices << "(" << outputDeviceBits
+ << ") inputDeviceNames:" << inputDeviceNames
+ << " outputDeviceNames:" << outputDeviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " underrun:" << underrun
+ << " flags:" << flags << "(" << flagsForStats
+ << ") type:" << type << "(" << typeForStats
+ << ")";
+ if (mAudioAnalytics.mDeliverStatistics) {
+ const auto [ result, str ] = sendToStatsd(AudioThreadDeviceUsageFields,
+ CONDITION(android::util::MEDIAMETRICS_AUDIOTHREADDEVICEUSAGE_REPORTED)
+ , isInput ? ENUM_EXTRACT(inputDeviceBits) : ENUM_EXTRACT(outputDeviceBits)
+ , isInput ? inputDeviceNames.c_str() : outputDeviceNames.c_str()
+ , deviceTimeNs
+ , ENUM_EXTRACT(encodingForStats)
+ , frameCount
+ , intervalCount
+ , sampleRate
+ , ENUM_EXTRACT(flagsForStats)
+ , underrun
+ , ENUM_EXTRACT(typeForStats)
+ );
+ ALOGV("%s: statsd %s", __func__, str.c_str());
+ mAudioAnalytics.mStatsdLog.log("%s", str.c_str());
+ }
+ } break;
+ case TRACK: {
+ std::string callerName;
+ const bool clientCalled = mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_CALLERNAME, &callerName) == OK;
+
std::string contentType;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_CONTENTTYPE, &contentType);
@@ -280,82 +605,83 @@
int32_t selectedDeviceId = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_SELECTEDDEVICEID, &selectedDeviceId);
-
+ std::string streamType;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_STREAMTYPE, &streamType);
+ std::string traits;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_TRAITS, &traits);
+ int32_t underrun = 0;
+ mAudioAnalytics.mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun);
std::string usage;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_USAGE, &usage);
- ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s "
- "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
- "sampleRate:%d underrun:%d "
- "callerName:%s contentType:%s "
- "deviceLatencyMs:%lf deviceStartupMs:%lf deviceVolume:%lf"
- "packageName:%s playbackPitch:%lf playbackSpeed:%lf "
- "selectedDevceId:%d usage:%s",
- key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(),
- (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
- sampleRate, underrun,
- callerName.c_str(), contentType.c_str(),
- deviceLatencyMs, deviceStartupMs, deviceVolume,
- packageName.c_str(), playbackPitch, playbackSpeed,
- selectedDeviceId, usage.c_str());
-#ifdef STATSD
- if (mAudioAnalytics.mDeliverStatistics) {
- (void)android::util::stats_write(
- android::util::MEDIAMETRICS_AUDIOTRACKDEVICEUSAGE_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , firstDevice.c_str()
- , name.c_str()
+ const auto callerNameForStats =
+ types::lookup<types::CALLER_NAME, short_enum_type_t>(callerName);
+ const auto contentTypeForStats =
+ types::lookup<types::CONTENT_TYPE, short_enum_type_t>(contentType);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats = types::lookup<types::OUTPUT_FLAG, short_enum_type_t>(flags);
+ const auto streamTypeForStats =
+ types::lookup<types::STREAM_TYPE, short_enum_type_t>(streamType);
+ const auto traitsForStats =
+ types::lookup<types::TRACK_TRAITS, short_enum_type_t>(traits);
+ const auto usageForStats = types::lookup<types::USAGE, short_enum_type_t>(usage);
+
+ LOG(LOG_LEVEL) << "key:" << key
+ << " id:" << id
+ << " outputDevices:" << outputDevices << "(" << outputDeviceBits
+ << ") outputDeviceNames:" << outputDeviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " underrun:" << underrun
+ << " flags:" << flags << "(" << flagsForStats
+ << ") callerName:" << callerName << "(" << callerNameForStats
+ << ") contentType:" << contentType << "(" << contentTypeForStats
+ << ") deviceLatencyMs:" << deviceLatencyMs
+ << " deviceStartupMs:" << deviceStartupMs
+ << " deviceVolume:" << deviceVolume
+ << " packageName:" << packageName
+ << " playbackPitch:" << playbackPitch
+ << " playbackSpeed:" << playbackSpeed
+ << " selectedDeviceId:" << selectedDeviceId
+ << " streamType:" << streamType << "(" << streamTypeForStats
+ << ") traits:" << traits << "(" << traitsForStats
+ << ") usage:" << usage << "(" << usageForStats
+ << ")";
+ if (clientCalled // only log if client app called AudioTracks
+ && mAudioAnalytics.mDeliverStatistics) {
+ const auto [ result, str ] = sendToStatsd(AudioTrackDeviceUsageFields,
+ CONDITION(android::util::MEDIAMETRICS_AUDIOTRACKDEVICEUSAGE_REPORTED)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , outputDeviceNames.c_str()
, deviceTimeNs
- , encoding.c_str()
+ , ENUM_EXTRACT(encodingForStats)
, frameCount
, intervalCount
, sampleRate
+ , ENUM_EXTRACT(flagsForStats)
, underrun
-
, packageName.c_str()
, (float)deviceLatencyMs
, (float)deviceStartupMs
, (float)deviceVolume
, selectedDeviceId
- , usage.c_str()
- , contentType.c_str()
- , callerName.c_str()
+ , ENUM_EXTRACT(streamTypeForStats)
+ , ENUM_EXTRACT(usageForStats)
+ , ENUM_EXTRACT(contentTypeForStats)
+ , ENUM_EXTRACT(callerNameForStats)
+ , ENUM_EXTRACT(traitsForStats)
);
+ ALOGV("%s: statsd %s", __func__, str.c_str());
+ mAudioAnalytics.mStatsdLog.log("%s", str.c_str());
}
-#endif
- } else {
-
- std::string flags;
- mAudioAnalytics.mAnalyticsState->timeMachine().get(
- key, AMEDIAMETRICS_PROP_FLAGS, &flags);
-
- ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s "
- "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
- "sampleRate:%d underrun:%d "
- "flags:%s",
- key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(),
- (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
- sampleRate, underrun,
- flags.c_str());
-#ifdef STATSD
- if (mAudioAnalytics.mDeliverStatistics) {
- (void)android::util::stats_write(
- android::util::MEDIAMETRICS_AUDIOTHREADDEVICEUSAGE_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , firstDevice.c_str()
- , name.c_str()
- , deviceTimeNs
- , encoding.c_str()
- , frameCount
- , intervalCount
- , sampleRate
- , underrun
- );
- }
-#endif
+ } break;
}
// Report this as needed.
@@ -368,49 +694,148 @@
void AudioAnalytics::DeviceConnection::a2dpConnected(
const std::shared_ptr<const android::mediametrics::Item> &item) {
const std::string& key = item->getKey();
-
- const int64_t connectedAtNs = item->getTimestamp();
+ const int64_t atNs = item->getTimestamp();
{
std::lock_guard l(mLock);
- mA2dpTimeConnectedNs = connectedAtNs;
- ++mA2dpConnectedAttempts;
+ mA2dpConnectionServiceNs = atNs;
+ ++mA2dpConnectionServices;
+
+ if (mA2dpConnectionRequestNs == 0) {
+ mAudioAnalytics.mTimedAction.postIn(std::chrono::seconds(5), [this](){ expire(); });
+ }
+ // This sets the time we were connected. Now we look for the delta in the future.
}
std::string name;
item->get(AMEDIAMETRICS_PROP_NAME, &name);
- ALOGD("(key=%s) a2dp connected device:%s "
- "connectedAtNs:%lld",
- key.c_str(), name.c_str(),
- (long long)connectedAtNs);
- // Note - we need to be able to cancel a timed event
- mAudioAnalytics.mTimedAction.postIn(std::chrono::seconds(5), [this](){ expire(); });
- // This sets the time we were connected. Now we look for the delta in the future.
+ ALOGD("(key=%s) a2dp connected device:%s atNs:%lld",
+ key.c_str(), name.c_str(), (long long)atNs);
}
void AudioAnalytics::DeviceConnection::createPatch(
const std::shared_ptr<const android::mediametrics::Item> &item) {
std::lock_guard l(mLock);
- if (mA2dpTimeConnectedNs == 0) return; // ignore
+ if (mA2dpConnectionServiceNs == 0) return; // patch unrelated to us.
const std::string& key = item->getKey();
std::string outputDevices;
item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
- if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
+ if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH_A2DP") != std::string::npos) {
// TODO compare address
- const int64_t timeDiff = item->getTimestamp() - mA2dpTimeConnectedNs;
- ALOGD("(key=%s) A2DP device connection time: %lld", key.c_str(), (long long)timeDiff);
- mA2dpTimeConnectedNs = 0; // reset counter.
- ++mA2dpConnectedSuccesses;
+ int64_t timeDiffNs = item->getTimestamp();
+ if (mA2dpConnectionRequestNs == 0) {
+ ALOGD("%s: A2DP create patch didn't see a connection request", __func__);
+ timeDiffNs -= mA2dpConnectionServiceNs;
+ } else {
+ timeDiffNs -= mA2dpConnectionRequestNs;
+ }
+
+ mA2dpConnectionRequestNs = 0;
+ mA2dpConnectionServiceNs = 0;
+ ++mA2dpConnectionSuccesses;
+
+ const auto connectionTimeMs = float(timeDiffNs * 1e-6);
+
+ const auto outputDeviceBits = types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(
+ "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP");
+
+ LOG(LOG_LEVEL) << "key:" << key
+ << " A2DP SUCCESS"
+ << " outputDevices:" << outputDeviceBits
+ << " deviceName:" << mA2dpDeviceName
+ << " connectionTimeMs:" << connectionTimeMs;
+ if (mAudioAnalytics.mDeliverStatistics) {
+ const long_enum_type_t inputDeviceBits{};
+
+ const auto [ result, str ] = sendToStatsd(AudioDeviceConnectionFields,
+ CONDITION(android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED)
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , mA2dpDeviceName.c_str()
+ , types::DEVICE_CONNECTION_RESULT_SUCCESS
+ , connectionTimeMs
+ , /* connection_count */ 1
+ );
+ ALOGV("%s: statsd %s", __func__, str.c_str());
+ mAudioAnalytics.mStatsdLog.log("%s", str.c_str());
+ }
}
}
+// Called through AudioManager when the BT service wants to enable
+void AudioAnalytics::DeviceConnection::postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ const std::shared_ptr<const android::mediametrics::Item> &item) {
+ const int64_t atNs = item->getTimestamp();
+ const std::string& key = item->getKey();
+ std::string state;
+ item->get(AMEDIAMETRICS_PROP_STATE, &state);
+ if (state != "connected") return;
+
+ std::string name;
+ item->get(AMEDIAMETRICS_PROP_NAME, &name);
+ {
+ std::lock_guard l(mLock);
+ mA2dpConnectionRequestNs = atNs;
+ ++mA2dpConnectionRequests;
+ mA2dpDeviceName = name;
+ }
+ ALOGD("(key=%s) a2dp connection name:%s request atNs:%lld",
+ key.c_str(), name.c_str(), (long long)atNs);
+ // TODO: attempt to cancel a timed event, rather than let it expire.
+ mAudioAnalytics.mTimedAction.postIn(std::chrono::seconds(5), [this](){ expire(); });
+}
+
void AudioAnalytics::DeviceConnection::expire() {
std::lock_guard l(mLock);
- if (mA2dpTimeConnectedNs == 0) return; // ignore
+ if (mA2dpConnectionRequestNs == 0) return; // ignore (this was an internal connection).
- // An expiration may occur because there is no audio playing.
+ const long_enum_type_t inputDeviceBits{};
+ const auto outputDeviceBits = types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(
+ "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP");
+
+ if (mA2dpConnectionServiceNs == 0) {
+ ++mA2dpConnectionJavaServiceCancels; // service did not connect to A2DP
+
+ LOG(LOG_LEVEL) << "A2DP CANCEL"
+ << " outputDevices:" << outputDeviceBits
+ << " deviceName:" << mA2dpDeviceName;
+ if (mAudioAnalytics.mDeliverStatistics) {
+ const auto [ result, str ] = sendToStatsd(AudioDeviceConnectionFields,
+ CONDITION(android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED)
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , mA2dpDeviceName.c_str()
+ , types::DEVICE_CONNECTION_RESULT_JAVA_SERVICE_CANCEL
+ , /* connection_time_ms */ 0.f
+ , /* connection_count */ 1
+ );
+ ALOGV("%s: statsd %s", __func__, str.c_str());
+ mAudioAnalytics.mStatsdLog.log("%s", str.c_str());
+ }
+ return;
+ }
+
+ // AudioFlinger didn't play - an expiration may occur because there is no audio playing.
+ // Should we check elsewhere?
// TODO: disambiguate this case.
- ALOGD("A2DP device connection expired");
- ++mA2dpConnectedFailures; // this is not a true failure.
- mA2dpTimeConnectedNs = 0;
+ mA2dpConnectionRequestNs = 0;
+ mA2dpConnectionServiceNs = 0;
+ ++mA2dpConnectionUnknowns; // connection result unknown
+
+ LOG(LOG_LEVEL) << "A2DP UNKNOWN"
+ << " outputDevices:" << outputDeviceBits
+ << " deviceName:" << mA2dpDeviceName;
+ if (mAudioAnalytics.mDeliverStatistics) {
+ const auto [ result, str ] = sendToStatsd(AudioDeviceConnectionFields,
+ CONDITION(android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED)
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , mA2dpDeviceName.c_str()
+ , types::DEVICE_CONNECTION_RESULT_UNKNOWN
+ , /* connection_time_ms */ 0.f
+ , /* connection_count */ 1
+ );
+ ALOGV("%s: statsd %s", __func__, str.c_str());
+ mAudioAnalytics.mStatsdLog.log("%s", str.c_str());
+ }
}
-} // namespace android
+} // namespace android::mediametrics
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
index eb9c228..df097b1 100644
--- a/services/mediametrics/AudioAnalytics.h
+++ b/services/mediametrics/AudioAnalytics.h
@@ -17,8 +17,10 @@
#pragma once
#include <android-base/thread_annotations.h>
+#include <audio_utils/SimpleLog.h>
#include "AnalyticsActions.h"
#include "AnalyticsState.h"
+#include "AudioPowerUsage.h"
#include "TimedAction.h"
#include "Wrap.h"
@@ -26,6 +28,9 @@
class AudioAnalytics
{
+ // AudioAnalytics action / state helper classes
+ friend AudioPowerUsage;
+
public:
AudioAnalytics();
~AudioAnalytics();
@@ -72,10 +77,24 @@
// underlying state is locked.
mPreviousAnalyticsState->clear();
mAnalyticsState->clear();
+
+ // Clear power usage state.
+ mAudioPowerUsage.clear();
}
private:
+ /*
+ * AudioAnalytics class does not contain a monitor mutex.
+ * Instead, all of its variables are individually locked for access.
+ * Since data and items are generally added only (gc removes it), this is a reasonable
+ * compromise for availability/concurrency versus consistency.
+ *
+ * It is possible for concurrent threads to be reading and writing inside of AudioAnalytics.
+ * Reads based on a prior time (e.g. one second) in the past from the TimeMachine can be
+ * used to achieve better consistency if needed.
+ */
+
/**
* Checks for any pending actions for a particular item.
*
@@ -91,31 +110,40 @@
*/
std::string getThreadFromTrack(const std::string& track) const;
- const bool mDeliverStatistics __unused = true;
+ const bool mDeliverStatistics;
// 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.
-
+ // These wrap pointers always point to a valid state object.
SharedPtrWrap<AnalyticsState> mAnalyticsState;
SharedPtrWrap<AnalyticsState> mPreviousAnalyticsState;
TimedAction mTimedAction; // locked internally
+ SimpleLog mStatsdLog{16 /* log lines */}; // locked internally
+
// DeviceUse is a nested class which handles audio device usage accounting.
// We define this class at the end to ensure prior variables all properly constructed.
// TODO: Track / Thread interaction
// TODO: Consider statistics aggregation.
class DeviceUse {
public:
+ enum ItemType {
+ RECORD = 0,
+ THREAD = 1,
+ TRACK = 2,
+ };
+
explicit DeviceUse(AudioAnalytics &audioAnalytics) : mAudioAnalytics{audioAnalytics} {}
// Called every time an endAudioIntervalGroup message is received.
void endAudioIntervalGroup(
const std::shared_ptr<const android::mediametrics::Item> &item,
- bool isTrack) const;
+ ItemType itemType) const;
+
private:
AudioAnalytics &mAudioAnalytics;
} mDeviceUse{*this};
@@ -137,6 +165,10 @@
void createPatch(
const std::shared_ptr<const android::mediametrics::Item> &item);
+ // Called through AudioManager when the BT service wants to notify connection
+ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ const std::shared_ptr<const android::mediametrics::Item> &item);
+
// When the timer expires.
void expire();
@@ -144,11 +176,20 @@
AudioAnalytics &mAudioAnalytics;
mutable std::mutex mLock;
- int64_t mA2dpTimeConnectedNs GUARDED_BY(mLock) = 0;
- int32_t mA2dpConnectedAttempts GUARDED_BY(mLock) = 0;
- int32_t mA2dpConnectedSuccesses GUARDED_BY(mLock) = 0;
- int32_t mA2dpConnectedFailures GUARDED_BY(mLock) = 0;
+ std::string mA2dpDeviceName;
+ int64_t mA2dpConnectionRequestNs GUARDED_BY(mLock) = 0; // Time for BT service request.
+ int64_t mA2dpConnectionServiceNs GUARDED_BY(mLock) = 0; // Time audio service agrees.
+
+ int32_t mA2dpConnectionRequests GUARDED_BY(mLock) = 0;
+ int32_t mA2dpConnectionServices GUARDED_BY(mLock) = 0;
+
+ // See the statsd atoms.proto
+ int32_t mA2dpConnectionSuccesses GUARDED_BY(mLock) = 0;
+ int32_t mA2dpConnectionJavaServiceCancels GUARDED_BY(mLock) = 0;
+ int32_t mA2dpConnectionUnknowns GUARDED_BY(mLock) = 0;
} mDeviceConnection{*this};
+
+ AudioPowerUsage mAudioPowerUsage{this};
};
} // namespace android::mediametrics
diff --git a/services/mediametrics/AudioPowerUsage.cpp b/services/mediametrics/AudioPowerUsage.cpp
new file mode 100644
index 0000000..cca6b41
--- /dev/null
+++ b/services/mediametrics/AudioPowerUsage.cpp
@@ -0,0 +1,395 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "AudioPowerUsage"
+#include <utils/Log.h>
+
+#include "AudioAnalytics.h"
+#include "MediaMetricsService.h"
+#include "StringUtils.h"
+#include <map>
+#include <sstream>
+#include <string>
+#include <audio_utils/clock.h>
+#include <cutils/properties.h>
+#include <statslog.h>
+#include <sys/timerfd.h>
+#include <system/audio-base.h>
+
+// property to disable audio power use metrics feature, default is enabled
+#define PROP_AUDIO_METRICS_DISABLED "persist.media.audio_metrics.power_usage_disabled"
+#define AUDIO_METRICS_DISABLED_DEFAULT (false)
+
+// property to set how long to send audio power use metrics data to statsd, default is 24hrs
+#define PROP_AUDIO_METRICS_INTERVAL_HR "persist.media.audio_metrics.interval_hr"
+#define INTERVAL_HR_DEFAULT (24)
+
+// for Audio Power Usage Metrics
+#define AUDIO_POWER_USAGE_KEY_AUDIO_USAGE "audio.power.usage"
+
+#define AUDIO_POWER_USAGE_PROP_DEVICE "device" // int32
+#define AUDIO_POWER_USAGE_PROP_DURATION_NS "durationNs" // int64
+#define AUDIO_POWER_USAGE_PROP_TYPE "type" // int32
+#define AUDIO_POWER_USAGE_PROP_VOLUME "volume" // double
+
+namespace android::mediametrics {
+
+/* static */
+bool AudioPowerUsage::typeFromString(const std::string& type_string, int32_t& type) {
+ static std::map<std::string, int32_t> typeTable = {
+ { "AUDIO_STREAM_VOICE_CALL", VOIP_CALL_TYPE },
+ { "AUDIO_STREAM_SYSTEM", MEDIA_TYPE },
+ { "AUDIO_STREAM_RING", RINGTONE_NOTIFICATION_TYPE },
+ { "AUDIO_STREAM_MUSIC", MEDIA_TYPE },
+ { "AUDIO_STREAM_ALARM", ALARM_TYPE },
+ { "AUDIO_STREAM_NOTIFICATION", RINGTONE_NOTIFICATION_TYPE },
+
+ { "AUDIO_CONTENT_TYPE_SPEECH", VOIP_CALL_TYPE },
+ { "AUDIO_CONTENT_TYPE_MUSIC", MEDIA_TYPE },
+ { "AUDIO_CONTENT_TYPE_MOVIE", MEDIA_TYPE },
+ { "AUDIO_CONTENT_TYPE_SONIFICATION", RINGTONE_NOTIFICATION_TYPE },
+
+ { "AUDIO_USAGE_MEDIA", MEDIA_TYPE },
+ { "AUDIO_USAGE_VOICE_COMMUNICATION", VOIP_CALL_TYPE },
+ { "AUDIO_USAGE_ALARM", ALARM_TYPE },
+ { "AUDIO_USAGE_NOTIFICATION", RINGTONE_NOTIFICATION_TYPE },
+
+ { "AUDIO_SOURCE_CAMCORDER", CAMCORDER_TYPE },
+ { "AUDIO_SOURCE_VOICE_COMMUNICATION", VOIP_CALL_TYPE },
+ { "AUDIO_SOURCE_DEFAULT", RECORD_TYPE },
+ { "AUDIO_SOURCE_MIC", RECORD_TYPE },
+ { "AUDIO_SOURCE_UNPROCESSED", RECORD_TYPE },
+ { "AUDIO_SOURCE_VOICE_RECOGNITION", RECORD_TYPE },
+ };
+
+ auto it = typeTable.find(type_string);
+ if (it == typeTable.end()) {
+ type = UNKNOWN_TYPE;
+ return false;
+ }
+
+ type = it->second;
+ return true;
+}
+
+/* static */
+bool AudioPowerUsage::deviceFromString(const std::string& device_string, int32_t& device) {
+ static std::map<std::string, int32_t> deviceTable = {
+ { "AUDIO_DEVICE_OUT_EARPIECE", OUTPUT_EARPIECE },
+ { "AUDIO_DEVICE_OUT_SPEAKER_SAFE", OUTPUT_SPEAKER_SAFE },
+ { "AUDIO_DEVICE_OUT_SPEAKER", OUTPUT_SPEAKER },
+ { "AUDIO_DEVICE_OUT_WIRED_HEADSET", OUTPUT_WIRED_HEADSET },
+ { "AUDIO_DEVICE_OUT_WIRED_HEADPHONE", OUTPUT_WIRED_HEADSET },
+ { "AUDIO_DEVICE_OUT_BLUETOOTH_SCO", OUTPUT_BLUETOOTH_SCO },
+ { "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP", OUTPUT_BLUETOOTH_A2DP },
+ { "AUDIO_DEVICE_OUT_USB_HEADSET", OUTPUT_USB_HEADSET },
+ { "AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET", OUTPUT_BLUETOOTH_SCO },
+
+ { "AUDIO_DEVICE_IN_BUILTIN_MIC", INPUT_BUILTIN_MIC },
+ { "AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET", INPUT_BLUETOOTH_SCO },
+ { "AUDIO_DEVICE_IN_WIRED_HEADSET", INPUT_WIRED_HEADSET_MIC },
+ { "AUDIO_DEVICE_IN_USB_DEVICE", INPUT_USB_HEADSET_MIC },
+ { "AUDIO_DEVICE_IN_BACK_MIC", INPUT_BUILTIN_BACK_MIC },
+ };
+
+ auto it = deviceTable.find(device_string);
+ if (it == deviceTable.end()) {
+ device = 0;
+ return false;
+ }
+
+ device = it->second;
+ return true;
+}
+
+int32_t AudioPowerUsage::deviceFromStringPairs(const std::string& device_strings) {
+ int32_t deviceMask = 0;
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(device_strings);
+ for (const auto &[device, addr] : devaddrvec) {
+ int32_t combo_device = 0;
+ deviceFromString(device, combo_device);
+ deviceMask |= combo_device;
+ }
+ return deviceMask;
+}
+
+/* static */
+void AudioPowerUsage::sendItem(const std::shared_ptr<const mediametrics::Item>& item)
+{
+ int32_t type;
+ if (!item->getInt32(AUDIO_POWER_USAGE_PROP_TYPE, &type)) return;
+
+ int32_t device;
+ if (!item->getInt32(AUDIO_POWER_USAGE_PROP_DEVICE, &device)) return;
+
+ int64_t duration_ns;
+ if (!item->getInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, &duration_ns)) return;
+
+ double volume;
+ if (!item->getDouble(AUDIO_POWER_USAGE_PROP_VOLUME, &volume)) return;
+
+ (void)android::util::stats_write(android::util::AUDIO_POWER_USAGE_DATA_REPORTED,
+ device,
+ (int32_t)(duration_ns / NANOS_PER_SECOND),
+ (float)volume,
+ type);
+}
+
+bool AudioPowerUsage::saveAsItem_l(
+ int32_t device, int64_t duration_ns, int32_t type, double average_vol)
+{
+ ALOGV("%s: (%#x, %d, %lld, %f)", __func__, device, type,
+ (long long)duration_ns, average_vol );
+ if (duration_ns == 0) {
+ return true; // skip duration 0 usage
+ }
+ if (device == 0) {
+ return true; //ignore unknown device
+ }
+
+ for (const auto& item : mItems) {
+ int32_t item_type = 0, item_device = 0;
+ double item_volume = 0.;
+ int64_t item_duration_ns = 0;
+ item->getInt32(AUDIO_POWER_USAGE_PROP_DEVICE, &item_device);
+ item->getInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, &item_duration_ns);
+ item->getInt32(AUDIO_POWER_USAGE_PROP_TYPE, &item_type);
+ item->getDouble(AUDIO_POWER_USAGE_PROP_VOLUME, &item_volume);
+
+ // aggregate by device and type
+ if (item_device == device && item_type == type) {
+ int64_t final_duration_ns = item_duration_ns + duration_ns;
+ double final_volume = (device & INPUT_DEVICE_BIT) ? 1.0:
+ ((item_volume * item_duration_ns +
+ average_vol * duration_ns) / final_duration_ns);
+
+ item->setInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, final_duration_ns);
+ item->setDouble(AUDIO_POWER_USAGE_PROP_VOLUME, final_volume);
+ item->setTimestamp(systemTime(SYSTEM_TIME_REALTIME));
+
+ ALOGV("%s: update (%#x, %d, %lld, %f) --> (%lld, %f)", __func__,
+ device, type,
+ (long long)item_duration_ns, item_volume,
+ (long long)final_duration_ns, final_volume);
+
+ return true;
+ }
+ }
+
+ auto sitem = std::make_shared<mediametrics::Item>(AUDIO_POWER_USAGE_KEY_AUDIO_USAGE);
+ sitem->setTimestamp(systemTime(SYSTEM_TIME_REALTIME));
+ sitem->setInt32(AUDIO_POWER_USAGE_PROP_DEVICE, device);
+ sitem->setInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, duration_ns);
+ sitem->setInt32(AUDIO_POWER_USAGE_PROP_TYPE, type);
+ sitem->setDouble(AUDIO_POWER_USAGE_PROP_VOLUME, average_vol);
+ mItems.emplace_back(sitem);
+ return true;
+}
+
+void AudioPowerUsage::checkTrackRecord(
+ const std::shared_ptr<const mediametrics::Item>& item, bool isTrack)
+{
+ const std::string key = item->getKey();
+
+ int64_t deviceTimeNs = 0;
+ if (!item->getInt64(AMEDIAMETRICS_PROP_DEVICETIMENS, &deviceTimeNs)) {
+ return;
+ }
+ double deviceVolume = 1.;
+ if (isTrack && !item->getDouble(AMEDIAMETRICS_PROP_DEVICEVOLUME, &deviceVolume)) {
+ return;
+ }
+ int32_t type = 0;
+ std::string type_string;
+ if ((isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_STREAMTYPE, &type_string) == OK) ||
+ (!isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_SOURCE, &type_string) == OK)) {
+ typeFromString(type_string, type);
+
+ if (isTrack && type == UNKNOWN_TYPE &&
+ mAudioAnalytics->mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_USAGE, &type_string) == OK) {
+ typeFromString(type_string, type);
+ }
+ if (isTrack && type == UNKNOWN_TYPE &&
+ mAudioAnalytics->mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_CONTENTTYPE, &type_string) == OK) {
+ typeFromString(type_string, type);
+ }
+ ALOGV("type = %s => %d", type_string.c_str(), type);
+ }
+
+ int32_t device = 0;
+ std::string device_strings;
+ if ((isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &device_strings) == OK) ||
+ (!isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_INPUTDEVICES, &device_strings) == OK)) {
+
+ device = deviceFromStringPairs(device_strings);
+ ALOGV("device = %s => %d", device_strings.c_str(), device);
+ }
+ std::lock_guard l(mLock);
+ saveAsItem_l(device, deviceTimeNs, type, deviceVolume);
+}
+
+void AudioPowerUsage::checkMode(const std::shared_ptr<const mediametrics::Item>& item)
+{
+ std::string mode;
+ if (!item->getString(AMEDIAMETRICS_PROP_AUDIOMODE, &mode)) return;
+
+ std::lock_guard l(mLock);
+ if (mode == mMode) return; // no change in mode.
+
+ if (mMode == "AUDIO_MODE_IN_CALL") { // leaving call mode
+ const int64_t endCallNs = item->getTimestamp();
+ const int64_t durationNs = endCallNs - mDeviceTimeNs;
+ if (durationNs > 0) {
+ mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) +
+ mVoiceVolume * double(endCallNs - mVolumeTimeNs)) / durationNs;
+ saveAsItem_l(mPrimaryDevice, durationNs, VOICE_CALL_TYPE, mDeviceVolume);
+ }
+ } else if (mode == "AUDIO_MODE_IN_CALL") { // entering call mode
+ mStartCallNs = item->getTimestamp(); // advisory only
+
+ mDeviceVolume = 0;
+ mVolumeTimeNs = mStartCallNs;
+ mDeviceTimeNs = mStartCallNs;
+ }
+ ALOGV("%s: new mode:%s old mode:%s", __func__, mode.c_str(), mMode.c_str());
+ mMode = mode;
+}
+
+void AudioPowerUsage::checkVoiceVolume(const std::shared_ptr<const mediametrics::Item>& item)
+{
+ double voiceVolume = 0.;
+ if (!item->getDouble(AMEDIAMETRICS_PROP_VOICEVOLUME, &voiceVolume)) return;
+
+ std::lock_guard l(mLock);
+ if (voiceVolume == mVoiceVolume) return; // no change in volume
+
+ // we only track average device volume when we are in-call
+ if (mMode == "AUDIO_MODE_IN_CALL") {
+ const int64_t timeNs = item->getTimestamp();
+ const int64_t durationNs = timeNs - mDeviceTimeNs;
+ if (durationNs > 0) {
+ mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) +
+ mVoiceVolume * double(timeNs - mVolumeTimeNs)) / durationNs;
+ mVolumeTimeNs = timeNs;
+ }
+ }
+ ALOGV("%s: new voice volume:%lf old voice volume:%lf", __func__, voiceVolume, mVoiceVolume);
+ mVoiceVolume = voiceVolume;
+}
+
+void AudioPowerUsage::checkCreatePatch(const std::shared_ptr<const mediametrics::Item>& item)
+{
+ std::string outputDevices;
+ if (!item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices)) return;
+
+ const std::string& key = item->getKey();
+ std::string flags;
+ if (mAudioAnalytics->mAnalyticsState->timeMachine().get(
+ key, AMEDIAMETRICS_PROP_FLAGS, &flags) != OK) return;
+
+ if (flags.find("AUDIO_OUTPUT_FLAG_PRIMARY") == std::string::npos) return;
+
+ const int32_t device = deviceFromStringPairs(outputDevices);
+
+ std::lock_guard l(mLock);
+ if (mPrimaryDevice == device) return;
+
+ if (mMode == "AUDIO_MODE_IN_CALL") {
+ // Save statistics
+ const int64_t endDeviceNs = item->getTimestamp();
+ const int64_t durationNs = endDeviceNs - mDeviceTimeNs;
+ if (durationNs > 0) {
+ mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) +
+ mVoiceVolume * double(endDeviceNs - mVolumeTimeNs)) / durationNs;
+ saveAsItem_l(mPrimaryDevice, durationNs, VOICE_CALL_TYPE, mDeviceVolume);
+ }
+ // reset statistics
+ mDeviceVolume = 0;
+ mDeviceTimeNs = endDeviceNs;
+ mVolumeTimeNs = endDeviceNs;
+ }
+ ALOGV("%s: new primary device:%#x old primary device:%#x", __func__, device, mPrimaryDevice);
+ mPrimaryDevice = device;
+}
+
+AudioPowerUsage::AudioPowerUsage(AudioAnalytics *audioAnalytics)
+ : mAudioAnalytics(audioAnalytics)
+ , mDisabled(property_get_bool(PROP_AUDIO_METRICS_DISABLED, AUDIO_METRICS_DISABLED_DEFAULT))
+ , mIntervalHours(property_get_int32(PROP_AUDIO_METRICS_INTERVAL_HR, INTERVAL_HR_DEFAULT))
+{
+ ALOGD("%s", __func__);
+ ALOGI_IF(mDisabled, "AudioPowerUsage is disabled.");
+ collect(); // send items
+}
+
+AudioPowerUsage::~AudioPowerUsage()
+{
+ ALOGD("%s", __func__);
+}
+
+void AudioPowerUsage::clear()
+{
+ std::lock_guard _l(mLock);
+ mItems.clear();
+}
+
+void AudioPowerUsage::collect()
+{
+ std::lock_guard _l(mLock);
+ for (const auto &item : mItems) {
+ sendItem(item);
+ }
+ mItems.clear();
+ mAudioAnalytics->mTimedAction.postIn(
+ mIntervalHours <= 0 ? std::chrono::seconds(5) : std::chrono::hours(mIntervalHours),
+ [this](){ collect(); });
+}
+
+std::pair<std::string, int32_t> AudioPowerUsage::dump(int limit) const {
+ if (limit <= 2) {
+ return {{}, 0};
+ }
+ std::lock_guard _l(mLock);
+ if (mDisabled) {
+ return {"AudioPowerUsage disabled\n", 1};
+ }
+ if (mItems.empty()) {
+ return {"AudioPowerUsage empty\n", 1};
+ }
+
+ int slot = 1;
+ std::stringstream ss;
+ ss << "AudioPowerUsage:\n";
+ for (const auto &item : mItems) {
+ if (slot >= limit - 1) {
+ ss << "-- AudioPowerUsage may be truncated!\n";
+ ++slot;
+ break;
+ }
+ ss << " " << slot << " " << item->toString() << "\n";
+ slot++;
+ }
+ return { ss.str(), slot };
+}
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/AudioPowerUsage.h b/services/mediametrics/AudioPowerUsage.h
new file mode 100644
index 0000000..446ff4f
--- /dev/null
+++ b/services/mediametrics/AudioPowerUsage.h
@@ -0,0 +1,105 @@
+/*
+ * 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 <android-base/thread_annotations.h>
+#include <deque>
+#include <media/MediaMetricsItem.h>
+#include <mutex>
+#include <thread>
+
+namespace android::mediametrics {
+
+class AudioAnalytics;
+
+class AudioPowerUsage {
+public:
+ explicit AudioPowerUsage(AudioAnalytics *audioAnalytics);
+ ~AudioPowerUsage();
+
+ void checkTrackRecord(const std::shared_ptr<const mediametrics::Item>& item, bool isTrack);
+ void checkMode(const std::shared_ptr<const mediametrics::Item>& item);
+ void checkVoiceVolume(const std::shared_ptr<const mediametrics::Item>& item);
+ void checkCreatePatch(const std::shared_ptr<const mediametrics::Item>& item);
+ void clear();
+
+ /**
+ * 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.
+ *
+ * \param lines the maximum number of lines in the string returned.
+ */
+ std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const;
+
+ // align with message AudioUsageDataReported in frameworks/base/cmds/statsd/src/atoms.proto
+ enum AudioType {
+ UNKNOWN_TYPE = 0,
+ VOICE_CALL_TYPE = 1, // voice call
+ VOIP_CALL_TYPE = 2, // voip call, including uplink and downlink
+ MEDIA_TYPE = 3, // music and system sound
+ RINGTONE_NOTIFICATION_TYPE = 4, // ringtone and notification
+ ALARM_TYPE = 5, // alarm type
+ // record type
+ CAMCORDER_TYPE = 6, // camcorder
+ RECORD_TYPE = 7, // other recording
+ };
+
+ enum AudioDevice {
+ OUTPUT_EARPIECE = 0x1,
+ OUTPUT_SPEAKER = 0x2,
+ OUTPUT_WIRED_HEADSET = 0x4,
+ OUTPUT_USB_HEADSET = 0x8,
+ OUTPUT_BLUETOOTH_SCO = 0x10,
+ OUTPUT_BLUETOOTH_A2DP = 0x20,
+ OUTPUT_SPEAKER_SAFE = 0x40,
+
+ INPUT_DEVICE_BIT = 0x40000000,
+ INPUT_BUILTIN_MIC = INPUT_DEVICE_BIT | 0x1, // non-negative positive int32.
+ INPUT_BUILTIN_BACK_MIC = INPUT_DEVICE_BIT | 0x2,
+ INPUT_WIRED_HEADSET_MIC = INPUT_DEVICE_BIT | 0x4,
+ INPUT_USB_HEADSET_MIC = INPUT_DEVICE_BIT | 0x8,
+ INPUT_BLUETOOTH_SCO = INPUT_DEVICE_BIT | 0x10,
+ };
+
+ static bool typeFromString(const std::string& type_string, int32_t& type);
+ static bool deviceFromString(const std::string& device_string, int32_t& device);
+ static int32_t deviceFromStringPairs(const std::string& device_strings);
+private:
+ bool saveAsItem_l(int32_t device, int64_t duration, int32_t type, double average_vol)
+ REQUIRES(mLock);
+ static void sendItem(const std::shared_ptr<const mediametrics::Item>& item);
+ void collect();
+
+ AudioAnalytics * const mAudioAnalytics;
+ const bool mDisabled;
+ const int32_t mIntervalHours;
+
+ mutable std::mutex mLock;
+ std::deque<std::shared_ptr<mediametrics::Item>> mItems GUARDED_BY(mLock);
+
+ double mVoiceVolume GUARDED_BY(mLock) = 0.;
+ double mDeviceVolume GUARDED_BY(mLock) = 0.;
+ int64_t mStartCallNs GUARDED_BY(mLock) = 0; // advisory only
+ int64_t mVolumeTimeNs GUARDED_BY(mLock) = 0;
+ int64_t mDeviceTimeNs GUARDED_BY(mLock) = 0;
+ int32_t mPrimaryDevice GUARDED_BY(mLock) = OUTPUT_SPEAKER;
+ std::string mMode GUARDED_BY(mLock) {"AUDIO_MODE_NORMAL"};
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/AudioTypes.cpp b/services/mediametrics/AudioTypes.cpp
new file mode 100644
index 0000000..aa44447
--- /dev/null
+++ b/services/mediametrics/AudioTypes.cpp
@@ -0,0 +1,433 @@
+/*
+ * 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.
+ */
+
+#include "AudioTypes.h"
+#include "StringUtils.h"
+#include <media/TypeConverter.h> // requires libmedia_helper to get the Audio code.
+
+namespace android::mediametrics::types {
+
+const std::unordered_map<std::string, int32_t>& getAudioCallerNameMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones).
+ // This may be found in frameworks/av/media/libmediametrics/include/MediaMetricsConstants.h
+ static std::unordered_map<std::string, int32_t> map{
+ {"unknown", 0}, // callerName not set
+ {"aaudio", 1}, // Native AAudio
+ {"java", 2}, // Java API layer
+ {"media", 3}, // libmedia (mediaplayer)
+ {"opensles", 4}, // Open SLES
+ {"rtp", 5}, // RTP communication
+ {"soundpool", 6}, // SoundPool
+ {"tonegenerator", 7}, // dial tones
+ // R values above.
+ };
+ return map;
+}
+
+// A map in case we need to return a flag for input devices.
+// This is 64 bits (and hence not the same as audio_device_t) because we need extra
+// bits to represent new devices.
+// NOT USED FOR R. We do not use int64 flags.
+// This can be out of date for now, as it is unused even for string validation
+// (instead TypeConverter<InputDeviceTraits> is used).
+const std::unordered_map<std::string, int64_t>& getAudioDeviceInMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones). This does NOT match audio_device_t.
+ static std::unordered_map<std::string, int64_t> map{
+ {"AUDIO_DEVICE_IN_COMMUNICATION", 1LL << 0},
+ {"AUDIO_DEVICE_IN_AMBIENT", 1LL << 1},
+ {"AUDIO_DEVICE_IN_BUILTIN_MIC", 1LL << 2},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET", 1LL << 3},
+ {"AUDIO_DEVICE_IN_WIRED_HEADSET", 1LL << 4},
+ {"AUDIO_DEVICE_IN_AUX_DIGITAL", 1LL << 5},
+ {"AUDIO_DEVICE_IN_HDMI", 1LL << 5}, // HDMI == AUX_DIGITAL (6 reserved)
+ {"AUDIO_DEVICE_IN_VOICE_CALL", 1LL << 7},
+ {"AUDIO_DEVICE_IN_TELEPHONY_RX", 1LL << 7}, // TELEPHONY_RX == VOICE_CALL (8 reserved)
+ {"AUDIO_DEVICE_IN_BACK_MIC", 1LL << 9},
+ {"AUDIO_DEVICE_IN_REMOTE_SUBMIX", 1LL << 10},
+ {"AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET", 1LL << 11},
+ {"AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET", 1LL << 12},
+ {"AUDIO_DEVICE_IN_USB_ACCESSORY", 1LL << 13},
+ {"AUDIO_DEVICE_IN_USB_DEVICE", 1LL << 14},
+ {"AUDIO_DEVICE_IN_FM_TUNER", 1LL << 15},
+ {"AUDIO_DEVICE_IN_TV_TUNER", 1LL << 16},
+ {"AUDIO_DEVICE_IN_LINE", 1LL << 17},
+ {"AUDIO_DEVICE_IN_SPDIF", 1LL << 18},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_A2DP", 1LL << 19},
+ {"AUDIO_DEVICE_IN_LOOPBACK", 1LL << 20},
+ {"AUDIO_DEVICE_IN_IP", 1LL << 21},
+ {"AUDIO_DEVICE_IN_BUS", 1LL << 22},
+ {"AUDIO_DEVICE_IN_PROXY", 1LL << 23},
+ {"AUDIO_DEVICE_IN_USB_HEADSET", 1LL << 24},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_BLE", 1LL << 25},
+ {"AUDIO_DEVICE_IN_HDMI_ARC", 1LL << 26},
+ {"AUDIO_DEVICE_IN_ECHO_REFERENCE", 1LL << 27},
+ {"AUDIO_DEVICE_IN_DEFAULT", 1LL << 28},
+ // R values above.
+ };
+ return map;
+}
+
+// A map in case we need to return a flag for output devices.
+// This is 64 bits (and hence not the same as audio_device_t) because we need extra
+// bits to represent new devices.
+// NOT USED FOR R. We do not use int64 flags.
+// This can be out of date for now, as it is unused even for string validation
+// (instead TypeConverter<OutputDeviceTraits> is used).
+const std::unordered_map<std::string, int64_t>& getAudioDeviceOutMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones). This does NOT match audio_device_t.
+ static std::unordered_map<std::string, int64_t> map{
+ {"AUDIO_DEVICE_OUT_EARPIECE", 1LL << 0},
+ {"AUDIO_DEVICE_OUT_SPEAKER", 1LL << 1},
+ {"AUDIO_DEVICE_OUT_WIRED_HEADSET", 1LL << 2},
+ {"AUDIO_DEVICE_OUT_WIRED_HEADPHONE", 1LL << 3},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO", 1LL << 4},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET", 1LL << 5},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT", 1LL << 6},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP", 1LL << 7},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES", 1LL << 8},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER", 1LL << 9},
+ {"AUDIO_DEVICE_OUT_AUX_DIGITAL", 1LL << 10},
+ {"AUDIO_DEVICE_OUT_HDMI", 1LL << 10}, // HDMI == AUX_DIGITAL (11 reserved)
+ {"AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET", 1LL << 12},
+ {"AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET", 1LL << 13},
+ {"AUDIO_DEVICE_OUT_USB_ACCESSORY", 1LL << 14},
+ {"AUDIO_DEVICE_OUT_USB_DEVICE", 1LL << 15},
+ {"AUDIO_DEVICE_OUT_REMOTE_SUBMIX", 1LL << 16},
+ {"AUDIO_DEVICE_OUT_TELEPHONY_TX", 1LL << 17},
+ {"AUDIO_DEVICE_OUT_LINE", 1LL << 18},
+ {"AUDIO_DEVICE_OUT_HDMI_ARC", 1LL << 19},
+ {"AUDIO_DEVICE_OUT_SPDIF", 1LL << 20},
+ {"AUDIO_DEVICE_OUT_FM", 1LL << 21},
+ {"AUDIO_DEVICE_OUT_AUX_LINE", 1LL << 22},
+ {"AUDIO_DEVICE_OUT_SPEAKER_SAFE", 1LL << 23},
+ {"AUDIO_DEVICE_OUT_IP", 1LL << 24},
+ {"AUDIO_DEVICE_OUT_BUS", 1LL << 25},
+ {"AUDIO_DEVICE_OUT_PROXY", 1LL << 26},
+ {"AUDIO_DEVICE_OUT_USB_HEADSET", 1LL << 27},
+ {"AUDIO_DEVICE_OUT_HEARING_AID", 1LL << 28},
+ {"AUDIO_DEVICE_OUT_ECHO_CANCELLER", 1LL << 29},
+ {"AUDIO_DEVICE_OUT_DEFAULT", 1LL << 30},
+ // R values above.
+ };
+ return map;
+}
+
+const std::unordered_map<std::string, int32_t>& getAudioThreadTypeMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones).
+ // This may be found in frameworks/av/services/audioflinger/Threads.h
+ static std::unordered_map<std::string, int32_t> map{
+ // UNKNOWN is -1
+ {"MIXER", 0}, // Thread class is MixerThread
+ {"DIRECT", 1}, // Thread class is DirectOutputThread
+ {"DUPLICATING", 2}, // Thread class is DuplicatingThread
+ {"RECORD", 3}, // Thread class is RecordThread
+ {"OFFLOAD", 4}, // Thread class is OffloadThread
+ {"MMAP_PLAYBACK", 5}, // Thread class for MMAP playback stream
+ {"MMAP_CAPTURE", 6}, // Thread class for MMAP capture stream
+ // R values above.
+ };
+ return map;
+}
+
+const std::unordered_map<std::string, int32_t>& getAudioTrackTraitsMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones).
+ static std::unordered_map<std::string, int32_t> map{
+ {"static", (1 << 0)}, // A static track
+ // R values above.
+ };
+ return map;
+}
+
+// Helper: Create the corresponding int32 from string flags split with '|'.
+template <typename Traits>
+int32_t int32FromFlags(const std::string &flags)
+{
+ const auto result = stringutils::split(flags, "|");
+ int32_t intFlags = 0;
+ for (const auto& flag : result) {
+ typename Traits::Type value;
+ if (!TypeConverter<Traits>::fromString(flag, value)) {
+ break;
+ }
+ intFlags |= value;
+ }
+ return intFlags;
+}
+
+template <typename Traits>
+std::string stringFromFlags(const std::string &flags, size_t len)
+{
+ const auto result = stringutils::split(flags, "|");
+ std::string sFlags;
+ for (const auto& flag : result) {
+ typename Traits::Type value;
+ if (!TypeConverter<Traits>::fromString(flag, value)) {
+ break;
+ }
+ if (len >= flag.size()) continue;
+ if (!sFlags.empty()) sFlags += "|";
+ sFlags += flag.c_str() + len;
+ }
+ return sFlags;
+}
+
+template <typename M>
+std::string validateStringFromMap(const std::string &str, const M& map)
+{
+ if (str.empty()) return {};
+
+ const auto result = stringutils::split(str, "|");
+ std::stringstream ss;
+ for (const auto &s : result) {
+ if (map.count(s) > 0) {
+ if (ss.tellp() > 0) ss << "|";
+ ss << s;
+ }
+ }
+ return ss.str();
+}
+
+template <typename M>
+typename M::mapped_type flagsFromMap(const std::string &str, const M& map)
+{
+ if (str.empty()) return {};
+
+ const auto result = stringutils::split(str, "|");
+ typename M::mapped_type value{};
+ for (const auto &s : result) {
+ auto it = map.find(s);
+ if (it == map.end()) continue;
+ value |= it->second;
+ }
+ return value;
+}
+
+template <>
+int32_t lookup<CONTENT_TYPE>(const std::string &contentType)
+{
+ AudioContentTraits::Type value;
+ if (!TypeConverter<AudioContentTraits>::fromString(contentType, value)) {
+ value = AUDIO_CONTENT_TYPE_UNKNOWN;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<CONTENT_TYPE>(const std::string &contentType)
+{
+ AudioContentTraits::Type value;
+ if (!TypeConverter<AudioContentTraits>::fromString(contentType, value)) {
+ return "";
+ }
+ return contentType.c_str() + sizeof("AUDIO_CONTENT_TYPE");
+}
+
+template <>
+int32_t lookup<ENCODING>(const std::string &encoding)
+{
+ FormatTraits::Type value;
+ if (!TypeConverter<FormatTraits>::fromString(encoding, value)) {
+ value = AUDIO_FORMAT_INVALID;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<ENCODING>(const std::string &encoding)
+{
+ FormatTraits::Type value;
+ if (!TypeConverter<FormatTraits>::fromString(encoding, value)) {
+ return "";
+ }
+ return encoding.c_str() + sizeof("AUDIO_FORMAT");
+}
+
+template <>
+int32_t lookup<INPUT_FLAG>(const std::string &inputFlag)
+{
+ return int32FromFlags<InputFlagTraits>(inputFlag);
+}
+
+template <>
+std::string lookup<INPUT_FLAG>(const std::string &inputFlag)
+{
+ return stringFromFlags<InputFlagTraits>(inputFlag, sizeof("AUDIO_INPUT_FLAG"));
+}
+
+template <>
+int32_t lookup<OUTPUT_FLAG>(const std::string &outputFlag)
+{
+ return int32FromFlags<OutputFlagTraits>(outputFlag);
+}
+
+template <>
+std::string lookup<OUTPUT_FLAG>(const std::string &outputFlag)
+{
+ return stringFromFlags<OutputFlagTraits>(outputFlag, sizeof("AUDIO_OUTPUT_FLAG"));
+}
+
+template <>
+int32_t lookup<SOURCE_TYPE>(const std::string &sourceType)
+{
+ SourceTraits::Type value;
+ if (!TypeConverter<SourceTraits>::fromString(sourceType, value)) {
+ value = AUDIO_SOURCE_DEFAULT;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<SOURCE_TYPE>(const std::string &sourceType)
+{
+ SourceTraits::Type value;
+ if (!TypeConverter<SourceTraits>::fromString(sourceType, value)) {
+ return "";
+ }
+ return sourceType.c_str() + sizeof("AUDIO_SOURCE");
+}
+
+template <>
+int32_t lookup<STREAM_TYPE>(const std::string &streamType)
+{
+ StreamTraits::Type value;
+ if (!TypeConverter<StreamTraits>::fromString(streamType, value)) {
+ value = AUDIO_STREAM_DEFAULT;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<STREAM_TYPE>(const std::string &streamType)
+{
+ StreamTraits::Type value;
+ if (!TypeConverter<StreamTraits>::fromString(streamType, value)) {
+ return "";
+ }
+ return streamType.c_str() + sizeof("AUDIO_STREAM");
+}
+
+template <>
+int32_t lookup<USAGE>(const std::string &usage)
+{
+ UsageTraits::Type value;
+ if (!TypeConverter<UsageTraits>::fromString(usage, value)) {
+ value = AUDIO_USAGE_UNKNOWN;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<USAGE>(const std::string &usage)
+{
+ UsageTraits::Type value;
+ if (!TypeConverter<UsageTraits>::fromString(usage, value)) {
+ return "";
+ }
+ return usage.c_str() + sizeof("AUDIO_USAGE");
+}
+
+template <>
+int64_t lookup<INPUT_DEVICE>(const std::string &inputDevice)
+{
+ // NOT USED FOR R.
+ // Returns a set of bits, each one representing a device in inputDevice.
+ // This is a 64 bit integer, not the same as audio_device_t.
+ return flagsFromMap(inputDevice, getAudioDeviceInMap());
+}
+
+template <>
+std::string lookup<INPUT_DEVICE>(const std::string &inputDevice)
+{
+ return stringFromFlags<InputDeviceTraits>(inputDevice, sizeof("AUDIO_DEVICE_IN"));
+}
+
+template <>
+int64_t lookup<OUTPUT_DEVICE>(const std::string &outputDevice)
+{
+ // NOT USED FOR R.
+ // Returns a set of bits, each one representing a device in outputDevice.
+ // This is a 64 bit integer, not the same as audio_device_t.
+ return flagsFromMap(outputDevice, getAudioDeviceOutMap());
+}
+
+template <>
+std::string lookup<OUTPUT_DEVICE>(const std::string &outputDevice)
+{
+ return stringFromFlags<OutputDeviceTraits>(outputDevice, sizeof("AUDIO_DEVICE_OUT"));
+}
+
+template <>
+int32_t lookup<CALLER_NAME>(const std::string &callerName)
+{
+ auto& map = getAudioCallerNameMap();
+ auto it = map.find(callerName);
+ if (it == map.end()) {
+ return 0; // return unknown
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<CALLER_NAME>(const std::string &callerName)
+{
+ auto& map = getAudioCallerNameMap();
+ auto it = map.find(callerName);
+ if (it == map.end()) {
+ return "";
+ }
+ return callerName;
+}
+
+template <>
+int32_t lookup<THREAD_TYPE>(const std::string &threadType)
+{
+ auto& map = getAudioThreadTypeMap();
+ auto it = map.find(threadType);
+ if (it == map.end()) {
+ return -1; // note this as an illegal thread value as we don't have unknown here.
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<THREAD_TYPE>(const std::string &threadType)
+{
+ auto& map = getAudioThreadTypeMap();
+ auto it = map.find(threadType);
+ if (it == map.end()) {
+ return "";
+ }
+ return threadType;
+}
+
+bool isInputThreadType(const std::string &threadType)
+{
+ return threadType == "RECORD" || threadType == "MMAP_CAPTURE";
+}
+
+template <>
+std::string lookup<TRACK_TRAITS>(const std::string &traits)
+{
+ return validateStringFromMap(traits, getAudioTrackTraitsMap());
+}
+
+template <>
+int32_t lookup<TRACK_TRAITS>(const std::string &traits)
+{
+ return flagsFromMap(traits, getAudioTrackTraitsMap());
+}
+
+} // namespace android::mediametrics::types
diff --git a/services/mediametrics/AudioTypes.h b/services/mediametrics/AudioTypes.h
new file mode 100644
index 0000000..e1deeb1
--- /dev/null
+++ b/services/mediametrics/AudioTypes.h
@@ -0,0 +1,77 @@
+/*
+ * 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 <string>
+#include <unordered_map>
+
+namespace android::mediametrics::types {
+
+// Helper methods that map mediametrics logged strings to integer codes.
+// In R we do not use the integer codes, but rather we can use these maps
+// to validate correct strings.
+const std::unordered_map<std::string, int32_t>& getAudioCallerNameMap();
+const std::unordered_map<std::string, int64_t>& getAudioDeviceInMap();
+const std::unordered_map<std::string, int64_t>& getAudioDeviceOutMap();
+const std::unordered_map<std::string, int32_t>& getAudioThreadTypeMap();
+const std::unordered_map<std::string, int32_t>& getAudioTrackTraitsMap();
+
+// Enumeration for the device connection results.
+enum DeviceConnectionResult : int32_t {
+ DEVICE_CONNECTION_RESULT_SUCCESS = 0, // Audio delivered
+ DEVICE_CONNECTION_RESULT_UNKNOWN = 1, // Success is unknown.
+ DEVICE_CONNECTION_RESULT_JAVA_SERVICE_CANCEL = 2, // Canceled in Java service
+ // Do not modify the constants above after R. Adding new constants is fine.
+};
+
+// Enumeration for all the string translations to integers (generally int32_t) unless noted.
+enum AudioEnumCategory {
+ CALLER_NAME,
+ CONTENT_TYPE,
+ ENCODING,
+ INPUT_DEVICE, // int64_t
+ INPUT_FLAG,
+ OUTPUT_DEVICE, // int64_t
+ OUTPUT_FLAG,
+ SOURCE_TYPE,
+ STREAM_TYPE,
+ THREAD_TYPE,
+ TRACK_TRAITS,
+ USAGE,
+};
+
+// Convert a string (or arbitrary S) from an AudioEnumCategory to a particular type.
+// This is used to convert log std::strings back to the original type (int32_t or int64_t).
+//
+// For a string, generally there is a prefix "AUDIO_INPUT_FLAG" or some such that could
+// actually indicate the category so the AudioEnumCategory could be superfluous, but
+// we use it to find the proper default value in case of an unknown string.
+//
+// lookup<ENCODING, int32_t>("AUDIO_FORMAT_PCM_16_BIT") -> 1
+//
+template <AudioEnumCategory C, typename T, typename S>
+T lookup(const S &str);
+
+// Helper: Allow using a const char * in lieu of std::string.
+template <AudioEnumCategory C, typename T>
+T lookup(const char *str) {
+ return lookup<C, T, std::string>(str);
+}
+
+bool isInputThreadType(const std::string &threadType);
+
+} // namespace android::mediametrics::types
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index 3b3dc3e..48e766e 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -32,7 +32,8 @@
namespace android {
-using namespace mediametrics;
+using mediametrics::Item;
+using mediametrics::startsWith;
// individual records kept in memory: age or count
// age: <= 28 hours (1 1/6 days)
@@ -63,7 +64,7 @@
bool MediaMetricsService::useUidForPackage(
const std::string& package, const std::string& installer)
{
- if (strchr(package.c_str(), '.') == NULL) {
+ if (strchr(package.c_str(), '.') == nullptr) {
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
@@ -94,58 +95,6 @@
}
}
-/* static */
-std::string MediaMetricsService::tokenizer(std::string::const_iterator& it,
- const std::string::const_iterator& end, const char *reserved) {
- // consume leading white space
- for (; it != end && std::isspace(*it); ++it);
- if (it == end) return {};
-
- auto start = it;
- // parse until we hit a reserved keyword or space
- if (strchr(reserved, *it)) return {start, ++it};
- for (;;) {
- ++it;
- if (it == end || std::isspace(*it) || strchr(reserved, *it)) return {start, it};
- }
-}
-
-/* static */
-std::vector<std::pair<std::string, std::string>>
-MediaMetricsService::getDeviceAddressPairs(const std::string& devices) {
- std::vector<std::pair<std::string, std::string>> result;
-
- // Currently, the device format is EXACTLY
- // (device1, addr1)|(device2, addr2)|...
-
- static constexpr char delim[] = "()|,";
- for (auto it = devices.begin(); ; ) {
- auto token = tokenizer(it, devices.end(), delim);
- if (token != "(") return result;
-
- auto device = tokenizer(it, devices.end(), delim);
- if (device.empty() || !std::isalnum(device[0])) return result;
-
- token = tokenizer(it, devices.end(), delim);
- if (token != ",") return result;
-
- // special handling here for empty addresses
- auto address = tokenizer(it, devices.end(), delim);
- if (address.empty() || !std::isalnum(device[0])) return result;
- if (address == ")") { // no address, just the ")"
- address.clear();
- } else {
- token = tokenizer(it, devices.end(), delim);
- if (token != ")") return result;
- }
-
- result.emplace_back(std::move(device), std::move(address));
-
- token = tokenizer(it, devices.end(), delim);
- if (token != "|") return result; // this includes end of string detection
- }
-}
-
MediaMetricsService::MediaMetricsService()
: mMaxRecords(kMaxRecords),
mMaxRecordAgeNs(kMaxRecordAgeNs),
@@ -203,9 +152,9 @@
// Overwrite package name and version if the caller was untrusted or empty
if (!isTrusted || item->getPkgName().empty()) {
- const uid_t uid = item->getUid();
+ const uid_t uidItem = item->getUid();
const auto [ pkgName, version ] =
- MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid);
+ MediaMetricsService::getSanitizedPackageNameAndVersionCode(uidItem);
item->setPkgName(pkgName);
item->setPkgVersionCode(version);
}
@@ -236,7 +185,7 @@
}
if (!isTrusted || item->getTimestamp() == 0) {
- // Westworld logs two times for events: ElapsedRealTimeNs (BOOTTIME) and
+ // Statsd logs two times for events: ElapsedRealTimeNs (BOOTTIME) and
// WallClockTimeNs (REALTIME), but currently logs REALTIME to cloud.
//
// For consistency and correlation with other logging mechanisms
@@ -320,7 +269,7 @@
String8 value(args[i]);
char *endp;
const char *p = value.string();
- long long sec = strtoll(p, &endp, 10);
+ const auto sec = (int64_t)strtoll(p, &endp, 10);
if (endp == p || *endp != '\0' || sec == 0) {
sinceNs = 0;
} else if (sec < 0) {
diff --git a/services/mediametrics/MediaMetricsService.h b/services/mediametrics/MediaMetricsService.h
index b8eb267..792b7f0 100644
--- a/services/mediametrics/MediaMetricsService.h
+++ b/services/mediametrics/MediaMetricsService.h
@@ -65,7 +65,7 @@
static nsecs_t roundTime(nsecs_t timeNs);
/**
- * Returns true if we should use uid for package name when uploading to WestWorld.
+ * Returns true if we should use uid for package name when uploading to statsd.
*/
static bool useUidForPackage(const std::string& package, const std::string& installer);
@@ -77,20 +77,6 @@
*/
static std::pair<std::string, int64_t> getSanitizedPackageNameAndVersionCode(uid_t uid);
- /**
- * Return string tokens from iterator, separated by spaces and reserved chars.
- */
- static std::string tokenizer(std::string::const_iterator& it,
- const std::string::const_iterator& end, const char *reserved);
-
- /**
- * Parse the devices string and return a vector of device address pairs.
- *
- * A failure to parse returns early with the contents that were able to be parsed.
- */
- static std::vector<std::pair<std::string, std::string>>
- getDeviceAddressPairs(const std::string &devices);
-
protected:
// Internal call where release is true if ownership of item is transferred
diff --git a/services/mediametrics/StringUtils.cpp b/services/mediametrics/StringUtils.cpp
new file mode 100644
index 0000000..50525bc
--- /dev/null
+++ b/services/mediametrics/StringUtils.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaMetricsService::stringutils"
+#include <utils/Log.h>
+
+#include "StringUtils.h"
+
+namespace android::mediametrics::stringutils {
+
+std::string tokenizer(std::string::const_iterator& it,
+ const std::string::const_iterator& end, const char *reserved)
+{
+ // consume leading white space
+ for (; it != end && std::isspace(*it); ++it);
+ if (it == end) return {};
+
+ auto start = it;
+ // parse until we hit a reserved keyword or space
+ if (strchr(reserved, *it)) return {start, ++it};
+ for (;;) {
+ ++it;
+ if (it == end || std::isspace(*it) || strchr(reserved, *it)) return {start, it};
+ }
+}
+
+std::vector<std::string> split(const std::string& flags, const char *delim)
+{
+ std::vector<std::string> result;
+ for (auto it = flags.begin(); ; ) {
+ auto flag = tokenizer(it, flags.end(), delim);
+ if (flag.empty() || !std::isalnum(flag[0])) return result;
+ result.emplace_back(std::move(flag));
+
+ // look for the delimeter and discard
+ auto token = tokenizer(it, flags.end(), delim);
+ if (token.size() != 1 || strchr(delim, token[0]) == nullptr) return result;
+ }
+}
+
+std::vector<std::pair<std::string, std::string>> getDeviceAddressPairs(const std::string& devices)
+{
+ std::vector<std::pair<std::string, std::string>> result;
+
+ // Currently, the device format is EXACTLY
+ // (device1, addr1)|(device2, addr2)|...
+
+ static constexpr char delim[] = "()|,";
+ for (auto it = devices.begin(); ; ) {
+ auto token = tokenizer(it, devices.end(), delim);
+ if (token != "(") return result;
+
+ auto device = tokenizer(it, devices.end(), delim);
+ if (device.empty() || !std::isalnum(device[0])) return result;
+
+ token = tokenizer(it, devices.end(), delim);
+ if (token != ",") return result;
+
+ // special handling here for empty addresses
+ auto address = tokenizer(it, devices.end(), delim);
+ if (address.empty() || !std::isalnum(device[0])) return result;
+ if (address == ")") { // no address, just the ")"
+ address.clear();
+ } else {
+ token = tokenizer(it, devices.end(), delim);
+ if (token != ")") return result;
+ }
+
+ result.emplace_back(std::move(device), std::move(address));
+
+ token = tokenizer(it, devices.end(), delim);
+ if (token != "|") return result; // this includes end of string detection
+ }
+}
+
+size_t replace(std::string &str, const char *targetChars, const char replaceChar)
+{
+ size_t replaced = 0;
+ for (char &c : str) {
+ if (strchr(targetChars, c) != nullptr) {
+ c = replaceChar;
+ ++replaced;
+ }
+ }
+ return replaced;
+}
+
+} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/StringUtils.h b/services/mediametrics/StringUtils.h
new file mode 100644
index 0000000..7a8bbee
--- /dev/null
+++ b/services/mediametrics/StringUtils.h
@@ -0,0 +1,71 @@
+/*
+ * 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 <string>
+#include <vector>
+
+namespace android::mediametrics::stringutils {
+
+/**
+ * fieldPrint is a helper method that logs to a stringstream a sequence of
+ * field names (in a fixed size array) together with a variable number of arg parameters.
+ *
+ * stringstream << field[0] << ":" << arg0 << " ";
+ * stringstream << field[1] << ":" << arg1 << " ";
+ * ...
+ * stringstream << field[N-1] << ":" << arg{N-1} << " ";
+ *
+ * The number of fields must exactly match the (variable) arguments.
+ *
+ * Example:
+ *
+ * const char * const fields[] = { "integer" };
+ * std::stringstream ss;
+ * fieldPrint(ss, fields, int(10));
+ */
+template <size_t N, typename... Targs>
+void fieldPrint(std::stringstream& ss, const char * const (& fields)[N], Targs... args) {
+ static_assert(N == sizeof...(args)); // guarantee #fields == #args
+ auto fptr = fields; // get a pointer to the base of fields array
+ ((ss << *fptr++ << ":" << args << " "), ...); // (fold expression), send to stringstream.
+}
+
+/**
+ * Return string tokens from iterator, separated by spaces and reserved chars.
+ */
+std::string tokenizer(std::string::const_iterator& it,
+ const std::string::const_iterator& end, const char *reserved);
+
+/**
+ * Splits flags string based on delimeters (or, whitespace which is removed).
+ */
+std::vector<std::string> split(const std::string& flags, const char *delim);
+
+/**
+ * Parse the devices string and return a vector of device address pairs.
+ *
+ * A failure to parse returns early with the contents that were able to be parsed.
+ */
+std::vector<std::pair<std::string, std::string>> getDeviceAddressPairs(const std::string &devices);
+
+/**
+ * Replaces targetChars with replaceChar in string, returns number of chars replaced.
+ */
+size_t replace(std::string &str, const char *targetChars, const char replaceChar);
+
+} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/TimeMachine.h b/services/mediametrics/TimeMachine.h
index c82778b..ce579b3 100644
--- a/services/mediametrics/TimeMachine.h
+++ b/services/mediametrics/TimeMachine.h
@@ -18,6 +18,7 @@
#include <any>
#include <map>
+#include <mutex>
#include <sstream>
#include <string>
#include <variant>
@@ -81,6 +82,8 @@
, mCreationTime(time)
, mLastModificationTime(time)
{
+ (void)mCreationTime; // suppress unused warning.
+
// allowUid allows an untrusted client with a matching uid to set properties
// in this key.
// If allowUid == (uid_t)-1, no untrusted client may set properties in the key.
@@ -209,7 +212,7 @@
const std::string mKey;
const uid_t mAllowUid;
- const int64_t mCreationTime __unused;
+ const int64_t mCreationTime;
int64_t mLastModificationTime;
std::map<std::string /* property */, PropertyHistory> mPropertyMap;
@@ -217,10 +220,10 @@
using History = std::map<std::string /* key */, std::shared_ptr<KeyHistory>>;
- static inline constexpr size_t kTimeSequenceMaxElements = 100;
- static inline constexpr size_t kKeyMaxProperties = 100;
- static inline constexpr size_t kKeyLowWaterMark = 500;
- static inline constexpr size_t kKeyHighWaterMark = 1000;
+ static inline constexpr size_t kTimeSequenceMaxElements = 50;
+ static inline constexpr size_t kKeyMaxProperties = 50;
+ static inline constexpr size_t kKeyLowWaterMark = 400;
+ static inline constexpr size_t kKeyHighWaterMark = 500;
// Estimated max data space usage is 3KB * kKeyHighWaterMark.
@@ -252,6 +255,7 @@
{
std::lock_guard lock2(other.mLock);
mHistory = other.mHistory;
+ mGarbageCollectionCount = other.mGarbageCollectionCount.load();
}
// Now that we safely have our own shared pointers, let's dup them
@@ -417,6 +421,7 @@
void clear() {
std::lock_guard lock(mLock);
mHistory.clear();
+ mGarbageCollectionCount = 0;
}
/**
@@ -442,7 +447,7 @@
++it) {
if (ll <= 0) break;
if (prefix != nullptr && !startsWith(it->first, prefix)) break;
- std::lock_guard lock(getLockForKey(it->first));
+ std::lock_guard lock2(getLockForKey(it->first));
auto [s, l] = it->second->dump(ll, sinceNs);
ss << s;
ll -= l;
@@ -450,6 +455,10 @@
return { ss.str(), lines - ll };
}
+ size_t getGarbageCollectionCount() const {
+ return mGarbageCollectionCount;
+ }
+
private:
// Obtains the lock for a KeyHistory.
@@ -493,8 +502,6 @@
// TODO: something better than this for garbage collection.
if (mHistory.size() < mKeyHighWaterMark) return false;
- ALOGD("%s: garbage collection", __func__);
-
// erase everything explicitly expired.
std::multimap<int64_t, std::string> accessList;
// use a stale vector with precise type to avoid type erasure overhead in garbage
@@ -531,12 +538,16 @@
ALOGD("%s(%zu, %zu): key size:%zu",
__func__, mKeyLowWaterMark, mKeyHighWaterMark,
mHistory.size());
+
+ ++mGarbageCollectionCount;
return true;
}
const size_t mKeyLowWaterMark = kKeyLowWaterMark;
const size_t mKeyHighWaterMark = kKeyHighWaterMark;
+ std::atomic<size_t> mGarbageCollectionCount{};
+
/**
* Locking Strategy
*
diff --git a/services/mediametrics/TransactionLog.h b/services/mediametrics/TransactionLog.h
index 8a22826..0ca4639 100644
--- a/services/mediametrics/TransactionLog.h
+++ b/services/mediametrics/TransactionLog.h
@@ -43,9 +43,9 @@
// Transaction Log between the Low Water Mark and the High Water Mark.
// low water mark
- static inline constexpr size_t kLogItemsLowWater = 5000;
+ static inline constexpr size_t kLogItemsLowWater = 1700;
// high water mark
- static inline constexpr size_t kLogItemsHighWater = 10000;
+ static inline constexpr size_t kLogItemsHighWater = 2000;
// Estimated max data usage is 1KB * kLogItemsHighWater.
@@ -79,6 +79,7 @@
std::lock_guard lock2(other.mLock);
mLog = other.mLog;
mItemMap = other.mItemMap;
+ mGarbageCollectionCount = other.mGarbageCollectionCount.load();
return *this;
}
@@ -181,6 +182,11 @@
std::lock_guard lock(mLock);
mLog.clear();
mItemMap.clear();
+ mGarbageCollectionCount = 0;
+ }
+
+ size_t getGarbageCollectionCount() const {
+ return mGarbageCollectionCount;
}
private:
@@ -216,8 +222,6 @@
bool gc(std::vector<std::any>& garbage) REQUIRES(mLock) {
if (mLog.size() < mHighWaterMark) return false;
- ALOGD("%s: garbage collection", __func__);
-
auto eraseEnd = mLog.begin();
size_t toRemove = mLog.size() - mLowWaterMark;
// remove at least those elements.
@@ -265,6 +269,7 @@
ALOGD("%s(%zu, %zu): log size:%zu item map size:%zu, item map items:%zu",
__func__, mLowWaterMark, mHighWaterMark,
mLog.size(), mItemMap.size(), itemMapCount);
+ ++mGarbageCollectionCount;
return true;
}
@@ -287,6 +292,8 @@
const size_t mLowWaterMark = kLogItemsLowWater;
const size_t mHighWaterMark = kLogItemsHighWater;
+ std::atomic<size_t> mGarbageCollectionCount{};
+
mutable std::mutex mLock;
MapTimeItem mLog GUARDED_BY(mLock);
diff --git a/services/mediametrics/cleaner.cpp b/services/mediametrics/cleaner.cpp
new file mode 100644
index 0000000..e746842
--- /dev/null
+++ b/services/mediametrics/cleaner.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MetricsCleaner"
+#include <utils/Log.h>
+
+#include "cleaner.h"
+
+namespace android::mediametrics {
+
+// place time into buckets at 0,1,2,4,8,16,32 seconds and then at minute boundaries.
+// time is rounded up to the next boundary.
+//
+int64_t bucket_time_minutes(int64_t in_millis) {
+
+ const int64_t SEC_TO_MS = 1000;
+ const int64_t MIN_TO_MS = (60 * SEC_TO_MS);
+
+ if (in_millis <= 0) {
+ return 0;
+ }
+ if (in_millis <= 32 * SEC_TO_MS) {
+ for (int sec = 1; sec <= 32; sec *= 2) {
+ if (in_millis <= sec * SEC_TO_MS) {
+ return sec * SEC_TO_MS;
+ }
+ }
+ }
+ /* up to next 1 minute boundary */
+ int64_t minutes = (in_millis + MIN_TO_MS - 1) / MIN_TO_MS;
+ in_millis = minutes * MIN_TO_MS;
+ return in_millis;
+}
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/cleaner.h b/services/mediametrics/cleaner.h
new file mode 100644
index 0000000..72e24f9
--- /dev/null
+++ b/services/mediametrics/cleaner.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#ifndef MEDIAMETRICS_CLEANER_H
+#define MEDIAMETRICS_CLEANER_H
+
+namespace android::mediametrics {
+
+// break time into buckets at 1,2,4,8,16,32 seconds
+// and then at minute boundaries
+//
+extern int64_t bucket_time_minutes(int64_t incomingMs);
+
+} // namespace android::mediametrics
+
+#endif // MEDIAMETRICS_CLEANER_H
diff --git a/services/mediametrics/iface_statsd.cpp b/services/mediametrics/iface_statsd.cpp
index 3a1eea7..6e51f72 100644
--- a/services/mediametrics/iface_statsd.cpp
+++ b/services/mediametrics/iface_statsd.cpp
@@ -71,7 +71,7 @@
// give me a record, i'll look at the type and upload appropriately
bool dump2Statsd(const std::shared_ptr<const mediametrics::Item>& item) {
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// get the key
std::string key = item->getKey();
diff --git a/services/mediametrics/main_mediametrics.cpp b/services/mediametrics/main_mediametrics.cpp
index 6992c32..3a66538 100644
--- a/services/mediametrics/main_mediametrics.cpp
+++ b/services/mediametrics/main_mediametrics.cpp
@@ -25,9 +25,9 @@
#include <binder/ProcessState.h>
#include <mediautils/LimitProcessMemory.h>
-int main(int argc __unused, char **argv __unused)
+int main(int argc __unused, char **argv)
{
- using namespace android;
+ using namespace android; // NOLINT (clang-tidy)
limitProcessMemory(
"media.metrics.maxmem", /* property that defines limit */
@@ -39,7 +39,8 @@
// 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], MediaMetricsService::kServiceName);
+ const size_t origSize = strlen(argv[0]) + 1; // include null termination.
+ strlcpy(argv[0], MediaMetricsService::kServiceName, origSize);
defaultServiceManager()->addService(
String16(MediaMetricsService::kServiceName), new MediaMetricsService());
diff --git a/services/mediametrics/statsd_audiopolicy.cpp b/services/mediametrics/statsd_audiopolicy.cpp
index 634c801..393c6ae 100644
--- a/services/mediametrics/statsd_audiopolicy.cpp
+++ b/services/mediametrics/statsd_audiopolicy.cpp
@@ -39,7 +39,7 @@
bool statsd_audiopolicy(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// these go into the statsd wrapper
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
@@ -122,4 +122,4 @@
return true;
}
-};
+} // namespace android
diff --git a/services/mediametrics/statsd_audiorecord.cpp b/services/mediametrics/statsd_audiorecord.cpp
index 69d1661..43feda1 100644
--- a/services/mediametrics/statsd_audiorecord.cpp
+++ b/services/mediametrics/statsd_audiorecord.cpp
@@ -39,7 +39,7 @@
bool statsd_audiorecord(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// these go into the statsd wrapper
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
@@ -155,4 +155,4 @@
return true;
}
-};
+} // namespace android
diff --git a/services/mediametrics/statsd_audiothread.cpp b/services/mediametrics/statsd_audiothread.cpp
index 300151b..e867f5b 100644
--- a/services/mediametrics/statsd_audiothread.cpp
+++ b/services/mediametrics/statsd_audiothread.cpp
@@ -39,7 +39,7 @@
bool statsd_audiothread(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// these go into the statsd wrapper
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
@@ -204,4 +204,4 @@
return true;
}
-};
+} // namespace android
diff --git a/services/mediametrics/statsd_audiotrack.cpp b/services/mediametrics/statsd_audiotrack.cpp
index 397cdf3..ee5b9b2 100644
--- a/services/mediametrics/statsd_audiotrack.cpp
+++ b/services/mediametrics/statsd_audiotrack.cpp
@@ -39,7 +39,7 @@
bool statsd_audiotrack(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// these go into the statsd wrapper
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
@@ -146,4 +146,4 @@
return true;
}
-};
+} // namespace android
diff --git a/services/mediametrics/statsd_codec.cpp b/services/mediametrics/statsd_codec.cpp
index f5fa57e..ec9354f 100644
--- a/services/mediametrics/statsd_codec.cpp
+++ b/services/mediametrics/statsd_codec.cpp
@@ -31,6 +31,7 @@
#include <statslog.h>
+#include "cleaner.h"
#include "MediaMetricsService.h"
#include "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.pb.h"
#include "iface_statsd.h"
@@ -39,7 +40,7 @@
bool statsd_codec(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// these go into the statsd wrapper
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
@@ -168,6 +169,23 @@
}
// android.media.mediacodec.latency.hist NOT EMITTED
+ // android.media.mediacodec.bitrate_mode string
+ std::string bitrate_mode;
+ if (item->getString("android.media.mediacodec.bitrate_mode", &bitrate_mode)) {
+ metrics_proto.set_bitrate_mode(std::move(bitrate_mode));
+ }
+ // android.media.mediacodec.bitrate int32
+ int32_t bitrate = -1;
+ if (item->getInt32("android.media.mediacodec.bitrate", &bitrate)) {
+ metrics_proto.set_bitrate(bitrate);
+ }
+ // android.media.mediacodec.lifetimeMs int64
+ int64_t lifetimeMs = -1;
+ if ( item->getInt64("android.media.mediacodec.lifetimeMs", &lifetimeMs)) {
+ lifetimeMs = mediametrics::bucket_time_minutes(lifetimeMs);
+ metrics_proto.set_lifetime_millis(lifetimeMs);
+ }
+
std::string serialized;
if (!metrics_proto.SerializeToString(&serialized)) {
ALOGE("Failed to serialize codec metrics");
@@ -188,4 +206,4 @@
return true;
}
-};
+} // namespace android
diff --git a/services/mediametrics/statsd_drm.cpp b/services/mediametrics/statsd_drm.cpp
index 4f2e861..ac58929 100644
--- a/services/mediametrics/statsd_drm.cpp
+++ b/services/mediametrics/statsd_drm.cpp
@@ -43,67 +43,60 @@
// mediadrm
bool statsd_mediadrm(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
- char *vendor = NULL;
- (void) item->getCString("vendor", &vendor);
- char *description = NULL;
- (void) item->getCString("description", &description);
- char *serialized_metrics = NULL;
- (void) item->getCString("serialized_metrics", &serialized_metrics);
+ std::string vendor;
+ (void) item->getString("vendor", &vendor);
+ std::string description;
+ (void) item->getString("description", &description);
+ std::string serialized_metrics;
+ (void) item->getString("serialized_metrics", &serialized_metrics);
if (enabled_statsd) {
- android::util::BytesField bf_serialized(serialized_metrics ? serialized_metrics : NULL,
- serialized_metrics ? strlen(serialized_metrics)
- : 0);
+ android::util::BytesField bf_serialized(serialized_metrics.c_str(),
+ serialized_metrics.size());
android::util::stats_write(android::util::MEDIAMETRICS_MEDIADRM_REPORTED,
timestamp, pkgName.c_str(), pkgVersionCode,
mediaApexVersion,
- vendor, description,
+ vendor.c_str(),
+ description.c_str(),
bf_serialized);
} else {
- ALOGV("NOT sending: mediadrm private data (len=%zu)",
- serialized_metrics ? strlen(serialized_metrics) : 0);
+ ALOGV("NOT sending: mediadrm private data (len=%zu)", serialized_metrics.size());
}
- free(vendor);
- free(description);
- free(serialized_metrics);
return true;
}
// widevineCDM
bool statsd_widevineCDM(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
- char *serialized_metrics = NULL;
- (void) item->getCString("serialized_metrics", &serialized_metrics);
+ std::string serialized_metrics;
+ (void) item->getString("serialized_metrics", &serialized_metrics);
if (enabled_statsd) {
- android::util::BytesField bf_serialized(serialized_metrics ? serialized_metrics : NULL,
- serialized_metrics ? strlen(serialized_metrics)
- : 0);
+ android::util::BytesField bf_serialized(serialized_metrics.c_str(),
+ serialized_metrics.size());
android::util::stats_write(android::util::MEDIAMETRICS_DRM_WIDEVINE_REPORTED,
timestamp, pkgName.c_str(), pkgVersionCode,
mediaApexVersion,
bf_serialized);
} else {
- ALOGV("NOT sending: widevine private data (len=%zu)",
- serialized_metrics ? strlen(serialized_metrics) : 0);
+ ALOGV("NOT sending: widevine private data (len=%zu)", serialized_metrics.size());
}
- free(serialized_metrics);
return true;
}
@@ -111,7 +104,7 @@
bool statsd_drmmanager(const mediametrics::Item *item)
{
using namespace std::string_literals;
- if (item == NULL) return false;
+ if (item == nullptr) return false;
if (!enabled_statsd) {
ALOGV("NOT sending: drmmanager data");
@@ -123,14 +116,14 @@
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
- char *plugin_id = NULL;
- (void) item->getCString("plugin_id", &plugin_id);
- char *description = NULL;
- (void) item->getCString("description", &description);
+ std::string plugin_id;
+ (void) item->getString("plugin_id", &plugin_id);
+ std::string description;
+ (void) item->getString("description", &description);
int32_t method_id = -1;
(void) item->getInt32("method_id", &method_id);
- char *mime_types = NULL;
- (void) item->getCString("mime_types", &mime_types);
+ std::string mime_types;
+ (void) item->getString("mime_types", &mime_types);
// Corresponds to the 13 APIs tracked in the MediametricsDrmManagerReported statsd proto
// Please see also DrmManager::kMethodIdMap
@@ -141,16 +134,15 @@
android::util::stats_write(android::util::MEDIAMETRICS_DRMMANAGER_REPORTED,
timestamp, pkgName.c_str(), pkgVersionCode, mediaApexVersion,
- plugin_id, description, method_id, mime_types,
+ plugin_id.c_str(), description.c_str(),
+ method_id, mime_types.c_str(),
methodCounts[0], methodCounts[1], methodCounts[2],
methodCounts[3], methodCounts[4], methodCounts[5],
methodCounts[6], methodCounts[7], methodCounts[8],
methodCounts[9], methodCounts[10], methodCounts[11],
methodCounts[12]);
- free(plugin_id);
- free(description);
- free(mime_types);
return true;
}
+
} // namespace android
diff --git a/services/mediametrics/statsd_extractor.cpp b/services/mediametrics/statsd_extractor.cpp
index 8574358..3d5739f 100644
--- a/services/mediametrics/statsd_extractor.cpp
+++ b/services/mediametrics/statsd_extractor.cpp
@@ -39,7 +39,7 @@
bool statsd_extractor(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// these go into the statsd wrapper
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
@@ -91,4 +91,4 @@
return true;
}
-};
+} // namespace android
diff --git a/services/mediametrics/statsd_nuplayer.cpp b/services/mediametrics/statsd_nuplayer.cpp
index df7e59f..488bdcb 100644
--- a/services/mediametrics/statsd_nuplayer.cpp
+++ b/services/mediametrics/statsd_nuplayer.cpp
@@ -43,7 +43,7 @@
*/
bool statsd_nuplayer(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// these go into the statsd wrapper
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
@@ -167,4 +167,4 @@
return true;
}
-};
+} // namespace android
diff --git a/services/mediametrics/statsd_recorder.cpp b/services/mediametrics/statsd_recorder.cpp
index 4de1746..6d5fca0 100644
--- a/services/mediametrics/statsd_recorder.cpp
+++ b/services/mediametrics/statsd_recorder.cpp
@@ -39,7 +39,7 @@
bool statsd_recorder(const mediametrics::Item *item)
{
- if (item == NULL) return false;
+ if (item == nullptr) return false;
// these go into the statsd wrapper
const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
@@ -186,4 +186,4 @@
return true;
}
-};
+} // namespace android
diff --git a/services/mediametrics/tests/Android.bp b/services/mediametrics/tests/Android.bp
index bdeda30..c2e0759 100644
--- a/services/mediametrics/tests/Android.bp
+++ b/services/mediametrics/tests/Android.bp
@@ -21,6 +21,10 @@
"libutils",
],
+ header_libs: [
+ "libaudioutils_headers",
+ ],
+
srcs: [
"mediametrics_tests.cpp",
],
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index b465ecd..478355b 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -20,9 +20,14 @@
#include "MediaMetricsService.h"
#include <stdio.h>
+#include <unordered_set>
#include <gtest/gtest.h>
#include <media/MediaMetricsItem.h>
+#include <system/audio.h>
+
+#include "AudioTypes.h"
+#include "StringUtils.h"
using namespace android;
@@ -35,6 +40,15 @@
return count;
}
+template <typename M>
+ssize_t countDuplicates(const M& map) {
+ std::unordered_set<typename M::mapped_type> s;
+ for (const auto &m : map) {
+ s.emplace(m.second);
+ }
+ return map.size() - s.size();
+}
+
TEST(mediametrics_tests, startsWith) {
std::string s("test");
ASSERT_EQ(true, android::mediametrics::startsWith(s, "te"));
@@ -803,7 +817,7 @@
// TODO: Verify contents of AudioAnalytics.
// Currently there is no getter API in AudioAnalytics besides dump.
- ASSERT_EQ(9, audioAnalytics.dump(1000).second /* lines */);
+ ASSERT_EQ(11, audioAnalytics.dump(1000).second /* lines */);
ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item, true /* isTrusted */));
// untrusted entities can add to an existing key
@@ -839,7 +853,7 @@
// TODO: Verify contents of AudioAnalytics.
// Currently there is no getter API in AudioAnalytics besides dump.
- ASSERT_EQ(9, audioAnalytics.dump(1000).second /* lines */);
+ ASSERT_EQ(11, audioAnalytics.dump(1000).second /* lines */);
ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item, true /* isTrusted */));
// untrusted entities can add to an existing key
@@ -884,12 +898,12 @@
}
TEST(mediametrics_tests, device_parsing) {
- auto devaddr = android::MediaMetricsService::getDeviceAddressPairs("(DEVICE, )");
+ auto devaddr = android::mediametrics::stringutils::getDeviceAddressPairs("(DEVICE, )");
ASSERT_EQ((size_t)1, devaddr.size());
ASSERT_EQ("DEVICE", devaddr[0].first);
ASSERT_EQ("", devaddr[0].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
"(DEVICE1, A)|(D, ADDRB)");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("DEVICE1", devaddr[0].first);
@@ -897,7 +911,7 @@
ASSERT_EQ("D", devaddr[1].first);
ASSERT_EQ("ADDRB", devaddr[1].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
"(A,B)|(C,D)");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("A", devaddr[0].first);
@@ -905,7 +919,7 @@
ASSERT_EQ("C", devaddr[1].first);
ASSERT_EQ("D", devaddr[1].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
" ( A1 , B ) | ( C , D2 ) ");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("A1", devaddr[0].first);
@@ -925,6 +939,132 @@
ASSERT_EQ((size_t)1, timedAction.size());
}
+// Ensure we don't introduce unexpected duplicates into our maps.
+TEST(mediametrics_tests, audio_types_tables) {
+ using namespace android::mediametrics::types;
+
+ ASSERT_EQ(0, countDuplicates(getAudioCallerNameMap()));
+ ASSERT_EQ(2, countDuplicates(getAudioDeviceInMap())); // has dups
+ ASSERT_EQ(1, countDuplicates(getAudioDeviceOutMap())); // has dups
+ ASSERT_EQ(0, countDuplicates(getAudioThreadTypeMap()));
+ ASSERT_EQ(0, countDuplicates(getAudioTrackTraitsMap()));
+}
+
+// Check our string validation (before logging to statsd).
+// This variant checks the logged, possibly shortened string.
+TEST(mediametrics_tests, audio_types_string) {
+ using namespace android::mediametrics::types;
+
+ ASSERT_EQ("java", (lookup<CALLER_NAME, std::string>)("java"));
+ ASSERT_EQ("", (lookup<CALLER_NAME, std::string>)("random"));
+
+ ASSERT_EQ("SPEECH", (lookup<CONTENT_TYPE, std::string>)("AUDIO_CONTENT_TYPE_SPEECH"));
+ ASSERT_EQ("", (lookup<CONTENT_TYPE, std::string>)("random"));
+
+ ASSERT_EQ("FLAC", (lookup<ENCODING, std::string>)("AUDIO_FORMAT_FLAC"));
+ ASSERT_EQ("", (lookup<ENCODING, std::string>)("random"));
+
+ ASSERT_EQ("USB_DEVICE", (lookup<INPUT_DEVICE, std::string>)("AUDIO_DEVICE_IN_USB_DEVICE"));
+ ASSERT_EQ("BUILTIN_MIC|WIRED_HEADSET", (lookup<INPUT_DEVICE, std::string>)(
+ "AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET"));
+ ASSERT_EQ("", (lookup<INPUT_DEVICE, std::string>)("random"));
+
+ ASSERT_EQ("RAW", (lookup<INPUT_FLAG, std::string>)("AUDIO_INPUT_FLAG_RAW"));
+ ASSERT_EQ("HW_AV_SYNC|VOIP_TX", (lookup<INPUT_FLAG, std::string>)(
+ "AUDIO_INPUT_FLAG_HW_AV_SYNC|AUDIO_INPUT_FLAG_VOIP_TX"));
+ ASSERT_EQ("", (lookup<INPUT_FLAG, std::string>)("random"));
+
+ ASSERT_EQ("BLUETOOTH_SCO_CARKIT",
+ (lookup<OUTPUT_DEVICE, std::string>)("AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT"));
+ ASSERT_EQ("SPEAKER|HDMI", (lookup<OUTPUT_DEVICE, std::string>)(
+ "AUDIO_DEVICE_OUT_SPEAKER|AUDIO_DEVICE_OUT_HDMI"));
+ ASSERT_EQ("", (lookup<OUTPUT_DEVICE, std::string>)("random"));
+
+ ASSERT_EQ("PRIMARY", (lookup<OUTPUT_FLAG, std::string>)("AUDIO_OUTPUT_FLAG_PRIMARY"));
+ ASSERT_EQ("DEEP_BUFFER|NON_BLOCKING", (lookup<OUTPUT_FLAG, std::string>)(
+ "AUDIO_OUTPUT_FLAG_DEEP_BUFFER|AUDIO_OUTPUT_FLAG_NON_BLOCKING"));
+ ASSERT_EQ("", (lookup<OUTPUT_FLAG, std::string>)("random"));
+
+ ASSERT_EQ("MIC", (lookup<SOURCE_TYPE, std::string>)("AUDIO_SOURCE_MIC"));
+ ASSERT_EQ("", (lookup<SOURCE_TYPE, std::string>)("random"));
+
+ ASSERT_EQ("TTS", (lookup<STREAM_TYPE, std::string>)("AUDIO_STREAM_TTS"));
+ ASSERT_EQ("", (lookup<STREAM_TYPE, std::string>)("random"));
+
+ ASSERT_EQ("DIRECT", (lookup<THREAD_TYPE, std::string>)("DIRECT"));
+ ASSERT_EQ("", (lookup<THREAD_TYPE, std::string>)("random"));
+
+ ASSERT_EQ("static", (lookup<TRACK_TRAITS, std::string>)("static"));
+ ASSERT_EQ("", (lookup<TRACK_TRAITS, std::string>)("random"));
+
+ ASSERT_EQ("VOICE_COMMUNICATION",
+ (lookup<USAGE, std::string>)("AUDIO_USAGE_VOICE_COMMUNICATION"));
+ ASSERT_EQ("", (lookup<USAGE, std::string>)("random"));
+}
+
+// Check our string validation (before logging to statsd).
+// This variant checks integral value logging.
+TEST(mediametrics_tests, audio_types_integer) {
+ using namespace android::mediametrics::types;
+
+ ASSERT_EQ(2, (lookup<CALLER_NAME, int32_t>)("java"));
+ ASSERT_EQ(0, (lookup<CALLER_NAME, int32_t>)("random")); // 0 == unknown
+
+ ASSERT_EQ((int32_t)AUDIO_CONTENT_TYPE_SPEECH,
+ (lookup<CONTENT_TYPE, int32_t>)("AUDIO_CONTENT_TYPE_SPEECH"));
+ ASSERT_EQ((int32_t)AUDIO_CONTENT_TYPE_UNKNOWN, (lookup<CONTENT_TYPE, int32_t>)("random"));
+
+ ASSERT_EQ((int32_t)AUDIO_FORMAT_FLAC, (lookup<ENCODING, int32_t>)("AUDIO_FORMAT_FLAC"));
+ ASSERT_EQ((int32_t)AUDIO_FORMAT_INVALID, (lookup<ENCODING, int32_t>)("random"));
+
+ ASSERT_EQ(getAudioDeviceInMap().at("AUDIO_DEVICE_IN_USB_DEVICE"),
+ (lookup<INPUT_DEVICE, int64_t>)("AUDIO_DEVICE_IN_USB_DEVICE"));
+ ASSERT_EQ(getAudioDeviceInMap().at("AUDIO_DEVICE_IN_BUILTIN_MIC")
+ | getAudioDeviceInMap().at("AUDIO_DEVICE_IN_WIRED_HEADSET"),
+ (lookup<INPUT_DEVICE, int64_t>)(
+ "AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET"));
+ ASSERT_EQ(0, (lookup<INPUT_DEVICE, int64_t>)("random"));
+
+ ASSERT_EQ((int32_t)AUDIO_INPUT_FLAG_RAW,
+ (lookup<INPUT_FLAG, int32_t>)("AUDIO_INPUT_FLAG_RAW"));
+ ASSERT_EQ((int32_t)AUDIO_INPUT_FLAG_HW_AV_SYNC
+ | (int32_t)AUDIO_INPUT_FLAG_VOIP_TX,
+ (lookup<INPUT_FLAG, int32_t>)(
+ "AUDIO_INPUT_FLAG_HW_AV_SYNC|AUDIO_INPUT_FLAG_VOIP_TX"));
+ ASSERT_EQ(0, (lookup<INPUT_FLAG, int32_t>)("random"));
+
+ ASSERT_EQ(getAudioDeviceOutMap().at("AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT"),
+ (lookup<OUTPUT_DEVICE, int64_t>)("AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT"));
+ ASSERT_EQ(getAudioDeviceOutMap().at("AUDIO_DEVICE_OUT_SPEAKER")
+ | getAudioDeviceOutMap().at("AUDIO_DEVICE_OUT_HDMI"),
+ (lookup<OUTPUT_DEVICE, int64_t>)(
+ "AUDIO_DEVICE_OUT_SPEAKER|AUDIO_DEVICE_OUT_HDMI"));
+ ASSERT_EQ(0, (lookup<OUTPUT_DEVICE, int64_t>)("random"));
+
+ ASSERT_EQ((int32_t)AUDIO_OUTPUT_FLAG_PRIMARY,
+ (lookup<OUTPUT_FLAG, int32_t>)("AUDIO_OUTPUT_FLAG_PRIMARY"));
+ ASSERT_EQ((int32_t)AUDIO_OUTPUT_FLAG_DEEP_BUFFER | (int32_t)AUDIO_OUTPUT_FLAG_NON_BLOCKING,
+ (lookup<OUTPUT_FLAG, int32_t>)(
+ "AUDIO_OUTPUT_FLAG_DEEP_BUFFER|AUDIO_OUTPUT_FLAG_NON_BLOCKING"));
+ ASSERT_EQ(0, (lookup<OUTPUT_FLAG, int32_t>)("random"));
+
+ ASSERT_EQ((int32_t)AUDIO_SOURCE_MIC, (lookup<SOURCE_TYPE, int32_t>)("AUDIO_SOURCE_MIC"));
+ ASSERT_EQ((int32_t)AUDIO_SOURCE_DEFAULT, (lookup<SOURCE_TYPE, int32_t>)("random"));
+
+ ASSERT_EQ((int32_t)AUDIO_STREAM_TTS, (lookup<STREAM_TYPE, int32_t>)("AUDIO_STREAM_TTS"));
+ ASSERT_EQ((int32_t)AUDIO_STREAM_DEFAULT, (lookup<STREAM_TYPE, int32_t>)("random"));
+
+ ASSERT_EQ(1, (lookup<THREAD_TYPE, int32_t>)("DIRECT"));
+ ASSERT_EQ(-1, (lookup<THREAD_TYPE, int32_t>)("random"));
+
+ ASSERT_EQ(getAudioTrackTraitsMap().at("static"), (lookup<TRACK_TRAITS, int32_t>)("static"));
+ ASSERT_EQ(0, (lookup<TRACK_TRAITS, int32_t>)("random"));
+
+ ASSERT_EQ((int32_t)AUDIO_USAGE_VOICE_COMMUNICATION,
+ (lookup<USAGE, int32_t>)("AUDIO_USAGE_VOICE_COMMUNICATION"));
+ ASSERT_EQ((int32_t)AUDIO_USAGE_UNKNOWN, (lookup<USAGE, int32_t>)("random"));
+}
+
#if 0
// Stress test code for garbage collection, you need to enable AID_SHELL as trusted to run
// in MediaMetricsService.cpp.
diff --git a/services/mediaresourcemanager/ResourceManagerService.cpp b/services/mediaresourcemanager/ResourceManagerService.cpp
index ff45c87..3d36f8e 100644
--- a/services/mediaresourcemanager/ResourceManagerService.cpp
+++ b/services/mediaresourcemanager/ResourceManagerService.cpp
@@ -57,9 +57,11 @@
ALOGW("ResourceManagerService is dead as well.");
return;
}
- service->removeResource(mPid, mClientId, false);
service->overridePid(mPid, -1);
+ // thiz is freed in the call below, so it must be last call referring thiz
+ service->removeResource(mPid, mClientId, false);
+
}
template <typename T>
diff --git a/services/minijail/Android.bp b/services/minijail/Android.bp
index 5ea6d1e..b057968 100644
--- a/services/minijail/Android.bp
+++ b/services/minijail/Android.bp
@@ -18,6 +18,7 @@
name: "libavservices_minijail",
defaults: ["libavservices_minijail_defaults"],
vendor_available: true,
+ min_sdk_version: "29",
export_include_dirs: ["."],
}
diff --git a/services/oboeservice/AAudioClientTracker.cpp b/services/oboeservice/AAudioClientTracker.cpp
index 6e14434..9d9ca63 100644
--- a/services/oboeservice/AAudioClientTracker.cpp
+++ b/services/oboeservice/AAudioClientTracker.cpp
@@ -106,18 +106,9 @@
aaudio_result_t
AAudioClientTracker::registerClientStream(pid_t pid, sp<AAudioServiceStreamBase> serviceStream) {
- aaudio_result_t result = AAUDIO_OK;
ALOGV("registerClientStream(%d,)\n", pid);
std::lock_guard<std::mutex> lock(mLock);
- sp<NotificationClient> notificationClient = mNotificationClients[pid];
- if (notificationClient == 0) {
- // This will get called the first time the audio server registers an internal stream.
- ALOGV("registerClientStream(%d,) unrecognized pid\n", pid);
- notificationClient = new NotificationClient(pid, nullptr);
- mNotificationClients[pid] = notificationClient;
- }
- notificationClient->registerClientStream(serviceStream);
- return result;
+ return getNotificationClient_l(pid)->registerClientStream(serviceStream);
}
// Find the tracker for this process and remove it.
@@ -136,6 +127,33 @@
return AAUDIO_OK;
}
+void AAudioClientTracker::setExclusiveEnabled(pid_t pid, bool enabled) {
+ ALOGD("%s(%d, %d)\n", __func__, pid, enabled);
+ std::lock_guard<std::mutex> lock(mLock);
+ getNotificationClient_l(pid)->setExclusiveEnabled(enabled);
+}
+
+bool AAudioClientTracker::isExclusiveEnabled(pid_t pid) {
+ std::lock_guard<std::mutex> lock(mLock);
+ return getNotificationClient_l(pid)->isExclusiveEnabled();
+}
+
+sp<AAudioClientTracker::NotificationClient>
+ AAudioClientTracker::getNotificationClient_l(pid_t pid) {
+ sp<NotificationClient> notificationClient = mNotificationClients[pid];
+ if (notificationClient == nullptr) {
+ // This will get called the first time the audio server uses this PID.
+ ALOGV("%s(%d,) unrecognized PID\n", __func__, pid);
+ notificationClient = new AAudioClientTracker::NotificationClient(pid, nullptr);
+ mNotificationClients[pid] = notificationClient;
+ }
+ return notificationClient;
+}
+
+// =======================================
+// AAudioClientTracker::NotificationClient
+// =======================================
+
AAudioClientTracker::NotificationClient::NotificationClient(pid_t pid, const sp<IBinder>& binder)
: mProcessId(pid), mBinder(binder) {
}
diff --git a/services/oboeservice/AAudioClientTracker.h b/services/oboeservice/AAudioClientTracker.h
index 00ff467..943b809 100644
--- a/services/oboeservice/AAudioClientTracker.h
+++ b/services/oboeservice/AAudioClientTracker.h
@@ -58,6 +58,15 @@
aaudio_result_t unregisterClientStream(pid_t pid,
android::sp<AAudioServiceStreamBase> serviceStream);
+ /**
+ * Specify whether a process is allowed to create an EXCLUSIVE MMAP stream.
+ * @param pid
+ * @param enabled
+ */
+ void setExclusiveEnabled(pid_t pid, bool enabled);
+
+ bool isExclusiveEnabled(pid_t pid);
+
android::AAudioService *getAAudioService() const {
return mAAudioService;
}
@@ -84,17 +93,29 @@
aaudio_result_t unregisterClientStream(android::sp<AAudioServiceStreamBase> serviceStream);
+ void setExclusiveEnabled(bool enabled) {
+ mExclusiveEnabled = enabled;
+ }
+
+ bool isExclusiveEnabled() {
+ return mExclusiveEnabled;
+ }
+
// IBinder::DeathRecipient
virtual void binderDied(const android::wp<IBinder>& who);
- protected:
+ private:
mutable std::mutex mLock;
const pid_t mProcessId;
std::set<android::sp<AAudioServiceStreamBase>> mStreams;
// hold onto binder to receive death notifications
android::sp<IBinder> mBinder;
+ bool mExclusiveEnabled = true;
};
+ // This must be called under mLock
+ android::sp<NotificationClient> getNotificationClient_l(pid_t pid);
+
mutable std::mutex mLock;
std::map<pid_t, android::sp<NotificationClient>> mNotificationClients;
android::AAudioService *mAAudioService = nullptr;
diff --git a/services/oboeservice/AAudioEndpointManager.cpp b/services/oboeservice/AAudioEndpointManager.cpp
index c9bf72f..9f34153 100644
--- a/services/oboeservice/AAudioEndpointManager.cpp
+++ b/services/oboeservice/AAudioEndpointManager.cpp
@@ -25,6 +25,7 @@
#include <sstream>
#include <utility/AAudioUtilities.h>
+#include "AAudioClientTracker.h"
#include "AAudioEndpointManager.h"
#include "AAudioServiceEndpointShared.h"
#include "AAudioServiceEndpointMMAP.h"
@@ -174,7 +175,15 @@
&& !request.isSharingModeMatchRequired()) { // app did not request a shared stream
ALOGD("%s() endpoint in EXCLUSIVE use. Steal it!", __func__);
mExclusiveStolenCount++;
- endpointToSteal = endpoint;
+ // Prevent this process from getting another EXCLUSIVE stream.
+ // This will prevent two clients from colliding after a DISCONNECTION
+ // when they both try to open an exclusive stream at the same time.
+ // That can result in a stream getting disconnected between the OPEN
+ // and START calls. This will help preserve app compatibility.
+ // An app can avoid having this happen by closing their streams when
+ // the app is paused.
+ AAudioClientTracker::getInstance().setExclusiveEnabled(request.getProcessId(), false);
+ endpointToSteal = endpoint; // return it to caller
}
return nullptr;
} else {
diff --git a/services/oboeservice/AAudioService.cpp b/services/oboeservice/AAudioService.cpp
index ecbcb7e..22cdb35 100644
--- a/services/oboeservice/AAudioService.cpp
+++ b/services/oboeservice/AAudioService.cpp
@@ -23,7 +23,6 @@
#include <sstream>
#include <aaudio/AAudio.h>
-#include <mediautils/SchedulingPolicyService.h>
#include <mediautils/ServiceUtilities.h>
#include <utils/String16.h>
@@ -118,7 +117,8 @@
return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
}
- if (sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE) {
+ if (sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE
+ && AAudioClientTracker::getInstance().isExclusiveEnabled(request.getProcessId())) {
// only trust audioserver for in service indication
bool inService = false;
if (isCallerInService()) {
@@ -162,28 +162,6 @@
}
}
-// If a close request is pending then close the stream
-bool AAudioService::releaseStream(const sp<AAudioServiceStreamBase> &serviceStream) {
- bool closed = false;
- // decrementAndRemoveStreamByHandle() uses a lock so that if there are two simultaneous closes
- // then only one will get the pointer and do the close.
- sp<AAudioServiceStreamBase> foundStream = mStreamTracker.decrementAndRemoveStreamByHandle(
- serviceStream->getHandle());
- if (foundStream.get() != nullptr) {
- foundStream->close();
- pid_t pid = foundStream->getOwnerProcessId();
- AAudioClientTracker::getInstance().unregisterClientStream(pid, foundStream);
- closed = true;
- }
- return closed;
-}
-
-aaudio_result_t AAudioService::checkForPendingClose(
- const sp<AAudioServiceStreamBase> &serviceStream,
- aaudio_result_t defaultResult) {
- return releaseStream(serviceStream) ? AAUDIO_ERROR_INVALID_STATE : defaultResult;
-}
-
aaudio_result_t AAudioService::closeStream(aaudio_handle_t streamHandle) {
// Check permission and ownership first.
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
@@ -195,17 +173,20 @@
}
aaudio_result_t AAudioService::closeStream(sp<AAudioServiceStreamBase> serviceStream) {
+ // This is protected by a lock in AAudioClientTracker.
+ // It is safe to unregister the same stream twice.
pid_t pid = serviceStream->getOwnerProcessId();
AAudioClientTracker::getInstance().unregisterClientStream(pid, serviceStream);
+ // This is protected by a lock in mStreamTracker.
+ // It is safe to remove the same stream twice.
+ mStreamTracker.removeStreamByHandle(serviceStream->getHandle());
- serviceStream->markCloseNeeded();
- (void) releaseStream(serviceStream);
- return AAUDIO_OK;
+ return serviceStream->close();
}
sp<AAudioServiceStreamBase> AAudioService::convertHandleToServiceStream(
aaudio_handle_t streamHandle) {
- sp<AAudioServiceStreamBase> serviceStream = mStreamTracker.getStreamByHandleAndIncrement(
+ sp<AAudioServiceStreamBase> serviceStream = mStreamTracker.getStreamByHandle(
streamHandle);
if (serviceStream.get() != nullptr) {
// Only allow owner or the aaudio service to access the stream.
@@ -218,8 +199,6 @@
if (!allowed) {
ALOGE("AAudioService: calling uid %d cannot access stream 0x%08X owned by %d",
callingUserId, streamHandle, ownerUserId);
- // We incremented the reference count so we must check if it needs to be closed.
- checkForPendingClose(serviceStream, AAUDIO_OK);
serviceStream.clear();
}
}
@@ -234,96 +213,66 @@
ALOGE("getStreamDescription(), illegal stream handle = 0x%0x", streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
-
- aaudio_result_t result = serviceStream->getDescription(parcelable);
- // parcelable.dump();
- return checkForPendingClose(serviceStream, result);
+ return serviceStream->getDescription(parcelable);
}
aaudio_result_t AAudioService::startStream(aaudio_handle_t streamHandle) {
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
- ALOGE("startStream(), illegal stream handle = 0x%0x", streamHandle);
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
-
- aaudio_result_t result = serviceStream->start();
- return checkForPendingClose(serviceStream, result);
+ return serviceStream->start();
}
aaudio_result_t AAudioService::pauseStream(aaudio_handle_t streamHandle) {
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
- ALOGE("pauseStream(), illegal stream handle = 0x%0x", streamHandle);
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
- aaudio_result_t result = serviceStream->pause();
- return checkForPendingClose(serviceStream, result);
+ return serviceStream->pause();
}
aaudio_result_t AAudioService::stopStream(aaudio_handle_t streamHandle) {
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
- ALOGE("stopStream(), illegal stream handle = 0x%0x", streamHandle);
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
- aaudio_result_t result = serviceStream->stop();
- return checkForPendingClose(serviceStream, result);
+ return serviceStream->stop();
}
aaudio_result_t AAudioService::flushStream(aaudio_handle_t streamHandle) {
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
- ALOGE("flushStream(), illegal stream handle = 0x%0x", streamHandle);
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
- aaudio_result_t result = serviceStream->flush();
- return checkForPendingClose(serviceStream, result);
+ return serviceStream->flush();
}
aaudio_result_t AAudioService::registerAudioThread(aaudio_handle_t streamHandle,
pid_t clientThreadId,
- int64_t periodNanoseconds) {
- aaudio_result_t result = AAUDIO_OK;
+ int64_t /* periodNanoseconds */) {
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
- ALOGE("registerAudioThread(), illegal stream handle = 0x%0x", streamHandle);
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
- if (serviceStream->getRegisteredThread() != AAudioServiceStreamBase::ILLEGAL_THREAD_ID) {
- ALOGE("AAudioService::registerAudioThread(), thread already registered");
- result = AAUDIO_ERROR_INVALID_STATE;
- } else {
- const pid_t ownerPid = IPCThreadState::self()->getCallingPid(); // TODO review
- int32_t priority = isCallerInService()
- ? kRealTimeAudioPriorityService : kRealTimeAudioPriorityClient;
- serviceStream->setRegisteredThread(clientThreadId);
- int err = android::requestPriority(ownerPid, clientThreadId,
- priority, true /* isForApp */);
- if (err != 0) {
- ALOGE("AAudioService::registerAudioThread(%d) failed, errno = %d, priority = %d",
- clientThreadId, errno, priority);
- result = AAUDIO_ERROR_INTERNAL;
- }
- }
- return checkForPendingClose(serviceStream, result);
+ int32_t priority = isCallerInService()
+ ? kRealTimeAudioPriorityService : kRealTimeAudioPriorityClient;
+ return serviceStream->registerAudioThread(clientThreadId, priority);
}
aaudio_result_t AAudioService::unregisterAudioThread(aaudio_handle_t streamHandle,
pid_t clientThreadId) {
- aaudio_result_t result = AAUDIO_OK;
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
- ALOGE("%s(), illegal stream handle = 0x%0x", __func__, streamHandle);
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
- if (serviceStream->getRegisteredThread() != clientThreadId) {
- ALOGE("%s(), wrong thread", __func__);
- result = AAUDIO_ERROR_ILLEGAL_ARGUMENT;
- } else {
- serviceStream->setRegisteredThread(0);
- }
- return checkForPendingClose(serviceStream, result);
+ return serviceStream->unregisterAudioThread(clientThreadId);
}
aaudio_result_t AAudioService::startClient(aaudio_handle_t streamHandle,
@@ -332,22 +281,20 @@
audio_port_handle_t *clientHandle) {
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
- ALOGE("%s(), illegal stream handle = 0x%0x", __func__, streamHandle);
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
- aaudio_result_t result = serviceStream->startClient(client, attr, clientHandle);
- return checkForPendingClose(serviceStream, result);
+ return serviceStream->startClient(client, attr, clientHandle);
}
aaudio_result_t AAudioService::stopClient(aaudio_handle_t streamHandle,
audio_port_handle_t portHandle) {
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
- ALOGE("%s(), illegal stream handle = 0x%0x", __func__, streamHandle);
+ ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
- aaudio_result_t result = serviceStream->stopClient(portHandle);
- return checkForPendingClose(serviceStream, result);
+ return serviceStream->stopClient(portHandle);
}
// This is only called internally when AudioFlinger wants to tear down a stream.
@@ -355,12 +302,13 @@
aaudio_result_t AAudioService::disconnectStreamByPortHandle(audio_port_handle_t portHandle) {
ALOGD("%s(%d) called", __func__, portHandle);
sp<AAudioServiceStreamBase> serviceStream =
- mStreamTracker.findStreamByPortHandleAndIncrement(portHandle);
+ mStreamTracker.findStreamByPortHandle(portHandle);
if (serviceStream.get() == nullptr) {
ALOGE("%s(), could not find stream with portHandle = %d", __func__, portHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
+ // This is protected by a lock and will just return if already stopped.
aaudio_result_t result = serviceStream->stop();
serviceStream->disconnect();
- return checkForPendingClose(serviceStream, result);
+ return result;
}
diff --git a/services/oboeservice/AAudioService.h b/services/oboeservice/AAudioService.h
index 6a2ac1f..caf48a5 100644
--- a/services/oboeservice/AAudioService.h
+++ b/services/oboeservice/AAudioService.h
@@ -114,11 +114,6 @@
sp<aaudio::AAudioServiceStreamBase> convertHandleToServiceStream(
aaudio::aaudio_handle_t streamHandle);
- bool releaseStream(const sp<aaudio::AAudioServiceStreamBase> &serviceStream);
-
- aaudio_result_t checkForPendingClose(const sp<aaudio::AAudioServiceStreamBase> &serviceStream,
- aaudio_result_t defaultResult);
-
android::AudioClient mAudioClient;
aaudio::AAudioStreamTracker mStreamTracker;
diff --git a/services/oboeservice/AAudioServiceEndpoint.cpp b/services/oboeservice/AAudioServiceEndpoint.cpp
index b09cbf4..ceefe93 100644
--- a/services/oboeservice/AAudioServiceEndpoint.cpp
+++ b/services/oboeservice/AAudioServiceEndpoint.cpp
@@ -90,14 +90,23 @@
std::vector<android::sp<AAudioServiceStreamBase>>
AAudioServiceEndpoint::disconnectRegisteredStreams() {
std::vector<android::sp<AAudioServiceStreamBase>> streamsDisconnected;
- std::lock_guard<std::mutex> lock(mLockStreams);
+ {
+ std::lock_guard<std::mutex> lock(mLockStreams);
+ mRegisteredStreams.swap(streamsDisconnected);
+ }
mConnected.store(false);
- for (const auto &stream : mRegisteredStreams) {
- ALOGD("%s() - stop and disconnect port %d", __func__, stream->getPortHandle());
+ // We need to stop all the streams before we disconnect them.
+ // Otherwise there is a race condition where the first disconnected app
+ // tries to reopen a stream as MMAP but is blocked by the second stream,
+ // which hasn't stopped yet. Then the first app ends up with a Legacy stream.
+ for (const auto &stream : streamsDisconnected) {
+ ALOGD("%s() - stop(), port = %d", __func__, stream->getPortHandle());
stream->stop();
+ }
+ for (const auto &stream : streamsDisconnected) {
+ ALOGD("%s() - disconnect(), port = %d", __func__, stream->getPortHandle());
stream->disconnect();
}
- mRegisteredStreams.swap(streamsDisconnected);
return streamsDisconnected;
}
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.cpp b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
index 9dab770..0843e0b 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.cpp
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
@@ -23,10 +23,10 @@
#include <map>
#include <mutex>
#include <sstream>
+#include <thread>
#include <utils/Singleton.h>
#include <vector>
-
#include "AAudioEndpointManager.h"
#include "AAudioServiceEndpoint.h"
@@ -36,7 +36,6 @@
#include "AAudioServiceEndpointPlay.h"
#include "AAudioServiceEndpointMMAP.h"
-
#define AAUDIO_BUFFER_CAPACITY_MIN 4 * 512
#define AAUDIO_SAMPLE_RATE_DEFAULT 48000
@@ -48,7 +47,6 @@
using namespace android; // TODO just import names needed
using namespace aaudio; // TODO just import names needed
-
AAudioServiceEndpointMMAP::AAudioServiceEndpointMMAP(AAudioService &audioService)
: mMmapStream(nullptr)
, mAAudioService(audioService) {}
@@ -318,9 +316,8 @@
return 0; // TODO
}
-// This is called by AudioFlinger when it wants to destroy a stream.
-void AAudioServiceEndpointMMAP::onTearDown(audio_port_handle_t portHandle) {
- ALOGD("%s(portHandle = %d) called", __func__, portHandle);
+// This is called by onTearDown() in a separate thread to avoid deadlocks.
+void AAudioServiceEndpointMMAP::handleTearDownAsync(audio_port_handle_t portHandle) {
// Are we tearing down the EXCLUSIVE MMAP stream?
if (isStreamRegistered(portHandle)) {
ALOGD("%s(%d) tearing down this entire MMAP endpoint", __func__, portHandle);
@@ -333,6 +330,13 @@
}
};
+// This is called by AudioFlinger when it wants to destroy a stream.
+void AAudioServiceEndpointMMAP::onTearDown(audio_port_handle_t portHandle) {
+ ALOGD("%s(portHandle = %d) called", __func__, portHandle);
+ std::thread asyncTask(&AAudioServiceEndpointMMAP::handleTearDownAsync, this, portHandle);
+ asyncTask.detach();
+}
+
void AAudioServiceEndpointMMAP::onVolumeChanged(audio_channel_mask_t channels,
android::Vector<float> values) {
// TODO Do we really need a different volume for each channel?
@@ -345,12 +349,20 @@
}
};
-void AAudioServiceEndpointMMAP::onRoutingChanged(audio_port_handle_t deviceId) {
+void AAudioServiceEndpointMMAP::onRoutingChanged(audio_port_handle_t portHandle) {
+ const int32_t deviceId = static_cast<int32_t>(portHandle);
ALOGD("%s() called with dev %d, old = %d", __func__, deviceId, getDeviceId());
- if (getDeviceId() != AUDIO_PORT_HANDLE_NONE && getDeviceId() != deviceId) {
- disconnectRegisteredStreams();
+ if (getDeviceId() != deviceId) {
+ if (getDeviceId() != AUDIO_PORT_HANDLE_NONE) {
+ std::thread asyncTask([this, deviceId]() {
+ disconnectRegisteredStreams();
+ setDeviceId(deviceId);
+ });
+ asyncTask.detach();
+ } else {
+ setDeviceId(deviceId);
+ }
}
- setDeviceId(deviceId);
};
/**
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.h b/services/oboeservice/AAudioServiceEndpointMMAP.h
index f599066..3d10861 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.h
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.h
@@ -68,13 +68,15 @@
aaudio_result_t getTimestamp(int64_t *positionFrames, int64_t *timeNanos) override;
+ void handleTearDownAsync(audio_port_handle_t portHandle);
+
// -------------- Callback functions for MmapStreamCallback ---------------------
- void onTearDown(audio_port_handle_t handle) override;
+ void onTearDown(audio_port_handle_t portHandle) override;
void onVolumeChanged(audio_channel_mask_t channels,
android::Vector<float> values) override;
- void onRoutingChanged(audio_port_handle_t deviceId) override;
+ void onRoutingChanged(audio_port_handle_t portHandle) override;
// ------------------------------------------------------------------------------
aaudio_result_t getDownDataDescription(AudioEndpointParcelable &parcelable);
diff --git a/services/oboeservice/AAudioServiceEndpointShared.cpp b/services/oboeservice/AAudioServiceEndpointShared.cpp
index 21253c8..dc21886 100644
--- a/services/oboeservice/AAudioServiceEndpointShared.cpp
+++ b/services/oboeservice/AAudioServiceEndpointShared.cpp
@@ -168,13 +168,11 @@
aaudio_result_t AAudioServiceEndpointShared::stopStream(sp<AAudioServiceStreamBase> sharedStream,
audio_port_handle_t clientHandle) {
- // Don't lock here because the disconnectRegisteredStreams also uses the lock.
-
// Ignore result.
(void) getStreamInternal()->stopClient(clientHandle);
if (--mRunningStreamCount == 0) { // atomic
- stopSharingThread();
+ stopSharingThread(); // the sharing thread locks mLockStreams
getStreamInternal()->requestStop();
}
return AAUDIO_OK;
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index dba9fb9..663dae2 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -24,6 +24,7 @@
#include <media/MediaMetricsItem.h>
#include <media/TypeConverter.h>
+#include <mediautils/SchedulingPolicyService.h>
#include "binding/IAAudioService.h"
#include "binding/AAudioServiceMessage.h"
@@ -169,11 +170,16 @@
}
aaudio_result_t AAudioServiceStreamBase::close() {
+ std::lock_guard<std::mutex> lock(mLock);
+ return close_l();
+}
+
+aaudio_result_t AAudioServiceStreamBase::close_l() {
if (getState() == AAUDIO_STREAM_STATE_CLOSED) {
return AAUDIO_OK;
}
- stop();
+ stop_l();
aaudio_result_t result = AAUDIO_OK;
sp<AAudioServiceEndpoint> endpoint = mServiceEndpointWeak.promote();
@@ -185,7 +191,7 @@
endpointManager.closeEndpoint(endpoint);
// AAudioService::closeStream() prevents two threads from closing at the same time.
- mServiceEndpoint.clear(); // endpoint will hold the pointer until this method returns.
+ mServiceEndpoint.clear(); // endpoint will hold the pointer after this method returns.
}
{
@@ -219,19 +225,28 @@
* An AAUDIO_SERVICE_EVENT_STARTED will be sent to the client when complete.
*/
aaudio_result_t AAudioServiceStreamBase::start() {
+ std::lock_guard<std::mutex> lock(mLock);
+
const int64_t beginNs = AudioClock::getNanoseconds();
aaudio_result_t result = AAUDIO_OK;
+ if (auto state = getState();
+ state == AAUDIO_STREAM_STATE_CLOSED || state == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ ALOGW("%s() already CLOSED, returns INVALID_STATE, handle = %d",
+ __func__, getHandle());
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_START)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, AudioGlobal_convertStreamStateToText(getState()))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)result)
.record(); });
if (isRunning()) {
- return AAUDIO_OK;
+ return result;
}
setFlowing(false);
@@ -254,21 +269,26 @@
return result;
error:
- disconnect();
+ disconnect_l();
return result;
}
aaudio_result_t AAudioServiceStreamBase::pause() {
- const int64_t beginNs = AudioClock::getNanoseconds();
+ std::lock_guard<std::mutex> lock(mLock);
+ return pause_l();
+}
+
+aaudio_result_t AAudioServiceStreamBase::pause_l() {
aaudio_result_t result = AAUDIO_OK;
if (!isRunning()) {
return result;
}
+ const int64_t beginNs = AudioClock::getNanoseconds();
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_PAUSE)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, AudioGlobal_convertStreamStateToText(getState()))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)result)
.record(); });
@@ -279,7 +299,7 @@
result = stopTimestampThread();
if (result != AAUDIO_OK) {
- disconnect();
+ disconnect_l();
return result;
}
@@ -292,7 +312,7 @@
result = endpoint->stopStream(this, mClientHandle);
if (result != AAUDIO_OK) {
ALOGE("%s() mServiceEndpoint returned %d, %s", __func__, result, getTypeText());
- disconnect(); // TODO should we return or pause Base first?
+ disconnect_l(); // TODO should we return or pause Base first?
}
sendServiceEvent(AAUDIO_SERVICE_EVENT_PAUSED);
@@ -301,16 +321,21 @@
}
aaudio_result_t AAudioServiceStreamBase::stop() {
- const int64_t beginNs = AudioClock::getNanoseconds();
+ std::lock_guard<std::mutex> lock(mLock);
+ return stop_l();
+}
+
+aaudio_result_t AAudioServiceStreamBase::stop_l() {
aaudio_result_t result = AAUDIO_OK;
if (!isRunning()) {
return result;
}
+ const int64_t beginNs = AudioClock::getNanoseconds();
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_STOP)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, AudioGlobal_convertStreamStateToText(getState()))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)result)
.record(); });
@@ -322,7 +347,7 @@
sendCurrentTimestamp(); // warning - this calls a virtual function
result = stopTimestampThread();
if (result != AAUDIO_OK) {
- disconnect();
+ disconnect_l();
return result;
}
@@ -336,7 +361,7 @@
result = endpoint->stopStream(this, mClientHandle);
if (result != AAUDIO_OK) {
ALOGE("%s() stopStream returned %d, %s", __func__, result, getTypeText());
- disconnect();
+ disconnect_l();
// TODO what to do with result here?
}
@@ -355,16 +380,17 @@
}
aaudio_result_t AAudioServiceStreamBase::flush() {
- const int64_t beginNs = AudioClock::getNanoseconds();
+ std::lock_guard<std::mutex> lock(mLock);
aaudio_result_t result = AAudio_isFlushAllowed(getState());
if (result != AAUDIO_OK) {
return result;
}
+ const int64_t beginNs = AudioClock::getNanoseconds();
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_FLUSH)
- .set(AMEDIAMETRICS_PROP_DURATIONNS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
+ .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, AudioGlobal_convertStreamStateToText(getState()))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)result)
.record(); });
@@ -404,16 +430,66 @@
}
void AAudioServiceStreamBase::disconnect() {
- if (getState() != AAUDIO_STREAM_STATE_DISCONNECTED) {
+ std::lock_guard<std::mutex> lock(mLock);
+ disconnect_l();
+}
+
+void AAudioServiceStreamBase::disconnect_l() {
+ if (getState() != AAUDIO_STREAM_STATE_DISCONNECTED
+ && getState() != AAUDIO_STREAM_STATE_CLOSED) {
+
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_DISCONNECT)
.set(AMEDIAMETRICS_PROP_STATE, AudioGlobal_convertStreamStateToText(getState()))
.record();
+
sendServiceEvent(AAUDIO_SERVICE_EVENT_DISCONNECTED);
setState(AAUDIO_STREAM_STATE_DISCONNECTED);
}
}
+aaudio_result_t AAudioServiceStreamBase::registerAudioThread(pid_t clientThreadId,
+ int priority) {
+ std::lock_guard<std::mutex> lock(mLock);
+ aaudio_result_t result = AAUDIO_OK;
+ if (getRegisteredThread() != AAudioServiceStreamBase::ILLEGAL_THREAD_ID) {
+ ALOGE("AAudioService::registerAudioThread(), thread already registered");
+ result = AAUDIO_ERROR_INVALID_STATE;
+ } else {
+ const pid_t ownerPid = IPCThreadState::self()->getCallingPid(); // TODO review
+ setRegisteredThread(clientThreadId);
+ int err = android::requestPriority(ownerPid, clientThreadId,
+ priority, true /* isForApp */);
+ if (err != 0) {
+ ALOGE("AAudioService::registerAudioThread(%d) failed, errno = %d, priority = %d",
+ clientThreadId, errno, priority);
+ result = AAUDIO_ERROR_INTERNAL;
+ }
+ }
+ return result;
+}
+
+aaudio_result_t AAudioServiceStreamBase::unregisterAudioThread(pid_t clientThreadId) {
+ std::lock_guard<std::mutex> lock(mLock);
+ aaudio_result_t result = AAUDIO_OK;
+ if (getRegisteredThread() != clientThreadId) {
+ ALOGE("%s(), wrong thread", __func__);
+ result = AAUDIO_ERROR_ILLEGAL_ARGUMENT;
+ } else {
+ setRegisteredThread(0);
+ }
+ return result;
+}
+
+void AAudioServiceStreamBase::setState(aaudio_stream_state_t state) {
+ // CLOSED is a final state.
+ if (mState != AAUDIO_STREAM_STATE_CLOSED) {
+ mState = state;
+ } else {
+ ALOGW_IF(mState != state, "%s(%d) when already CLOSED", __func__, state);
+ }
+}
+
aaudio_result_t AAudioServiceStreamBase::sendServiceEvent(aaudio_service_event_t event,
double dataDouble) {
AAudioServiceMessage command;
@@ -511,6 +587,7 @@
* used to communicate with the underlying HAL or Service.
*/
aaudio_result_t AAudioServiceStreamBase::getDescription(AudioEndpointParcelable &parcelable) {
+ std::lock_guard<std::mutex> lock(mLock);
{
std::lock_guard<std::mutex> lock(mUpMessageQueueLock);
if (mUpMessageQueue == nullptr) {
diff --git a/services/oboeservice/AAudioServiceStreamBase.h b/services/oboeservice/AAudioServiceStreamBase.h
index 79dd738..94cc980 100644
--- a/services/oboeservice/AAudioServiceStreamBase.h
+++ b/services/oboeservice/AAudioServiceStreamBase.h
@@ -77,7 +77,7 @@
// because we had to wait until we generated the handle.
void logOpen(aaudio_handle_t streamHandle);
- virtual aaudio_result_t close();
+ aaudio_result_t close();
/**
* Start the flow of audio data.
@@ -85,7 +85,7 @@
* This is not guaranteed to be synchronous but it currently is.
* An AAUDIO_SERVICE_EVENT_STARTED will be sent to the client when complete.
*/
- virtual aaudio_result_t start();
+ aaudio_result_t start();
/**
* Stop the flow of data so that start() can resume without loss of data.
@@ -93,7 +93,7 @@
* This is not guaranteed to be synchronous but it currently is.
* An AAUDIO_SERVICE_EVENT_PAUSED will be sent to the client when complete.
*/
- virtual aaudio_result_t pause();
+ aaudio_result_t pause();
/**
* Stop the flow of data after the currently queued data has finished playing.
@@ -102,17 +102,14 @@
* An AAUDIO_SERVICE_EVENT_STOPPED will be sent to the client when complete.
*
*/
- virtual aaudio_result_t stop();
-
- aaudio_result_t stopTimestampThread();
+ aaudio_result_t stop();
/**
* Discard any data held by the underlying HAL or Service.
*
* An AAUDIO_SERVICE_EVENT_FLUSHED will be sent to the client when complete.
*/
- virtual aaudio_result_t flush();
-
+ aaudio_result_t flush();
virtual aaudio_result_t startClient(const android::AudioClient& client,
const audio_attributes_t *attr __unused,
@@ -126,29 +123,19 @@
return AAUDIO_ERROR_UNAVAILABLE;
}
+ aaudio_result_t registerAudioThread(pid_t clientThreadId, int priority);
+
+ aaudio_result_t unregisterAudioThread(pid_t clientThreadId);
+
bool isRunning() const {
return mState == AAUDIO_STREAM_STATE_STARTED;
}
- // -------------------------------------------------------------------
-
- /**
- * Send a message to the client with an int64_t data value.
- */
- aaudio_result_t sendServiceEvent(aaudio_service_event_t event,
- int64_t dataLong = 0);
- /**
- * Send a message to the client with an double data value.
- */
- aaudio_result_t sendServiceEvent(aaudio_service_event_t event,
- double dataDouble);
-
/**
* Fill in a parcelable description of stream.
*/
aaudio_result_t getDescription(AudioEndpointParcelable &parcelable);
-
void setRegisteredThread(pid_t pid) {
mRegisteredClientThread = pid;
}
@@ -262,9 +249,13 @@
aaudio_result_t open(const aaudio::AAudioStreamRequest &request,
aaudio_sharing_mode_t sharingMode);
- void setState(aaudio_stream_state_t state) {
- mState = state;
- }
+ // These must be called under mLock
+ virtual aaudio_result_t close_l();
+ virtual aaudio_result_t pause_l();
+ virtual aaudio_result_t stop_l();
+ void disconnect_l();
+
+ void setState(aaudio_stream_state_t state);
/**
* Device specific startup.
@@ -319,6 +310,19 @@
private:
+ aaudio_result_t stopTimestampThread();
+
+ /**
+ * Send a message to the client with an int64_t data value.
+ */
+ aaudio_result_t sendServiceEvent(aaudio_service_event_t event,
+ int64_t dataLong = 0);
+ /**
+ * Send a message to the client with a double data value.
+ */
+ aaudio_result_t sendServiceEvent(aaudio_service_event_t event,
+ double dataDouble);
+
/**
* @return true if the queue is getting full.
*/
@@ -336,6 +340,10 @@
// This indicate that a running stream should not be processed because of an error,
// for example a full message queue. Note that this atomic is unrelated to mCloseNeeded.
std::atomic<bool> mSuspended{false};
+
+ // Locking order is important.
+ // Always acquire mLock before acquiring AAudioServiceEndpoint::mLockStreams
+ std::mutex mLock; // Prevent start/stop/close etcetera from colliding
};
} /* namespace aaudio */
diff --git a/services/oboeservice/AAudioServiceStreamMMAP.cpp b/services/oboeservice/AAudioServiceStreamMMAP.cpp
index 639a0a8..54d7d06 100644
--- a/services/oboeservice/AAudioServiceStreamMMAP.cpp
+++ b/services/oboeservice/AAudioServiceStreamMMAP.cpp
@@ -92,11 +92,11 @@
}
// Stop the flow of data such that start() can resume with loss of data.
-aaudio_result_t AAudioServiceStreamMMAP::pause() {
+aaudio_result_t AAudioServiceStreamMMAP::pause_l() {
if (!isRunning()) {
return AAUDIO_OK;
}
- aaudio_result_t result = AAudioServiceStreamBase::pause();
+ aaudio_result_t result = AAudioServiceStreamBase::pause_l();
// TODO put before base::pause()?
if (!mInService) {
(void) stopClient(mClientHandle);
@@ -104,11 +104,11 @@
return result;
}
-aaudio_result_t AAudioServiceStreamMMAP::stop() {
+aaudio_result_t AAudioServiceStreamMMAP::stop_l() {
if (!isRunning()) {
return AAUDIO_OK;
}
- aaudio_result_t result = AAudioServiceStreamBase::stop();
+ aaudio_result_t result = AAudioServiceStreamBase::stop_l();
// TODO put before base::stop()?
if (!mInService) {
(void) stopClient(mClientHandle);
diff --git a/services/oboeservice/AAudioServiceStreamMMAP.h b/services/oboeservice/AAudioServiceStreamMMAP.h
index 9105469..5902613 100644
--- a/services/oboeservice/AAudioServiceStreamMMAP.h
+++ b/services/oboeservice/AAudioServiceStreamMMAP.h
@@ -52,16 +52,6 @@
aaudio_result_t open(const aaudio::AAudioStreamRequest &request) override;
- /**
- * Stop the flow of data so that start() can resume without loss of data.
- *
- * This is not guaranteed to be synchronous but it currently is.
- * An AAUDIO_SERVICE_EVENT_PAUSED will be sent to the client when complete.
- */
- aaudio_result_t pause() override;
-
- aaudio_result_t stop() override;
-
aaudio_result_t startClient(const android::AudioClient& client,
const audio_attributes_t *attr,
audio_port_handle_t *clientHandle) override;
@@ -72,6 +62,16 @@
protected:
+ /**
+ * Stop the flow of data so that start() can resume without loss of data.
+ *
+ * This is not guaranteed to be synchronous but it currently is.
+ * An AAUDIO_SERVICE_EVENT_PAUSED will be sent to the client when complete.
+ */
+ aaudio_result_t pause_l() override;
+
+ aaudio_result_t stop_l() override;
+
aaudio_result_t getAudioDataDescription(AudioEndpointParcelable &parcelable) override;
aaudio_result_t getFreeRunningPosition(int64_t *positionFrames, int64_t *timeNanos) override;
diff --git a/services/oboeservice/AAudioServiceStreamShared.cpp b/services/oboeservice/AAudioServiceStreamShared.cpp
index 2ca847a..01b1c2e 100644
--- a/services/oboeservice/AAudioServiceStreamShared.cpp
+++ b/services/oboeservice/AAudioServiceStreamShared.cpp
@@ -203,9 +203,8 @@
return result;
}
-
-aaudio_result_t AAudioServiceStreamShared::close() {
- aaudio_result_t result = AAudioServiceStreamBase::close();
+aaudio_result_t AAudioServiceStreamShared::close_l() {
+ aaudio_result_t result = AAudioServiceStreamBase::close_l();
{
std::lock_guard<std::mutex> lock(mAudioDataQueueLock);
diff --git a/services/oboeservice/AAudioServiceStreamShared.h b/services/oboeservice/AAudioServiceStreamShared.h
index 61769b5..abcb782 100644
--- a/services/oboeservice/AAudioServiceStreamShared.h
+++ b/services/oboeservice/AAudioServiceStreamShared.h
@@ -52,7 +52,7 @@
aaudio_result_t open(const aaudio::AAudioStreamRequest &request) override;
- aaudio_result_t close() override;
+ aaudio_result_t close_l() override;
/**
* This must be locked when calling getAudioDataFifoBuffer_l() and while
diff --git a/services/oboeservice/AAudioStreamTracker.cpp b/services/oboeservice/AAudioStreamTracker.cpp
index 3328159..8e66b94 100644
--- a/services/oboeservice/AAudioStreamTracker.cpp
+++ b/services/oboeservice/AAudioStreamTracker.cpp
@@ -30,32 +30,20 @@
using namespace android;
using namespace aaudio;
-sp<AAudioServiceStreamBase> AAudioStreamTracker::decrementAndRemoveStreamByHandle(
+int32_t AAudioStreamTracker::removeStreamByHandle(
aaudio_handle_t streamHandle) {
std::lock_guard<std::mutex> lock(mHandleLock);
- sp<AAudioServiceStreamBase> serviceStream;
- auto it = mStreamsByHandle.find(streamHandle);
- if (it != mStreamsByHandle.end()) {
- sp<AAudioServiceStreamBase> tempStream = it->second;
- // Does the caller need to close the stream?
- // The reference count should never be negative.
- // But it is safer to check for <= 0 than == 0.
- if ((tempStream->decrementServiceReferenceCount_l() <= 0) && tempStream->isCloseNeeded()) {
- serviceStream = tempStream; // Only return stream if ready to be closed.
- mStreamsByHandle.erase(it);
- }
- }
- return serviceStream;
+ auto count = mStreamsByHandle.erase(streamHandle);
+ return static_cast<int32_t>(count);
}
-sp<AAudioServiceStreamBase> AAudioStreamTracker::getStreamByHandleAndIncrement(
+sp<AAudioServiceStreamBase> AAudioStreamTracker::getStreamByHandle(
aaudio_handle_t streamHandle) {
std::lock_guard<std::mutex> lock(mHandleLock);
sp<AAudioServiceStreamBase> serviceStream;
auto it = mStreamsByHandle.find(streamHandle);
if (it != mStreamsByHandle.end()) {
serviceStream = it->second;
- serviceStream->incrementServiceReferenceCount_l();
}
return serviceStream;
}
@@ -63,7 +51,7 @@
// The port handle is only available when the stream is started.
// So we have to iterate over all the streams.
// Luckily this rarely happens.
-sp<AAudioServiceStreamBase> AAudioStreamTracker::findStreamByPortHandleAndIncrement(
+sp<AAudioServiceStreamBase> AAudioStreamTracker::findStreamByPortHandle(
audio_port_handle_t portHandle) {
std::lock_guard<std::mutex> lock(mHandleLock);
sp<AAudioServiceStreamBase> serviceStream;
@@ -72,7 +60,6 @@
auto candidate = it->second;
if (candidate->getPortHandle() == portHandle) {
serviceStream = candidate;
- serviceStream->incrementServiceReferenceCount_l();
break;
}
it++;
diff --git a/services/oboeservice/AAudioStreamTracker.h b/services/oboeservice/AAudioStreamTracker.h
index 57ec426..d1301a2 100644
--- a/services/oboeservice/AAudioStreamTracker.h
+++ b/services/oboeservice/AAudioStreamTracker.h
@@ -32,25 +32,20 @@
public:
/**
- * Find the stream associated with the handle.
- * Decrement its reference counter. If zero and the stream needs
- * to be closed then remove the stream and return a pointer to the stream.
- * Otherwise return null if it does not need to be closed.
+ * Remove any streams with the matching handle.
*
* @param streamHandle
- * @return strong pointer to the stream if it needs to be closed, or nullptr
+ * @return number of streams removed
*/
- android::sp<AAudioServiceStreamBase> decrementAndRemoveStreamByHandle(
- aaudio_handle_t streamHandle);
+ int32_t removeStreamByHandle(aaudio_handle_t streamHandle);
/**
* Look up a stream based on the handle.
- * Increment its service reference count if found.
*
* @param streamHandle
* @return strong pointer to the stream if found, or nullptr
*/
- android::sp<aaudio::AAudioServiceStreamBase> getStreamByHandleAndIncrement(
+ android::sp<aaudio::AAudioServiceStreamBase> getStreamByHandle(
aaudio_handle_t streamHandle);
/**
@@ -60,7 +55,7 @@
* @param portHandle
* @return strong pointer to the stream if found, or nullptr
*/
- android::sp<aaudio::AAudioServiceStreamBase> findStreamByPortHandleAndIncrement(
+ android::sp<aaudio::AAudioServiceStreamBase> findStreamByPortHandle(
audio_port_handle_t portHandle);
/**
diff --git a/tools/mainline_hook_partial.sh b/tools/mainline_hook_partial.sh
new file mode 100755
index 0000000..3dc6163
--- /dev/null
+++ b/tools/mainline_hook_partial.sh
@@ -0,0 +1,200 @@
+#!/bin/bash
+#set -x
+
+# used for projects where some files are mainline, some are not
+# we get a list of the files/directories out of the project's root.
+#
+# invocation $0 ${repo_root} ${preupload_files}
+#
+# Example PREUPLOAD.cfg:
+#
+# [Hook Scripts]
+# mainline_hook = ${REPO_ROOT}/frameworks/av/tools/mainline_hook_partial.sh ${REPO_ROOT} ${PREUPLOAD_FILES}
+#
+# MainlineFiles.cfg syntax:
+#
+# ignore comment (#) lines and blank lines
+# rest are path prefixes starting at root of the project
+# (so OWNERS, not frameworks/av/OWNERS)
+#
+# path
+# INCLUDE path
+# EXCLUDE path
+#
+# 'path' and 'INCLUDE path' are identical -- they both indicate that this path
+# is part of mainline
+# EXCLUDE indicates that this is not part of mainline,
+# so 'foo/' and 'EXCLUDE foo/nope'
+# means everything under foo/ is part of mainline EXCEPT foo/nope.
+# INCLUDE/EXCLUDE/INCLUDE nested structuring is not supported
+#
+# matching is purely prefix
+# so 'foo' will match 'foo', 'foo.c', 'foo/bar/baz'
+# if you want to exclude a directory, best to use a pattern like "foo/"
+#
+
+## tunables:
+##
+DEV_BRANCH=rvc-dev
+filelist_file=MainlineFiles.cfg
+
+###
+
+REPO_ROOT=$1; shift
+# the rest of the command line is the file list
+PREUPLOAD_FILES="$*"
+
+RED=$(tput setaf 1)
+NORMAL=$(tput sgr0)
+
+## get the active branch:
+## * <localbranch> <shainfo> [goog/master] Fix to handle missing checks on error returned
+## strip this down to "master"
+##
+current=`git branch -vv | grep -P "^\*[^\[]+\[goog/"|sed -e 's/^.*\[//' | sed -e 's/:.*$//'| sed -e 's/^goog\///'`
+if [ "${current}" = "" ] ; then
+ current=unknown
+fi
+
+## figure out whether which files are for mainline and which are not
+if [ "${PREUPLOAD_FILES}" = "" ] ; then
+ # empty files? what's up there, i suppose we'll let that go
+ exit 0
+fi
+
+## get the list of files out of the project's root
+## figure out which way I'm going ..
+## use list of files to scan PREUPLOAD_FILES
+## use PREUPLOAD_FILES to scan the list of good/bad from the project root
+##
+## remember to do an exclude, so I can say
+## include/these/files/
+## EXCLUDE include/these/files/nested/
+##
+## and it should all be prefix based stuff...
+
+if [ ! -f ${REPO_ROOT}/${REPO_PATH}/${filelist_file} ] ; then
+ echo "Poorly Configured project, missing ${filelist_file} in root of project"
+ exit 1
+fi
+
+# is 1st arg a prefix of 2nd arg
+beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
+
+exclusions=""
+inclusions=""
+while read p1 p2
+do
+ # ignore comment lines in the file
+ # ignore empty lines in the file
+ if beginswith "#" "${p1}" ; then
+ # ignore this line
+ true
+ elif [ -z "${p1}" ] ; then
+ # ignore blanks
+ true
+ elif [ ${p1} = "EXCLUDE" ] ; then
+ # add to the exclusion list
+ if [ ! -z ${p2} ] ; then
+ exlusions="${exclusions} ${p2}"
+ fi
+ elif [ ${p1} = "INCLUDE" ] ; then
+ # add to the inclusion list
+ if [ ! -z ${p2} ] ; then
+ inclusions="${inclusions} ${p2}"
+ fi
+ elif [ ! -z ${p1} ] ; then
+ inclusions="${inclusions} ${p1}"
+ fi
+done < ${REPO_ROOT}/${REPO_PATH}/${filelist_file}
+
+# so we can play with array syntax
+#INCLUSIONS=( ${inclusions} )
+#EXCLUSIONS=( ${exclusions} )
+
+mainline_yes=""
+mainline_no=""
+
+# is it part of the list of mainline files/directories?
+for path in ${PREUPLOAD_FILES} ; do
+ #echo is ${path} a mainline file...
+ for aprefix in ${inclusions} .. ; do
+ #echo compare against ${aprefix} ...
+ if [ "${aprefix}" = ".." ] ; then
+ mainline_no="${mainline_no} ${path}"
+ elif beginswith ${aprefix} ${path} ; then
+ mainline_yes="${mainline_yes} ${path}"
+ break # on to next uploaded file
+ fi
+ done
+done
+
+# TODO: audit the yes list to see if some should be moved to the no list
+
+# 3 situations
+# -- everything is on mainline (mainline_yes non-empty, other empty)
+# -- some is mainline, some is not (files_* both non-empty)
+# -- none is mainline (mainline_yes empty, other non_empty
+# -- both empty only happens if PREUPLOAD_FILES is empty, covered above
+
+if [ -z "${mainline_yes}" ] ; then
+ # no mainline files, everything else is non-mainline, let it go
+ exit 0
+fi
+
+result=0
+if [ ! -z "${mainline_no}" ] ; then
+ # mixed bag, suggest (not insist) that developer split them.
+ result=1
+ cat - <<EOF
+This CL contains files contains both mainline and non-mainline files. Consider separating
+them into separate CLs. It may also be appropriate to update the list of mainline
+files in ${RED}${REPO_ROOT}/${filelist_file}${NORMAL}.
+
+EOF
+ echo "===== Mainline files ====="
+ echo -e ${RED}
+ echo ${mainline_yes} | sed -e 's/ /
/g'
+ echo -e ${NORMAL}
+
+ echo "===== Non-Mainline files ====="
+ echo -e ${RED}
+ echo ${mainline_no} | sed -e 's/ /
/g'
+ echo -e ${NORMAL}
+
+fi
+
+if [ "${current}" != "${DEV_BRANCH}" ] ; then
+ # Change is not in the desired mainline dev branch
+ result=1
+
+ #echo -e "${RED}"
+ cat - <<EOF
+
+You are uploading repo ${RED}${REPO_PATH}${NORMAL} to branch ${RED}${current}${NORMAL}.
+The source of truth for ${RED}${REPO_PATH}${NORMAL} is branch ${RED}${DEV_BRANCH}${NORMAL}.
+
+Please upload this change to branch ${RED}${DEV_BRANCH}${NORMAL} unless one or more of
+the following apply:
+- this is a security bug prohibited from disclosure before the next dessert release.
+ (moderate security bugs fall into this category).
+- this is new functionality prohibitied from disclosure before the next dessert release.
+EOF
+ #echo -e "${NORMAL}"
+
+fi
+
+## since stdout is buffered in a way that complicates the below, we're just going
+## to tell the user what they can do to get around this check instead of asking them
+## as part of this run of the command.
+
+if [ ${result} != 0 ] ; then
+ cat - <<EOF
+
+If you are sure you want to proceed uploading to branch ${RED}${current}${NORMAL},
+re-run your repo upload command with the '--no-verify' option
+
+EOF
+fi
+exit ${result}
+
diff --git a/tools/mainline_hook_project.sh b/tools/mainline_hook_project.sh
new file mode 100755
index 0000000..8d35470
--- /dev/null
+++ b/tools/mainline_hook_project.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+#set -x
+
+# called for repo projects that are part of the media mainline modules
+# this is for projects where the entire project is part of mainline.
+# we have a separate script for projects where only part of that project gets
+# pulled into mainline.
+#
+# if the project's PREUPLOAD.cfg points to this script, it is by definition a project
+# which is entirely within mainline.
+#
+# example PREUPLOAD.cfg using this script
+# [Hook Scripts]
+# mainline_hook = ${REPO_ROOT}/frameworks/av/tools/mainline_hook_project.sh
+#
+
+
+# tunables
+DEV_BRANCH=rvc-dev
+
+###
+RED=$(tput setaf 1)
+NORMAL=$(tput sgr0)
+
+## check the active branch:
+## * b131183694 d198c6a [goog/master] Fix to handle missing checks on error returned
+##
+current=`git branch -vv | grep -P "^\*[^\[]+\[goog/"|sed -e 's/^.*\[//' | sed -e 's/:.*$//'| sed -e 's/^goog\///'`
+if [ "${current}" = "" ] ; then
+ current=unknown
+fi
+
+if [ "${current}" = "${DEV_BRANCH}" ] ; then
+ # Change appears to be in mainline dev branch
+ exit 0
+fi
+
+## warn the user that about not being on the typical/desired branch.
+
+cat - <<EOF
+
+You are uploading repo ${RED}${REPO_PATH}${NORMAL} to branch ${RED}${current}${NORMAL}.
+The source of truth for ${RED}${REPO_PATH}${NORMAL} is branch ${RED}${DEV_BRANCH}${NORMAL}.
+
+Please upload this change to branch ${RED}${DEV_BRANCH}${NORMAL} unless one or more of
+the following apply:
+- this is a security bug prohibited from disclosure before the next dessert release.
+ (moderate security bugs fall into this category).
+- this is new functionality prohibitied from disclosure before the next dessert release.
+EOF
+
+
+##
+## TODO: prompt the user y/n to continue right now instead of re-invoking with no-verify
+## this has to get around how repo buffers stdout from this script such that the output
+## is not flushed before we try to read the input.
+##
+
+cat - <<EOF
+If you are sure you want to proceed uploading to branch ${RED}${current}${NORMAL},
+re-run your repo upload command with the '--no-verify' option
+
+EOF
+exit 1
+