Merge "MatroskaExtractor: release buffers in error cases" into rvc-dev am: 0ad9221e8a am: cd55c15ee8
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/av/+/11388502
Change-Id: Ibae4a77476ee27065a635df284d3cb0ac2b0d8fe
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: NOTICE
+}
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..8fe48c2 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,11 @@
[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}
+
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+# Only turn on clang-format check for the following subfolders.
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+ media/libmediatranscoding/
+ services/mediatranscoding/
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/Android.bp b/camera/Android.bp
index fa36bb3..b777d74 100644
--- a/camera/Android.bp
+++ b/camera/Android.bp
@@ -38,7 +38,6 @@
"ICamera.cpp",
"ICameraClient.cpp",
"ICameraRecordingProxy.cpp",
- "ICameraRecordingProxyListener.cpp",
"camera2/CaptureRequest.cpp",
"camera2/ConcurrentCamera.cpp",
"camera2/OutputConfiguration.cpp",
diff --git a/camera/Camera.cpp b/camera/Camera.cpp
index 84d1d93..f7d194e 100644
--- a/camera/Camera.cpp
+++ b/camera/Camera.cpp
@@ -25,7 +25,6 @@
#include <binder/IMemory.h>
#include <Camera.h>
-#include <ICameraRecordingProxyListener.h>
#include <android/hardware/ICameraService.h>
#include <android/hardware/ICamera.h>
@@ -77,63 +76,6 @@
return CameraBaseT::connect(cameraId, clientPackageName, clientUid, clientPid);
}
-status_t Camera::connectLegacy(int cameraId, int halVersion,
- const String16& clientPackageName,
- int clientUid,
- sp<Camera>& camera)
-{
- ALOGV("%s: connect legacy camera device", __FUNCTION__);
- sp<Camera> c = new Camera(cameraId);
- sp<::android::hardware::ICameraClient> cl = c;
- status_t status = NO_ERROR;
- const sp<::android::hardware::ICameraService>& cs = CameraBaseT::getCameraService();
-
- binder::Status ret;
- if (cs != nullptr) {
- ret = cs.get()->connectLegacy(cl, cameraId, halVersion, clientPackageName,
- clientUid, /*out*/&(c->mCamera));
- }
- if (ret.isOk() && c->mCamera != nullptr) {
- IInterface::asBinder(c->mCamera)->linkToDeath(c);
- c->mStatus = NO_ERROR;
- camera = c;
- } else {
- switch(ret.serviceSpecificErrorCode()) {
- case hardware::ICameraService::ERROR_DISCONNECTED:
- status = -ENODEV;
- break;
- case hardware::ICameraService::ERROR_CAMERA_IN_USE:
- status = -EBUSY;
- break;
- case hardware::ICameraService::ERROR_INVALID_OPERATION:
- status = -EINVAL;
- break;
- case hardware::ICameraService::ERROR_MAX_CAMERAS_IN_USE:
- status = -EUSERS;
- break;
- case hardware::ICameraService::ERROR_ILLEGAL_ARGUMENT:
- status = BAD_VALUE;
- break;
- case hardware::ICameraService::ERROR_DEPRECATED_HAL:
- status = -EOPNOTSUPP;
- break;
- case hardware::ICameraService::ERROR_DISABLED:
- status = -EACCES;
- break;
- case hardware::ICameraService::ERROR_PERMISSION_DENIED:
- status = PERMISSION_DENIED;
- break;
- default:
- status = -EINVAL;
- ALOGW("An error occurred while connecting to camera %d: %s", cameraId,
- (cs != nullptr) ? "Service not available" : ret.toString8().string());
- break;
- }
- c.clear();
- }
- return status;
-}
-
status_t Camera::reconnect()
{
ALOGV("reconnect");
@@ -214,10 +156,6 @@
void Camera::stopRecording()
{
ALOGV("stopRecording");
- {
- Mutex::Autolock _l(mLock);
- mRecordingProxyListener.clear();
- }
sp <::android::hardware::ICamera> c = mCamera;
if (c == 0) return;
c->stopRecording();
@@ -325,12 +263,6 @@
mListener = listener;
}
-void Camera::setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener)
-{
- Mutex::Autolock _l(mLock);
- mRecordingProxyListener = listener;
-}
-
void Camera::setPreviewCallbackFlags(int flag)
{
ALOGV("setPreviewCallbackFlags");
@@ -384,19 +316,6 @@
// callback from camera service when timestamped frame is ready
void Camera::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr)
{
- // If recording proxy listener is registered, forward the frame and return.
- // The other listener (mListener) is ignored because the receiver needs to
- // call releaseRecordingFrame.
- sp<ICameraRecordingProxyListener> proxylistener;
- {
- Mutex::Autolock _l(mLock);
- proxylistener = mRecordingProxyListener;
- }
- if (proxylistener != NULL) {
- proxylistener->dataCallbackTimestamp(timestamp, msgType, dataPtr);
- return;
- }
-
sp<CameraListener> listener;
{
Mutex::Autolock _l(mLock);
@@ -413,19 +332,6 @@
void Camera::recordingFrameHandleCallbackTimestamp(nsecs_t timestamp, native_handle_t* handle)
{
- // If recording proxy listener is registered, forward the frame and return.
- // The other listener (mListener) is ignored because the receiver needs to
- // call releaseRecordingFrameHandle.
- sp<ICameraRecordingProxyListener> proxylistener;
- {
- Mutex::Autolock _l(mLock);
- proxylistener = mRecordingProxyListener;
- }
- if (proxylistener != NULL) {
- proxylistener->recordingFrameHandleCallbackTimestamp(timestamp, handle);
- return;
- }
-
sp<CameraListener> listener;
{
Mutex::Autolock _l(mLock);
@@ -444,19 +350,6 @@
const std::vector<nsecs_t>& timestamps,
const std::vector<native_handle_t*>& handles)
{
- // If recording proxy listener is registered, forward the frame and return.
- // The other listener (mListener) is ignored because the receiver needs to
- // call releaseRecordingFrameHandle.
- sp<ICameraRecordingProxyListener> proxylistener;
- {
- Mutex::Autolock _l(mLock);
- proxylistener = mRecordingProxyListener;
- }
- if (proxylistener != NULL) {
- proxylistener->recordingFrameHandleCallbackTimestampBatch(timestamps, handles);
- return;
- }
-
sp<CameraListener> listener;
{
Mutex::Autolock _l(mLock);
@@ -476,10 +369,9 @@
return new RecordingProxy(this);
}
-status_t Camera::RecordingProxy::startRecording(const sp<ICameraRecordingProxyListener>& listener)
+status_t Camera::RecordingProxy::startRecording()
{
ALOGV("RecordingProxy::startRecording");
- mCamera->setRecordingProxyListener(listener);
mCamera->reconnect();
return mCamera->startRecording();
}
@@ -490,23 +382,6 @@
mCamera->stopRecording();
}
-void Camera::RecordingProxy::releaseRecordingFrame(const sp<IMemory>& mem)
-{
- ALOGV("RecordingProxy::releaseRecordingFrame");
- mCamera->releaseRecordingFrame(mem);
-}
-
-void Camera::RecordingProxy::releaseRecordingFrameHandle(native_handle_t* handle) {
- ALOGV("RecordingProxy::releaseRecordingFrameHandle");
- mCamera->releaseRecordingFrameHandle(handle);
-}
-
-void Camera::RecordingProxy::releaseRecordingFrameHandleBatch(
- const std::vector<native_handle_t*>& handles) {
- ALOGV("RecordingProxy::releaseRecordingFrameHandleBatch");
- mCamera->releaseRecordingFrameHandleBatch(handles);
-}
-
Camera::RecordingProxy::RecordingProxy(const sp<Camera>& camera)
{
mCamera = camera;
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/CameraParameters.cpp b/camera/CameraParameters.cpp
index 68969cf..e95c91c 100644
--- a/camera/CameraParameters.cpp
+++ b/camera/CameraParameters.cpp
@@ -20,6 +20,7 @@
#include <string.h>
#include <stdlib.h>
+#include <unistd.h>
#include <camera/CameraParameters.h>
#include <system/graphics.h>
diff --git a/camera/CameraParameters2.cpp b/camera/CameraParameters2.cpp
index c29233c..a1cf355 100644
--- a/camera/CameraParameters2.cpp
+++ b/camera/CameraParameters2.cpp
@@ -21,6 +21,7 @@
#include <string.h>
#include <stdlib.h>
+#include <unistd.h>
#include <camera/CameraParameters2.h>
namespace android {
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/ICameraRecordingProxy.cpp b/camera/ICameraRecordingProxy.cpp
index bd6af75..97523a5 100644
--- a/camera/ICameraRecordingProxy.cpp
+++ b/camera/ICameraRecordingProxy.cpp
@@ -18,7 +18,6 @@
#define LOG_TAG "ICameraRecordingProxy"
#include <camera/CameraUtils.h>
#include <camera/ICameraRecordingProxy.h>
-#include <camera/ICameraRecordingProxyListener.h>
#include <binder/IMemory.h>
#include <binder/Parcel.h>
#include <media/hardware/HardwareAPI.h>
@@ -29,10 +28,7 @@
enum {
START_RECORDING = IBinder::FIRST_CALL_TRANSACTION,
- STOP_RECORDING,
- RELEASE_RECORDING_FRAME,
- RELEASE_RECORDING_FRAME_HANDLE,
- RELEASE_RECORDING_FRAME_HANDLE_BATCH,
+ STOP_RECORDING
};
@@ -44,12 +40,11 @@
{
}
- status_t startRecording(const sp<ICameraRecordingProxyListener>& listener)
+ status_t startRecording()
{
ALOGV("startRecording");
Parcel data, reply;
data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
- data.writeStrongBinder(IInterface::asBinder(listener));
remote()->transact(START_RECORDING, data, &reply);
return reply.readInt32();
}
@@ -61,46 +56,6 @@
data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
remote()->transact(STOP_RECORDING, data, &reply);
}
-
- void releaseRecordingFrame(const sp<IMemory>& mem)
- {
- ALOGV("releaseRecordingFrame");
- Parcel data, reply;
- data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
- data.writeStrongBinder(IInterface::asBinder(mem));
- remote()->transact(RELEASE_RECORDING_FRAME, data, &reply);
- }
-
- void releaseRecordingFrameHandle(native_handle_t *handle) {
- ALOGV("releaseRecordingFrameHandle");
- Parcel data, reply;
- data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
- data.writeNativeHandle(handle);
-
- remote()->transact(RELEASE_RECORDING_FRAME_HANDLE, data, &reply);
-
- // Close the native handle because camera received a dup copy.
- native_handle_close(handle);
- native_handle_delete(handle);
- }
-
- void releaseRecordingFrameHandleBatch(const std::vector<native_handle_t*>& handles) {
- ALOGV("releaseRecordingFrameHandleBatch");
- Parcel data, reply;
- data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
- uint32_t n = handles.size();
- data.writeUint32(n);
- for (auto& handle : handles) {
- data.writeNativeHandle(handle);
- }
- remote()->transact(RELEASE_RECORDING_FRAME_HANDLE_BATCH, data, &reply);
-
- // Close the native handle because camera received a dup copy.
- for (auto& handle : handles) {
- native_handle_close(handle);
- native_handle_delete(handle);
- }
- }
};
IMPLEMENT_META_INTERFACE(CameraRecordingProxy, "android.hardware.ICameraRecordingProxy");
@@ -114,9 +69,7 @@
case START_RECORDING: {
ALOGV("START_RECORDING");
CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
- sp<ICameraRecordingProxyListener> listener =
- interface_cast<ICameraRecordingProxyListener>(data.readStrongBinder());
- reply->writeInt32(startRecording(listener));
+ reply->writeInt32(startRecording());
return NO_ERROR;
} break;
case STOP_RECORDING: {
@@ -125,46 +78,6 @@
stopRecording();
return NO_ERROR;
} break;
- case RELEASE_RECORDING_FRAME: {
- ALOGV("RELEASE_RECORDING_FRAME");
- CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
- sp<IMemory> mem = interface_cast<IMemory>(data.readStrongBinder());
- releaseRecordingFrame(mem);
- return NO_ERROR;
- } break;
- case RELEASE_RECORDING_FRAME_HANDLE: {
- ALOGV("RELEASE_RECORDING_FRAME_HANDLE");
- CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
-
- // releaseRecordingFrameHandle will be responsble to close the native handle.
- releaseRecordingFrameHandle(data.readNativeHandle());
- return NO_ERROR;
- } break;
- case RELEASE_RECORDING_FRAME_HANDLE_BATCH: {
- ALOGV("RELEASE_RECORDING_FRAME_HANDLE_BATCH");
- CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
- uint32_t n = 0;
- status_t res = data.readUint32(&n);
- if (res != OK) {
- ALOGE("%s: Failed to read batch size: %s (%d)", __FUNCTION__, strerror(-res), res);
- return BAD_VALUE;
- }
- std::vector<native_handle_t*> handles;
- handles.reserve(n);
- for (uint32_t i = 0; i < n; i++) {
- native_handle_t* handle = data.readNativeHandle();
- if (handle == nullptr) {
- ALOGE("%s: Received a null native handle at handles[%d]",
- __FUNCTION__, i);
- return BAD_VALUE;
- }
- handles.push_back(handle);
- }
-
- // releaseRecordingFrameHandleBatch will be responsble to close the native handle.
- releaseRecordingFrameHandleBatch(handles);
- return NO_ERROR;
- } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
@@ -173,4 +86,3 @@
// ----------------------------------------------------------------------------
}; // namespace android
-
diff --git a/camera/ICameraRecordingProxyListener.cpp b/camera/ICameraRecordingProxyListener.cpp
deleted file mode 100644
index 66faf8f..0000000
--- a/camera/ICameraRecordingProxyListener.cpp
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2011 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 "ICameraRecordingProxyListener"
-#include <camera/CameraUtils.h>
-#include <camera/ICameraRecordingProxyListener.h>
-#include <binder/IMemory.h>
-#include <binder/Parcel.h>
-#include <media/hardware/HardwareAPI.h>
-#include <utils/Log.h>
-
-namespace android {
-
-enum {
- DATA_CALLBACK_TIMESTAMP = IBinder::FIRST_CALL_TRANSACTION,
- RECORDING_FRAME_HANDLE_CALLBACK_TIMESTAMP,
- RECORDING_FRAME_HANDLE_CALLBACK_TIMESTAMP_BATCH
-};
-
-class BpCameraRecordingProxyListener: public BpInterface<ICameraRecordingProxyListener>
-{
-public:
- explicit BpCameraRecordingProxyListener(const sp<IBinder>& impl)
- : BpInterface<ICameraRecordingProxyListener>(impl)
- {
- }
-
- void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& imageData)
- {
- ALOGV("dataCallback");
- Parcel data, reply;
- data.writeInterfaceToken(ICameraRecordingProxyListener::getInterfaceDescriptor());
- data.writeInt64(timestamp);
- data.writeInt32(msgType);
- data.writeStrongBinder(IInterface::asBinder(imageData));
- remote()->transact(DATA_CALLBACK_TIMESTAMP, data, &reply, IBinder::FLAG_ONEWAY);
- }
-
- void recordingFrameHandleCallbackTimestamp(nsecs_t timestamp, native_handle_t* handle) {
- ALOGV("recordingFrameHandleCallbackTimestamp");
- Parcel data, reply;
- data.writeInterfaceToken(ICameraRecordingProxyListener::getInterfaceDescriptor());
- data.writeInt64(timestamp);
- data.writeNativeHandle(handle);
- remote()->transact(RECORDING_FRAME_HANDLE_CALLBACK_TIMESTAMP, data, &reply,
- IBinder::FLAG_ONEWAY);
-
- // The native handle is dupped in ICameraClient so we need to free it here.
- native_handle_close(handle);
- native_handle_delete(handle);
- }
-
- void recordingFrameHandleCallbackTimestampBatch(
- const std::vector<nsecs_t>& timestamps,
- const std::vector<native_handle_t*>& handles) {
- ALOGV("recordingFrameHandleCallbackTimestampBatch");
- Parcel data, reply;
- data.writeInterfaceToken(ICameraRecordingProxyListener::getInterfaceDescriptor());
-
- uint32_t n = timestamps.size();
- if (n != handles.size()) {
- ALOGE("%s: size of timestamps(%zu) and handles(%zu) mismatch!",
- __FUNCTION__, timestamps.size(), handles.size());
- return;
- }
- data.writeUint32(n);
- for (auto ts : timestamps) {
- data.writeInt64(ts);
- }
- for (auto& handle : handles) {
- data.writeNativeHandle(handle);
- }
- remote()->transact(RECORDING_FRAME_HANDLE_CALLBACK_TIMESTAMP_BATCH, data, &reply,
- IBinder::FLAG_ONEWAY);
-
- // The native handle is dupped in ICameraClient so we need to free it here.
- for (auto& handle : handles) {
- native_handle_close(handle);
- native_handle_delete(handle);
- }
- }
-};
-
-IMPLEMENT_META_INTERFACE(CameraRecordingProxyListener, "android.hardware.ICameraRecordingProxyListener");
-
-// ----------------------------------------------------------------------
-
-status_t BnCameraRecordingProxyListener::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
- switch(code) {
- case DATA_CALLBACK_TIMESTAMP: {
- ALOGV("DATA_CALLBACK_TIMESTAMP");
- CHECK_INTERFACE(ICameraRecordingProxyListener, data, reply);
- nsecs_t timestamp = data.readInt64();
- int32_t msgType = data.readInt32();
- sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
- dataCallbackTimestamp(timestamp, msgType, imageData);
- return NO_ERROR;
- } break;
- case RECORDING_FRAME_HANDLE_CALLBACK_TIMESTAMP: {
- ALOGV("RECORDING_FRAME_HANDLE_CALLBACK_TIMESTAMP");
- CHECK_INTERFACE(ICameraRecordingProxyListener, data, reply);
- nsecs_t timestamp;
- status_t res = data.readInt64(×tamp);
- if (res != OK) {
- ALOGE("%s: Failed to read timestamp: %s (%d)", __FUNCTION__, strerror(-res), res);
- return BAD_VALUE;
- }
-
- native_handle_t* handle = data.readNativeHandle();
- if (handle == nullptr) {
- ALOGE("%s: Received a null native handle", __FUNCTION__);
- return BAD_VALUE;
- }
- // The native handle will be freed in
- // BpCameraRecordingProxy::releaseRecordingFrameHandle.
- recordingFrameHandleCallbackTimestamp(timestamp, handle);
- return NO_ERROR;
- } break;
- case RECORDING_FRAME_HANDLE_CALLBACK_TIMESTAMP_BATCH: {
- ALOGV("RECORDING_FRAME_HANDLE_CALLBACK_TIMESTAMP_BATCH");
- CHECK_INTERFACE(ICameraRecordingProxyListener, data, reply);
- uint32_t n = 0;
- status_t res = data.readUint32(&n);
- if (res != OK) {
- ALOGE("%s: Failed to read batch size: %s (%d)", __FUNCTION__, strerror(-res), res);
- return BAD_VALUE;
- }
- std::vector<nsecs_t> timestamps;
- std::vector<native_handle_t*> handles;
- timestamps.reserve(n);
- handles.reserve(n);
- for (uint32_t i = 0; i < n; i++) {
- nsecs_t t;
- res = data.readInt64(&t);
- if (res != OK) {
- ALOGE("%s: Failed to read timestamp[%d]: %s (%d)",
- __FUNCTION__, i, strerror(-res), res);
- return BAD_VALUE;
- }
- timestamps.push_back(t);
- }
- for (uint32_t i = 0; i < n; i++) {
- native_handle_t* handle = data.readNativeHandle();
- if (handle == nullptr) {
- ALOGE("%s: Received a null native handle at handles[%d]",
- __FUNCTION__, i);
- return BAD_VALUE;
- }
- handles.push_back(handle);
- }
- // The native handle will be freed in
- // BpCameraRecordingProxy::releaseRecordingFrameHandleBatch.
- recordingFrameHandleCallbackTimestampBatch(timestamps, handles);
- return NO_ERROR;
- } break;
- default:
- return BBinder::onTransact(code, data, reply, flags);
- }
-}
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
diff --git a/camera/TEST_MAPPING b/camera/TEST_MAPPING
new file mode 100644
index 0000000..683e183
--- /dev/null
+++ b/camera/TEST_MAPPING
@@ -0,0 +1,11 @@
+{
+ "postsubmit": [
+ {
+ "name": "CtsCameraTestCases"
+ },
+ {
+ "name": "CtsCameraTestCases",
+ "keywords": ["primary-device"]
+ }
+ ]
+}
diff --git a/camera/aidl/android/hardware/ICameraService.aidl b/camera/aidl/android/hardware/ICameraService.aidl
index ac7a35b..8af704d 100644
--- a/camera/aidl/android/hardware/ICameraService.aidl
+++ b/camera/aidl/android/hardware/ICameraService.aidl
@@ -69,7 +69,7 @@
/**
* Default UID/PID values for non-privileged callers of
- * connect(), connectDevice(), and connectLegacy()
+ * connect() and connectDevice()
*/
const int USE_CALLING_UID = -1;
const int USE_CALLING_PID = -1;
@@ -93,20 +93,6 @@
int clientUid);
/**
- * halVersion constant for connectLegacy
- */
- const int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1;
-
- /**
- * Open a camera device in legacy mode, if supported by the camera module HAL.
- */
- ICamera connectLegacy(ICameraClient client,
- int cameraId,
- int halVersion,
- String opPackageName,
- int clientUid);
-
- /**
* Add listener for changes to camera device and flashlight state.
*
* Also returns the set of currently-known camera IDs and state of each device.
diff --git a/camera/cameraserver/Android.bp b/camera/cameraserver/Android.bp
index dc7f88a..09a333b 100644
--- a/camera/cameraserver/Android.bp
+++ b/camera/cameraserver/Android.bp
@@ -37,7 +37,7 @@
"android.hardware.camera.device@3.2",
"android.hardware.camera.device@3.4",
],
- compile_multilib: "32",
+ compile_multilib: "prefer32",
cflags: [
"-Wall",
"-Wextra",
diff --git a/camera/include/camera/Camera.h b/camera/include/camera/Camera.h
index 2cdb617..5579183 100644
--- a/camera/include/camera/Camera.h
+++ b/camera/include/camera/Camera.h
@@ -24,7 +24,6 @@
#include <gui/IGraphicBufferProducer.h>
#include <system/camera.h>
#include <camera/ICameraRecordingProxy.h>
-#include <camera/ICameraRecordingProxyListener.h>
#include <camera/android/hardware/ICamera.h>
#include <camera/android/hardware/ICameraClient.h>
#include <camera/CameraBase.h>
@@ -84,10 +83,6 @@
const String16& clientPackageName,
int clientUid, int clientPid);
- static status_t connectLegacy(int cameraId, int halVersion,
- const String16& clientPackageName,
- int clientUid, sp<Camera>& camera);
-
virtual ~Camera();
status_t reconnect();
@@ -154,7 +149,6 @@
status_t setVideoTarget(const sp<IGraphicBufferProducer>& bufferProducer);
void setListener(const sp<CameraListener>& listener);
- void setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener);
// Configure preview callbacks to app. Only one of the older
// callbacks or the callback surface can be active at the same time;
@@ -187,12 +181,8 @@
explicit RecordingProxy(const sp<Camera>& camera);
// ICameraRecordingProxy interface
- virtual status_t startRecording(const sp<ICameraRecordingProxyListener>& listener);
+ virtual status_t startRecording();
virtual void stopRecording();
- virtual void releaseRecordingFrame(const sp<IMemory>& mem);
- virtual void releaseRecordingFrameHandle(native_handle_t* handle);
- virtual void releaseRecordingFrameHandleBatch(
- const std::vector<native_handle_t*>& handles);
private:
sp<Camera> mCamera;
@@ -203,8 +193,6 @@
Camera(const Camera&);
Camera& operator=(const Camera);
- sp<ICameraRecordingProxyListener> mRecordingProxyListener;
-
friend class CameraBase;
};
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/include/camera/ICameraRecordingProxy.h b/camera/include/camera/ICameraRecordingProxy.h
index 02af2f3..4306dc1 100644
--- a/camera/include/camera/ICameraRecordingProxy.h
+++ b/camera/include/camera/ICameraRecordingProxy.h
@@ -24,13 +24,11 @@
namespace android {
-class ICameraRecordingProxyListener;
-class IMemory;
class Parcel;
/*
- * The purpose of ICameraRecordingProxy and ICameraRecordingProxyListener is to
- * allow applications using the camera during recording.
+ * The purpose of ICameraRecordingProxy is to
+ * allow applications to use the camera during recording with the old camera API.
*
* Camera service allows only one client at a time. Since camcorder application
* needs to own the camera to do things like zoom, the media recorder cannot
@@ -42,35 +40,29 @@
* ICameraRecordingProxy
* startRecording()
* stopRecording()
- * releaseRecordingFrame()
*
- * ICameraRecordingProxyListener
- * dataCallbackTimestamp()
-
* The camcorder app opens the camera and starts the preview. The app passes
* ICamera and ICameraRecordingProxy to the media recorder by
* MediaRecorder::setCamera(). The recorder uses ICamera to setup the camera in
* MediaRecorder::start(). After setup, the recorder disconnects from camera
- * service. The recorder calls ICameraRecordingProxy::startRecording() and
- * passes a ICameraRecordingProxyListener to the app. The app connects back to
- * camera service and starts the recording. The app owns the camera and can do
- * things like zoom. The media recorder receives the video frames from the
- * listener and releases them by ICameraRecordingProxy::releaseRecordingFrame.
- * The recorder calls ICameraRecordingProxy::stopRecording() to stop the
- * recording.
+ * service. The recorder calls ICameraRecordingProxy::startRecording() and The
+ * app owns the camera and can do things like zoom. The media recorder receives
+ * the video frames via a buffer queue. The recorder calls
+ * ICameraRecordingProxy::stopRecording() to stop the recording.
*
* The call sequences are as follows:
* 1. The app: Camera.unlock().
* 2. The app: MediaRecorder.setCamera().
* 3. Start recording
* (1) The app: MediaRecorder.start().
- * (2) The recorder: ICamera.unlock() and ICamera.disconnect().
- * (3) The recorder: ICameraRecordingProxy.startRecording().
- * (4) The app: ICamera.reconnect().
- * (5) The app: ICamera.startRecording().
+ * (2) The recorder: ICamera.setVideoTarget(buffer queue).
+ * (3) The recorder: ICamera.unlock() and ICamera.disconnect().
+ * (4) The recorder: ICameraRecordingProxy.startRecording().
+ * (5) The app: ICamera.reconnect().
+ * (6) The app: ICamera.startRecording().
* 4. During recording
- * (1) The recorder: receive frames from ICameraRecordingProxyListener.dataCallbackTimestamp()
- * (2) The recorder: release frames by ICameraRecordingProxy.releaseRecordingFrame().
+ * (1) The recorder: receive frames via a buffer queue
+ * (2) The recorder: release frames via a buffer queue
* 5. Stop recording
* (1) The app: MediaRecorder.stop()
* (2) The recorder: ICameraRecordingProxy.stopRecording().
@@ -82,12 +74,8 @@
public:
DECLARE_META_INTERFACE(CameraRecordingProxy);
- virtual status_t startRecording(const sp<ICameraRecordingProxyListener>& listener) = 0;
+ virtual status_t startRecording() = 0;
virtual void stopRecording() = 0;
- virtual void releaseRecordingFrame(const sp<IMemory>& mem) = 0;
- virtual void releaseRecordingFrameHandle(native_handle_t *handle) = 0;
- virtual void releaseRecordingFrameHandleBatch(
- const std::vector<native_handle_t*>& handles) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/camera/include/camera/ICameraRecordingProxyListener.h b/camera/include/camera/ICameraRecordingProxyListener.h
deleted file mode 100644
index da03c56..0000000
--- a/camera/include/camera/ICameraRecordingProxyListener.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_LISTENER_H
-#define ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_LISTENER_H
-
-#include <vector>
-#include <binder/IInterface.h>
-#include <cutils/native_handle.h>
-#include <stdint.h>
-#include <utils/RefBase.h>
-#include <utils/Timers.h>
-
-namespace android {
-
-class Parcel;
-class IMemory;
-
-class ICameraRecordingProxyListener: public IInterface
-{
-public:
- DECLARE_META_INTERFACE(CameraRecordingProxyListener);
-
- virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType,
- const sp<IMemory>& data) = 0;
-
- virtual void recordingFrameHandleCallbackTimestamp(nsecs_t timestamp,
- native_handle_t* handle) = 0;
-
- virtual void recordingFrameHandleCallbackTimestampBatch(
- const std::vector<nsecs_t>& timestamps,
- const std::vector<native_handle_t*>& handles) = 0;
-};
-
-// ----------------------------------------------------------------------------
-
-class BnCameraRecordingProxyListener: public BnInterface<ICameraRecordingProxyListener>
-{
-public:
- virtual status_t onTransact( uint32_t code,
- const Parcel& data,
- Parcel* reply,
- uint32_t flags = 0);
-};
-
-}; // namespace android
-
-#endif
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/impl/ACameraManager.cpp b/camera/ndk/impl/ACameraManager.cpp
index b58ebe2..419250c 100644
--- a/camera/ndk/impl/ACameraManager.cpp
+++ b/camera/ndk/impl/ACameraManager.cpp
@@ -743,7 +743,7 @@
// No way to get package name from native.
// Send a zero length package name and let camera service figure it out from UID
binder::Status serviceRet = cs->connectDevice(
- callbacks, String16(cameraId), String16(""), std::unique_ptr<String16>(),
+ callbacks, String16(cameraId), String16(""), {},
hardware::ICameraService::USE_CALLING_UID, /*out*/&deviceRemote);
if (!serviceRet.isOk()) {
diff --git a/camera/ndk/impl/ACameraMetadata.cpp b/camera/ndk/impl/ACameraMetadata.cpp
index bfa60d9..631f6cd 100644
--- a/camera/ndk/impl/ACameraMetadata.cpp
+++ b/camera/ndk/impl/ACameraMetadata.cpp
@@ -527,6 +527,7 @@
case ACAMERA_LENS_OPTICAL_STABILIZATION_MODE:
case ACAMERA_NOISE_REDUCTION_MODE:
case ACAMERA_SCALER_CROP_REGION:
+ case ACAMERA_SCALER_ROTATE_AND_CROP:
case ACAMERA_SENSOR_EXPOSURE_TIME:
case ACAMERA_SENSOR_FRAME_DURATION:
case ACAMERA_SENSOR_SENSITIVITY:
diff --git a/camera/ndk/include/camera/NdkCameraMetadata.h b/camera/ndk/include/camera/NdkCameraMetadata.h
index 072bb02..a840bd1 100644
--- a/camera/ndk/include/camera/NdkCameraMetadata.h
+++ b/camera/ndk/include/camera/NdkCameraMetadata.h
@@ -36,6 +36,7 @@
#ifndef _NDK_CAMERA_METADATA_H
#define _NDK_CAMERA_METADATA_H
+#include <stdbool.h>
#include <stdint.h>
#include <sys/cdefs.h>
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 16457ac..96dc541 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>
@@ -3728,6 +3730,108 @@
ACAMERA_SCALER_AVAILABLE_RECOMMENDED_INPUT_OUTPUT_FORMATS_MAP =
// int32
ACAMERA_SCALER_START + 15,
+ /**
+ * <p>List of rotate-and-crop modes for ACAMERA_SCALER_ROTATE_AND_CROP that are supported by this camera device.</p>
+ *
+ * @see ACAMERA_SCALER_ROTATE_AND_CROP
+ *
+ * <p>Type: byte[n]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * </ul></p>
+ *
+ * <p>This entry lists the valid modes for ACAMERA_SCALER_ROTATE_AND_CROP for this camera device.</p>
+ * <p>Starting with API level 30, all devices will list at least <code>ROTATE_AND_CROP_NONE</code>.
+ * Devices with support for rotate-and-crop will additionally list at least
+ * <code>ROTATE_AND_CROP_AUTO</code> and <code>ROTATE_AND_CROP_90</code>.</p>
+ *
+ * @see ACAMERA_SCALER_ROTATE_AND_CROP
+ */
+ ACAMERA_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES = // byte[n]
+ ACAMERA_SCALER_START + 16,
+ /**
+ * <p>Whether a rotation-and-crop operation is applied to processed
+ * outputs from the camera.</p>
+ *
+ * <p>Type: byte (acamera_metadata_enum_android_scaler_rotate_and_crop_t)</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * <li>ACaptureRequest</li>
+ * </ul></p>
+ *
+ * <p>This control is primarily intended to help camera applications with no support for
+ * multi-window modes to work correctly on devices where multi-window scenarios are
+ * unavoidable, such as foldables or other devices with variable display geometry or more
+ * free-form window placement (such as laptops, which often place portrait-orientation apps
+ * in landscape with pillarboxing).</p>
+ * <p>If supported, the default value is <code>ROTATE_AND_CROP_AUTO</code>, which allows the camera API
+ * to enable backwards-compatibility support for applications that do not support resizing
+ * / multi-window modes, when the device is in fact in a multi-window mode (such as inset
+ * portrait on laptops, or on a foldable device in some fold states). In addition,
+ * <code>ROTATE_AND_CROP_NONE</code> and <code>ROTATE_AND_CROP_90</code> will always be available if this control
+ * is supported by the device. If not supported, devices API level 30 or higher will always
+ * list only <code>ROTATE_AND_CROP_NONE</code>.</p>
+ * <p>When <code>CROP_AUTO</code> is in use, and the camera API activates backward-compatibility mode,
+ * several metadata fields will also be parsed differently to ensure that coordinates are
+ * correctly handled for features like drawing face detection boxes or passing in
+ * tap-to-focus coordinates. The camera API will convert positions in the active array
+ * coordinate system to/from the cropped-and-rotated coordinate system to make the
+ * operation transparent for applications. The following controls are affected:</p>
+ * <ul>
+ * <li>ACAMERA_CONTROL_AE_REGIONS</li>
+ * <li>ACAMERA_CONTROL_AF_REGIONS</li>
+ * <li>ACAMERA_CONTROL_AWB_REGIONS</li>
+ * <li>android.statistics.faces</li>
+ * </ul>
+ * <p>Capture results will contain the actual value selected by the API;
+ * <code>ROTATE_AND_CROP_AUTO</code> will never be seen in a capture result.</p>
+ * <p>Applications can also select their preferred cropping mode, either to opt out of the
+ * backwards-compatibility treatment, or to use the cropping feature themselves as needed.
+ * In this case, no coordinate translation will be done automatically, and all controls
+ * will continue to use the normal active array coordinates.</p>
+ * <p>Cropping and rotating is done after the application of digital zoom (via either
+ * ACAMERA_SCALER_CROP_REGION or ACAMERA_CONTROL_ZOOM_RATIO), but before each individual
+ * output is further cropped and scaled. It only affects processed outputs such as
+ * YUV, PRIVATE, and JPEG. It has no effect on RAW outputs.</p>
+ * <p>When <code>CROP_90</code> or <code>CROP_270</code> are selected, there is a significant loss to the field of
+ * view. For example, with a 4:3 aspect ratio output of 1600x1200, <code>CROP_90</code> will still
+ * produce 1600x1200 output, but these buffers are cropped from a vertical 3:4 slice at the
+ * center of the 4:3 area, then rotated to be 4:3, and then upscaled to 1600x1200. Only
+ * 56.25% of the original FOV is still visible. In general, for an aspect ratio of <code>w:h</code>,
+ * the crop and rotate operation leaves <code>(h/w)^2</code> of the field of view visible. For 16:9,
+ * this is ~31.6%.</p>
+ * <p>As a visual example, the figure below shows the effect of <code>ROTATE_AND_CROP_90</code> on the
+ * outputs for the following parameters:</p>
+ * <ul>
+ * <li>Sensor active array: <code>2000x1500</code></li>
+ * <li>Crop region: top-left: <code>(500, 375)</code>, size: <code>(1000, 750)</code> (4:3 aspect ratio)</li>
+ * <li>Output streams: YUV <code>640x480</code> and YUV <code>1280x720</code></li>
+ * <li><code>ROTATE_AND_CROP_90</code></li>
+ * </ul>
+ * <p><img alt="Effect of ROTATE_AND_CROP_90" src="../images/camera2/metadata/android.scaler.rotateAndCrop/crop-region-rotate-90-43-ratio.png" /></p>
+ * <p>With these settings, the regions of the active array covered by the output streams are:</p>
+ * <ul>
+ * <li>640x480 stream crop: top-left: <code>(219, 375)</code>, size: <code>(562, 750)</code></li>
+ * <li>1280x720 stream crop: top-left: <code>(289, 375)</code>, size: <code>(422, 750)</code></li>
+ * </ul>
+ * <p>Since the buffers are rotated, the buffers as seen by the application are:</p>
+ * <ul>
+ * <li>640x480 stream: top-left: <code>(781, 375)</code> on active array, size: <code>(640, 480)</code>, downscaled 1.17x from sensor pixels</li>
+ * <li>1280x720 stream: top-left: <code>(711, 375)</code> on active array, size: <code>(1280, 720)</code>, upscaled 1.71x from sensor pixels</li>
+ * </ul>
+ *
+ * @see ACAMERA_CONTROL_AE_REGIONS
+ * @see ACAMERA_CONTROL_AF_REGIONS
+ * @see ACAMERA_CONTROL_AWB_REGIONS
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
+ * @see ACAMERA_SCALER_CROP_REGION
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP = // byte (acamera_metadata_enum_android_scaler_rotate_and_crop_t)
+ ACAMERA_SCALER_START + 17,
ACAMERA_SCALER_END,
/**
@@ -4381,8 +4485,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 +4495,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 +6156,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 +8191,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
@@ -8263,6 +8394,51 @@
} acamera_metadata_enum_android_scaler_available_recommended_stream_configurations_t;
+// ACAMERA_SCALER_ROTATE_AND_CROP
+typedef enum acamera_metadata_enum_acamera_scaler_rotate_and_crop {
+ /**
+ * <p>No rotate and crop is applied. Processed outputs are in the sensor orientation.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_NONE = 0,
+
+ /**
+ * <p>Processed images are rotated by 90 degrees clockwise, and then cropped
+ * to the original aspect ratio.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_90 = 1,
+
+ /**
+ * <p>Processed images are rotated by 180 degrees. Since the aspect ratio does not
+ * change, no cropping is performed.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_180 = 2,
+
+ /**
+ * <p>Processed images are rotated by 270 degrees clockwise, and then cropped
+ * to the original aspect ratio.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_270 = 3,
+
+ /**
+ * <p>The camera API automatically selects the best concrete value for
+ * rotate-and-crop based on the application's support for resizability and the current
+ * multi-window mode.</p>
+ * <p>If the application does not support resizing but the display mode for its main
+ * Activity is not in a typical orientation, the camera API will set <code>ROTATE_AND_CROP_90</code>
+ * or some other supported rotation value, depending on device configuration,
+ * to ensure preview and captured images are correctly shown to the user. Otherwise,
+ * <code>ROTATE_AND_CROP_NONE</code> will be selected.</p>
+ * <p>When a value other than NONE is selected, several metadata fields will also be parsed
+ * differently to ensure that coordinates are correctly handled for features like drawing
+ * face detection boxes or passing in tap-to-focus coordinates. The camera API will
+ * convert positions in the active array coordinate system to/from the cropped-and-rotated
+ * coordinate system to make the operation transparent for applications.</p>
+ * <p>No coordinate mapping will be done when the application selects a non-AUTO mode.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_AUTO = 4,
+
+} acamera_metadata_enum_android_scaler_rotate_and_crop_t;
+
// ACAMERA_SENSOR_REFERENCE_ILLUMINANT1
typedef enum acamera_metadata_enum_acamera_sensor_reference_illuminant1 {
diff --git a/camera/ndk/include/camera/NdkCameraWindowType.h b/camera/ndk/include/camera/NdkCameraWindowType.h
index 99f67e9..df977da 100644
--- a/camera/ndk/include/camera/NdkCameraWindowType.h
+++ b/camera/ndk/include/camera/NdkCameraWindowType.h
@@ -44,7 +44,7 @@
*/
#ifdef __ANDROID_VNDK__
#include <cutils/native_handle.h>
-typedef native_handle_t ACameraWindowType;
+typedef const native_handle_t ACameraWindowType;
#else
#include <android/native_window.h>
typedef ANativeWindow ACameraWindowType;
diff --git a/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h b/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h
index e1af8c1..5a1af79 100644
--- a/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h
+++ b/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h
@@ -18,7 +18,7 @@
#include "utils.h"
struct ACaptureSessionOutput {
- explicit ACaptureSessionOutput(native_handle_t* window, bool isShared = false,
+ explicit ACaptureSessionOutput(const native_handle_t* window, bool isShared = false,
const char* physicalCameraId = "") :
mWindow(window), mIsShared(isShared), mPhysicalCameraId(physicalCameraId) {};
diff --git a/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp b/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp
index e511a3f..0fcb700 100644
--- a/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp
+++ b/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp
@@ -355,7 +355,7 @@
std::vector<int32_t> requestStreamIdxList;
std::vector<int32_t> requestSurfaceIdxList;
for (auto outputTarget : request->targets->mOutputs) {
- native_handle_t* anw = outputTarget.mWindow;
+ const native_handle_t* anw = outputTarget.mWindow;
bool found = false;
req->mSurfaceList.push_back(anw);
// lookup stream/surface ID
@@ -434,7 +434,7 @@
}
pRequest->targets = new ACameraOutputTargets();
for (size_t i = 0; i < req->mSurfaceList.size(); i++) {
- native_handle_t* anw = req->mSurfaceList[i];
+ const native_handle_t* anw = req->mSurfaceList[i];
ACameraOutputTarget outputTarget(anw);
pRequest->targets->mOutputs.insert(outputTarget);
}
@@ -611,7 +611,7 @@
std::set<std::pair<native_handle_ptr_wrapper, OutputConfigurationWrapper>> outputSet;
for (auto outConfig : outputs->mOutputs) {
- native_handle_t* anw = outConfig.mWindow;
+ const native_handle_t* anw = outConfig.mWindow;
OutputConfigurationWrapper outConfigInsertW;
OutputConfiguration &outConfigInsert = outConfigInsertW.mOutputConfiguration;
outConfigInsert.rotation = utils::convertToHidl(outConfig.mRotation);
@@ -846,8 +846,7 @@
for (auto streamAndWindowId : request->mCaptureRequest.streamAndWindowIds) {
int32_t windowId = streamAndWindowId.windowId;
if (utils::isWindowNativeHandleEqual(windowHandles[windowId],outHandle)) {
- native_handle_t* anw =
- const_cast<native_handle_t *>(windowHandles[windowId].getNativeHandle());
+ const native_handle_t* anw = windowHandles[windowId].getNativeHandle();
ALOGV("Camera %s Lost output buffer for ANW %p frame %" PRId64,
getId(), anw, frameNumber);
@@ -1244,7 +1243,7 @@
return;
}
- native_handle_t* anw;
+ const native_handle_t* anw;
found = msg->findPointer(kAnwKey, (void**) &anw);
if (!found) {
ALOGE("%s: Cannot find native_handle_t!", __FUNCTION__);
diff --git a/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h b/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h
index ed67615..5715d77 100644
--- a/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h
+++ b/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h
@@ -17,7 +17,7 @@
#include "utils.h"
struct ACameraOutputTarget {
- explicit ACameraOutputTarget(native_handle_t* window) : mWindow(window) {};
+ explicit ACameraOutputTarget(const native_handle_t* window) : mWindow(window) {};
bool operator == (const ACameraOutputTarget& other) const {
return mWindow == other.mWindow;
diff --git a/camera/ndk/ndk_vendor/impl/utils.h b/camera/ndk/ndk_vendor/impl/utils.h
index f389f03..6f5820e 100644
--- a/camera/ndk/ndk_vendor/impl/utils.h
+++ b/camera/ndk/ndk_vendor/impl/utils.h
@@ -42,7 +42,7 @@
// Utility class so that CaptureRequest can be stored by sp<>
struct CaptureRequest : public RefBase {
frameworks::cameraservice::device::V2_0::CaptureRequest mCaptureRequest;
- std::vector<native_handle_t *> mSurfaceList;
+ std::vector<const native_handle_t *> mSurfaceList;
//Physical camera settings metadata is stored here, since the capture request
//might not contain it. That's since, fmq might have consumed it.
hidl_vec<PhysicalCameraSettings> mPhysicalCameraSettings;
@@ -62,13 +62,13 @@
// Utility class so the native_handle_t can be compared with its contents instead
// of just raw pointer comparisons.
struct native_handle_ptr_wrapper {
- native_handle_t *mWindow = nullptr;
+ const native_handle_t *mWindow = nullptr;
- native_handle_ptr_wrapper(native_handle_t *nh) : mWindow(nh) { }
+ native_handle_ptr_wrapper(const native_handle_t *nh) : mWindow(nh) { }
native_handle_ptr_wrapper() = default;
- operator native_handle_t *() const { return mWindow; }
+ operator const native_handle_t *() const { return mWindow; }
bool operator ==(const native_handle_ptr_wrapper other) const {
return isWindowNativeHandleEqual(mWindow, other.mWindow);
diff --git a/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp b/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp
index 938b5f5..ba14c5c 100644
--- a/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp
+++ b/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp
@@ -50,7 +50,7 @@
static constexpr int kTestImageFormat = AIMAGE_FORMAT_YUV_420_888;
using android::hardware::camera::common::V1_0::helper::VendorTagDescriptorCache;
-using ConfiguredWindows = std::set<native_handle_t *>;
+using ConfiguredWindows = std::set<const native_handle_t *>;
class CameraHelper {
public:
@@ -60,11 +60,11 @@
struct PhysicalImgReaderInfo {
const char* physicalCameraId;
- native_handle_t* anw;
+ const native_handle_t* anw;
};
// Retaining the error code in case the caller needs to analyze it.
- std::variant<int, ConfiguredWindows> initCamera(native_handle_t* imgReaderAnw,
+ std::variant<int, ConfiguredWindows> initCamera(const native_handle_t* imgReaderAnw,
const std::vector<PhysicalImgReaderInfo>& physicalImgReaders,
bool usePhysicalSettings) {
ConfiguredWindows configuredWindows;
@@ -257,7 +257,7 @@
ACameraDevice_StateCallbacks mDeviceCb{this, nullptr, nullptr};
ACameraCaptureSession_stateCallbacks mSessionCb{ this, nullptr, nullptr, nullptr};
- native_handle_t* mImgReaderAnw = nullptr; // not owned by us.
+ const native_handle_t* mImgReaderAnw = nullptr; // not owned by us.
// Camera device
ACameraDevice* mDevice = nullptr;
@@ -396,7 +396,7 @@
return 0;
}
- native_handle_t* getNativeWindow() { return mImgReaderAnw; }
+ const native_handle_t* getNativeWindow() { return mImgReaderAnw; }
int getAcquiredImageCount() {
std::lock_guard<std::mutex> lock(mMutex);
diff --git a/camera/tests/CameraBinderTests.cpp b/camera/tests/CameraBinderTests.cpp
index 8ccded2..eee05ff 100644
--- a/camera/tests/CameraBinderTests.cpp
+++ b/camera/tests/CameraBinderTests.cpp
@@ -378,7 +378,7 @@
sp<TestCameraDeviceCallbacks> callbacks(new TestCameraDeviceCallbacks());
sp<hardware::camera2::ICameraDeviceUser> device;
res = service->connectDevice(callbacks, cameraId, String16("meeeeeeeee!"),
- std::unique_ptr<String16>(), hardware::ICameraService::USE_CALLING_UID,
+ {}, hardware::ICameraService::USE_CALLING_UID,
/*out*/&device);
EXPECT_TRUE(res.isOk()) << res;
ASSERT_NE(nullptr, device.get());
@@ -421,7 +421,7 @@
{
SCOPED_TRACE("openNewDevice");
binder::Status res = service->connectDevice(callbacks, deviceId, String16("meeeeeeeee!"),
- std::unique_ptr<String16>(), hardware::ICameraService::USE_CALLING_UID,
+ {}, hardware::ICameraService::USE_CALLING_UID,
/*out*/&device);
EXPECT_TRUE(res.isOk()) << res;
}
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp
index 37091c4..098c278 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -259,31 +259,6 @@
printf("$\n");
#endif
-#if 0
- CameraSource *source = CameraSource::Create(
- String16(argv[0], strlen(argv[0])));
- source->start();
-
- printf("source = %p\n", source);
-
- for (int i = 0; i < 100; ++i) {
- MediaBuffer *buffer;
- status_t err = source->read(&buffer);
- CHECK_EQ(err, (status_t)OK);
-
- printf("got a frame, data=%p, size=%d\n",
- buffer->data(), buffer->range_length());
-
- buffer->release();
- buffer = NULL;
- }
-
- err = source->stop();
-
- delete source;
- source = NULL;
-#endif
-
if (err != OK && err != ERROR_END_OF_STREAM) {
fprintf(stderr, "record failed: %d\n", err);
return 1;
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/drmserver/Android.bp b/drm/drmserver/Android.bp
index b68e6c2..fcd291f 100644
--- a/drm/drmserver/Android.bp
+++ b/drm/drmserver/Android.bp
@@ -43,7 +43,7 @@
"-Werror",
],
- compile_multilib: "32",
+ compile_multilib: "prefer32",
init_rc: ["drmserver.rc"],
}
diff --git a/drm/libmediadrm/DrmSessionManager.cpp b/drm/libmediadrm/DrmSessionManager.cpp
index 5292705..e31395d 100644
--- a/drm/libmediadrm/DrmSessionManager.cpp
+++ b/drm/libmediadrm/DrmSessionManager.cpp
@@ -66,7 +66,7 @@
std::vector<MediaResourceParcel> resources;
MediaResourceParcel resource{
Type::kDrmSession, SubType::kUnspecifiedSubType,
- toStdVec<int8_t>(sessionId), value};
+ toStdVec<>(sessionId), value};
resources.push_back(resource);
return resources;
}
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/TEST_MAPPING b/media/TEST_MAPPING
index a6dfb21..b006f38 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -1,32 +1,71 @@
+// for frameworks/av/media
{
- "presubmit": [
- {
- "name": "GtsMediaTestCases",
- "options" : [
+ "presubmit": [
+ // runs whenever we change something in this tree
{
- "include-annotation": "android.platform.test.annotations.Presubmit"
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.EncodeDecodeTest"
+ }
+ ]
},
{
- "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
- }
- ]
- },
- {
- "name": "GtsExoPlayerTestCases",
- "options" : [
- {
- "include-annotation": "android.platform.test.annotations.SocPresubmit"
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.DecodeEditEncodeTest"
+ }
+ ]
},
{
- "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+ "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"
+ }
+ ]
}
- ]
- }
- ],
- "imports": [
- {
- "path": "frameworks/av/drm/mediadrm/plugins"
- }
- ]
-}
+ ],
+ "imports": [
+ {
+ "path": "frameworks/av/drm/mediadrm/plugins"
+ }
+ ],
+
+ "platinum-postsubmit": [
+ // runs regularly, independent of changes in this tree.
+ // signals if changes elsewhere break media functionality
+ {
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.EncodeDecodeTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.DecodeEditEncodeTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/media/audioserver/Android.bp b/media/audioserver/Android.bp
new file mode 100644
index 0000000..ca3c81c
--- /dev/null
+++ b/media/audioserver/Android.bp
@@ -0,0 +1,58 @@
+cc_binary {
+ name: "audioserver",
+
+ srcs: [
+ "main_audioserver.cpp",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ header_libs: [
+ "libaudiohal_headers",
+ "libmediametrics_headers",
+ ],
+
+ shared_libs: [
+ "libaaudioservice",
+ "libaudioflinger",
+ "libaudiopolicyservice",
+ "libaudioprocessing",
+ "libbinder",
+ "libcutils",
+ "libhidlbase",
+ "liblog",
+ "libmedia",
+ "libmedialogservice",
+ "libmediautils",
+ "libnbaio",
+ "libnblog",
+ "libpowermanager",
+ "libutils",
+ "libvibrator",
+
+ ],
+
+ // TODO check if we still need all of these include directories
+ include_dirs: [
+ "external/sonic",
+ "frameworks/av/media/libaaudio/include",
+ "frameworks/av/media/libaaudio/src",
+ "frameworks/av/media/libaaudio/src/binding",
+ "frameworks/av/media/libmedia/include",
+ "frameworks/av/services/audioflinger",
+ "frameworks/av/services/audiopolicy",
+ "frameworks/av/services/audiopolicy/common/include",
+ "frameworks/av/services/audiopolicy/common/managerdefinitions/include",
+ "frameworks/av/services/audiopolicy/engine/interface",
+ "frameworks/av/services/audiopolicy/service",
+ "frameworks/av/services/medialog",
+
+ // TODO oboeservice is the old folder name for aaudioservice. It will be changed.
+ "frameworks/av/services/oboeservice",
+ ],
+
+ init_rc: ["audioserver.rc"],
+}
diff --git a/media/audioserver/Android.mk b/media/audioserver/Android.mk
deleted file mode 100644
index cf1c14c..0000000
--- a/media/audioserver/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- main_audioserver.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- libaaudioservice \
- libaudioflinger \
- libaudiopolicyservice \
- libaudioprocessing \
- libbinder \
- libcutils \
- liblog \
- libhidlbase \
- libmedia \
- libmedialogservice \
- libmediautils \
- libnbaio \
- libnblog \
- libutils \
- libvibrator
-
-LOCAL_HEADER_LIBRARIES := \
- libaudiohal_headers \
- libmediametrics_headers \
-
-# TODO oboeservice is the old folder name for aaudioservice. It will be changed.
-LOCAL_C_INCLUDES := \
- frameworks/av/services/audioflinger \
- frameworks/av/services/audiopolicy \
- frameworks/av/services/audiopolicy/common/managerdefinitions/include \
- frameworks/av/services/audiopolicy/common/include \
- frameworks/av/services/audiopolicy/engine/interface \
- frameworks/av/services/audiopolicy/service \
- frameworks/av/services/medialog \
- frameworks/av/services/oboeservice \
- frameworks/av/media/libaaudio/include \
- frameworks/av/media/libaaudio/src \
- frameworks/av/media/libaaudio/src/binding \
- frameworks/av/media/libmedia/include \
- external/sonic \
-
-LOCAL_MODULE := audioserver
-
-LOCAL_INIT_RC := audioserver.rc
-
-LOCAL_CFLAGS := -Werror -Wall
-
-include $(BUILD_EXECUTABLE)
diff --git a/media/audioserver/audioserver.rc b/media/audioserver/audioserver.rc
index 6cb0c73..c4a6601 100644
--- a/media/audioserver/audioserver.rc
+++ b/media/audioserver/audioserver.rc
@@ -6,8 +6,12 @@
capabilities BLOCK_SUSPEND
ioprio rt 4
task_profiles ProcessCapacityHigh HighPerformance
-
- onrestart setprop sys.audio.restart.hal 1
+ onrestart restart vendor.audio-hal
+ onrestart restart vendor.audio-hal-4-0-msd
+ onrestart restart audio_proxy_service
+ # Keep the original service names for backward compatibility
+ onrestart restart vendor.audio-hal-2-0
+ onrestart restart audio-hal-2-0
on property:vts.native_server.on=1
stop audioserver
@@ -17,23 +21,41 @@
on property:init.svc.audioserver=stopped
stop vendor.audio-hal
stop vendor.audio-hal-4-0-msd
+ stop audio_proxy_service
# 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
+ start audio_proxy_service
+ # 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
start vendor.audio-hal-4-0-msd
+ start audio_proxy_service
# Keep the original service names for backward compatibility
start vendor.audio-hal-2-0
start audio-hal-2-0
on property:sys.audio.restart.hal=1
- restart vendor.audio-hal
- restart vendor.audio-hal-4-0-msd
+ # See b/159966243. Avoid restart loop between audioserver and HAL.
# Keep the original service names for backward compatibility
- restart vendor.audio-hal-2-0
- restart audio-hal-2-0
+ stop vendor.audio-hal
+ stop vendor.audio-hal-4-0-msd
+ stop audio_proxy_service
+ stop vendor.audio-hal-2-0
+ stop audio-hal-2-0
+ start vendor.audio-hal
+ start vendor.audio-hal-4-0-msd
+ start audio_proxy_service
+ start vendor.audio-hal-2-0
+ start audio-hal-2-0
# reset the property
setprop sys.audio.restart.hal 0
diff --git a/media/audioserver/main_audioserver.cpp b/media/audioserver/main_audioserver.cpp
index f9f4f31..533d330 100644
--- a/media/audioserver/main_audioserver.cpp
+++ b/media/audioserver/main_audioserver.cpp
@@ -29,8 +29,8 @@
#include <mediautils/LimitProcessMemory.h>
#include <utils/Log.h>
-// from LOCAL_C_INCLUDES
-#include "aaudio/AAudioTesting.h"
+// from include_dirs
+#include "aaudio/AAudioTesting.h" // aaudio_policy_t, AAUDIO_PROP_MMAP_POLICY, AAUDIO_POLICY_*
#include "AudioFlinger.h"
#include "AudioPolicyService.h"
#include "AAudioService.h"
diff --git a/media/bufferpool/1.0/TEST_MAPPING b/media/bufferpool/1.0/TEST_MAPPING
new file mode 100644
index 0000000..a1e6a58
--- /dev/null
+++ b/media/bufferpool/1.0/TEST_MAPPING
@@ -0,0 +1,8 @@
+// mappings for frameworks/av/media/bufferpool/1.0
+{
+ "presubmit": [
+
+ { "name": "VtsVndkHidlBufferpoolV1_0TargetSingleTest" },
+ { "name": "VtsVndkHidlBufferpoolV1_0TargetMultiTest"}
+ ]
+}
diff --git a/media/bufferpool/1.0/vts/Android.bp b/media/bufferpool/1.0/vts/Android.bp
index ee5a757..691ed40 100644
--- a/media/bufferpool/1.0/vts/Android.bp
+++ b/media/bufferpool/1.0/vts/Android.bp
@@ -16,6 +16,7 @@
cc_test {
name: "VtsVndkHidlBufferpoolV1_0TargetSingleTest",
+ test_suites: ["device-tests"],
defaults: ["VtsHalTargetTestDefaults"],
srcs: [
"allocator.cpp",
@@ -34,6 +35,7 @@
cc_test {
name: "VtsVndkHidlBufferpoolV1_0TargetMultiTest",
+ test_suites: ["device-tests"],
defaults: ["VtsHalTargetTestDefaults"],
srcs: [
"allocator.cpp",
diff --git a/media/bufferpool/1.0/vts/multi.cpp b/media/bufferpool/1.0/vts/multi.cpp
index 1796819..d8cc285 100644
--- a/media/bufferpool/1.0/vts/multi.cpp
+++ b/media/bufferpool/1.0/vts/multi.cpp
@@ -215,7 +215,7 @@
} // anonymous namespace
int main(int argc, char** argv) {
- setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+ android::hardware::details::setTrebleTestingOverride(true);
::testing::InitGoogleTest(&argc, argv);
int status = RUN_ALL_TESTS();
LOG(INFO) << "Test result = " << status;
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/bufferpool/2.0/TEST_MAPPING b/media/bufferpool/2.0/TEST_MAPPING
new file mode 100644
index 0000000..65dee2c
--- /dev/null
+++ b/media/bufferpool/2.0/TEST_MAPPING
@@ -0,0 +1,7 @@
+// mappings for frameworks/av/media/bufferpool/2.0
+{
+ "presubmit": [
+ { "name": "VtsVndkHidlBufferpoolV2_0TargetSingleTest"},
+ { "name": "VtsVndkHidlBufferpoolV2_0TargetMultiTest"}
+ ]
+}
diff --git a/media/bufferpool/2.0/tests/Android.bp b/media/bufferpool/2.0/tests/Android.bp
index 8b44f61..8492939 100644
--- a/media/bufferpool/2.0/tests/Android.bp
+++ b/media/bufferpool/2.0/tests/Android.bp
@@ -16,6 +16,7 @@
cc_test {
name: "VtsVndkHidlBufferpoolV2_0TargetSingleTest",
+ test_suites: ["device-tests"],
defaults: ["VtsHalTargetTestDefaults"],
srcs: [
"allocator.cpp",
@@ -34,6 +35,7 @@
cc_test {
name: "VtsVndkHidlBufferpoolV2_0TargetMultiTest",
+ test_suites: ["device-tests"],
defaults: ["VtsHalTargetTestDefaults"],
srcs: [
"allocator.cpp",
diff --git a/media/bufferpool/2.0/tests/multi.cpp b/media/bufferpool/2.0/tests/multi.cpp
index 68b6992..b40838e 100644
--- a/media/bufferpool/2.0/tests/multi.cpp
+++ b/media/bufferpool/2.0/tests/multi.cpp
@@ -215,7 +215,7 @@
} // anonymous namespace
int main(int argc, char** argv) {
- setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+ android::hardware::details::setTrebleTestingOverride(true);
::testing::InitGoogleTest(&argc, argv);
int status = RUN_ALL_TESTS();
LOG(INFO) << "Test result = " << status;
diff --git a/media/codec2/TEST_MAPPING b/media/codec2/TEST_MAPPING
index 8afa1a8..fca3477 100644
--- a/media/codec2/TEST_MAPPING
+++ b/media/codec2/TEST_MAPPING
@@ -1,5 +1,10 @@
{
"presubmit": [
+ // TODO failing 4 of 13
+ // { "name": "codec2_core_param_test"},
+ // TODO(b/155516524)
+ // { "name": "codec2_vndk_interface_test"},
+ { "name": "codec2_vndk_test"},
{
"name": "CtsMediaTestCases",
"options": [
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/g711/Android.bp b/media/codec2/components/g711/Android.bp
index 3ede68c..0101b1a 100644
--- a/media/codec2/components/g711/Android.bp
+++ b/media/codec2/components/g711/Android.bp
@@ -7,6 +7,8 @@
srcs: ["C2SoftG711Dec.cpp"],
+ static_libs: ["codecs_g711dec"],
+
cflags: [
"-DALAW",
],
@@ -20,4 +22,6 @@
],
srcs: ["C2SoftG711Dec.cpp"],
+
+ static_libs: ["codecs_g711dec"],
}
diff --git a/media/codec2/components/g711/C2SoftG711Dec.cpp b/media/codec2/components/g711/C2SoftG711Dec.cpp
index 4ff0793..7f9c34e 100644
--- a/media/codec2/components/g711/C2SoftG711Dec.cpp
+++ b/media/codec2/components/g711/C2SoftG711Dec.cpp
@@ -22,7 +22,7 @@
#include <C2PlatformSupport.h>
#include <SimpleC2Interface.h>
-
+#include <g711Dec.h>
#include "C2SoftG711Dec.h"
namespace android {
@@ -224,53 +224,6 @@
return C2_OK;
}
-#ifdef ALAW
-void C2SoftG711Dec::DecodeALaw(
- int16_t *out, const uint8_t *in, size_t inSize) {
- while (inSize > 0) {
- inSize--;
- int32_t x = *in++;
-
- int32_t ix = x ^ 0x55;
- ix &= 0x7f;
-
- int32_t iexp = ix >> 4;
- int32_t mant = ix & 0x0f;
-
- if (iexp > 0) {
- mant += 16;
- }
-
- mant = (mant << 4) + 8;
-
- if (iexp > 1) {
- mant = mant << (iexp - 1);
- }
-
- *out++ = (x > 127) ? mant : -mant;
- }
-}
-#else
-void C2SoftG711Dec::DecodeMLaw(
- int16_t *out, const uint8_t *in, size_t inSize) {
- while (inSize > 0) {
- inSize--;
- int32_t x = *in++;
-
- int32_t mantissa = ~x;
- int32_t exponent = (mantissa >> 4) & 7;
- int32_t segment = exponent + 1;
- mantissa &= 0x0f;
-
- int32_t step = 4 << segment;
-
- int32_t abs = (0x80l << exponent) + step * mantissa + step / 2 - 4 * 33;
-
- *out++ = (x < 0x80) ? -abs : abs;
- }
-}
-#endif
-
class C2SoftG711DecFactory : public C2ComponentFactory {
public:
C2SoftG711DecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>(
diff --git a/media/codec2/components/g711/C2SoftG711Dec.h b/media/codec2/components/g711/C2SoftG711Dec.h
index 23e8ffc..f93840b 100644
--- a/media/codec2/components/g711/C2SoftG711Dec.h
+++ b/media/codec2/components/g711/C2SoftG711Dec.h
@@ -45,12 +45,6 @@
std::shared_ptr<IntfImpl> mIntf;
bool mSignalledOutputEos;
-#ifdef ALAW
- void DecodeALaw(int16_t *out, const uint8_t *in, size_t inSize);
-#else
- void DecodeMLaw(int16_t *out, const uint8_t *in, size_t inSize);
-#endif
-
C2_DO_NOT_COPY(C2SoftG711Dec);
};
diff --git a/media/codec2/components/gav1/Android.bp b/media/codec2/components/gav1/Android.bp
index 5c4abb7..f374089 100644
--- a/media/codec2/components/gav1/Android.bp
+++ b/media/codec2/components/gav1/Android.bp
@@ -13,8 +13,4 @@
srcs: ["C2SoftGav1Dec.cpp"],
static_libs: ["libgav1"],
-
- include_dirs: [
- "external/libgav1/libgav1/",
- ],
}
diff --git a/media/codec2/components/gav1/C2SoftGav1Dec.cpp b/media/codec2/components/gav1/C2SoftGav1Dec.cpp
index ec5f549..22aa905 100644
--- a/media/codec2/components/gav1/C2SoftGav1Dec.cpp
+++ b/media/codec2/components/gav1/C2SoftGav1Dec.cpp
@@ -288,9 +288,7 @@
void C2SoftGav1Dec::onRelease() { destroyDecoder(); }
c2_status_t C2SoftGav1Dec::onFlush_sm() {
- Libgav1StatusCode status =
- mCodecCtx->EnqueueFrame(/*data=*/nullptr, /*size=*/0,
- /*user_private_data=*/0);
+ Libgav1StatusCode status = mCodecCtx->SignalEOS();
if (status != kLibgav1StatusOk) {
ALOGE("Failed to flush av1 decoder. status: %d.", status);
return C2_CORRUPTED;
@@ -299,7 +297,7 @@
// Dequeue frame (if any) that was enqueued previously.
const libgav1::DecoderBuffer *buffer;
status = mCodecCtx->DequeueFrame(&buffer);
- if (status != kLibgav1StatusOk) {
+ if (status != kLibgav1StatusOk && status != kLibgav1StatusNothingToDequeue) {
ALOGE("Failed to dequeue frame after flushing the av1 decoder. status: %d",
status);
return C2_CORRUPTED;
@@ -433,7 +431,8 @@
TIME_DIFF(mTimeEnd, mTimeStart, delay);
const Libgav1StatusCode status =
- mCodecCtx->EnqueueFrame(bitstream, inSize, frameIndex);
+ mCodecCtx->EnqueueFrame(bitstream, inSize, frameIndex,
+ /*buffer_private_data=*/nullptr);
GETTIME(&mTimeEnd, nullptr);
TIME_DIFF(mTimeStart, mTimeEnd, decodeTime);
@@ -448,9 +447,7 @@
}
} else {
- const Libgav1StatusCode status =
- mCodecCtx->EnqueueFrame(/*data=*/nullptr, /*size=*/0,
- /*user_private_data=*/0);
+ const Libgav1StatusCode status = mCodecCtx->SignalEOS();
if (status != kLibgav1StatusOk) {
ALOGE("Failed to flush av1 decoder. status: %d.", status);
work->result = C2_CORRUPTED;
@@ -607,13 +604,14 @@
const libgav1::DecoderBuffer *buffer;
const Libgav1StatusCode status = mCodecCtx->DequeueFrame(&buffer);
- if (status != kLibgav1StatusOk) {
+ if (status != kLibgav1StatusOk && status != kLibgav1StatusNothingToDequeue) {
ALOGE("av1 decoder DequeueFrame failed. status: %d.", status);
return false;
}
- // |buffer| can be NULL if status was equal to kLibgav1StatusOk. This is not
- // an error. This could mean one of two things:
+ // |buffer| can be NULL if status was equal to kLibgav1StatusOk or
+ // kLibgav1StatusNothingToDequeue. This is not an error. This could mean one
+ // of two things:
// - The EnqueueFrame() call was either a flush (called with nullptr).
// - The enqueued frame did not have any displayable frames.
if (!buffer) {
@@ -722,9 +720,7 @@
return C2_OMITTED;
}
- Libgav1StatusCode status =
- mCodecCtx->EnqueueFrame(/*data=*/nullptr, /*size=*/0,
- /*user_private_data=*/0);
+ const Libgav1StatusCode status = mCodecCtx->SignalEOS();
if (status != kLibgav1StatusOk) {
ALOGE("Failed to flush av1 decoder. status: %d.", status);
return C2_CORRUPTED;
diff --git a/media/codec2/components/gav1/C2SoftGav1Dec.h b/media/codec2/components/gav1/C2SoftGav1Dec.h
index a7c08bb..555adc9 100644
--- a/media/codec2/components/gav1/C2SoftGav1Dec.h
+++ b/media/codec2/components/gav1/C2SoftGav1Dec.h
@@ -18,8 +18,8 @@
#define ANDROID_C2_SOFT_GAV1_DEC_H_
#include <SimpleC2Component.h>
-#include "libgav1/src/decoder.h"
-#include "libgav1/src/decoder_settings.h"
+#include "libgav1/src/gav1/decoder.h"
+#include "libgav1/src/gav1/decoder_settings.h"
#define GETTIME(a, b) gettimeofday(a, b);
#define TIME_DIFF(start, end, diff) \
diff --git a/media/codec2/components/gsm/C2SoftGsmDec.h b/media/codec2/components/gsm/C2SoftGsmDec.h
index 2b209fe..edd273b 100644
--- a/media/codec2/components/gsm/C2SoftGsmDec.h
+++ b/media/codec2/components/gsm/C2SoftGsmDec.h
@@ -19,10 +19,7 @@
#include <SimpleC2Component.h>
-
-extern "C" {
- #include "gsm.h"
-}
+#include "gsm.h"
namespace android {
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..33fafa7 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,
},
@@ -19,6 +21,10 @@
"-Werror",
],
+ header_abi_checker: {
+ check_all_apis: true,
+ },
+
header_libs: [
"libcodec2_headers",
"libhardware_headers",
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/hidl/services/Android.bp b/media/codec2/hidl/services/Android.bp
index a16b106..3780a5a 100644
--- a/media/codec2/hidl/services/Android.bp
+++ b/media/codec2/hidl/services/Android.bp
@@ -52,6 +52,9 @@
// directly in the main device manifest.xml file or via vintf_fragments.
// (Remove the line below if the entry is already in the main manifest.)
vintf_fragments: ["manifest_media_c2_V1_1_default.xml"],
+
+ // Remove this line to enable this module.
+ enabled: false,
}
// seccomp policy file.
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index 1e4560c..73b3857 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");
@@ -1812,15 +1816,18 @@
// move all info into output-stream #0 domain
updates.emplace_back(C2Param::CopyAsStream(*info, true /* output */, stream));
}
- for (const C2ConstGraphicBlock &block : buf->data().graphicBlocks()) {
+
+ const std::vector<C2ConstGraphicBlock> blocks = buf->data().graphicBlocks();
+ // for now only do the first block
+ if (!blocks.empty()) {
// ALOGV("got output buffer with crop %u,%u+%u,%u and size %u,%u",
// block.crop().left, block.crop().top,
// block.crop().width, block.crop().height,
// block.width(), block.height());
+ const C2ConstGraphicBlock &block = blocks[0];
updates.emplace_back(new C2StreamCropRectInfo::output(stream, block.crop()));
updates.emplace_back(new C2StreamPictureSizeInfo::output(
stream, block.crop().width, block.crop().height));
- break; // for now only do the first block
}
++stream;
}
@@ -1832,7 +1839,7 @@
// copy standard infos to graphic buffers if not already present (otherwise, we
// may overwrite the actual intermediate value with a final value)
stream = 0;
- const static std::vector<C2Param::Index> stdGfxInfos = {
+ const static C2Param::Index stdGfxInfos[] = {
C2StreamRotationInfo::output::PARAM_TYPE,
C2StreamColorAspectsInfo::output::PARAM_TYPE,
C2StreamDataSpaceInfo::output::PARAM_TYPE,
@@ -1861,7 +1868,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 +1958,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 +2067,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 +2085,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 +2130,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 +2160,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..369087d 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,16 @@
}
}
- if (!buffer && !flags && outputFormat == nullptr) {
- ALOGV("[%s] onWorkDone: nothing to report from the work (%lld)",
+ bool drop = false;
+ if (worklet->output.flags & C2FrameData::FLAG_DROP_FRAME) {
+ ALOGV("[%s] onWorkDone: drop buffer but keep metadata", mName);
+ drop = true;
+ }
+
+ if (notifyClient && !buffer && !flags && !(drop && outputFormat)) {
+ ALOGV("[%s] onWorkDone: Not reporting output buffer (%lld)",
mName, work->input.ordinal.frameIndex.peekull());
- return true;
+ notifyClient = false;
}
if (buffer) {
@@ -1727,63 +1746,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(
+ drop ? nullptr : 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/TEST_MAPPING b/media/codec2/sfplugin/TEST_MAPPING
new file mode 100644
index 0000000..045e5b5
--- /dev/null
+++ b/media/codec2/sfplugin/TEST_MAPPING
@@ -0,0 +1,12 @@
+// mappings for frameworks/av/media/codec2/sfplugin
+{
+ "presubmit": [
+ // failing 1 of 11
+ // TODO(b/156167471)
+ // { "name": "ccodec_unit_test" },
+
+ // failing 4 of 17, around max-input-size defaults & overrides
+ // TODO(b/156167471)
+ //{ "name": "mc_sanity_test"}
+ ]
+}
diff --git a/media/codec2/sfplugin/tests/Android.bp b/media/codec2/sfplugin/tests/Android.bp
index 8d1a9c3..5c774a2 100644
--- a/media/codec2/sfplugin/tests/Android.bp
+++ b/media/codec2/sfplugin/tests/Android.bp
@@ -1,5 +1,6 @@
cc_test {
name: "ccodec_unit_test",
+ test_suites: ["device-tests"],
srcs: [
"CCodecBuffers_test.cpp",
@@ -43,6 +44,7 @@
cc_test {
name: "mc_sanity_test",
+ test_suites: ["device-tests"],
srcs: [
"MediaCodec_sanity_test.cpp",
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/tests/Android.bp b/media/codec2/tests/Android.bp
index fce6e21..c9169a9 100644
--- a/media/codec2/tests/Android.bp
+++ b/media/codec2/tests/Android.bp
@@ -1,5 +1,6 @@
cc_test {
name: "codec2_core_param_test",
+ test_suites: ["device-tests"],
srcs: [
"C2Param_test.cpp",
@@ -28,6 +29,7 @@
cc_test {
name: "codec2_vndk_test",
+ test_suites: ["device-tests"],
srcs: [
"C2_test.cpp",
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/codecs/g711/decoder/Android.bp b/media/codecs/g711/decoder/Android.bp
new file mode 100644
index 0000000..efff60b
--- /dev/null
+++ b/media/codecs/g711/decoder/Android.bp
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+cc_library_static {
+ name: "codecs_g711dec",
+ vendor_available: true,
+ host_supported: true,
+
+ srcs: [
+ "g711DecAlaw.cpp",
+ "g711DecMlaw.cpp",
+ ],
+
+ export_include_dirs: ["."],
+
+ cflags: ["-Werror"],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ },
+ apex_available: ["com.android.media.swcodec"],
+ min_sdk_version: "29",
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
diff --git a/media/codecs/g711/decoder/g711Dec.h b/media/codecs/g711/decoder/g711Dec.h
new file mode 100644
index 0000000..ca357a5
--- /dev/null
+++ b/media/codecs/g711/decoder/g711Dec.h
@@ -0,0 +1,41 @@
+/*
+ * 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 G711_DEC_H_
+#define G711_DEC_H_
+
+/**
+ * @file g711Dec.h
+ * @brief g711 Decoder API: DecodeALaw and DecodeMLaw
+ */
+
+/** Decodes input bytes of size inSize according to ALAW
+ *
+ * @param [in] out <tt>int16_t*</tt>: output buffer to be filled with decoded bytes.
+ * @param [in] in <tt>const uint8_t*</tt>: input buffer containing bytes to be decoded.
+ * @param [in] inSize <tt>size_t</tt>: size of the input buffer.
+ */
+void DecodeALaw(int16_t *out, const uint8_t *in, size_t inSize);
+
+/** Decodes input bytes of size inSize according to MLAW
+ *
+ * @param [in] out <tt>int16_t*</tt>: output buffer to be filled with decoded bytes.
+ * @param [in] in <tt>const uint8_t*</tt>: input buffer containing bytes to be decoded.
+ * @param [in] inSize <tt>size_t</tt>: size of the input buffer.
+ */
+void DecodeMLaw(int16_t *out, const uint8_t *in, size_t inSize);
+
+#endif // G711_DECODER_H_
diff --git a/media/codecs/g711/decoder/g711DecAlaw.cpp b/media/codecs/g711/decoder/g711DecAlaw.cpp
new file mode 100644
index 0000000..e41a7b4
--- /dev/null
+++ b/media/codecs/g711/decoder/g711DecAlaw.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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 <stddef.h>
+#include <stdint.h>
+
+void DecodeALaw(int16_t *out, const uint8_t *in, size_t inSize) {
+ if (out != nullptr && in != nullptr) {
+ while (inSize > 0) {
+ inSize--;
+ int32_t x = *in++;
+
+ int32_t ix = x ^ 0x55;
+ ix &= 0x7f;
+
+ int32_t iexp = ix >> 4;
+ int32_t mant = ix & 0x0f;
+
+ if (iexp > 0) {
+ mant += 16;
+ }
+
+ mant = (mant << 4) + 8;
+
+ if (iexp > 1) {
+ mant = mant << (iexp - 1);
+ }
+
+ *out++ = (x > 127) ? mant : -mant;
+ }
+ }
+}
diff --git a/media/codecs/g711/decoder/g711DecMlaw.cpp b/media/codecs/g711/decoder/g711DecMlaw.cpp
new file mode 100644
index 0000000..bb2caea
--- /dev/null
+++ b/media/codecs/g711/decoder/g711DecMlaw.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 <stddef.h>
+#include <stdint.h>
+
+void DecodeMLaw(int16_t *out, const uint8_t *in, size_t inSize) {
+ if (out != nullptr && in != nullptr) {
+ while (inSize > 0) {
+ inSize--;
+ int32_t x = *in++;
+
+ int32_t mantissa = ~x;
+ int32_t exponent = (mantissa >> 4) & 7;
+ int32_t segment = exponent + 1;
+ mantissa &= 0x0f;
+
+ int32_t step = 4 << segment;
+
+ int32_t abs = (0x80l << exponent) + step * mantissa + step / 2 - 4 * 33;
+
+ *out++ = (x < 0x80) ? -abs : abs;
+ }
+ }
+}
diff --git a/media/codecs/g711/fuzzer/Android.bp b/media/codecs/g711/fuzzer/Android.bp
new file mode 100644
index 0000000..ff5efa9
--- /dev/null
+++ b/media/codecs/g711/fuzzer/Android.bp
@@ -0,0 +1,56 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+cc_fuzz {
+ name: "g711alaw_dec_fuzzer",
+ host_supported: true,
+ srcs: [
+ "g711_dec_fuzzer.cpp",
+ ],
+ static_libs: [
+ "codecs_g711dec",
+ ],
+ cflags: [
+ "-DALAW",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
+
+cc_fuzz {
+ name: "g711mlaw_dec_fuzzer",
+ host_supported: true,
+ srcs: [
+ "g711_dec_fuzzer.cpp",
+ ],
+ static_libs: [
+ "codecs_g711dec",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
diff --git a/media/codecs/g711/fuzzer/README.md b/media/codecs/g711/fuzzer/README.md
new file mode 100644
index 0000000..0c1c36b
--- /dev/null
+++ b/media/codecs/g711/fuzzer/README.md
@@ -0,0 +1,49 @@
+# Fuzzer for libstagefright_g711dec decoder
+
+## Plugin Design Considerations
+The fuzzer plugin for G711 is designed based on the understanding of the
+codec and tries to achieve the following:
+
+##### Maximize code coverage
+G711 supports two types of decoding:
+1. DecodeALaw
+2. DecodeMLaw
+
+These two decoder API's are fuzzed separately using g711alaw_dec_fuzzer and
+g711mlaw_dec_fuzzer respectively.
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the codec as expected by decoder API.
+
+## Build
+
+This describes steps to build g711alaw_dec_fuzzer and g711mlaw_dec_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) g711alaw_dec_fuzzer
+ $ mm -j$(nproc) g711mlaw_dec_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some g711 files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/g711alaw_dec_fuzzer/g711alaw_dec_fuzzer CORPUS_DIR
+ $ adb shell /data/fuzz/arm64/g711mlaw_dec_fuzzer/g711mlaw_dec_fuzzer CORPUS_DIR
+```
+To run on host
+```
+ $ $ANDROID_HOST_OUT/fuzz/x86_64/g711alaw_dec_fuzzer/g711alaw_dec_fuzzer CORPUS_DIR
+ $ $ANDROID_HOST_OUT/fuzz/x86_64/g711mlaw_dec_fuzzer/g711mlaw_dec_fuzzer CORPUS_DIR
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/media/codecs/g711/fuzzer/g711_dec_fuzzer.cpp b/media/codecs/g711/fuzzer/g711_dec_fuzzer.cpp
new file mode 100644
index 0000000..adfbcf5
--- /dev/null
+++ b/media/codecs/g711/fuzzer/g711_dec_fuzzer.cpp
@@ -0,0 +1,58 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "g711Dec.h"
+
+class Codec {
+ public:
+ Codec() = default;
+ ~Codec() = default;
+ void decodeFrames(const uint8_t *data, size_t size);
+};
+
+void Codec::decodeFrames(const uint8_t *data, size_t size) {
+ size_t outputBufferSize = sizeof(int16_t) * size;
+ int16_t *out = new int16_t[outputBufferSize];
+ if (!out) {
+ return;
+ }
+#ifdef ALAW
+ DecodeALaw(out, data, size);
+#else
+ DecodeMLaw(out, data, size);
+#endif
+ delete[] out;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < 1) {
+ return 0;
+ }
+ Codec *codec = new Codec();
+ if (!codec) {
+ return 0;
+ }
+ codec->decodeFrames(data, size);
+ delete codec;
+ return 0;
+}
diff --git a/media/extractors/Android.bp b/media/extractors/Android.bp
index bb42580..f9abfe3 100644
--- a/media/extractors/Android.bp
+++ b/media/extractors/Android.bp
@@ -21,9 +21,11 @@
shared_libs: [
"liblog",
- "libmediandk#29",
],
+ // extractors are supposed to work on Q(29)
+ min_sdk_version: "29",
+
relative_install_path: "extractors",
compile_multilib: "first",
@@ -36,6 +38,21 @@
version_script: "exports.lds",
+ target: {
+ android: {
+ shared_libs: [
+ "libmediandk#29",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libutils",
+ "libmediandk_format",
+ "libmedia_ndkformatpriv",
+ ],
+ },
+ },
+
sanitize: {
cfi: true,
misc_undefined: [
diff --git a/media/extractors/TEST_MAPPING b/media/extractors/TEST_MAPPING
new file mode 100644
index 0000000..038b99a
--- /dev/null
+++ b/media/extractors/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ // { "name": "ExtractorUnitTest" },
+
+ // 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/aac/Android.bp b/media/extractors/aac/Android.bp
index 60d3ae1..c036bb5 100644
--- a/media/extractors/aac/Android.bp
+++ b/media/extractors/aac/Android.bp
@@ -10,4 +10,11 @@
"libutils",
],
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
diff --git a/media/extractors/amr/Android.bp b/media/extractors/amr/Android.bp
index 49c9567..440065f 100644
--- a/media/extractors/amr/Android.bp
+++ b/media/extractors/amr/Android.bp
@@ -8,4 +8,10 @@
"libstagefright_foundation",
],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ }
}
diff --git a/media/extractors/flac/Android.bp b/media/extractors/flac/Android.bp
index 826c1a0..2593000 100644
--- a/media/extractors/flac/Android.bp
+++ b/media/extractors/flac/Android.bp
@@ -21,4 +21,12 @@
"libutils",
],
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
}
diff --git a/media/extractors/fuzzers/Android.bp b/media/extractors/fuzzers/Android.bp
new file mode 100644
index 0000000..e900e57
--- /dev/null
+++ b/media/extractors/fuzzers/Android.bp
@@ -0,0 +1,336 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+cc_defaults {
+ name: "extractor-fuzzerbase-defaults",
+
+ local_include_dirs: [
+ "include",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ static_libs: [
+ "liblog",
+ "libstagefright_foundation",
+ "libmediandk_format",
+ "libmedia_ndkformatpriv",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "libbinder",
+ "libbase",
+ "libcutils",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
+
+cc_defaults {
+ name: "extractor-fuzzer-defaults",
+ defaults: ["extractor-fuzzerbase-defaults"],
+
+ static_libs: [
+ "libextractorfuzzerbase",
+ ],
+
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
+
+cc_defaults {
+ name: "mpeg2-extractor-fuzzer-defaults",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ include_dirs: [
+ "frameworks/av/media/extractors/mpeg2",
+ "frameworks/av/media/libstagefright",
+ ],
+
+ static_libs: [
+ "libstagefright_foundation_without_imemory",
+ "libstagefright_mpeg2support",
+ "libstagefright_mpeg2extractor",
+ "libstagefright_esds",
+ "libmpeg2extractor",
+ "libmedia_helper",
+ ],
+
+ shared_libs: [
+ "android.hardware.cas@1.0",
+ "android.hardware.cas.native@1.0",
+ "android.hidl.token@1.0-utils",
+ "android.hidl.allocator@1.0",
+ "libcrypto",
+ "libhidlmemory",
+ "libhidlbase",
+ ],
+}
+
+cc_library_static {
+ name: "libextractorfuzzerbase",
+ defaults: ["extractor-fuzzerbase-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "ExtractorFuzzerBase.cpp",
+ ],
+}
+
+cc_fuzz {
+ name: "mp4_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "mp4_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/mp4",
+ ],
+
+ header_libs: [
+ "libaudioclient_headers",
+ ],
+
+ static_libs: [
+ "libstagefright_id3",
+ "libstagefright_esds",
+ "libmp4extractor",
+ ],
+
+ dictionary: "mp4_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
+ name: "wav_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "wav_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/wav",
+ ],
+
+ static_libs: [
+ "libfifo",
+ "libwavextractor",
+ ],
+
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+}
+
+cc_fuzz {
+ name: "amr_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "amr_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/amr",
+ ],
+
+ static_libs: [
+ "libamrextractor",
+ ],
+
+ dictionary: "amr_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
+ name: "mkv_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "mkv_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/mkv",
+ ],
+
+ static_libs: [
+ "libwebm",
+ "libstagefright_flacdec",
+ "libstagefright_metadatautils",
+ "libmkvextractor",
+ "libFLAC",
+ ],
+
+ dictionary: "mkv_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
+ name: "ogg_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "ogg_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/ogg",
+ ],
+
+ static_libs: [
+ "libstagefright_metadatautils",
+ "libvorbisidec",
+ "liboggextractor",
+ ],
+
+ dictionary: "ogg_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
+ name: "mpeg2ps_extractor_fuzzer",
+ defaults: ["mpeg2-extractor-fuzzer-defaults"],
+
+ srcs: [
+ "mpeg2_extractor_fuzzer.cpp",
+ ],
+
+ cflags: [
+ "-DMPEG2PS",
+ ],
+
+ dictionary: "mpeg2ps_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
+ name: "mpeg2ts_extractor_fuzzer",
+ defaults: ["mpeg2-extractor-fuzzer-defaults"],
+
+ srcs: [
+ "mpeg2_extractor_fuzzer.cpp",
+ ],
+
+ dictionary: "mpeg2ts_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
+ name: "mp3_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "mp3_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/mp3",
+ ],
+
+ static_libs: [
+ "libfifo",
+ "libmp3extractor",
+ "libstagefright_id3",
+ ],
+}
+
+cc_fuzz {
+ name: "aac_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "aac_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/aac",
+ ],
+
+ static_libs: [
+ "libaacextractor",
+ "libstagefright_metadatautils",
+ ],
+}
+
+cc_fuzz {
+ name: "flac_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+ host_supported: true,
+
+ srcs: [
+ "flac_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/flac",
+ ],
+
+ static_libs: [
+ "libstagefright_metadatautils",
+ "libFLAC",
+ "libflacextractor",
+ ],
+
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+
+ dictionary: "flac_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
+ name: "midi_extractor_fuzzer",
+ defaults: ["extractor-fuzzer-defaults"],
+
+ srcs: [
+ "midi_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/midi",
+ ],
+
+ static_libs: [
+ "libsonivox",
+ "libmedia_midiiowrapper",
+ "libmidiextractor",
+ "libwatchdog",
+ ],
+
+ dictionary: "midi_extractor_fuzzer.dict",
+
+ host_supported: true,
+}
diff --git a/media/extractors/fuzzers/ExtractorFuzzerBase.cpp b/media/extractors/fuzzers/ExtractorFuzzerBase.cpp
new file mode 100644
index 0000000..1be8466
--- /dev/null
+++ b/media/extractors/fuzzers/ExtractorFuzzerBase.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 "ExtractorFuzzerBase"
+#include <utils/Log.h>
+
+#include "ExtractorFuzzerBase.h"
+
+using namespace android;
+
+bool ExtractorFuzzerBase::setDataSource(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return false;
+ }
+ mBufferSource = new BufferSource(data, size);
+ mDataSource = reinterpret_cast<DataSource*>(mBufferSource.get());
+ if (!mDataSource) {
+ return false;
+ }
+ return true;
+}
+
+void ExtractorFuzzerBase::getExtractorDef() {
+ float confidence;
+ void* meta = nullptr;
+ FreeMetaFunc freeMeta = nullptr;
+
+ ExtractorDef extractorDef = GETEXTRACTORDEF();
+ if (extractorDef.def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+ extractorDef.u.v2.sniff(mDataSource->wrap(), &confidence, &meta, &freeMeta);
+ } else if (extractorDef.def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+ extractorDef.u.v3.sniff(mDataSource->wrap(), &confidence, &meta, &freeMeta);
+ }
+
+ if (meta != nullptr && freeMeta != nullptr) {
+ freeMeta(meta);
+ }
+}
+
+void ExtractorFuzzerBase::extractTracks() {
+ MediaBufferGroup* bufferGroup = new MediaBufferGroup();
+ if (!bufferGroup) {
+ return;
+ }
+ size_t trackCount = mExtractor->countTracks();
+ for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
+ MediaTrackHelper* track = mExtractor->getTrack(trackIndex);
+ if (!track) {
+ continue;
+ }
+ extractTrack(track, bufferGroup);
+ delete track;
+ }
+ delete bufferGroup;
+}
+
+void ExtractorFuzzerBase::extractTrack(MediaTrackHelper* track, MediaBufferGroup* bufferGroup) {
+ CMediaTrack* cTrack = wrap(track);
+ if (!cTrack) {
+ return;
+ }
+
+ media_status_t status = cTrack->start(track, bufferGroup->wrap());
+ if (status != AMEDIA_OK) {
+ free(cTrack);
+ return;
+ }
+
+ do {
+ MediaBufferHelper* buffer = nullptr;
+ status = track->read(&buffer);
+ if (buffer) {
+ buffer->release();
+ }
+ } while (status == AMEDIA_OK);
+
+ cTrack->stop(track);
+ free(cTrack);
+}
+
+void ExtractorFuzzerBase::getTracksMetadata() {
+ AMediaFormat* format = AMediaFormat_new();
+ uint32_t flags = MediaExtractorPluginHelper::kIncludeExtensiveMetaData;
+
+ size_t trackCount = mExtractor->countTracks();
+ for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
+ mExtractor->getTrackMetaData(format, trackIndex, flags);
+ }
+
+ AMediaFormat_delete(format);
+}
+
+void ExtractorFuzzerBase::getMetadata() {
+ AMediaFormat* format = AMediaFormat_new();
+ mExtractor->getMetaData(format);
+ AMediaFormat_delete(format);
+}
+
+void ExtractorFuzzerBase::setDataSourceFlags(uint32_t flags) {
+ mBufferSource->setFlags(flags);
+}
+
+void ExtractorFuzzerBase::seekAndExtractTracks() {
+ MediaBufferGroup* bufferGroup = new MediaBufferGroup();
+ if (!bufferGroup) {
+ return;
+ }
+ size_t trackCount = mExtractor->countTracks();
+ for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
+ MediaTrackHelper* track = mExtractor->getTrack(trackIndex);
+ if (!track) {
+ continue;
+ }
+
+ AMediaFormat* trackMetaData = AMediaFormat_new();
+ int64_t trackDuration = 0;
+ uint32_t flags = MediaExtractorPluginHelper::kIncludeExtensiveMetaData;
+ mExtractor->getTrackMetaData(trackMetaData, trackIndex, flags);
+ AMediaFormat_getInt64(trackMetaData, AMEDIAFORMAT_KEY_DURATION, &trackDuration);
+
+ seekAndExtractTrack(track, bufferGroup, trackDuration);
+ AMediaFormat_delete(trackMetaData);
+ delete track;
+ }
+ delete bufferGroup;
+}
+
+void ExtractorFuzzerBase::seekAndExtractTrack(MediaTrackHelper* track,
+ MediaBufferGroup* bufferGroup,
+ int64_t trackDuration) {
+ CMediaTrack* cTrack = wrap(track);
+ if (!cTrack) {
+ return;
+ }
+
+ media_status_t status = cTrack->start(track, bufferGroup->wrap());
+ if (status != AMEDIA_OK) {
+ free(cTrack);
+ return;
+ }
+
+ int32_t seekCount = 0;
+ std::vector<int64_t> seekToTimeStamp;
+ while (seekCount <= kFuzzerMaxSeekPointsCount) {
+ /* This ensures kFuzzerMaxSeekPointsCount seek points are within the clipDuration and 1 seek
+ * point is outside of the clipDuration.
+ */
+ int64_t timeStamp = (seekCount * trackDuration) / (kFuzzerMaxSeekPointsCount - 1);
+ seekToTimeStamp.push_back(timeStamp);
+ seekCount++;
+ }
+
+ std::vector<uint32_t> seekOptions;
+ seekOptions.push_back(CMediaTrackReadOptions::SEEK | CMediaTrackReadOptions::SEEK_CLOSEST);
+ seekOptions.push_back(CMediaTrackReadOptions::SEEK | CMediaTrackReadOptions::SEEK_CLOSEST_SYNC);
+ seekOptions.push_back(CMediaTrackReadOptions::SEEK | CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC);
+ seekOptions.push_back(CMediaTrackReadOptions::SEEK | CMediaTrackReadOptions::SEEK_NEXT_SYNC);
+ seekOptions.push_back(CMediaTrackReadOptions::SEEK | CMediaTrackReadOptions::SEEK_FRAME_INDEX);
+
+ for (uint32_t seekOption : seekOptions) {
+ for (int64_t seekPts : seekToTimeStamp) {
+ MediaTrackHelper::ReadOptions* options =
+ new MediaTrackHelper::ReadOptions(seekOption, seekPts);
+ MediaBufferHelper* buffer = nullptr;
+ track->read(&buffer, options);
+ if (buffer) {
+ buffer->release();
+ }
+ delete options;
+ }
+ }
+
+ cTrack->stop(track);
+ free(cTrack);
+}
+
+void ExtractorFuzzerBase::processData(const uint8_t* data, size_t size) {
+ if (setDataSource(data, size)) {
+ if (createExtractor()) {
+ getExtractorDef();
+ getMetadata();
+ extractTracks();
+ getTracksMetadata();
+ seekAndExtractTracks();
+ }
+ }
+}
diff --git a/media/extractors/fuzzers/README.md b/media/extractors/fuzzers/README.md
new file mode 100644
index 0000000..fb1d52f
--- /dev/null
+++ b/media/extractors/fuzzers/README.md
@@ -0,0 +1,362 @@
+# Fuzzer for extractors
+
+## Table of contents
++ [libextractorfuzzerbase](#ExtractorFuzzerBase)
++ [libmp4extractor](#mp4ExtractorFuzzer)
++ [libwavextractor](#wavExtractorFuzzer)
++ [libamrextractor](#amrExtractorFuzzer)
++ [libmkvextractor](#mkvExtractorFuzzer)
++ [liboggextractor](#oggExtractorFuzzer)
++ [libmpeg2extractor](#mpeg2ExtractorFuzzer)
++ [libmp3extractor](#mp3ExtractorFuzzer)
++ [libaacextractor](#aacExtractorFuzzer)
++ [libflacextractor](#flacExtractor)
++ [libmidiextractor](#midiExtractorFuzzer)
+
+# <a name="ExtractorFuzzerBase"></a> Fuzzer for libextractorfuzzerbase
+All the extractors have a common API - creating a data source, extraction
+of all the tracks, etc. These common APIs have been abstracted in a base class
+called `ExtractorFuzzerBase` to ensure code is reused between fuzzer plugins.
+
+Additionally, `ExtractorFuzzerBase` also has support for memory based buffer
+`BufferSource` since the fuzzing engine feeds data using memory buffers and
+usage of standard data source objects like FileSource, HTTPSource, etc. is
+not feasible.
+
+# <a name="mp4ExtractorFuzzer"></a> Fuzzer for libmp4extractor
+
+## Plugin Design Considerations
+The fuzzer plugin for MP4 extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the MP4 extractor class.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for MP4 to ensure that the required MP4
+atoms are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered as a range of MP4 atoms will be
+present in the input data.
+
+
+## Build
+
+This describes steps to build mp4_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) mp4_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some MP4 files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/mp4_extractor_fuzzer/mp4_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="wavExtractorFuzzer"></a> Fuzzer for libwavextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for WAV extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the WAV extractor class.
+
+
+## Build
+
+This describes steps to build wav_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) wav_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some wav files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/wav_extractor_fuzzer/wav_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="amrExtractorFuzzer"></a> Fuzzer for libamrextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for AMR extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the AMR extractor class.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for AMR to ensure that the required start
+bytes are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+
+## Build
+
+This describes steps to build amr_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) amr_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some AMR files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/amr_extractor_fuzzer/amr_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="mkvExtractorFuzzer"></a> Fuzzer for libmkvextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for MKV extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the MKV extractor class.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for MKV to ensure that the required element
+ID's are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+
+## Build
+
+This describes steps to build mkv_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) mkv_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some mkv files to that folder.
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/mkv_extractor_fuzzer/mkv_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="oggExtractorFuzzer"></a> Fuzzer for liboggextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for OGG extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the OGG extractor object.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for OGG to ensure that the required start
+bytes are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+
+## Build
+
+This describes steps to build ogg_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) ogg_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some ogg files to that folder.
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/ogg_extractor_fuzzer/ogg_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="mpeg2ExtractorFuzzer"></a> Fuzzer for libmpeg2extractor
+
+## Plugin Design Considerations
+The fuzzer plugins for MPEG2-PS and MPEG2-TS extractor use the `ExtractorFuzzerBase` class and
+implement only the `createExtractor` to create the MPEG2-PS or MPEG2-TS extractor
+object respectively.
+
+##### Maximize code coverage
+Dict files (dictionary files) are created for MPEG2-PS and MPEG2-TS to ensure that the
+required start bytes are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+##### Other considerations
+Two fuzzer binaries - mpeg2ps_extractor_fuzzer and mpeg2ts_extractor_fuzzer are
+generated based on the presence of a flag - `MPEG2PS`
+
+
+## Build
+
+This describes steps to build mpeg2ps_extractor_fuzzer and mpeg2ts_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) mpeg2ps_extractor_fuzzer
+ $ mm -j$(nproc) mpeg2ts_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some mpeg2 files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/mpeg2ps_extractor_fuzzer/mpeg2ps_extractor_fuzzer CORPUS_DIR
+ $ adb shell /data/fuzz/arm64/mpeg2ts_extractor_fuzzer/mpeg2ts_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="mp3ExtractorFuzzer"></a> Fuzzer for libmp3extractor
+
+## Plugin Design Considerations
+The fuzzer plugin for MP3 extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the MP3 extractor class.
+
+
+## Build
+
+This describes steps to build mp3_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) mp3_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some mp3 files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/mp3_extractor_fuzzer/mp3_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="aacExtractorFuzzer"></a> Fuzzer for libaacextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for AAC extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the AAC extractor class.
+
+
+## Build
+
+This describes steps to build aac_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) aac_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some aac files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/aac_extractor_fuzzer/aac_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="flacExtractor"></a> Fuzzer for libflacextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for FLAC extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the FLAC extractor object.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for FLAC to ensure that the required start
+bytes are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+
+## Build
+
+This describes steps to build flac_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) flac_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some flac files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/flac_extractor_fuzzer/flac_extractor_fuzzer CORPUS_DIR
+```
+
+# <a name="midiExtractorFuzzer"></a> Fuzzer for libmidiextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for MIDI extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the MIDI extractor class.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for MIDI to ensure that the required MIDI
+headers are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered as a range of MIDI headers will be
+present in the input data.
+
+
+## Build
+
+This describes steps to build midi_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) midi_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some MIDI files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/midi_extractor_fuzzer/midi_extractor_fuzzer CORPUS_DIR
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/media/extractors/fuzzers/aac_extractor_fuzzer.cpp b/media/extractors/fuzzers/aac_extractor_fuzzer.cpp
new file mode 100644
index 0000000..98a6cc9
--- /dev/null
+++ b/media/extractors/fuzzers/aac_extractor_fuzzer.cpp
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "AACExtractor.h"
+
+#include "ExtractorFuzzerBase.h"
+
+using namespace android;
+
+class AacExtractor : public ExtractorFuzzerBase {
+ public:
+ AacExtractor() = default;
+ ~AacExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool AacExtractor::createExtractor() {
+ mExtractor = new AACExtractor(new DataSourceHelper(mDataSource->wrap()), 0);
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ AacExtractor* extractor = new AacExtractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/amr_extractor_fuzzer.cpp b/media/extractors/fuzzers/amr_extractor_fuzzer.cpp
new file mode 100644
index 0000000..6c9e1a5
--- /dev/null
+++ b/media/extractors/fuzzers/amr_extractor_fuzzer.cpp
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "AMRExtractor.h"
+
+using namespace android;
+
+class AmrExtractor : public ExtractorFuzzerBase {
+ public:
+ AmrExtractor() = default;
+ ~AmrExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool AmrExtractor::createExtractor() {
+ mExtractor = new AMRExtractor(new DataSourceHelper(mDataSource->wrap()));
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ AmrExtractor* extractor = new AmrExtractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/amr_extractor_fuzzer.dict b/media/extractors/fuzzers/amr_extractor_fuzzer.dict
new file mode 100644
index 0000000..bc5726c
--- /dev/null
+++ b/media/extractors/fuzzers/amr_extractor_fuzzer.dict
@@ -0,0 +1,2 @@
+# Start code
+kw1="#!AMR"
diff --git a/media/extractors/fuzzers/flac_extractor_fuzzer.cpp b/media/extractors/fuzzers/flac_extractor_fuzzer.cpp
new file mode 100644
index 0000000..8734d45
--- /dev/null
+++ b/media/extractors/fuzzers/flac_extractor_fuzzer.cpp
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "FLACExtractor.h"
+
+using namespace android;
+
+class FlacExtractor : public ExtractorFuzzerBase {
+ public:
+ FlacExtractor() = default;
+ ~FlacExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool FlacExtractor::createExtractor() {
+ mExtractor = new FLACExtractor(new DataSourceHelper(mDataSource->wrap()));
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ FlacExtractor* extractor = new FlacExtractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/flac_extractor_fuzzer.dict b/media/extractors/fuzzers/flac_extractor_fuzzer.dict
new file mode 100644
index 0000000..53ad44f
--- /dev/null
+++ b/media/extractors/fuzzers/flac_extractor_fuzzer.dict
@@ -0,0 +1,3 @@
+# Start code (bytes 0-3)
+# The below 4 bytes correspond to "fLaC" in ASCII
+kw1="\x66\x4C\x61\x43"
diff --git a/media/extractors/fuzzers/include/ExtractorFuzzerBase.h b/media/extractors/fuzzers/include/ExtractorFuzzerBase.h
new file mode 100644
index 0000000..6a2a1c1
--- /dev/null
+++ b/media/extractors/fuzzers/include/ExtractorFuzzerBase.h
@@ -0,0 +1,139 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#ifndef __EXTRACTOR_FUZZER_BASE_H__
+#define __EXTRACTOR_FUZZER_BASE_H__
+
+#include <media/DataSource.h>
+#include <media/MediaExtractorPluginHelper.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <vector>
+
+extern "C" {
+android::ExtractorDef GETEXTRACTORDEF();
+}
+
+constexpr int32_t kFuzzerMaxSeekPointsCount = 5;
+
+namespace android {
+
+class ExtractorFuzzerBase {
+ public:
+ ExtractorFuzzerBase() = default;
+ virtual ~ExtractorFuzzerBase() {
+ if (mExtractor) {
+ delete mExtractor;
+ mExtractor = nullptr;
+ }
+ if (mBufferSource) {
+ mBufferSource.clear();
+ mBufferSource = nullptr;
+ }
+ }
+
+ /** Function to create the media extractor component.
+ * To be implemented by the derived class.
+ */
+ virtual bool createExtractor() = 0;
+
+ /** Parent class functions to be reused by derived class.
+ * These are common for all media extractor components.
+ */
+ bool setDataSource(const uint8_t* data, size_t size);
+
+ void getExtractorDef();
+
+ void extractTracks();
+
+ void getMetadata();
+
+ void getTracksMetadata();
+
+ void setDataSourceFlags(uint32_t flags);
+
+ void seekAndExtractTracks();
+
+ void processData(const uint8_t* data, size_t size);
+
+ protected:
+ class BufferSource : public DataSource {
+ public:
+ BufferSource(const uint8_t* data, size_t length) : mData(data), mLength(length) {}
+ virtual ~BufferSource() { mData = nullptr; }
+
+ void setFlags(uint32_t flags) { mFlags = flags; }
+
+ uint32_t flags() { return mFlags; }
+
+ status_t initCheck() const { return mData != nullptr ? OK : NO_INIT; }
+
+ ssize_t readAt(off64_t offset, void* data, size_t size) {
+ if (!mData) {
+ return NO_INIT;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ if ((offset >= static_cast<off64_t>(mLength)) || (offset < 0)) {
+ return 0; // read beyond bounds.
+ }
+ size_t numAvailable = mLength - static_cast<size_t>(offset);
+ if (size > numAvailable) {
+ size = numAvailable;
+ }
+ return readAt_l(offset, data, size);
+ }
+
+ status_t getSize(off64_t* size) {
+ if (!mData) {
+ return NO_INIT;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ *size = static_cast<off64_t>(mLength);
+ return OK;
+ }
+
+ protected:
+ ssize_t readAt_l(off64_t offset, void* data, size_t size) {
+ void* result = memcpy(data, mData + offset, size);
+ return result != nullptr ? size : 0;
+ }
+
+ const uint8_t* mData = nullptr;
+ size_t mLength = 0;
+ Mutex mLock;
+ uint32_t mFlags = 0;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(BufferSource);
+ };
+
+ sp<BufferSource> mBufferSource;
+ DataSource* mDataSource = nullptr;
+ MediaExtractorPluginHelper* mExtractor = nullptr;
+
+ virtual void extractTrack(MediaTrackHelper* track, MediaBufferGroup* bufferGroup);
+ virtual void seekAndExtractTrack(MediaTrackHelper* track, MediaBufferGroup* bufferGroup,
+ int64_t trackDuration);
+};
+
+} // namespace android
+
+#endif // __EXTRACTOR_FUZZER_BASE_H__
diff --git a/media/extractors/fuzzers/midi_extractor_fuzzer.cpp b/media/extractors/fuzzers/midi_extractor_fuzzer.cpp
new file mode 100644
index 0000000..e02a12b
--- /dev/null
+++ b/media/extractors/fuzzers/midi_extractor_fuzzer.cpp
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "MidiExtractor.h"
+
+using namespace android;
+
+class MIDIExtractor : public ExtractorFuzzerBase {
+ public:
+ MIDIExtractor() = default;
+ ~MIDIExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool MIDIExtractor::createExtractor() {
+ mExtractor = new MidiExtractor(mDataSource->wrap());
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ MIDIExtractor* extractor = new MIDIExtractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/midi_extractor_fuzzer.dict b/media/extractors/fuzzers/midi_extractor_fuzzer.dict
new file mode 100644
index 0000000..5b6bb8b
--- /dev/null
+++ b/media/extractors/fuzzers/midi_extractor_fuzzer.dict
@@ -0,0 +1,3 @@
+# MIDI Chunks
+kw1="MThd"
+kw2="MTrk"
diff --git a/media/extractors/fuzzers/mkv_extractor_fuzzer.cpp b/media/extractors/fuzzers/mkv_extractor_fuzzer.cpp
new file mode 100644
index 0000000..eceb93f
--- /dev/null
+++ b/media/extractors/fuzzers/mkv_extractor_fuzzer.cpp
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "MatroskaExtractor.h"
+
+using namespace android;
+
+class MKVExtractor : public ExtractorFuzzerBase {
+ public:
+ MKVExtractor() = default;
+ ~MKVExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool MKVExtractor::createExtractor() {
+ mExtractor = new MatroskaExtractor(new DataSourceHelper(mDataSource->wrap()));
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ MKVExtractor* extractor = new MKVExtractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/mkv_extractor_fuzzer.dict b/media/extractors/fuzzers/mkv_extractor_fuzzer.dict
new file mode 100644
index 0000000..b3815dc
--- /dev/null
+++ b/media/extractors/fuzzers/mkv_extractor_fuzzer.dict
@@ -0,0 +1,244 @@
+# Elements ID's
+kw1="\x42\x86"
+kw2="\x42\xF7"
+kw3="\x42\xF2"
+kw4="\x42\xF3"
+kw5="\x42\x87"
+kw6="\x42\x85"
+kw7="\x18\x53\x80\x67"
+kw8="\x11\x4D\x9B\x74"
+kw9="\x4D\xBB"
+kw10="\x53\xAB"
+kw11="\x53\xAC"
+kw12="\x15\x49\xA9\x66"
+kw13="\x73\xA4"
+kw14="\x73\x84"
+kw15="\x3C\xB9\x23"
+kw16="\x3C\x83\xAB"
+kw17="\x3C\xB9\x23"
+kw18="\x3E\x83\xBB"
+kw19="\x44\x44"
+kw20="\x69\x24"
+kw21="\x69\xFC"
+kw22="\x69\xBF"
+kw23="\x69\xA5"
+kw24="\x2A\xD7\xB1"
+kw25="\x44\x89"
+kw26="\x44\x61"
+kw27="\x7B\xA9"
+kw28="\x4D\x80"
+kw29="\x57\x41"
+kw30="\x1F\x43\xB6\x75"
+kw31="\xE7"
+kw32="\x58\x54"
+kw33="\x58\xD7"
+kw34="\xA7"
+kw35="\xAB"
+kw36="\xA3"
+kw37="\xA0"
+kw38="\xA1"
+kw39="\xA2"
+kw40="\x75\xA1"
+kw41="\x2A\xD7\xB1"
+kw42="\xA6"
+kw43="\xEE"
+kw44="\xA5"
+kw45="\x9A"
+kw46="\xFA"
+kw47="\xFB"
+kw48="\xFD"
+kw49="\xA4"
+kw50="\x75\xA2"
+kw51="\x8E"
+kw52="\xE8"
+kw53="\xCC"
+kw54="\xCD"
+kw55="\xCB"
+kw56="\xCE"
+kw57="\xCF"
+kw58="\xC8"
+kw59="\xC9"
+kw60="\xCA"
+kw61="\xAF"
+kw62="\x16\x54\xAE\x6B"
+kw63="\xAE"
+kw64="\xD7"
+kw65="\x73\xC5"
+kw66="\x83"
+kw67="\xB9"
+kw68="\x88"
+kw69="\x55\xAA"
+kw70="\x9C"
+kw71="\x6D\xE7"
+kw72="\x6D\xF8"
+kw73="\x23\xE3\x83"
+kw74="\x23\x4E\x7A"
+kw75="\x23\x31\x4F"
+kw76="\x53\x7F"
+kw77="\x55\xEE"
+kw78="\x53\x6E"
+kw79="\x22\xB5\x9C"
+kw80="\x22\xB5\x9D"
+kw81="\x86"
+kw82="\x63\xA2"
+kw83="\x25\x86\x88"
+kw84="\x26\xB2\x40"
+kw85="\xAA"
+kw86="\x6F\xAB"
+kw87="\x56\xAA"
+kw88="\x56\xBB"
+kw89="\x66\x24"
+kw90="\x66\xFC"
+kw91="\x66\xBF"
+kw92="\xE0"
+kw93="\x9A"
+kw94="\x9D"
+kw95="\x53\xB8"
+kw96="\x53\xC0"
+kw97="\x53\xB9"
+kw98="\xB0"
+kw99="\xBA"
+kw100="\x54\xAA"
+kw101="\x54\xBB"
+kw102="\x54\xCC"
+kw103="\x54\xDD"
+kw104="\x54\xB0"
+kw105="\x54\xBA"
+kw106="\x54\xB2"
+kw107="\x54\xB3"
+kw108="\x2E\xB5\x24"
+kw109="\x2F\xB5\x23"
+kw110="\x23\x83\xE3"
+kw111="\x55\xB0"
+kw112="\x55\xB1"
+kw113="\x55\xB2"
+kw114="\x55\xB3"
+kw115="\x55\xB4"
+kw116="\x55\xB5"
+kw117="\x55\xB6"
+kw118="\x55\xB7"
+kw119="\x55\xB8"
+kw120="\x55\xB9"
+kw121="\x55\xBA"
+kw122="\x55\xBB"
+kw123="\x55\xBC"
+kw124="\x55\xBD"
+kw125="\x55\xD0"
+kw126="\x55\xD1"
+kw127="\x55\xD2"
+kw128="\x55\xD3"
+kw129="\x55\xD4"
+kw130="\x55\xD5"
+kw131="\x55\xD6"
+kw132="\x55\xD7"
+kw133="\x55\xD8"
+kw134="\x55\xD9"
+kw135="\x55\xDA"
+kw136="\x76\x70"
+kw137="\x76\x71"
+kw138="\x76\x72"
+kw139="\x76\x73"
+kw140="\x76\x74"
+kw141="\x76\x75"
+kw142="\xE1"
+kw143="\xB5"
+kw144="\x78\xB5"
+kw145="\x9F"
+kw146="\x7D\x7B"
+kw147="\x62\x64"
+kw148="\xE2"
+kw149="\xE3"
+kw150="\xE4"
+kw151="\xE5"
+kw152="\xE6"
+kw153="\xE9"
+kw154="\xED"
+kw155="\xC0"
+kw156="\xC1"
+kw157="\xC6"
+kw158="\xC7"
+kw159="\xC4"
+kw160="\x6D\x80"
+kw161="\x62\x40"
+kw162="\x50\x31"
+kw163="\x50\x32"
+kw164="\x50\x33"
+kw165="\x50\x34"
+kw166="\x50\x35"
+kw167="\x42\x54"
+kw168="\x42\x55"
+kw169="\x47\xE1"
+kw170="\x47\xE2"
+kw171="\x47\xE7"
+kw172="\x47\xE8"
+kw173="\x47\xE3"
+kw174="\x47\xE4"
+kw175="\x47\xE5"
+kw176="\x47\xE6"
+kw177="\x1C\x53\xBB\x6B"
+kw178="\xBB"
+kw179="\xB3"
+kw180="\xB7"
+kw181="\xF7"
+kw182="\xF1"
+kw183="\xF0"
+kw184="\xB2"
+kw185="\x53\x78"
+kw186="\xEA"
+kw187="\xDB"
+kw188="\x96"
+kw189="\x97"
+kw190="\x53\x5F"
+kw191="\xEB"
+kw192="\x19\x41\xA4\x69"
+kw193="\x46\x7E"
+kw194="\x46\x6E"
+kw195="\x46\x60"
+kw196="\x46\x5C"
+kw197="\x46\xAE"
+kw198="\x46\x75"
+kw199="\x46\x61"
+kw200="\x46\x62"
+kw201="\x10\x43\xA7\x70"
+kw202="\x45\xB9"
+kw203="\x45\xBC"
+kw204="\x45\xBD"
+kw205="\x45\xDB"
+kw206="\x45\xDD"
+kw207="\xB6"
+kw208="\x73\xC4"
+kw209="\x56\x54"
+kw210="\x91"
+kw211="\x92"
+kw212="\x98"
+kw213="\x45\x98"
+kw214="\x6E\x67"
+kw215="\x6E\xBC"
+kw216="\x63\xC3"
+kw217="\x8F"
+kw218="\x89"
+kw219="\x80"
+kw220="\x85"
+kw221="\x43\x7C"
+kw222="\x43\x7D"
+kw223="\x43\x7E"
+kw224="\x69\x44"
+kw225="\x69\x55"
+kw226="\x45\x0D"
+kw227="\x69\x11"
+kw228="\x69\x22"
+kw229="\x69\x33"
+kw230="\x12\x54\xC3\x67"
+kw231="\x73\x73"
+kw232="\x63\xC0"
+kw233="\x68\xCA"
+kw234="\x63\xCA"
+kw235="\x63\xC5"
+kw236="\x63\xC9"
+kw237="\x67\xC8"
+kw238="\x45\xA3"
+kw239="\x44\x7A"
+kw240="\x44\x7B"
+kw241="\x44\x84"
+kw242="\x44\x87"
+kw243="\x44\x85"
diff --git a/media/extractors/fuzzers/mp3_extractor_fuzzer.cpp b/media/extractors/fuzzers/mp3_extractor_fuzzer.cpp
new file mode 100644
index 0000000..9a47c18
--- /dev/null
+++ b/media/extractors/fuzzers/mp3_extractor_fuzzer.cpp
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "MP3Extractor.h"
+
+using namespace android;
+
+class Mp3Extractor : public ExtractorFuzzerBase {
+ public:
+ Mp3Extractor() = default;
+ ~Mp3Extractor() = default;
+
+ bool createExtractor();
+};
+
+bool Mp3Extractor::createExtractor() {
+ mExtractor = new MP3Extractor(new DataSourceHelper(mDataSource->wrap()), nullptr);
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ Mp3Extractor* extractor = new Mp3Extractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/mp4_extractor_fuzzer.cpp b/media/extractors/fuzzers/mp4_extractor_fuzzer.cpp
new file mode 100644
index 0000000..3903519
--- /dev/null
+++ b/media/extractors/fuzzers/mp4_extractor_fuzzer.cpp
@@ -0,0 +1,56 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "MPEG4Extractor.h"
+#include "SampleTable.h"
+
+using namespace android;
+
+class MP4Extractor : public ExtractorFuzzerBase {
+ public:
+ MP4Extractor() = default;
+ ~MP4Extractor() = default;
+
+ bool createExtractor();
+};
+
+bool MP4Extractor::createExtractor() {
+ mExtractor = new MPEG4Extractor(new DataSourceHelper(mDataSource->wrap()));
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ setDataSourceFlags(DataSourceBase::kWantsPrefetching | DataSourceBase::kIsCachingDataSource);
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ MP4Extractor* extractor = new MP4Extractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/mp4_extractor_fuzzer.dict b/media/extractors/fuzzers/mp4_extractor_fuzzer.dict
new file mode 100644
index 0000000..3683649
--- /dev/null
+++ b/media/extractors/fuzzers/mp4_extractor_fuzzer.dict
@@ -0,0 +1,248 @@
+# MP4 Atoms/Boxes
+kw1="ftyp"
+kw2="free"
+kw3="mdat"
+kw4="moov"
+kw5="mvhd"
+kw6="trak"
+kw7="tkhd"
+kw8="edts"
+kw9="elst"
+kw10="mdia"
+kw11="mdhd"
+kw12="hdlr"
+kw13="minf"
+kw14="vmhd"
+kw15="dinf"
+kw16="dref"
+kw17="url "
+kw18="stbl"
+kw19="stsd"
+kw20="avc1"
+kw21="avcC"
+kw22="stts"
+kw23="stss"
+kw24="ctts"
+kw25="stsc"
+kw26="stsz"
+kw27="stco"
+kw28="mp4a"
+kw29="esds"
+kw30="udta"
+kw31="meta"
+kw32="ilst"
+kw33="samr"
+kw34="sawb"
+kw35="ec-3"
+kw36="mp4v"
+kw37="s263"
+kw38="h263"
+kw39="H263"
+kw40="avc1"
+kw41="hvc1"
+kw42="hev1"
+kw43="ac-4"
+kw44="Opus"
+kw45="twos"
+kw46="sowt"
+kw47="alac"
+kw48="fLaC"
+kw49="av01"
+kw50=".mp3"
+kw51="keys"
+kw52="cprt"
+kw53="covr"
+kw54="mvex"
+kw55="moof"
+kw56="traf"
+kw57="mfra"
+kw58="sinf"
+kw59="schi"
+kw60="wave"
+kw61="schm"
+kw62="cbc1"
+kw63="cbcs"
+kw64="cenc"
+kw65="cens"
+kw66="frma"
+kw67="tenc"
+kw68="tref"
+kw69="thmb"
+kw70="pssh"
+kw71="mett"
+kw72="enca"
+kw73="encv"
+kw74="co64"
+kw75="stz2"
+kw76="\xA9xyz"
+kw77="btrt"
+kw78="hvcC"
+kw79="av1C"
+kw80="d263"
+kw81="iloc"
+kw82="iinf"
+kw83="iprp"
+kw84="pitm"
+kw85="idat"
+kw86="iref"
+kw87="ipro"
+kw88="mean"
+kw89="name"
+kw90="data"
+kw91="mehd"
+kw92="text"
+kw93="sbtl"
+kw94="trex"
+kw95="tx3g"
+kw96="colr"
+kw97="titl"
+kw98="perf"
+kw99="auth"
+kw100="gnre"
+kw101="albm"
+kw102="yrrc"
+kw103="ID32"
+kw104="----"
+kw105="sidx"
+kw106="ac-3"
+kw107="qt "
+kw108="mif1"
+kw109="heic"
+kw110="dac4"
+kw111="dec3"
+kw112="dac3"
+kw113="\xA9alb"
+kw114="\xA9ART"
+kw115="aART"
+kw116="\xA9day"
+kw117="\xA9nam"
+kw118="\xA9wrt"
+kw119="\xA9gen"
+kw120="cpil"
+kw121="trkn"
+kw122="disk"
+kw123="nclx"
+kw124="nclc"
+kw125="tfhd"
+kw126="trun"
+kw127="saiz"
+kw128="saio"
+kw129="senc"
+kw130="isom"
+kw131="iso2"
+kw132="3gp4"
+kw133="mp41"
+kw134="mp42"
+kw135="dash"
+kw136="nvr1"
+kw137="MSNV"
+kw138="wmf "
+kw139="3g2a"
+kw140="3g2b"
+kw141="msf1"
+kw142="hevc"
+kw143="pdin"
+kw144="trgr"
+kw145="smhd"
+kw146="hmhd"
+kw147="nmhd"
+kw148="cslg"
+kw149="stsh"
+kw150="padb"
+kw151="stdp"
+kw152="sdtp"
+kw153="sbgp"
+kw154="sgpd"
+kw155="subs"
+kw156="leva"
+kw157="mfhd"
+kw158="tfdt"
+kw159="tfra"
+kw160="mfro"
+kw161="skip"
+kw162="tsel"
+kw163="strk"
+kw164="stri"
+kw165="strd"
+kw166="xml "
+kw167="bxml"
+kw168="fiin"
+kw169="paen"
+kw170="fire"
+kw171="fpar"
+kw172="fecr"
+kw173="segr"
+kw174="gitn"
+kw175="meco"
+kw176="mere"
+kw177="styp"
+kw178="ssix"
+kw179="prft"
+kw180="hint"
+kw181="cdsc"
+kw182="hind"
+kw183="vdep"
+kw184="vplx"
+kw185="msrc"
+kw186="urn "
+kw187="enct"
+kw188="encs"
+kw189="rinf"
+kw190="srpp"
+kw191="stsg"
+kw192="stvi"
+kw193="tims"
+kw194="tsro"
+kw195="snro"
+kw196="rtp "
+kw197="srtp"
+kw198="rtpo"
+kw199="hnti"
+kw200="sdp "
+kw201="trpy"
+kw202="nump"
+kw203="tpyl"
+kw204="totl"
+kw205="npck"
+kw206="tpay"
+kw207="maxr"
+kw208="dmed"
+kw209="dimm"
+kw210="drep"
+kw211="tmin"
+kw212="tmax"
+kw213="pmax"
+kw214="dmax"
+kw215="payt"
+kw216="fdp "
+kw217="fdsa"
+kw218="fdpa"
+kw219="extr"
+kw220="feci"
+kw221="rm2t"
+kw222="sm2t"
+kw223="tPAT"
+kw224="tPMT"
+kw225="tOD "
+kw226="tsti"
+kw227="istm"
+kw228="pm2t"
+kw229="rrtp"
+kw230="rssr"
+kw231="rscr"
+kw232="rsrp"
+kw233="rssr"
+kw234="ccid"
+kw235="sroc"
+kw236="prtp"
+kw237="roll"
+kw238="rash"
+kw239="alst"
+kw240="rap "
+kw241="tele"
+kw242="mp71"
+kw243="iso3"
+kw244="iso4"
+kw245="iso5"
+kw246="resv"
+kw247="iso6"
diff --git a/media/extractors/fuzzers/mpeg2_extractor_fuzzer.cpp b/media/extractors/fuzzers/mpeg2_extractor_fuzzer.cpp
new file mode 100644
index 0000000..240ef66
--- /dev/null
+++ b/media/extractors/fuzzers/mpeg2_extractor_fuzzer.cpp
@@ -0,0 +1,62 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#ifdef MPEG2PS
+#include "MPEG2PSExtractor.h"
+#else
+#include "MPEG2TSExtractor.h"
+#endif
+
+using namespace android;
+
+class MPEG2Extractor : public ExtractorFuzzerBase {
+ public:
+ MPEG2Extractor() = default;
+ ~MPEG2Extractor() = default;
+
+ bool createExtractor();
+};
+
+bool MPEG2Extractor::createExtractor() {
+#ifdef MPEG2PS
+ mExtractor = new MPEG2PSExtractor(new DataSourceHelper(mDataSource->wrap()));
+#else
+ mExtractor = new MPEG2TSExtractor(new DataSourceHelper(mDataSource->wrap()));
+#endif
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ MPEG2Extractor* extractor = new MPEG2Extractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/mpeg2ps_extractor_fuzzer.dict b/media/extractors/fuzzers/mpeg2ps_extractor_fuzzer.dict
new file mode 100644
index 0000000..69d390a
--- /dev/null
+++ b/media/extractors/fuzzers/mpeg2ps_extractor_fuzzer.dict
@@ -0,0 +1,2 @@
+# Start code (bytes 0-3)
+kw1="\x00\x00\x01\xBA"
diff --git a/media/extractors/fuzzers/mpeg2ts_extractor_fuzzer.dict b/media/extractors/fuzzers/mpeg2ts_extractor_fuzzer.dict
new file mode 100644
index 0000000..006a1eb
--- /dev/null
+++ b/media/extractors/fuzzers/mpeg2ts_extractor_fuzzer.dict
@@ -0,0 +1,2 @@
+# Start byte
+kw1="\x47"
diff --git a/media/extractors/fuzzers/ogg_extractor_fuzzer.cpp b/media/extractors/fuzzers/ogg_extractor_fuzzer.cpp
new file mode 100644
index 0000000..bd2fcc5
--- /dev/null
+++ b/media/extractors/fuzzers/ogg_extractor_fuzzer.cpp
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "OggExtractor.h"
+
+using namespace android;
+
+class OGGExtractor : public ExtractorFuzzerBase {
+ public:
+ OGGExtractor() = default;
+ ~OGGExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool OGGExtractor::createExtractor() {
+ mExtractor = new OggExtractor(new DataSourceHelper(mDataSource->wrap()));
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ OGGExtractor* extractor = new OGGExtractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/fuzzers/ogg_extractor_fuzzer.dict b/media/extractors/fuzzers/ogg_extractor_fuzzer.dict
new file mode 100644
index 0000000..df2fc38
--- /dev/null
+++ b/media/extractors/fuzzers/ogg_extractor_fuzzer.dict
@@ -0,0 +1,3 @@
+# Start code(bytes 0-3)
+# The below 4 bytes correspond to "OggS" in ASCII
+kw1="\x4F\x67\x67\x53"
diff --git a/media/extractors/fuzzers/wav_extractor_fuzzer.cpp b/media/extractors/fuzzers/wav_extractor_fuzzer.cpp
new file mode 100644
index 0000000..cb11ebd
--- /dev/null
+++ b/media/extractors/fuzzers/wav_extractor_fuzzer.cpp
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "WAVExtractor.h"
+
+using namespace android;
+
+class wavExtractor : public ExtractorFuzzerBase {
+ public:
+ wavExtractor() = default;
+ ~wavExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool wavExtractor::createExtractor() {
+ mExtractor = new WAVExtractor(new DataSourceHelper(mDataSource->wrap()));
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ wavExtractor* extractor = new wavExtractor();
+ if (extractor) {
+ extractor->processData(data, size);
+ delete extractor;
+ }
+ return 0;
+}
diff --git a/media/extractors/midi/Android.bp b/media/extractors/midi/Android.bp
index b8255fc..1c69bb8 100644
--- a/media/extractors/midi/Android.bp
+++ b/media/extractors/midi/Android.bp
@@ -5,7 +5,7 @@
srcs: ["MidiExtractor.cpp"],
header_libs: [
- "libmedia_headers",
+ "libmedia_datasource_headers",
],
static_libs: [
@@ -18,4 +18,12 @@
shared_libs: [
"libbase",
],
+
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
diff --git a/media/extractors/mkv/Android.bp b/media/extractors/mkv/Android.bp
index 7ad8cc1..330d4fe 100644
--- a/media/extractors/mkv/Android.bp
+++ b/media/extractors/mkv/Android.bp
@@ -21,4 +21,12 @@
"libutils",
],
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
}
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index b88e4e8..fd6a8c6 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -1888,13 +1888,12 @@
for(size_t i = 0; i < track->GetContentEncodingCount(); i++) {
const mkvparser::ContentEncoding *encoding = track->GetContentEncodingByIndex(i);
- for(size_t j = 0; j < encoding->GetEncryptionCount(); j++) {
+ if (encoding->GetEncryptionCount() > 0) {
const mkvparser::ContentEncoding::ContentEncryption *encryption;
- encryption = encoding->GetEncryptionByIndex(j);
+ encryption = encoding->GetEncryptionByIndex(0);
AMediaFormat_setBuffer(trackInfo->mMeta,
AMEDIAFORMAT_KEY_CRYPTO_KEY, encryption->key_id, encryption->key_id_len);
trackInfo->mEncrypted = true;
- break;
}
for(size_t j = 0; j < encoding->GetCompressionCount(); j++) {
diff --git a/media/extractors/mp3/Android.bp b/media/extractors/mp3/Android.bp
index 102ac81..7d70548 100644
--- a/media/extractors/mp3/Android.bp
+++ b/media/extractors/mp3/Android.bp
@@ -13,4 +13,11 @@
"libstagefright_foundation",
],
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
diff --git a/media/extractors/mp4/Android.bp b/media/extractors/mp4/Android.bp
index e48e1b7..afa055f 100644
--- a/media/extractors/mp4/Android.bp
+++ b/media/extractors/mp4/Android.bp
@@ -16,4 +16,12 @@
"libstagefright_id3",
"libutils",
],
+
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
old mode 100755
new mode 100644
index 54f1fa2..019a99a
--- 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 &);
@@ -369,6 +372,8 @@
return MEDIA_MIMETYPE_AUDIO_FLAC;
case FOURCC("av01"):
return MEDIA_MIMETYPE_VIDEO_AV1;
+ case FOURCC("vp09"):
+ return MEDIA_MIMETYPE_VIDEO_VP9;
case FOURCC(".mp3"):
case 0x6D730055: // "ms U" mp3 audio
return MEDIA_MIMETYPE_AUDIO_MPEG;
@@ -1784,7 +1789,7 @@
return ERROR_IO;
}
- uint16_t data_ref_index __unused = U16_AT(&buffer[6]);
+ // we can get data_ref_index value from U16_AT(&buffer[6])
uint16_t version = U16_AT(&buffer[8]);
uint32_t num_channels = U16_AT(&buffer[16]);
@@ -1969,6 +1974,7 @@
case FOURCC("dvh1"):
case FOURCC("dav1"):
case FOURCC("av01"):
+ case FOURCC("vp09"):
{
uint8_t buffer[78];
if (chunk_data_size < (ssize_t)sizeof(buffer)) {
@@ -1981,7 +1987,7 @@
return ERROR_IO;
}
- uint16_t data_ref_index __unused = U16_AT(&buffer[6]);
+ // we can get data_ref_index value from U16_AT(&buffer[6])
uint16_t width = U16_AT(&buffer[6 + 18]);
uint16_t height = U16_AT(&buffer[6 + 20]);
@@ -2431,6 +2437,8 @@
*offset += chunk_size;
break;
}
+
+ case FOURCC("vpcC"):
case FOURCC("av1C"):
{
auto buffer = heapbuffer<uint8_t>(chunk_data_size);
@@ -2872,6 +2880,21 @@
break;
}
+ case FOURCC("pasp"):
+ {
+ *offset += chunk_size;
+ // this must be in a VisualSampleEntry box under the Sample Description Box ('stsd')
+ // ignore otherwise
+ if (depth >= 2 && mPath[depth - 2] == FOURCC("stsd")) {
+ status_t err = parsePaspBox(data_offset, chunk_data_size);
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ break;
+ }
+
case FOURCC("titl"):
case FOURCC("perf"):
case FOURCC("auth"):
@@ -3404,7 +3427,7 @@
}
// skip
- unsigned bsmod __unused = br.getBits(3);
+ br.skipBits(3); // bsmod
unsigned acmod = br.getBits(3);
unsigned lfeon = br.getBits(1);
@@ -3715,19 +3738,18 @@
return ERROR_IO;
}
- uint64_t ctime __unused, mtime __unused, duration __unused;
int32_t id;
if (version == 1) {
- ctime = U64_AT(&buffer[4]);
- mtime = U64_AT(&buffer[12]);
+ // we can get ctime value from U64_AT(&buffer[4])
+ // we can get mtime value from U64_AT(&buffer[12])
id = U32_AT(&buffer[20]);
- duration = U64_AT(&buffer[28]);
+ // we can get duration value from U64_AT(&buffer[28])
} else if (version == 0) {
- ctime = U32_AT(&buffer[4]);
- mtime = U32_AT(&buffer[8]);
+ // we can get ctime value from U32_AT(&buffer[4])
+ // we can get mtime value from U32_AT(&buffer[8])
id = U32_AT(&buffer[12]);
- duration = U32_AT(&buffer[20]);
+ // we can get duration value from U32_AT(&buffer[20])
} else {
return ERROR_UNSUPPORTED;
}
@@ -3814,43 +3836,44 @@
switch ((int32_t)mPath[4]) {
case FOURCC("\251alb"):
{
- metadataKey = "album";
+ metadataKey = AMEDIAFORMAT_KEY_ALBUM;
break;
}
case FOURCC("\251ART"):
{
- metadataKey = "artist";
+ metadataKey = AMEDIAFORMAT_KEY_ARTIST;
break;
}
case FOURCC("aART"):
{
- metadataKey = "albumartist";
+ metadataKey = AMEDIAFORMAT_KEY_ALBUMARTIST;
break;
}
case FOURCC("\251day"):
{
- metadataKey = "year";
+ metadataKey = AMEDIAFORMAT_KEY_YEAR;
break;
}
case FOURCC("\251nam"):
{
- metadataKey = "title";
+ metadataKey = AMEDIAFORMAT_KEY_TITLE;
break;
}
case FOURCC("\251wrt"):
{
- metadataKey = "writer";
+ // various open source taggers agree that the "©wrt" tag is for composer, not writer
+ metadataKey = AMEDIAFORMAT_KEY_COMPOSER;
break;
}
case FOURCC("covr"):
{
- metadataKey = "albumart";
+ metadataKey = AMEDIAFORMAT_KEY_ALBUMART;
break;
}
case FOURCC("gnre"):
case FOURCC("\251gen"):
{
- metadataKey = "genre";
+ metadataKey = AMEDIAFORMAT_KEY_GENRE;
break;
}
case FOURCC("cpil"):
@@ -3955,7 +3978,7 @@
if (!strcmp(metadataKey, "albumart")) {
AMediaFormat_setBuffer(mFileMetaData, metadataKey,
buffer + 8, size - 8);
- } else if (!strcmp(metadataKey, "genre")) {
+ } else if (!strcmp(metadataKey, AMEDIAFORMAT_KEY_GENRE)) {
if (flags == 0) {
// uint8_t genre code, iTunes genre codes are
// the standard id3 codes, except they start
@@ -4044,6 +4067,26 @@
return OK;
}
+status_t MPEG4Extractor::parsePaspBox(off64_t offset, size_t size) {
+ if (size < 8 || size == SIZE_MAX || mLastTrack == NULL) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t data[2]; // hSpacing, vSpacing
+ if (mDataSource->readAt(offset, data, 8) < 8) {
+ return ERROR_IO;
+ }
+ uint32_t hSpacing = ntohl(data[0]);
+ uint32_t vSpacing = ntohl(data[1]);
+
+ if (hSpacing != 0 && vSpacing != 0) {
+ AMediaFormat_setInt32(mLastTrack->meta, AMEDIAFORMAT_KEY_SAR_WIDTH, hSpacing);
+ AMediaFormat_setInt32(mLastTrack->meta, AMEDIAFORMAT_KEY_SAR_HEIGHT, vSpacing);
+ }
+
+ return OK;
+}
+
status_t MPEG4Extractor::parse3GPPMetaData(off64_t offset, size_t size, int depth) {
if (size < 4 || size == SIZE_MAX) {
return ERROR_MALFORMED;
@@ -4333,6 +4376,18 @@
if (size < 5 || ptr[0] != 0x81) { // configurationVersion == 1
return NULL;
}
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_VP9)) {
+ void *data;
+ size_t size;
+ if (!AMediaFormat_getBuffer(track->meta, AMEDIAFORMAT_KEY_CSD_0, &data, &size)) {
+ return NULL;
+ }
+
+ const uint8_t *ptr = (const uint8_t *)data;
+
+ if (size < 5 || ptr[0] != 0x01) { // configurationVersion == 1
+ return NULL;
+ }
}
ALOGV("track->elst_shift_start_ticks :%" PRIu64, track->elst_shift_start_ticks);
@@ -4387,6 +4442,10 @@
if (!AMediaFormat_getBuffer(track->meta, AMEDIAFORMAT_KEY_CSD_0, &data, &size)) {
return ERROR_MALFORMED;
}
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_VP9)) {
+ if (!AMediaFormat_getBuffer(track->meta, AMEDIAFORMAT_KEY_CSD_0, &data, &size)) {
+ return ERROR_MALFORMED;
+ }
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4)
|| !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG2)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) {
@@ -4454,7 +4513,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 +4661,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);
}
@@ -4634,18 +4693,17 @@
if (objectType == AOT_SBR || objectType == AOT_PS) {//SBR specific config per 14496-3 tbl 1.13
if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
uint32_t extFreqIndex = br.getBits(4);
- int32_t extSampleRate __unused;
if (extFreqIndex == 15) {
if (csd_size < 8) {
return ERROR_MALFORMED;
}
if (br.numBitsLeft() < 24) return ERROR_MALFORMED;
- extSampleRate = br.getBits(24);
+ br.skipBits(24); // extSampleRate
} else {
if (extFreqIndex == 13 || extFreqIndex == 14) {
return ERROR_MALFORMED;
}
- extSampleRate = kSamplingRate[extFreqIndex];
+ //extSampleRate = kSamplingRate[extFreqIndex];
}
//TODO: save the extension sampling rate value in meta data =>
// AMediaFormat_setInt32(mLastTrack->meta, kKeyExtSampleRate, extSampleRate);
@@ -4688,13 +4746,13 @@
objectType == AOT_ER_AAC_LD || objectType == AOT_ER_AAC_SCAL ||
objectType == AOT_ER_BSAC) {
if (br.numBitsLeft() < 2) return ERROR_MALFORMED;
- const int32_t frameLengthFlag __unused = br.getBits(1);
+ br.skipBits(1); // frameLengthFlag
const int32_t dependsOnCoreCoder = br.getBits(1);
if (dependsOnCoreCoder ) {
if (br.numBitsLeft() < 14) return ERROR_MALFORMED;
- const int32_t coreCoderDelay __unused = br.getBits(14);
+ br.skipBits(14); // coreCoderDelay
}
int32_t extensionFlag = -1;
@@ -4726,64 +4784,64 @@
if (br.numBitsLeft() < 32) {
return ERROR_MALFORMED;
}
- const int32_t ElementInstanceTag __unused = br.getBits(4);
- const int32_t Profile __unused = br.getBits(2);
- const int32_t SamplingFrequencyIndex __unused = br.getBits(4);
+ br.skipBits(4); // ElementInstanceTag
+ br.skipBits(2); // Profile
+ br.skipBits(4); // SamplingFrequencyIndex
const int32_t NumFrontChannelElements = br.getBits(4);
const int32_t NumSideChannelElements = br.getBits(4);
const int32_t NumBackChannelElements = br.getBits(4);
const int32_t NumLfeChannelElements = br.getBits(2);
- const int32_t NumAssocDataElements __unused = br.getBits(3);
- const int32_t NumValidCcElements __unused = br.getBits(4);
+ br.skipBits(3); // NumAssocDataElements
+ br.skipBits(4); // NumValidCcElements
const int32_t MonoMixdownPresent = br.getBits(1);
if (MonoMixdownPresent != 0) {
if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
- const int32_t MonoMixdownElementNumber __unused = br.getBits(4);
+ br.skipBits(4); // MonoMixdownElementNumber
}
if (br.numBitsLeft() < 1) return ERROR_MALFORMED;
const int32_t StereoMixdownPresent = br.getBits(1);
if (StereoMixdownPresent != 0) {
if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
- const int32_t StereoMixdownElementNumber __unused = br.getBits(4);
+ br.skipBits(4); // StereoMixdownElementNumber
}
if (br.numBitsLeft() < 1) return ERROR_MALFORMED;
const int32_t MatrixMixdownIndexPresent = br.getBits(1);
if (MatrixMixdownIndexPresent != 0) {
if (br.numBitsLeft() < 3) return ERROR_MALFORMED;
- const int32_t MatrixMixdownIndex __unused = br.getBits(2);
- const int32_t PseudoSurroundEnable __unused = br.getBits(1);
+ br.skipBits(2); // MatrixMixdownIndex
+ br.skipBits(1); // PseudoSurroundEnable
}
int i;
for (i=0; i < NumFrontChannelElements; i++) {
if (br.numBitsLeft() < 5) return ERROR_MALFORMED;
const int32_t FrontElementIsCpe = br.getBits(1);
- const int32_t FrontElementTagSelect __unused = br.getBits(4);
+ br.skipBits(4); // FrontElementTagSelect
channelsNum += FrontElementIsCpe ? 2 : 1;
}
for (i=0; i < NumSideChannelElements; i++) {
if (br.numBitsLeft() < 5) return ERROR_MALFORMED;
const int32_t SideElementIsCpe = br.getBits(1);
- const int32_t SideElementTagSelect __unused = br.getBits(4);
+ br.skipBits(4); // SideElementTagSelect
channelsNum += SideElementIsCpe ? 2 : 1;
}
for (i=0; i < NumBackChannelElements; i++) {
if (br.numBitsLeft() < 5) return ERROR_MALFORMED;
const int32_t BackElementIsCpe = br.getBits(1);
- const int32_t BackElementTagSelect __unused = br.getBits(4);
+ br.skipBits(4); // BackElementTagSelect
channelsNum += BackElementIsCpe ? 2 : 1;
}
channelsEffectiveNum = channelsNum;
for (i=0; i < NumLfeChannelElements; i++) {
if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
- const int32_t LfeElementTagSelect __unused = br.getBits(4);
+ br.skipBits(4); // LfeElementTagSelect
channelsNum += 1;
}
ALOGV("mpeg4 audio channelsNum = %d", channelsNum);
@@ -4979,6 +5037,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 +5217,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 +5281,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 +5478,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 +6036,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);
}
@@ -6682,6 +6767,7 @@
FOURCC("hvc1"),
FOURCC("hev1"),
FOURCC("av01"),
+ FOURCC("vp09"),
FOURCC("3gp4"),
FOURCC("mp41"),
FOURCC("mp42"),
diff --git a/media/extractors/mp4/MPEG4Extractor.h b/media/extractors/mp4/MPEG4Extractor.h
index 1e49d50..bafc7f5 100644
--- a/media/extractors/mp4/MPEG4Extractor.h
+++ b/media/extractors/mp4/MPEG4Extractor.h
@@ -160,6 +160,7 @@
status_t parseChunk(off64_t *offset, int depth);
status_t parseITunesMetaData(off64_t offset, size_t size);
status_t parseColorInfo(off64_t offset, size_t size);
+ status_t parsePaspBox(off64_t offset, size_t size);
status_t parse3GPPMetaData(off64_t offset, size_t size, int depth);
void parseID3v2MetaData(off64_t offset, uint64_t size);
status_t parseQTMetaKey(off64_t data_offset, size_t data_size);
diff --git a/media/extractors/mpeg2/Android.bp b/media/extractors/mpeg2/Android.bp
index bc8632c..4c25314 100644
--- a/media/extractors/mpeg2/Android.bp
+++ b/media/extractors/mpeg2/Android.bp
@@ -1,6 +1,16 @@
cc_library {
name: "libmpeg2extractor",
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ android: {
+ shared_libs: ["libvndksupport#29"],
+ },
+ },
+
defaults: ["extractor-defaults"],
srcs: [
@@ -12,14 +22,13 @@
shared_libs: [
"libbase",
"libcgrouprc#29",
- "libvndksupport#29",
],
header_libs: [
"libaudioclient_headers",
"libbase_headers",
"libstagefright_headers",
- "libmedia_headers",
+ "libmedia_datasource_headers",
],
static_libs: [
@@ -37,7 +46,7 @@
"libstagefright_esds",
"libstagefright_foundation_without_imemory",
"libstagefright_mpeg2extractor",
- "libstagefright_mpeg2support",
+ "libstagefright_mpeg2support_nocrypto",
"libutils",
],
diff --git a/media/extractors/ogg/Android.bp b/media/extractors/ogg/Android.bp
index 7aed683..579065e 100644
--- a/media/extractors/ogg/Android.bp
+++ b/media/extractors/ogg/Android.bp
@@ -20,4 +20,11 @@
"libvorbisidec",
],
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
diff --git a/media/extractors/tests/Android.bp b/media/extractors/tests/Android.bp
index b3afe2f..0bca6f5 100644
--- a/media/extractors/tests/Android.bp
+++ b/media/extractors/tests/Android.bp
@@ -17,6 +17,7 @@
cc_test {
name: "ExtractorUnitTest",
gtest: true,
+ test_suites: ["device-tests"],
srcs: ["ExtractorUnitTest.cpp"],
diff --git a/media/extractors/tests/AndroidTest.xml b/media/extractors/tests/AndroidTest.xml
index 6bb2c8a..1f17d42 100644
--- a/media/extractors/tests/AndroidTest.xml
+++ b/media/extractors/tests/AndroidTest.xml
@@ -19,7 +19,7 @@
<option name="cleanup" value="true" />
<option name="push" value="ExtractorUnitTest->/data/local/tmp/ExtractorUnitTest" />
<option name="push-file"
- key="https://storage.googleapis.com/android_media/frameworks/av/media/extractors/tests/extractor.zip?unzip=true"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/extractors/tests/extractor-1.3.zip?unzip=true"
value="/data/local/tmp/ExtractorUnitTestRes/" />
</target_preparer>
diff --git a/media/extractors/tests/ExtractorUnitTest.cpp b/media/extractors/tests/ExtractorUnitTest.cpp
index 518166e..b7c6c59 100644
--- a/media/extractors/tests/ExtractorUnitTest.cpp
+++ b/media/extractors/tests/ExtractorUnitTest.cpp
@@ -20,8 +20,10 @@
#include <datasource/FileSource.h>
#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaDataUtils.h>
+#include <media/stagefright/foundation/OpusHeader.h>
#include "aac/AACExtractor.h"
#include "amr/AMRExtractor.h"
@@ -43,11 +45,76 @@
#define OUTPUT_DUMP_FILE "/data/local/tmp/extractorOutput"
constexpr int32_t kMaxCount = 10;
-constexpr int32_t kOpusSeekPreRollUs = 80000; // 80 ms;
+constexpr int32_t kAudioDefaultSampleDuration = 20000; // 20ms
+constexpr int32_t kRandomSeekToleranceUs = 2 * kAudioDefaultSampleDuration; // 40 ms;
+constexpr int32_t kRandomSeed = 700;
+constexpr int32_t kUndefined = -1;
+
+enum inputID {
+ // audio streams
+ AAC_1,
+ AMR_NB_1,
+ AMR_WB_1,
+ FLAC_1,
+ GSM_1,
+ MIDI_1,
+ MP3_1,
+ OPUS_1,
+ VORBIS_1,
+ // video streams
+ HEVC_1,
+ HEVC_2,
+ MPEG2_PS_1,
+ MPEG2_TS_1,
+ MPEG4_1,
+ VP9_1,
+ UNKNOWN_ID,
+};
+
+// LookUpTable of clips and metadata for component testing
+static const struct InputData {
+ inputID inpId;
+ string mime;
+ string inputFile;
+ int32_t firstParam;
+ int32_t secondParam;
+ int32_t profile;
+ int32_t frameRate;
+} kInputData[] = {
+ {AAC_1, MEDIA_MIMETYPE_AUDIO_AAC, "test_mono_44100Hz_aac.aac", 44100, 1, AACObjectLC,
+ kUndefined},
+ {AMR_NB_1, MEDIA_MIMETYPE_AUDIO_AMR_NB, "bbb_mono_8kHz_amrnb.amr", 8000, 1, kUndefined,
+ kUndefined},
+ {AMR_WB_1, MEDIA_MIMETYPE_AUDIO_AMR_WB, "bbb_mono_16kHz_amrwb.amr", 16000, 1, kUndefined,
+ kUndefined},
+ {FLAC_1, MEDIA_MIMETYPE_AUDIO_RAW, "bbb_stereo_48kHz_flac.flac", 48000, 2, kUndefined,
+ kUndefined},
+ {GSM_1, MEDIA_MIMETYPE_AUDIO_MSGSM, "test_mono_8kHz_gsm.wav", 8000, 1, kUndefined,
+ kUndefined},
+ {MIDI_1, MEDIA_MIMETYPE_AUDIO_RAW, "midi_a.mid", 22050, 2, kUndefined, kUndefined},
+ {MP3_1, MEDIA_MIMETYPE_AUDIO_MPEG, "bbb_stereo_48kHz_mp3.mp3", 48000, 2, kUndefined,
+ kUndefined},
+ {OPUS_1, MEDIA_MIMETYPE_AUDIO_OPUS, "test_stereo_48kHz_opus.opus", 48000, 2, kUndefined,
+ kUndefined},
+ {VORBIS_1, MEDIA_MIMETYPE_AUDIO_VORBIS, "bbb_stereo_48kHz_vorbis.ogg", 48000, 2, kUndefined,
+ kUndefined},
+
+ // Test (b/151677264) for MP4 extractor
+ {HEVC_1, MEDIA_MIMETYPE_VIDEO_HEVC, "crowd_508x240_25fps_hevc.mp4", 508, 240,
+ HEVCProfileMain, 25},
+ {HEVC_2, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC, "test3.heic", 820, 460, kUndefined, kUndefined},
+ {MPEG2_PS_1, MEDIA_MIMETYPE_VIDEO_MPEG2, "swirl_144x136_mpeg2.mpg", 144, 136,
+ MPEG2ProfileMain, 12},
+ {MPEG2_TS_1, MEDIA_MIMETYPE_VIDEO_MPEG2, "bbb_cif_768kbps_30fps_mpeg2.ts", 352, 288,
+ MPEG2ProfileMain, 30},
+ {MPEG4_1, MEDIA_MIMETYPE_VIDEO_MPEG4, "bbb_cif_768kbps_30fps_mpeg4.mkv", 352, 288,
+ MPEG4ProfileSimple, 30},
+ {VP9_1, MEDIA_MIMETYPE_VIDEO_VP9, "bbb_340x280_30fps_vp9.webm", 340, 280, VP9Profile0, 30},
+};
static ExtractorUnitTestEnvironment *gEnv = nullptr;
-class ExtractorUnitTest : public ::testing::TestWithParam<pair<string, string>> {
+class ExtractorUnitTest {
public:
ExtractorUnitTest() : mInputFp(nullptr), mDataSource(nullptr), mExtractor(nullptr) {}
@@ -66,16 +133,16 @@
}
}
- virtual void SetUp() override {
+ void setupExtractor(string writerFormat) {
mExtractorName = unknown_comp;
mDisableTest = false;
static const std::map<std::string, standardExtractors> mapExtractor = {
- {"aac", AAC}, {"amr", AMR}, {"mp3", MP3}, {"ogg", OGG},
- {"wav", WAV}, {"mkv", MKV}, {"flac", FLAC}, {"midi", MIDI},
- {"mpeg4", MPEG4}, {"mpeg2ts", MPEG2TS}, {"mpeg2ps", MPEG2PS}};
+ {"aac", AAC}, {"amr", AMR}, {"mp3", MP3}, {"ogg", OGG},
+ {"wav", WAV}, {"mkv", MKV}, {"flac", FLAC}, {"midi", MIDI},
+ {"mpeg4", MPEG4}, {"mpeg2ts", MPEG2TS}, {"mpeg2ps", MPEG2PS}, {"mp4", MPEG4},
+ {"webm", MKV}, {"ts", MPEG2TS}, {"mpeg", MPEG2PS}};
// Find the component type
- string writerFormat = GetParam().first;
if (mapExtractor.find(writerFormat) != mapExtractor.end()) {
mExtractorName = mapExtractor.at(writerFormat);
}
@@ -112,6 +179,39 @@
MediaExtractorPluginHelper *mExtractor;
};
+class ExtractorFunctionalityTest
+ : public ExtractorUnitTest,
+ public ::testing::TestWithParam<tuple<string /* container */, string /* InputFile */,
+ int32_t /* numTracks */, bool /* seekSupported */>> {
+ public:
+ virtual void SetUp() override {
+ tuple<string, string, int32_t, bool> params = GetParam();
+ mContainer = get<0>(params);
+ mNumTracks = get<2>(params);
+ setupExtractor(mContainer);
+ }
+ string mContainer;
+ int32_t mNumTracks;
+};
+
+class ConfigParamTest : public ExtractorUnitTest,
+ public ::testing::TestWithParam<pair<string, inputID>> {
+ public:
+ virtual void SetUp() override { setupExtractor(GetParam().first); }
+
+ struct configFormat {
+ string mime;
+ int32_t width;
+ int32_t height;
+ int32_t sampleRate;
+ int32_t channelCount;
+ int32_t profile;
+ int32_t frameRate;
+ };
+
+ void getFileProperties(inputID inputId, string &inputFile, configFormat &configParam);
+};
+
int32_t ExtractorUnitTest::setDataSource(string inputFileName) {
mInputFp = fopen(inputFileName.c_str(), "rb");
if (!mInputFp) {
@@ -168,6 +268,75 @@
return 0;
}
+void ConfigParamTest::getFileProperties(inputID inputId, string &inputFile,
+ configFormat &configParam) {
+ int32_t inputDataSize = sizeof(kInputData) / sizeof(kInputData[0]);
+ int32_t inputIdx = 0;
+ for (; inputIdx < inputDataSize; inputIdx++) {
+ if (inputId == kInputData[inputIdx].inpId) {
+ break;
+ }
+ }
+ if (inputIdx == inputDataSize) {
+ return;
+ }
+ inputFile += kInputData[inputIdx].inputFile;
+ configParam.mime = kInputData[inputIdx].mime;
+ size_t found = configParam.mime.find("audio/");
+ // Check if 'audio/' is present in the begininig of the mime type
+ if (found == 0) {
+ configParam.sampleRate = kInputData[inputIdx].firstParam;
+ configParam.channelCount = kInputData[inputIdx].secondParam;
+ } else {
+ configParam.width = kInputData[inputIdx].firstParam;
+ configParam.height = kInputData[inputIdx].secondParam;
+ }
+ configParam.profile = kInputData[inputIdx].profile;
+ configParam.frameRate = kInputData[inputIdx].frameRate;
+ return;
+}
+
+void randomSeekTest(MediaTrackHelper *track, int64_t clipDuration) {
+ int32_t status = 0;
+ int32_t seekCount = 0;
+ bool hasTimestamp = false;
+ vector<int64_t> seekToTimeStamp;
+ string seekPtsString;
+
+ srand(kRandomSeed);
+ while (seekCount < kMaxCount) {
+ int64_t timeStamp = ((double)rand() / RAND_MAX) * clipDuration;
+ seekToTimeStamp.push_back(timeStamp);
+ seekPtsString.append(to_string(timeStamp));
+ seekPtsString.append(", ");
+ seekCount++;
+ }
+
+ for (int64_t seekPts : seekToTimeStamp) {
+ MediaTrackHelper::ReadOptions *options = new MediaTrackHelper::ReadOptions(
+ CMediaTrackReadOptions::SEEK_CLOSEST | CMediaTrackReadOptions::SEEK, seekPts);
+ ASSERT_NE(options, nullptr) << "Cannot create read option";
+
+ MediaBufferHelper *buffer = nullptr;
+ status = track->read(&buffer, options);
+ if (buffer) {
+ AMediaFormat *metaData = buffer->meta_data();
+ int64_t timeStamp = 0;
+ hasTimestamp = AMediaFormat_getInt64(metaData, AMEDIAFORMAT_KEY_TIME_US, &timeStamp);
+ ASSERT_TRUE(hasTimestamp) << "Extractor didn't set timestamp for the given sample";
+
+ buffer->release();
+ EXPECT_LE(abs(timeStamp - seekPts), kRandomSeekToleranceUs)
+ << "Seek unsuccessful. Expected timestamp range ["
+ << seekPts - kRandomSeekToleranceUs << ", " << seekPts + kRandomSeekToleranceUs
+ << "] "
+ << "received " << timeStamp << ", list of input seek timestamps ["
+ << seekPtsString << "]";
+ }
+ delete options;
+ }
+}
+
void getSeekablePoints(vector<int64_t> &seekablePoints, MediaTrackHelper *track) {
int32_t status = 0;
if (!seekablePoints.empty()) {
@@ -190,20 +359,21 @@
}
}
-TEST_P(ExtractorUnitTest, CreateExtractorTest) {
+TEST_P(ExtractorFunctionalityTest, CreateExtractorTest) {
if (mDisableTest) return;
ALOGV("Checks if a valid extractor is created for a given input file");
- string inputFileName = gEnv->getRes() + GetParam().second;
+ string inputFileName = gEnv->getRes() + get<1>(GetParam());
- ASSERT_EQ(setDataSource(inputFileName), 0)
- << "SetDataSource failed for" << GetParam().first << "extractor";
+ int32_t status = setDataSource(inputFileName);
+ ASSERT_EQ(status, 0) << "SetDataSource failed for" << mContainer << "extractor";
- ASSERT_EQ(createExtractor(), 0)
- << "Extractor creation failed for" << GetParam().first << "extractor";
+ status = createExtractor();
+ ASSERT_EQ(status, 0) << "Extractor creation failed for" << mContainer << "extractor";
- // A valid extractor instace should return success for following calls
- ASSERT_GT(mExtractor->countTracks(), 0);
+ int32_t numTracks = mExtractor->countTracks();
+ ASSERT_EQ(numTracks, mNumTracks)
+ << "Extractor reported wrong number of track for the given clip";
AMediaFormat *format = AMediaFormat_new();
ASSERT_NE(format, nullptr) << "AMediaFormat_new returned null AMediaformat";
@@ -212,20 +382,21 @@
AMediaFormat_delete(format);
}
-TEST_P(ExtractorUnitTest, ExtractorTest) {
+TEST_P(ExtractorFunctionalityTest, ExtractorTest) {
if (mDisableTest) return;
- ALOGV("Validates %s Extractor for a given input file", GetParam().first.c_str());
- string inputFileName = gEnv->getRes() + GetParam().second;
+ ALOGV("Validates %s Extractor for a given input file", mContainer.c_str());
+ string inputFileName = gEnv->getRes() + get<1>(GetParam());
int32_t status = setDataSource(inputFileName);
- ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
+ ASSERT_EQ(status, 0) << "SetDataSource failed for" << mContainer << "extractor";
status = createExtractor();
- ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
+ ASSERT_EQ(status, 0) << "Extractor creation failed for" << mContainer << "extractor";
int32_t numTracks = mExtractor->countTracks();
- ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
+ ASSERT_EQ(numTracks, mNumTracks)
+ << "Extractor reported wrong number of track for the given clip";
for (int32_t idx = 0; idx < numTracks; idx++) {
MediaTrackHelper *track = mExtractor->getTrack(idx);
@@ -262,20 +433,21 @@
}
}
-TEST_P(ExtractorUnitTest, MetaDataComparisonTest) {
+TEST_P(ExtractorFunctionalityTest, MetaDataComparisonTest) {
if (mDisableTest) return;
ALOGV("Validates Extractor's meta data for a given input file");
- string inputFileName = gEnv->getRes() + GetParam().second;
+ string inputFileName = gEnv->getRes() + get<1>(GetParam());
int32_t status = setDataSource(inputFileName);
- ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
+ ASSERT_EQ(status, 0) << "SetDataSource failed for" << mContainer << "extractor";
status = createExtractor();
- ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
+ ASSERT_EQ(status, 0) << "Extractor creation failed for" << mContainer << "extractor";
int32_t numTracks = mExtractor->countTracks();
- ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
+ ASSERT_EQ(numTracks, mNumTracks)
+ << "Extractor reported wrong number of track for the given clip";
AMediaFormat *extractorFormat = AMediaFormat_new();
ASSERT_NE(extractorFormat, nullptr) << "AMediaFormat_new returned null AMediaformat";
@@ -337,20 +509,21 @@
AMediaFormat_delete(extractorFormat);
}
-TEST_P(ExtractorUnitTest, MultipleStartStopTest) {
+TEST_P(ExtractorFunctionalityTest, MultipleStartStopTest) {
if (mDisableTest) return;
- ALOGV("Test %s extractor for multiple start and stop calls", GetParam().first.c_str());
- string inputFileName = gEnv->getRes() + GetParam().second;
+ ALOGV("Test %s extractor for multiple start and stop calls", mContainer.c_str());
+ string inputFileName = gEnv->getRes() + get<1>(GetParam());
int32_t status = setDataSource(inputFileName);
- ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
+ ASSERT_EQ(status, 0) << "SetDataSource failed for" << mContainer << "extractor";
status = createExtractor();
- ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
+ ASSERT_EQ(status, 0) << "Extractor creation failed for" << mContainer << "extractor";
int32_t numTracks = mExtractor->countTracks();
- ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
+ ASSERT_EQ(numTracks, mNumTracks)
+ << "Extractor reported wrong number of track for the given clip";
// start/stop the tracks multiple times
for (int32_t count = 0; count < kMaxCount; count++) {
@@ -379,27 +552,28 @@
}
}
-TEST_P(ExtractorUnitTest, SeekTest) {
- // Both Flac and Wav extractor can give samples from any pts and mark the given sample as
- // sync frame. So, this seek test is not applicable to FLAC and WAV extractors
- if (mDisableTest || mExtractorName == FLAC || mExtractorName == WAV) return;
+TEST_P(ExtractorFunctionalityTest, SeekTest) {
+ if (mDisableTest) return;
- ALOGV("Validates %s Extractor behaviour for different seek modes", GetParam().first.c_str());
- string inputFileName = gEnv->getRes() + GetParam().second;
+ ALOGV("Validates %s Extractor behaviour for different seek modes", mContainer.c_str());
+ string inputFileName = gEnv->getRes() + get<1>(GetParam());
int32_t status = setDataSource(inputFileName);
- ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
+ ASSERT_EQ(status, 0) << "SetDataSource failed for" << mContainer << "extractor";
status = createExtractor();
- ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
+ ASSERT_EQ(status, 0) << "Extractor creation failed for" << mContainer << "extractor";
int32_t numTracks = mExtractor->countTracks();
- ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
+ ASSERT_EQ(numTracks, mNumTracks)
+ << "Extractor reported wrong number of track for the given clip";
uint32_t seekFlag = mExtractor->flags();
- if (!(seekFlag & MediaExtractorPluginHelper::CAN_SEEK)) {
- cout << "[ WARN ] Test Skipped. " << GetParam().first
- << " Extractor doesn't support seek\n";
+ bool seekSupported = get<3>(GetParam());
+ bool seekable = seekFlag & MediaExtractorPluginHelper::CAN_SEEK;
+ if (!seekable) {
+ ASSERT_FALSE(seekSupported) << mContainer << "Extractor is expected to support seek ";
+ cout << "[ WARN ] Test Skipped. " << mContainer << " Extractor doesn't support seek\n";
return;
}
@@ -415,19 +589,73 @@
MediaBufferGroup *bufferGroup = new MediaBufferGroup();
status = cTrack->start(track, bufferGroup->wrap());
ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
- getSeekablePoints(seekablePoints, track);
- ASSERT_GT(seekablePoints.size(), 0)
- << "Failed to get seekable points for " << GetParam().first << " extractor";
+
+ // For Flac, Wav and Midi extractor, all samples are seek points.
+ // We cannot create list of all seekable points for these.
+ // This means that if we pass a seekToTimeStamp between two seek points, we may
+ // end up getting the timestamp of next sample as a seekable timestamp.
+ // This timestamp may/may not be a part of the seekable point vector thereby failing the
+ // test. So we test these extractors using random seek test.
+ if (mExtractorName == FLAC || mExtractorName == WAV || mExtractorName == MIDI) {
+ AMediaFormat *trackMeta = AMediaFormat_new();
+ ASSERT_NE(trackMeta, nullptr) << "AMediaFormat_new returned null AMediaformat";
+
+ status = mExtractor->getTrackMetaData(trackMeta, idx, 1);
+ ASSERT_EQ(OK, (media_status_t)status) << "Failed to get trackMetaData";
+
+ int64_t clipDuration = 0;
+ AMediaFormat_getInt64(trackMeta, AMEDIAFORMAT_KEY_DURATION, &clipDuration);
+ ASSERT_GT(clipDuration, 0) << "Invalid clip duration ";
+ randomSeekTest(track, clipDuration);
+ AMediaFormat_delete(trackMeta);
+ continue;
+ }
AMediaFormat *trackFormat = AMediaFormat_new();
ASSERT_NE(trackFormat, nullptr) << "AMediaFormat_new returned null format";
status = track->getFormat(trackFormat);
ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
- bool isOpus = false;
const char *mime;
- AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
- if (!strcmp(mime, "audio/opus")) isOpus = true;
+ ASSERT_TRUE(AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime))
+ << "Failed to get mime";
+
+ // Image formats are not expected to be seekable
+ if (!strncmp(mime, "image/", 6)) continue;
+
+ // Request seekable points for remaining extractors which will be used to validate the seek
+ // accuracy for the extractors. Depending on SEEK Mode, we expect the extractors to return
+ // the expected sync frame. We don't prefer random seek test for these extractors because
+ // they aren't expected to seek to random samples. MP4 for instance can seek to
+ // next/previous sync frames but not to samples between two sync frames.
+ getSeekablePoints(seekablePoints, track);
+ ASSERT_GT(seekablePoints.size(), 0)
+ << "Failed to get seekable points for " << mContainer << " extractor";
+
+ bool isOpus = false;
+ int64_t opusSeekPreRollUs = 0;
+ if (!strcmp(mime, "audio/opus")) {
+ isOpus = true;
+ void *seekPreRollBuf = nullptr;
+ size_t size = 0;
+ if (!AMediaFormat_getBuffer(trackFormat, "csd-2", &seekPreRollBuf, &size)) {
+ size_t opusHeadSize = 0;
+ size_t codecDelayBufSize = 0;
+ size_t seekPreRollBufSize = 0;
+ void *csdBuffer = nullptr;
+ void *opusHeadBuf = nullptr;
+ void *codecDelayBuf = nullptr;
+ AMediaFormat_getBuffer(trackFormat, "csd-0", &csdBuffer, &size);
+ ASSERT_NE(csdBuffer, nullptr);
+
+ GetOpusHeaderBuffers((uint8_t *)csdBuffer, size, &opusHeadBuf, &opusHeadSize,
+ &codecDelayBuf, &codecDelayBufSize, &seekPreRollBuf,
+ &seekPreRollBufSize);
+ }
+ ASSERT_NE(seekPreRollBuf, nullptr)
+ << "Invalid track format. SeekPreRoll info missing for Opus file";
+ opusSeekPreRollUs = *((int64_t *)seekPreRollBuf);
+ }
AMediaFormat_delete(trackFormat);
int32_t seekIdx = 0;
@@ -448,7 +676,7 @@
// extractor is calculated based on (seekPts - seekPreRollUs).
// So we add the preRoll value to the timeStamp we want to seek to.
if (isOpus) {
- seekToTimeStamp += kOpusSeekPreRollUs;
+ seekToTimeStamp += opusSeekPreRollUs;
}
MediaTrackHelper::ReadOptions *options = new MediaTrackHelper::ReadOptions(
@@ -496,24 +724,461 @@
seekablePoints.clear();
}
-// TODO: (b/145332185)
-// Add MIDI inputs
-INSTANTIATE_TEST_SUITE_P(ExtractorUnitTestAll, ExtractorUnitTest,
- ::testing::Values(make_pair("aac", "loudsoftaac.aac"),
- make_pair("amr", "testamr.amr"),
- make_pair("amr", "amrwb.wav"),
- make_pair("ogg", "john_cage.ogg"),
- make_pair("wav", "monotestgsm.wav"),
- make_pair("mpeg2ts", "segment000001.ts"),
- make_pair("flac", "sinesweepflac.flac"),
- make_pair("ogg", "testopus.opus"),
- make_pair("mkv", "sinesweepvorbis.mkv"),
- make_pair("mpeg4", "sinesweepoggmp4.mp4"),
- make_pair("mp3", "sinesweepmp3lame.mp3"),
- make_pair("mkv", "swirl_144x136_vp9.webm"),
- make_pair("mkv", "swirl_144x136_vp8.webm"),
- make_pair("mpeg2ps", "swirl_144x136_mpeg2.mpg"),
- make_pair("mpeg4", "swirl_132x130_mpeg4.mp4")));
+// Tests the extractors for seek beyond range : (0, ClipDuration)
+TEST_P(ExtractorFunctionalityTest, MonkeySeekTest) {
+ if (mDisableTest) return;
+ // TODO(b/155630778): Enable test for wav extractors
+ if (mExtractorName == WAV) return;
+
+ ALOGV("Validates %s Extractor behaviour for invalid seek points", mContainer.c_str());
+ string inputFileName = gEnv->getRes() + get<1>(GetParam());
+
+ int32_t status = setDataSource(inputFileName);
+ ASSERT_EQ(status, 0) << "SetDataSource failed for" << mContainer << "extractor";
+
+ status = createExtractor();
+ ASSERT_EQ(status, 0) << "Extractor creation failed for" << mContainer << "extractor";
+
+ int32_t numTracks = mExtractor->countTracks();
+ ASSERT_EQ(numTracks, mNumTracks)
+ << "Extractor reported wrong number of track for the given clip";
+
+ uint32_t seekFlag = mExtractor->flags();
+ bool seekSupported = get<3>(GetParam());
+ bool seekable = seekFlag & MediaExtractorPluginHelper::CAN_SEEK;
+ if (!seekable) {
+ ASSERT_FALSE(seekSupported) << mContainer << "Extractor is expected to support seek ";
+ cout << "[ WARN ] Test Skipped. " << mContainer << " Extractor doesn't support seek\n";
+ return;
+ }
+
+ for (int32_t idx = 0; idx < numTracks; idx++) {
+ MediaTrackHelper *track = mExtractor->getTrack(idx);
+ ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
+
+ CMediaTrack *cTrack = wrap(track);
+ ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
+
+ MediaBufferGroup *bufferGroup = new MediaBufferGroup();
+ status = cTrack->start(track, bufferGroup->wrap());
+ ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
+
+ AMediaFormat *trackMeta = AMediaFormat_new();
+ ASSERT_NE(trackMeta, nullptr) << "AMediaFormat_new returned null AMediaformat";
+
+ status = mExtractor->getTrackMetaData(
+ trackMeta, idx, MediaExtractorPluginHelper::kIncludeExtensiveMetaData);
+ ASSERT_EQ(OK, (media_status_t)status) << "Failed to get trackMetaData";
+
+ const char *mime;
+ ASSERT_TRUE(AMediaFormat_getString(trackMeta, AMEDIAFORMAT_KEY_MIME, &mime))
+ << "Failed to get mime";
+
+ int64_t clipDuration = 0;
+ AMediaFormat_getInt64(trackMeta, AMEDIAFORMAT_KEY_DURATION, &clipDuration);
+ // Image formats are not expected to have duration information
+ ASSERT_TRUE(clipDuration > 0 || !strncmp(mime, "image/", 6)) << "Invalid clip duration ";
+ AMediaFormat_delete(trackMeta);
+
+ int64_t seekToTimeStampUs[] = {-clipDuration, clipDuration / 2, clipDuration,
+ clipDuration * 2};
+ for (int32_t mode = CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC;
+ mode <= CMediaTrackReadOptions::SEEK_CLOSEST; mode++) {
+ for (int64_t seekTimeUs : seekToTimeStampUs) {
+ MediaTrackHelper::ReadOptions *options = new MediaTrackHelper::ReadOptions(
+ mode | CMediaTrackReadOptions::SEEK, seekTimeUs);
+ ASSERT_NE(options, nullptr) << "Cannot create read option";
+
+ MediaBufferHelper *buffer = nullptr;
+ status = track->read(&buffer, options);
+ if (status == AMEDIA_ERROR_END_OF_STREAM) {
+ delete options;
+ continue;
+ }
+ if (buffer) {
+ AMediaFormat *metaData = buffer->meta_data();
+ int64_t timeStamp;
+ AMediaFormat_getInt64(metaData, AMEDIAFORMAT_KEY_TIME_US, &timeStamp);
+ ALOGV("Seeked to timestamp : %lld, requested : %lld", (long long)timeStamp,
+ (long long)seekTimeUs);
+ buffer->release();
+ }
+ delete options;
+ }
+ }
+ status = cTrack->stop(track);
+ ASSERT_EQ(OK, status) << "Failed to stop the track";
+ delete bufferGroup;
+ delete track;
+ }
+}
+
+// Tests extractors for invalid tracks
+TEST_P(ExtractorFunctionalityTest, SanityTest) {
+ if (mDisableTest) return;
+ // TODO(b/155626946): Enable test for MPEG2 TS/PS extractors
+ if (mExtractorName == MPEG2TS || mExtractorName == MPEG2PS) return;
+
+ ALOGV("Validates %s Extractor behaviour for invalid tracks", mContainer.c_str());
+ string inputFileName = gEnv->getRes() + get<1>(GetParam());
+
+ int32_t status = setDataSource(inputFileName);
+ ASSERT_EQ(status, 0) << "SetDataSource failed for" << mContainer << "extractor";
+
+ status = createExtractor();
+ ASSERT_EQ(status, 0) << "Extractor creation failed for" << mContainer << "extractor";
+
+ int32_t numTracks = mExtractor->countTracks();
+ ASSERT_EQ(numTracks, mNumTracks)
+ << "Extractor reported wrong number of track for the given clip";
+
+ int32_t trackIdx[] = {-1, numTracks};
+ for (int32_t idx : trackIdx) {
+ MediaTrackHelper *track = mExtractor->getTrack(idx);
+ ASSERT_EQ(track, nullptr) << "Failed to get track for index " << idx << "\n";
+
+ AMediaFormat *extractorFormat = AMediaFormat_new();
+ ASSERT_NE(extractorFormat, nullptr) << "AMediaFormat_new returned null AMediaformat";
+
+ status = mExtractor->getTrackMetaData(
+ extractorFormat, idx, MediaExtractorPluginHelper::kIncludeExtensiveMetaData);
+ ASSERT_NE(OK, status) << "getTrackMetaData should return error for invalid index " << idx;
+ AMediaFormat_delete(extractorFormat);
+ }
+
+ // Validate Extractor's getTrackMetaData for null format
+ AMediaFormat *mediaFormat = nullptr;
+ status = mExtractor->getTrackMetaData(mediaFormat, 0,
+ MediaExtractorPluginHelper::kIncludeExtensiveMetaData);
+ ASSERT_NE(OK, status) << "getTrackMetaData should return error for null Media format";
+}
+
+// This test validates config params for a given input file.
+// For this test we only take single track files since the focus of this test is
+// to validate the file properties reported by Extractor and not multi-track behavior
+TEST_P(ConfigParamTest, ConfigParamValidation) {
+ if (mDisableTest) return;
+
+ string container = GetParam().first;
+ ALOGV("Validates %s Extractor for input's file properties", container.c_str());
+ string inputFileName = gEnv->getRes();
+ inputID inputFileId = GetParam().second;
+ configFormat configParam;
+ getFileProperties(inputFileId, inputFileName, configParam);
+
+ int32_t status = setDataSource(inputFileName);
+ ASSERT_EQ(status, 0) << "SetDataSource failed for " << container << "extractor";
+
+ status = createExtractor();
+ ASSERT_EQ(status, 0) << "Extractor creation failed for " << container << "extractor";
+
+ int32_t numTracks = mExtractor->countTracks();
+ ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
+
+ MediaTrackHelper *track = mExtractor->getTrack(0);
+ ASSERT_NE(track, nullptr) << "Failed to get track for index 0";
+
+ AMediaFormat *trackFormat = AMediaFormat_new();
+ ASSERT_NE(trackFormat, nullptr) << "AMediaFormat_new returned null format";
+
+ status = track->getFormat(trackFormat);
+ ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
+
+ const char *trackMime;
+ bool valueFound = AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &trackMime);
+ ASSERT_TRUE(valueFound) << "Mime type not set by extractor";
+ ASSERT_STREQ(configParam.mime.c_str(), trackMime) << "Invalid track format";
+
+ if (!strncmp(trackMime, "audio/", 6)) {
+ int32_t trackSampleRate, trackChannelCount;
+ ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
+ &trackChannelCount));
+ ASSERT_TRUE(
+ AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &trackSampleRate));
+ ASSERT_EQ(configParam.sampleRate, trackSampleRate) << "SampleRate not as expected";
+ ASSERT_EQ(configParam.channelCount, trackChannelCount) << "ChannelCount not as expected";
+ } else {
+ int32_t trackWidth, trackHeight;
+ ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_WIDTH, &trackWidth));
+ ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_HEIGHT, &trackHeight));
+ ASSERT_EQ(configParam.width, trackWidth) << "Width not as expected";
+ ASSERT_EQ(configParam.height, trackHeight) << "Height not as expected";
+
+ if (configParam.frameRate != kUndefined) {
+ int32_t frameRate;
+ ASSERT_TRUE(
+ AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_FRAME_RATE, &frameRate));
+ ASSERT_EQ(configParam.frameRate, frameRate) << "frameRate not as expected";
+ }
+ }
+ // validate the profile for the input clip
+ int32_t profile;
+ if (configParam.profile != kUndefined) {
+ if (AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_PROFILE, &profile)) {
+ ASSERT_EQ(configParam.profile, profile) << "profile not as expected";
+ } else if (mExtractorName == AAC &&
+ AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_AAC_PROFILE, &profile)) {
+ ASSERT_EQ(configParam.profile, profile) << "profile not as expected";
+ } else {
+ ASSERT_TRUE(false) << "profile not returned in extractor";
+ }
+ }
+
+ delete track;
+ AMediaFormat_delete(trackFormat);
+}
+
+class ExtractorComparison
+ : public ExtractorUnitTest,
+ public ::testing::TestWithParam<pair<string /* InputFile0 */, string /* InputFile1 */>> {
+ public:
+ ~ExtractorComparison() {
+ for (int8_t *extractorOp : mExtractorOutput) {
+ if (extractorOp != nullptr) {
+ free(extractorOp);
+ }
+ }
+ }
+
+ virtual void SetUp() override {
+ string input0 = GetParam().first;
+ string input1 = GetParam().second;
+
+ // Allocate memory to hold extracted data for both extractors
+ struct stat buf;
+ int32_t status = stat((gEnv->getRes() + input0).c_str(), &buf);
+ ASSERT_EQ(status, 0) << "Unable to get file properties";
+
+ // allocating the buffer size as 2x since some
+ // extractors like flac, midi and wav decodes the file.
+ mExtractorOutput[0] = (int8_t *)calloc(1, buf.st_size * 2);
+ ASSERT_NE(mExtractorOutput[0], nullptr)
+ << "Unable to allocate memory for writing extractor's output";
+ mExtractorOuputSize[0] = buf.st_size * 2;
+
+ status = stat((gEnv->getRes() + input1).c_str(), &buf);
+ ASSERT_EQ(status, 0) << "Unable to get file properties";
+
+ // allocate buffer for extractor output, 2x input file size.
+ mExtractorOutput[1] = (int8_t *)calloc(1, buf.st_size * 2);
+ ASSERT_NE(mExtractorOutput[1], nullptr)
+ << "Unable to allocate memory for writing extractor's output";
+ mExtractorOuputSize[1] = buf.st_size * 2;
+ }
+
+ int8_t *mExtractorOutput[2]{};
+ size_t mExtractorOuputSize[2]{};
+};
+
+// Compare output of two extractors for identical content
+TEST_P(ExtractorComparison, ExtractorComparisonTest) {
+ vector<string> inputFileNames = {GetParam().first, GetParam().second};
+ size_t extractedOutputSize[2]{};
+ AMediaFormat *extractorFormat[2]{};
+ int32_t status = OK;
+
+ for (int32_t idx = 0; idx < inputFileNames.size(); idx++) {
+ string containerFormat = inputFileNames[idx].substr(inputFileNames[idx].find(".") + 1);
+ setupExtractor(containerFormat);
+ if (mDisableTest) {
+ ALOGV("Unknown extractor %s. Skipping the test", containerFormat.c_str());
+ return;
+ }
+
+ ALOGV("Validates %s Extractor for %s", containerFormat.c_str(),
+ inputFileNames[idx].c_str());
+ string inputFileName = gEnv->getRes() + inputFileNames[idx];
+
+ status = setDataSource(inputFileName);
+ ASSERT_EQ(status, 0) << "SetDataSource failed for" << containerFormat << "extractor";
+
+ status = createExtractor();
+ ASSERT_EQ(status, 0) << "Extractor creation failed for " << containerFormat << " extractor";
+
+ int32_t numTracks = mExtractor->countTracks();
+ ASSERT_EQ(numTracks, 1) << "This test expects inputs with one track only";
+
+ int32_t trackIdx = 0;
+ MediaTrackHelper *track = mExtractor->getTrack(trackIdx);
+ ASSERT_NE(track, nullptr) << "Failed to get track for index " << trackIdx;
+
+ extractorFormat[idx] = AMediaFormat_new();
+ ASSERT_NE(extractorFormat[idx], nullptr) << "AMediaFormat_new returned null AMediaformat";
+
+ status = track->getFormat(extractorFormat[idx]);
+ ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
+
+ CMediaTrack *cTrack = wrap(track);
+ ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << trackIdx;
+
+ MediaBufferGroup *bufferGroup = new MediaBufferGroup();
+ status = cTrack->start(track, bufferGroup->wrap());
+ ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
+
+ int32_t offset = 0;
+ while (status != AMEDIA_ERROR_END_OF_STREAM) {
+ MediaBufferHelper *buffer = nullptr;
+ status = track->read(&buffer);
+ ALOGV("track->read Status = %d buffer %p", status, buffer);
+ if (buffer) {
+ ASSERT_LE(offset + buffer->range_length(), mExtractorOuputSize[idx])
+ << "Memory overflow. Extracted output size more than expected";
+
+ memcpy(mExtractorOutput[idx] + offset, buffer->data(), buffer->range_length());
+ extractedOutputSize[idx] += buffer->range_length();
+ offset += buffer->range_length();
+ buffer->release();
+ }
+ }
+ status = cTrack->stop(track);
+ ASSERT_EQ(OK, status) << "Failed to stop the track";
+
+ fclose(mInputFp);
+ delete bufferGroup;
+ delete track;
+ mDataSource.clear();
+ delete mExtractor;
+ mInputFp = nullptr;
+ mExtractor = nullptr;
+ }
+
+ // Compare the meta data from both the extractors
+ const char *mime[2];
+ AMediaFormat_getString(extractorFormat[0], AMEDIAFORMAT_KEY_MIME, &mime[0]);
+ AMediaFormat_getString(extractorFormat[1], AMEDIAFORMAT_KEY_MIME, &mime[1]);
+ ASSERT_STREQ(mime[0], mime[1]) << "Mismatch between extractor's format";
+
+ if (!strncmp(mime[0], "audio/", 6)) {
+ int32_t channelCount0, channelCount1;
+ int32_t sampleRate0, sampleRate1;
+ ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat[0], AMEDIAFORMAT_KEY_CHANNEL_COUNT,
+ &channelCount0));
+ ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat[0], AMEDIAFORMAT_KEY_SAMPLE_RATE,
+ &sampleRate0));
+ ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat[1], AMEDIAFORMAT_KEY_CHANNEL_COUNT,
+ &channelCount1));
+ ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat[1], AMEDIAFORMAT_KEY_SAMPLE_RATE,
+ &sampleRate1));
+ ASSERT_EQ(channelCount0, channelCount1) << "Mismatch between extractor's channelCount";
+ ASSERT_EQ(sampleRate0, sampleRate1) << "Mismatch between extractor's sampleRate";
+ } else if (!strncmp(mime[0], "video/", 6)) {
+ int32_t width0, height0;
+ int32_t width1, height1;
+ ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat[0], AMEDIAFORMAT_KEY_WIDTH, &width0));
+ ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat[0], AMEDIAFORMAT_KEY_HEIGHT, &height0));
+ ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat[1], AMEDIAFORMAT_KEY_WIDTH, &width1));
+ ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat[1], AMEDIAFORMAT_KEY_HEIGHT, &height1));
+ ASSERT_EQ(width0, width1) << "Mismatch between extractor's width";
+ ASSERT_EQ(height0, height1) << "Mismatch between extractor's height";
+ } else {
+ ASSERT_TRUE(false) << "Invalid mime type " << mime[0];
+ }
+
+ for (AMediaFormat *exFormat : extractorFormat) {
+ AMediaFormat_delete(exFormat);
+ }
+
+ // Compare the extracted outputs of both extractor
+ ASSERT_EQ(extractedOutputSize[0], extractedOutputSize[1])
+ << "Extractor's output size doesn't match between " << inputFileNames[0] << "and "
+ << inputFileNames[1] << " extractors";
+ status = memcmp(mExtractorOutput[0], mExtractorOutput[1], extractedOutputSize[0]);
+ ASSERT_EQ(status, 0) << "Extracted content mismatch between " << inputFileNames[0] << "and "
+ << inputFileNames[1] << " extractors";
+}
+
+INSTANTIATE_TEST_SUITE_P(ExtractorComparisonAll, ExtractorComparison,
+ ::testing::Values(make_pair("swirl_144x136_vp9.mp4",
+ "swirl_144x136_vp9.webm"),
+ make_pair("video_480x360_mp4_vp9_333kbps_25fps.mp4",
+ "video_480x360_webm_vp9_333kbps_25fps.webm"),
+ make_pair("video_1280x720_av1_hdr_static_3mbps.mp4",
+ "video_1280x720_av1_hdr_static_3mbps.webm"),
+ make_pair("loudsoftaac.aac", "loudsoftaac.mkv")));
+
+INSTANTIATE_TEST_SUITE_P(ConfigParamTestAll, ConfigParamTest,
+ ::testing::Values(make_pair("aac", AAC_1),
+ make_pair("amr", AMR_NB_1),
+ make_pair("amr", AMR_WB_1),
+ make_pair("flac", FLAC_1),
+ make_pair("wav", GSM_1),
+ make_pair("midi", MIDI_1),
+ make_pair("mp3", MP3_1),
+ make_pair("ogg", OPUS_1),
+ make_pair("ogg", VORBIS_1),
+
+ make_pair("mpeg4", HEVC_1),
+ make_pair("mpeg4", HEVC_2),
+ make_pair("mpeg2ps", MPEG2_PS_1),
+ make_pair("mpeg2ts", MPEG2_TS_1),
+ make_pair("mkv", MPEG4_1),
+ make_pair("mkv", VP9_1)));
+
+// Validate extractors for container format, input file, no. of tracks and supports seek flag
+INSTANTIATE_TEST_SUITE_P(
+ ExtractorUnitTestAll, ExtractorFunctionalityTest,
+ ::testing::Values(
+ make_tuple("aac", "loudsoftaac.aac", 1, true),
+ make_tuple("amr", "testamr.amr", 1, true),
+ make_tuple("amr", "amrwb.wav", 1, true),
+ make_tuple("flac", "sinesweepflac.flac", 1, true),
+ make_tuple("midi", "midi_a.mid", 1, true),
+ make_tuple("mkv", "sinesweepvorbis.mkv", 1, true),
+ make_tuple("mkv", "sinesweepmp3lame.mkv", 1, true),
+ make_tuple("mkv", "loudsoftaac.mkv", 1, true),
+ make_tuple("mp3", "sinesweepmp3lame.mp3", 1, true),
+ make_tuple("mp3", "id3test10.mp3", 1, true),
+ make_tuple("mpeg2ts", "segment000001.ts", 2, false),
+ make_tuple("mpeg2ts", "testac3ts.ts", 1, false),
+ make_tuple("mpeg2ts", "testac4ts.ts", 1, false),
+ make_tuple("mpeg2ts", "testeac3ts.ts", 1, false),
+ make_tuple("mpeg4", "audio_aac_mono_70kbs_44100hz.mp4", 2, true),
+ make_tuple("mpeg4", "multi0_ac4.mp4", 1, true),
+ make_tuple("mpeg4", "noise_6ch_44khz_aot5_dr_sbr_sig2_mp4.m4a", 1, true),
+ make_tuple("mpeg4", "sinesweepalac.mov", 1, true),
+ make_tuple("mpeg4", "sinesweepflacmp4.mp4", 1, true),
+ make_tuple("mpeg4", "sinesweepm4a.m4a", 1, true),
+ make_tuple("mpeg4", "sinesweepoggmp4.mp4", 1, true),
+ make_tuple("mpeg4", "sinesweepopusmp4.mp4", 1, true),
+ make_tuple("mpeg4", "testac3mp4.mp4", 1, true),
+ make_tuple("mpeg4", "testeac3mp4.mp4", 1, true),
+ make_tuple("ogg", "john_cage.ogg", 1, true),
+ make_tuple("ogg", "testopus.opus", 1, true),
+ make_tuple("ogg", "sinesweepoggalbumart.ogg", 1, true),
+ make_tuple("wav", "loudsoftwav.wav", 1, true),
+ make_tuple("wav", "monotestgsm.wav", 1, true),
+ make_tuple("wav", "noise_5ch_44khz_aot2_wave.wav", 1, true),
+ make_tuple("wav", "sine1khzm40db_alaw.wav", 1, true),
+ make_tuple("wav", "sine1khzm40db_f32le.wav", 1, true),
+ make_tuple("wav", "sine1khzm40db_mulaw.wav", 1, true),
+
+ make_tuple("mkv", "swirl_144x136_avc.mkv", 1, true),
+ make_tuple("mkv", "withoutcues.mkv", 2, true),
+ make_tuple("mkv", "swirl_144x136_vp9.webm", 1, true),
+ make_tuple("mkv", "swirl_144x136_vp8.webm", 1, true),
+ make_tuple("mpeg2ps", "swirl_144x136_mpeg2.mpg", 1, false),
+ make_tuple("mpeg2ps", "programstream.mpeg", 2, false),
+ make_tuple("mpeg4", "color_176x144_bt601_525_lr_sdr_h264.mp4", 1, true),
+ make_tuple("mpeg4", "heifwriter_input.heic", 4, false),
+ make_tuple("mpeg4", "psshtest.mp4", 1, true),
+ make_tuple("mpeg4", "swirl_132x130_mpeg4.mp4", 1, true),
+ make_tuple("mpeg4", "testvideo.3gp", 4, true),
+ make_tuple("mpeg4", "testvideo_with_2_timedtext_tracks.3gp", 4, true),
+ make_tuple("mpeg4",
+ "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_"
+ "metadata_gyro_compliant.3gp",
+ 3, true),
+ make_tuple(
+ "mpeg4",
+ "video_1920x1080_mp4_mpeg2_12000kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+ 2, true),
+ make_tuple("mpeg4",
+ "video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 2,
+ true),
+ make_tuple(
+ "mpeg4",
+ "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash.mp4",
+ 2, true)));
int main(int argc, char **argv) {
gEnv = new ExtractorUnitTestEnvironment();
diff --git a/media/extractors/wav/Android.bp b/media/extractors/wav/Android.bp
index 5d38a81..85d4cce 100644
--- a/media/extractors/wav/Android.bp
+++ b/media/extractors/wav/Android.bp
@@ -19,4 +19,11 @@
"libfifo",
"libstagefright_foundation",
],
+
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
diff --git a/media/libaaudio/Doxyfile.orig b/media/libaaudio/Doxyfile.orig
deleted file mode 100644
index 137facb..0000000
--- a/media/libaaudio/Doxyfile.orig
+++ /dev/null
@@ -1,2303 +0,0 @@
-# Doxyfile 1.8.6
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project.
-#
-# All text after a double hash (##) is considered a comment and is placed in
-# front of the TAG it is preceding.
-#
-# All text after a single hash (#) is considered a comment and will be ignored.
-# The format is:
-# TAG = value [value, ...]
-# For lists, items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (\" \").
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all text
-# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
-# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
-# for the list of possible encodings.
-# The default value is: UTF-8.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
-# double-quotes, unless you are using Doxywizard) that should identify the
-# project for which the documentation is generated. This name is used in the
-# title of most generated pages and in a few other places.
-# The default value is: My Project.
-
-PROJECT_NAME = "My Project"
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
-# could be handy for archiving the generated documentation or if some version
-# control system is used.
-
-PROJECT_NUMBER =
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer a
-# quick idea about the purpose of the project. Keep the description short.
-
-PROJECT_BRIEF =
-
-# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
-# the documentation. The maximum height of the logo should not exceed 55 pixels
-# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
-# to the output directory.
-
-PROJECT_LOGO =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
-# into which the generated documentation will be written. If a relative path is
-# entered, it will be relative to the location where doxygen was started. If
-# left blank the current directory will be used.
-
-OUTPUT_DIRECTORY =
-
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
-# directories (in 2 levels) under the output directory of each output format and
-# will distribute the generated files over these directories. Enabling this
-# option can be useful when feeding doxygen a huge amount of source files, where
-# putting all generated files in the same directory would otherwise causes
-# performance problems for the file system.
-# The default value is: NO.
-
-CREATE_SUBDIRS = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
-# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
-# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
-# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
-# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
-# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
-# Ukrainian and Vietnamese.
-# The default value is: English.
-
-OUTPUT_LANGUAGE = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
-# descriptions after the members that are listed in the file and class
-# documentation (similar to Javadoc). Set to NO to disable this.
-# The default value is: YES.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
-# description of a member or function before the detailed description
-#
-# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-# The default value is: YES.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator that is
-# used to form the text in various listings. Each string in this list, if found
-# as the leading text of the brief description, will be stripped from the text
-# and the result, after processing the whole list, is used as the annotated
-# text. Otherwise, the brief description is used as-is. If left blank, the
-# following values are used ($name is automatically replaced with the name of
-# the entity):The $name class, The $name widget, The $name file, is, provides,
-# specifies, contains, represents, a, an and the.
-
-ABBREVIATE_BRIEF =
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# doxygen will generate a detailed section even if there is only a brief
-# description.
-# The default value is: NO.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-# The default value is: NO.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
-# before files name in the file list and in the header files. If set to NO the
-# shortest path that makes the file name unique will be used
-# The default value is: YES.
-
-FULL_PATH_NAMES = YES
-
-# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
-# Stripping is only done if one of the specified strings matches the left-hand
-# part of the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the path to
-# strip.
-#
-# Note that you can specify absolute paths here, but also relative paths, which
-# will be relative from the directory where doxygen is started.
-# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-
-STRIP_FROM_PATH =
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
-# path mentioned in the documentation of a class, which tells the reader which
-# header file to include in order to use a class. If left blank only the name of
-# the header file containing the class definition is used. Otherwise one should
-# specify the list of include paths that are normally passed to the compiler
-# using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
-# less readable) file names. This can be useful is your file systems doesn't
-# support long names like on DOS, Mac, or CD-ROM.
-# The default value is: NO.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
-# first line (until the first dot) of a Javadoc-style comment as the brief
-# description. If set to NO, the Javadoc-style will behave just like regular Qt-
-# style comments (thus requiring an explicit @brief command for a brief
-# description.)
-# The default value is: NO.
-
-JAVADOC_AUTOBRIEF = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
-# line (until the first dot) of a Qt-style comment as the brief description. If
-# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
-# requiring an explicit \brief command for a brief description.)
-# The default value is: NO.
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
-# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
-# a brief description. This used to be the default behavior. The new default is
-# to treat a multi-line C++ comment block as a detailed description. Set this
-# tag to YES if you prefer the old behavior instead.
-#
-# Note that setting this tag to YES also means that rational rose comments are
-# not recognized any more.
-# The default value is: NO.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
-# documentation from any documented member that it re-implements.
-# The default value is: YES.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
-# new page for each member. If set to NO, the documentation of a member will be
-# part of the file/class/namespace that contains it.
-# The default value is: NO.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
-# uses this value to replace tabs by spaces in code fragments.
-# Minimum value: 1, maximum value: 16, default value: 4.
-
-TAB_SIZE = 4
-
-# This tag can be used to specify a number of aliases that act as commands in
-# the documentation. An alias has the form:
-# name=value
-# For example adding
-# "sideeffect=@par Side Effects:\n"
-# will allow you to put the command \sideeffect (or @sideeffect) in the
-# documentation, which will result in a user-defined paragraph with heading
-# "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines.
-
-ALIASES =
-
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
-# only. Doxygen will then generate output that is more tailored for C. For
-# instance, some of the names that are used will be different. The list of all
-# members will be omitted, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_FOR_C = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
-# Python sources only. Doxygen will then generate output that is more tailored
-# for that language. For instance, namespaces will be presented as packages,
-# qualified scopes will look different, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources. Doxygen will then generate output that is tailored for Fortran.
-# The default value is: NO.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for VHDL.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given
-# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make
-# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
-# (default is Fortran), use: inc=Fortran f=C.
-#
-# Note For files without extension you can use no_extension as a placeholder.
-#
-# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
-# the files are not read by doxygen.
-
-EXTENSION_MAPPING =
-
-# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
-# according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you can
-# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
-# case of backward compatibilities issues.
-# The default value is: YES.
-
-MARKDOWN_SUPPORT = YES
-
-# When enabled doxygen tries to link words that correspond to documented
-# classes, or namespaces to their corresponding documentation. Such a link can
-# be prevented in individual cases by by putting a % sign in front of the word
-# or globally by setting AUTOLINK_SUPPORT to NO.
-# The default value is: YES.
-
-AUTOLINK_SUPPORT = YES
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should set this
-# tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string);
-# versus func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-# The default value is: NO.
-
-BUILTIN_STL_SUPPORT = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-# The default value is: NO.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
-# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
-# will parse them like normal C++ but will assume all classes use public instead
-# of private inheritance when no explicit protection keyword is present.
-# The default value is: NO.
-
-SIP_SUPPORT = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate
-# getter and setter methods for a property. Setting this option to YES will make
-# doxygen to replace the get and set methods by a property in the documentation.
-# This will only work if the methods are indeed getting or setting a simple
-# type. If this is not the case, or you want to show the methods anyway, you
-# should set this option to NO.
-# The default value is: YES.
-
-IDL_PROPERTY_SUPPORT = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-# The default value is: NO.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# Set the SUBGROUPING tag to YES to allow class member groups of the same type
-# (for instance a group of public functions) to be put as a subgroup of that
-# type (e.g. under the Public Functions section). Set it to NO to prevent
-# subgrouping. Alternatively, this can be done per class using the
-# \nosubgrouping command.
-# The default value is: YES.
-
-SUBGROUPING = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
-# are shown inside the group in which they are included (e.g. using \ingroup)
-# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
-# and RTF).
-#
-# Note that this feature does not work in combination with
-# SEPARATE_MEMBER_PAGES.
-# The default value is: NO.
-
-INLINE_GROUPED_CLASSES = NO
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
-# with only public data fields or simple typedef fields will be shown inline in
-# the documentation of the scope in which they are defined (i.e. file,
-# namespace, or group documentation), provided this scope is documented. If set
-# to NO, structs, classes, and unions are shown on a separate page (for HTML and
-# Man pages) or section (for LaTeX and RTF).
-# The default value is: NO.
-
-INLINE_SIMPLE_STRUCTS = NO
-
-# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
-# enum is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically be
-# useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-# The default value is: NO.
-
-TYPEDEF_HIDES_STRUCT = NO
-
-# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
-# cache is used to resolve symbols given their name and scope. Since this can be
-# an expensive process and often the same symbol appears multiple times in the
-# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
-# doxygen will become slower. If the cache is too large, memory is wasted. The
-# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
-# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
-# symbols. At the end of a run doxygen will report the cache usage and suggest
-# the optimal cache size from a speed point of view.
-# Minimum value: 0, maximum value: 9, default value: 0.
-
-LOOKUP_CACHE_SIZE = 0
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available. Private
-# class members and static file members will be hidden unless the
-# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
-# Note: This will also disable the warnings about undocumented members that are
-# normally produced when WARNINGS is set to YES.
-# The default value is: NO.
-
-EXTRACT_ALL = NO
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
-# be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PRIVATE = NO
-
-# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
-# scope will be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PACKAGE = NO
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
-# included in the documentation.
-# The default value is: NO.
-
-EXTRACT_STATIC = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
-# locally in source files will be included in the documentation. If set to NO
-# only classes defined in header files are included. Does not have any effect
-# for Java sources.
-# The default value is: YES.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. When set to YES local methods,
-# which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO only methods in the interface are
-# included.
-# The default value is: NO.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base name of
-# the file that contains the anonymous namespace. By default anonymous namespace
-# are hidden.
-# The default value is: NO.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
-# undocumented members inside documented classes or files. If set to NO these
-# members will be included in the various overviews, but no documentation
-# section is generated. This option has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy. If set
-# to NO these classes will be included in the various overviews. This option has
-# no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO these declarations will be
-# included in the documentation.
-# The default value is: NO.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
-# documentation blocks found inside the body of a function. If set to NO these
-# blocks will be appended to the function's detailed documentation block.
-# The default value is: NO.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation that is typed after a
-# \internal command is included. If the tag is set to NO then the documentation
-# will be excluded. Set it to YES to include the internal documentation.
-# The default value is: NO.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-# The default value is: system dependent.
-
-CASE_SENSE_NAMES = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES the
-# scope will be hidden.
-# The default value is: NO.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
-# the files that are included by a file in the documentation of that file.
-# The default value is: YES.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
-# grouped member an include statement to the documentation, telling the reader
-# which file to include in order to use the member.
-# The default value is: NO.
-
-SHOW_GROUPED_MEMB_INC = NO
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
-# files with double quotes in the documentation rather than with sharp brackets.
-# The default value is: NO.
-
-FORCE_LOCAL_INCLUDES = NO
-
-# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
-# documentation for inline members.
-# The default value is: YES.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
-# (detailed) documentation of file and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order.
-# The default value is: YES.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
-# descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order. Note that
-# this will also influence the order of the classes in the class list.
-# The default value is: NO.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
-# (brief and detailed) documentation of class members so that constructors and
-# destructors are listed first. If set to NO the constructors will appear in the
-# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
-# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
-# member documentation.
-# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
-# detailed member documentation.
-# The default value is: NO.
-
-SORT_MEMBERS_CTORS_1ST = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
-# of group names into alphabetical order. If set to NO the group names will
-# appear in their defined order.
-# The default value is: NO.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
-# fully-qualified names, including namespaces. If set to NO, the class list will
-# be sorted only by class name, not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the alphabetical
-# list.
-# The default value is: NO.
-
-SORT_BY_SCOPE_NAME = NO
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
-# type resolution of all parameters of a function it will reject a match between
-# the prototype and the implementation of a member function even if there is
-# only one candidate or it is obvious which candidate to choose by doing a
-# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
-# accept a match between prototype and implementation in such cases.
-# The default value is: NO.
-
-STRICT_PROTO_MATCHING = NO
-
-# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
-# todo list. This list is created by putting \todo commands in the
-# documentation.
-# The default value is: YES.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
-# test list. This list is created by putting \test commands in the
-# documentation.
-# The default value is: YES.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
-# list. This list is created by putting \bug commands in the documentation.
-# The default value is: YES.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
-# the deprecated list. This list is created by putting \deprecated commands in
-# the documentation.
-# The default value is: YES.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional documentation
-# sections, marked by \if <section_label> ... \endif and \cond <section_label>
-# ... \endcond blocks.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
-# initial value of a variable or macro / define can have for it to appear in the
-# documentation. If the initializer consists of more lines than specified here
-# it will be hidden. Use a value of 0 to hide initializers completely. The
-# appearance of the value of individual variables and macros / defines can be
-# controlled using \showinitializer or \hideinitializer command in the
-# documentation regardless of this setting.
-# Minimum value: 0, maximum value: 10000, default value: 30.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
-# the bottom of the documentation of classes and structs. If set to YES the list
-# will mention the files that were used to generate the documentation.
-# The default value is: YES.
-
-SHOW_USED_FILES = YES
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
-# will remove the Files entry from the Quick Index and from the Folder Tree View
-# (if specified).
-# The default value is: YES.
-
-SHOW_FILES = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
-# page. This will remove the Namespaces entry from the Quick Index and from the
-# Folder Tree View (if specified).
-# The default value is: YES.
-
-SHOW_NAMESPACES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command command input-file, where command is the value of the
-# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
-# by doxygen. Whatever the program writes to standard output is used as the file
-# version. For an example see the documentation.
-
-FILE_VERSION_FILTER =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option. You can
-# optionally specify a file name after the option, if omitted DoxygenLayout.xml
-# will be used as the name of the layout file.
-#
-# Note that if you run doxygen from a directory containing a file called
-# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
-# tag is left empty.
-
-LAYOUT_FILE =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
-# the reference definitions. This must be a list of .bib files. The .bib
-# extension is automatically appended if omitted. This requires the bibtex tool
-# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
-# For LaTeX the style of the bibliography can be controlled using
-# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
-# search path. Do not use file names with spaces, bibtex cannot handle them. See
-# also \cite for info how to create references.
-
-CITE_BIB_FILES =
-
-#---------------------------------------------------------------------------
-# Configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated to
-# standard output by doxygen. If QUIET is set to YES this implies that the
-# messages are off.
-# The default value is: NO.
-
-QUIET = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
-# this implies that the warnings are on.
-#
-# Tip: Turn warnings on while writing the documentation.
-# The default value is: YES.
-
-WARNINGS = YES
-
-# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
-# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
-# will automatically be disabled.
-# The default value is: YES.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some parameters
-# in a documented function, or documenting parameters that don't exist or using
-# markup commands wrongly.
-# The default value is: YES.
-
-WARN_IF_DOC_ERROR = YES
-
-# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
-# are documented, but have no documentation for their parameters or return
-# value. If set to NO doxygen will only warn about wrong or incomplete parameter
-# documentation, but not about the absence of documentation.
-# The default value is: NO.
-
-WARN_NO_PARAMDOC = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that doxygen
-# can produce. The string should contain the $file, $line, and $text tags, which
-# will be replaced by the file and line number from which the warning originated
-# and the warning text. Optionally the format may contain $version, which will
-# be replaced by the version of the file (if it could be obtained via
-# FILE_VERSION_FILTER)
-# The default value is: $file:$line: $text.
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning and error
-# messages should be written. If left blank the output is written to standard
-# error (stderr).
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag is used to specify the files and/or directories that contain
-# documented source files. You may enter file names like myfile.cpp or
-# directories like /usr/src/myproject. Separate the files or directories with
-# spaces.
-# Note: If this tag is empty the current directory is searched.
-
-INPUT =
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
-# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: http://www.gnu.org/software/libiconv) for the list of
-# possible encodings.
-# The default value is: UTF-8.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank the
-# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
-# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
-# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
-# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
-# *.qsf, *.as and *.js.
-
-FILE_PATTERNS =
-
-# The RECURSIVE tag can be used to specify whether or not subdirectories should
-# be searched for input files as well.
-# The default value is: NO.
-
-RECURSIVE = NO
-
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-#
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
-EXCLUDE =
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-# The default value is: NO.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories.
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories for example use the pattern */test/*
-
-EXCLUDE_PATTERNS =
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories use the pattern */test/*
-
-EXCLUDE_SYMBOLS =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or directories
-# that contain example code fragments that are included (see the \include
-# command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank all
-# files are included.
-
-EXAMPLE_PATTERNS =
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude commands
-# irrespective of the value of the RECURSIVE tag.
-# The default value is: NO.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or directories
-# that contain images that are to be included in the documentation (see the
-# \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command:
-#
-# <filter> <input-file>
-#
-# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
-# name of an input file. Doxygen will then use the output that the filter
-# program writes to standard output. If FILTER_PATTERNS is specified, this tag
-# will be ignored.
-#
-# Note that the filter must not add or remove lines; it is applied before the
-# code is scanned, but not when the output code is generated. If lines are added
-# or removed, the anchors will not be placed correctly.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form: pattern=filter
-# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
-# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
-# patterns match the file name, INPUT_FILTER is applied.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER ) will also be used to filter the input files that are used for
-# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
-# The default value is: NO.
-
-FILTER_SOURCE_FILES = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
-# it is also possible to disable source filtering for a specific pattern using
-# *.ext= (so without naming a filter).
-# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
-
-FILTER_SOURCE_PATTERNS =
-
-# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page
-# (index.html). This can be useful if you have a project on for instance GitHub
-# and want to reuse the introduction page also for the doxygen output.
-
-USE_MDFILE_AS_MAINPAGE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
-# generated. Documented entities will be cross-referenced with these sources.
-#
-# Note: To get rid of all source code in the generated output, make sure that
-# also VERBATIM_HEADERS is set to NO.
-# The default value is: NO.
-
-SOURCE_BROWSER = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body of functions,
-# classes and enums directly into the documentation.
-# The default value is: NO.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
-# special comment blocks from generated source code fragments. Normal C, C++ and
-# Fortran comments will always remain visible.
-# The default value is: YES.
-
-STRIP_CODE_COMMENTS = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# function all documented functions referencing it will be listed.
-# The default value is: NO.
-
-REFERENCED_BY_RELATION = NO
-
-# If the REFERENCES_RELATION tag is set to YES then for each documented function
-# all documented entities called/used by that function will be listed.
-# The default value is: NO.
-
-REFERENCES_RELATION = NO
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
-# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
-# link to the documentation.
-# The default value is: YES.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
-# source code will show a tooltip with additional information such as prototype,
-# brief description and links to the definition and documentation. Since this
-# will make the HTML file larger and loading of large files a bit slower, you
-# can opt to disable this feature.
-# The default value is: YES.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-SOURCE_TOOLTIPS = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code will
-# point to the HTML generated by the htags(1) tool instead of doxygen built-in
-# source browser. The htags tool is part of GNU's global source tagging system
-# (see http://www.gnu.org/software/global/global.html). You will need version
-# 4.8.6 or higher.
-#
-# To use it do the following:
-# - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
-# - Make sure the INPUT points to the root of the source tree
-# - Run doxygen as normal
-#
-# Doxygen will invoke htags (and that will in turn invoke gtags), so these
-# tools must be available from the command line (i.e. in the search path).
-#
-# The result: instead of the source browser generated by doxygen, the links to
-# source code will now point to the output of htags.
-# The default value is: NO.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
-# verbatim copy of the header file for each class for which an include is
-# specified. Set to NO to disable this.
-# See also: Section \class.
-# The default value is: YES.
-
-VERBATIM_HEADERS = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
-# compounds will be generated. Enable this if the project contains a lot of
-# classes, structs, unions or interfaces.
-# The default value is: YES.
-
-ALPHABETICAL_INDEX = YES
-
-# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
-# which the alphabetical index list will be split.
-# Minimum value: 1, maximum value: 20, default value: 5.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-COLS_IN_ALPHA_INDEX = 5
-
-# In case all classes in a project start with a common prefix, all classes will
-# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
-# can be used to specify a prefix (or a list of prefixes) that should be ignored
-# while generating the index headers.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
-# The default value is: YES.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_OUTPUT = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
-# generated HTML page (for example: .htm, .php, .asp).
-# The default value is: .html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
-# each generated HTML page. If the tag is left blank doxygen will generate a
-# standard header.
-#
-# To get valid HTML the header file that includes any scripts and style sheets
-# that doxygen needs, which is dependent on the configuration options used (e.g.
-# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
-# default header using
-# doxygen -w html new_header.html new_footer.html new_stylesheet.css
-# YourConfigFile
-# and then modify the file new_header.html. See also section "Doxygen usage"
-# for information on how to generate the default header that doxygen normally
-# uses.
-# Note: The header is subject to change so you typically have to regenerate the
-# default header when upgrading to a newer version of doxygen. For a description
-# of the possible markers and block names see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
-# generated HTML page. If the tag is left blank doxygen will generate a standard
-# footer. See HTML_HEADER for more information on how to generate a default
-# footer and what special commands can be used inside the footer. See also
-# section "Doxygen usage" for information on how to generate the default footer
-# that doxygen normally uses.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
-# sheet that is used by each HTML page. It can be used to fine-tune the look of
-# the HTML output. If left blank doxygen will generate a default style sheet.
-# See also section "Doxygen usage" for information on how to generate the style
-# sheet that doxygen normally uses.
-# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
-# it is more robust and this tag (HTML_STYLESHEET) will in the future become
-# obsolete.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_STYLESHEET =
-
-# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
-# defined cascading style sheet that is included after the standard style sheets
-# created by doxygen. Using this option one can overrule certain style aspects.
-# This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefor more robust against future updates.
-# Doxygen will copy the style sheet file to the output directory. For an example
-# see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_STYLESHEET =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
-# files will be copied as-is; there are no commands or markers available.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_FILES =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the stylesheet and background images according to
-# this color. Hue is specified as an angle on a colorwheel, see
-# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
-# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
-# purple, and 360 is red again.
-# Minimum value: 0, maximum value: 359, default value: 220.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_HUE = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
-# in the HTML output. For a value of 0 the output will use grayscales only. A
-# value of 255 will produce the most vivid colors.
-# Minimum value: 0, maximum value: 255, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_SAT = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
-# luminance component of the colors in the HTML output. Values below 100
-# gradually make the output lighter, whereas values above 100 make the output
-# darker. The value divided by 100 is the actual gamma applied, so 80 represents
-# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
-# change the gamma.
-# Minimum value: 40, maximum value: 240, default value: 80.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_GAMMA = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
-# shown in the various tree structured indices initially; the user can expand
-# and collapse entries dynamically later on. Doxygen will expand the tree to
-# such a level that at most the specified number of entries are visible (unless
-# a fully collapsed tree already exceeds this amount). So setting the number of
-# entries 1 will produce a full collapsed tree by default. 0 is a special value
-# representing an infinite number of entries and will result in a full expanded
-# tree by default.
-# Minimum value: 0, maximum value: 9999, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_INDEX_NUM_ENTRIES = 100
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files will be
-# generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: http://developer.apple.com/tools/xcode/), introduced with
-# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
-# Makefile in the HTML output directory. Running make will produce the docset in
-# that directory and running make install will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_DOCSET = NO
-
-# This tag determines the name of the docset feed. A documentation feed provides
-# an umbrella under which multiple documentation sets from a single provider
-# (such as a company or product suite) can be grouped.
-# The default value is: Doxygen generated docs.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# This tag specifies a string that should uniquely identify the documentation
-# set bundle. This should be a reverse domain-name style string, e.g.
-# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_BUNDLE_ID = org.doxygen.Project
-
-# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
-# the documentation publisher. This should be a reverse domain-name style
-# string, e.g. com.mycompany.MyDocSet.documentation.
-# The default value is: org.doxygen.Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_ID = org.doxygen.Publisher
-
-# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
-# The default value is: Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_NAME = Publisher
-
-# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
-# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
-# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
-# Windows.
-#
-# The HTML Help Workshop contains a compiler that can convert all HTML output
-# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
-# files are now used as the Windows 98 help format, and will replace the old
-# Windows help format (.hlp) on all Windows platforms in the future. Compressed
-# HTML files also contain an index, a table of contents, and you can search for
-# words in the documentation. The HTML workshop also contains a viewer for
-# compressed HTML files.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_HTMLHELP = NO
-
-# The CHM_FILE tag can be used to specify the file name of the resulting .chm
-# file. You can add a path in front of the file if the result should not be
-# written to the html output directory.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_FILE =
-
-# The HHC_LOCATION tag can be used to specify the location (absolute path
-# including file name) of the HTML help compiler ( hhc.exe). If non-empty
-# doxygen will try to run the HTML help compiler on the generated index.hhp.
-# The file has to be specified with full path.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-HHC_LOCATION =
-
-# The GENERATE_CHI flag controls if a separate .chi index file is generated (
-# YES) or that it should be included in the master .chm file ( NO).
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-GENERATE_CHI = NO
-
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
-# and project file content.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_INDEX_ENCODING =
-
-# The BINARY_TOC flag controls whether a binary table of contents is generated (
-# YES) or a normal table of contents ( NO) in the .chm file.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members to
-# the table of contents of the HTML help documentation and to the tree view.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-TOC_EXPAND = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
-# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
-# (.qch) of the generated HTML documentation.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_QHP = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
-# the file name of the resulting .qch file. The path specified is relative to
-# the HTML output folder.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QCH_FILE =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
-# Project output. For more information please see Qt Help Project / Namespace
-# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_NAMESPACE = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
-# Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
-# folders).
-# The default value is: doc.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_VIRTUAL_FOLDER = doc
-
-# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
-# filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_NAME =
-
-# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_ATTRS =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's filter section matches. Qt Help Project / Filter Attributes (see:
-# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_SECT_FILTER_ATTRS =
-
-# The QHG_LOCATION tag can be used to specify the location of Qt's
-# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
-# generated .qhp file.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHG_LOCATION =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
-# generated, together with the HTML files, they form an Eclipse help plugin. To
-# install this plugin and make it available under the help contents menu in
-# Eclipse, the contents of the directory containing the HTML and XML files needs
-# to be copied into the plugins directory of eclipse. The name of the directory
-# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
-# After copying Eclipse needs to be restarted before the help appears.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_ECLIPSEHELP = NO
-
-# A unique identifier for the Eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have this
-# name. Each documentation set should have its own identifier.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
-
-ECLIPSE_DOC_ID = org.doxygen.Project
-
-# If you want full control over the layout of the generated HTML pages it might
-# be necessary to disable the index and replace it with your own. The
-# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
-# of each HTML page. A value of NO enables the index and the value YES disables
-# it. Since the tabs in the index contain the same information as the navigation
-# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-DISABLE_INDEX = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information. If the tag
-# value is set to YES, a side panel will be generated containing a tree-like
-# index structure (just like the one that is generated for HTML Help). For this
-# to work a browser that supports JavaScript, DHTML, CSS and frames is required
-# (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
-# further fine-tune the look of the index. As an example, the default style
-# sheet generated by doxygen has an example that shows how to put an image at
-# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
-# the same information as the tab index, you could consider setting
-# DISABLE_INDEX to YES when enabling this option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_TREEVIEW = NO
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
-# doxygen will group on one line in the generated HTML documentation.
-#
-# Note that a value of 0 will completely suppress the enum values from appearing
-# in the overview section.
-# Minimum value: 0, maximum value: 20, default value: 4.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-ENUM_VALUES_PER_LINE = 4
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
-# to set the initial width (in pixels) of the frame in which the tree is shown.
-# Minimum value: 0, maximum value: 1500, default value: 250.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-TREEVIEW_WIDTH = 250
-
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
-# external symbols imported via tag files in a separate window.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-EXT_LINKS_IN_WINDOW = NO
-
-# Use this tag to change the font size of LaTeX formulas included as images in
-# the HTML documentation. When you change the font size after a successful
-# doxygen run you need to manually remove any form_*.png images from the HTML
-# output directory to force them to be regenerated.
-# Minimum value: 8, maximum value: 50, default value: 10.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_FONTSIZE = 10
-
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT = YES
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# http://www.mathjax.org) which uses client side Javascript for the rendering
-# instead of using prerendered bitmaps. Use this if you do not have LaTeX
-# installed or if you want to formulas look prettier in the HTML output. When
-# enabled you may also need to install MathJax separately and configure the path
-# to it using the MATHJAX_RELPATH option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-USE_MATHJAX = NO
-
-# When MathJax is enabled you can set the default output format to be used for
-# the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/latest/output.html) for more details.
-# Possible values are: HTML-CSS (which is slower, but has the best
-# compatibility), NativeMML (i.e. MathML) and SVG.
-# The default value is: HTML-CSS.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_FORMAT = HTML-CSS
-
-# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from http://www.mathjax.org before deployment.
-# The default value is: http://cdn.mathjax.org/mathjax/latest.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
-# extension names that should be enabled during MathJax rendering. For example
-# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_EXTENSIONS =
-
-# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
-# example see the documentation.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_CODEFILE =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
-# the HTML output. The underlying search engine uses javascript and DHTML and
-# should work on any modern browser. Note that when using HTML help
-# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
-# there is already a search function so this one should typically be disabled.
-# For large projects the javascript based search engine can be slow, then
-# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
-# search using the keyboard; to jump to the search box use <access key> + S
-# (what the <access key> is depends on the OS and browser, but it is typically
-# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
-# key> to jump into the search results window, the results can be navigated
-# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
-# the search. The filter options can be selected when the cursor is inside the
-# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
-# to select a filter and <Enter> or <escape> to activate or cancel the filter
-# option.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-SEARCHENGINE = YES
-
-# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
-# are two flavours of web server based searching depending on the
-# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
-# searching and an index file used by the script. When EXTERNAL_SEARCH is
-# enabled the indexing and searching needs to be provided by external tools. See
-# the section "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SERVER_BASED_SEARCH = NO
-
-# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
-# script for searching. Instead the search results are written to an XML file
-# which needs to be processed by an external indexer. Doxygen will invoke an
-# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
-# search results.
-#
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/).
-#
-# See the section "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH = NO
-
-# The SEARCHENGINE_URL should point to a search engine hosted by a web server
-# which will return the search results when EXTERNAL_SEARCH is enabled.
-#
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/). See the section "External Indexing and
-# Searching" for details.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHENGINE_URL =
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
-# search data is written to a file for indexing by an external tool. With the
-# SEARCHDATA_FILE tag the name of this file can be specified.
-# The default file is: searchdata.xml.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHDATA_FILE = searchdata.xml
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
-# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
-# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
-# projects and redirect the results back to the right project.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH_ID =
-
-# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
-# projects other than the one defined by this configuration file, but that are
-# all added to the same external search index. Each project needs to have a
-# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
-# to a relative location where the documentation can be found. The format is:
-# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTRA_SEARCH_MAPPINGS =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
-# The default value is: YES.
-
-GENERATE_LATEX = YES
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_OUTPUT = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked.
-#
-# Note that when enabling USE_PDFLATEX this option is only used for generating
-# bitmaps for formulas in the HTML output, but not in the Makefile that is
-# written to the output directory.
-# The default file is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_CMD_NAME = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
-# index for LaTeX.
-# The default file is: makeindex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-MAKEINDEX_CMD_NAME = makeindex
-
-# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used by the
-# printer.
-# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
-# 14 inches) and executive (7.25 x 10.5 inches).
-# The default value is: a4.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PAPER_TYPE = a4
-
-# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
-# that should be included in the LaTeX output. To get the times font for
-# instance you can specify
-# EXTRA_PACKAGES=times
-# If left blank no extra packages will be included.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
-# generated LaTeX document. The header should contain everything until the first
-# chapter. If it is left blank doxygen will generate a standard header. See
-# section "Doxygen usage" for information on how to let doxygen write the
-# default header to a separate file.
-#
-# Note: Only use a user-defined header if you know what you are doing! The
-# following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will
-# replace them by respectively the title of the page, the current date and time,
-# only the current date, the version number of doxygen, the project name (see
-# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HEADER =
-
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
-# generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer.
-#
-# Note: Only use a user-defined footer if you know what you are doing!
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_FOOTER =
-
-# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the LATEX_OUTPUT output
-# directory. Note that the files will be copied as-is; there are no commands or
-# markers available.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_EXTRA_FILES =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
-# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
-# contain links (just like the HTML output) instead of page references. This
-# makes the output suitable for online browsing using a PDF viewer.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PDF_HYPERLINKS = YES
-
-# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES to get a
-# higher quality PDF documentation.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-USE_PDFLATEX = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
-# command to the generated LaTeX files. This will instruct LaTeX to keep running
-# if errors occur, instead of asking the user for help. This option is also used
-# when generating formulas in HTML.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BATCHMODE = NO
-
-# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
-# index chapters (such as File Index, Compound Index, etc.) in the output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HIDE_INDICES = NO
-
-# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
-# code with syntax highlighting in the LaTeX output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_SOURCE_CODE = NO
-
-# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. See
-# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
-# The default value is: plain.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BIB_STYLE = plain
-
-#---------------------------------------------------------------------------
-# Configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
-# RTF output is optimized for Word 97 and may not look too pretty with other RTF
-# readers/editors.
-# The default value is: NO.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: rtf.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
-# contain hyperlink fields. The RTF file will contain links (just like the HTML
-# output) instead of page references. This makes the output suitable for online
-# browsing using Word or some other Word compatible readers that support those
-# fields.
-#
-# Note: WordPad (write) and others do not support links.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_HYPERLINKS = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's config
-# file, i.e. a series of assignments. You only have to provide replacements,
-# missing definitions are set to their default value.
-#
-# See also section "Doxygen usage" for information on how to generate the
-# default style sheet that doxygen normally uses.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an RTF document. Syntax is
-# similar to doxygen's config file. A template extensions file can be generated
-# using doxygen -e rtf extensionFile.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_EXTENSIONS_FILE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
-# classes and files.
-# The default value is: NO.
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it. A directory man3 will be created inside the directory specified by
-# MAN_OUTPUT.
-# The default directory is: man.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_OUTPUT = man
-
-# The MAN_EXTENSION tag determines the extension that is added to the generated
-# man pages. In case the manual section does not start with a number, the number
-# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
-# optional.
-# The default value is: .3.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_EXTENSION = .3
-
-# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
-# will generate one additional man file for each entity documented in the real
-# man page(s). These additional files only source the real man page, but without
-# them the man command would be unable to find the correct page.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
-# captures the structure of the code including all documentation.
-# The default value is: NO.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: xml.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_OUTPUT = xml
-
-# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
-# validating XML parser to check the syntax of the XML files.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_SCHEMA =
-
-# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
-# validating XML parser to check the syntax of the XML files.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_DTD =
-
-# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
-# listings (including syntax highlighting and cross-referencing information) to
-# the XML output. Note that enabling this will significantly increase the size
-# of the XML output.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the DOCBOOK output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
-# that can be used to generate PDF.
-# The default value is: NO.
-
-GENERATE_DOCBOOK = NO
-
-# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
-# front of it.
-# The default directory is: docbook.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-DOCBOOK_OUTPUT = docbook
-
-#---------------------------------------------------------------------------
-# Configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
-# Definitions (see http://autogen.sf.net) file that captures the structure of
-# the code including all documentation. Note that this feature is still
-# experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
-# file that captures the structure of the code including all documentation.
-#
-# Note that this feature is still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
-# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
-# output from the Perl module output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
-# formatted so it can be parsed by a human reader. This is useful if you want to
-# understand what is going on. On the other hand, if this tag is set to NO the
-# size of the Perl module output will be much smaller and Perl will parse it
-# just the same.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file are
-# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
-# so different doxyrules.make files included by the same Makefile don't
-# overwrite each other's variables.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
-# C-preprocessor directives found in the sources and include files.
-# The default value is: YES.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
-# in the source code. If set to NO only conditional compilation will be
-# performed. Macro expansion can be done in a controlled way by setting
-# EXPAND_ONLY_PREDEF to YES.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-MACRO_EXPANSION = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
-# the macro expansion is limited to the macros specified with the PREDEFINED and
-# EXPAND_AS_DEFINED tags.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_ONLY_PREDEF = NO
-
-# If the SEARCH_INCLUDES tag is set to YES the includes files in the
-# INCLUDE_PATH will be searched if a #include is found.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SEARCH_INCLUDES = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by the
-# preprocessor.
-# This tag requires that the tag SEARCH_INCLUDES is set to YES.
-
-INCLUDE_PATH =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will be
-# used.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-INCLUDE_FILE_PATTERNS =
-
-# The PREDEFINED tag can be used to specify one or more macro names that are
-# defined before the preprocessor is started (similar to the -D option of e.g.
-# gcc). The argument of the tag is a list of macros of the form: name or
-# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
-# is assumed. To prevent a macro definition from being undefined via #undef or
-# recursively expanded use the := operator instead of the = operator.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-PREDEFINED =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
-# tag can be used to specify a list of macro names that should be expanded. The
-# macro definition that is found in the sources will be used. Use the PREDEFINED
-# tag if you want to use a different macro definition that overrules the
-# definition found in the source code.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
-# remove all refrences to function-like macros that are alone on a line, have an
-# all uppercase name, and do not end with a semicolon. Such function macros are
-# typically used for boiler-plate code, and will confuse the parser if not
-# removed.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES tag can be used to specify one or more tag files. For each tag
-# file the location of the external documentation should be added. The format of
-# a tag file without this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where loc1 and loc2 can be relative or absolute paths or URLs. See the
-# section "Linking to external documentation" for more information about the use
-# of tag files.
-# Note: Each tag file must have an unique name (where the name does NOT include
-# the path). If a tag file is not located in the directory in which doxygen is
-# run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
-# tag file that is based on the input files it reads. See section "Linking to
-# external documentation" for more information about the usage of tag files.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
-# class index. If set to NO only the inherited external classes will be listed.
-# The default value is: NO.
-
-ALLEXTERNALS = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
-# the modules index. If set to NO, only the current project's groups will be
-# listed.
-# The default value is: YES.
-
-EXTERNAL_GROUPS = YES
-
-# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
-# the related pages index. If set to NO, only the current project's pages will
-# be listed.
-# The default value is: YES.
-
-EXTERNAL_PAGES = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of 'which perl').
-# The default file (with absolute path) is: /usr/bin/perl.
-
-PERL_PATH = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
-# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
-# NO turns the diagrams off. Note that this option also works with HAVE_DOT
-# disabled, but it is recommended to install and use dot, since it yields more
-# powerful graphs.
-# The default value is: YES.
-
-CLASS_DIAGRAMS = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see:
-# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH =
-
-# You can include diagrams made with dia in doxygen documentation. Doxygen will
-# then run dia to produce the diagram and insert it in the documentation. The
-# DIA_PATH tag allows you to specify the directory where the dia binary resides.
-# If left empty dia is assumed to be found in the default search path.
-
-DIA_PATH =
-
-# If set to YES, the inheritance and collaboration graphs will hide inheritance
-# and usage relations if the target is undocumented or is not a class.
-# The default value is: YES.
-
-HIDE_UNDOC_RELATIONS = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz (see:
-# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
-# Bell Labs. The other options in this section have no effect if this option is
-# set to NO
-# The default value is: NO.
-
-HAVE_DOT = NO
-
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
-# to run in parallel. When set to 0 doxygen will base this on the number of
-# processors available in the system. You can set it explicitly to a value
-# larger than 0 to get control over the balance between CPU load and processing
-# speed.
-# Minimum value: 0, maximum value: 32, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_NUM_THREADS = 0
-
-# When you want a differently looking font n the dot files that doxygen
-# generates you can specify the font name using DOT_FONTNAME. You need to make
-# sure dot is able to find the font, which can be done by putting it in a
-# standard location or by setting the DOTFONTPATH environment variable or by
-# setting DOT_FONTPATH to the directory containing the font.
-# The default value is: Helvetica.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTNAME = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
-# dot graphs.
-# Minimum value: 4, maximum value: 24, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTSIZE = 10
-
-# By default doxygen will tell dot to use the default font as specified with
-# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
-# the path where dot can find it using this tag.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTPATH =
-
-# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
-# each documented class showing the direct and indirect inheritance relations.
-# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
-# graph for each documented class showing the direct and indirect implementation
-# dependencies (inheritance, containment, and class references variables) of the
-# class with other documented classes.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-COLLABORATION_GRAPH = YES
-
-# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
-# groups, showing the direct groups dependencies.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LOOK = NO
-
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
-# class node. If there are many fields or methods and many nodes the graph may
-# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
-# number of items for each type to make the size more manageable. Set this to 0
-# for no limit. Note that the threshold may be exceeded by 50% before the limit
-# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
-# but if the number exceeds 15, the total amount of fields shown is limited to
-# 10.
-# Minimum value: 0, maximum value: 100, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LIMIT_NUM_FIELDS = 10
-
-# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
-# collaboration graphs will show the relations between templates and their
-# instances.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-TEMPLATE_RELATIONS = NO
-
-# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
-# YES then doxygen will generate a graph for each documented file showing the
-# direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDE_GRAPH = YES
-
-# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
-# set to YES then doxygen will generate a graph for each documented file showing
-# the direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable call graphs for selected
-# functions only using the \callgraph command.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALL_GRAPH = NO
-
-# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable caller graphs for selected
-# functions only using the \callergraph command.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALLER_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
-# hierarchy of all classes instead of a textual one.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
-# dependencies a directory has on other directories in a graphical way. The
-# dependency relations are determined by the #include relations between the
-# files in the directories.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot.
-# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
-# to make the SVG files visible in IE 9+ (other browsers do not have this
-# requirement).
-# Possible values are: png, jpg, gif and svg.
-# The default value is: png.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_IMAGE_FORMAT = png
-
-# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
-# enable generation of interactive SVG images that allow zooming and panning.
-#
-# Note that this requires a modern browser other than Internet Explorer. Tested
-# and working are Firefox, Chrome, Safari, and Opera.
-# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
-# the SVG files visible. Older versions of IE do not have SVG support.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INTERACTIVE_SVG = NO
-
-# The DOT_PATH tag can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the \dotfile
-# command).
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOTFILE_DIRS =
-
-# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the \mscfile
-# command).
-
-MSCFILE_DIRS =
-
-# The DIAFILE_DIRS tag can be used to specify one or more directories that
-# contain dia files that are included in the documentation (see the \diafile
-# command).
-
-DIAFILE_DIRS =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
-# that will be shown in the graph. If the number of nodes in a graph becomes
-# larger than this value, doxygen will truncate the graph, which is visualized
-# by representing a node as a red box. Note that doxygen if the number of direct
-# children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
-# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-# Minimum value: 0, maximum value: 10000, default value: 50.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_GRAPH_MAX_NODES = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
-# generated by dot. A depth value of 3 means that only nodes reachable from the
-# root by following a path via at most 3 edges will be shown. Nodes that lay
-# further from the root node will be omitted. Note that setting this option to 1
-# or 2 may greatly reduce the computation time needed for large code bases. Also
-# note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-# Minimum value: 0, maximum value: 1000, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not seem
-# to support this out of the box.
-#
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_TRANSPARENT = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10) support
-# this, this feature is disabled by default.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_MULTI_TARGETS = YES
-
-# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
-# explaining the meaning of the various boxes and arrows in the dot generated
-# graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
-# files that are used to generate the various graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_CLEANUP = YES
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index a47f189..7aaf908 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -29,6 +29,8 @@
#ifndef AAUDIO_AAUDIO_H
#define AAUDIO_AAUDIO_H
+#include <stdbool.h>
+#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
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..ac7ad9a 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;
@@ -353,6 +353,8 @@
// Clear any stale timestamps from the previous run.
drainTimestampsFromService();
+ prepareBuffersForStart(); // tell subclasses to get ready
+
aaudio_result_t result = mServiceInterface.startStream(mServiceStreamHandle);
if (result == AAUDIO_ERROR_INVALID_HANDLE) {
ALOGD("%s() INVALID_HANDLE, stream was probably stolen", __func__);
@@ -783,6 +785,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/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 61591b3..63be978 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -123,7 +123,9 @@
aaudio_result_t stopCallback();
- virtual void advanceClientToMatchServerPosition() = 0;
+ virtual void prepareBuffersForStart() {}
+
+ virtual void advanceClientToMatchServerPosition(int32_t serverMargin = 0) = 0;
virtual void onFlushFromServer() {}
diff --git a/media/libaaudio/src/client/AudioStreamInternalCapture.cpp b/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
index 9fa2e40..d014608 100644
--- a/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
@@ -41,9 +41,9 @@
AudioStreamInternalCapture::~AudioStreamInternalCapture() {}
-void AudioStreamInternalCapture::advanceClientToMatchServerPosition() {
+void AudioStreamInternalCapture::advanceClientToMatchServerPosition(int32_t serverMargin) {
int64_t readCounter = mAudioEndpoint->getDataReadCounter();
- int64_t writeCounter = mAudioEndpoint->getDataWriteCounter();
+ int64_t writeCounter = mAudioEndpoint->getDataWriteCounter() + serverMargin;
// Bump offset so caller does not see the retrograde motion in getFramesRead().
int64_t offset = readCounter - writeCounter;
diff --git a/media/libaaudio/src/client/AudioStreamInternalCapture.h b/media/libaaudio/src/client/AudioStreamInternalCapture.h
index 6436a53..1d65d87 100644
--- a/media/libaaudio/src/client/AudioStreamInternalCapture.h
+++ b/media/libaaudio/src/client/AudioStreamInternalCapture.h
@@ -46,7 +46,7 @@
}
protected:
- void advanceClientToMatchServerPosition() override;
+ void advanceClientToMatchServerPosition(int32_t serverOffset = 0) override;
/**
* Low level data processing that will not block. It will just read or write as much as it can.
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index 1303daf..6337b53 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -86,8 +86,13 @@
return mServiceInterface.flushStream(mServiceStreamHandle);
}
-void AudioStreamInternalPlay::advanceClientToMatchServerPosition() {
- int64_t readCounter = mAudioEndpoint->getDataReadCounter();
+void AudioStreamInternalPlay::prepareBuffersForStart() {
+ // Prevent stale data from being played.
+ mAudioEndpoint->eraseDataMemory();
+}
+
+void AudioStreamInternalPlay::advanceClientToMatchServerPosition(int32_t serverMargin) {
+ int64_t readCounter = mAudioEndpoint->getDataReadCounter() + serverMargin;
int64_t writeCounter = mAudioEndpoint->getDataWriteCounter();
// Bump offset so caller does not see the retrograde motion in getFramesRead().
@@ -145,7 +150,9 @@
if (mNeedCatchUp.isRequested()) {
// Catch an MMAP pointer that is already advancing.
// This will avoid initial underruns caused by a slow cold start.
- advanceClientToMatchServerPosition();
+ // We add a one burst margin in case the DSP advances before we can write the data.
+ // This can help prevent the beginning of the stream from being skipped.
+ advanceClientToMatchServerPosition(getFramesPerBurst());
mNeedCatchUp.acknowledge();
}
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h
index 2e93157..be95da6 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.h
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -65,7 +65,9 @@
protected:
- void advanceClientToMatchServerPosition() override;
+ void prepareBuffersForStart() override;
+
+ void advanceClientToMatchServerPosition(int32_t serverMargin = 0) override;
void onFlushFromServer() override;
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 0644368..983887b 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.
@@ -313,8 +315,11 @@
void AudioStream::setState(aaudio_stream_state_t state) {
ALOGD("%s(s#%d) from %d to %d", __func__, getId(), mState, state);
+ if (state == mState) {
+ return; // no change
+ }
// Track transition to DISCONNECTED state.
- if (state == AAUDIO_STREAM_STATE_DISCONNECTED && mState != state) {
+ if (state == AAUDIO_STREAM_STATE_DISCONNECTED) {
android::mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_DISCONNECT)
.set(AMEDIAMETRICS_PROP_STATE, AudioGlobal_convertStreamStateToText(getState()))
@@ -322,18 +327,18 @@
}
// CLOSED is a final state
if (mState == AAUDIO_STREAM_STATE_CLOSED) {
- ALOGE("%s(%d) tried to set to %d but already CLOSED", __func__, getId(), state);
+ ALOGW("%s(%d) tried to set to %d but already CLOSED", __func__, getId(), state);
// Once CLOSING, we can only move to CLOSED state.
} else if (mState == AAUDIO_STREAM_STATE_CLOSING
&& state != AAUDIO_STREAM_STATE_CLOSED) {
- ALOGE("%s(%d) tried to set to %d but already CLOSING", __func__, getId(), state);
+ ALOGW("%s(%d) tried to set to %d but already CLOSING", __func__, getId(), state);
// Once DISCONNECTED, we can only move to CLOSING or CLOSED state.
} else if (mState == AAUDIO_STREAM_STATE_DISCONNECTED
&& !(state == AAUDIO_STREAM_STATE_CLOSING
|| state == AAUDIO_STREAM_STATE_CLOSED)) {
- ALOGE("%s(%d) tried to set to %d but already DISCONNECTED", __func__, getId(), state);
+ ALOGW("%s(%d) tried to set to %d but already DISCONNECTED", __func__, getId(), state);
} else {
mState = state;
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..3bfa2b7 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -212,9 +212,6 @@
setSamplesPerFrame(mAudioRecord->channelCount());
int32_t actualSampleRate = mAudioRecord->getSampleRate();
- ALOGW_IF(actualSampleRate != getSampleRate(),
- "open() sampleRate changed from %d to %d",
- getSampleRate(), actualSampleRate);
setSampleRate(actualSampleRate);
// We may need to pass the data through a block size adapter to guarantee constant size.
@@ -292,7 +289,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 +342,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..0427220 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -195,9 +195,6 @@
setDeviceFormat(mAudioTrack->format());
int32_t actualSampleRate = mAudioTrack->getSampleRate();
- ALOGW_IF(actualSampleRate != getSampleRate(),
- "open() sampleRate changed from %d to %d",
- getSampleRate(), actualSampleRate);
setSampleRate(actualSampleRate);
// We may need to pass the data through a block size adapter to guarantee constant size.
@@ -240,11 +237,11 @@
setSharingMode(AAUDIO_SHARING_MODE_SHARED); // EXCLUSIVE mode not supported in legacy
- // Log warning if we did not get what we asked for.
- ALOGW_IF(actualFlags != flags,
+ // Log if we did not get what we asked for.
+ ALOGD_IF(actualFlags != flags,
"open() flags changed from 0x%08X to 0x%08X",
flags, actualFlags);
- ALOGW_IF(actualPerformanceMode != perfMode,
+ ALOGD_IF(actualPerformanceMode != perfMode,
"open() perfMode changed from %d to %d",
perfMode, actualPerformanceMode);
@@ -254,7 +251,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 +304,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/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index d2e4805..82eb77d 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -21,6 +21,7 @@
#include <functional>
#include <stdint.h>
#include <sys/types.h>
+#include <unistd.h>
#include <utils/Errors.h>
#include <system/audio.h>
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..e8e1a09 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",
],
@@ -10,6 +12,12 @@
export_header_lib_headers: [
"libaudiofoundation_headers",
],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
cc_library_shared {
diff --git a/media/libaudioclient/AudioAttributes.cpp b/media/libaudioclient/AudioAttributes.cpp
index 1ee6930..ff4ba06 100644
--- a/media/libaudioclient/AudioAttributes.cpp
+++ b/media/libaudioclient/AudioAttributes.cpp
@@ -57,7 +57,7 @@
parcel->writeInt32(0);
} else {
parcel->writeInt32(1);
- parcel->writeUtf8AsUtf16(mAttributes.tags);
+ parcel->writeUtf8AsUtf16(std::string(mAttributes.tags));
}
parcel->writeInt32(static_cast<int32_t>(mStreamType));
parcel->writeUint32(static_cast<uint32_t>(mGroupId));
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/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index 6357da4..f621aa5 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -581,7 +581,8 @@
case AUDIO_INPUT_CONFIG_CHANGED: {
sp<AudioIoDescriptor> oldDesc = getIoDescriptor_l(ioDesc->mIoHandle);
if (oldDesc == 0) {
- ALOGW("ioConfigChanged() modifying unknown output! %d", ioDesc->mIoHandle);
+ ALOGW("ioConfigChanged() modifying unknown %s! %d",
+ event == AUDIO_OUTPUT_CONFIG_CHANGED ? "output" : "input", ioDesc->mIoHandle);
break;
}
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/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp
index 16d2232..6d79aba 100644
--- a/media/libaudioclient/IAudioFlinger.cpp
+++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -1001,7 +1001,7 @@
break;
}
- // Whitelist of relevant events to trigger log merging.
+ // List of relevant events that trigger log merging.
// Log merging should activate during audio activity of any kind. This are considered the
// most relevant events.
// TODO should select more wisely the items from the list
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..a8e6c31 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",
@@ -10,6 +12,12 @@
"libaudio_system_headers",
"libmedia_helper_headers",
],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
cc_library {
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/libaudiohal/impl/StreamPowerLog.h b/media/libaudiohal/impl/StreamPowerLog.h
index 5fd3912..f6a554b 100644
--- a/media/libaudiohal/impl/StreamPowerLog.h
+++ b/media/libaudiohal/impl/StreamPowerLog.h
@@ -19,6 +19,7 @@
#include <audio_utils/clock.h>
#include <audio_utils/PowerLog.h>
+#include <cutils/bitops.h>
#include <cutils/properties.h>
#include <system/audio.h>
diff --git a/media/libaudioprocessing/AudioMixer.cpp b/media/libaudioprocessing/AudioMixer.cpp
index 1a31420..2ea215f 100644
--- a/media/libaudioprocessing/AudioMixer.cpp
+++ b/media/libaudioprocessing/AudioMixer.cpp
@@ -423,7 +423,7 @@
}
} break;
case HAPTIC_INTENSITY: {
- const haptic_intensity_t hapticIntensity = static_cast<haptic_intensity_t>(valueInt);
+ const os::HapticScale hapticIntensity = static_cast<os::HapticScale>(valueInt);
if (track->mHapticIntensity != hapticIntensity) {
track->mHapticIntensity = hapticIntensity;
}
@@ -545,7 +545,7 @@
t->mPlaybackRate = AUDIO_PLAYBACK_RATE_DEFAULT;
// haptic
t->mHapticPlaybackEnabled = false;
- t->mHapticIntensity = HAPTIC_SCALE_NONE;
+ t->mHapticIntensity = os::HapticScale::NONE;
t->mMixerHapticChannelMask = AUDIO_CHANNEL_NONE;
t->mMixerHapticChannelCount = 0;
t->mAdjustInChannelCount = t->channelCount + t->mHapticChannelCount;
@@ -590,19 +590,12 @@
const std::shared_ptr<Track> &t = getTrack(name);
if (t->mHapticPlaybackEnabled) {
size_t sampleCount = mFrameCount * t->mMixerHapticChannelCount;
- float gamma = t->getHapticScaleGamma();
- float maxAmplitudeRatio = t->getHapticMaxAmplitudeRatio();
uint8_t* buffer = (uint8_t*)pair.first + mFrameCount * audio_bytes_per_frame(
t->mMixerChannelCount, t->mMixerFormat);
switch (t->mMixerFormat) {
// Mixer format should be AUDIO_FORMAT_PCM_FLOAT.
case AUDIO_FORMAT_PCM_FLOAT: {
- float* fout = (float*) buffer;
- for (size_t i = 0; i < sampleCount; i++) {
- float mul = fout[i] >= 0 ? 1.0 : -1.0;
- fout[i] = powf(fabsf(fout[i] / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
- * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * mul;
- }
+ os::scaleHapticData((float*) buffer, sampleCount, t->mHapticIntensity);
} break;
default:
LOG_ALWAYS_FATAL("bad mMixerFormat: %#x", t->mMixerFormat);
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..8d374c9 100644
--- a/media/libaudioprocessing/AudioMixerOps.h
+++ b/media/libaudioprocessing/AudioMixerOps.h
@@ -234,16 +234,20 @@
static_assert(NCHAN > 0 && NCHAN <= 8);
static_assert(MIXTYPE == MIXTYPE_MULTI_STEREOVOL
|| MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
- || MIXTYPE == MIXTYPE_STEREOEXPAND);
+ || MIXTYPE == MIXTYPE_STEREOEXPAND
+ || MIXTYPE == MIXTYPE_MONOEXPAND);
auto proc = [](auto& a, const auto& b) {
- if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL) {
+ if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
+ || MIXTYPE == MIXTYPE_STEREOEXPAND
+ || MIXTYPE == MIXTYPE_MONOEXPAND) {
a += b;
} else {
a = b;
}
};
auto inp = [&in]() -> const TI& {
- if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) {
+ if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND
+ || MIXTYPE == MIXTYPE_MONOEXPAND) {
return *in;
} else {
return *in++;
@@ -311,6 +315,8 @@
* TV/TAV: int32_t (U4.28) or int16_t (U4.12) or float
* Input channel count is 1.
* vol: represents volume array.
+ * This uses stereo balanced volume vol[0] and vol[1].
+ * Before R, this was a full volume array but was called only for channels <= 2.
*
* This accumulates into the out pointer.
*
@@ -355,17 +361,13 @@
do {
TA auxaccum = 0;
if constexpr (MIXTYPE == MIXTYPE_MULTI) {
+ static_assert(NCHAN <= 2);
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
vol[i] += volinc[i];
}
- } else if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) {
- for (int i = 0; i < NCHAN; ++i) {
- *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
- vol[i] += volinc[i];
- }
- in++;
} else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
+ static_assert(NCHAN <= 2);
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
vol[i] += volinc[i];
@@ -382,11 +384,13 @@
vol[0] += volinc[0];
} else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
|| MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
+ || MIXTYPE == MIXTYPE_MONOEXPAND
|| MIXTYPE == MIXTYPE_STEREOEXPAND) {
stereoVolumeHelper<MIXTYPE, NCHAN>(
out, in, vol, [&auxaccum] (auto &a, const auto &b) {
return MixMulAux<TO, TI, TV, TA>(a, b, &auxaccum);
});
+ if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
vol[0] += volinc[0];
vol[1] += volinc[1];
@@ -400,17 +404,13 @@
} else {
do {
if constexpr (MIXTYPE == MIXTYPE_MULTI) {
+ static_assert(NCHAN <= 2);
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
vol[i] += volinc[i];
}
- } else if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) {
- for (int i = 0; i < NCHAN; ++i) {
- *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
- vol[i] += volinc[i];
- }
- in++;
} else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
+ static_assert(NCHAN <= 2);
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
vol[i] += volinc[i];
@@ -427,10 +427,12 @@
vol[0] += volinc[0];
} else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
|| MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
+ || MIXTYPE == MIXTYPE_MONOEXPAND
|| MIXTYPE == MIXTYPE_STEREOEXPAND) {
stereoVolumeHelper<MIXTYPE, NCHAN>(out, in, vol, [] (auto &a, const auto &b) {
return MixMul<TO, TI, TV>(a, b);
});
+ if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
vol[0] += volinc[0];
vol[1] += volinc[1];
@@ -453,15 +455,12 @@
do {
TA auxaccum = 0;
if constexpr (MIXTYPE == MIXTYPE_MULTI) {
+ static_assert(NCHAN <= 2);
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
}
- } else if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) {
- for (int i = 0; i < NCHAN; ++i) {
- *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
- }
- in++;
} else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
+ static_assert(NCHAN <= 2);
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
}
@@ -475,11 +474,13 @@
}
} else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
|| MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
+ || MIXTYPE == MIXTYPE_MONOEXPAND
|| MIXTYPE == MIXTYPE_STEREOEXPAND) {
stereoVolumeHelper<MIXTYPE, NCHAN>(
out, in, vol, [&auxaccum] (auto &a, const auto &b) {
return MixMulAux<TO, TI, TV, TA>(a, b, &auxaccum);
});
+ if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
} else /* constexpr */ {
static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
@@ -489,16 +490,14 @@
} while (--frameCount);
} else {
do {
+ // ALOGD("Mixtype:%d NCHAN:%d", MIXTYPE, NCHAN);
if constexpr (MIXTYPE == MIXTYPE_MULTI) {
+ static_assert(NCHAN <= 2);
for (int i = 0; i < NCHAN; ++i) {
*out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
}
- } else if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) {
- for (int i = 0; i < NCHAN; ++i) {
- *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
- }
- in++;
} else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
+ static_assert(NCHAN <= 2);
for (int i = 0; i < NCHAN; ++i) {
*out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
}
@@ -512,10 +511,12 @@
}
} else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
|| MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
+ || MIXTYPE == MIXTYPE_MONOEXPAND
|| MIXTYPE == MIXTYPE_STEREOEXPAND) {
stereoVolumeHelper<MIXTYPE, NCHAN>(out, in, vol, [] (auto &a, const auto &b) {
return MixMul<TO, TI, TV>(a, b);
});
+ if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
} else /* constexpr */ {
static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
diff --git a/media/libaudioprocessing/include/media/AudioMixer.h b/media/libaudioprocessing/include/media/AudioMixer.h
index 3f7cd48..70eafe3 100644
--- a/media/libaudioprocessing/include/media/AudioMixer.h
+++ b/media/libaudioprocessing/include/media/AudioMixer.h
@@ -22,10 +22,10 @@
#include <stdint.h>
#include <sys/types.h>
-#include <android/os/IExternalVibratorService.h>
#include <media/AudioMixerBase.h>
#include <media/BufferProviders.h>
#include <utils/threads.h>
+#include <vibrator/ExternalVibrationUtils.h>
// FIXME This is actually unity gain, which might not be max in future, expressed in U.12
#define MAX_GAIN_INT AudioMixerBase::UNITY_GAIN_INT
@@ -55,32 +55,6 @@
// parameter 'value' is a pointer to the new playback rate.
};
- typedef enum { // Haptic intensity, should keep consistent with VibratorService
- HAPTIC_SCALE_MUTE = os::IExternalVibratorService::SCALE_MUTE,
- HAPTIC_SCALE_VERY_LOW = os::IExternalVibratorService::SCALE_VERY_LOW,
- HAPTIC_SCALE_LOW = os::IExternalVibratorService::SCALE_LOW,
- HAPTIC_SCALE_NONE = os::IExternalVibratorService::SCALE_NONE,
- HAPTIC_SCALE_HIGH = os::IExternalVibratorService::SCALE_HIGH,
- HAPTIC_SCALE_VERY_HIGH = os::IExternalVibratorService::SCALE_VERY_HIGH,
- } haptic_intensity_t;
- static constexpr float HAPTIC_SCALE_VERY_LOW_RATIO = 2.0f / 3.0f;
- static constexpr float HAPTIC_SCALE_LOW_RATIO = 3.0f / 4.0f;
- static const constexpr float HAPTIC_MAX_AMPLITUDE_FLOAT = 1.0f;
-
- static inline bool isValidHapticIntensity(haptic_intensity_t hapticIntensity) {
- switch (hapticIntensity) {
- case HAPTIC_SCALE_MUTE:
- case HAPTIC_SCALE_VERY_LOW:
- case HAPTIC_SCALE_LOW:
- case HAPTIC_SCALE_NONE:
- case HAPTIC_SCALE_HIGH:
- case HAPTIC_SCALE_VERY_HIGH:
- return true;
- default:
- return false;
- }
- }
-
AudioMixer(size_t frameCount, uint32_t sampleRate)
: AudioMixerBase(frameCount, sampleRate) {
pthread_once(&sOnceControl, &sInitRoutine);
@@ -170,7 +144,7 @@
// Haptic
bool mHapticPlaybackEnabled;
- haptic_intensity_t mHapticIntensity;
+ os::HapticScale mHapticIntensity;
audio_channel_mask_t mHapticChannelMask;
uint32_t mHapticChannelCount;
audio_channel_mask_t mMixerHapticChannelMask;
@@ -180,38 +154,6 @@
uint32_t mAdjustNonDestructiveInChannelCount;
uint32_t mAdjustNonDestructiveOutChannelCount;
bool mKeepContractedChannels;
-
- float getHapticScaleGamma() const {
- // Need to keep consistent with the value in VibratorService.
- switch (mHapticIntensity) {
- case HAPTIC_SCALE_VERY_LOW:
- return 2.0f;
- case HAPTIC_SCALE_LOW:
- return 1.5f;
- case HAPTIC_SCALE_HIGH:
- return 0.5f;
- case HAPTIC_SCALE_VERY_HIGH:
- return 0.25f;
- default:
- return 1.0f;
- }
- }
-
- float getHapticMaxAmplitudeRatio() const {
- // Need to keep consistent with the value in VibratorService.
- switch (mHapticIntensity) {
- case HAPTIC_SCALE_VERY_LOW:
- return HAPTIC_SCALE_VERY_LOW_RATIO;
- case HAPTIC_SCALE_LOW:
- return HAPTIC_SCALE_LOW_RATIO;
- case HAPTIC_SCALE_NONE:
- case HAPTIC_SCALE_HIGH:
- case HAPTIC_SCALE_VERY_HIGH:
- return 1.0f;
- default:
- return 0.0f;
- }
- }
};
inline std::shared_ptr<Track> getTrack(int name) {
diff --git a/media/libaudioprocessing/tests/mixerops_benchmark.cpp b/media/libaudioprocessing/tests/mixerops_benchmark.cpp
index 86f5429..7a4c5c7 100644
--- a/media/libaudioprocessing/tests/mixerops_benchmark.cpp
+++ b/media/libaudioprocessing/tests/mixerops_benchmark.cpp
@@ -74,28 +74,32 @@
}
}
-BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI, 2);
-BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY, 2);
+// MULTI mode and MULTI_SAVEONLY mode are not used by AudioMixer for channels > 2,
+// which is ensured by a static_assert (won't compile for those configurations).
+// So we benchmark MIXTYPE_MULTI_MONOVOL and MIXTYPE_MULTI_SAVEONLY_MONOVOL compared
+// with MIXTYPE_MULTI_STEREOVOL and MIXTYPE_MULTI_SAVEONLY_STEREOVOL.
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_MONOVOL, 2);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_MONOVOL, 2);
BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_STEREOVOL, 2);
BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 2);
-BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI, 4);
-BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY, 4);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_MONOVOL, 4);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_MONOVOL, 4);
BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_STEREOVOL, 4);
BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 4);
-BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI, 5);
-BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY, 5);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_MONOVOL, 5);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_MONOVOL, 5);
BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_STEREOVOL, 5);
BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 5);
-BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI, 8);
-BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY, 8);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_MONOVOL, 8);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_MONOVOL, 8);
BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_STEREOVOL, 8);
BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 8);
-BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI, 8);
-BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI_SAVEONLY, 8);
+BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI_MONOVOL, 8);
+BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI_SAVEONLY_MONOVOL, 8);
BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI_STEREOVOL, 8);
BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 8);
diff --git a/media/libcpustats/ThreadCpuUsage.cpp b/media/libcpustats/ThreadCpuUsage.cpp
index 4b7549f..e71a7db 100644
--- a/media/libcpustats/ThreadCpuUsage.cpp
+++ b/media/libcpustats/ThreadCpuUsage.cpp
@@ -21,6 +21,7 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
+#include <unistd.h>
#include <utils/Log.h>
diff --git a/media/libeffects/data/audio_effects.xml b/media/libeffects/data/audio_effects.xml
index 2e5f529..93a2181 100644
--- a/media/libeffects/data/audio_effects.xml
+++ b/media/libeffects/data/audio_effects.xml
@@ -21,6 +21,7 @@
<library name="downmix" path="libdownmix.so"/>
<library name="loudness_enhancer" path="libldnhncr.so"/>
<library name="dynamics_processing" path="libdynproc.so"/>
+ <library name="haptic_generator" path="libhapticgenerator.so"/>
</libraries>
<!-- list of effects to load.
@@ -58,6 +59,7 @@
<effect name="downmix" library="downmix" uuid="93f04452-e4fe-41cc-91f9-e475b6d1d69f"/>
<effect name="loudness_enhancer" library="loudness_enhancer" uuid="fa415329-2034-4bea-b5dc-5b381c8d1e2c"/>
<effect name="dynamics_processing" library="dynamics_processing" uuid="e0e6539b-1781-7261-676f-6d7573696340"/>
+ <effect name="haptic_generator" library="haptic_generator" uuid="97c4acd1-8b82-4f2f-832e-c2fe5d7a9931"/>
</effects>
<!-- Audio pre processor configurations.
diff --git a/media/libeffects/hapticgenerator/Android.bp b/media/libeffects/hapticgenerator/Android.bp
new file mode 100644
index 0000000..f947339
--- /dev/null
+++ b/media/libeffects/hapticgenerator/Android.bp
@@ -0,0 +1,51 @@
+// 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.
+
+// HapticGenerator library
+cc_library_shared {
+ name: "libhapticgenerator",
+
+ vendor: true,
+
+ srcs: [
+ "EffectHapticGenerator.cpp",
+ "Processors.cpp",
+ ],
+
+ cflags: [
+ "-O2", // Turning on the optimization in order to reduce effect processing time.
+ // The latency is around 1/5 less than without the optimization.
+ "-Wall",
+ "-Werror",
+ "-ffast-math", // This is needed for the non-zero coefficients optimization for
+ // BiquadFilter. Try the biquad_filter_benchmark test in audio_utils
+ // with/without `-ffast-math` for more context.
+ "-fvisibility=hidden",
+ ],
+
+ shared_libs: [
+ "libaudioutils",
+ "libbinder",
+ "liblog",
+ "libutils",
+ "libvibrator",
+ ],
+
+ relative_install_path: "soundfx",
+
+ header_libs: [
+ "libaudioeffects",
+ ],
+}
+
diff --git a/media/libeffects/hapticgenerator/EffectHapticGenerator.cpp b/media/libeffects/hapticgenerator/EffectHapticGenerator.cpp
new file mode 100644
index 0000000..311e5ba
--- /dev/null
+++ b/media/libeffects/hapticgenerator/EffectHapticGenerator.cpp
@@ -0,0 +1,519 @@
+/*
+ * 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_TAG "EffectHG"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "EffectHapticGenerator.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include <errno.h>
+#include <inttypes.h>
+
+#include <audio_effects/effect_hapticgenerator.h>
+#include <audio_utils/format.h>
+#include <system/audio.h>
+
+// This is the only symbol that needs to be exported
+__attribute__ ((visibility ("default")))
+audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = {
+ .tag = AUDIO_EFFECT_LIBRARY_TAG,
+ .version = EFFECT_LIBRARY_API_VERSION,
+ .name = "HapticGenerator Library",
+ .implementor = "The Android Open Source Project",
+ .create_effect = android::audio_effect::haptic_generator::HapticGeneratorLib_Create,
+ .release_effect = android::audio_effect::haptic_generator::HapticGeneratorLib_Release,
+ .get_descriptor = android::audio_effect::haptic_generator::HapticGeneratorLib_GetDescriptor,
+};
+
+namespace android::audio_effect::haptic_generator {
+
+// effect_handle_t interface implementation for haptic generator effect
+const struct effect_interface_s gHapticGeneratorInterface = {
+ HapticGenerator_Process,
+ HapticGenerator_Command,
+ HapticGenerator_GetDescriptor,
+ nullptr /* no process_reverse function, no reference stream needed */
+};
+
+//-----------------------------------------------------------------------------
+// Effect Descriptor
+//-----------------------------------------------------------------------------
+
+// UUIDs for effect types have been generated from http://www.itu.int/ITU-T/asn1/uuid.html
+// Haptic Generator
+static const effect_descriptor_t gHgDescriptor = {
+ FX_IID_HAPTICGENERATOR_, // type
+ {0x97c4acd1, 0x8b82, 0x4f2f, 0x832e, {0xc2, 0xfe, 0x5d, 0x7a, 0x99, 0x31}}, // uuid
+ EFFECT_CONTROL_API_VERSION,
+ EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST,
+ 0, // FIXME what value should be reported? // cpu load
+ 0, // FIXME what value should be reported? // memory usage
+ "Haptic Generator",
+ "The Android Open Source Project"
+};
+
+//-----------------------------------------------------------------------------
+// Internal functions
+//-----------------------------------------------------------------------------
+
+namespace {
+
+int HapticGenerator_Init(struct HapticGeneratorContext *context) {
+ context->itfe = &gHapticGeneratorInterface;
+
+ context->config.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ;
+ context->config.inputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+ context->config.inputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
+ context->config.inputCfg.samplingRate = 0;
+ context->config.inputCfg.bufferProvider.getBuffer = nullptr;
+ context->config.inputCfg.bufferProvider.releaseBuffer = nullptr;
+ context->config.inputCfg.bufferProvider.cookie = nullptr;
+ context->config.inputCfg.mask = EFFECT_CONFIG_ALL;
+ context->config.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE;
+ context->config.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+ context->config.outputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
+ context->config.outputCfg.samplingRate = 0;
+ context->config.outputCfg.bufferProvider.getBuffer = nullptr;
+ context->config.outputCfg.bufferProvider.releaseBuffer = nullptr;
+ context->config.outputCfg.bufferProvider.cookie = nullptr;
+ context->config.outputCfg.mask = EFFECT_CONFIG_ALL;
+
+ memset(context->param.hapticChannelSource, 0, sizeof(context->param.hapticChannelSource));
+ context->param.hapticChannelCount = 0;
+ context->param.audioChannelCount = 0;
+ context->param.maxHapticIntensity = os::HapticScale::MUTE;
+
+ context->state = HAPTICGENERATOR_STATE_INITIALIZED;
+ return 0;
+}
+
+void addBiquadFilter(
+ std::vector<std::function<void(float *, const float *, size_t)>> &processingChain,
+ struct HapticGeneratorProcessorsRecord &processorsRecord,
+ std::shared_ptr<BiquadFilter> filter) {
+ // The process chain captures the shared pointer of the filter in lambda.
+ // The process record will keep a shared pointer to the filter so that it is possible to access
+ // the filter outside of the process chain.
+ processorsRecord.filters.push_back(filter);
+ processingChain.push_back([filter](float *out, const float *in, size_t frameCount) {
+ filter->process(out, in, frameCount);
+ });
+}
+
+/**
+ * \brief build haptic generator processing chain.
+ *
+ * \param processingChain
+ * \param processorsRecord a structure to cache all the shared pointers for processors
+ * \param sampleRate the audio sampling rate. Use a float here as it may be used to create filters
+ * \param channelCount haptic channel count
+ */
+void HapticGenerator_buildProcessingChain(
+ std::vector<std::function<void(float*, const float*, size_t)>>& processingChain,
+ struct HapticGeneratorProcessorsRecord& processorsRecord,
+ float sampleRate, size_t channelCount) {
+ float highPassCornerFrequency = 100.0f;
+ auto hpf = createHPF2(highPassCornerFrequency, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, hpf);
+ float lowPassCornerFrequency = 3000.0f;
+ auto lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, lpf);
+
+ auto ramp = std::make_shared<Ramp>(channelCount);
+ // The process chain captures the shared pointer of the ramp in lambda. It will be the only
+ // reference to the ramp.
+ // The process record will keep a weak pointer to the ramp so that it is possible to access
+ // the ramp outside of the process chain.
+ processorsRecord.ramps.push_back(ramp);
+ processingChain.push_back([ramp](float *out, const float *in, size_t frameCount) {
+ ramp->process(out, in, frameCount);
+ });
+
+ highPassCornerFrequency = 60.0f;
+ hpf = createHPF2(highPassCornerFrequency, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, hpf);
+ lowPassCornerFrequency = 700.0f;
+ lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, lpf);
+
+ lowPassCornerFrequency = 5.0f;
+ float normalizationPower = -0.3f;
+ // The process chain captures the shared pointer of the slow envelope in lambda. It will
+ // be the only reference to the slow envelope.
+ // The process record will keep a weak pointer to the slow envelope so that it is possible
+ // to access the slow envelope outside of the process chain.
+ auto slowEnv = std::make_shared<SlowEnvelope>(
+ lowPassCornerFrequency, sampleRate, normalizationPower, channelCount);
+ processorsRecord.slowEnvs.push_back(slowEnv);
+ processingChain.push_back([slowEnv](float *out, const float *in, size_t frameCount) {
+ slowEnv->process(out, in, frameCount);
+ });
+
+ lowPassCornerFrequency = 400.0f;
+ lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, lpf);
+ lowPassCornerFrequency = 500.0f;
+ lpf = createLPF2(lowPassCornerFrequency, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, lpf);
+
+ auto apf = createAPF2(400.0f, 200.0f, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, apf);
+ apf = createAPF2(100.0f, 50.0f, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, apf);
+ float allPassCornerFrequency = 25.0f;
+ apf = createAPF(allPassCornerFrequency, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, apf);
+
+ float resonantFrequency = 150.0f;
+ float bandpassQ = 1.0f;
+ auto bpf = createBPF(resonantFrequency, bandpassQ, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, bpf);
+
+ float zeroQ = 8.0f;
+ float poleQ = 4.0f;
+ auto bsf = createBSF(resonantFrequency, zeroQ, poleQ, sampleRate, channelCount);
+ addBiquadFilter(processingChain, processorsRecord, bsf);
+}
+
+int HapticGenerator_Configure(struct HapticGeneratorContext *context, effect_config_t *config) {
+ if (config->inputCfg.samplingRate != config->outputCfg.samplingRate ||
+ config->inputCfg.format != config->outputCfg.format ||
+ config->inputCfg.format != AUDIO_FORMAT_PCM_FLOAT ||
+ config->inputCfg.channels != config->outputCfg.channels ||
+ config->inputCfg.buffer.frameCount != config->outputCfg.buffer.frameCount) {
+ return -EINVAL;
+ }
+ if (&context->config != config) {
+ context->processingChain.clear();
+ context->processorsRecord.filters.clear();
+ context->processorsRecord.ramps.clear();
+ context->processorsRecord.slowEnvs.clear();
+ memcpy(&context->config, config, sizeof(effect_config_t));
+ context->param.audioChannelCount = audio_channel_count_from_out_mask(
+ ((audio_channel_mask_t) config->inputCfg.channels) & ~AUDIO_CHANNEL_HAPTIC_ALL);
+ context->param.hapticChannelCount = audio_channel_count_from_out_mask(
+ ((audio_channel_mask_t) config->outputCfg.channels) & AUDIO_CHANNEL_HAPTIC_ALL);
+ ALOG_ASSERT(context->param.hapticChannelCount <= 2,
+ "haptic channel count(%zu) is too large",
+ context->param.hapticChannelCount);
+ context->audioDataBytesPerFrame = audio_bytes_per_frame(
+ context->param.audioChannelCount, (audio_format_t) config->inputCfg.format);
+ for (size_t i = 0; i < context->param.hapticChannelCount; ++i) {
+ // By default, use the first audio channel to generate haptic channels.
+ context->param.hapticChannelSource[i] = 0;
+ }
+
+ HapticGenerator_buildProcessingChain(context->processingChain,
+ context->processorsRecord,
+ config->inputCfg.samplingRate,
+ context->param.hapticChannelCount);
+ }
+ return 0;
+}
+
+int HapticGenerator_Reset(struct HapticGeneratorContext *context) {
+ for (auto& filter : context->processorsRecord.filters) {
+ filter->clear();
+ }
+ for (auto& slowEnv : context->processorsRecord.slowEnvs) {
+ slowEnv->clear();
+ }
+ return 0;
+}
+
+int HapticGenerator_SetParameter(struct HapticGeneratorContext *context,
+ int32_t param,
+ uint32_t size,
+ void *value) {
+ switch (param) {
+ case HG_PARAM_HAPTIC_INTENSITY: {
+ if (value == nullptr || size != (uint32_t) (2 * sizeof(int))) {
+ return -EINVAL;
+ }
+ int id = *(int *) value;
+ os::HapticScale hapticIntensity = static_cast<os::HapticScale>(*((int *) value + 1));
+ if (hapticIntensity == os::HapticScale::MUTE) {
+ context->param.id2Intensity.erase(id);
+ } else {
+ context->param.id2Intensity.emplace(id, hapticIntensity);
+ }
+ context->param.maxHapticIntensity = hapticIntensity;
+ for (const auto&[id, intensity] : context->param.id2Intensity) {
+ context->param.maxHapticIntensity = std::max(
+ context->param.maxHapticIntensity, intensity);
+ }
+ break;
+ }
+
+ default:
+ ALOGW("Unknown param: %d", param);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * \brief run the processing chain to generate haptic data from audio data
+ *
+ * \param processingChain the processing chain for generating haptic data
+ * \param buf1 a buffer contains raw audio data
+ * \param buf2 a buffer that is large enough to keep all the data
+ * \param frameCount frame count of the data
+ * \return a pointer to the output buffer
+ */
+float* HapticGenerator_runProcessingChain(
+ const std::vector<std::function<void(float*, const float*, size_t)>>& processingChain,
+ float* buf1, float* buf2, size_t frameCount) {
+ float *in = buf1;
+ float *out = buf2;
+ for (const auto processingFunc : processingChain) {
+ processingFunc(out, in, frameCount);
+ std::swap(in, out);
+ }
+ return in;
+}
+
+} // namespace (anonymous)
+
+//-----------------------------------------------------------------------------
+// Effect API Implementation
+//-----------------------------------------------------------------------------
+
+/*--- Effect Library Interface Implementation ---*/
+
+int32_t HapticGeneratorLib_Create(const effect_uuid_t *uuid,
+ int32_t sessionId __unused,
+ int32_t ioId __unused,
+ effect_handle_t *handle) {
+ if (handle == nullptr || uuid == nullptr) {
+ return -EINVAL;
+ }
+
+ if (memcmp(uuid, &gHgDescriptor.uuid, sizeof(*uuid)) != 0) {
+ return -EINVAL;
+ }
+
+ HapticGeneratorContext *context = new HapticGeneratorContext;
+ HapticGenerator_Init(context);
+
+ *handle = (effect_handle_t) context;
+ ALOGV("%s context is %p", __func__, context);
+ return 0;
+}
+
+int32_t HapticGeneratorLib_Release(effect_handle_t handle) {
+ HapticGeneratorContext *context = (HapticGeneratorContext *) handle;
+ delete context;
+ return 0;
+}
+
+int32_t HapticGeneratorLib_GetDescriptor(const effect_uuid_t *uuid,
+ effect_descriptor_t *descriptor) {
+
+ if (descriptor == nullptr || uuid == nullptr) {
+ ALOGE("%s() called with NULL pointer", __func__);
+ return -EINVAL;
+ }
+
+ if (memcmp(uuid, &gHgDescriptor.uuid, sizeof(*uuid)) == 0) {
+ *descriptor = gHgDescriptor;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/*--- Effect Control Interface Implementation ---*/
+
+int32_t HapticGenerator_Process(effect_handle_t self,
+ audio_buffer_t *inBuffer, audio_buffer_t *outBuffer) {
+ HapticGeneratorContext *context = (HapticGeneratorContext *) self;
+
+ if (inBuffer == nullptr || inBuffer->raw == nullptr
+ || outBuffer == nullptr || outBuffer->raw == nullptr) {
+ return 0;
+ }
+
+ // The audio data must not be modified but just written to
+ // output buffer according the access mode.
+ size_t audioBytes = context->audioDataBytesPerFrame * inBuffer->frameCount;
+ size_t audioSampleCount = inBuffer->frameCount * context->param.audioChannelCount;
+ if (inBuffer->raw != outBuffer->raw) {
+ if (context->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) {
+ for (size_t i = 0; i < audioSampleCount; ++i) {
+ outBuffer->f32[i] += inBuffer->f32[i];
+ }
+ } else {
+ memcpy(outBuffer->raw, inBuffer->raw, audioBytes);
+ }
+ }
+
+ if (context->state != HAPTICGENERATOR_STATE_ACTIVE) {
+ ALOGE("State(%d) is not HAPTICGENERATOR_STATE_ACTIVE when calling %s",
+ context->state, __func__);
+ return -ENODATA;
+ }
+
+ if (context->param.maxHapticIntensity == os::HapticScale::MUTE) {
+ // Haptic channels are muted, not need to generate haptic data.
+ return 0;
+ }
+
+ // Resize buffer if the haptic sample count is greater than buffer size.
+ size_t hapticSampleCount = inBuffer->frameCount * context->param.hapticChannelCount;
+ if (hapticSampleCount > context->inputBuffer.size()) {
+ // The context->inputBuffer and context->outputBuffer must have the same size,
+ // which must be at least the haptic sample count.
+ context->inputBuffer.resize(hapticSampleCount);
+ context->outputBuffer.resize(hapticSampleCount);
+ }
+
+ // Construct input buffer according to haptic channel source
+ for (size_t i = 0; i < inBuffer->frameCount; ++i) {
+ for (size_t j = 0; j < context->param.hapticChannelCount; ++j) {
+ context->inputBuffer[i * context->param.hapticChannelCount + j] =
+ inBuffer->f32[i * context->param.audioChannelCount
+ + context->param.hapticChannelSource[j]];
+ }
+ }
+
+ float* hapticOutBuffer = HapticGenerator_runProcessingChain(
+ context->processingChain, context->inputBuffer.data(),
+ context->outputBuffer.data(), inBuffer->frameCount);
+ os::scaleHapticData(hapticOutBuffer, hapticSampleCount, context->param.maxHapticIntensity);
+
+ // For haptic data, the haptic playback thread will copy the data from effect input buffer,
+ // which contains haptic data at the end of the buffer, directly to sink buffer.
+ // In that case, copy haptic data to input buffer instead of output buffer.
+ // Note: this may not work with rpc/binder calls
+ memcpy_by_audio_format(static_cast<char*>(inBuffer->raw) + audioBytes,
+ static_cast<audio_format_t>(context->config.outputCfg.format),
+ hapticOutBuffer,
+ AUDIO_FORMAT_PCM_FLOAT,
+ hapticSampleCount);
+
+ return 0;
+}
+
+int32_t HapticGenerator_Command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
+ void *cmdData, uint32_t *replySize, void *replyData) {
+ HapticGeneratorContext *context = (HapticGeneratorContext *) self;
+
+ if (context == nullptr || context->state == HAPTICGENERATOR_STATE_UNINITIALIZED) {
+ return -EINVAL;
+ }
+
+ ALOGV("HapticGenerator_Command command %u cmdSize %u", cmdCode, cmdSize);
+
+ switch (cmdCode) {
+ case EFFECT_CMD_INIT:
+ if (replyData == nullptr || replySize == nullptr || *replySize != sizeof(int)) {
+ return -EINVAL;
+ }
+ *(int *) replyData = HapticGenerator_Init(context);
+ break;
+
+ case EFFECT_CMD_SET_CONFIG:
+ if (cmdData == nullptr || cmdSize != sizeof(effect_config_t)
+ || replyData == nullptr || replySize == nullptr || *replySize != sizeof(int)) {
+ return -EINVAL;
+ }
+ *(int *) replyData = HapticGenerator_Configure(
+ context, (effect_config_t *) cmdData);
+ break;
+
+ case EFFECT_CMD_RESET:
+ HapticGenerator_Reset(context);
+ break;
+
+ case EFFECT_CMD_GET_PARAM:
+ ALOGV("HapticGenerator_Command EFFECT_CMD_GET_PARAM cmdData %p,"
+ "*replySize %u, replyData: %p",
+ cmdData, *replySize, replyData);
+ break;
+
+ case EFFECT_CMD_SET_PARAM: {
+ ALOGV("HapticGenerator_Command EFFECT_CMD_SET_PARAM cmdSize %d cmdData %p, "
+ "*replySize %u, replyData %p", cmdSize, cmdData,
+ replySize ? *replySize : 0, replyData);
+ if (cmdData == nullptr || (cmdSize < (int) (sizeof(effect_param_t) + sizeof(int32_t)))
+ || replyData == nullptr || replySize == nullptr ||
+ *replySize != (int) sizeof(int32_t)) {
+ return -EINVAL;
+ }
+ effect_param_t *cmd = (effect_param_t *) cmdData;
+ *(int *) replyData = HapticGenerator_SetParameter(
+ context, *(int32_t *) cmd->data, cmd->vsize, cmd->data + sizeof(int32_t));
+ }
+ break;
+
+ case EFFECT_CMD_ENABLE:
+ if (replyData == nullptr || replySize == nullptr || *replySize != sizeof(int)) {
+ return -EINVAL;
+ }
+ if (context->state != HAPTICGENERATOR_STATE_INITIALIZED) {
+ return -ENOSYS;
+ }
+ context->state = HAPTICGENERATOR_STATE_ACTIVE;
+ ALOGV("EFFECT_CMD_ENABLE() OK");
+ *(int *) replyData = 0;
+ break;
+
+ case EFFECT_CMD_DISABLE:
+ if (replyData == nullptr || replySize == nullptr || *replySize != sizeof(int)) {
+ return -EINVAL;
+ }
+ if (context->state != HAPTICGENERATOR_STATE_ACTIVE) {
+ return -ENOSYS;
+ }
+ context->state = HAPTICGENERATOR_STATE_INITIALIZED;
+ ALOGV("EFFECT_CMD_DISABLE() OK");
+ *(int *) replyData = 0;
+ break;
+
+ case EFFECT_CMD_SET_VOLUME:
+ case EFFECT_CMD_SET_DEVICE:
+ case EFFECT_CMD_SET_AUDIO_MODE:
+ break;
+
+ default:
+ ALOGW("HapticGenerator_Command invalid command %u", cmdCode);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int32_t HapticGenerator_GetDescriptor(effect_handle_t self, effect_descriptor_t *descriptor) {
+ HapticGeneratorContext *context = (HapticGeneratorContext *) self;
+
+ if (context == nullptr ||
+ context->state == HAPTICGENERATOR_STATE_UNINITIALIZED) {
+ return -EINVAL;
+ }
+
+ memcpy(descriptor, &gHgDescriptor, sizeof(effect_descriptor_t));
+
+ return 0;
+}
+
+} // namespace android::audio_effect::haptic_generator
diff --git a/media/libeffects/hapticgenerator/EffectHapticGenerator.h b/media/libeffects/hapticgenerator/EffectHapticGenerator.h
new file mode 100644
index 0000000..a5688de
--- /dev/null
+++ b/media/libeffects/hapticgenerator/EffectHapticGenerator.h
@@ -0,0 +1,118 @@
+/*
+ * 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_EFFECTHAPTICGENERATOR_H_
+#define ANDROID_EFFECTHAPTICGENERATOR_H_
+
+#include <functional>
+#include <vector>
+#include <map>
+
+#include <hardware/audio_effect.h>
+#include <system/audio_effect.h>
+#include <vibrator/ExternalVibrationUtils.h>
+
+#include "Processors.h"
+
+namespace android::audio_effect::haptic_generator {
+
+//-----------------------------------------------------------------------------
+// Definition
+//-----------------------------------------------------------------------------
+
+enum hapticgenerator_state_t {
+ HAPTICGENERATOR_STATE_UNINITIALIZED,
+ HAPTICGENERATOR_STATE_INITIALIZED,
+ HAPTICGENERATOR_STATE_ACTIVE,
+};
+
+// parameters for each haptic generator
+struct HapticGeneratorParam {
+ uint32_t hapticChannelSource[2]; // The audio channels used to generate haptic channels.
+ // The first channel will be used to generate HAPTIC_A,
+ // The second channel will be used to generate HAPTIC_B
+ // The value will be offset of audio channel
+ uint32_t audioChannelCount;
+ uint32_t hapticChannelCount;
+
+ // A map from track id to haptic intensity.
+ std::map<int, os::HapticScale> id2Intensity;
+ os::HapticScale maxHapticIntensity; // max intensity will be used to scale haptic data.
+};
+
+// A structure to keep all shared pointers for all processors in HapticGenerator.
+struct HapticGeneratorProcessorsRecord {
+ std::vector<std::shared_ptr<BiquadFilter>> filters;
+ std::vector<std::shared_ptr<Ramp>> ramps;
+ std::vector<std::shared_ptr<SlowEnvelope>> slowEnvs;
+};
+
+// A structure to keep all the context for HapticGenerator.
+struct HapticGeneratorContext {
+ const struct effect_interface_s *itfe;
+ effect_config_t config;
+ hapticgenerator_state_t state;
+ struct HapticGeneratorParam param;
+ size_t audioDataBytesPerFrame;
+
+ // A cache for all shared pointers of the HapticGenerator
+ struct HapticGeneratorProcessorsRecord processorsRecord;
+
+ // Using a vector of functions to record the processing chain for haptic-generating algorithm.
+ // The three parameters of the processing functions are pointer to output buffer, pointer to
+ // input buffer and frame count.
+ std::vector<std::function<void(float*, const float*, size_t)>> processingChain;
+
+ // inputBuffer is where to keep input buffer for the generating algorithm. It will be
+ // constructed according to HapticGeneratorParam.hapticChannelSource.
+ std::vector<float> inputBuffer;
+
+ // outputBuffer is a buffer having the same length as inputBuffer. It can be used as
+ // intermediate buffer in the generating algorithm.
+ std::vector<float> outputBuffer;
+};
+
+//-----------------------------------------------------------------------------
+// Effect API
+//-----------------------------------------------------------------------------
+
+int32_t HapticGeneratorLib_Create(const effect_uuid_t *uuid,
+ int32_t sessionId,
+ int32_t ioId,
+ effect_handle_t *handle);
+
+int32_t HapticGeneratorLib_Release(effect_handle_t handle);
+
+int32_t HapticGeneratorLib_GetDescriptor(const effect_uuid_t *uuid,
+ effect_descriptor_t *descriptor);
+
+int32_t HapticGenerator_Process(effect_handle_t self,
+ audio_buffer_t *inBuffer,
+ audio_buffer_t *outBuffer);
+
+int32_t HapticGenerator_Command(effect_handle_t self,
+ uint32_t cmdCode,
+ uint32_t cmdSize,
+ void *cmdData,
+ uint32_t *replySize,
+ void *replyData);
+
+int32_t HapticGenerator_GetDescriptor(effect_handle_t self,
+ effect_descriptor_t *descriptor);
+
+} // namespace android::audio_effect::haptic_generator
+
+#endif // ANDROID_EFFECTHAPTICGENERATOR_H_
diff --git a/media/libeffects/hapticgenerator/MODULE_LICENSE_APACHE2 b/media/libeffects/hapticgenerator/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media/libeffects/hapticgenerator/MODULE_LICENSE_APACHE2
diff --git a/media/libeffects/hapticgenerator/Processors.cpp b/media/libeffects/hapticgenerator/Processors.cpp
new file mode 100644
index 0000000..179b5dc
--- /dev/null
+++ b/media/libeffects/hapticgenerator/Processors.cpp
@@ -0,0 +1,234 @@
+/*
+ * 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_TAG "EffectHG_Processors"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <assert.h>
+
+#include <cmath>
+
+#include "Processors.h"
+
+#if defined(__aarch64__) || defined(__ARM_NEON__)
+#ifndef USE_NEON
+#define USE_NEON (true)
+#endif
+#else
+#define USE_NEON (false)
+#endif
+#if USE_NEON
+#include <arm_neon.h>
+#endif
+
+namespace android::audio_effect::haptic_generator {
+
+float getRealPoleZ(float cornerFrequency, float sampleRate) {
+ // This will be a pole of a first order filter.
+ float realPoleS = -2 * M_PI * cornerFrequency;
+ return exp(realPoleS / sampleRate); // zero-pole matching
+}
+
+std::pair<float, float> getComplexPoleZ(float ringingFrequency, float q, float sampleRate) {
+ // This is the pole for 1/(s^2 + s/q + 1) in normalized frequency. The other pole is
+ // the complex conjugate of this.
+ float poleImagS = 2 * M_PI * ringingFrequency;
+ float poleRealS = -poleImagS / (2 * q);
+ float poleRadius = exp(poleRealS / sampleRate);
+ float poleImagZ = poleRadius * sin(poleImagS / sampleRate);
+ float poleRealZ = poleRadius * cos(poleImagS / sampleRate);
+ return {poleRealZ, poleImagZ};
+}
+
+// Implementation of Ramp
+
+Ramp::Ramp(size_t channelCount) : mChannelCount(channelCount) {}
+
+void Ramp::process(float *out, const float *in, size_t frameCount) {
+ size_t i = 0;
+#if USE_NEON
+ size_t sampleCount = frameCount * mChannelCount;
+ float32x2_t allZero = vdup_n_f32(0.0f);
+ while (i + 1 < sampleCount) {
+ vst1_f32(out, vmax_f32(vld1_f32(in), allZero));
+ in += 2;
+ out += 2;
+ i += 2;
+ }
+#endif // USE_NEON
+ for (; i < frameCount * mChannelCount; ++i) {
+ *out = *in >= 0.0f ? *in : 0.0f;
+ out++;
+ in++;
+ }
+}
+
+// Implementation of SlowEnvelope
+
+SlowEnvelope::SlowEnvelope(
+ float cornerFrequency,
+ float sampleRate,
+ float normalizationPower,
+ size_t channelCount)
+ : mLpf(createLPF(cornerFrequency, sampleRate, channelCount)),
+ mNormalizationPower(normalizationPower),
+ mChannelCount(channelCount),
+ mEnv(0.25 * (sampleRate / (2 * M_PI * cornerFrequency))) {}
+
+void SlowEnvelope::process(float* out, const float* in, size_t frameCount) {
+ size_t sampleCount = frameCount * mChannelCount;
+ if (sampleCount > mLpfInBuffer.size()) {
+ mLpfInBuffer.resize(sampleCount, mEnv);
+ mLpfOutBuffer.resize(sampleCount);
+ }
+ mLpf->process(mLpfOutBuffer.data(), mLpfInBuffer.data(), frameCount);
+ for (size_t i = 0; i < sampleCount; ++i) {
+ *out = *in * pow(mLpfOutBuffer[i], mNormalizationPower);
+ out++;
+ in++;
+ }
+}
+
+void SlowEnvelope::clear() {
+ mLpf->clear();
+}
+
+// Implementation of helper functions
+
+BiquadFilterCoefficients cascadeFirstOrderFilters(const BiquadFilterCoefficients &coefs1,
+ const BiquadFilterCoefficients &coefs2) {
+ assert(coefs1[2] == 0.0f);
+ assert(coefs2[2] == 0.0f);
+ assert(coefs1[4] == 0.0f);
+ assert(coefs2[4] == 0.0f);
+ return {coefs1[0] * coefs2[0],
+ coefs1[0] * coefs2[1] + coefs1[1] * coefs2[0],
+ coefs1[1] * coefs2[1],
+ coefs1[3] + coefs2[3],
+ coefs1[3] * coefs2[3]};
+}
+
+BiquadFilterCoefficients lpfCoefs(const float cornerFrequency, const float sampleRate) {
+ BiquadFilterCoefficients coefficient;
+ float realPoleZ = getRealPoleZ(cornerFrequency, sampleRate);
+ // This is a zero at nyquist
+ coefficient[0] = 0.5f * (1 - realPoleZ);
+ coefficient[1] = coefficient[0];
+ coefficient[2] = 0.0f;
+ coefficient[3] = -realPoleZ; // This is traditional 1/(s+1) filter
+ coefficient[4] = 0.0f;
+ return coefficient;
+}
+
+std::shared_ptr<BiquadFilter> createLPF(const float cornerFrequency,
+ const float sampleRate,
+ const size_t channelCount) {
+ BiquadFilterCoefficients coefficient = lpfCoefs(cornerFrequency, sampleRate);
+ return std::make_shared<BiquadFilter>(channelCount, coefficient);
+}
+
+std::shared_ptr<BiquadFilter> createLPF2(const float cornerFrequency,
+ const float sampleRate,
+ const size_t channelCount) {
+ BiquadFilterCoefficients coefficient = lpfCoefs(cornerFrequency, sampleRate);
+ return std::make_shared<BiquadFilter>(
+ channelCount, cascadeFirstOrderFilters(coefficient, coefficient));
+}
+
+std::shared_ptr<BiquadFilter> createHPF2(const float cornerFrequency,
+ const float sampleRate,
+ const size_t channelCount) {
+ BiquadFilterCoefficients coefficient;
+ // Note: this is valid only when corner frequency is less than nyquist / 2.
+ float realPoleZ = getRealPoleZ(cornerFrequency, sampleRate);
+
+ // Note: this is a zero at DC
+ coefficient[0] = 0.5f * (1 + realPoleZ);
+ coefficient[1] = -coefficient[0];
+ coefficient[2] = 0.0f;
+ coefficient[3] = -realPoleZ;
+ coefficient[4] = 0.0f;
+ return std::make_shared<BiquadFilter>(
+ channelCount, cascadeFirstOrderFilters(coefficient, coefficient));
+}
+
+BiquadFilterCoefficients apfCoefs(const float cornerFrequency, const float sampleRate) {
+ BiquadFilterCoefficients coefficient;
+ float realPoleZ = getRealPoleZ(cornerFrequency, sampleRate);
+ float zeroZ = 1.0f / realPoleZ;
+ coefficient[0] = (1.0f - realPoleZ) / (1.0f - zeroZ);
+ coefficient[1] = -coefficient[0] * zeroZ;
+ coefficient[2] = 0.0f;
+ coefficient[3] = -realPoleZ;
+ coefficient[4] = 0.0f;
+ return coefficient;
+}
+
+std::shared_ptr<BiquadFilter> createAPF(const float cornerFrequency,
+ const float sampleRate,
+ const size_t channelCount) {
+ BiquadFilterCoefficients coefficient = apfCoefs(cornerFrequency, sampleRate);
+ return std::make_shared<BiquadFilter>(channelCount, coefficient);
+}
+
+std::shared_ptr<BiquadFilter> createAPF2(const float cornerFrequency1,
+ const float cornerFrequency2,
+ const float sampleRate,
+ const size_t channelCount) {
+ BiquadFilterCoefficients coefs1 = apfCoefs(cornerFrequency1, sampleRate);
+ BiquadFilterCoefficients coefs2 = apfCoefs(cornerFrequency2, sampleRate);
+ return std::make_shared<BiquadFilter>(
+ channelCount, cascadeFirstOrderFilters(coefs1, coefs2));
+}
+
+std::shared_ptr<BiquadFilter> createBPF(const float ringingFrequency,
+ const float q,
+ const float sampleRate,
+ const size_t channelCount) {
+ BiquadFilterCoefficients coefficient;
+ const auto [real, img] = getComplexPoleZ(ringingFrequency, q, sampleRate);
+ // Note: this is not a standard cookbook BPF, but a low pass filter with zero at DC
+ coefficient[0] = 1.0f;
+ coefficient[1] = -1.0f;
+ coefficient[2] = 0.0f;
+ coefficient[3] = -2 * real;
+ coefficient[4] = real * real + img * img;
+ return std::make_shared<BiquadFilter>(channelCount, coefficient);
+}
+
+std::shared_ptr<BiquadFilter> createBSF(const float ringingFrequency,
+ const float zq,
+ const float pq,
+ const float sampleRate,
+ const size_t channelCount) {
+ BiquadFilterCoefficients coefficient;
+ const auto [zeroReal, zeroImg] = getComplexPoleZ(ringingFrequency, zq, sampleRate);
+ float zeroCoeff1 = -2 * zeroReal;
+ float zeroCoeff2 = zeroReal* zeroReal + zeroImg * zeroImg;
+ const auto [poleReal, poleImg] = getComplexPoleZ(ringingFrequency, pq, sampleRate);
+ float poleCoeff1 = -2 * poleReal;
+ float poleCoeff2 = poleReal * poleReal + poleImg * poleImg;
+ const float norm = (1.0f + poleCoeff1 + poleCoeff2) / (1.0f + zeroCoeff1 + zeroCoeff2);
+ coefficient[0] = 1.0f * norm;
+ coefficient[1] = zeroCoeff1 * norm;
+ coefficient[2] = zeroCoeff2 * norm;
+ coefficient[3] = poleCoeff1;
+ coefficient[4] = poleCoeff2;
+ return std::make_shared<BiquadFilter>(channelCount, coefficient);
+}
+
+} // namespace android::audio_effect::haptic_generator
diff --git a/media/libeffects/hapticgenerator/Processors.h b/media/libeffects/hapticgenerator/Processors.h
new file mode 100644
index 0000000..e14458b
--- /dev/null
+++ b/media/libeffects/hapticgenerator/Processors.h
@@ -0,0 +1,104 @@
+/*
+ * 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 _EFFECT_HAPTIC_GENERATOR_PROCESSORS_H_
+#define _EFFECT_HAPTIC_GENERATOR_PROCESSORS_H_
+
+#include <sys/types.h>
+
+#include <memory>
+#include <vector>
+
+#include <audio_utils/BiquadFilter.h>
+
+using android::audio_utils::BiquadFilter;
+using BiquadFilterCoefficients = std::array<float, android::audio_utils::kBiquadNumCoefs>;
+
+namespace android::audio_effect::haptic_generator {
+
+// A class providing a process function that makes input data non-negative.
+class Ramp {
+public:
+ explicit Ramp(size_t channelCount);
+
+ void process(float *out, const float *in, size_t frameCount);
+
+private:
+ const size_t mChannelCount;
+};
+
+
+class SlowEnvelope {
+public:
+ SlowEnvelope(float cornerFrequency, float sampleRate,
+ float normalizationPower, size_t channelCount);
+
+ void process(float *out, const float *in, size_t frameCount);
+
+ void clear();
+
+private:
+ const std::shared_ptr<BiquadFilter> mLpf;
+ std::vector<float> mLpfInBuffer;
+ std::vector<float> mLpfOutBuffer;
+ const float mNormalizationPower;
+ const float mChannelCount;
+ const float mEnv;
+};
+
+// Helper functions
+
+BiquadFilterCoefficients cascadeFirstOrderFilters(const BiquadFilterCoefficients &coefs1,
+ const BiquadFilterCoefficients &coefs2);
+
+std::shared_ptr<BiquadFilter> createLPF(const float cornerFrequency,
+ const float sampleRate,
+ const size_t channelCount);
+
+// Create two cascaded LPF with same corner frequency.
+std::shared_ptr<BiquadFilter> createLPF2(const float cornerFrequency,
+ const float sampleRate,
+ const size_t channelCount);
+
+// Create two cascaded HPF with same corner frequency.
+std::shared_ptr<BiquadFilter> createHPF2(const float cornerFrequency,
+ const float sampleRate,
+ const size_t channelCount);
+
+std::shared_ptr<BiquadFilter> createAPF(const float cornerFrequency,
+ const float sampleRate,
+ const size_t channelCount);
+
+// Create two cascaded APF with two different corner frequency.
+std::shared_ptr<BiquadFilter> createAPF2(const float cornerFrequency1,
+ const float cornerFrequency2,
+ const float sampleRate,
+ const size_t channelCount);
+
+std::shared_ptr<BiquadFilter> createBPF(const float ringingFrequency,
+ const float q,
+ const float sampleRate,
+ const size_t channelCount);
+
+std::shared_ptr<BiquadFilter> createBSF(const float ringingFrequency,
+ const float zq,
+ const float pq,
+ const float sampleRate,
+ const size_t channelCount);
+
+} // namespace android::audio_effect::haptic_generator
+
+#endif // _EFFECT_HAPTIC_GENERATOR_PROCESSORS_H_
diff --git a/media/libeffects/lvm/lib/Common/src/DC_2I_D16_TRC_WRA_01_Private.h b/media/libeffects/lvm/lib/Common/src/DC_2I_D16_TRC_WRA_01_Private.h
index 6508b73..4170b3c 100644
--- a/media/libeffects/lvm/lib/Common/src/DC_2I_D16_TRC_WRA_01_Private.h
+++ b/media/libeffects/lvm/lib/Common/src/DC_2I_D16_TRC_WRA_01_Private.h
@@ -18,7 +18,7 @@
#ifndef _DC_2I_D16_TRC_WRA_01_PRIVATE_H_
#define _DC_2I_D16_TRC_WRA_01_PRIVATE_H_
-#define DC_FLOAT_STEP 0.0000002384f;
+#define DC_FLOAT_STEP 0.0000002384f
/* The internal state variables are implemented in a (for the user) hidden structure */
/* In this (private) file, the internal structure is declared fro private use.*/
diff --git a/media/libeffects/preprocessing/Android.bp b/media/libeffects/preprocessing/Android.bp
index c87635f..16cd0ad 100644
--- a/media/libeffects/preprocessing/Android.bp
+++ b/media/libeffects/preprocessing/Android.bp
@@ -8,12 +8,6 @@
srcs: ["PreProcessing.cpp"],
- include_dirs: [
- "external/webrtc",
- "external/webrtc/webrtc/modules/include",
- "external/webrtc/webrtc/modules/audio_processing/include",
- ],
-
shared_libs: [
"libwebrtc_audio_preprocessing",
"libspeexresampler",
diff --git a/media/libeffects/preprocessing/PreProcessing.cpp b/media/libeffects/preprocessing/PreProcessing.cpp
index 5fab5be..c7afe2f 100644
--- a/media/libeffects/preprocessing/PreProcessing.cpp
+++ b/media/libeffects/preprocessing/PreProcessing.cpp
@@ -1240,9 +1240,9 @@
memcpy(outBuffer->s16,
session->outBuf,
fr * session->outChannelCount * sizeof(int16_t));
- memcpy(session->outBuf,
- session->outBuf + fr * session->outChannelCount,
- (session->framesOut - fr) * session->outChannelCount * sizeof(int16_t));
+ memmove(session->outBuf,
+ session->outBuf + fr * session->outChannelCount,
+ (session->framesOut - fr) * session->outChannelCount * sizeof(int16_t));
session->framesOut -= fr;
framesWr += fr;
}
@@ -1303,9 +1303,9 @@
session->procFrame->data_,
&frOut);
}
- memcpy(session->inBuf,
- session->inBuf + frIn * session->inChannelCount,
- (session->framesIn - frIn) * session->inChannelCount * sizeof(int16_t));
+ memmove(session->inBuf,
+ session->inBuf + frIn * session->inChannelCount,
+ (session->framesIn - frIn) * session->inChannelCount * sizeof(int16_t));
session->framesIn -= frIn;
} else {
size_t fr = session->frameCount - session->framesIn;
@@ -1381,9 +1381,9 @@
memcpy(outBuffer->s16 + framesWr * session->outChannelCount,
session->outBuf,
fr * session->outChannelCount * sizeof(int16_t));
- memcpy(session->outBuf,
- session->outBuf + fr * session->outChannelCount,
- (session->framesOut - fr) * session->outChannelCount * sizeof(int16_t));
+ memmove(session->outBuf,
+ session->outBuf + fr * session->outChannelCount,
+ (session->framesOut - fr) * session->outChannelCount * sizeof(int16_t));
session->framesOut -= fr;
outBuffer->frameCount += fr;
@@ -1837,9 +1837,9 @@
session->revFrame->data_,
&frOut);
}
- memcpy(session->revBuf,
- session->revBuf + frIn * session->inChannelCount,
- (session->framesRev - frIn) * session->inChannelCount * sizeof(int16_t));
+ memmove(session->revBuf,
+ session->revBuf + frIn * session->inChannelCount,
+ (session->framesRev - frIn) * session->inChannelCount * sizeof(int16_t));
session->framesRev -= frIn;
} else {
size_t fr = session->frameCount - session->framesRev;
diff --git a/media/libeffects/visualizer/Android.bp b/media/libeffects/visualizer/Android.bp
new file mode 100644
index 0000000..f6c585e
--- /dev/null
+++ b/media/libeffects/visualizer/Android.bp
@@ -0,0 +1,32 @@
+// Visualizer library
+cc_library_shared {
+ name: "libvisualizer",
+
+ vendor: true,
+
+ srcs: [
+ "EffectVisualizer.cpp",
+ ],
+
+ cflags: [
+ "-O2",
+ "-fvisibility=hidden",
+
+ "-DBUILD_FLOAT",
+ "-DSUPPORT_MC",
+
+ "-Wall",
+ "-Werror",
+ ],
+
+ shared_libs: [
+ "liblog",
+ ],
+
+ relative_install_path: "soundfx",
+
+ header_libs: [
+ "libaudioeffects",
+ "libaudioutils_headers",
+ ],
+}
diff --git a/media/libeffects/visualizer/Android.mk b/media/libeffects/visualizer/Android.mk
deleted file mode 100644
index 35e2f3d..0000000
--- a/media/libeffects/visualizer/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-# Visualizer library
-include $(CLEAR_VARS)
-
-LOCAL_VENDOR_MODULE := true
-LOCAL_SRC_FILES:= \
- EffectVisualizer.cpp
-
-LOCAL_CFLAGS+= -O2 -fvisibility=hidden
-LOCAL_CFLAGS += -Wall -Werror
-LOCAL_CFLAGS += -DBUILD_FLOAT -DSUPPORT_MC
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- liblog \
- libdl
-
-LOCAL_MODULE_RELATIVE_PATH := soundfx
-LOCAL_MODULE:= libvisualizer
-
-LOCAL_C_INCLUDES := \
- $(call include-path-for, audio-effects) \
- $(call include-path-for, audio-utils)
-
-
-LOCAL_HEADER_LIBRARIES += libhardware_headers
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index be3f995..1caee04 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",
@@ -15,6 +17,22 @@
],
}
+cc_library_headers {
+ name: "libmedia_datasource_headers",
+ export_include_dirs: ["include"],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
+}
+
filegroup {
name: "libmedia_omx_aidl",
srcs: [
@@ -31,10 +49,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 +59,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 +210,8 @@
cc_library_static {
name: "libmedia_midiiowrapper",
+ min_sdk_version: "29",
+
srcs: ["MidiIoWrapper.cpp"],
static_libs: [
@@ -191,7 +219,7 @@
],
header_libs: [
- "libmedia_headers",
+ "libmedia_datasource_headers",
"media_ndk_headers",
],
@@ -208,6 +236,14 @@
],
cfi: true,
},
+
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
cc_library_shared {
@@ -297,11 +333,13 @@
header_libs: [
"libstagefright_headers",
"media_ndk_headers",
+ "jni_headers",
],
export_header_lib_headers: [
"libstagefright_headers",
"media_ndk_headers",
+ "jni_headers",
],
shared_libs: [
@@ -360,3 +398,36 @@
cfi: true,
},
}
+
+cc_library_static {
+ name: "libmedia_ndkformatpriv",
+
+ host_supported: true,
+
+ srcs: [
+ "NdkMediaFormatPriv.cpp",
+ "NdkMediaErrorPriv.cpp",
+ ],
+
+ header_libs: [
+ "libstagefright_foundation_headers",
+ "libstagefright_headers",
+ "media_ndk_headers",
+ ],
+
+ cflags: [
+ "-DEXPORT=__attribute__((visibility(\"default\")))",
+ "-Werror",
+ "-Wall",
+ ],
+
+ export_include_dirs: ["include"],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
+ apex_available: ["com.android.media"],
+}
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index 20bc23d..c08d187 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -40,6 +40,7 @@
SET_DATA_SOURCE_FD,
SET_DATA_SOURCE_STREAM,
SET_DATA_SOURCE_CALLBACK,
+ SET_DATA_SOURCE_RTP,
SET_BUFFERING_SETTINGS,
GET_BUFFERING_SETTINGS,
PREPARE_ASYNC,
@@ -161,6 +162,15 @@
return reply.readInt32();
}
+ status_t setDataSource(const String8& rtpParams) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+ data.writeString8(rtpParams);
+ remote()->transact(SET_DATA_SOURCE_RTP, data, &reply);
+
+ return reply.readInt32();
+ }
+
// pass the buffered IGraphicBufferProducer to the media player service
status_t setVideoSurfaceTexture(const sp<IGraphicBufferProducer>& bufferProducer)
{
@@ -685,6 +695,12 @@
}
return NO_ERROR;
}
+ case SET_DATA_SOURCE_RTP: {
+ CHECK_INTERFACE(IMediaPlayer, data, reply);
+ String8 rtpParams = data.readString8();
+ reply->writeInt32(setDataSource(rtpParams));
+ return NO_ERROR;
+ }
case SET_VIDEO_SURFACETEXTURE: {
CHECK_INTERFACE(IMediaPlayer, data, reply);
sp<IGraphicBufferProducer> bufferProducer =
diff --git a/media/libmedia/MediaResource.cpp b/media/libmedia/MediaResource.cpp
index 0936a99..fe86d27 100644
--- a/media/libmedia/MediaResource.cpp
+++ b/media/libmedia/MediaResource.cpp
@@ -35,7 +35,7 @@
this->value = value;
}
-MediaResource::MediaResource(Type type, const std::vector<int8_t> &id, int64_t value) {
+MediaResource::MediaResource(Type type, const std::vector<uint8_t> &id, int64_t value) {
this->type = type;
this->subType = SubType::kUnspecifiedSubType;
this->id = id;
@@ -66,11 +66,11 @@
}
//static
-MediaResource MediaResource::DrmSessionResource(const std::vector<int8_t> &id, int64_t value) {
+MediaResource MediaResource::DrmSessionResource(const std::vector<uint8_t> &id, int64_t value) {
return MediaResource(Type::kDrmSession, id, value);
}
-static String8 bytesToHexString(const std::vector<int8_t> &bytes) {
+static String8 bytesToHexString(const std::vector<uint8_t> &bytes) {
String8 str;
for (auto &b : bytes) {
str.appendFormat("%02x", b);
diff --git a/media/libmedia/MidiIoWrapper.cpp b/media/libmedia/MidiIoWrapper.cpp
index e71ea2c..da272e3 100644
--- a/media/libmedia/MidiIoWrapper.cpp
+++ b/media/libmedia/MidiIoWrapper.cpp
@@ -18,8 +18,9 @@
#define LOG_TAG "MidiIoWrapper"
#include <utils/Log.h>
-#include <sys/stat.h>
#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
#include <media/MidiIoWrapper.h>
#include <media/MediaExtractorPluginApi.h>
diff --git a/media/libmedia/NdkMediaFormatPriv.cpp b/media/libmedia/NdkMediaFormatPriv.cpp
index 3a9fb8b..7983184 100644
--- a/media/libmedia/NdkMediaFormatPriv.cpp
+++ b/media/libmedia/NdkMediaFormatPriv.cpp
@@ -24,8 +24,6 @@
#include <media/NdkMediaFormatPriv.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <jni.h>
-
using namespace android;
namespace android {
diff --git a/media/libmedia/include/media/IMediaPlayer.h b/media/libmedia/include/media/IMediaPlayer.h
index a4c0ec6..3548a1e 100644
--- a/media/libmedia/include/media/IMediaPlayer.h
+++ b/media/libmedia/include/media/IMediaPlayer.h
@@ -59,6 +59,7 @@
virtual status_t setDataSource(int fd, int64_t offset, int64_t length) = 0;
virtual status_t setDataSource(const sp<IStreamSource>& source) = 0;
virtual status_t setDataSource(const sp<IDataSource>& source) = 0;
+ virtual status_t setDataSource(const String8& rtpParams) = 0;
virtual status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer) = 0;
virtual status_t getBufferingSettings(
diff --git a/media/libmedia/include/media/MediaResource.h b/media/libmedia/include/media/MediaResource.h
index e7362c1..4927d28 100644
--- a/media/libmedia/include/media/MediaResource.h
+++ b/media/libmedia/include/media/MediaResource.h
@@ -35,13 +35,13 @@
MediaResource() = delete;
MediaResource(Type type, int64_t value);
MediaResource(Type type, SubType subType, int64_t value);
- MediaResource(Type type, const std::vector<int8_t> &id, int64_t value);
+ MediaResource(Type type, const std::vector<uint8_t> &id, int64_t value);
static MediaResource CodecResource(bool secure, bool video);
static MediaResource GraphicMemoryResource(int64_t value);
static MediaResource CpuBoostResource();
static MediaResource VideoBatteryResource();
- static MediaResource DrmSessionResource(const std::vector<int8_t> &id, int64_t value);
+ static MediaResource DrmSessionResource(const std::vector<uint8_t> &id, int64_t value);
};
inline static const char *asString(MediaResource::Type i, const char *def = "??") {
diff --git a/media/libmedia/include/media/mediaplayer.h b/media/libmedia/include/media/mediaplayer.h
index 2335c5a..d0a8e38 100644
--- a/media/libmedia/include/media/mediaplayer.h
+++ b/media/libmedia/include/media/mediaplayer.h
@@ -60,6 +60,7 @@
MEDIA_META_DATA = 202,
MEDIA_DRM_INFO = 210,
MEDIA_TIME_DISCONTINUITY = 211,
+ MEDIA_IMS_RX_NOTICE = 300,
MEDIA_AUDIO_ROUTING_CHANGED = 10000,
};
@@ -217,6 +218,7 @@
status_t setDataSource(int fd, int64_t offset, int64_t length);
status_t setDataSource(const sp<IDataSource> &source);
+ status_t setDataSource(const String8& rtpParams);
status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer);
status_t setListener(const sp<MediaPlayerListener>& listener);
diff --git a/media/libmedia/include/media/mediarecorder.h b/media/libmedia/include/media/mediarecorder.h
index 6e2d94d..fbcdb28 100644
--- a/media/libmedia/include/media/mediarecorder.h
+++ b/media/libmedia/include/media/mediarecorder.h
@@ -291,6 +291,8 @@
bool mIsOutputFileSet;
Mutex mLock;
Mutex mNotifyLock;
+
+ output_format mOutputFormat;
};
}; // namespace android
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 1fadc94..1b89fc7 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -195,6 +195,22 @@
return err;
}
+status_t MediaPlayer::setDataSource(const String8& rtpParams)
+{
+ ALOGV("setDataSource(rtpParams)");
+ status_t err = UNKNOWN_ERROR;
+ const sp<IMediaPlayerService> service(getMediaPlayerService());
+ if (service != 0) {
+ sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
+ if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
+ (NO_ERROR != player->setDataSource(rtpParams))) {
+ player.clear();
+ }
+ err = attachNewPlayer(player);
+ }
+ return err;
+}
+
status_t MediaPlayer::invoke(const Parcel& request, Parcel *reply)
{
Mutex::Autolock _l(mLock);
@@ -943,6 +959,9 @@
case MEDIA_META_DATA:
ALOGV("Received timed metadata message");
break;
+ case MEDIA_IMS_RX_NOTICE:
+ ALOGV("Received IMS Rx notice message");
+ break;
default:
ALOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2);
break;
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 70655d5..d9d1f25 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -244,6 +244,7 @@
mCurrentState = MEDIA_RECORDER_ERROR;
return ret;
}
+ mOutputFormat = (output_format)of;
mCurrentState = MEDIA_RECORDER_DATASOURCE_CONFIGURED;
return ret;
}
@@ -479,6 +480,13 @@
(MEDIA_RECORDER_PREPARED |
MEDIA_RECORDER_RECORDING |
MEDIA_RECORDER_ERROR));
+
+ // For RTP video, parameter should be set dynamically.
+ if (isInvalidState) {
+ if (mCurrentState == MEDIA_RECORDER_RECORDING &&
+ mOutputFormat == OUTPUT_FORMAT_RTP_AVP)
+ isInvalidState = false;
+ }
if (isInvalidState) {
ALOGE("setParameters is called in an invalid state: %d", mCurrentState);
return INVALID_OPERATION;
@@ -737,6 +745,7 @@
mIsAudioEncoderSet = false;
mIsVideoEncoderSet = false;
mIsOutputFileSet = false;
+ mOutputFormat = OUTPUT_FORMAT_DEFAULT;
}
// Release should be OK in any state
diff --git a/media/libmediahelper/Android.bp b/media/libmediahelper/Android.bp
index 72edeec..b46c98a 100644
--- a/media/libmediahelper/Android.bp
+++ b/media/libmediahelper/Android.bp
@@ -1,7 +1,14 @@
cc_library_headers {
name: "libmedia_helper_headers",
vendor_available: true,
+ min_sdk_version: "29",
export_include_dirs: ["include"],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
cc_library {
@@ -26,4 +33,10 @@
"libmedia_helper_headers",
],
clang: true,
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
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/Android.bp b/media/libmediaplayerservice/Android.bp
index 5301f5c..324f4ae 100644
--- a/media/libmediaplayerservice/Android.bp
+++ b/media/libmediaplayerservice/Android.bp
@@ -16,6 +16,7 @@
"android.hardware.media.c2@1.0",
"android.hardware.media.omx@1.0",
"libbase",
+ "libandroid_net",
"libaudioclient",
"libbinder",
"libcamera_client",
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index c0da0ce..555f459 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1062,6 +1062,17 @@
return mStatus = setDataSource_post(p, p->setDataSource(dataSource));
}
+status_t MediaPlayerService::Client::setDataSource(
+ const String8& rtpParams) {
+ player_type playerType = NU_PLAYER;
+ sp<MediaPlayerBase> p = setDataSource_pre(playerType);
+ if (p == NULL) {
+ return NO_INIT;
+ }
+ // now set data source
+ return mStatus = setDataSource_post(p, p->setDataSource(rtpParams));
+}
+
void MediaPlayerService::Client::disconnectNativeWindow_l() {
if (mConnectedWindow != NULL) {
status_t err = nativeWindowDisconnect(
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 6431ca1..3d596a5 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -368,6 +368,7 @@
virtual status_t setDataSource(const sp<IStreamSource> &source);
virtual status_t setDataSource(const sp<IDataSource> &source);
+ virtual status_t setDataSource(const String8& rtpParams);
sp<MediaPlayerBase> setDataSource_pre(player_type playerType);
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index cf7f423..7e5fe56 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -44,6 +44,7 @@
#include <media/stagefright/CameraSourceTimeLapse.h>
#include <media/stagefright/MPEG2TSWriter.h>
#include <media/stagefright/MPEG4Writer.h>
+#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/MediaCodecSource.h>
@@ -117,6 +118,11 @@
mAudioSource((audio_source_t)AUDIO_SOURCE_CNT), // initialize with invalid value
mPrivacySensitive(PRIVACY_SENSITIVE_DEFAULT),
mVideoSource(VIDEO_SOURCE_LIST_END),
+ mRTPCVOExtMap(-1),
+ mRTPCVODegrees(0),
+ mRTPSockDscp(0),
+ mRTPSockNetwork(0),
+ mLastSeqNo(0),
mStarted(false),
mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),
mDeviceCallbackEnabled(false),
@@ -567,6 +573,30 @@
// range that a specific encoder supports. The mismatch between the
// the target and requested bit rate will NOT be treated as an error.
mVideoBitRate = bitRate;
+
+ // A new bitrate(TMMBR) should be applied on runtime as well if OutputFormat is RTP_AVP
+ if (mOutputFormat == OUTPUT_FORMAT_RTP_AVP && mStarted && mPauseStartTimeUs == 0) {
+ // Regular I frames may overload the network so we reduce the bitrate to allow
+ // margins for the I frame overruns.
+ // Still send requested bitrate (TMMBR) in the reply (TMMBN).
+ const float coefficient = 0.8f;
+ mVideoBitRate = (bitRate * coefficient) / 1000 * 1000;
+ mVideoEncoderSource->setEncodingBitrate(mVideoBitRate);
+ ARTPWriter* rtpWriter = static_cast<ARTPWriter*>(mWriter.get());
+ rtpWriter->setTMMBNInfo(mOpponentID, bitRate);
+ }
+
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamVideoBitRateMode(int32_t bitRateMode) {
+ ALOGV("setParamVideoBitRateMode: %d", bitRateMode);
+ // TODO: clarify what bitrate mode of -1 is as these start from 0
+ if (bitRateMode < -1) {
+ ALOGE("Unsupported video bitrate mode: %d", bitRateMode);
+ return BAD_VALUE;
+ }
+ mVideoBitRateMode = bitRateMode;
return OK;
}
@@ -776,6 +806,105 @@
return OK;
}
+status_t StagefrightRecorder::setParamRtpLocalIp(const String8 &localIp) {
+ ALOGV("setParamVideoLocalIp: %s", localIp.string());
+
+ mLocalIp.setTo(localIp.string());
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamRtpLocalPort(int32_t localPort) {
+ ALOGV("setParamVideoLocalPort: %d", localPort);
+
+ mLocalPort = localPort;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamRtpRemoteIp(const String8 &remoteIp) {
+ ALOGV("setParamVideoRemoteIp: %s", remoteIp.string());
+
+ mRemoteIp.setTo(remoteIp.string());
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamRtpRemotePort(int32_t remotePort) {
+ ALOGV("setParamVideoRemotePort: %d", remotePort);
+
+ mRemotePort = remotePort;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamSelfID(int32_t selfID) {
+ ALOGV("setParamSelfID: %x", selfID);
+
+ mSelfID = selfID;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamVideoOpponentID(int32_t opponentID) {
+ mOpponentID = opponentID;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamPayloadType(int32_t payloadType) {
+ ALOGV("setParamPayloadType: %d", payloadType);
+
+ mPayloadType = payloadType;
+
+ if (mStarted && mOutputFormat == OUTPUT_FORMAT_RTP_AVP) {
+ mWriter->updatePayloadType(mPayloadType);
+ }
+
+ return OK;
+}
+
+status_t StagefrightRecorder::setRTPCVOExtMap(int32_t extmap) {
+ ALOGV("setRtpCvoExtMap: %d", extmap);
+
+ mRTPCVOExtMap = extmap;
+ return OK;
+}
+
+status_t StagefrightRecorder::setRTPCVODegrees(int32_t cvoDegrees) {
+ Mutex::Autolock autolock(mLock);
+ ALOGV("setRtpCvoDegrees: %d", cvoDegrees);
+
+ mRTPCVODegrees = cvoDegrees;
+
+ if (mStarted && mOutputFormat == OUTPUT_FORMAT_RTP_AVP) {
+ mWriter->updateCVODegrees(mRTPCVODegrees);
+ }
+
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamRtpDscp(int32_t dscp) {
+ ALOGV("setParamRtpDscp: %d", dscp);
+
+ mRTPSockDscp = dscp;
+ return OK;
+}
+
+status_t StagefrightRecorder::setSocketNetwork(int64_t networkHandle) {
+ ALOGV("setSocketNetwork: %llu", (unsigned long long) networkHandle);
+
+ mRTPSockNetwork = networkHandle;
+ if (mStarted && mOutputFormat == OUTPUT_FORMAT_RTP_AVP) {
+ mWriter->updateSocketNetwork(mRTPSockNetwork);
+ }
+ return OK;
+}
+
+status_t StagefrightRecorder::requestIDRFrame() {
+ status_t ret = BAD_VALUE;
+ if (mVideoEncoderSource != NULL) {
+ ret = mVideoEncoderSource->requestIDRFrame();
+ } else {
+ ALOGV("requestIDRFrame: Encoder not ready");
+ }
+ return ret;
+}
+
status_t StagefrightRecorder::setParameter(
const String8 &key, const String8 &value) {
ALOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
@@ -844,6 +973,11 @@
if (safe_strtoi32(value.string(), &video_bitrate)) {
return setParamVideoEncodingBitRate(video_bitrate);
}
+ } else if (key == "video-param-bitrate-mode") {
+ int32_t video_bitrate_mode;
+ if (safe_strtoi32(value.string(), &video_bitrate_mode)) {
+ return setParamVideoBitRateMode(video_bitrate_mode);
+ }
} else if (key == "video-param-rotation-angle-degrees") {
int32_t degrees;
if (safe_strtoi32(value.string(), °rees)) {
@@ -884,6 +1018,61 @@
if (safe_strtod(value.string(), &fps)) {
return setParamCaptureFps(fps);
}
+ } else if (key == "rtp-param-local-ip") {
+ return setParamRtpLocalIp(value);
+ } else if (key == "rtp-param-local-port") {
+ int32_t localPort;
+ if (safe_strtoi32(value.string(), &localPort)) {
+ return setParamRtpLocalPort(localPort);
+ }
+ } else if (key == "rtp-param-remote-ip") {
+ return setParamRtpRemoteIp(value);
+ } else if (key == "rtp-param-remote-port") {
+ int32_t remotePort;
+ if (safe_strtoi32(value.string(), &remotePort)) {
+ return setParamRtpRemotePort(remotePort);
+ }
+ } else if (key == "rtp-param-self-id") {
+ int32_t selfID;
+ int64_t temp;
+ if (safe_strtoi64(value.string(), &temp)) {
+ selfID = static_cast<int32_t>(temp);
+ return setParamSelfID(selfID);
+ }
+ } else if (key == "rtp-param-opponent-id") {
+ int32_t opnId;
+ int64_t temp;
+ if (safe_strtoi64(value.string(), &temp)) {
+ opnId = static_cast<int32_t>(temp);
+ return setParamVideoOpponentID(opnId);
+ }
+ } else if (key == "rtp-param-payload-type") {
+ int32_t payloadType;
+ if (safe_strtoi32(value.string(), &payloadType)) {
+ return setParamPayloadType(payloadType);
+ }
+ } else if (key == "rtp-param-ext-cvo-extmap") {
+ int32_t extmap;
+ if (safe_strtoi32(value.string(), &extmap)) {
+ return setRTPCVOExtMap(extmap);
+ }
+ } else if (key == "rtp-param-ext-cvo-degrees") {
+ int32_t degrees;
+ if (safe_strtoi32(value.string(), °rees)) {
+ return setRTPCVODegrees(degrees);
+ }
+ } else if (key == "video-param-request-i-frame") {
+ return requestIDRFrame();
+ } else if (key == "rtp-param-set-socket-dscp") {
+ int32_t dscp;
+ if (safe_strtoi32(value.string(), &dscp)) {
+ return setParamRtpDscp(dscp);
+ }
+ } else if (key == "rtp-param-set-socket-network") {
+ int64_t networkHandle;
+ if (safe_strtoi64(value.string(), &networkHandle)) {
+ return setSocketNetwork(networkHandle);
+ }
} else {
ALOGE("setParameter: failed to find key %s", key.string());
}
@@ -1050,6 +1239,17 @@
sp<MetaData> meta = new MetaData;
int64_t startTimeUs = systemTime() / 1000;
meta->setInt64(kKeyTime, startTimeUs);
+ meta->setInt32(kKeySelfID, mSelfID);
+ meta->setInt32(kKeyPayloadType, mPayloadType);
+ meta->setInt64(kKeySocketNetwork, mRTPSockNetwork);
+ if (mRTPCVOExtMap > 0) {
+ meta->setInt32(kKeyRtpExtMap, mRTPCVOExtMap);
+ meta->setInt32(kKeyRtpCvoDegrees, mRTPCVODegrees);
+ }
+ if (mRTPSockDscp > 0) {
+ meta->setInt32(kKeyRtpDscp, mRTPSockDscp);
+ }
+
status = mWriter->start(meta.get());
break;
}
@@ -1330,7 +1530,7 @@
mVideoEncoderSource = source;
}
- mWriter = new ARTPWriter(mOutputFd);
+ mWriter = new ARTPWriter(mOutputFd, mLocalIp, mLocalPort, mRemoteIp, mRemotePort, mLastSeqNo);
mWriter->addSource(source);
mWriter->setListener(mListener);
@@ -1767,6 +1967,10 @@
format->setInt32("stride", stride);
format->setInt32("slice-height", sliceHeight);
format->setInt32("color-format", colorFormat);
+ if (mOutputFormat == OUTPUT_FORMAT_RTP_AVP) {
+ // This indicates that a raw image provided to encoder needs to be rotated.
+ format->setInt32("rotation-degrees", mRotationDegrees);
+ }
} else {
format->setInt32("width", mVideoWidth);
format->setInt32("height", mVideoHeight);
@@ -1785,6 +1989,7 @@
}
format->setInt32("bitrate", mVideoBitRate);
+ format->setInt32("bitrate-mode", mVideoBitRateMode);
format->setInt32("frame-rate", mFrameRate);
format->setInt32("i-frame-interval", mIFramesIntervalSec);
@@ -2011,6 +2216,7 @@
}
if (mOutputFormat == OUTPUT_FORMAT_MPEG_4 || mOutputFormat == OUTPUT_FORMAT_THREE_GPP) {
(*meta)->setInt32(kKeyEmptyTrackMalFormed, true);
+ (*meta)->setInt32(kKey4BitTrackIds, true);
}
}
@@ -2129,6 +2335,7 @@
if (mWriter != NULL) {
err = mWriter->stop();
+ mLastSeqNo = mWriter->getSequenceNum();
mWriter.clear();
}
@@ -2205,6 +2412,8 @@
mVideoHeight = 144;
mFrameRate = -1;
mVideoBitRate = 192000;
+ // Following MediaCodec's default
+ mVideoBitRateMode = BITRATE_MODE_VBR;
mSampleRate = 8000;
mAudioChannels = 1;
mAudioBitRate = 12200;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index a725bee..0362edd 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -119,6 +119,7 @@
int32_t mVideoWidth, mVideoHeight;
int32_t mFrameRate;
int32_t mVideoBitRate;
+ int32_t mVideoBitRateMode;
int32_t mAudioBitRate;
int32_t mAudioChannels;
int32_t mSampleRate;
@@ -138,6 +139,18 @@
int32_t mLongitudex10000;
int32_t mStartTimeOffsetMs;
int32_t mTotalBitRate;
+ String8 mLocalIp;
+ String8 mRemoteIp;
+ int32_t mLocalPort;
+ int32_t mRemotePort;
+ int32_t mSelfID;
+ int32_t mOpponentID;
+ int32_t mPayloadType;
+ int32_t mRTPCVOExtMap;
+ int32_t mRTPCVODegrees;
+ int32_t mRTPSockDscp;
+ int64_t mRTPSockNetwork;
+ uint32_t mLastSeqNo;
int64_t mDurationRecordedUs;
int64_t mStartedRecordingUs;
@@ -205,6 +218,7 @@
status_t setParamCaptureFpsEnable(int32_t timeLapseEnable);
status_t setParamCaptureFps(double fps);
status_t setParamVideoEncodingBitRate(int32_t bitRate);
+ status_t setParamVideoBitRateMode(int32_t bitRateMode);
status_t setParamVideoIFramesInterval(int32_t seconds);
status_t setParamVideoEncoderProfile(int32_t profile);
status_t setParamVideoEncoderLevel(int32_t level);
@@ -219,6 +233,18 @@
status_t setParamMovieTimeScale(int32_t timeScale);
status_t setParamGeoDataLongitude(int64_t longitudex10000);
status_t setParamGeoDataLatitude(int64_t latitudex10000);
+ status_t setParamRtpLocalIp(const String8 &localIp);
+ status_t setParamRtpLocalPort(int32_t localPort);
+ status_t setParamRtpRemoteIp(const String8 &remoteIp);
+ status_t setParamRtpRemotePort(int32_t remotePort);
+ status_t setParamSelfID(int32_t selfID);
+ status_t setParamVideoOpponentID(int32_t opponentID);
+ status_t setParamPayloadType(int32_t payloadType);
+ status_t setRTPCVOExtMap(int32_t extmap);
+ status_t setRTPCVODegrees(int32_t cvoDegrees);
+ status_t setParamRtpDscp(int32_t dscp);
+ status_t setSocketNetwork(int64_t networkHandle);
+ status_t requestIDRFrame();
void clipVideoBitRate();
void clipVideoFrameRate();
void clipVideoFrameWidth();
diff --git a/media/libmediaplayerservice/include/MediaPlayerInterface.h b/media/libmediaplayerservice/include/MediaPlayerInterface.h
index 436cb31..1b5cb4b 100644
--- a/media/libmediaplayerservice/include/MediaPlayerInterface.h
+++ b/media/libmediaplayerservice/include/MediaPlayerInterface.h
@@ -183,6 +183,10 @@
return INVALID_OPERATION;
}
+ virtual status_t setDataSource(const String8& /* rtpParams */) {
+ return INVALID_OPERATION;
+ }
+
// pass the buffered IGraphicBufferProducer to the media player service
virtual status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer) = 0;
diff --git a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
index 684ba2e..7bee002 100644
--- a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
+++ b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
@@ -52,18 +52,19 @@
if (binder == NULL) {
ALOGW("could not get the power manager service");
} else {
- mPowerManager = interface_cast<IPowerManager>(binder);
+ mPowerManager = interface_cast<os::IPowerManager>(binder);
binder->linkToDeath(mDeathRecipient);
}
}
if (mPowerManager != NULL) {
sp<IBinder> binder = new BBinder();
int64_t token = IPCThreadState::self()->clearCallingIdentity();
- status_t status = mPowerManager->acquireWakeLock(
- POWERMANAGER_PARTIAL_WAKE_LOCK,
- binder, String16("AWakeLock"), String16("media"));
+ binder::Status status = mPowerManager->acquireWakeLock(
+ binder, POWERMANAGER_PARTIAL_WAKE_LOCK,
+ String16("AWakeLock"), String16("media"),
+ {} /* workSource */, {} /* historyTag */);
IPCThreadState::self()->restoreCallingIdentity(token);
- if (status == NO_ERROR) {
+ if (status.isOk()) {
mWakeLockToken = binder;
mWakeLockCount++;
return true;
diff --git a/media/libmediaplayerservice/nuplayer/AWakeLock.h b/media/libmediaplayerservice/nuplayer/AWakeLock.h
index 323e7d7..8aa3b41 100644
--- a/media/libmediaplayerservice/nuplayer/AWakeLock.h
+++ b/media/libmediaplayerservice/nuplayer/AWakeLock.h
@@ -18,7 +18,7 @@
#define A_WAKELOCK_H_
#include <media/stagefright/foundation/ABase.h>
-#include <powermanager/IPowerManager.h>
+#include <android/os/IPowerManager.h>
#include <utils/RefBase.h>
namespace android {
@@ -37,7 +37,7 @@
virtual ~AWakeLock();
private:
- sp<IPowerManager> mPowerManager;
+ sp<os::IPowerManager> mPowerManager;
sp<IBinder> mWakeLockToken;
uint32_t mWakeLockCount;
diff --git a/media/libmediaplayerservice/nuplayer/Android.bp b/media/libmediaplayerservice/nuplayer/Android.bp
index 32c97cf..f5e44c7 100644
--- a/media/libmediaplayerservice/nuplayer/Android.bp
+++ b/media/libmediaplayerservice/nuplayer/Android.bp
@@ -14,6 +14,7 @@
"NuPlayerRenderer.cpp",
"NuPlayerStreamListener.cpp",
"RTSPSource.cpp",
+ "RTPSource.cpp",
"StreamingSource.cpp",
],
@@ -30,6 +31,7 @@
"frameworks/av/media/libstagefright/mpeg2ts",
"frameworks/av/media/libstagefright/rtsp",
"frameworks/av/media/libstagefright/timedtext",
+ "frameworks/native/include/android",
],
cflags: [
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/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index c1c4b55..4e7daa5 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -31,6 +31,7 @@
#include "NuPlayerDriver.h"
#include "NuPlayerRenderer.h"
#include "NuPlayerSource.h"
+#include "RTPSource.h"
#include "RTSPSource.h"
#include "StreamingSource.h"
#include "GenericSource.h"
@@ -368,6 +369,18 @@
return err;
}
+void NuPlayer::setDataSourceAsync(const String8& rtpParams) {
+ ALOGD("setDataSourceAsync for RTP = %s", rtpParams.string());
+ sp<AMessage> msg = new AMessage(kWhatSetDataSource, this);
+
+ sp<AMessage> notify = new AMessage(kWhatSourceNotify, this);
+ sp<Source> source = new RTPSource(notify, rtpParams);
+
+ msg->setObject("source", source);
+ msg->post();
+ mDataSourceType = DATA_SOURCE_TYPE_RTP;
+}
+
void NuPlayer::prepareAsync() {
ALOGV("prepareAsync");
@@ -1915,6 +1928,11 @@
format->setInt32("priority", 0 /* realtime */);
+ if (mDataSourceType == DATA_SOURCE_TYPE_RTP) {
+ ALOGV("instantiateDecoder: set decoder error free on stream corrupt.");
+ format->setInt32("corrupt-free", true);
+ }
+
if (!audio) {
AString mime;
CHECK(format->findString("mime", &mime));
@@ -2715,6 +2733,14 @@
break;
}
+ case Source::kWhatIMSRxNotice:
+ {
+ sp<AMessage> IMSRxNotice;
+ CHECK(msg->findMessage("message", &IMSRxNotice));
+ sendIMSRxNotice(IMSRxNotice);
+ break;
+ }
+
default:
TRESPASS();
}
@@ -2817,11 +2843,53 @@
}
}
+void NuPlayer::sendIMSRxNotice(const sp<AMessage> &msg) {
+ int32_t payloadType;
+
+ CHECK(msg->findInt32("payload-type", &payloadType));
+
+ Parcel in;
+ in.writeInt32(payloadType);
+
+ switch (payloadType) {
+ case NuPlayer::RTPSource::RTCP_TSFB: // RTCP TSFB
+ case NuPlayer::RTPSource::RTCP_PSFB: // RTCP PSFB
+ case NuPlayer::RTPSource::RTP_AUTODOWN:
+ {
+ int32_t feedbackType, id;
+ CHECK(msg->findInt32("feedback-type", &feedbackType));
+ CHECK(msg->findInt32("sender", &id));
+ in.writeInt32(feedbackType);
+ in.writeInt32(id);
+ if (payloadType == NuPlayer::RTPSource::RTCP_TSFB) {
+ int32_t bitrate;
+ CHECK(msg->findInt32("bit-rate", &bitrate));
+ in.writeInt32(bitrate);
+ }
+ break;
+ }
+ case NuPlayer::RTPSource::RTP_CVO:
+ {
+ int32_t cvo;
+ CHECK(msg->findInt32("cvo", &cvo));
+ in.writeInt32(cvo);
+ break;
+ }
+ default:
+ break;
+ }
+
+ notifyListener(MEDIA_IMS_RX_NOTICE, 0, 0, &in);
+}
+
const char *NuPlayer::getDataSourceType() {
switch (mDataSourceType) {
case DATA_SOURCE_TYPE_HTTP_LIVE:
return "HTTPLive";
+ case DATA_SOURCE_TYPE_RTP:
+ return "RTP";
+
case DATA_SOURCE_TYPE_RTSP:
return "RTSP";
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index ef4354c..0105248 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -51,6 +51,8 @@
void setDataSourceAsync(const sp<DataSource> &source);
+ void setDataSourceAsync(const String8& rtpParams);
+
status_t getBufferingSettings(BufferingSettings* buffering /* nonnull */);
status_t setBufferingSettings(const BufferingSettings& buffering);
@@ -117,6 +119,7 @@
struct GenericSource;
struct HTTPLiveSource;
struct Renderer;
+ struct RTPSource;
struct RTSPSource;
struct StreamingSource;
struct Action;
@@ -257,6 +260,7 @@
typedef enum {
DATA_SOURCE_TYPE_NONE,
DATA_SOURCE_TYPE_HTTP_LIVE,
+ DATA_SOURCE_TYPE_RTP,
DATA_SOURCE_TYPE_RTSP,
DATA_SOURCE_TYPE_GENERIC_URL,
DATA_SOURCE_TYPE_GENERIC_FD,
@@ -334,6 +338,7 @@
void sendSubtitleData(const sp<ABuffer> &buffer, int32_t baseIndex);
void sendTimedMetaData(const sp<ABuffer> &buffer);
void sendTimedTextData(const sp<ABuffer> &buffer);
+ void sendIMSRxNotice(const sp<AMessage> &msg);
void writeTrackInfo(Parcel* reply, const sp<AMessage>& format) const;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index f734439..8628edc 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -1050,7 +1050,7 @@
uint32_t flags = 0;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
- int32_t eos, csd;
+ int32_t eos, csd, cvo;
// we do not expect SYNCFRAME for decoder
if (buffer->meta()->findInt32("eos", &eos) && eos) {
flags |= MediaCodec::BUFFER_FLAG_EOS;
@@ -1058,6 +1058,24 @@
flags |= MediaCodec::BUFFER_FLAG_CODECCONFIG;
}
+ if (buffer->meta()->findInt32("cvo", (int32_t*)&cvo)) {
+ ALOGV("[%s] cvo(%d) found at %lld us", mComponentName.c_str(), cvo, (long long)timeUs);
+ switch (cvo) {
+ case 0:
+ codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_0);
+ break;
+ case 1:
+ codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_90);
+ break;
+ case 2:
+ codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_180);
+ break;
+ case 3:
+ codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_270);
+ break;
+ }
+ }
+
// Modular DRM
MediaBufferBase *mediaBuf = NULL;
NuPlayerDrm::CryptoInfo *cryptInfo = NULL;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 24afd43..2d82944 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);
}
@@ -215,6 +218,26 @@
return mAsyncResult;
}
+status_t NuPlayerDriver::setDataSource(const String8& rtpParams) {
+ ALOGV("setDataSource(%p) rtp source", this);
+ Mutex::Autolock autoLock(mLock);
+
+ if (mState != STATE_IDLE) {
+ return INVALID_OPERATION;
+ }
+
+ mState = STATE_SET_DATASOURCE_PENDING;
+
+ mPlayer->setDataSourceAsync(rtpParams);
+
+ while (mState == STATE_SET_DATASOURCE_PENDING) {
+ mCondition.wait(mLock);
+ }
+
+ return mAsyncResult;
+}
+
+
status_t NuPlayerDriver::setVideoSurfaceTexture(
const sp<IGraphicBufferProducer> &bufferProducer) {
ALOGV("setVideoSurfaceTexture(%p)", this);
@@ -542,10 +565,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 +653,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 +662,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 +823,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 +1056,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..55a0fad 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -43,6 +43,8 @@
virtual status_t setDataSource(const sp<DataSource>& dataSource);
+ virtual status_t setDataSource(const String8& rtpParams);
+
virtual status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer> &bufferProducer);
@@ -142,6 +144,7 @@
uint32_t mPlayerFlags;
mediametrics::Item *mMetricsItem;
+ mutable Mutex mMetricsLock;
uid_t mClientUid;
bool mAtEOS;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
index f137c52..eb39870 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
@@ -58,6 +58,7 @@
kWhatInstantiateSecureDecoders,
// Modular DRM
kWhatDrmInfo,
+ kWhatIMSRxNotice,
};
// The provides message is used to notify the player about various
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/RTPSource.cpp b/media/libmediaplayerservice/nuplayer/RTPSource.cpp
new file mode 100644
index 0000000..a6601cd
--- /dev/null
+++ b/media/libmediaplayerservice/nuplayer/RTPSource.cpp
@@ -0,0 +1,774 @@
+/*
+ * Copyright (C) 2010 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 "RTPSource"
+#include <utils/Log.h>
+
+#include "RTPSource.h"
+
+
+
+
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <string.h>
+
+namespace android {
+
+const int64_t kNearEOSTimeoutUs = 2000000ll; // 2 secs
+static int32_t kMaxAllowedStaleAccessUnits = 20;
+
+NuPlayer::RTPSource::RTPSource(
+ const sp<AMessage> ¬ify,
+ const String8& rtpParams)
+ : Source(notify),
+ mRTPParams(rtpParams),
+ mFlags(0),
+ mState(DISCONNECTED),
+ mFinalResult(OK),
+ mBuffering(false),
+ mInPreparationPhase(true),
+ mRTPConn(new ARTPConnection(ARTPConnection::kViLTEConnection)),
+ mEOSTimeoutAudio(0),
+ mEOSTimeoutVideo(0),
+ mLastCVOUpdated(-1) {
+ ALOGD("RTPSource initialized with rtpParams=%s", rtpParams.string());
+}
+
+NuPlayer::RTPSource::~RTPSource() {
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(id());
+ mLooper->unregisterHandler(mRTPConn->id());
+ mLooper->stop();
+ }
+}
+
+status_t NuPlayer::RTPSource::getBufferingSettings(
+ BufferingSettings* buffering /* nonnull */) {
+ Mutex::Autolock _l(mBufferingSettingsLock);
+ *buffering = mBufferingSettings;
+ return OK;
+}
+
+status_t NuPlayer::RTPSource::setBufferingSettings(const BufferingSettings& buffering) {
+ Mutex::Autolock _l(mBufferingSettingsLock);
+ mBufferingSettings = buffering;
+ return OK;
+}
+
+void NuPlayer::RTPSource::prepareAsync() {
+ if (mLooper == NULL) {
+ mLooper = new ALooper;
+ mLooper->setName("rtp");
+ mLooper->start();
+
+ mLooper->registerHandler(this);
+ mLooper->registerHandler(mRTPConn);
+ }
+
+ CHECK_EQ(mState, (int)DISCONNECTED);
+ mState = CONNECTING;
+
+ setParameters(mRTPParams);
+
+ TrackInfo *info = NULL;
+ unsigned i;
+ for (i = 0; i < mTracks.size(); i++) {
+ info = &mTracks.editItemAt(i);
+
+ if (info == NULL)
+ break;
+
+ AString sdp;
+ ASessionDescription::SDPStringFactory(sdp, info->mLocalIp,
+ info->mIsAudio, info->mLocalPort, info->mPayloadType, info->mAS, info->mCodecName,
+ NULL, info->mWidth, info->mHeight, info->mCVOExtMap);
+ ALOGD("RTPSource SDP =>\n%s", sdp.c_str());
+
+ sp<ASessionDescription> desc = new ASessionDescription;
+ bool isValidSdp = desc->setTo(sdp.c_str(), sdp.size());
+ ALOGV("RTPSource isValidSdp => %d", isValidSdp);
+
+ int sockRtp, sockRtcp;
+ ARTPConnection::MakeRTPSocketPair(&sockRtp, &sockRtcp, info->mLocalIp, info->mRemoteIp,
+ info->mLocalPort, info->mRemotePort, info->mSocketNetwork);
+
+ sp<AMessage> notify = new AMessage('accu', this);
+
+ ALOGV("RTPSource addStream. track-index=%d", i);
+ notify->setSize("trackIndex", i);
+ // index(i) should be started from 1. 0 is reserved for [root]
+ mRTPConn->addStream(sockRtp, sockRtcp, desc, i + 1, notify, false);
+ mRTPConn->setSelfID(info->mSelfID);
+ mRTPConn->setMinMaxBitrate(kMinVideoBitrate, info->mAS * 1000 /* kbps */);
+
+ info->mRTPSocket = sockRtp;
+ info->mRTCPSocket = sockRtcp;
+ info->mFirstSeqNumInSegment = 0;
+ info->mNewSegment = true;
+ info->mAllowedStaleAccessUnits = kMaxAllowedStaleAccessUnits;
+ info->mRTPAnchor = 0;
+ info->mNTPAnchorUs = -1;
+ info->mNormalPlayTimeRTP = 0;
+ info->mNormalPlayTimeUs = 0ll;
+
+ // index(i) should be started from 1. 0 is reserved for [root]
+ info->mPacketSource = new APacketSource(desc, i + 1);
+
+ int32_t timeScale;
+ sp<MetaData> format = getTrackFormat(i, &timeScale);
+ sp<AnotherPacketSource> source = new AnotherPacketSource(format);
+
+ if (info->mIsAudio) {
+ mAudioTrack = source;
+ } else {
+ mVideoTrack = source;
+ }
+
+ info->mSource = source;
+ }
+
+ if (mInPreparationPhase) {
+ mInPreparationPhase = false;
+ notifyPrepared();
+ }
+}
+
+void NuPlayer::RTPSource::start() {
+}
+
+void NuPlayer::RTPSource::pause() {
+ mState = PAUSED;
+}
+
+void NuPlayer::RTPSource::resume() {
+ mState = CONNECTING;
+}
+
+void NuPlayer::RTPSource::stop() {
+ if (mLooper == NULL) {
+ return;
+ }
+ sp<AMessage> msg = new AMessage(kWhatDisconnect, this);
+
+ sp<AMessage> dummy;
+ msg->postAndAwaitResponse(&dummy);
+}
+
+status_t NuPlayer::RTPSource::feedMoreTSData() {
+ Mutex::Autolock _l(mBufferingLock);
+ return mFinalResult;
+}
+
+sp<MetaData> NuPlayer::RTPSource::getFormatMeta(bool audio) {
+ sp<AnotherPacketSource> source = getSource(audio);
+
+ if (source == NULL) {
+ return NULL;
+ }
+
+ return source->getFormat();
+}
+
+bool NuPlayer::RTPSource::haveSufficientDataOnAllTracks() {
+ // We're going to buffer at least 2 secs worth data on all tracks before
+ // starting playback (both at startup and after a seek).
+
+ static const int64_t kMinDurationUs = 2000000ll;
+
+ int64_t mediaDurationUs = 0;
+ getDuration(&mediaDurationUs);
+ if ((mAudioTrack != NULL && mAudioTrack->isFinished(mediaDurationUs))
+ || (mVideoTrack != NULL && mVideoTrack->isFinished(mediaDurationUs))) {
+ return true;
+ }
+
+ status_t err;
+ int64_t durationUs;
+ if (mAudioTrack != NULL
+ && (durationUs = mAudioTrack->getBufferedDurationUs(&err))
+ < kMinDurationUs
+ && err == OK) {
+ ALOGV("audio track doesn't have enough data yet. (%.2f secs buffered)",
+ durationUs / 1E6);
+ return false;
+ }
+
+ if (mVideoTrack != NULL
+ && (durationUs = mVideoTrack->getBufferedDurationUs(&err))
+ < kMinDurationUs
+ && err == OK) {
+ ALOGV("video track doesn't have enough data yet. (%.2f secs buffered)",
+ durationUs / 1E6);
+ return false;
+ }
+
+ return true;
+}
+
+status_t NuPlayer::RTPSource::dequeueAccessUnit(
+ bool audio, sp<ABuffer> *accessUnit) {
+
+ sp<AnotherPacketSource> source = getSource(audio);
+
+ if (mState == PAUSED) {
+ ALOGV("-EWOULDBLOCK");
+ return -EWOULDBLOCK;
+ }
+
+ status_t finalResult;
+ if (!source->hasBufferAvailable(&finalResult)) {
+ if (finalResult == OK) {
+ int64_t mediaDurationUs = 0;
+ getDuration(&mediaDurationUs);
+ sp<AnotherPacketSource> otherSource = getSource(!audio);
+ status_t otherFinalResult;
+
+ // If other source already signaled EOS, this source should also signal EOS
+ if (otherSource != NULL &&
+ !otherSource->hasBufferAvailable(&otherFinalResult) &&
+ otherFinalResult == ERROR_END_OF_STREAM) {
+ source->signalEOS(ERROR_END_OF_STREAM);
+ return ERROR_END_OF_STREAM;
+ }
+
+ // If this source has detected near end, give it some time to retrieve more
+ // data before signaling EOS
+ if (source->isFinished(mediaDurationUs)) {
+ int64_t eosTimeout = audio ? mEOSTimeoutAudio : mEOSTimeoutVideo;
+ if (eosTimeout == 0) {
+ setEOSTimeout(audio, ALooper::GetNowUs());
+ } else if ((ALooper::GetNowUs() - eosTimeout) > kNearEOSTimeoutUs) {
+ setEOSTimeout(audio, 0);
+ source->signalEOS(ERROR_END_OF_STREAM);
+ return ERROR_END_OF_STREAM;
+ }
+ return -EWOULDBLOCK;
+ }
+
+ if (!(otherSource != NULL && otherSource->isFinished(mediaDurationUs))) {
+ // We should not enter buffering mode
+ // if any of the sources already have detected EOS.
+ // TODO: needs to be checked whether below line is needed or not.
+ // startBufferingIfNecessary();
+ }
+
+ return -EWOULDBLOCK;
+ }
+ return finalResult;
+ }
+
+ setEOSTimeout(audio, 0);
+
+ finalResult = source->dequeueAccessUnit(accessUnit);
+ if (finalResult != OK) {
+ return finalResult;
+ }
+
+ int32_t cvo;
+ if ((*accessUnit) != NULL && (*accessUnit)->meta()->findInt32("cvo", &cvo)) {
+ if (cvo != mLastCVOUpdated) {
+ sp<AMessage> msg = new AMessage();
+ msg->setInt32("payload-type", NuPlayer::RTPSource::RTP_CVO);
+ msg->setInt32("cvo", cvo);
+
+ sp<AMessage> notify = dupNotify();
+ notify->setInt32("what", kWhatIMSRxNotice);
+ notify->setMessage("message", msg);
+ notify->post();
+
+ ALOGV("notify cvo updated (%d)->(%d) to upper layer", mLastCVOUpdated, cvo);
+ mLastCVOUpdated = cvo;
+ }
+ }
+
+ return finalResult;
+}
+
+sp<AnotherPacketSource> NuPlayer::RTPSource::getSource(bool audio) {
+ return audio ? mAudioTrack : mVideoTrack;
+}
+
+void NuPlayer::RTPSource::setEOSTimeout(bool audio, int64_t timeout) {
+ if (audio) {
+ mEOSTimeoutAudio = timeout;
+ } else {
+ mEOSTimeoutVideo = timeout;
+ }
+}
+
+status_t NuPlayer::RTPSource::getDuration(int64_t *durationUs) {
+ *durationUs = 0ll;
+
+ int64_t audioDurationUs;
+ if (mAudioTrack != NULL
+ && mAudioTrack->getFormat()->findInt64(
+ kKeyDuration, &audioDurationUs)
+ && audioDurationUs > *durationUs) {
+ *durationUs = audioDurationUs;
+ }
+
+ int64_t videoDurationUs;
+ if (mVideoTrack != NULL
+ && mVideoTrack->getFormat()->findInt64(
+ kKeyDuration, &videoDurationUs)
+ && videoDurationUs > *durationUs) {
+ *durationUs = videoDurationUs;
+ }
+
+ return OK;
+}
+
+status_t NuPlayer::RTPSource::seekTo(int64_t seekTimeUs, MediaPlayerSeekMode mode) {
+ ALOGV("RTPSource::seekTo=%d, mode=%d", (int)seekTimeUs, mode);
+ return OK;
+}
+
+void NuPlayer::RTPSource::schedulePollBuffering() {
+ sp<AMessage> msg = new AMessage(kWhatPollBuffering, this);
+ msg->post(kBufferingPollIntervalUs); // 1 second intervals
+}
+
+void NuPlayer::RTPSource::onPollBuffering() {
+ schedulePollBuffering();
+}
+
+void NuPlayer::RTPSource::onMessageReceived(const sp<AMessage> &msg) {
+ ALOGV("onMessageReceived =%d", msg->what());
+
+ switch (msg->what()) {
+ case kWhatAccessUnitComplete:
+ {
+ if (mState == CONNECTING) {
+ mState = CONNECTED;
+ }
+
+ int32_t timeUpdate;
+ //"time-update" raised from ARTPConnection::parseSR()
+ if (msg->findInt32("time-update", &timeUpdate) && timeUpdate) {
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ uint32_t rtpTime;
+ uint64_t ntpTime;
+ CHECK(msg->findInt32("rtp-time", (int32_t *)&rtpTime));
+ CHECK(msg->findInt64("ntp-time", (int64_t *)&ntpTime));
+
+ onTimeUpdate(trackIndex, rtpTime, ntpTime);
+ break;
+ }
+
+ int32_t firstRTCP;
+ if (msg->findInt32("first-rtcp", &firstRTCP)) {
+ // There won't be an access unit here, it's just a notification
+ // that the data communication worked since we got the first
+ // rtcp packet.
+ ALOGV("first-rtcp");
+ break;
+ }
+
+ int32_t IMSRxNotice;
+ if (msg->findInt32("rtcp-event", &IMSRxNotice)) {
+ int32_t payloadType, feedbackType;
+ CHECK(msg->findInt32("payload-type", &payloadType));
+ CHECK(msg->findInt32("feedback-type", &feedbackType));
+
+ sp<AMessage> notify = dupNotify();
+ notify->setInt32("what", kWhatIMSRxNotice);
+ notify->setMessage("message", msg);
+ notify->post();
+
+ ALOGV("IMSRxNotice \t\t payload : %d feedback : %d",
+ payloadType, feedbackType);
+ break;
+ }
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ sp<ABuffer> accessUnit;
+ if (msg->findBuffer("access-unit", &accessUnit) == false) {
+ break;
+ }
+
+ int32_t damaged;
+ if (accessUnit->meta()->findInt32("damaged", &damaged)
+ && damaged) {
+ ALOGD("dropping damaged access unit.");
+ break;
+ }
+
+ // Implicitly assert on valid trackIndex here, which we ensure by
+ // never removing tracks.
+ TrackInfo *info = &mTracks.editItemAt(trackIndex);
+
+ sp<AnotherPacketSource> source = info->mSource;
+ if (source != NULL) {
+ uint32_t rtpTime;
+ CHECK(accessUnit->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ /* AnotherPacketSource make an assertion if there is no ntp provided
+ RTPSource should provide ntpUs all the times.
+ if (!info->mNPTMappingValid) {
+ // This is a live stream, we didn't receive any normal
+ // playtime mapping. We won't map to npt time.
+ source->queueAccessUnit(accessUnit);
+ break;
+ }
+ */
+
+ int64_t nptUs =
+ ((double)rtpTime - (double)info->mRTPTime)
+ / info->mTimeScale
+ * 1000000ll
+ + info->mNormalPlaytimeUs;
+
+ accessUnit->meta()->setInt64("timeUs", nptUs);
+
+ source->queueAccessUnit(accessUnit);
+ }
+
+ break;
+ }
+ case kWhatDisconnect:
+ {
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *info = &mTracks.editItemAt(i);
+
+ if (info->mIsAudio) {
+ mAudioTrack->signalEOS(ERROR_END_OF_STREAM);
+ mAudioTrack = NULL;
+ ALOGV("mAudioTrack disconnected");
+ } else {
+ mVideoTrack->signalEOS(ERROR_END_OF_STREAM);
+ mVideoTrack = NULL;
+ ALOGV("mVideoTrack disconnected");
+ }
+
+ mRTPConn->removeStream(info->mRTPSocket, info->mRTCPSocket);
+ close(info->mRTPSocket);
+ close(info->mRTCPSocket);
+ }
+
+ mTracks.clear();
+ mFirstAccessUnit = true;
+ mAllTracksHaveTime = false;
+ mNTPAnchorUs = -1;
+ mMediaAnchorUs = -1;
+ mLastMediaTimeUs = -1;
+ mNumAccessUnitsReceived = 0;
+ mReceivedFirstRTCPPacket = false;
+ mReceivedFirstRTPPacket = false;
+ mPausing = false;
+ mPauseGeneration = 0;
+
+ (new AMessage)->postReply(replyID);
+
+ break;
+ }
+ case kWhatPollBuffering:
+ break;
+ default:
+ TRESPASS();
+ }
+}
+
+void NuPlayer::RTPSource::onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime) {
+ ALOGV("onTimeUpdate track %d, rtpTime = 0x%08x, ntpTime = %#016llx",
+ trackIndex, rtpTime, (long long)ntpTime);
+
+ // convert ntpTime in Q32 seconds to microseconds. Note: this will not lose precision
+ // because ntpTimeUs is at most 52 bits (double holds 53 bits)
+ int64_t ntpTimeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+
+ TrackInfo *track = &mTracks.editItemAt(trackIndex);
+
+ track->mRTPAnchor = rtpTime;
+ track->mNTPAnchorUs = ntpTimeUs;
+
+ if (mNTPAnchorUs < 0) {
+ mNTPAnchorUs = ntpTimeUs;
+ mMediaAnchorUs = mLastMediaTimeUs;
+ }
+
+ if (!mAllTracksHaveTime) {
+ bool allTracksHaveTime = (mTracks.size() > 0);
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *track = &mTracks.editItemAt(i);
+ if (track->mNTPAnchorUs < 0) {
+ allTracksHaveTime = false;
+ break;
+ }
+ }
+ if (allTracksHaveTime) {
+ mAllTracksHaveTime = true;
+ ALOGI("Time now established for all tracks.");
+ }
+ }
+ if (mAllTracksHaveTime && dataReceivedOnAllChannels()) {
+ // Time is now established, lets start timestamping immediately
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *trackInfo = &mTracks.editItemAt(i);
+ while (!trackInfo->mPackets.empty()) {
+ sp<ABuffer> accessUnit = *trackInfo->mPackets.begin();
+ trackInfo->mPackets.erase(trackInfo->mPackets.begin());
+
+ if (addMediaTimestamp(i, trackInfo, accessUnit)) {
+ postQueueAccessUnit(i, accessUnit);
+ }
+ }
+ }
+ }
+}
+
+bool NuPlayer::RTPSource::addMediaTimestamp(
+ int32_t trackIndex, const TrackInfo *track,
+ const sp<ABuffer> &accessUnit) {
+
+ uint32_t rtpTime;
+ CHECK(accessUnit->meta()->findInt32(
+ "rtp-time", (int32_t *)&rtpTime));
+
+ int64_t relRtpTimeUs =
+ (((int64_t)rtpTime - (int64_t)track->mRTPAnchor) * 1000000ll)
+ / track->mTimeScale;
+
+ int64_t ntpTimeUs = track->mNTPAnchorUs + relRtpTimeUs;
+
+ int64_t mediaTimeUs = mMediaAnchorUs + ntpTimeUs - mNTPAnchorUs;
+
+ if (mediaTimeUs > mLastMediaTimeUs) {
+ mLastMediaTimeUs = mediaTimeUs;
+ }
+
+ if (mediaTimeUs < 0) {
+ ALOGV("dropping early accessUnit.");
+ return false;
+ }
+
+ ALOGV("track %d rtpTime=%u mediaTimeUs = %lld us (%.2f secs)",
+ trackIndex, rtpTime, (long long)mediaTimeUs, mediaTimeUs / 1E6);
+
+ accessUnit->meta()->setInt64("timeUs", mediaTimeUs);
+
+ return true;
+}
+
+bool NuPlayer::RTPSource::dataReceivedOnAllChannels() {
+ TrackInfo *track;
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ track = &mTracks.editItemAt(i);
+ if (track->mPackets.empty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void NuPlayer::RTPSource::postQueueAccessUnit(
+ size_t trackIndex, const sp<ABuffer> &accessUnit) {
+ sp<AMessage> msg = new AMessage(kWhatAccessUnit, this);
+ msg->setInt32("what", kWhatAccessUnit);
+ msg->setSize("trackIndex", trackIndex);
+ msg->setBuffer("accessUnit", accessUnit);
+ msg->post();
+}
+
+void NuPlayer::RTPSource::postQueueEOS(size_t trackIndex, status_t finalResult) {
+ sp<AMessage> msg = new AMessage(kWhatEOS, this);
+ msg->setInt32("what", kWhatEOS);
+ msg->setSize("trackIndex", trackIndex);
+ msg->setInt32("finalResult", finalResult);
+ msg->post();
+}
+
+sp<MetaData> NuPlayer::RTPSource::getTrackFormat(size_t index, int32_t *timeScale) {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ const TrackInfo &info = mTracks.itemAt(index);
+
+ *timeScale = info.mTimeScale;
+
+ return info.mPacketSource->getFormat();
+}
+
+void NuPlayer::RTPSource::onConnected() {
+ ALOGV("onConnected");
+ mState = CONNECTED;
+}
+
+void NuPlayer::RTPSource::onDisconnected(const sp<AMessage> &msg) {
+ if (mState == DISCONNECTED) {
+ return;
+ }
+
+ status_t err;
+ CHECK(msg->findInt32("result", &err));
+ CHECK_NE(err, (status_t)OK);
+
+// mLooper->unregisterHandler(mHandler->id());
+// mHandler.clear();
+
+ if (mState == CONNECTING) {
+ // We're still in the preparation phase, signal that it
+ // failed.
+ notifyPrepared(err);
+ }
+
+ mState = DISCONNECTED;
+// setError(err);
+
+}
+
+status_t NuPlayer::RTPSource::setParameter(const String8 &key, const String8 &value) {
+ ALOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
+
+ bool isAudioKey = key.contains("audio");
+ TrackInfo *info = NULL;
+ for (unsigned i = 0; i < mTracks.size(); ++i) {
+ info = &mTracks.editItemAt(i);
+ if (info != NULL && info->mIsAudio == isAudioKey) {
+ ALOGV("setParameter: %s track (%d) found", isAudioKey ? "audio" : "video" , i);
+ break;
+ }
+ }
+
+ if (info == NULL) {
+ TrackInfo newTrackInfo;
+ newTrackInfo.mIsAudio = isAudioKey;
+ mTracks.push(newTrackInfo);
+ info = &mTracks.editTop();
+ }
+
+ if (key == "rtp-param-mime-type") {
+ info->mMimeType = value;
+
+ const char *mime = value.string();
+ const char *delimiter = strchr(mime, '/');
+ info->mCodecName = delimiter ? (delimiter + 1) : "<none>";
+
+ ALOGV("rtp-param-mime-type: mMimeType (%s) => mCodecName (%s)",
+ info->mMimeType.string(), info->mCodecName.string());
+ } else if (key == "video-param-decoder-profile") {
+ info->mCodecProfile = atoi(value);
+ } else if (key == "video-param-decoder-level") {
+ info->mCodecLevel = atoi(value);
+ } else if (key == "video-param-width") {
+ info->mWidth = atoi(value);
+ } else if (key == "video-param-height") {
+ info->mHeight = atoi(value);
+ } else if (key == "rtp-param-local-ip") {
+ info->mLocalIp = value;
+ } else if (key == "rtp-param-local-port") {
+ info->mLocalPort = atoi(value);
+ } else if (key == "rtp-param-remote-ip") {
+ info->mRemoteIp = value;
+ } else if (key == "rtp-param-remote-port") {
+ info->mRemotePort = atoi(value);
+ } else if (key == "rtp-param-payload-type") {
+ info->mPayloadType = atoi(value);
+ } else if (key == "rtp-param-as") {
+ //AS means guaranteed bit rate that negotiated from sdp.
+ info->mAS = atoi(value);
+ } else if (key == "rtp-param-rtp-timeout") {
+ } else if (key == "rtp-param-rtcp-timeout") {
+ } else if (key == "rtp-param-time-scale") {
+ } else if (key == "rtp-param-self-id") {
+ info->mSelfID = atoi(value);
+ } else if (key == "rtp-param-ext-cvo-extmap") {
+ info->mCVOExtMap = atoi(value);
+ } else if (key == "rtp-param-set-socket-network") {
+ int64_t networkHandle = atoll(value);
+ setSocketNetwork(networkHandle);
+ }
+
+ return OK;
+}
+
+status_t NuPlayer::RTPSource::setParameters(const String8 ¶ms) {
+ ALOGV("setParameters: %s", params.string());
+ const char *cparams = params.string();
+ const char *key_start = cparams;
+ for (;;) {
+ const char *equal_pos = strchr(key_start, '=');
+ if (equal_pos == NULL) {
+ ALOGE("Parameters %s miss a value", cparams);
+ return BAD_VALUE;
+ }
+ String8 key(key_start, equal_pos - key_start);
+ TrimString(&key);
+ if (key.length() == 0) {
+ ALOGE("Parameters %s contains an empty key", cparams);
+ return BAD_VALUE;
+ }
+ const char *value_start = equal_pos + 1;
+ const char *semicolon_pos = strchr(value_start, ';');
+ String8 value;
+ if (semicolon_pos == NULL) {
+ value.setTo(value_start);
+ } else {
+ value.setTo(value_start, semicolon_pos - value_start);
+ }
+ if (setParameter(key, value) != OK) {
+ return BAD_VALUE;
+ }
+ if (semicolon_pos == NULL) {
+ break; // Reaches the end
+ }
+ key_start = semicolon_pos + 1;
+ }
+ return OK;
+}
+
+void NuPlayer::RTPSource::setSocketNetwork(int64_t networkHandle) {
+ ALOGV("setSocketNetwork: %llu", (unsigned long long)networkHandle);
+
+ TrackInfo *info = NULL;
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ info = &mTracks.editItemAt(i);
+
+ if (info == NULL)
+ break;
+
+ info->mSocketNetwork = networkHandle;
+ }
+}
+
+// Trim both leading and trailing whitespace from the given string.
+//static
+void NuPlayer::RTPSource::TrimString(String8 *s) {
+ size_t num_bytes = s->bytes();
+ const char *data = s->string();
+
+ size_t leading_space = 0;
+ while (leading_space < num_bytes && isspace(data[leading_space])) {
+ ++leading_space;
+ }
+
+ size_t i = num_bytes;
+ while (i > leading_space && isspace(data[i - 1])) {
+ --i;
+ }
+
+ s->setTo(String8(&data[leading_space], i - leading_space));
+}
+
+} // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/RTPSource.h b/media/libmediaplayerservice/nuplayer/RTPSource.h
new file mode 100644
index 0000000..5085a7e
--- /dev/null
+++ b/media/libmediaplayerservice/nuplayer/RTPSource.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2010 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 RTP_SOURCE_H_
+
+#define RTP_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/Utils.h>
+#include <media/BufferingSettings.h>
+
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+#include <utils/RefBase.h>
+
+#include "AnotherPacketSource.h"
+#include "APacketSource.h"
+#include "ARTPConnection.h"
+#include "ASessionDescription.h"
+#include "NuPlayerSource.h"
+
+
+
+
+
+
+namespace android {
+
+struct ALooper;
+struct AnotherPacketSource;
+
+struct NuPlayer::RTPSource : public NuPlayer::Source {
+ RTPSource(
+ const sp<AMessage> ¬ify,
+ const String8& rtpParams);
+
+ enum {
+ RTCP_TSFB = 205,
+ RTCP_PSFB = 206,
+ RTP_CVO = 300,
+ RTP_AUTODOWN = 400,
+ };
+
+ virtual status_t getBufferingSettings(
+ BufferingSettings* buffering /* nonnull */) override;
+ virtual status_t setBufferingSettings(const BufferingSettings& buffering) override;
+
+ virtual void prepareAsync();
+ virtual void start();
+ virtual void stop();
+ virtual void pause();
+ virtual void resume();
+
+ virtual status_t feedMoreTSData();
+
+ virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+
+ virtual status_t getDuration(int64_t *durationUs);
+ virtual status_t seekTo(
+ int64_t seekTimeUs,
+ MediaPlayerSeekMode mode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC) override;
+
+ void onMessageReceived(const sp<AMessage> &msg);
+
+protected:
+ virtual ~RTPSource();
+
+ virtual sp<MetaData> getFormatMeta(bool audio);
+
+private:
+ enum {
+ kWhatAccessUnit = 'accU',
+ kWhatAccessUnitComplete = 'accu',
+ kWhatDisconnect = 'disc',
+ kWhatEOS = 'eos!',
+ kWhatPollBuffering = 'poll',
+ kWhatSetBufferingSettings = 'sBuS',
+ };
+
+ const int64_t kBufferingPollIntervalUs = 1000000ll;
+ const int32_t kMinVideoBitrate = 192000; /* bps */
+
+ enum State {
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED,
+ PAUSED,
+ };
+
+ struct TrackInfo {
+
+ /* SDP of track */
+ bool mIsAudio;
+ int32_t mPayloadType;
+ String8 mMimeType;
+ String8 mCodecName;
+ int32_t mCodecProfile;
+ int32_t mCodecLevel;
+ int32_t mWidth;
+ int32_t mHeight;
+ String8 mLocalIp;
+ String8 mRemoteIp;
+ int32_t mLocalPort;
+ int32_t mRemotePort;
+ int64_t mSocketNetwork;
+ int32_t mTimeScale;
+ int32_t mAS;
+
+ /* Unique ID indicates itself */
+ uint32_t mSelfID;
+ /* extmap:<value> for CVO will be set to here */
+ int32_t mCVOExtMap;
+
+ /* a copy of TrackInfo in RTSPSource */
+ sp<AnotherPacketSource> mSource;
+ uint32_t mRTPTime;
+ int64_t mNormalPlaytimeUs;
+ bool mNPTMappingValid;
+
+ /* a copy of TrackInfo in MyHandler.h */
+ int mRTPSocket;
+ int mRTCPSocket;
+ uint32_t mFirstSeqNumInSegment;
+ bool mNewSegment;
+ int32_t mAllowedStaleAccessUnits;
+ uint32_t mRTPAnchor;
+ int64_t mNTPAnchorUs;
+ bool mEOSReceived;
+ uint32_t mNormalPlayTimeRTP;
+ int64_t mNormalPlayTimeUs;
+ sp<APacketSource> mPacketSource;
+ List<sp<ABuffer>> mPackets;
+ };
+
+ const String8 mRTPParams;
+ uint32_t mFlags;
+ State mState;
+ status_t mFinalResult;
+
+ // below 3 parameters need to be checked whether it needed or not.
+ Mutex mBufferingLock;
+ bool mBuffering;
+ bool mInPreparationPhase;
+ Mutex mBufferingSettingsLock;
+ BufferingSettings mBufferingSettings;
+
+ sp<ALooper> mLooper;
+
+ sp<ARTPConnection> mRTPConn;
+
+ Vector<TrackInfo> mTracks;
+ sp<AnotherPacketSource> mAudioTrack;
+ sp<AnotherPacketSource> mVideoTrack;
+
+ int64_t mEOSTimeoutAudio;
+ int64_t mEOSTimeoutVideo;
+
+ /* MyHandler.h */
+ bool mFirstAccessUnit;
+ bool mAllTracksHaveTime;
+ int64_t mNTPAnchorUs;
+ int64_t mMediaAnchorUs;
+ int64_t mLastMediaTimeUs;
+ int64_t mNumAccessUnitsReceived;
+ int32_t mLastCVOUpdated;
+ bool mReceivedFirstRTCPPacket;
+ bool mReceivedFirstRTPPacket;
+ bool mPausing;
+ int32_t mPauseGeneration;
+
+ sp<AnotherPacketSource> getSource(bool audio);
+
+ /* MyHandler.h */
+ void onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime);
+ bool addMediaTimestamp(int32_t trackIndex, const TrackInfo *track,
+ const sp<ABuffer> &accessUnit);
+ bool dataReceivedOnAllChannels();
+ void postQueueAccessUnit(size_t trackIndex, const sp<ABuffer> &accessUnit);
+ void postQueueEOS(size_t trackIndex, status_t finalResult);
+ sp<MetaData> getTrackFormat(size_t index, int32_t *timeScale);
+ void onConnected();
+ void onDisconnected(const sp<AMessage> &msg);
+
+ void schedulePollBuffering();
+ void onPollBuffering();
+
+ bool haveSufficientDataOnAllTracks();
+
+ void setEOSTimeout(bool audio, int64_t timeout);
+
+ status_t setParameters(const String8 ¶ms);
+ status_t setParameter(const String8 &key, const String8 &value);
+ void setSocketNetwork(int64_t networkHandle);
+ static void TrimString(String8 *s);
+
+ DISALLOW_EVIL_CONSTRUCTORS(RTPSource);
+};
+
+} // namespace android
+
+#endif // RTP_SOURCE_H_
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/libmediatranscoding/.clang-format b/media/libmediatranscoding/.clang-format
index 3198d00..f23b842 100644
--- a/media/libmediatranscoding/.clang-format
+++ b/media/libmediatranscoding/.clang-format
@@ -26,4 +26,26 @@
DerivePointerAlignment: false
IndentWidth: 4
PointerAlignment: Left
-TabWidth: 4
\ No newline at end of file
+TabWidth: 4
+
+# Deviations from the above file:
+# "Don't indent the section label"
+AccessModifierOffset: -4
+# "Each line of text in your code should be at most 100 columns long."
+ColumnLimit: 100
+# "Constructor initializer lists can be all on one line or with subsequent
+# lines indented eight spaces.". clang-format does not support having the colon
+# on the same line as the constructor function name, so this is the best
+# approximation of that rule, which makes all entries in the list (except the
+# first one) have an eight space indentation.
+ConstructorInitializerIndentWidth: 6
+# There is nothing in go/droidcppstyle about case labels, but there seems to be
+# more code that does not indent the case labels in frameworks/base.
+IndentCaseLabels: false
+# There have been some bugs in which subsequent formatting operations introduce
+# weird comment jumps.
+ReflowComments: false
+# Android supports C++17 now, but it seems only Cpp11 will work now.
+# "Cpp11 is a deprecated alias for Latest" according to
+# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+Standard: Cpp11
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index f948bd8..29ed65a 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -21,22 +21,35 @@
local_include_dir: "aidl",
srcs: [
"aidl/android/media/IMediaTranscodingService.aidl",
- "aidl/android/media/ITranscodingServiceClient.aidl",
+ "aidl/android/media/ITranscodingClient.aidl",
+ "aidl/android/media/ITranscodingClientCallback.aidl",
"aidl/android/media/TranscodingErrorCode.aidl",
"aidl/android/media/TranscodingJobPriority.aidl",
+ "aidl/android/media/TranscodingJobStats.aidl",
"aidl/android/media/TranscodingType.aidl",
"aidl/android/media/TranscodingVideoCodecType.aidl",
+ "aidl/android/media/TranscodingVideoTrackFormat.aidl",
"aidl/android/media/TranscodingJobParcel.aidl",
"aidl/android/media/TranscodingRequestParcel.aidl",
"aidl/android/media/TranscodingResultParcel.aidl",
+ "aidl/android/media/TranscodingTestConfig.aidl",
],
+ backend:
+ {
+ java: {
+ enabled: true,
+ },
+ },
}
cc_library_shared {
name: "libmediatranscoding",
srcs: [
- "TranscodingClientManager.cpp"
+ "TranscodingClientManager.cpp",
+ "TranscodingJobScheduler.cpp",
+ "TranscodingUidPolicy.cpp",
+ "TranscoderWrapper.cpp",
],
shared_libs: [
@@ -44,6 +57,9 @@
"libcutils",
"liblog",
"libutils",
+ "libmediatranscoder",
+ "libbinder",
+ "libmediandk",
],
export_include_dirs: ["include"],
@@ -53,9 +69,13 @@
],
cflags: [
- "-Werror",
- "-Wno-error=deprecated-declarations",
"-Wall",
+ "-Werror",
+ "-Wformat",
+ "-Wno-error=deprecated-declarations",
+ "-Wthread-safety",
+ "-Wunused",
+ "-Wunreachable-code",
],
sanitize: {
diff --git a/media/libmediatranscoding/OWNERS b/media/libmediatranscoding/OWNERS
index 02287cb..b08d573 100644
--- a/media/libmediatranscoding/OWNERS
+++ b/media/libmediatranscoding/OWNERS
@@ -1,3 +1,5 @@
-akersten@google.com
+chz@google.com
+gokrishnan@google.com
hkuang@google.com
lnilsson@google.com
+pawin@google.com
diff --git a/media/libmediatranscoding/TranscoderWrapper.cpp b/media/libmediatranscoding/TranscoderWrapper.cpp
new file mode 100644
index 0000000..bd03671
--- /dev/null
+++ b/media/libmediatranscoding/TranscoderWrapper.cpp
@@ -0,0 +1,469 @@
+/*
+ * 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 "TranscoderWrapper"
+
+#include <aidl/android/media/TranscodingErrorCode.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
+#include <media/MediaTranscoder.h>
+#include <media/NdkCommon.h>
+#include <media/TranscoderWrapper.h>
+#include <utils/Log.h>
+
+#include <thread>
+
+namespace android {
+using Status = ::ndk::ScopedAStatus;
+using ::aidl::android::media::TranscodingErrorCode;
+using ::aidl::android::media::TranscodingVideoCodecType;
+using ::aidl::android::media::TranscodingVideoTrackFormat;
+
+static TranscodingErrorCode toTranscodingError(media_status_t status) {
+ switch (status) {
+ case AMEDIA_OK:
+ return TranscodingErrorCode::kNoError;
+ case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE: // FALLTHRU
+ case AMEDIACODEC_ERROR_RECLAIMED:
+ return TranscodingErrorCode::kInsufficientResources;
+ case AMEDIA_ERROR_MALFORMED:
+ return TranscodingErrorCode::kMalformed;
+ case AMEDIA_ERROR_UNSUPPORTED:
+ return TranscodingErrorCode::kUnsupported;
+ case AMEDIA_ERROR_INVALID_OBJECT: // FALLTHRU
+ case AMEDIA_ERROR_INVALID_PARAMETER:
+ return TranscodingErrorCode::kInvalidParameter;
+ case AMEDIA_ERROR_INVALID_OPERATION:
+ return TranscodingErrorCode::kInvalidOperation;
+ case AMEDIA_ERROR_IO:
+ return TranscodingErrorCode::kErrorIO;
+ case AMEDIA_ERROR_UNKNOWN: // FALLTHRU
+ default:
+ return TranscodingErrorCode::kUnknown;
+ }
+}
+
+static AMediaFormat* getVideoFormat(
+ const char* originalMime,
+ const std::optional<TranscodingVideoTrackFormat>& requestedFormat) {
+ if (requestedFormat == std::nullopt) {
+ return nullptr;
+ }
+
+ AMediaFormat* format = AMediaFormat_new();
+ bool changed = false;
+ if (requestedFormat->codecType == TranscodingVideoCodecType::kHevc &&
+ strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_HEVC)) {
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
+ changed = true;
+ } else if (requestedFormat->codecType == TranscodingVideoCodecType::kAvc &&
+ strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_AVC)) {
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
+ changed = true;
+ }
+ if (requestedFormat->bitrateBps > 0) {
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, requestedFormat->bitrateBps);
+ changed = true;
+ }
+ // TODO: translate other fields from requestedFormat to the format for MediaTranscoder.
+ // Also need to determine more settings to expose in TranscodingVideoTrackFormat.
+ if (!changed) {
+ AMediaFormat_delete(format);
+ // Use null format for passthru.
+ format = nullptr;
+ }
+ return format;
+}
+
+//static
+const char* TranscoderWrapper::toString(Event::Type type) {
+ switch (type) {
+ case Event::Start:
+ return "Start";
+ case Event::Pause:
+ return "Pause";
+ case Event::Resume:
+ return "Resume";
+ case Event::Stop:
+ return "Stop";
+ case Event::Finish:
+ return "Finish";
+ case Event::Error:
+ return "Error";
+ case Event::Progress:
+ return "Progress";
+ default:
+ break;
+ }
+ return "(unknown)";
+}
+
+class TranscoderWrapper::CallbackImpl : public MediaTranscoder::CallbackInterface {
+public:
+ CallbackImpl(const std::shared_ptr<TranscoderWrapper>& owner, ClientIdType clientId,
+ JobIdType jobId)
+ : mOwner(owner), mClientId(clientId), mJobId(jobId) {}
+
+ virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
+ auto owner = mOwner.lock();
+ if (owner != nullptr) {
+ owner->onFinish(mClientId, mJobId);
+ }
+ }
+
+ virtual void onError(const MediaTranscoder* transcoder __unused,
+ media_status_t error) override {
+ auto owner = mOwner.lock();
+ if (owner != nullptr) {
+ owner->onError(mClientId, mJobId, toTranscodingError(error));
+ }
+ }
+
+ virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
+ int32_t progress) override {
+ auto owner = mOwner.lock();
+ if (owner != nullptr) {
+ owner->onProgress(mClientId, mJobId, progress);
+ }
+ }
+
+ virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
+ const std::shared_ptr<const Parcel>& pausedState
+ __unused) override {
+ ALOGV("%s: job {%lld, %d}", __FUNCTION__, (long long)mClientId, mJobId);
+ }
+
+private:
+ std::weak_ptr<TranscoderWrapper> mOwner;
+ ClientIdType mClientId;
+ JobIdType mJobId;
+};
+
+TranscoderWrapper::TranscoderWrapper() : mCurrentClientId(0), mCurrentJobId(-1) {
+ std::thread(&TranscoderWrapper::threadLoop, this).detach();
+}
+
+void TranscoderWrapper::setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) {
+ mCallback = cb;
+}
+
+void TranscoderWrapper::start(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+ queueEvent(Event::Start, clientId, jobId, [=] {
+ TranscodingErrorCode err = handleStart(clientId, jobId, request, clientCb);
+
+ auto callback = mCallback.lock();
+ if (err != TranscodingErrorCode::kNoError) {
+ cleanup();
+
+ if (callback != nullptr) {
+ callback->onError(clientId, jobId, err);
+ }
+ } else {
+ if (callback != nullptr) {
+ callback->onStarted(clientId, jobId);
+ }
+ }
+ });
+}
+
+void TranscoderWrapper::pause(ClientIdType clientId, JobIdType jobId) {
+ queueEvent(Event::Pause, clientId, jobId, [=] {
+ TranscodingErrorCode err = handlePause(clientId, jobId);
+
+ cleanup();
+
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ if (err != TranscodingErrorCode::kNoError) {
+ callback->onError(clientId, jobId, err);
+ } else {
+ callback->onPaused(clientId, jobId);
+ }
+ }
+ });
+}
+
+void TranscoderWrapper::resume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+ queueEvent(Event::Resume, clientId, jobId, [=] {
+ TranscodingErrorCode err = handleResume(clientId, jobId, request, clientCb);
+
+ auto callback = mCallback.lock();
+ if (err != TranscodingErrorCode::kNoError) {
+ cleanup();
+
+ if (callback != nullptr) {
+ callback->onError(clientId, jobId, err);
+ }
+ } else {
+ if (callback != nullptr) {
+ callback->onResumed(clientId, jobId);
+ }
+ }
+ });
+}
+
+void TranscoderWrapper::stop(ClientIdType clientId, JobIdType jobId) {
+ queueEvent(Event::Stop, clientId, jobId, [=] {
+ if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+ // Cancelling the currently running job.
+ media_status_t err = mTranscoder->cancel();
+ if (err != AMEDIA_OK) {
+ ALOGE("failed to stop transcoder: %d", err);
+ } else {
+ ALOGI("transcoder stopped");
+ }
+ cleanup();
+ } else {
+ // For jobs that's not currently running, release any pausedState for the job.
+ mPausedStateMap.erase(JobKeyType(clientId, jobId));
+ }
+ // No callback needed for stop.
+ });
+}
+
+void TranscoderWrapper::onFinish(ClientIdType clientId, JobIdType jobId) {
+ queueEvent(Event::Finish, clientId, jobId, [=] {
+ if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+ cleanup();
+ }
+
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ callback->onFinish(clientId, jobId);
+ }
+ });
+}
+
+void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId,
+ TranscodingErrorCode error) {
+ queueEvent(Event::Error, clientId, jobId, [=] {
+ if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+ cleanup();
+ }
+
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ callback->onError(clientId, jobId, error);
+ }
+ });
+}
+
+void TranscoderWrapper::onProgress(ClientIdType clientId, JobIdType jobId, int32_t progress) {
+ queueEvent(Event::Progress, clientId, jobId, [=] {
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ callback->onProgressUpdate(clientId, jobId, progress);
+ }
+ });
+}
+
+TranscodingErrorCode TranscoderWrapper::setupTranscoder(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb,
+ const std::shared_ptr<const Parcel>& pausedState) {
+ if (clientCb == nullptr) {
+ ALOGE("client callback is null");
+ return TranscodingErrorCode::kInvalidParameter;
+ }
+
+ if (mTranscoder != nullptr) {
+ ALOGE("transcoder already running");
+ return TranscodingErrorCode::kInvalidOperation;
+ }
+
+ Status status;
+ ::ndk::ScopedFileDescriptor srcFd, dstFd;
+ status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
+ if (!status.isOk() || srcFd.get() < 0) {
+ ALOGE("failed to open source");
+ return TranscodingErrorCode::kErrorIO;
+ }
+
+ // Open dest file with "rw", as the transcoder could potentially reuse part of it
+ // for resume case. We might want the further differentiate and open with "w" only
+ // for start.
+ status = clientCb->openFileDescriptor(request.destinationFilePath, "rw", &dstFd);
+ if (!status.isOk() || dstFd.get() < 0) {
+ ALOGE("failed to open destination");
+ return TranscodingErrorCode::kErrorIO;
+ }
+
+ mCurrentClientId = clientId;
+ mCurrentJobId = jobId;
+ mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, jobId);
+ mTranscoder = MediaTranscoder::create(mTranscoderCb, pausedState);
+ if (mTranscoder == nullptr) {
+ ALOGE("failed to create transcoder");
+ return TranscodingErrorCode::kUnknown;
+ }
+
+ media_status_t err = mTranscoder->configureSource(srcFd.get());
+ if (err != AMEDIA_OK) {
+ ALOGE("failed to configure source: %d", err);
+ return toTranscodingError(err);
+ }
+
+ std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
+ if (trackFormats.size() == 0) {
+ ALOGE("failed to get track formats!");
+ return TranscodingErrorCode::kMalformed;
+ }
+
+ for (int i = 0; i < trackFormats.size(); ++i) {
+ AMediaFormat* format = nullptr;
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
+
+ if (!strncmp(mime, "video/", 6)) {
+ format = getVideoFormat(mime, request.requestedVideoTrackFormat);
+ }
+
+ err = mTranscoder->configureTrackFormat(i, format);
+ if (format != nullptr) {
+ AMediaFormat_delete(format);
+ }
+ if (err != AMEDIA_OK) {
+ ALOGE("failed to configure track format for track %d: %d", i, err);
+ return toTranscodingError(err);
+ }
+ }
+
+ err = mTranscoder->configureDestination(dstFd.get());
+ if (err != AMEDIA_OK) {
+ ALOGE("failed to configure dest: %d", err);
+ return toTranscodingError(err);
+ }
+
+ return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handleStart(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+ ALOGI("setting up transcoder for start");
+ TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb);
+ if (err != TranscodingErrorCode::kNoError) {
+ ALOGI("%s: failed to setup transcoder", __FUNCTION__);
+ return err;
+ }
+
+ media_status_t status = mTranscoder->start();
+ if (status != AMEDIA_OK) {
+ ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
+ return toTranscodingError(status);
+ }
+
+ ALOGI("%s: transcoder started", __FUNCTION__);
+ return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
+ if (mTranscoder == nullptr) {
+ ALOGE("%s: transcoder is not running", __FUNCTION__);
+ return TranscodingErrorCode::kInvalidOperation;
+ }
+
+ if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
+ ALOGW("%s: stopping job {%lld, %d} that's not current job {%lld, %d}", __FUNCTION__,
+ (long long)clientId, jobId, (long long)mCurrentClientId, mCurrentJobId);
+ }
+
+ std::shared_ptr<const Parcel> pauseStates;
+ media_status_t err = mTranscoder->pause(&pauseStates);
+ if (err != AMEDIA_OK) {
+ ALOGE("%s: failed to pause transcoder: %d", __FUNCTION__, err);
+ return toTranscodingError(err);
+ }
+ mPausedStateMap[JobKeyType(clientId, jobId)] = pauseStates;
+
+ ALOGI("%s: transcoder paused", __FUNCTION__);
+ return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handleResume(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+ std::shared_ptr<const Parcel> pausedState;
+ auto it = mPausedStateMap.find(JobKeyType(clientId, jobId));
+ if (it != mPausedStateMap.end()) {
+ pausedState = it->second;
+ mPausedStateMap.erase(it);
+ } else {
+ ALOGE("%s: can't find paused state", __FUNCTION__);
+ return TranscodingErrorCode::kInvalidOperation;
+ }
+
+ ALOGI("setting up transcoder for resume");
+ TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb, pausedState);
+ if (err != TranscodingErrorCode::kNoError) {
+ ALOGE("%s: failed to setup transcoder", __FUNCTION__);
+ return err;
+ }
+
+ media_status_t status = mTranscoder->resume();
+ if (status != AMEDIA_OK) {
+ ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
+ return toTranscodingError(status);
+ }
+
+ ALOGI("%s: transcoder resumed", __FUNCTION__);
+ return TranscodingErrorCode::kNoError;
+}
+
+void TranscoderWrapper::cleanup() {
+ mCurrentClientId = 0;
+ mCurrentJobId = -1;
+ mTranscoderCb = nullptr;
+ mTranscoder = nullptr;
+}
+
+void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
+ const std::function<void()> runnable) {
+ ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)clientId, jobId, toString(type));
+
+ std::scoped_lock lock{mLock};
+
+ mQueue.push_back({type, clientId, jobId, runnable});
+ mCondition.notify_one();
+}
+
+void TranscoderWrapper::threadLoop() {
+ std::unique_lock<std::mutex> lock{mLock};
+ // TranscoderWrapper currently lives in the transcoding service, as long as
+ // MediaTranscodingService itself.
+ while (true) {
+ // Wait for the next event.
+ while (mQueue.empty()) {
+ mCondition.wait(lock);
+ }
+
+ Event event = *mQueue.begin();
+ mQueue.pop_front();
+
+ ALOGD("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
+ toString(event.type));
+
+ lock.unlock();
+ event.runnable();
+ lock.lock();
+ }
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/TranscodingClientManager.cpp b/media/libmediatranscoding/TranscodingClientManager.cpp
index 7252437..ce3ac13 100644
--- a/media/libmediatranscoding/TranscodingClientManager.cpp
+++ b/media/libmediatranscoding/TranscodingClientManager.cpp
@@ -17,31 +17,203 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "TranscodingClientManager"
+#include <aidl/android/media/BnTranscodingClient.h>
+#include <aidl/android/media/IMediaTranscodingService.h>
+#include <android/binder_ibinder.h>
#include <inttypes.h>
#include <media/TranscodingClientManager.h>
+#include <media/TranscodingRequest.h>
#include <utils/Log.h>
-
namespace android {
-using Status = ::ndk::ScopedAStatus;
+static_assert(sizeof(ClientIdType) == sizeof(void*), "ClientIdType should be pointer-sized");
-// static
-TranscodingClientManager& TranscodingClientManager::getInstance() {
- static TranscodingClientManager gInstance{};
- return gInstance;
+using ::aidl::android::media::BnTranscodingClient;
+using ::aidl::android::media::IMediaTranscodingService; // For service error codes
+using ::aidl::android::media::TranscodingJobParcel;
+using ::aidl::android::media::TranscodingRequestParcel;
+using Status = ::ndk::ScopedAStatus;
+using ::ndk::SpAIBinder;
+
+//static
+std::atomic<ClientIdType> TranscodingClientManager::sCookieCounter = 0;
+//static
+std::mutex TranscodingClientManager::sCookie2ClientLock;
+//static
+std::map<ClientIdType, std::shared_ptr<TranscodingClientManager::ClientImpl>>
+ TranscodingClientManager::sCookie2Client;
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * ClientImpl implements a single client and contains all its information.
+ */
+struct TranscodingClientManager::ClientImpl : public BnTranscodingClient {
+ /* The remote client callback that this ClientInfo is associated with.
+ * Once the ClientInfo is created, we hold an SpAIBinder so that the binder
+ * object doesn't get created again, otherwise the binder object pointer
+ * may not be unique.
+ */
+ SpAIBinder mClientBinder;
+ std::shared_ptr<ITranscodingClientCallback> mClientCallback;
+ /* A unique id assigned to the client by the service. This number is used
+ * by the service for indexing. Here we use the binder object's pointer
+ * (casted to int64t_t) as the client id.
+ */
+ ClientIdType mClientId;
+ pid_t mClientPid;
+ uid_t mClientUid;
+ std::string mClientName;
+ std::string mClientOpPackageName;
+
+ // Next jobId to assign.
+ std::atomic<int32_t> mNextJobId;
+ // Whether this client has been unregistered already.
+ std::atomic<bool> mAbandoned;
+ // Weak pointer to the client manager for this client.
+ std::weak_ptr<TranscodingClientManager> mOwner;
+
+ ClientImpl(const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
+ const std::string& clientName, const std::string& opPackageName,
+ const std::weak_ptr<TranscodingClientManager>& owner);
+
+ Status submitRequest(const TranscodingRequestParcel& /*in_request*/,
+ TranscodingJobParcel* /*out_job*/, bool* /*_aidl_return*/) override;
+
+ Status cancelJob(int32_t /*in_jobId*/, bool* /*_aidl_return*/) override;
+
+ Status getJobWithId(int32_t /*in_jobId*/, TranscodingJobParcel* /*out_job*/,
+ bool* /*_aidl_return*/) override;
+
+ Status unregister() override;
+};
+
+TranscodingClientManager::ClientImpl::ClientImpl(
+ const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
+ const std::string& clientName, const std::string& opPackageName,
+ const std::weak_ptr<TranscodingClientManager>& owner)
+ : mClientBinder((callback != nullptr) ? callback->asBinder() : nullptr),
+ mClientCallback(callback),
+ mClientId(sCookieCounter.fetch_add(1, std::memory_order_relaxed)),
+ mClientPid(pid),
+ mClientUid(uid),
+ mClientName(clientName),
+ mClientOpPackageName(opPackageName),
+ mNextJobId(0),
+ mAbandoned(false),
+ mOwner(owner) {}
+
+Status TranscodingClientManager::ClientImpl::submitRequest(
+ const TranscodingRequestParcel& in_request, TranscodingJobParcel* out_job,
+ bool* _aidl_return) {
+ *_aidl_return = false;
+
+ std::shared_ptr<TranscodingClientManager> owner;
+ if (mAbandoned || (owner = mOwner.lock()) == nullptr) {
+ return Status::fromServiceSpecificError(IMediaTranscodingService::ERROR_DISCONNECTED);
+ }
+
+ if (in_request.sourceFilePath.empty() || in_request.destinationFilePath.empty()) {
+ // This is the only error we check for now.
+ return Status::ok();
+ }
+
+ int32_t jobId = mNextJobId.fetch_add(1);
+
+ *_aidl_return =
+ owner->mJobScheduler->submit(mClientId, jobId, mClientUid, in_request, mClientCallback);
+
+ if (*_aidl_return) {
+ out_job->jobId = jobId;
+
+ // TODO(chz): is some of this coming from JobScheduler?
+ *(TranscodingRequest*)&out_job->request = in_request;
+ out_job->awaitNumberOfJobs = 0;
+ }
+
+ return Status::ok();
}
+Status TranscodingClientManager::ClientImpl::cancelJob(int32_t in_jobId, bool* _aidl_return) {
+ *_aidl_return = false;
+
+ std::shared_ptr<TranscodingClientManager> owner;
+ if (mAbandoned || (owner = mOwner.lock()) == nullptr) {
+ return Status::fromServiceSpecificError(IMediaTranscodingService::ERROR_DISCONNECTED);
+ }
+
+ if (in_jobId < 0) {
+ return Status::ok();
+ }
+
+ *_aidl_return = owner->mJobScheduler->cancel(mClientId, in_jobId);
+ return Status::ok();
+}
+
+Status TranscodingClientManager::ClientImpl::getJobWithId(int32_t in_jobId,
+ TranscodingJobParcel* out_job,
+ bool* _aidl_return) {
+ *_aidl_return = false;
+
+ std::shared_ptr<TranscodingClientManager> owner;
+ if (mAbandoned || (owner = mOwner.lock()) == nullptr) {
+ return Status::fromServiceSpecificError(IMediaTranscodingService::ERROR_DISCONNECTED);
+ }
+
+ if (in_jobId < 0) {
+ return Status::ok();
+ }
+
+ *_aidl_return = owner->mJobScheduler->getJob(mClientId, in_jobId, &out_job->request);
+
+ if (*_aidl_return) {
+ out_job->jobId = in_jobId;
+ out_job->awaitNumberOfJobs = 0;
+ }
+ return Status::ok();
+}
+
+Status TranscodingClientManager::ClientImpl::unregister() {
+ bool abandoned = mAbandoned.exchange(true);
+
+ std::shared_ptr<TranscodingClientManager> owner;
+ if (abandoned || (owner = mOwner.lock()) == nullptr) {
+ return Status::fromServiceSpecificError(IMediaTranscodingService::ERROR_DISCONNECTED);
+ }
+
+ // Use jobId == -1 to cancel all realtime jobs for this client with the scheduler.
+ owner->mJobScheduler->cancel(mClientId, -1);
+ owner->removeClient(mClientId);
+
+ return Status::ok();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
// static
void TranscodingClientManager::BinderDiedCallback(void* cookie) {
- int32_t clientId = static_cast<int32_t>(reinterpret_cast<intptr_t>(cookie));
- ALOGD("Client %" PRId32 " is dead", clientId);
- // Don't check for pid validity since we know it's already dead.
- TranscodingClientManager& manager = TranscodingClientManager::getInstance();
- manager.removeClient(clientId);
+ ClientIdType clientId = reinterpret_cast<ClientIdType>(cookie);
+
+ ALOGD("Client %lld is dead", (long long)clientId);
+
+ std::shared_ptr<ClientImpl> client;
+
+ {
+ std::scoped_lock lock{sCookie2ClientLock};
+
+ auto it = sCookie2Client.find(clientId);
+ if (it != sCookie2Client.end()) {
+ client = it->second;
+ }
+ }
+
+ if (client != nullptr) {
+ client->unregister();
+ }
}
-TranscodingClientManager::TranscodingClientManager()
- : mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)) {
+TranscodingClientManager::TranscodingClientManager(
+ const std::shared_ptr<SchedulerClientInterface>& scheduler)
+ : mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)), mJobScheduler(scheduler) {
ALOGD("TranscodingClientManager started");
}
@@ -49,97 +221,108 @@
ALOGD("TranscodingClientManager exited");
}
-bool TranscodingClientManager::isClientIdRegistered(int32_t clientId) const {
- std::scoped_lock lock{mLock};
- return mClientIdToClientInfoMap.find(clientId) != mClientIdToClientInfoMap.end();
-}
-
void TranscodingClientManager::dumpAllClients(int fd, const Vector<String16>& args __unused) {
String8 result;
const size_t SIZE = 256;
char buffer[SIZE];
+ std::scoped_lock lock{mLock};
- snprintf(buffer, SIZE, " Total num of Clients: %zu\n", mClientIdToClientInfoMap.size());
+ snprintf(buffer, SIZE, " Total num of Clients: %zu\n", mClientIdToClientMap.size());
result.append(buffer);
- if (mClientIdToClientInfoMap.size() > 0) {
+ if (mClientIdToClientMap.size() > 0) {
snprintf(buffer, SIZE, "========== Dumping all clients =========\n");
result.append(buffer);
}
- for (const auto& iter : mClientIdToClientInfoMap) {
- const std::shared_ptr<ITranscodingServiceClient> client = iter.second->mClient;
- std::string clientName;
- Status status = client->getName(&clientName);
- if (!status.isOk()) {
- ALOGE("Failed to get client: %d information", iter.first);
- continue;
- }
- snprintf(buffer, SIZE, " -- Clients: %d name: %s\n", iter.first, clientName.c_str());
+ for (const auto& iter : mClientIdToClientMap) {
+ snprintf(buffer, SIZE, " -- Client id: %lld name: %s\n", (long long)iter.first,
+ iter.second->mClientName.c_str());
result.append(buffer);
}
write(fd, result.string(), result.size());
}
-status_t TranscodingClientManager::addClient(std::unique_ptr<ClientInfo> client) {
+status_t TranscodingClientManager::addClient(
+ const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
+ const std::string& clientName, const std::string& opPackageName,
+ std::shared_ptr<ITranscodingClient>* outClient) {
// Validate the client.
- if (client == nullptr || client->mClientId < 0 || client->mClientPid < 0 ||
- client->mClientUid < 0 || client->mClientOpPackageName.empty() ||
- client->mClientOpPackageName == "") {
+ if (callback == nullptr || pid < 0 || clientName.empty() || opPackageName.empty()) {
ALOGE("Invalid client");
- return BAD_VALUE;
+ return IMediaTranscodingService::ERROR_ILLEGAL_ARGUMENT;
}
+ SpAIBinder binder = callback->asBinder();
+
std::scoped_lock lock{mLock};
- // Check if the client already exists.
- if (mClientIdToClientInfoMap.count(client->mClientId) != 0) {
- ALOGW("Client already exists.");
- return ALREADY_EXISTS;
+ // Checks if the client already registers.
+ if (mRegisteredCallbacks.count((uintptr_t)binder.get()) > 0) {
+ return IMediaTranscodingService::ERROR_ALREADY_EXISTS;
}
- ALOGD("Adding client id %d pid: %d uid: %d %s", client->mClientId, client->mClientPid,
- client->mClientUid, client->mClientOpPackageName.c_str());
+ // Creates the client and uses its process id as client id.
+ std::shared_ptr<ClientImpl> client = ::ndk::SharedRefBase::make<ClientImpl>(
+ callback, pid, uid, clientName, opPackageName, shared_from_this());
- AIBinder_linkToDeath(client->mClient->asBinder().get(), mDeathRecipient.get(),
+ ALOGD("Adding client id %lld, pid %d, uid %d, name %s, package %s",
+ (long long)client->mClientId, client->mClientPid, client->mClientUid,
+ client->mClientName.c_str(), client->mClientOpPackageName.c_str());
+
+ {
+ std::scoped_lock lock{sCookie2ClientLock};
+ sCookie2Client.emplace(std::make_pair(client->mClientId, client));
+ }
+
+ AIBinder_linkToDeath(binder.get(), mDeathRecipient.get(),
reinterpret_cast<void*>(client->mClientId));
// Adds the new client to the map.
- mClientIdToClientInfoMap[client->mClientId] = std::move(client);
+ mRegisteredCallbacks.insert((uintptr_t)binder.get());
+ mClientIdToClientMap[client->mClientId] = client;
+
+ *outClient = client;
return OK;
}
-status_t TranscodingClientManager::removeClient(int32_t clientId) {
- ALOGD("Removing client id %d", clientId);
+status_t TranscodingClientManager::removeClient(ClientIdType clientId) {
+ ALOGD("Removing client id %lld", (long long)clientId);
std::scoped_lock lock{mLock};
// Checks if the client is valid.
- auto it = mClientIdToClientInfoMap.find(clientId);
- if (it == mClientIdToClientInfoMap.end()) {
- ALOGE("Client id %d does not exist", clientId);
- return INVALID_OPERATION;
+ auto it = mClientIdToClientMap.find(clientId);
+ if (it == mClientIdToClientMap.end()) {
+ ALOGE("Client id %lld does not exist", (long long)clientId);
+ return IMediaTranscodingService::ERROR_INVALID_OPERATION;
}
- std::shared_ptr<ITranscodingServiceClient> client = it->second->mClient;
+ SpAIBinder binder = it->second->mClientBinder;
// Check if the client still live. If alive, unlink the death.
- if (client) {
- AIBinder_unlinkToDeath(client->asBinder().get(), mDeathRecipient.get(),
- reinterpret_cast<void*>(clientId));
+ if (binder.get() != nullptr) {
+ AIBinder_unlinkToDeath(binder.get(), mDeathRecipient.get(),
+ reinterpret_cast<void*>(it->second->mClientId));
+ }
+
+ {
+ std::scoped_lock lock{sCookie2ClientLock};
+ sCookie2Client.erase(it->second->mClientId);
}
// Erase the entry.
- mClientIdToClientInfoMap.erase(it);
+ mClientIdToClientMap.erase(it);
+ mRegisteredCallbacks.erase((uintptr_t)binder.get());
return OK;
}
size_t TranscodingClientManager::getNumOfClients() const {
std::scoped_lock lock{mLock};
- return mClientIdToClientInfoMap.size();
+ return mClientIdToClientMap.size();
}
} // namespace android
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
new file mode 100644
index 0000000..3e4f319
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -0,0 +1,477 @@
+/*
+ * 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 "TranscodingJobScheduler"
+
+#define VALIDATE_STATE 1
+
+#include <inttypes.h>
+#include <media/TranscodingJobScheduler.h>
+#include <utils/Log.h>
+
+#include <utility>
+
+namespace android {
+
+static_assert((JobIdType)-1 < 0, "JobIdType should be signed");
+
+constexpr static uid_t OFFLINE_UID = -1;
+
+//static
+String8 TranscodingJobScheduler::jobToString(const JobKeyType& jobKey) {
+ return String8::format("{client:%lld, job:%d}", (long long)jobKey.first, jobKey.second);
+}
+
+TranscodingJobScheduler::TranscodingJobScheduler(
+ const std::shared_ptr<TranscoderInterface>& transcoder,
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy)
+ : mTranscoder(transcoder), mUidPolicy(uidPolicy), mCurrentJob(nullptr), mResourceLost(false) {
+ // Only push empty offline queue initially. Realtime queues are added when requests come in.
+ mUidSortedList.push_back(OFFLINE_UID);
+ mOfflineUidIterator = mUidSortedList.begin();
+ mJobQueues.emplace(OFFLINE_UID, JobQueueType());
+}
+
+TranscodingJobScheduler::~TranscodingJobScheduler() {}
+
+TranscodingJobScheduler::Job* TranscodingJobScheduler::getTopJob_l() {
+ if (mJobMap.empty()) {
+ return nullptr;
+ }
+ uid_t topUid = *mUidSortedList.begin();
+ JobKeyType topJobKey = *mJobQueues[topUid].begin();
+ return &mJobMap[topJobKey];
+}
+
+void TranscodingJobScheduler::updateCurrentJob_l() {
+ Job* topJob = getTopJob_l();
+ Job* curJob = mCurrentJob;
+ ALOGV("updateCurrentJob: topJob is %s, curJob is %s",
+ topJob == nullptr ? "null" : jobToString(topJob->key).c_str(),
+ curJob == nullptr ? "null" : jobToString(curJob->key).c_str());
+
+ // If we found a topJob that should be run, and it's not already running,
+ // take some actions to ensure it's running.
+ if (topJob != nullptr && (topJob != curJob || topJob->state != Job::RUNNING)) {
+ // If another job is currently running, pause it first.
+ if (curJob != nullptr && curJob->state == Job::RUNNING) {
+ mTranscoder->pause(curJob->key.first, curJob->key.second);
+ curJob->state = Job::PAUSED;
+ }
+ // If we are not experiencing resource loss, we can start or resume
+ // the topJob now.
+ if (!mResourceLost) {
+ if (topJob->state == Job::NOT_STARTED) {
+ mTranscoder->start(topJob->key.first, topJob->key.second, topJob->request,
+ topJob->callback.lock());
+ } else if (topJob->state == Job::PAUSED) {
+ mTranscoder->resume(topJob->key.first, topJob->key.second, topJob->request,
+ topJob->callback.lock());
+ }
+ topJob->state = Job::RUNNING;
+ }
+ }
+ mCurrentJob = topJob;
+}
+
+void TranscodingJobScheduler::removeJob_l(const JobKeyType& jobKey) {
+ ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+ return;
+ }
+
+ // Remove job from uid's queue.
+ const uid_t uid = mJobMap[jobKey].uid;
+ JobQueueType& jobQueue = mJobQueues[uid];
+ auto it = std::find(jobQueue.begin(), jobQueue.end(), jobKey);
+ if (it == jobQueue.end()) {
+ ALOGE("couldn't find job %s in queue for uid %d", jobToString(jobKey).c_str(), uid);
+ return;
+ }
+ jobQueue.erase(it);
+
+ // If this is the last job in a real-time queue, remove this uid's queue.
+ if (uid != OFFLINE_UID && jobQueue.empty()) {
+ mUidSortedList.remove(uid);
+ mJobQueues.erase(uid);
+ mUidPolicy->unregisterMonitorUid(uid);
+
+ std::unordered_set<uid_t> topUids = mUidPolicy->getTopUids();
+ moveUidsToTop_l(topUids, false /*preserveTopUid*/);
+ }
+
+ // Clear current job.
+ if (mCurrentJob == &mJobMap[jobKey]) {
+ mCurrentJob = nullptr;
+ }
+
+ // Remove job from job map.
+ mJobMap.erase(jobKey);
+}
+
+/**
+ * Moves the set of uids to the front of mUidSortedList (which is used to pick
+ * the next job to run).
+ *
+ * This is called when 1) we received a onTopUidsChanged() callbcak from UidPolicy,
+ * or 2) we removed the job queue for a uid because it becomes empty.
+ *
+ * In case of 1), if there are multiple uids in the set, and the current front
+ * uid in mUidSortedList is still in the set, we try to keep that uid at front
+ * so that current job run is not interrupted. (This is not a concern for case 2)
+ * because the queue for a uid was just removed entirely.)
+ */
+void TranscodingJobScheduler::moveUidsToTop_l(const std::unordered_set<uid_t>& uids,
+ bool preserveTopUid) {
+ // If uid set is empty, nothing to do. Do not change the queue status.
+ if (uids.empty()) {
+ return;
+ }
+
+ // Save the current top uid.
+ uid_t curTopUid = *mUidSortedList.begin();
+ bool pushCurTopToFront = false;
+ int32_t numUidsMoved = 0;
+
+ // Go through the sorted uid list once, and move the ones in top set to front.
+ for (auto it = mUidSortedList.begin(); it != mUidSortedList.end();) {
+ uid_t uid = *it;
+
+ if (uid != OFFLINE_UID && uids.count(uid) > 0) {
+ it = mUidSortedList.erase(it);
+
+ // If this is the top we're preserving, don't push it here, push
+ // it after the for-loop.
+ if (uid == curTopUid && preserveTopUid) {
+ pushCurTopToFront = true;
+ } else {
+ mUidSortedList.push_front(uid);
+ }
+
+ // If we found all uids in the set, break out.
+ if (++numUidsMoved == uids.size()) {
+ break;
+ }
+ } else {
+ ++it;
+ }
+ }
+
+ if (pushCurTopToFront) {
+ mUidSortedList.push_front(curTopUid);
+ }
+}
+
+bool TranscodingJobScheduler::submit(ClientIdType clientId, JobIdType jobId, uid_t uid,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& callback) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ ALOGV("%s: job %s, uid %d, prioirty %d", __FUNCTION__, jobToString(jobKey).c_str(), uid,
+ (int32_t)request.priority);
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) > 0) {
+ ALOGE("job %s already exists", jobToString(jobKey).c_str());
+ return false;
+ }
+
+ // TODO(chz): only support offline vs real-time for now. All kUnspecified jobs
+ // go to offline queue.
+ if (request.priority == TranscodingJobPriority::kUnspecified) {
+ uid = OFFLINE_UID;
+ }
+
+ // Add job to job map.
+ mJobMap[jobKey].key = jobKey;
+ mJobMap[jobKey].uid = uid;
+ mJobMap[jobKey].state = Job::NOT_STARTED;
+ mJobMap[jobKey].request = request;
+ mJobMap[jobKey].callback = callback;
+
+ // If it's an offline job, the queue was already added in constructor.
+ // If it's a real-time jobs, check if a queue is already present for the uid,
+ // and add a new queue if needed.
+ if (uid != OFFLINE_UID) {
+ if (mJobQueues.count(uid) == 0) {
+ mUidPolicy->registerMonitorUid(uid);
+ if (mUidPolicy->isUidOnTop(uid)) {
+ mUidSortedList.push_front(uid);
+ } else {
+ // Shouldn't be submitting real-time requests from non-top app,
+ // put it in front of the offline queue.
+ mUidSortedList.insert(mOfflineUidIterator, uid);
+ }
+ } else if (uid != *mUidSortedList.begin()) {
+ if (mUidPolicy->isUidOnTop(uid)) {
+ mUidSortedList.remove(uid);
+ mUidSortedList.push_front(uid);
+ }
+ }
+ }
+ // Append this job to the uid's queue.
+ mJobQueues[uid].push_back(jobKey);
+
+ updateCurrentJob_l();
+
+ validateState_l();
+ return true;
+}
+
+bool TranscodingJobScheduler::cancel(ClientIdType clientId, JobIdType jobId) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
+
+ std::list<JobKeyType> jobsToRemove;
+
+ std::scoped_lock lock{mLock};
+
+ if (jobId < 0) {
+ for (auto it = mJobMap.begin(); it != mJobMap.end(); ++it) {
+ if (it->first.first == clientId && it->second.uid != OFFLINE_UID) {
+ jobsToRemove.push_back(it->first);
+ }
+ }
+ } else {
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+ return false;
+ }
+ jobsToRemove.push_back(jobKey);
+ }
+
+ for (auto it = jobsToRemove.begin(); it != jobsToRemove.end(); ++it) {
+ // If the job has ever been started, stop it now.
+ // Note that stop() is needed even if the job is currently paused. This instructs
+ // the transcoder to discard any states for the job, otherwise the states may
+ // never be discarded.
+ if (mJobMap[*it].state != Job::NOT_STARTED) {
+ mTranscoder->stop(it->first, it->second);
+ }
+
+ // Remove the job.
+ removeJob_l(*it);
+ }
+
+ // Start next job.
+ updateCurrentJob_l();
+
+ validateState_l();
+ return true;
+}
+
+bool TranscodingJobScheduler::getJob(ClientIdType clientId, JobIdType jobId,
+ TranscodingRequestParcel* request) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+ return false;
+ }
+
+ *(TranscodingRequest*)request = mJobMap[jobKey].request;
+ return true;
+}
+
+void TranscodingJobScheduler::notifyClient(ClientIdType clientId, JobIdType jobId,
+ const char* reason,
+ std::function<void(const JobKeyType&)> func) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGW("%s: ignoring %s for job %s that doesn't exist", __FUNCTION__, reason,
+ jobToString(jobKey).c_str());
+ return;
+ }
+
+ // Only ignore if job was never started. In particular, propagate the status
+ // to client if the job is paused. Transcoder could have posted finish when
+ // we're pausing it, and the finish arrived after we changed current job.
+ if (mJobMap[jobKey].state == Job::NOT_STARTED) {
+ ALOGW("%s: ignoring %s for job %s that was never started", __FUNCTION__, reason,
+ jobToString(jobKey).c_str());
+ return;
+ }
+
+ ALOGV("%s: job %s %s", __FUNCTION__, jobToString(jobKey).c_str(), reason);
+ func(jobKey);
+}
+
+void TranscodingJobScheduler::onStarted(ClientIdType clientId, JobIdType jobId) {
+ notifyClient(clientId, jobId, "started", [=](const JobKeyType& jobKey) {
+ auto callback = mJobMap[jobKey].callback.lock();
+ if (callback != nullptr) {
+ callback->onTranscodingStarted(jobId);
+ }
+ });
+}
+
+void TranscodingJobScheduler::onPaused(ClientIdType clientId, JobIdType jobId) {
+ notifyClient(clientId, jobId, "paused", [=](const JobKeyType& jobKey) {
+ auto callback = mJobMap[jobKey].callback.lock();
+ if (callback != nullptr) {
+ callback->onTranscodingPaused(jobId);
+ }
+ });
+}
+
+void TranscodingJobScheduler::onResumed(ClientIdType clientId, JobIdType jobId) {
+ notifyClient(clientId, jobId, "resumed", [=](const JobKeyType& jobKey) {
+ auto callback = mJobMap[jobKey].callback.lock();
+ if (callback != nullptr) {
+ callback->onTranscodingResumed(jobId);
+ }
+ });
+}
+
+void TranscodingJobScheduler::onFinish(ClientIdType clientId, JobIdType jobId) {
+ notifyClient(clientId, jobId, "finish", [=](const JobKeyType& jobKey) {
+ {
+ auto clientCallback = mJobMap[jobKey].callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFinished(
+ jobId, TranscodingResultParcel({jobId, -1 /*actualBitrateBps*/,
+ std::nullopt /*jobStats*/}));
+ }
+ }
+
+ // Remove the job.
+ removeJob_l(jobKey);
+
+ // Start next job.
+ updateCurrentJob_l();
+
+ validateState_l();
+ });
+}
+
+void TranscodingJobScheduler::onError(ClientIdType clientId, JobIdType jobId,
+ TranscodingErrorCode err) {
+ notifyClient(clientId, jobId, "error", [=](const JobKeyType& jobKey) {
+ {
+ auto clientCallback = mJobMap[jobKey].callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFailed(jobId, err);
+ }
+ }
+
+ // Remove the job.
+ removeJob_l(jobKey);
+
+ // Start next job.
+ updateCurrentJob_l();
+
+ validateState_l();
+ });
+}
+
+void TranscodingJobScheduler::onProgressUpdate(ClientIdType clientId, JobIdType jobId,
+ int32_t progress) {
+ notifyClient(clientId, jobId, "progress", [=](const JobKeyType& jobKey) {
+ auto callback = mJobMap[jobKey].callback.lock();
+ if (callback != nullptr) {
+ callback->onProgressUpdate(jobId, progress);
+ }
+ });
+}
+
+void TranscodingJobScheduler::onResourceLost() {
+ ALOGV("%s", __FUNCTION__);
+
+ std::scoped_lock lock{mLock};
+
+ // If we receive a resource loss event, the TranscoderLibrary already paused
+ // the transcoding, so we don't need to call onPaused to notify it to pause.
+ // Only need to update the job state here.
+ if (mCurrentJob != nullptr && mCurrentJob->state == Job::RUNNING) {
+ mCurrentJob->state = Job::PAUSED;
+ }
+ mResourceLost = true;
+
+ validateState_l();
+}
+
+void TranscodingJobScheduler::onTopUidsChanged(const std::unordered_set<uid_t>& uids) {
+ if (uids.empty()) {
+ ALOGW("%s: ignoring empty uids", __FUNCTION__);
+ return;
+ }
+
+ std::string uidStr;
+ for (auto it = uids.begin(); it != uids.end(); it++) {
+ if (!uidStr.empty()) {
+ uidStr += ", ";
+ }
+ uidStr += std::to_string(*it);
+ }
+
+ ALOGD("%s: topUids: size %zu, uids: %s", __FUNCTION__, uids.size(), uidStr.c_str());
+
+ std::scoped_lock lock{mLock};
+
+ moveUidsToTop_l(uids, true /*preserveTopUid*/);
+
+ updateCurrentJob_l();
+
+ validateState_l();
+}
+
+void TranscodingJobScheduler::onResourceAvailable() {
+ ALOGV("%s", __FUNCTION__);
+
+ std::scoped_lock lock{mLock};
+
+ mResourceLost = false;
+ updateCurrentJob_l();
+
+ validateState_l();
+}
+
+void TranscodingJobScheduler::validateState_l() {
+#ifdef VALIDATE_STATE
+ LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_UID) != 1,
+ "mJobQueues offline queue number is not 1");
+ LOG_ALWAYS_FATAL_IF(*mOfflineUidIterator != OFFLINE_UID,
+ "mOfflineUidIterator not pointing to offline uid");
+ LOG_ALWAYS_FATAL_IF(mUidSortedList.size() != mJobQueues.size(),
+ "mUidList and mJobQueues size mismatch");
+
+ int32_t totalJobs = 0;
+ for (auto uidIt = mUidSortedList.begin(); uidIt != mUidSortedList.end(); uidIt++) {
+ LOG_ALWAYS_FATAL_IF(mJobQueues.count(*uidIt) != 1, "mJobQueues count for uid %d is not 1",
+ *uidIt);
+ for (auto jobIt = mJobQueues[*uidIt].begin(); jobIt != mJobQueues[*uidIt].end(); jobIt++) {
+ LOG_ALWAYS_FATAL_IF(mJobMap.count(*jobIt) != 1, "mJobs count for job %s is not 1",
+ jobToString(*jobIt).c_str());
+ }
+
+ totalJobs += mJobQueues[*uidIt].size();
+ }
+ LOG_ALWAYS_FATAL_IF(mJobMap.size() != totalJobs,
+ "mJobs size doesn't match total jobs counted from uid queues");
+#endif // VALIDATE_STATE
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/TranscodingUidPolicy.cpp b/media/libmediatranscoding/TranscodingUidPolicy.cpp
new file mode 100644
index 0000000..b72a2b9
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingUidPolicy.cpp
@@ -0,0 +1,248 @@
+/*
+ * 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 "TranscodingUidPolicy"
+
+#include <binder/ActivityManager.h>
+#include <cutils/misc.h> // FIRST_APPLICATION_UID
+#include <inttypes.h>
+#include <media/TranscodingUidPolicy.h>
+#include <utils/Log.h>
+
+#include <utility>
+
+namespace android {
+
+constexpr static uid_t OFFLINE_UID = -1;
+constexpr static const char* kTranscodingTag = "transcoding";
+
+struct TranscodingUidPolicy::UidObserver : public BnUidObserver,
+ public virtual IBinder::DeathRecipient {
+ explicit UidObserver(TranscodingUidPolicy* owner) : mOwner(owner) {}
+
+ // IUidObserver
+ void onUidGone(uid_t uid, bool disabled) override;
+ void onUidActive(uid_t uid) override;
+ void onUidIdle(uid_t uid, bool disabled) override;
+ void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
+ int32_t capability) override;
+
+ // IBinder::DeathRecipient implementation
+ void binderDied(const wp<IBinder>& who) override;
+
+ TranscodingUidPolicy* mOwner;
+};
+
+void TranscodingUidPolicy::UidObserver::onUidGone(uid_t uid __unused, bool disabled __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidActive(uid_t uid __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidIdle(uid_t uid __unused, bool disabled __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidStateChanged(uid_t uid, int32_t procState,
+ int64_t procStateSeq __unused,
+ int32_t capability __unused) {
+ mOwner->onUidStateChanged(uid, procState);
+}
+
+void TranscodingUidPolicy::UidObserver::binderDied(const wp<IBinder>& /*who*/) {
+ ALOGW("TranscodingUidPolicy: ActivityManager has died");
+ // TODO(chz): this is a rare event (since if the AMS is dead, the system is
+ // probably dead as well). But we should try to reconnect.
+ mOwner->setUidObserverRegistered(false);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+TranscodingUidPolicy::TranscodingUidPolicy()
+ : mAm(std::make_shared<ActivityManager>()),
+ mUidObserver(new UidObserver(this)),
+ mRegistered(false),
+ mTopUidState(ActivityManager::PROCESS_STATE_UNKNOWN) {
+ registerSelf();
+}
+
+TranscodingUidPolicy::~TranscodingUidPolicy() {
+ unregisterSelf();
+}
+
+void TranscodingUidPolicy::registerSelf() {
+ status_t res = mAm->linkToDeath(mUidObserver.get());
+ mAm->registerUidObserver(
+ mUidObserver.get(),
+ ActivityManager::UID_OBSERVER_GONE | ActivityManager::UID_OBSERVER_IDLE |
+ ActivityManager::UID_OBSERVER_ACTIVE | ActivityManager::UID_OBSERVER_PROCSTATE,
+ ActivityManager::PROCESS_STATE_UNKNOWN, String16(kTranscodingTag));
+
+ if (res == OK) {
+ Mutex::Autolock _l(mUidLock);
+
+ mRegistered = true;
+ ALOGI("TranscodingUidPolicy: Registered with ActivityManager");
+ } else {
+ mAm->unregisterUidObserver(mUidObserver.get());
+ }
+}
+
+void TranscodingUidPolicy::unregisterSelf() {
+ mAm->unregisterUidObserver(mUidObserver.get());
+ mAm->unlinkToDeath(mUidObserver.get());
+
+ Mutex::Autolock _l(mUidLock);
+
+ mRegistered = false;
+
+ ALOGI("TranscodingUidPolicy: Unregistered with ActivityManager");
+}
+
+void TranscodingUidPolicy::setUidObserverRegistered(bool registered) {
+ Mutex::Autolock _l(mUidLock);
+
+ mRegistered = registered;
+}
+
+void TranscodingUidPolicy::setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) {
+ mUidPolicyCallback = cb;
+}
+
+void TranscodingUidPolicy::registerMonitorUid(uid_t uid) {
+ Mutex::Autolock _l(mUidLock);
+ if (uid == OFFLINE_UID) {
+ ALOGW("Ignoring the offline uid");
+ return;
+ }
+ if (mUidStateMap.find(uid) != mUidStateMap.end()) {
+ ALOGE("%s: Trying to register uid: %d which is already monitored!", __FUNCTION__, uid);
+ return;
+ }
+
+ int32_t state = ActivityManager::PROCESS_STATE_UNKNOWN;
+ if (mRegistered && mAm->isUidActive(uid, String16(kTranscodingTag))) {
+ state = mAm->getUidProcessState(uid, String16(kTranscodingTag));
+ }
+
+ ALOGV("%s: inserting new uid: %u, procState %d", __FUNCTION__, uid, state);
+
+ mUidStateMap.emplace(std::pair<uid_t, int32_t>(uid, state));
+ mStateUidMap[state].insert(uid);
+
+ updateTopUid_l();
+}
+
+void TranscodingUidPolicy::unregisterMonitorUid(uid_t uid) {
+ Mutex::Autolock _l(mUidLock);
+
+ auto it = mUidStateMap.find(uid);
+ if (it == mUidStateMap.end()) {
+ ALOGE("%s: Trying to unregister uid: %d which is not monitored!", __FUNCTION__, uid);
+ return;
+ }
+
+ auto stateIt = mStateUidMap.find(it->second);
+ if (stateIt != mStateUidMap.end()) {
+ stateIt->second.erase(uid);
+ if (stateIt->second.empty()) {
+ mStateUidMap.erase(stateIt);
+ }
+ }
+ mUidStateMap.erase(it);
+
+ updateTopUid_l();
+}
+
+bool TranscodingUidPolicy::isUidOnTop(uid_t uid) {
+ Mutex::Autolock _l(mUidLock);
+
+ return mTopUidState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+ mTopUidState == getProcState_l(uid);
+}
+
+std::unordered_set<uid_t> TranscodingUidPolicy::getTopUids() const {
+ Mutex::Autolock _l(mUidLock);
+
+ if (mTopUidState == ActivityManager::PROCESS_STATE_UNKNOWN) {
+ return std::unordered_set<uid_t>();
+ }
+
+ return mStateUidMap.at(mTopUidState);
+}
+
+void TranscodingUidPolicy::onUidStateChanged(uid_t uid, int32_t procState) {
+ ALOGV("onUidStateChanged: %u, procState %d", uid, procState);
+
+ bool topUidSetChanged = false;
+ std::unordered_set<uid_t> topUids;
+ {
+ Mutex::Autolock _l(mUidLock);
+ auto it = mUidStateMap.find(uid);
+ if (it != mUidStateMap.end() && it->second != procState) {
+ // Top set changed if 1) the uid is in the current top uid set, or 2) the
+ // new procState is at least the same priority as the current top uid state.
+ bool isUidCurrentTop = mTopUidState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+ mStateUidMap[mTopUidState].count(uid) > 0;
+ bool isNewStateHigherThanTop = procState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+ (procState <= mTopUidState ||
+ mTopUidState == ActivityManager::PROCESS_STATE_UNKNOWN);
+ topUidSetChanged = (isUidCurrentTop || isNewStateHigherThanTop);
+
+ // Move uid to the new procState.
+ mStateUidMap[it->second].erase(uid);
+ mStateUidMap[procState].insert(uid);
+ it->second = procState;
+
+ if (topUidSetChanged) {
+ updateTopUid_l();
+
+ // Make a copy of the uid set for callback.
+ topUids = mStateUidMap[mTopUidState];
+ }
+ }
+ }
+
+ ALOGV("topUidSetChanged: %d", topUidSetChanged);
+
+ if (topUidSetChanged) {
+ auto callback = mUidPolicyCallback.lock();
+ if (callback != nullptr) {
+ callback->onTopUidsChanged(topUids);
+ }
+ }
+}
+
+void TranscodingUidPolicy::updateTopUid_l() {
+ mTopUidState = ActivityManager::PROCESS_STATE_UNKNOWN;
+
+ // Find the lowest uid state (ignoring PROCESS_STATE_UNKNOWN) with some monitored uids.
+ for (auto stateIt = mStateUidMap.begin(); stateIt != mStateUidMap.end(); stateIt++) {
+ if (stateIt->first != ActivityManager::PROCESS_STATE_UNKNOWN && !stateIt->second.empty()) {
+ mTopUidState = stateIt->first;
+ break;
+ }
+ }
+
+ ALOGV("%s: top uid state is %d", __FUNCTION__, mTopUidState);
+}
+
+int32_t TranscodingUidPolicy::getProcState_l(uid_t uid) {
+ auto it = mUidStateMap.find(uid);
+ if (it != mUidStateMap.end()) {
+ return it->second;
+ }
+ return ActivityManager::PROCESS_STATE_UNKNOWN;
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl b/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
index 07b6c1a..40ca2c2 100644
--- a/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
+++ b/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
@@ -16,9 +16,10 @@
package android.media;
+import android.media.ITranscodingClient;
+import android.media.ITranscodingClientCallback;
import android.media.TranscodingJobParcel;
import android.media.TranscodingRequestParcel;
-import android.media.ITranscodingServiceClient;
/**
* Binder interface for MediaTranscodingService.
@@ -48,64 +49,29 @@
/**
* Register the client with the MediaTranscodingService.
*
- * Client must call this function to register itself with the service in order to perform
- * transcoding. This function will return a unique positive Id assigned by the service.
- * Client should save this Id and use it for all the transaction with the service.
+ * Client must call this function to register itself with the service in
+ * order to perform transcoding tasks. This function will return an
+ * ITranscodingClient interface object. The client should save and use it
+ * for all future transactions with the service.
*
- * @param client interface for the MediaTranscodingService to call the client.
+ * @param callback client interface for the MediaTranscodingService to call
+ * the client.
+ * @param clientName name of the client.
* @param opPackageName op package name of the client.
* @param clientUid user id of the client.
* @param clientPid process id of the client.
- * @return a unique positive Id assigned to the client by the service, -1 means failed to
- * register.
+ * @return an ITranscodingClient interface object, with nullptr indicating
+ * failure to register.
*/
- int registerClient(in ITranscodingServiceClient client,
- in String opPackageName,
- in int clientUid,
- in int clientPid);
-
- /**
- * Unregister the client with the MediaTranscodingService.
- *
- * Client will not be able to perform any more transcoding after unregister.
- *
- * @param clientId assigned Id of the client.
- * @return true if succeeds, false otherwise.
- */
- boolean unregisterClient(in int clientId);
+ ITranscodingClient registerClient(
+ in ITranscodingClientCallback callback,
+ in String clientName,
+ in String opPackageName,
+ in int clientUid,
+ in int clientPid);
/**
* Returns the number of clients. This is used for debugging.
*/
int getNumOfClients();
-
- /**
- * Submits a transcoding request to MediaTranscodingService.
- *
- * @param clientId assigned Id of the client.
- * @param request a TranscodingRequest contains transcoding configuration.
- * @param job(output variable) a TranscodingJob generated by the MediaTranscodingService.
- * @return a unique positive jobId generated by the MediaTranscodingService, -1 means failure.
- */
- int submitRequest(in int clientId,
- in TranscodingRequestParcel request,
- out TranscodingJobParcel job);
-
- /**
- * Cancels a transcoding job.
- *
- * @param clientId assigned id of the client.
- * @param jobId a TranscodingJob generated by the MediaTranscodingService.
- * @return true if succeeds, false otherwise.
- */
- boolean cancelJob(in int clientId, in int jobId);
-
- /**
- * Queries the job detail associated with a jobId.
- *
- * @param jobId a TranscodingJob generated by the MediaTranscodingService.
- * @param job(output variable) the TranscodingJob associated with the jobId.
- * @return true if succeeds, false otherwise.
- */
- boolean getJobWithId(in int jobId, out TranscodingJobParcel job);
}
diff --git a/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl b/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl
new file mode 100644
index 0000000..37b5147
--- /dev/null
+++ b/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl
@@ -0,0 +1,63 @@
+/**
+ * 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.
+ */
+
+package android.media;
+
+import android.media.TranscodingJobParcel;
+import android.media.TranscodingRequestParcel;
+
+/**
+ * ITranscodingClient
+ *
+ * Interface for a client to communicate with MediaTranscodingService.
+ *
+ * {@hide}
+ */
+interface ITranscodingClient {
+ /**
+ * Submits a transcoding request to MediaTranscodingService.
+ *
+ * @param request a TranscodingRequest contains transcoding configuration.
+ * @param job(output variable) a TranscodingJob generated by the MediaTranscodingService.
+ * @return true if success, false otherwise.
+ */
+ boolean submitRequest(in TranscodingRequestParcel request,
+ out TranscodingJobParcel job);
+
+ /**
+ * Cancels a transcoding job.
+ *
+ * @param jobId a TranscodingJob generated by the MediaTranscodingService.
+ * @return true if succeeds, false otherwise.
+ */
+ boolean cancelJob(in int jobId);
+
+ /**
+ * Queries the job detail associated with a jobId.
+ *
+ * @param jobId a TranscodingJob generated by the MediaTranscodingService.
+ * @param job(output variable) the TranscodingJob associated with the jobId.
+ * @return true if succeeds, false otherwise.
+ */
+ boolean getJobWithId(in int jobId, out TranscodingJobParcel job);
+
+ /**
+ * Unregister the client with the MediaTranscodingService.
+ *
+ * Client will not be able to perform any more transcoding after unregister.
+ */
+ void unregister();
+}
diff --git a/media/libmediatranscoding/aidl/android/media/ITranscodingServiceClient.aidl b/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
similarity index 63%
rename from media/libmediatranscoding/aidl/android/media/ITranscodingServiceClient.aidl
rename to media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
index e23c833..73edb95 100644
--- a/media/libmediatranscoding/aidl/android/media/ITranscodingServiceClient.aidl
+++ b/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
@@ -19,19 +19,49 @@
import android.media.TranscodingErrorCode;
import android.media.TranscodingJobParcel;
import android.media.TranscodingResultParcel;
+import android.os.ParcelFileDescriptor;
/**
- * ITranscodingServiceClient interface for the MediaTranscodingervice to communicate with the
- * client.
+ * ITranscodingClientCallback
+ *
+ * Interface for the MediaTranscodingService to communicate with the client.
*
* {@hide}
*/
-//TODO(hkuang): Implement the interface.
-interface ITranscodingServiceClient {
+interface ITranscodingClientCallback {
/**
- * Retrieves the name of the client.
- */
- @utf8InCpp String getName();
+ * Called to open a raw file descriptor to access data under a URI
+ *
+ * @param fileUri The path of the filename.
+ * @param mode The file mode to use. Must be one of ("r, "w", "rw")
+ * @return ParcelFileDescriptor if open the file successfully, null otherwise.
+ */
+ ParcelFileDescriptor openFileDescriptor(in @utf8InCpp String fileUri,
+ in @utf8InCpp String mode);
+
+ /**
+ * Called when the transcoding associated with the jobId finished.
+ * This will only be called if client request to get all the status of the job.
+ *
+ * @param jobId jobId assigned by the MediaTranscodingService upon receiving request.
+ */
+ oneway void onTranscodingStarted(in int jobId);
+
+ /**
+ * Called when the transcoding associated with the jobId is paused.
+ * This will only be called if client request to get all the status of the job.
+ *
+ * @param jobId jobId assigned by the MediaTranscodingService upon receiving request.
+ */
+ oneway void onTranscodingPaused(in int jobId);
+
+ /**
+ * Called when the transcoding associated with the jobId is resumed.
+ * This will only be called if client request to get all the status of the job.
+ *
+ * @param jobId jobId assigned by the MediaTranscodingService upon receiving request.
+ */
+ oneway void onTranscodingResumed(in int jobId);
/**
* Called when the transcoding associated with the jobId finished.
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingErrorCode.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingErrorCode.aidl
index 7f47fdc..b044d41 100644
--- a/media/libmediatranscoding/aidl/android/media/TranscodingErrorCode.aidl
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingErrorCode.aidl
@@ -23,11 +23,12 @@
*/
@Backing(type = "int")
enum TranscodingErrorCode {
- kUnknown = 0,
- kUnsupported = 1,
- kDecoderError = 2,
- kEncoderError = 3,
- kExtractorError = 4,
- kMuxerError = 5,
- kInvalidBitstream = 6
+ kNoError = 0,
+ kUnknown = 1,
+ kMalformed = 2,
+ kUnsupported = 3,
+ kInvalidParameter = 4,
+ kInvalidOperation = 5,
+ kErrorIO = 6,
+ kInsufficientResources = 7,
}
\ No newline at end of file
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingJobParcel.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingJobParcel.aidl
index d912c38..baf4381 100644
--- a/media/libmediatranscoding/aidl/android/media/TranscodingJobParcel.aidl
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingJobParcel.aidl
@@ -17,6 +17,7 @@
package android.media;
import android.media.TranscodingRequestParcel;
+import android.media.TranscodingVideoTrackFormat;
/**
* TranscodingJob is generated by the MediaTranscodingService upon receiving a TranscodingRequest.
@@ -38,6 +39,12 @@
TranscodingRequestParcel request;
/**
+ * Output video track's format. This will only be avaiable for video transcoding and it will
+ * be avaiable when the job is finished.
+ */
+ @nullable TranscodingVideoTrackFormat videoTrackFormat;
+
+ /**
* Current number of jobs ahead of this job. The service schedules the job based on the priority
* passed from the client. Client could specify whether to receive updates when the
* awaitNumberOfJobs changes through setting requestProgressUpdate in the TranscodingRequest.
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingJobStats.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingJobStats.aidl
new file mode 100644
index 0000000..1b41b87
--- /dev/null
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingJobStats.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package android.media;
+
+/**
+ * TranscodingJobStats encapsulated the stats of the a TranscodingJob.
+ *
+ * {@hide}
+ */
+parcelable TranscodingJobStats {
+ /**
+ * System time of when the job is created.
+ */
+ long jobCreatedTimeUs;
+
+ /**
+ * System time of when the job is finished.
+ */
+ long jobFinishedTimeUs;
+
+ /**
+ * Total time spend on transcoding, exclude the time in pause.
+ */
+ long totalProcessingTimeUs;
+
+ /**
+ * Total time spend on handling the job, include the time in pause.
+ * The totaltimeUs is actually the same as jobFinishedTimeUs - jobCreatedTimeUs.
+ */
+ long totalTimeUs;
+}
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
index 7b7986d..83ea707 100644
--- a/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
@@ -17,7 +17,9 @@
package android.media;
import android.media.TranscodingJobPriority;
+import android.media.TranscodingTestConfig;
import android.media.TranscodingType;
+import android.media.TranscodingVideoTrackFormat;
/**
* TranscodingRequest contains the desired configuration for the transcoding.
@@ -27,9 +29,14 @@
//TODO(hkuang): Implement the parcelable.
parcelable TranscodingRequestParcel {
/**
- * Name of file to be transcoded.
+ * The absolute file path of the source file.
*/
- @utf8InCpp String fileName;
+ @utf8InCpp String sourceFilePath;
+
+ /**
+ * The absolute file path of the destination file.
+ */
+ @utf8InCpp String destinationFilePath;
/**
* Type of the transcoding.
@@ -37,14 +44,12 @@
TranscodingType transcodingType;
/**
- * Input source file descriptor.
+ * Requested video track format for the transcoding.
+ * Note that the transcoding service will try to fulfill the requested format as much as
+ * possbile, while subject to hardware and software limitation. The final video track format
+ * will be available in the TranscodingJobParcel when the job is finished.
*/
- ParcelFileDescriptor inFd;
-
- /**
- * Output transcoded file descriptor.
- */
- ParcelFileDescriptor outFd;
+ @nullable TranscodingVideoTrackFormat requestedVideoTrackFormat;
/**
* Priority of this transcoding. Service will schedule the transcoding based on the priority.
@@ -53,6 +58,30 @@
/**
* Whether to receive update on progress and change of awaitNumJobs.
+ * Default to false.
*/
- boolean requestUpdate;
+ boolean requestProgressUpdate = false;
+
+ /**
+ * Whether to receive update on job's start/stop/pause/resume.
+ * Default to false.
+ */
+ boolean requestJobEventUpdate = false;
+
+ /**
+ * Whether this request is for testing.
+ */
+ boolean isForTesting = false;
+
+ /**
+ * Test configuration. This will be available only when isForTesting is set to true.
+ */
+ @nullable TranscodingTestConfig testConfig;
+
+ /**
+ * Whether to get the stats of the transcoding.
+ * If this is enabled, the TranscodingJobStats will be returned in TranscodingResultParcel
+ * upon transcoding finishes.
+ */
+ boolean enableStats = false;
}
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingResultParcel.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingResultParcel.aidl
index 65c49e7..a20c8b1 100644
--- a/media/libmediatranscoding/aidl/android/media/TranscodingResultParcel.aidl
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingResultParcel.aidl
@@ -16,6 +16,8 @@
package android.media;
+import android.media.TranscodingJobStats;
+
/**
* Result of the transcoding.
*
@@ -34,5 +36,9 @@
*/
int actualBitrateBps;
- // TODO(hkuang): Add more fields.
+ /**
+ * Stats of the transcoding job. This will only be available when client requests to get the
+ * stats in TranscodingRequestParcel.
+ */
+ @nullable TranscodingJobStats jobStats;
}
\ No newline at end of file
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingTestConfig.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingTestConfig.aidl
new file mode 100644
index 0000000..a564799
--- /dev/null
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingTestConfig.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+ package android.media;
+
+ /**
+ * TranscodingTestConfig contains the test configureation used in testing.
+ *
+ * {@hide}
+ */
+parcelable TranscodingTestConfig {
+ /**
+ * Whether to use SimulatedTranscoder for testing. Note that SimulatedTranscoder does not send
+ * transcoding jobs to real MediaTranscoder.
+ */
+ boolean useSimulatedTranscoder = false;
+
+ /**
+ * Passthrough mode used for testing. The transcoding service will assume the destination
+ * path already contains the transcoding of the source file and return it to client directly.
+ */
+ boolean passThroughMode = false;
+
+ /**
+ * Time of processing the job in milliseconds. Service will return the job result at least after
+ * processingTotalTimeMs from the time it starts to process the job. Note that if service uses
+ * real MediaTranscoder to do transcoding, the time spent on transcoding may be more than that.
+ */
+ int processingTotalTimeMs = 0;
+}
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingVideoTrackFormat.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingVideoTrackFormat.aidl
new file mode 100644
index 0000000..a567a95
--- /dev/null
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingVideoTrackFormat.aidl
@@ -0,0 +1,70 @@
+/**
+ * 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.
+ */
+
+package android.media;
+
+import android.media.TranscodingVideoCodecType;
+
+/**
+ * TranscodingVideoTrackFormat contains the video track format of a video.
+ *
+ * TODO(hkuang): Switch to PersistableBundle when b/156428735 is fixed or after we remove
+ * aidl_interface
+ *
+ * Note that TranscodingVideoTrackFormat is used in TranscodingRequestParcel for the client to
+ * specify the desired transcoded video format, and is also used in TranscodingJobParcel for the
+ * service to notify client of the final video format for transcoding.
+ * When used as input in TranscodingRequestParcel, the client only needs to specify the config that
+ * they want to change, e.g. codec or resolution, and all the missing configs will be extracted
+ * from the source video and applied to the destination video.
+ * When used as output in TranscodingJobParcel, all the configs will be populated to indicate the
+ * final encoder configs used for transcoding.
+ *
+ * {@hide}
+ */
+parcelable TranscodingVideoTrackFormat {
+ /**
+ * Video Codec type.
+ */
+ TranscodingVideoCodecType codecType; // TranscodingVideoCodecType::kUnspecified;
+
+ /**
+ * Width of the video in pixels. -1 means unavailable.
+ */
+ int width = -1;
+
+ /**
+ * Height of the video in pixels. -1 means unavailable.
+ */
+ int height = -1;
+
+ /**
+ * Bitrate in bits per second. -1 means unavailable.
+ */
+ int bitrateBps = -1;
+
+ /**
+ * Codec profile. This must be the same constant as used in MediaCodecInfo.CodecProfileLevel.
+ * -1 means unavailable.
+ */
+ int profile = -1;
+
+ /**
+ * Codec level. This must be the same constant as used in MediaCodecInfo.CodecProfileLevel.
+ * -1 means unavailable.
+ */
+ int level = -1;
+}
diff --git a/media/libmediatranscoding/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/build_and_run_all_unit_tests.sh
new file mode 100755
index 0000000..388e2ea
--- /dev/null
+++ b/media/libmediatranscoding/build_and_run_all_unit_tests.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# Script to run all transcoding related tests from subfolders.
+# Run script from this folder.
+#
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+fi
+
+# ensure we have mm
+. $ANDROID_BUILD_TOP/build/envsetup.sh
+
+mm
+
+echo "waiting for device"
+
+adb root && adb wait-for-device remount && adb sync
+SYNC_FINISHED=true
+
+# Run the transcoding service tests.
+pushd tests
+. build_and_run_all_unit_tests.sh
+popd
+
+# Run the transcoder tests.
+pushd transcoder/tests/
+. build_and_run_all_unit_tests.sh
+popd
+
diff --git a/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h b/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
index 0e8dcfd..9ca2ee9 100644
--- a/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
+++ b/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
@@ -38,7 +38,7 @@
*/
template <class T, class Comparator = std::less<T>>
class AdjustableMaxPriorityQueue {
- public:
+public:
typedef typename std::vector<T>::iterator iterator;
typedef typename std::vector<T>::const_iterator const_iterator;
@@ -104,7 +104,7 @@
/* Return the backbone storage of this PriorityQueue. Mainly used for debugging. */
const std::vector<T>& getStorage() const { return mHeap; };
- private:
+private:
std::vector<T> mHeap;
/* Implementation shared by both public push() methods. */
diff --git a/media/libmediatranscoding/include/media/SchedulerClientInterface.h b/media/libmediatranscoding/include/media/SchedulerClientInterface.h
new file mode 100644
index 0000000..e00cfb2
--- /dev/null
+++ b/media/libmediatranscoding/include/media/SchedulerClientInterface.h
@@ -0,0 +1,68 @@
+/*
+ * 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_MEDIA_SCHEDULER_CLIENT_INTERFACE_H
+#define ANDROID_MEDIA_SCHEDULER_CLIENT_INTERFACE_H
+
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
+#include <media/TranscodingDefs.h>
+
+namespace android {
+
+using ::aidl::android::media::ITranscodingClientCallback;
+using ::aidl::android::media::TranscodingRequestParcel;
+
+// Interface for a client to call the scheduler to schedule or retrieve
+// the status of a job.
+class SchedulerClientInterface {
+public:
+ /**
+ * Submits one request to the scheduler.
+ *
+ * Returns true on success and false on failure. This call will fail is a job identified
+ * by <clientId, jobId> already exists.
+ */
+ virtual bool submit(ClientIdType clientId, JobIdType jobId, uid_t uid,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& clientCallback) = 0;
+
+ /**
+ * Cancels a job identified by <clientId, jobId>.
+ *
+ * If jobId is negative (<0), all jobs with a specified priority (that's not
+ * TranscodingJobPriority::kUnspecified) will be cancelled. Otherwise, only the single job
+ * <clientId, jobId> will be cancelled.
+ *
+ * Returns false if a single job is being cancelled but it doesn't exist. Returns
+ * true otherwise.
+ */
+ virtual bool cancel(ClientIdType clientId, JobIdType jobId) = 0;
+
+ /**
+ * Retrieves information about a job.
+ *
+ * Returns true and the job if it exists, and false otherwise.
+ */
+ virtual bool getJob(ClientIdType clientId, JobIdType jobId,
+ TranscodingRequestParcel* request) = 0;
+
+protected:
+ virtual ~SchedulerClientInterface() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SCHEDULER_CLIENT_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscoderInterface.h b/media/libmediatranscoding/include/media/TranscoderInterface.h
new file mode 100644
index 0000000..1a3f505
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscoderInterface.h
@@ -0,0 +1,72 @@
+/*
+ * 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_MEDIA_TRANSCODER_INTERFACE_H
+#define ANDROID_MEDIA_TRANSCODER_INTERFACE_H
+
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <aidl/android/media/TranscodingErrorCode.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
+#include <media/TranscodingDefs.h>
+
+namespace android {
+
+using ::aidl::android::media::ITranscodingClientCallback;
+using ::aidl::android::media::TranscodingErrorCode;
+using ::aidl::android::media::TranscodingRequestParcel;
+class TranscoderCallbackInterface;
+
+// Interface for the scheduler to call the transcoder to take actions.
+class TranscoderInterface {
+public:
+ virtual void setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) = 0;
+ virtual void start(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) = 0;
+ virtual void pause(ClientIdType clientId, JobIdType jobId) = 0;
+ virtual void resume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) = 0;
+ virtual void stop(ClientIdType clientId, JobIdType jobId) = 0;
+
+protected:
+ virtual ~TranscoderInterface() = default;
+};
+
+// Interface for the transcoder to notify the scheduler of the status of
+// the currently running job, or temporary loss of transcoding resources.
+class TranscoderCallbackInterface {
+public:
+ // TODO(chz): determine what parameters are needed here.
+ virtual void onStarted(ClientIdType clientId, JobIdType jobId) = 0;
+ virtual void onPaused(ClientIdType clientId, JobIdType jobId) = 0;
+ virtual void onResumed(ClientIdType clientId, JobIdType jobId) = 0;
+ virtual void onFinish(ClientIdType clientId, JobIdType jobId) = 0;
+ virtual void onError(ClientIdType clientId, JobIdType jobId, TranscodingErrorCode err) = 0;
+ virtual void onProgressUpdate(ClientIdType clientId, JobIdType jobId, int32_t progress) = 0;
+
+ // Called when transcoding becomes temporarily inaccessible due to loss of resource.
+ // If there is any job currently running, it will be paused. When resource contention
+ // is solved, the scheduler should call TranscoderInterface's to either start a new job,
+ // or resume a paused job.
+ virtual void onResourceLost() = 0;
+
+protected:
+ virtual ~TranscoderCallbackInterface() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODER_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscoderWrapper.h b/media/libmediatranscoding/include/media/TranscoderWrapper.h
new file mode 100644
index 0000000..a4c92c5
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscoderWrapper.h
@@ -0,0 +1,95 @@
+/*
+ * 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_TRANSCODER_WRAPPER_H
+#define ANDROID_TRANSCODER_WRAPPER_H
+
+#include <android-base/thread_annotations.h>
+#include <media/TranscoderInterface.h>
+
+#include <list>
+#include <map>
+#include <mutex>
+
+namespace android {
+
+class MediaTranscoder;
+class Parcelable;
+
+/*
+ * Wrapper class around MediaTranscoder.
+ * Implements TranscoderInterface for TranscodingJobScheduler to use.
+ */
+class TranscoderWrapper : public TranscoderInterface,
+ public std::enable_shared_from_this<TranscoderWrapper> {
+public:
+ TranscoderWrapper();
+
+ virtual void setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) override;
+ virtual void start(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
+ virtual void pause(ClientIdType clientId, JobIdType jobId) override;
+ virtual void resume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
+ virtual void stop(ClientIdType clientId, JobIdType jobId) override;
+
+private:
+ class CallbackImpl;
+ struct Event {
+ enum Type { NoEvent, Start, Pause, Resume, Stop, Finish, Error, Progress } type;
+ ClientIdType clientId;
+ JobIdType jobId;
+ std::function<void()> runnable;
+ };
+ using JobKeyType = std::pair<ClientIdType, JobIdType>;
+
+ std::shared_ptr<CallbackImpl> mTranscoderCb;
+ std::shared_ptr<MediaTranscoder> mTranscoder;
+ std::weak_ptr<TranscoderCallbackInterface> mCallback;
+ std::mutex mLock;
+ std::condition_variable mCondition;
+ std::list<Event> mQueue; // GUARDED_BY(mLock);
+ std::map<JobKeyType, std::shared_ptr<const Parcel>> mPausedStateMap;
+ ClientIdType mCurrentClientId;
+ JobIdType mCurrentJobId;
+
+ static const char* toString(Event::Type type);
+ void onFinish(ClientIdType clientId, JobIdType jobId);
+ void onError(ClientIdType clientId, JobIdType jobId, TranscodingErrorCode error);
+ void onProgress(ClientIdType clientId, JobIdType jobId, int32_t progress);
+
+ TranscodingErrorCode handleStart(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& callback);
+ TranscodingErrorCode handlePause(ClientIdType clientId, JobIdType jobId);
+ TranscodingErrorCode handleResume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& callback);
+ TranscodingErrorCode setupTranscoder(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& callback,
+ const std::shared_ptr<const Parcel>& pausedState = nullptr);
+
+ void cleanup();
+ void queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
+ const std::function<void()> runnable);
+ void threadLoop();
+};
+
+} // namespace android
+#endif // ANDROID_TRANSCODER_WRAPPER_H
diff --git a/media/libmediatranscoding/include/media/TranscodingClientManager.h b/media/libmediatranscoding/include/media/TranscodingClientManager.h
index eec120a..a62ad8c 100644
--- a/media/libmediatranscoding/include/media/TranscodingClientManager.h
+++ b/media/libmediatranscoding/include/media/TranscodingClientManager.h
@@ -17,73 +17,76 @@
#ifndef ANDROID_MEDIA_TRANSCODING_CLIENT_MANAGER_H
#define ANDROID_MEDIA_TRANSCODING_CLIENT_MANAGER_H
-#include <aidl/android/media/BnTranscodingServiceClient.h>
-#include <android/binder_ibinder.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
#include <sys/types.h>
#include <utils/Condition.h>
-#include <utils/RefBase.h>
#include <utils/String8.h>
#include <utils/Vector.h>
+#include <map>
#include <mutex>
#include <unordered_map>
+#include <unordered_set>
+
+#include "SchedulerClientInterface.h"
namespace android {
-using ::aidl::android::media::ITranscodingServiceClient;
-
-class MediaTranscodingService;
+using ::aidl::android::media::ITranscodingClient;
+using ::aidl::android::media::ITranscodingClientCallback;
/*
* TranscodingClientManager manages all the transcoding clients across different processes.
*
- * TranscodingClientManager is a global singleton that could only acquired by
- * MediaTranscodingService. It manages all the clients's registration/unregistration and clients'
- * information. It also bookkeeps all the clients' information. It also monitors to the death of the
+ * TranscodingClientManager manages all the clients's registration/unregistration and clients'
+ * information. It also bookkeeps all the clients' information. It also monitors the death of the
* clients. Upon client's death, it will remove the client from it.
*
* TODO(hkuang): Hook up with ResourceManager for resource management.
* TODO(hkuang): Hook up with MediaMetrics to log all the transactions.
*/
-class TranscodingClientManager {
- public:
+class TranscodingClientManager : public std::enable_shared_from_this<TranscodingClientManager> {
+public:
virtual ~TranscodingClientManager();
/**
- * ClientInfo contains a single client's information.
- */
- struct ClientInfo {
- /* The remote client that this ClientInfo is associated with. */
- std::shared_ptr<ITranscodingServiceClient> mClient;
- /* A unique positive Id assigned to the client by the service. */
- int32_t mClientId;
- /* Process id of the client */
- int32_t mClientPid;
- /* User id of the client. */
- int32_t mClientUid;
- /* Package name of the client. */
- std::string mClientOpPackageName;
-
- ClientInfo(const std::shared_ptr<ITranscodingServiceClient>& client, int64_t clientId,
- int32_t pid, int32_t uid, const std::string& opPackageName)
- : mClient(client),
- mClientId(clientId),
- mClientPid(pid),
- mClientUid(uid),
- mClientOpPackageName(opPackageName) {}
- };
-
- /**
* Adds a new client to the manager.
*
- * The client must have valid clientId, pid, uid and opPackageName, otherwise, this will return
- * a non-zero errorcode. If the client has already been added, it will also return non-zero
- * errorcode.
+ * The client must have valid callback, pid, uid, clientName and opPackageName.
+ * Otherwise, this will return a non-zero errorcode. If the client callback has
+ * already been added, it will also return non-zero errorcode.
*
- * @param client to be added to the manager.
+ * @param callback client callback for the service to call this client.
+ * @param pid client's process id.
+ * @param uid client's user id.
+ * @param clientName client's name.
+ * @param opPackageName client's package name.
+ * @param client output holding the ITranscodingClient interface for the client
+ * to use for subsequent communications with the service.
* @return 0 if client is added successfully, non-zero errorcode otherwise.
*/
- status_t addClient(std::unique_ptr<ClientInfo> client);
+ status_t addClient(const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid,
+ uid_t uid, const std::string& clientName, const std::string& opPackageName,
+ std::shared_ptr<ITranscodingClient>* client);
+
+ /**
+ * Gets the number of clients.
+ */
+ size_t getNumOfClients() const;
+
+ /**
+ * Dump all the client information to the fd.
+ */
+ void dumpAllClients(int fd, const Vector<String16>& args);
+
+private:
+ friend class MediaTranscodingService;
+ friend class TranscodingClientManagerTest;
+ struct ClientImpl;
+
+ // Only allow MediaTranscodingService and unit tests to instantiate.
+ TranscodingClientManager(const std::shared_ptr<SchedulerClientInterface>& scheduler);
/**
* Removes an existing client from the manager.
@@ -93,39 +96,23 @@
* @param clientId id of the client to be removed..
* @return 0 if client is removed successfully, non-zero errorcode otherwise.
*/
- status_t removeClient(int32_t clientId);
-
- /**
- * Gets the number of clients.
- */
- size_t getNumOfClients() const;
-
- /**
- * Checks if a client with clientId is already registered.
- */
- bool isClientIdRegistered(int32_t clientId) const;
-
- /**
- * Dump all the client information to the fd.
- */
- void dumpAllClients(int fd, const Vector<String16>& args);
-
- private:
- friend class MediaTranscodingService;
- friend class TranscodingClientManagerTest;
-
- /** Get the singleton instance of the TranscodingClientManager. */
- static TranscodingClientManager& getInstance();
-
- TranscodingClientManager();
+ status_t removeClient(ClientIdType clientId);
static void BinderDiedCallback(void* cookie);
mutable std::mutex mLock;
- std::unordered_map<int32_t, std::unique_ptr<ClientInfo>> mClientIdToClientInfoMap
+ std::unordered_map<ClientIdType, std::shared_ptr<ClientImpl>> mClientIdToClientMap
GUARDED_BY(mLock);
+ std::unordered_set<uintptr_t> mRegisteredCallbacks GUARDED_BY(mLock);
::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+
+ std::shared_ptr<SchedulerClientInterface> mJobScheduler;
+
+ static std::atomic<ClientIdType> sCookieCounter;
+ static std::mutex sCookie2ClientLock;
+ static std::map<ClientIdType, std::shared_ptr<ClientImpl>> sCookie2Client
+ GUARDED_BY(sCookie2ClientLock);
};
} // namespace android
diff --git a/media/libmediatranscoding/include/media/TranscodingDefs.h b/media/libmediatranscoding/include/media/TranscodingDefs.h
new file mode 100644
index 0000000..31d83ac
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingDefs.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 ANDROID_MEDIA_TRANSCODING_DEFS_H
+#define ANDROID_MEDIA_TRANSCODING_DEFS_H
+
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
+
+namespace android {
+
+using ClientIdType = uintptr_t;
+using JobIdType = int32_t;
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODING_DEFS_H
diff --git a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
new file mode 100644
index 0000000..5ccadad
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
@@ -0,0 +1,120 @@
+/*
+ * 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_MEDIA_TRANSCODING_JOB_SCHEDULER_H
+#define ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
+
+#include <aidl/android/media/TranscodingJobPriority.h>
+#include <media/SchedulerClientInterface.h>
+#include <media/TranscoderInterface.h>
+#include <media/TranscodingRequest.h>
+#include <media/UidPolicyInterface.h>
+#include <utils/String8.h>
+
+#include <list>
+#include <map>
+#include <mutex>
+
+namespace android {
+using ::aidl::android::media::TranscodingJobPriority;
+using ::aidl::android::media::TranscodingResultParcel;
+
+class TranscodingJobScheduler : public UidPolicyCallbackInterface,
+ public SchedulerClientInterface,
+ public TranscoderCallbackInterface {
+public:
+ virtual ~TranscodingJobScheduler();
+
+ // SchedulerClientInterface
+ bool submit(ClientIdType clientId, JobIdType jobId, uid_t uid,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override;
+ bool cancel(ClientIdType clientId, JobIdType jobId) override;
+ bool getJob(ClientIdType clientId, JobIdType jobId, TranscodingRequestParcel* request) override;
+ // ~SchedulerClientInterface
+
+ // TranscoderCallbackInterface
+ void onStarted(ClientIdType clientId, JobIdType jobId) override;
+ void onPaused(ClientIdType clientId, JobIdType jobId) override;
+ void onResumed(ClientIdType clientId, JobIdType jobId) override;
+ void onFinish(ClientIdType clientId, JobIdType jobId) override;
+ void onError(ClientIdType clientId, JobIdType jobId, TranscodingErrorCode err) override;
+ void onProgressUpdate(ClientIdType clientId, JobIdType jobId, int32_t progress) override;
+ void onResourceLost() override;
+ // ~TranscoderCallbackInterface
+
+ // UidPolicyCallbackInterface
+ void onTopUidsChanged(const std::unordered_set<uid_t>& uids) override;
+ void onResourceAvailable() override;
+ // ~UidPolicyCallbackInterface
+
+private:
+ friend class MediaTranscodingService;
+ friend class TranscodingJobSchedulerTest;
+
+ using JobKeyType = std::pair<ClientIdType, JobIdType>;
+ using JobQueueType = std::list<JobKeyType>;
+
+ struct Job {
+ JobKeyType key;
+ uid_t uid;
+ enum JobState {
+ NOT_STARTED,
+ RUNNING,
+ PAUSED,
+ } state;
+ TranscodingRequest request;
+ std::weak_ptr<ITranscodingClientCallback> callback;
+ };
+
+ // TODO(chz): call transcoder without global lock.
+ // Use mLock for all entrypoints for now.
+ mutable std::mutex mLock;
+
+ std::map<JobKeyType, Job> mJobMap;
+
+ // uid->JobQueue map (uid == -1: offline queue)
+ std::map<uid_t, JobQueueType> mJobQueues;
+
+ // uids, with the head being the most-recently-top app, 2nd item is the
+ // previous top app, etc.
+ std::list<uid_t> mUidSortedList;
+ std::list<uid_t>::iterator mOfflineUidIterator;
+
+ std::shared_ptr<TranscoderInterface> mTranscoder;
+ std::shared_ptr<UidPolicyInterface> mUidPolicy;
+
+ Job* mCurrentJob;
+ bool mResourceLost;
+
+ // Only allow MediaTranscodingService and unit tests to instantiate.
+ TranscodingJobScheduler(const std::shared_ptr<TranscoderInterface>& transcoder,
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy);
+
+ Job* getTopJob_l();
+ void updateCurrentJob_l();
+ void removeJob_l(const JobKeyType& jobKey);
+ void moveUidsToTop_l(const std::unordered_set<uid_t>& uids, bool preserveTopUid);
+ void notifyClient(ClientIdType clientId, JobIdType jobId, const char* reason,
+ std::function<void(const JobKeyType&)> func);
+ // Internal state verifier (debug only)
+ void validateState_l();
+
+ static String8 jobToString(const JobKeyType& jobKey);
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
diff --git a/media/libmediatranscoding/include/media/TranscodingRequest.h b/media/libmediatranscoding/include/media/TranscodingRequest.h
new file mode 100644
index 0000000..63de1fb
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingRequest.h
@@ -0,0 +1,51 @@
+/*
+ * 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_MEDIA_TRANSCODING_REQUEST_H
+#define ANDROID_MEDIA_TRANSCODING_REQUEST_H
+
+#include <aidl/android/media/TranscodingRequestParcel.h>
+
+namespace android {
+
+using ::aidl::android::media::TranscodingRequestParcel;
+
+// Helper class for duplicating a TranscodingRequestParcel
+class TranscodingRequest : public TranscodingRequestParcel {
+public:
+ TranscodingRequest() = default;
+ TranscodingRequest(const TranscodingRequestParcel& parcel) { setTo(parcel); }
+ TranscodingRequest& operator=(const TranscodingRequest& request) {
+ setTo(request);
+ return *this;
+ }
+
+private:
+ void setTo(const TranscodingRequestParcel& parcel) {
+ sourceFilePath = parcel.sourceFilePath;
+ destinationFilePath = parcel.destinationFilePath;
+ transcodingType = parcel.transcodingType;
+ requestedVideoTrackFormat = parcel.requestedVideoTrackFormat;
+ priority = parcel.priority;
+ requestProgressUpdate = parcel.requestProgressUpdate;
+ requestJobEventUpdate = parcel.requestJobEventUpdate;
+ isForTesting = parcel.isForTesting;
+ testConfig = parcel.testConfig;
+ }
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODING_REQUEST_H
diff --git a/media/libmediatranscoding/include/media/TranscodingUidPolicy.h b/media/libmediatranscoding/include/media/TranscodingUidPolicy.h
new file mode 100644
index 0000000..27dadd2
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingUidPolicy.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.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODING_UID_POLICY_H
+#define ANDROID_MEDIA_TRANSCODING_UID_POLICY_H
+
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <media/UidPolicyInterface.h>
+#include <sys/types.h>
+#include <utils/Condition.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <map>
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+
+class ActivityManager;
+// Observer for UID lifecycle and provide information about the uid's app
+// priority used by the job scheduler.
+class TranscodingUidPolicy : public UidPolicyInterface {
+public:
+ explicit TranscodingUidPolicy();
+ ~TranscodingUidPolicy();
+
+ // UidPolicyInterface
+ bool isUidOnTop(uid_t uid) override;
+ void registerMonitorUid(uid_t uid) override;
+ void unregisterMonitorUid(uid_t uid) override;
+ std::unordered_set<uid_t> getTopUids() const override;
+ void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) override;
+
+private:
+ void onUidStateChanged(uid_t uid, int32_t procState);
+ void setUidObserverRegistered(bool registerd);
+ void registerSelf();
+ void unregisterSelf();
+ int32_t getProcState_l(uid_t uid) NO_THREAD_SAFETY_ANALYSIS;
+ void updateTopUid_l() NO_THREAD_SAFETY_ANALYSIS;
+
+ struct UidObserver;
+ mutable Mutex mUidLock;
+ std::shared_ptr<ActivityManager> mAm;
+ sp<UidObserver> mUidObserver;
+ bool mRegistered GUARDED_BY(mUidLock);
+ int32_t mTopUidState GUARDED_BY(mUidLock);
+ std::unordered_map<uid_t, int32_t> mUidStateMap GUARDED_BY(mUidLock);
+ std::map<int32_t, std::unordered_set<uid_t>> mStateUidMap GUARDED_BY(mUidLock);
+ std::weak_ptr<UidPolicyCallbackInterface> mUidPolicyCallback;
+}; // class TranscodingUidPolicy
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODING_SERVICE_H
diff --git a/media/libmediatranscoding/include/media/UidPolicyInterface.h b/media/libmediatranscoding/include/media/UidPolicyInterface.h
new file mode 100644
index 0000000..dc28027
--- /dev/null
+++ b/media/libmediatranscoding/include/media/UidPolicyInterface.h
@@ -0,0 +1,62 @@
+/*
+ * 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_MEDIA_UID_POLICY_INTERFACE_H
+#define ANDROID_MEDIA_UID_POLICY_INTERFACE_H
+
+#include <unordered_set>
+
+namespace android {
+
+class UidPolicyCallbackInterface;
+
+// Interface for the scheduler to query a uid's info.
+class UidPolicyInterface {
+public:
+ // Instruct the uid policy to start monitoring a uid.
+ virtual void registerMonitorUid(uid_t uid) = 0;
+ // Instruct the uid policy to stop monitoring a uid.
+ virtual void unregisterMonitorUid(uid_t uid) = 0;
+ // Whether a uid is among the set of uids that's currently top priority.
+ virtual bool isUidOnTop(uid_t uid) = 0;
+ // Retrieves the set of uids that's currently top priority.
+ virtual std::unordered_set<uid_t> getTopUids() const = 0;
+ // Set the associated callback interface to send the events when uid states change.
+ virtual void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) = 0;
+
+protected:
+ virtual ~UidPolicyInterface() = default;
+};
+
+// Interface for notifying the scheduler of a change in uid states or
+// transcoding resource availability.
+class UidPolicyCallbackInterface {
+public:
+ // Called when the set of uids that's top priority among the uids of interest
+ // has changed. The receiver of this callback should adjust accordingly.
+ virtual void onTopUidsChanged(const std::unordered_set<uid_t>& uids) = 0;
+
+ // Called when resources become available for transcoding use. The scheduler
+ // may use this as a signal to attempt restart transcoding activity that
+ // were previously paused due to temporary resource loss.
+ virtual void onResourceAvailable() = 0;
+
+protected:
+ virtual ~UidPolicyCallbackInterface() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_UID_POLICY_INTERFACE_H
diff --git a/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp b/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
index d58af4e..2e49f32 100644
--- a/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
+++ b/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
@@ -36,7 +36,7 @@
namespace android {
class IntUniquePtrComp {
- public:
+public:
bool operator()(const std::unique_ptr<int>& lhs, const std::unique_ptr<int>& rhs) const {
return *lhs < *rhs;
}
@@ -233,7 +233,7 @@
// The job is arranging according to priority with highest priority comes first.
// For the job with the same priority, the job with early createTime will come first.
class TranscodingJobComp {
- public:
+ public:
bool operator()(const std::unique_ptr<TranscodingJob>& lhs,
const std::unique_ptr<TranscodingJob>& rhs) const {
if (lhs->priority != rhs->priority) {
diff --git a/media/libmediatranscoding/tests/Android.bp b/media/libmediatranscoding/tests/Android.bp
index 8191b00..b54022a 100644
--- a/media/libmediatranscoding/tests/Android.bp
+++ b/media/libmediatranscoding/tests/Android.bp
@@ -38,6 +38,16 @@
}
//
+// TranscodingJobScheduler unit test
+//
+cc_test {
+ name: "TranscodingJobScheduler_tests",
+ defaults: ["libmediatranscoding_test_defaults"],
+
+ srcs: ["TranscodingJobScheduler_tests.cpp"],
+}
+
+//
// AdjustableMaxPriorityQueue unit test
//
cc_test {
@@ -45,4 +55,4 @@
defaults: ["libmediatranscoding_test_defaults"],
srcs: ["AdjustableMaxPriorityQueue_tests.cpp"],
-}
\ No newline at end of file
+}
diff --git a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
index 5d2419d..1583325 100644
--- a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
@@ -19,52 +19,66 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "TranscodingClientManagerTest"
-#include <aidl/android/media/BnTranscodingServiceClient.h>
+#include <aidl/android/media/BnTranscodingClientCallback.h>
#include <aidl/android/media/IMediaTranscodingService.h>
-#include <aidl/android/media/ITranscodingServiceClient.h>
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <gtest/gtest.h>
+#include <media/SchedulerClientInterface.h>
#include <media/TranscodingClientManager.h>
+#include <media/TranscodingRequest.h>
#include <utils/Log.h>
+#include <list>
+
namespace android {
using Status = ::ndk::ScopedAStatus;
-using aidl::android::media::BnTranscodingServiceClient;
-using aidl::android::media::IMediaTranscodingService;
-using aidl::android::media::ITranscodingServiceClient;
+using ::aidl::android::media::BnTranscodingClientCallback;
+using ::aidl::android::media::IMediaTranscodingService;
+using ::aidl::android::media::TranscodingErrorCode;
+using ::aidl::android::media::TranscodingJobParcel;
+using ::aidl::android::media::TranscodingJobPriority;
+using ::aidl::android::media::TranscodingRequestParcel;
+using ::aidl::android::media::TranscodingResultParcel;
-constexpr int32_t kInvalidClientId = -1;
-constexpr int32_t kInvalidClientPid = -1;
-constexpr int32_t kInvalidClientUid = -1;
-constexpr const char* kInvalidClientOpPackageName = "";
+constexpr pid_t kInvalidClientPid = -1;
+constexpr const char* kInvalidClientName = "";
+constexpr const char* kInvalidClientPackage = "";
-constexpr int32_t kClientId = 1;
-constexpr int32_t kClientPid = 2;
-constexpr int32_t kClientUid = 3;
-constexpr const char* kClientOpPackageName = "TestClient";
+constexpr pid_t kClientPid = 2;
+constexpr uid_t kClientUid = 3;
+constexpr const char* kClientName = "TestClientName";
+constexpr const char* kClientPackage = "TestClientPackage";
-struct TestClient : public BnTranscodingServiceClient {
- TestClient(const std::shared_ptr<IMediaTranscodingService>& service) : mService(service) {
- ALOGD("TestClient Created");
- }
+#define JOB(n) (n)
- Status getName(std::string* _aidl_return) override {
- *_aidl_return = "test_client";
+struct TestClientCallback : public BnTranscodingClientCallback {
+ TestClientCallback() { ALOGI("TestClientCallback Created"); }
+
+ virtual ~TestClientCallback() { ALOGI("TestClientCallback destroyed"); };
+
+ Status openFileDescriptor(const std::string& /*in_fileUri*/, const std::string& /*in_mode*/,
+ ::ndk::ScopedFileDescriptor* /*_aidl_return*/) override {
return Status::ok();
}
- Status onTranscodingFinished(
- int32_t /* in_jobId */,
- const ::aidl::android::media::TranscodingResultParcel& /* in_result */) override {
+ Status onTranscodingStarted(int32_t /*in_jobId*/) override { return Status::ok(); }
+
+ Status onTranscodingPaused(int32_t /*in_jobId*/) override { return Status::ok(); }
+
+ Status onTranscodingResumed(int32_t /*in_jobId*/) override { return Status::ok(); }
+
+ Status onTranscodingFinished(int32_t in_jobId,
+ const TranscodingResultParcel& in_result) override {
+ EXPECT_EQ(in_jobId, in_result.jobId);
+ mEventQueue.push_back(Finished(in_jobId));
return Status::ok();
}
- Status onTranscodingFailed(
- int32_t /* in_jobId */,
- ::aidl::android::media::TranscodingErrorCode /*in_errorCode */) override {
+ Status onTranscodingFailed(int32_t in_jobId, TranscodingErrorCode /*in_errorCode */) override {
+ mEventQueue.push_back(Failed(in_jobId));
return Status::ok();
}
@@ -77,204 +91,444 @@
return Status::ok();
}
- virtual ~TestClient() { ALOGI("TestClient destroyed"); };
+ struct Event {
+ enum {
+ NoEvent,
+ Finished,
+ Failed,
+ } type;
+ JobIdType jobId;
+ };
- private:
- std::shared_ptr<IMediaTranscodingService> mService;
- TestClient(const TestClient&) = delete;
- TestClient& operator=(const TestClient&) = delete;
+ static constexpr Event NoEvent = {Event::NoEvent, 0};
+#define DECLARE_EVENT(action) \
+ static Event action(JobIdType jobId) { return {Event::action, jobId}; }
+
+ DECLARE_EVENT(Finished);
+ DECLARE_EVENT(Failed);
+
+ const Event& popEvent() {
+ if (mEventQueue.empty()) {
+ mPoppedEvent = NoEvent;
+ } else {
+ mPoppedEvent = *mEventQueue.begin();
+ mEventQueue.pop_front();
+ }
+ return mPoppedEvent;
+ }
+
+private:
+ Event mPoppedEvent;
+ std::list<Event> mEventQueue;
+
+ TestClientCallback(const TestClientCallback&) = delete;
+ TestClientCallback& operator=(const TestClientCallback&) = delete;
+};
+
+bool operator==(const TestClientCallback::Event& lhs, const TestClientCallback::Event& rhs) {
+ return lhs.type == rhs.type && lhs.jobId == rhs.jobId;
+}
+
+struct TestScheduler : public SchedulerClientInterface {
+ TestScheduler() { ALOGI("TestScheduler Created"); }
+
+ virtual ~TestScheduler() { ALOGI("TestScheduler Destroyed"); }
+
+ bool submit(ClientIdType clientId, JobIdType jobId, uid_t /*uid*/,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+ if (mJobs.count(jobKey) > 0) {
+ return false;
+ }
+
+ // This is the secret name we'll check, to test error propagation from
+ // the scheduler back to client.
+ if (request.sourceFilePath == "bad_source_file") {
+ return false;
+ }
+
+ mJobs[jobKey].request = request;
+ mJobs[jobKey].callback = clientCallback;
+
+ mLastJob = jobKey;
+ return true;
+ }
+
+ bool cancel(ClientIdType clientId, JobIdType jobId) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ if (mJobs.count(jobKey) == 0) {
+ return false;
+ }
+ mJobs.erase(jobKey);
+ return true;
+ }
+
+ bool getJob(ClientIdType clientId, JobIdType jobId,
+ TranscodingRequestParcel* request) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+ if (mJobs.count(jobKey) == 0) {
+ return false;
+ }
+
+ *(TranscodingRequest*)request = mJobs[jobKey].request;
+ return true;
+ }
+
+ void finishLastJob() {
+ auto it = mJobs.find(mLastJob);
+ if (it == mJobs.end()) {
+ return;
+ }
+ {
+ auto clientCallback = it->second.callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFinished(
+ mLastJob.second,
+ TranscodingResultParcel({mLastJob.second, 0, std::nullopt}));
+ }
+ }
+ mJobs.erase(it);
+ }
+
+ void abortLastJob() {
+ auto it = mJobs.find(mLastJob);
+ if (it == mJobs.end()) {
+ return;
+ }
+ {
+ auto clientCallback = it->second.callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFailed(mLastJob.second,
+ TranscodingErrorCode::kUnknown);
+ }
+ }
+ mJobs.erase(it);
+ }
+
+ struct Job {
+ TranscodingRequest request;
+ std::weak_ptr<ITranscodingClientCallback> callback;
+ };
+
+ typedef std::pair<ClientIdType, JobIdType> JobKeyType;
+ std::map<JobKeyType, Job> mJobs;
+ JobKeyType mLastJob;
};
class TranscodingClientManagerTest : public ::testing::Test {
- public:
- TranscodingClientManagerTest() : mClientManager(TranscodingClientManager::getInstance()) {
+public:
+ TranscodingClientManagerTest()
+ : mScheduler(new TestScheduler()),
+ mClientManager(new TranscodingClientManager(mScheduler)) {
ALOGD("TranscodingClientManagerTest created");
}
void SetUp() override {
- ::ndk::SpAIBinder binder(AServiceManager_getService("media.transcoding"));
- mService = IMediaTranscodingService::fromBinder(binder);
- if (mService == nullptr) {
- ALOGE("Failed to connect to the media.trascoding service.");
- return;
- }
-
- mTestClient = ::ndk::SharedRefBase::make<TestClient>(mService);
+ mClientCallback1 = ::ndk::SharedRefBase::make<TestClientCallback>();
+ mClientCallback2 = ::ndk::SharedRefBase::make<TestClientCallback>();
+ mClientCallback3 = ::ndk::SharedRefBase::make<TestClientCallback>();
}
- void TearDown() override {
- ALOGI("TranscodingClientManagerTest tear down");
- mService = nullptr;
- }
+ void TearDown() override { ALOGI("TranscodingClientManagerTest tear down"); }
~TranscodingClientManagerTest() { ALOGD("TranscodingClientManagerTest destroyed"); }
- TranscodingClientManager& mClientManager;
- std::shared_ptr<ITranscodingServiceClient> mTestClient = nullptr;
- std::shared_ptr<IMediaTranscodingService> mService = nullptr;
+ void addMultipleClients() {
+ EXPECT_EQ(mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient1),
+ OK);
+ EXPECT_NE(mClient1, nullptr);
+
+ EXPECT_EQ(mClientManager->addClient(mClientCallback2, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient2),
+ OK);
+ EXPECT_NE(mClient2, nullptr);
+
+ EXPECT_EQ(mClientManager->addClient(mClientCallback3, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient3),
+ OK);
+ EXPECT_NE(mClient3, nullptr);
+
+ EXPECT_EQ(mClientManager->getNumOfClients(), 3);
+ }
+
+ void unregisterMultipleClients() {
+ EXPECT_TRUE(mClient1->unregister().isOk());
+ EXPECT_TRUE(mClient2->unregister().isOk());
+ EXPECT_TRUE(mClient3->unregister().isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
+ }
+
+ std::shared_ptr<TestScheduler> mScheduler;
+ std::shared_ptr<TranscodingClientManager> mClientManager;
+ std::shared_ptr<ITranscodingClient> mClient1;
+ std::shared_ptr<ITranscodingClient> mClient2;
+ std::shared_ptr<ITranscodingClient> mClient3;
+ std::shared_ptr<TestClientCallback> mClientCallback1;
+ std::shared_ptr<TestClientCallback> mClientCallback2;
+ std::shared_ptr<TestClientCallback> mClientCallback3;
};
-TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientId) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid client id.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kInvalidClientId, kClientPid, kClientUid, kClientOpPackageName);
-
- // Add the client to the manager and expect failure.
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientCallback) {
+ // Add a client with null callback and expect failure.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(nullptr, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
+ EXPECT_EQ(err, IMediaTranscodingService::ERROR_ILLEGAL_ARGUMENT);
}
TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientPid) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid Pid.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kClientId, kInvalidClientPid, kClientUid, kClientOpPackageName);
-
- // Add the client to the manager and expect failure.
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+ // Add a client with invalid Pid and expect failure.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kInvalidClientPid, kClientUid,
+ kClientName, kClientPackage, &client);
+ EXPECT_EQ(err, IMediaTranscodingService::ERROR_ILLEGAL_ARGUMENT);
}
-TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientUid) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid Uid.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kClientId, kClientPid, kInvalidClientUid, kClientOpPackageName);
-
- // Add the client to the manager and expect failure.
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientName) {
+ // Add a client with invalid name and expect failure.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid,
+ kInvalidClientName, kClientPackage, &client);
+ EXPECT_EQ(err, IMediaTranscodingService::ERROR_ILLEGAL_ARGUMENT);
}
TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientPackageName) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid packagename.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kClientId, kClientPid, kClientUid, kInvalidClientOpPackageName);
-
- // Add the client to the manager and expect failure.
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+ // Add a client with invalid packagename and expect failure.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kInvalidClientPackage, &client);
+ EXPECT_EQ(err, IMediaTranscodingService::ERROR_ILLEGAL_ARGUMENT);
}
TEST_F(TranscodingClientManagerTest, TestAddingValidClient) {
- std::shared_ptr<ITranscodingServiceClient> client1 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
+ // Add a valid client, should succeed.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
+ EXPECT_EQ(err, OK);
+ EXPECT_NE(client.get(), nullptr);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client1, kClientId, kClientPid, kClientUid, kClientOpPackageName);
-
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err == OK);
-
- size_t numOfClients = mClientManager.getNumOfClients();
- EXPECT_EQ(numOfClients, 1);
-
- err = mClientManager.removeClient(kClientId);
- EXPECT_TRUE(err == OK);
+ // Unregister client, should succeed.
+ Status status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
}
TEST_F(TranscodingClientManagerTest, TestAddingDupliacteClient) {
- std::shared_ptr<ITranscodingServiceClient> client1 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
+ EXPECT_EQ(err, OK);
+ EXPECT_NE(client.get(), nullptr);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client1, kClientId, kClientPid, kClientUid, kClientOpPackageName);
+ std::shared_ptr<ITranscodingClient> dupClient;
+ err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, "dupClient",
+ "dupPackage", &dupClient);
+ EXPECT_EQ(err, IMediaTranscodingService::ERROR_ALREADY_EXISTS);
+ EXPECT_EQ(dupClient.get(), nullptr);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err == OK);
+ Status status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
- err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+ err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, "dupClient",
+ "dupPackage", &dupClient);
+ EXPECT_EQ(err, OK);
+ EXPECT_NE(dupClient.get(), nullptr);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- err = mClientManager.removeClient(kClientId);
- EXPECT_TRUE(err == OK);
+ status = dupClient->unregister();
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
}
TEST_F(TranscodingClientManagerTest, TestAddingMultipleClient) {
- std::shared_ptr<ITranscodingServiceClient> client1 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo1 =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client1, kClientId, kClientPid, kClientUid, kClientOpPackageName);
-
- status_t err = mClientManager.addClient(std::move(clientInfo1));
- EXPECT_TRUE(err == OK);
-
- std::shared_ptr<ITranscodingServiceClient> client2 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo2 =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client2, kClientId + 1, kClientPid, kClientUid, kClientOpPackageName);
-
- err = mClientManager.addClient(std::move(clientInfo2));
- EXPECT_TRUE(err == OK);
-
- std::shared_ptr<ITranscodingServiceClient> client3 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid packagename.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo3 =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client3, kClientId + 2, kClientPid, kClientUid, kClientOpPackageName);
-
- err = mClientManager.addClient(std::move(clientInfo3));
- EXPECT_TRUE(err == OK);
-
- size_t numOfClients = mClientManager.getNumOfClients();
- EXPECT_EQ(numOfClients, 3);
-
- err = mClientManager.removeClient(kClientId);
- EXPECT_TRUE(err == OK);
-
- err = mClientManager.removeClient(kClientId + 1);
- EXPECT_TRUE(err == OK);
-
- err = mClientManager.removeClient(kClientId + 2);
- EXPECT_TRUE(err == OK);
+ addMultipleClients();
+ unregisterMultipleClients();
}
-TEST_F(TranscodingClientManagerTest, TestRemovingNonExistClient) {
- status_t err = mClientManager.removeClient(kInvalidClientId);
- EXPECT_TRUE(err != OK);
+TEST_F(TranscodingClientManagerTest, TestSubmitCancelGetJobs) {
+ addMultipleClients();
- err = mClientManager.removeClient(1000 /* clientId */);
- EXPECT_TRUE(err != OK);
+ // Test jobId assignment.
+ TranscodingRequestParcel request;
+ request.sourceFilePath = "test_source_file_0";
+ request.destinationFilePath = "test_desintaion_file_0";
+ TranscodingJobParcel job;
+ bool result;
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(0));
+
+ request.sourceFilePath = "test_source_file_1";
+ request.destinationFilePath = "test_desintaion_file_1";
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(1));
+
+ request.sourceFilePath = "test_source_file_2";
+ request.destinationFilePath = "test_desintaion_file_2";
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(2));
+
+ // Test submit bad request (no valid sourceFilePath) fails.
+ TranscodingRequestParcel badRequest;
+ badRequest.sourceFilePath = "bad_source_file";
+ badRequest.destinationFilePath = "bad_destination_file";
+ EXPECT_TRUE(mClient1->submitRequest(badRequest, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test get jobs by id.
+ EXPECT_TRUE(mClient1->getJobWithId(JOB(2), &job, &result).isOk());
+ EXPECT_EQ(job.jobId, JOB(2));
+ EXPECT_EQ(job.request.sourceFilePath, "test_source_file_2");
+ EXPECT_TRUE(result);
+
+ // Test get jobs by invalid id fails.
+ EXPECT_TRUE(mClient1->getJobWithId(JOB(100), &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test cancel non-existent job fail.
+ EXPECT_TRUE(mClient2->cancelJob(JOB(100), &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test cancel valid jobId in arbitrary order.
+ EXPECT_TRUE(mClient1->cancelJob(JOB(2), &result).isOk());
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(mClient1->cancelJob(JOB(0), &result).isOk());
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(mClient1->cancelJob(JOB(1), &result).isOk());
+ EXPECT_TRUE(result);
+
+ // Test cancel job again fails.
+ EXPECT_TRUE(mClient1->cancelJob(JOB(1), &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test get job after cancel fails.
+ EXPECT_TRUE(mClient1->getJobWithId(JOB(2), &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test jobId independence for each client.
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(0));
+
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(1));
+
+ unregisterMultipleClients();
}
-TEST_F(TranscodingClientManagerTest, TestCheckClientWithClientId) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
+TEST_F(TranscodingClientManagerTest, TestClientCallback) {
+ addMultipleClients();
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kClientId, kClientPid, kClientUid, kClientOpPackageName);
+ TranscodingRequestParcel request;
+ request.sourceFilePath = "test_source_file_name";
+ request.destinationFilePath = "test_destination_file_name";
+ TranscodingJobParcel job;
+ bool result;
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(0));
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err == OK);
+ mScheduler->finishLastJob();
+ EXPECT_EQ(mClientCallback1->popEvent(), TestClientCallback::Finished(job.jobId));
- bool res = mClientManager.isClientIdRegistered(kClientId);
- EXPECT_TRUE(res);
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(1));
- res = mClientManager.isClientIdRegistered(kInvalidClientId);
- EXPECT_FALSE(res);
+ mScheduler->abortLastJob();
+ EXPECT_EQ(mClientCallback1->popEvent(), TestClientCallback::Failed(job.jobId));
+
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(2));
+
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, JOB(0));
+
+ mScheduler->finishLastJob();
+ EXPECT_EQ(mClientCallback2->popEvent(), TestClientCallback::Finished(job.jobId));
+
+ unregisterMultipleClients();
}
-} // namespace android
\ No newline at end of file
+TEST_F(TranscodingClientManagerTest, TestUseAfterUnregister) {
+ // Add a client.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
+ EXPECT_EQ(err, OK);
+ EXPECT_NE(client.get(), nullptr);
+
+ // Submit 2 requests, 1 offline and 1 realtime.
+ TranscodingRequestParcel request;
+ TranscodingJobParcel job;
+ bool result;
+
+ request.sourceFilePath = "test_source_file_0";
+ request.destinationFilePath = "test_destination_file_0";
+ request.priority = TranscodingJobPriority::kUnspecified;
+ EXPECT_TRUE(client->submitRequest(request, &job, &result).isOk() && result);
+ EXPECT_EQ(job.jobId, JOB(0));
+
+ request.sourceFilePath = "test_source_file_1";
+ request.destinationFilePath = "test_destination_file_1";
+ request.priority = TranscodingJobPriority::kNormal;
+ EXPECT_TRUE(client->submitRequest(request, &job, &result).isOk() && result);
+ EXPECT_EQ(job.jobId, JOB(1));
+
+ // Unregister client, should succeed.
+ Status status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ // Test submit new request after unregister, should fail with ERROR_DISCONNECTED.
+ request.sourceFilePath = "test_source_file_2";
+ request.destinationFilePath = "test_destination_file_2";
+ request.priority = TranscodingJobPriority::kNormal;
+ status = client->submitRequest(request, &job, &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+
+ // Test cancel jobs after unregister, should fail with ERROR_DISCONNECTED
+ // regardless of realtime or offline job, or whether the jobId is valid.
+ status = client->cancelJob(JOB(0), &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+
+ status = client->cancelJob(JOB(1), &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+
+ status = client->cancelJob(JOB(2), &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+
+ // Test get jobs, should fail with ERROR_DISCONNECTED regardless of realtime
+ // or offline job, or whether the jobId is valid.
+ status = client->getJobWithId(JOB(0), &job, &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+
+ status = client->getJobWithId(JOB(1), &job, &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+
+ status = client->getJobWithId(JOB(2), &job, &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
new file mode 100644
index 0000000..d21b595
--- /dev/null
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -0,0 +1,602 @@
+/*
+ * 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.
+ */
+
+// Unit Test for TranscodingJobScheduler
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "TranscodingJobSchedulerTest"
+
+#include <aidl/android/media/BnTranscodingClientCallback.h>
+#include <aidl/android/media/IMediaTranscodingService.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include <media/TranscodingClientManager.h>
+#include <media/TranscodingJobScheduler.h>
+#include <utils/Log.h>
+
+#include <unordered_set>
+
+namespace android {
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::media::BnTranscodingClientCallback;
+using aidl::android::media::IMediaTranscodingService;
+using aidl::android::media::ITranscodingClient;
+using aidl::android::media::TranscodingRequestParcel;
+
+constexpr ClientIdType kClientId = 1000;
+constexpr JobIdType kClientJobId = 0;
+constexpr uid_t kClientUid = 5000;
+constexpr uid_t kInvalidUid = (uid_t)-1;
+
+#define CLIENT(n) (kClientId + (n))
+#define JOB(n) (kClientJobId + (n))
+#define UID(n) (kClientUid + (n))
+
+class TestUidPolicy : public UidPolicyInterface {
+public:
+ TestUidPolicy() = default;
+ virtual ~TestUidPolicy() = default;
+
+ // UidPolicyInterface
+ void registerMonitorUid(uid_t /*uid*/) override {}
+ void unregisterMonitorUid(uid_t /*uid*/) override {}
+ bool isUidOnTop(uid_t uid) override { return mTopUids.count(uid) > 0; }
+ std::unordered_set<uid_t> getTopUids() const override { return mTopUids; }
+ void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) override {
+ mUidPolicyCallback = cb;
+ }
+ void setTop(uid_t uid) {
+ std::unordered_set<uid_t> uids = {uid};
+ setTop(uids);
+ }
+ void setTop(const std::unordered_set<uid_t>& uids) {
+ mTopUids = uids;
+ auto uidPolicyCb = mUidPolicyCallback.lock();
+ if (uidPolicyCb != nullptr) {
+ uidPolicyCb->onTopUidsChanged(mTopUids);
+ }
+ }
+
+ std::unordered_set<uid_t> mTopUids;
+ std::weak_ptr<UidPolicyCallbackInterface> mUidPolicyCallback;
+};
+
+class TestTranscoder : public TranscoderInterface {
+public:
+ TestTranscoder() : mLastError(TranscodingErrorCode::kUnknown) {}
+ virtual ~TestTranscoder() {}
+
+ // TranscoderInterface
+ void setCallback(const std::shared_ptr<TranscoderCallbackInterface>& /*cb*/) override {}
+
+ void start(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& /*request*/,
+ const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) override {
+ mEventQueue.push_back(Start(clientId, jobId));
+ }
+ void pause(ClientIdType clientId, JobIdType jobId) override {
+ mEventQueue.push_back(Pause(clientId, jobId));
+ }
+ void resume(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& /*request*/,
+ const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) override {
+ mEventQueue.push_back(Resume(clientId, jobId));
+ }
+ void stop(ClientIdType clientId, JobIdType jobId) override {
+ mEventQueue.push_back(Stop(clientId, jobId));
+ }
+
+ void onFinished(ClientIdType clientId, JobIdType jobId) {
+ mEventQueue.push_back(Finished(clientId, jobId));
+ }
+
+ void onFailed(ClientIdType clientId, JobIdType jobId, TranscodingErrorCode err) {
+ mLastError = err;
+ mEventQueue.push_back(Failed(clientId, jobId));
+ }
+
+ TranscodingErrorCode getLastError() {
+ TranscodingErrorCode result = mLastError;
+ mLastError = TranscodingErrorCode::kUnknown;
+ return result;
+ }
+
+ struct Event {
+ enum { NoEvent, Start, Pause, Resume, Stop, Finished, Failed } type;
+ ClientIdType clientId;
+ JobIdType jobId;
+ };
+
+ static constexpr Event NoEvent = {Event::NoEvent, 0, 0};
+
+#define DECLARE_EVENT(action) \
+ static Event action(ClientIdType clientId, JobIdType jobId) { \
+ return {Event::action, clientId, jobId}; \
+ }
+
+ DECLARE_EVENT(Start);
+ DECLARE_EVENT(Pause);
+ DECLARE_EVENT(Resume);
+ DECLARE_EVENT(Stop);
+ DECLARE_EVENT(Finished);
+ DECLARE_EVENT(Failed);
+
+ const Event& popEvent() {
+ if (mEventQueue.empty()) {
+ mPoppedEvent = NoEvent;
+ } else {
+ mPoppedEvent = *mEventQueue.begin();
+ mEventQueue.pop_front();
+ }
+ return mPoppedEvent;
+ }
+
+private:
+ Event mPoppedEvent;
+ std::list<Event> mEventQueue;
+ TranscodingErrorCode mLastError;
+};
+
+bool operator==(const TestTranscoder::Event& lhs, const TestTranscoder::Event& rhs) {
+ return lhs.type == rhs.type && lhs.clientId == rhs.clientId && lhs.jobId == rhs.jobId;
+}
+
+struct TestClientCallback : public BnTranscodingClientCallback {
+ TestClientCallback(TestTranscoder* owner, int64_t clientId)
+ : mOwner(owner), mClientId(clientId) {
+ ALOGD("TestClient Created");
+ }
+
+ Status openFileDescriptor(const std::string& /*in_fileUri*/, const std::string& /*in_mode*/,
+ ::ndk::ScopedFileDescriptor* /*_aidl_return*/) override {
+ return Status::ok();
+ }
+
+ Status onTranscodingStarted(int32_t /*in_jobId*/) override { return Status::ok(); }
+
+ Status onTranscodingPaused(int32_t /*in_jobId*/) override { return Status::ok(); }
+
+ Status onTranscodingResumed(int32_t /*in_jobId*/) override { return Status::ok(); }
+
+ Status onTranscodingFinished(int32_t in_jobId,
+ const TranscodingResultParcel& in_result) override {
+ EXPECT_EQ(in_jobId, in_result.jobId);
+ ALOGD("TestClientCallback: received onTranscodingFinished");
+ mOwner->onFinished(mClientId, in_jobId);
+ return Status::ok();
+ }
+
+ Status onTranscodingFailed(int32_t in_jobId, TranscodingErrorCode in_errorCode) override {
+ mOwner->onFailed(mClientId, in_jobId, in_errorCode);
+ return Status::ok();
+ }
+
+ Status onAwaitNumberOfJobsChanged(int32_t /* in_jobId */, int32_t /* in_oldAwaitNumber */,
+ int32_t /* in_newAwaitNumber */) override {
+ return Status::ok();
+ }
+
+ Status onProgressUpdate(int32_t /* in_jobId */, int32_t /* in_progress */) override {
+ return Status::ok();
+ }
+
+ virtual ~TestClientCallback() { ALOGI("TestClient destroyed"); };
+
+private:
+ TestTranscoder* mOwner;
+ int64_t mClientId;
+ TestClientCallback(const TestClientCallback&) = delete;
+ TestClientCallback& operator=(const TestClientCallback&) = delete;
+};
+
+class TranscodingJobSchedulerTest : public ::testing::Test {
+public:
+ TranscodingJobSchedulerTest() { ALOGI("TranscodingJobSchedulerTest created"); }
+
+ void SetUp() override {
+ ALOGI("TranscodingJobSchedulerTest set up");
+ mTranscoder.reset(new TestTranscoder());
+ mUidPolicy.reset(new TestUidPolicy());
+ mScheduler.reset(new TranscodingJobScheduler(mTranscoder, mUidPolicy));
+ mUidPolicy->setCallback(mScheduler);
+
+ // Set priority only, ignore other fields for now.
+ mOfflineRequest.priority = TranscodingJobPriority::kUnspecified;
+ mRealtimeRequest.priority = TranscodingJobPriority::kHigh;
+ mClientCallback0 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(0));
+ mClientCallback1 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(1));
+ mClientCallback2 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(2));
+ mClientCallback3 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(3));
+ }
+
+ void TearDown() override { ALOGI("TranscodingJobSchedulerTest tear down"); }
+
+ ~TranscodingJobSchedulerTest() { ALOGD("TranscodingJobSchedulerTest destroyed"); }
+
+ std::shared_ptr<TestTranscoder> mTranscoder;
+ std::shared_ptr<TestUidPolicy> mUidPolicy;
+ std::shared_ptr<TranscodingJobScheduler> mScheduler;
+ TranscodingRequestParcel mOfflineRequest;
+ TranscodingRequestParcel mRealtimeRequest;
+ std::shared_ptr<TestClientCallback> mClientCallback0;
+ std::shared_ptr<TestClientCallback> mClientCallback1;
+ std::shared_ptr<TestClientCallback> mClientCallback2;
+ std::shared_ptr<TestClientCallback> mClientCallback3;
+};
+
+TEST_F(TranscodingJobSchedulerTest, TestSubmitJob) {
+ ALOGD("TestSubmitJob");
+
+ // Start with UID(1) on top.
+ mUidPolicy->setTop(UID(1));
+
+ // Submit offline job to CLIENT(0) in UID(0).
+ // Should start immediately (because this is the only job).
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), 0));
+
+ // Submit real-time job to CLIENT(0).
+ // Should pause offline job and start new job, even if UID(0) is not on top.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job to CLIENT(0), should be queued after the previous job.
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit real-time job to CLIENT(1) in same uid, should be queued after the previous job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit real-time job to CLIENT(2) in UID(1).
+ // Should pause previous job and start new job, because UID(1) is (has been) top.
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
+
+ // Submit offline job, shouldn't generate any event.
+ mScheduler->submit(CLIENT(2), JOB(1), UID(1), mOfflineRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Bring UID(0) to top.
+ mUidPolicy->setTop(UID(0));
+ // Should pause current job, and resume last job in UID(0).
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(1)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestCancelJob) {
+ ALOGD("TestCancelJob");
+
+ // Submit real-time job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should not start.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit offline job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Cancel queued real-time job.
+ // Cancel real-time job JOB(1), should be cancelled.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(1)));
+
+ // Cancel queued offline job.
+ // Cancel offline job JOB(2), should be cancelled.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(2)));
+
+ // Submit offline job JOB(3), shouldn't cause any event.
+ mScheduler->submit(CLIENT(0), JOB(3), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Cancel running real-time job JOB(0).
+ // - Should be stopped first then cancelled.
+ // - Should also start offline job JOB(2) because real-time queue is empty.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Stop(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(3)));
+
+ // Submit real-time job JOB(4), offline JOB(3) should pause and JOB(4) should start.
+ mScheduler->submit(CLIENT(0), JOB(4), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(3)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(4)));
+
+ // Cancel paused JOB(3). JOB(3) should be stopped.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(3)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Stop(CLIENT(0), JOB(3)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestFinishJob) {
+ ALOGD("TestFinishJob");
+
+ // Start with unspecified top UID.
+ // Finish without any jobs submitted, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit offline job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should pause offline job and start immediately.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Finish when the job never started, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(2));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // UID(1) moves to top.
+ mUidPolicy->setTop(UID(1));
+ // Submit real-time job to CLIENT(1) in UID(1), should pause previous job and start new job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(1), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
+
+ // Simulate Finish that arrived late, after pause issued by scheduler.
+ // Should still be propagated to client, but shouldn't trigger any new start.
+ mScheduler->onFinish(CLIENT(0), JOB(1));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(1)));
+
+ // Finish running real-time job, should start next real-time job in queue.
+ mScheduler->onFinish(CLIENT(1), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(2)));
+
+ // Finish running real-time job, should resume next job (offline job) in queue.
+ mScheduler->onFinish(CLIENT(0), JOB(2));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
+
+ // Finish running offline job.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
+
+ // Duplicate finish for last job, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestFailJob) {
+ ALOGD("TestFailJob");
+
+ // Start with unspecified top UID.
+ // Fail without any jobs submitted, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit offline job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should pause offline job and start immediately.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Fail when the job never started, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // UID(1) moves to top.
+ mUidPolicy->setTop(UID(1));
+ // Submit real-time job to CLIENT(1) in UID(1), should pause previous job and start new job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(1), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
+
+ // Simulate Fail that arrived late, after pause issued by scheduler.
+ // Should still be propagated to client, but shouldn't trigger any new start.
+ mScheduler->onError(CLIENT(0), JOB(1), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(1)));
+
+ // Fail running real-time job, should start next real-time job in queue.
+ mScheduler->onError(CLIENT(1), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(2)));
+
+ // Fail running real-time job, should resume next job (offline job) in queue.
+ mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
+
+ // Fail running offline job, and test error code propagation.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kInvalidOperation);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->getLastError(), TranscodingErrorCode::kInvalidOperation);
+
+ // Duplicate fail for last job, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestTopUidChanged) {
+ ALOGD("TestTopUidChanged");
+
+ // Start with unspecified top UID.
+ // Submit real-time job to CLIENT(0), job should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
+
+ // Submit offline job to CLIENT(0), should not start.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Move UID(1) to top.
+ mUidPolicy->setTop(UID(1));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit real-time job to CLIENT(2) in different uid UID(1).
+ // Should pause previous job and start new job.
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
+
+ // Bring UID(0) back to top.
+ mUidPolicy->setTop(UID(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
+
+ // Bring invalid uid to top.
+ mUidPolicy->setTop(kInvalidUid);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Finish job, next real-time job should resume.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
+
+ // Finish job, offline job should start.
+ mScheduler->onFinish(CLIENT(2), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestTopUidSetChanged) {
+ ALOGD("TestTopUidChanged_MultipleUids");
+
+ // Start with unspecified top UID.
+ // Submit real-time job to CLIENT(0), job should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
+
+ // Submit offline job to CLIENT(0), should not start.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Set UID(0), UID(1) to top set.
+ // UID(0) should continue to run.
+ mUidPolicy->setTop({UID(0), UID(1)});
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit real-time job to CLIENT(2) in different uid UID(1).
+ // UID(0) should pause and UID(1) should start.
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
+
+ // Remove UID(0) from top set, and only leave UID(1) in the set.
+ // UID(1) should continue to run.
+ mUidPolicy->setTop(UID(1));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Set UID(0), UID(2) to top set.
+ // UID(1) should continue to run.
+ mUidPolicy->setTop({UID(1), UID(2)});
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Bring UID(0) back to top.
+ mUidPolicy->setTop(UID(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
+
+ // Bring invalid uid to top.
+ mUidPolicy->setTop(kInvalidUid);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Finish job, next real-time job from UID(1) should resume, even if UID(1) no longer top.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
+
+ // Finish job, offline job should start.
+ mScheduler->onFinish(CLIENT(2), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestResourceLost) {
+ ALOGD("TestResourceLost");
+
+ // Start with unspecified top UID.
+ // Submit real-time job to CLIENT(0), job should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
+
+ // Submit offline job to CLIENT(0), should not start.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Move UID(1) to top.
+ mUidPolicy->setTop(UID(1));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit real-time job to CLIENT(2) in different uid UID(1).
+ // Should pause previous job and start new job.
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
+
+ // Test 1: No queue change during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Signal resource available, CLIENT(2) should resume.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
+
+ // Test 2: Change of queue order during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Move UID(0) back to top, should have no resume due to no resource.
+ mUidPolicy->setTop(UID(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Signal resource available, CLIENT(0) should resume.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
+
+ // Test 3: Adding new queue during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Move UID(2) to top.
+ mUidPolicy->setTop(UID(2));
+
+ // Submit real-time job to CLIENT(3) in UID(2), job shouldn't start due to no resource.
+ mScheduler->submit(CLIENT(3), JOB(0), UID(2), mRealtimeRequest, mClientCallback3);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Signal resource available, CLIENT(3)'s job should start.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(3), JOB(0)));
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/tests/assets/backyard_hevc_1920x1080_20Mbps.mp4 b/media/libmediatranscoding/tests/assets/backyard_hevc_1920x1080_20Mbps.mp4
new file mode 100644
index 0000000..80d1ec3
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/backyard_hevc_1920x1080_20Mbps.mp4
Binary files differ
diff --git a/media/libmediatranscoding/tests/assets/cubicle_avc_480x240_aac_24KHz.mp4 b/media/libmediatranscoding/tests/assets/cubicle_avc_480x240_aac_24KHz.mp4
new file mode 100644
index 0000000..ef7e1b7
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/cubicle_avc_480x240_aac_24KHz.mp4
Binary files differ
diff --git a/media/libmediatranscoding/tests/assets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4 b/media/libmediatranscoding/tests/assets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4
new file mode 100644
index 0000000..df42a15
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4
Binary files differ
diff --git a/media/libmediatranscoding/tests/assets/jets_hevc_1280x720_20Mbps.mp4 b/media/libmediatranscoding/tests/assets/jets_hevc_1280x720_20Mbps.mp4
new file mode 100644
index 0000000..7794b99
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/jets_hevc_1280x720_20Mbps.mp4
Binary files differ
diff --git a/media/libmediatranscoding/tests/assets/longtest_15s.mp4 b/media/libmediatranscoding/tests/assets/longtest_15s.mp4
new file mode 100644
index 0000000..b50d8e4
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/longtest_15s.mp4
Binary files differ
diff --git a/media/libmediatranscoding/tests/assets/plex_hevc_3840x2160_12Mbps.mp4 b/media/libmediatranscoding/tests/assets/plex_hevc_3840x2160_12Mbps.mp4
new file mode 100644
index 0000000..92dda3b
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/plex_hevc_3840x2160_12Mbps.mp4
Binary files differ
diff --git a/media/libmediatranscoding/tests/assets/plex_hevc_3840x2160_20Mbps.mp4 b/media/libmediatranscoding/tests/assets/plex_hevc_3840x2160_20Mbps.mp4
new file mode 100644
index 0000000..2fe37bd
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/plex_hevc_3840x2160_20Mbps.mp4
Binary files differ
diff --git a/media/libmediatranscoding/tests/assets/push_assets.sh b/media/libmediatranscoding/tests/assets/push_assets.sh
new file mode 100755
index 0000000..8afc947
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/push_assets.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# Pushes the assets to the /data/local/tmp.
+#
+
+if [ "$SYNC_FINISHED" != true ]; then
+ if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+ fi
+
+ # ensure we have mm
+ . $ANDROID_BUILD_TOP/build/envsetup.sh
+
+ mm
+
+ echo "waiting for device"
+
+ adb root && adb wait-for-device remount
+fi
+
+echo "Copying files to device"
+
+adb shell mkdir -p /data/local/tmp/TranscodingTestAssets
+
+FILES=$ANDROID_BUILD_TOP/frameworks/av/media/libmediatranscoding/tests/assets/*
+for file in $FILES
+do
+adb push --sync $file /data/local/tmp/TranscodingTestAssets
+done
+
+echo "Copy done"
diff --git a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
index d8e4830..ff6df2c 100644
--- a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -3,24 +3,32 @@
# Run tests in this directory.
#
-if [ -z "$ANDROID_BUILD_TOP" ]; then
- echo "Android build environment not set"
- exit -1
+if [ "$SYNC_FINISHED" != true ]; then
+ if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+ fi
+
+ # ensure we have mm
+ . $ANDROID_BUILD_TOP/build/envsetup.sh
+
+ mm
+
+ echo "waiting for device"
+
+ adb root && adb wait-for-device remount && adb sync
fi
-# ensure we have mm
-. $ANDROID_BUILD_TOP/build/envsetup.sh
-
-mm
-
-echo "waiting for device"
-
-adb root && adb wait-for-device remount && adb sync
-
echo "========================================"
echo "testing TranscodingClientManager"
-adb shell /data/nativetest64/TranscodingClientManager_tests/TranscodingClientManager_tests
+#adb shell /data/nativetest64/TranscodingClientManager_tests/TranscodingClientManager_tests
+adb shell /data/nativetest/TranscodingClientManager_tests/TranscodingClientManager_tests
echo "testing AdjustableMaxPriorityQueue"
-adb shell /data/nativetest64/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
+#adb shell /data/nativetest64/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
+adb shell /data/nativetest/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
+
+echo "testing TranscodingJobScheduler"
+#adb shell /data/nativetest64/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
+adb shell /data/nativetest/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
new file mode 100644
index 0000000..c153a42
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+cc_library_shared {
+ name: "libmediatranscoder",
+
+ srcs: [
+ "MediaSampleQueue.cpp",
+ "MediaSampleReaderNDK.cpp",
+ "MediaSampleWriter.cpp",
+ "MediaTrackTranscoder.cpp",
+ "MediaTranscoder.cpp",
+ "NdkCommon.cpp",
+ "PassthroughTrackTranscoder.cpp",
+ "VideoTrackTranscoder.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "libmediandk",
+ "libnativewindow",
+ "libutils",
+ // TODO: Use libbinder_ndk
+ "libbinder",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wformat",
+ "-Wno-error=deprecated-declarations",
+ "-Wthread-safety",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ },
+}
diff --git a/media/libmediatranscoding/transcoder/MediaSampleQueue.cpp b/media/libmediatranscoding/transcoder/MediaSampleQueue.cpp
new file mode 100644
index 0000000..691ee1c
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaSampleQueue.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "MediaSampleQueue"
+
+#include <android-base/logging.h>
+#include <media/MediaSampleQueue.h>
+
+namespace android {
+
+bool MediaSampleQueue::enqueue(const std::shared_ptr<MediaSample>& sample) {
+ std::scoped_lock<std::mutex> lock(mMutex);
+ if (!mAborted) {
+ mSampleQueue.push(sample);
+ mCondition.notify_one();
+ }
+ return mAborted;
+}
+
+// Unfortunately std::unique_lock is incompatible with -Wthread-safety
+bool MediaSampleQueue::dequeue(std::shared_ptr<MediaSample>* sample) NO_THREAD_SAFETY_ANALYSIS {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (mSampleQueue.empty() && !mAborted) {
+ mCondition.wait(lock);
+ }
+
+ if (!mAborted) {
+ if (sample != nullptr) {
+ *sample = mSampleQueue.front();
+ }
+ mSampleQueue.pop();
+ }
+ return mAborted;
+}
+
+void MediaSampleQueue::abort() {
+ std::scoped_lock<std::mutex> lock(mMutex);
+ // Clear the queue and notify consumers.
+ std::queue<std::shared_ptr<MediaSample>> empty = {};
+ std::swap(mSampleQueue, empty);
+ mAborted = true;
+ mCondition.notify_all();
+}
+} // namespace android
\ No newline at end of file
diff --git a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
new file mode 100644
index 0000000..a0096c7
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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 "MediaSampleReader"
+
+#include <android-base/logging.h>
+#include <media/MediaSampleReaderNDK.h>
+
+#include <algorithm>
+#include <vector>
+
+namespace android {
+
+// Check that the extractor sample flags have the expected NDK meaning.
+static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
+ "Sample flag mismatch: SYNC_SAMPLE");
+
+// static
+std::shared_ptr<MediaSampleReader> MediaSampleReaderNDK::createFromFd(int fd, size_t offset,
+ size_t size) {
+ AMediaExtractor* extractor = AMediaExtractor_new();
+ if (extractor == nullptr) {
+ LOG(ERROR) << "Unable to allocate AMediaExtractor";
+ return nullptr;
+ }
+
+ media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, offset, size);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "AMediaExtractor_setDataSourceFd returned error: " << status;
+ AMediaExtractor_delete(extractor);
+ return nullptr;
+ }
+
+ auto sampleReader = std::shared_ptr<MediaSampleReaderNDK>(new MediaSampleReaderNDK(extractor));
+ status = sampleReader->init();
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "MediaSampleReaderNDK::init returned error: " << status;
+ return nullptr;
+ }
+
+ return sampleReader;
+}
+
+MediaSampleReaderNDK::MediaSampleReaderNDK(AMediaExtractor* extractor)
+ : mExtractor(extractor), mTrackCount(AMediaExtractor_getTrackCount(mExtractor)) {
+ if (mTrackCount > 0) {
+ mTrackCursors.resize(mTrackCount);
+ mTrackCursors.resize(mTrackCount);
+ }
+}
+
+media_status_t MediaSampleReaderNDK::init() {
+ for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ media_status_t status = AMediaExtractor_selectTrack(mExtractor, trackIndex);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "AMediaExtractor_selectTrack returned error: " << status;
+ return status;
+ }
+ }
+
+ mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ if (mExtractorTrackIndex >= 0) {
+ mTrackCursors[mExtractorTrackIndex].current.set(mExtractorSampleIndex,
+ AMediaExtractor_getSampleTime(mExtractor));
+ } else if (mTrackCount > 0) {
+ // The extractor track index is only allowed to be invalid if there are no tracks.
+ LOG(ERROR) << "Track index " << mExtractorTrackIndex << " is invalid for track count "
+ << mTrackCount;
+ return AMEDIA_ERROR_MALFORMED;
+ }
+
+ return AMEDIA_OK;
+}
+
+MediaSampleReaderNDK::~MediaSampleReaderNDK() {
+ if (mExtractor != nullptr) {
+ AMediaExtractor_delete(mExtractor);
+ }
+}
+
+bool MediaSampleReaderNDK::advanceExtractor_l() {
+ // Reset the "next" sample time whenever the extractor advances past a sample that is current,
+ // to ensure that "next" is appropriately updated when the extractor advances over the next
+ // sample of that track.
+ if (mTrackCursors[mExtractorTrackIndex].current.isSet &&
+ mTrackCursors[mExtractorTrackIndex].current.index == mExtractorSampleIndex) {
+ mTrackCursors[mExtractorTrackIndex].next.reset();
+ }
+
+ if (!AMediaExtractor_advance(mExtractor)) {
+ return false;
+ }
+
+ mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ mExtractorSampleIndex++;
+
+ SampleCursor& cursor = mTrackCursors[mExtractorTrackIndex];
+ if (mExtractorSampleIndex > cursor.previous.index) {
+ if (!cursor.current.isSet) {
+ cursor.current.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
+ } else if (!cursor.next.isSet && mExtractorSampleIndex > cursor.current.index) {
+ cursor.next.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
+ }
+ }
+ return true;
+}
+
+media_status_t MediaSampleReaderNDK::seekExtractorBackwards_l(int64_t targetTimeUs,
+ int targetTrackIndex,
+ uint64_t targetSampleIndex) {
+ if (targetSampleIndex > mExtractorSampleIndex) {
+ LOG(ERROR) << "Error: Forward seek is not supported";
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ // AMediaExtractor supports reading negative timestamps but does not support seeking to them.
+ const int64_t seekToTimeUs = std::max(targetTimeUs, (int64_t)0);
+ media_status_t status =
+ AMediaExtractor_seekTo(mExtractor, seekToTimeUs, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to seek to " << seekToTimeUs << ", target " << targetTimeUs;
+ return status;
+ }
+ mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ int64_t sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
+
+ while (sampleTimeUs != targetTimeUs || mExtractorTrackIndex != targetTrackIndex) {
+ if (!AMediaExtractor_advance(mExtractor)) {
+ return AMEDIA_ERROR_END_OF_STREAM;
+ }
+ mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
+ }
+ mExtractorSampleIndex = targetSampleIndex;
+ return AMEDIA_OK;
+}
+
+void MediaSampleReaderNDK::advanceTrack(int trackIndex) {
+ std::scoped_lock lock(mExtractorMutex);
+
+ if (trackIndex < 0 || trackIndex >= mTrackCount) {
+ LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
+ return;
+ }
+
+ // Note: Positioning the extractor before advancing the track is needed for two reasons:
+ // 1. To enable multiple advances without explicitly letting the extractor catch up.
+ // 2. To prevent the extractor from being farther than "next".
+ (void)positionExtractorForTrack_l(trackIndex);
+
+ SampleCursor& cursor = mTrackCursors[trackIndex];
+ cursor.previous = cursor.current;
+ cursor.current = cursor.next;
+ cursor.next.reset();
+}
+
+media_status_t MediaSampleReaderNDK::positionExtractorForTrack_l(int trackIndex) {
+ media_status_t status = AMEDIA_OK;
+ const SampleCursor& cursor = mTrackCursors[trackIndex];
+
+ // Seek backwards if the extractor is ahead of the current time.
+ if (cursor.current.isSet && mExtractorSampleIndex > cursor.current.index) {
+ status = seekExtractorBackwards_l(cursor.current.timeStampUs, trackIndex,
+ cursor.current.index);
+ if (status != AMEDIA_OK) return status;
+ }
+
+ // Advance until extractor points to the current sample.
+ while (!(cursor.current.isSet && cursor.current.index == mExtractorSampleIndex)) {
+ if (!advanceExtractor_l()) {
+ return AMEDIA_ERROR_END_OF_STREAM;
+ }
+ }
+
+ return AMEDIA_OK;
+}
+
+media_status_t MediaSampleReaderNDK::getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) {
+ std::scoped_lock lock(mExtractorMutex);
+
+ if (trackIndex < 0 || trackIndex >= mTrackCount) {
+ LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ } else if (info == nullptr) {
+ LOG(ERROR) << "MediaSampleInfo pointer is NULL.";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ media_status_t status = positionExtractorForTrack_l(trackIndex);
+ if (status == AMEDIA_OK) {
+ info->presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
+ info->flags = AMediaExtractor_getSampleFlags(mExtractor);
+ info->size = AMediaExtractor_getSampleSize(mExtractor);
+ } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
+ info->presentationTimeUs = 0;
+ info->flags = SAMPLE_FLAG_END_OF_STREAM;
+ info->size = 0;
+ }
+
+ return status;
+}
+
+media_status_t MediaSampleReaderNDK::readSampleDataForTrack(int trackIndex, uint8_t* buffer,
+ size_t bufferSize) {
+ std::scoped_lock lock(mExtractorMutex);
+
+ if (trackIndex < 0 || trackIndex >= mTrackCount) {
+ LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ } else if (buffer == nullptr) {
+ LOG(ERROR) << "buffer pointer is NULL";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ media_status_t status = positionExtractorForTrack_l(trackIndex);
+ if (status != AMEDIA_OK) return status;
+
+ ssize_t sampleSize = AMediaExtractor_getSampleSize(mExtractor);
+ if (bufferSize < sampleSize) {
+ LOG(ERROR) << "Buffer is too small for sample, " << bufferSize << " vs " << sampleSize;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ ssize_t bytesRead = AMediaExtractor_readSampleData(mExtractor, buffer, bufferSize);
+ if (bytesRead < sampleSize) {
+ LOG(ERROR) << "Unable to read full sample, " << bytesRead << " vs " << sampleSize;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ return AMEDIA_OK;
+}
+
+AMediaFormat* MediaSampleReaderNDK::getFileFormat() {
+ return AMediaExtractor_getFileFormat(mExtractor);
+}
+
+size_t MediaSampleReaderNDK::getTrackCount() const {
+ return mTrackCount;
+}
+
+AMediaFormat* MediaSampleReaderNDK::getTrackFormat(int trackIndex) {
+ if (trackIndex < 0 || trackIndex >= mTrackCount) {
+ LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
+ return AMediaFormat_new();
+ }
+
+ return AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
new file mode 100644
index 0000000..3676d73
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
@@ -0,0 +1,281 @@
+/*
+ * 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 "MediaSampleWriter"
+
+#include <android-base/logging.h>
+#include <media/MediaSampleWriter.h>
+#include <media/NdkMediaMuxer.h>
+
+namespace android {
+
+class DefaultMuxer : public MediaSampleWriterMuxerInterface {
+public:
+ // MediaSampleWriterMuxerInterface
+ ssize_t addTrack(AMediaFormat* trackFormat) override {
+ // If the track format has rotation, need to call AMediaMuxer_setOrientationHint
+ // to set the rotation. Muxer doesn't take rotation specified on the track.
+ const char* mime;
+ if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime) &&
+ strncmp(mime, "video/", 6) == 0) {
+ int32_t rotation;
+ if (AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_ROTATION, &rotation) &&
+ (rotation != 0)) {
+ AMediaMuxer_setOrientationHint(mMuxer, rotation);
+ }
+ }
+
+ return AMediaMuxer_addTrack(mMuxer, trackFormat);
+ }
+ media_status_t start() override { return AMediaMuxer_start(mMuxer); }
+ media_status_t writeSampleData(size_t trackIndex, const uint8_t* data,
+ const AMediaCodecBufferInfo* info) override {
+ return AMediaMuxer_writeSampleData(mMuxer, trackIndex, data, info);
+ }
+ media_status_t stop() override { return AMediaMuxer_stop(mMuxer); }
+ // ~MediaSampleWriterMuxerInterface
+
+ static std::shared_ptr<DefaultMuxer> create(int fd) {
+ AMediaMuxer* ndkMuxer = AMediaMuxer_new(fd, AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4);
+ if (ndkMuxer == nullptr) {
+ LOG(ERROR) << "Unable to create AMediaMuxer";
+ return nullptr;
+ }
+
+ return std::make_shared<DefaultMuxer>(ndkMuxer);
+ }
+
+ ~DefaultMuxer() {
+ if (mMuxer != nullptr) {
+ AMediaMuxer_delete(mMuxer);
+ }
+ }
+
+ DefaultMuxer(AMediaMuxer* muxer) : mMuxer(muxer){};
+ DefaultMuxer() = delete;
+
+private:
+ AMediaMuxer* mMuxer;
+};
+
+MediaSampleWriter::~MediaSampleWriter() {
+ if (mState == STARTED) {
+ stop(); // Join thread.
+ }
+}
+
+bool MediaSampleWriter::init(int fd, const std::weak_ptr<CallbackInterface>& callbacks) {
+ return init(DefaultMuxer::create(fd), callbacks);
+}
+
+bool MediaSampleWriter::init(const std::shared_ptr<MediaSampleWriterMuxerInterface>& muxer,
+ const std::weak_ptr<CallbackInterface>& callbacks) {
+ if (callbacks.lock() == nullptr) {
+ LOG(ERROR) << "Callback object cannot be null";
+ return false;
+ } else if (muxer == nullptr) {
+ LOG(ERROR) << "Muxer cannot be null";
+ return false;
+ }
+
+ std::scoped_lock lock(mStateMutex);
+ if (mState != UNINITIALIZED) {
+ LOG(ERROR) << "Sample writer is already initialized";
+ return false;
+ }
+
+ mState = INITIALIZED;
+ mMuxer = muxer;
+ mCallbacks = callbacks;
+ return true;
+}
+
+bool MediaSampleWriter::addTrack(const std::shared_ptr<MediaSampleQueue>& sampleQueue,
+ const std::shared_ptr<AMediaFormat>& trackFormat) {
+ if (sampleQueue == nullptr || trackFormat == nullptr) {
+ LOG(ERROR) << "Sample queue and track format must be non-null";
+ return false;
+ }
+
+ std::scoped_lock lock(mStateMutex);
+ if (mState != INITIALIZED) {
+ LOG(ERROR) << "Muxer needs to be initialized when adding tracks.";
+ return false;
+ }
+ ssize_t trackIndex = mMuxer->addTrack(trackFormat.get());
+ if (trackIndex < 0) {
+ LOG(ERROR) << "Failed to add media track to muxer: " << trackIndex;
+ return false;
+ }
+
+ int64_t durationUs;
+ if (!AMediaFormat_getInt64(trackFormat.get(), AMEDIAFORMAT_KEY_DURATION, &durationUs)) {
+ durationUs = 0;
+ }
+
+ const char* mime = nullptr;
+ const bool isVideo = AMediaFormat_getString(trackFormat.get(), AMEDIAFORMAT_KEY_MIME, &mime) &&
+ (strncmp(mime, "video/", 6) == 0);
+
+ mTracks.emplace_back(sampleQueue, static_cast<size_t>(trackIndex), durationUs, isVideo);
+ return true;
+}
+
+bool MediaSampleWriter::start() {
+ std::scoped_lock lock(mStateMutex);
+
+ if (mTracks.size() == 0) {
+ LOG(ERROR) << "No tracks to write.";
+ return false;
+ } else if (mState != INITIALIZED) {
+ LOG(ERROR) << "Sample writer is not initialized";
+ return false;
+ }
+
+ mThread = std::thread([this] {
+ media_status_t status = writeSamples();
+ if (auto callbacks = mCallbacks.lock()) {
+ callbacks->onFinished(this, status);
+ }
+ });
+ mState = STARTED;
+ return true;
+}
+
+bool MediaSampleWriter::stop() {
+ std::scoped_lock lock(mStateMutex);
+
+ if (mState != STARTED) {
+ LOG(ERROR) << "Sample writer is not started.";
+ return false;
+ }
+
+ // Stop the sources, and wait for thread to join.
+ for (auto& track : mTracks) {
+ track.mSampleQueue->abort();
+ }
+ mThread.join();
+ mState = STOPPED;
+ return true;
+}
+
+media_status_t MediaSampleWriter::writeSamples() {
+ media_status_t muxerStatus = mMuxer->start();
+ if (muxerStatus != AMEDIA_OK) {
+ LOG(ERROR) << "Error starting muxer: " << muxerStatus;
+ return muxerStatus;
+ }
+
+ media_status_t writeStatus = runWriterLoop();
+ if (writeStatus != AMEDIA_OK) {
+ LOG(ERROR) << "Error writing samples: " << writeStatus;
+ }
+
+ muxerStatus = mMuxer->stop();
+ if (muxerStatus != AMEDIA_OK) {
+ LOG(ERROR) << "Error stopping muxer: " << muxerStatus;
+ }
+
+ return writeStatus != AMEDIA_OK ? writeStatus : muxerStatus;
+}
+
+media_status_t MediaSampleWriter::runWriterLoop() {
+ AMediaCodecBufferInfo bufferInfo;
+ uint32_t segmentEndTimeUs = mTrackSegmentLengthUs;
+ bool samplesLeft = true;
+ int32_t lastProgressUpdate = 0;
+
+ // Set the "primary" track that will be used to determine progress to the track with longest
+ // duration.
+ int primaryTrackIndex = -1;
+ int64_t longestDurationUs = 0;
+ for (int trackIndex = 0; trackIndex < mTracks.size(); ++trackIndex) {
+ if (mTracks[trackIndex].mDurationUs > longestDurationUs) {
+ primaryTrackIndex = trackIndex;
+ longestDurationUs = mTracks[trackIndex].mDurationUs;
+ }
+ }
+
+ while (samplesLeft) {
+ samplesLeft = false;
+ for (auto& track : mTracks) {
+ if (track.mReachedEos) continue;
+
+ std::shared_ptr<MediaSample> sample;
+ do {
+ if (track.mSampleQueue->dequeue(&sample)) {
+ // Track queue was aborted.
+ return AMEDIA_ERROR_UNKNOWN; // TODO(lnilsson): Custom error code.
+ } else if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
+ // Track reached end of stream.
+ track.mReachedEos = true;
+
+ // Preserve source track duration by setting the appropriate timestamp on the
+ // empty End-Of-Stream sample.
+ if (track.mDurationUs > 0 && track.mFirstSampleTimeSet) {
+ sample->info.presentationTimeUs =
+ track.mDurationUs + track.mFirstSampleTimeUs;
+ }
+ } else {
+ samplesLeft = true;
+ }
+
+ track.mPrevSampleTimeUs = sample->info.presentationTimeUs;
+ if (!track.mFirstSampleTimeSet) {
+ // Record the first sample's timestamp in order to translate duration to EOS
+ // time for tracks that does not start at 0.
+ track.mFirstSampleTimeUs = sample->info.presentationTimeUs;
+ track.mFirstSampleTimeSet = true;
+ }
+
+ bufferInfo.offset = sample->dataOffset;
+ bufferInfo.size = sample->info.size;
+ bufferInfo.flags = sample->info.flags;
+ bufferInfo.presentationTimeUs = sample->info.presentationTimeUs;
+
+ media_status_t status =
+ mMuxer->writeSampleData(track.mTrackIndex, sample->buffer, &bufferInfo);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "writeSampleData returned " << status;
+ return status;
+ }
+
+ } while (sample->info.presentationTimeUs < segmentEndTimeUs && !track.mReachedEos);
+ }
+
+ // TODO(lnilsson): Add option to toggle progress reporting on/off.
+ if (primaryTrackIndex >= 0) {
+ const TrackRecord& track = mTracks[primaryTrackIndex];
+
+ const int64_t elapsed = track.mPrevSampleTimeUs - track.mFirstSampleTimeUs;
+ int32_t progress = (elapsed * 100) / track.mDurationUs;
+ progress = std::clamp(progress, 0, 100);
+
+ if (progress > lastProgressUpdate) {
+ if (auto callbacks = mCallbacks.lock()) {
+ callbacks->onProgressUpdate(this, progress);
+ }
+ lastProgressUpdate = progress;
+ }
+ }
+
+ segmentEndTimeUs += mTrackSegmentLengthUs;
+ }
+
+ return AMEDIA_OK;
+}
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
new file mode 100644
index 0000000..e3996b0
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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 "MediaTrackTranscoder"
+
+#include <android-base/logging.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/MediaTrackTranscoderCallback.h>
+
+namespace android {
+
+media_status_t MediaTrackTranscoder::configure(
+ const std::shared_ptr<MediaSampleReader>& mediaSampleReader, int trackIndex,
+ const std::shared_ptr<AMediaFormat>& destinationFormat) {
+ std::scoped_lock lock{mStateMutex};
+
+ if (mState != UNINITIALIZED) {
+ LOG(ERROR) << "Configure can only be called once";
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ if (mediaSampleReader == nullptr) {
+ LOG(ERROR) << "MediaSampleReader is null";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+ if (trackIndex < 0 || trackIndex >= mediaSampleReader->getTrackCount()) {
+ LOG(ERROR) << "TrackIndex is invalid " << trackIndex;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ mMediaSampleReader = mediaSampleReader;
+ mTrackIndex = trackIndex;
+
+ mSourceFormat =
+ std::shared_ptr<AMediaFormat>(mMediaSampleReader->getTrackFormat(mTrackIndex),
+ std::bind(AMediaFormat_delete, std::placeholders::_1));
+ if (mSourceFormat == nullptr) {
+ LOG(ERROR) << "Unable to get format for track #" << mTrackIndex;
+ return AMEDIA_ERROR_MALFORMED;
+ }
+
+ media_status_t status = configureDestinationFormat(destinationFormat);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "configure failed with error " << status;
+ return status;
+ }
+
+ mState = CONFIGURED;
+ return AMEDIA_OK;
+}
+
+bool MediaTrackTranscoder::start() {
+ std::scoped_lock lock{mStateMutex};
+
+ if (mState != CONFIGURED) {
+ LOG(ERROR) << "TrackTranscoder must be configured before started";
+ return false;
+ }
+
+ mTranscodingThread = std::thread([this] {
+ media_status_t status = runTranscodeLoop();
+
+ // Notify the client.
+ if (auto callbacks = mTranscoderCallback.lock()) {
+ if (status != AMEDIA_OK) {
+ callbacks->onTrackError(this, status);
+ } else {
+ callbacks->onTrackFinished(this);
+ }
+ }
+ });
+
+ mState = STARTED;
+ return true;
+}
+
+bool MediaTrackTranscoder::stop() {
+ std::scoped_lock lock{mStateMutex};
+
+ if (mState == STARTED) {
+ abortTranscodeLoop();
+ mTranscodingThread.join();
+ mOutputQueue->abort(); // Wake up any threads waiting for samples.
+ mState = STOPPED;
+ return true;
+ }
+
+ LOG(ERROR) << "TrackTranscoder must be started before stopped";
+ return false;
+}
+
+void MediaTrackTranscoder::notifyTrackFormatAvailable() {
+ if (auto callbacks = mTranscoderCallback.lock()) {
+ callbacks->onTrackFormatAvailable(this);
+ }
+}
+
+std::shared_ptr<MediaSampleQueue> MediaTrackTranscoder::getOutputQueue() const {
+ return mOutputQueue;
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
new file mode 100644
index 0000000..ed702db
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -0,0 +1,354 @@
+/*
+ * 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 "MediaTranscoder"
+
+#include <android-base/logging.h>
+#include <binder/Parcel.h>
+#include <fcntl.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/MediaSampleWriter.h>
+#include <media/MediaTranscoder.h>
+#include <media/PassthroughTrackTranscoder.h>
+#include <media/VideoTrackTranscoder.h>
+#include <unistd.h>
+
+namespace android {
+
+#define DEFINE_FORMAT_VALUE_COPY_FUNC(_type, _typeName) \
+ static void copy##_typeName(const char* key, AMediaFormat* to, AMediaFormat* from) { \
+ _type value; \
+ if (AMediaFormat_get##_typeName(from, key, &value)) { \
+ AMediaFormat_set##_typeName(to, key, value); \
+ } \
+ }
+
+DEFINE_FORMAT_VALUE_COPY_FUNC(const char*, String);
+DEFINE_FORMAT_VALUE_COPY_FUNC(int64_t, Int64);
+DEFINE_FORMAT_VALUE_COPY_FUNC(int32_t, Int32);
+
+static AMediaFormat* mergeMediaFormats(AMediaFormat* base, AMediaFormat* overlay) {
+ if (base == nullptr || overlay == nullptr) {
+ LOG(ERROR) << "Cannot merge null formats";
+ return nullptr;
+ }
+
+ AMediaFormat* format = AMediaFormat_new();
+ if (AMediaFormat_copy(format, base) != AMEDIA_OK) {
+ AMediaFormat_delete(format);
+ return nullptr;
+ }
+
+ // Note: AMediaFormat does not expose a function for appending values from another format or for
+ // iterating over all values and keys in a format. Instead we define a static list of known keys
+ // along with their value types and copy the ones that are present. A better solution would be
+ // to either implement required functions in NDK or to parse the overlay format's string
+ // representation and copy all existing keys.
+ static const struct {
+ const char* key;
+ void (*copyValue)(const char* key, AMediaFormat* to, AMediaFormat* from);
+ } kSupportedConfigs[] = {
+ {AMEDIAFORMAT_KEY_MIME, copyString},
+ {AMEDIAFORMAT_KEY_DURATION, copyInt64},
+ {AMEDIAFORMAT_KEY_WIDTH, copyInt32},
+ {AMEDIAFORMAT_KEY_HEIGHT, copyInt32},
+ {AMEDIAFORMAT_KEY_BIT_RATE, copyInt32},
+ {AMEDIAFORMAT_KEY_PROFILE, copyInt32},
+ {AMEDIAFORMAT_KEY_LEVEL, copyInt32},
+ {AMEDIAFORMAT_KEY_COLOR_FORMAT, copyInt32},
+ {AMEDIAFORMAT_KEY_COLOR_RANGE, copyInt32},
+ {AMEDIAFORMAT_KEY_COLOR_STANDARD, copyInt32},
+ {AMEDIAFORMAT_KEY_COLOR_TRANSFER, copyInt32},
+ {AMEDIAFORMAT_KEY_FRAME_RATE, copyInt32},
+ {AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, copyInt32},
+ };
+
+ for (int i = 0; i < (sizeof(kSupportedConfigs) / sizeof(kSupportedConfigs[0])); ++i) {
+ kSupportedConfigs[i].copyValue(kSupportedConfigs[i].key, format, overlay);
+ }
+
+ return format;
+}
+
+void MediaTranscoder::sendCallback(media_status_t status) {
+ // If the transcoder is already cancelled explicitly, don't send any error callbacks.
+ // Tracks and sample writer will report errors for abort. However, currently we can't
+ // tell it apart from real errors. Ideally we still want to report real errors back
+ // to client, as there is a small chance that explicit abort and the real error come
+ // at around the same time, we should report that if abort has a specific error code.
+ // On the other hand, if the transcoder actually finished (status is AMEDIA_OK) at around
+ // the same time of the abort, we should still report the finish back to the client.
+ if (mCancelled && status != AMEDIA_OK) {
+ return;
+ }
+
+ bool expected = false;
+ if (mCallbackSent.compare_exchange_strong(expected, true)) {
+ if (status == AMEDIA_OK) {
+ mCallbacks->onFinished(this);
+ } else {
+ mCallbacks->onError(this, status);
+ }
+
+ // Transcoding is done and the callback to the client has been sent, so tear down the
+ // pipeline but do it asynchronously to avoid deadlocks. If an error occurred, client
+ // should clean up the file.
+ std::thread asyncCancelThread{[self = shared_from_this()] { self->cancel(); }};
+ asyncCancelThread.detach();
+ }
+}
+
+void MediaTranscoder::onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) {
+ LOG(INFO) << "TrackTranscoder " << transcoder << " format available.";
+
+ std::scoped_lock lock{mTracksAddedMutex};
+
+ // Ignore duplicate format change.
+ if (mTracksAdded.count(transcoder) > 0) {
+ return;
+ }
+
+ // Add track to the writer.
+ const bool ok =
+ mSampleWriter->addTrack(transcoder->getOutputQueue(), transcoder->getOutputFormat());
+ if (!ok) {
+ LOG(ERROR) << "Unable to add track to sample writer.";
+ sendCallback(AMEDIA_ERROR_UNKNOWN);
+ return;
+ }
+
+ mTracksAdded.insert(transcoder);
+ if (mTracksAdded.size() == mTrackTranscoders.size()) {
+ LOG(INFO) << "Starting sample writer.";
+ bool started = mSampleWriter->start();
+ if (!started) {
+ LOG(ERROR) << "Unable to start sample writer.";
+ sendCallback(AMEDIA_ERROR_UNKNOWN);
+ }
+ }
+}
+
+void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
+ LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
+}
+
+void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
+ LOG(DEBUG) << "TrackTranscoder " << transcoder << " returned error " << status;
+ sendCallback(status);
+}
+
+void MediaTranscoder::onFinished(const MediaSampleWriter* writer __unused, media_status_t status) {
+ LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
+ sendCallback(status);
+}
+
+void MediaTranscoder::onProgressUpdate(const MediaSampleWriter* writer __unused, int32_t progress) {
+ // Dispatch progress updated to the client.
+ mCallbacks->onProgressUpdate(this, progress);
+}
+
+MediaTranscoder::MediaTranscoder(const std::shared_ptr<CallbackInterface>& callbacks)
+ : mCallbacks(callbacks) {}
+
+std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
+ const std::shared_ptr<CallbackInterface>& callbacks,
+ const std::shared_ptr<const Parcel>& pausedState) {
+ if (pausedState != nullptr) {
+ LOG(INFO) << "Initializing from paused state.";
+ }
+ if (callbacks == nullptr) {
+ LOG(ERROR) << "Callbacks cannot be null";
+ return nullptr;
+ }
+
+ return std::shared_ptr<MediaTranscoder>(new MediaTranscoder(callbacks));
+}
+
+media_status_t MediaTranscoder::configureSource(int fd) {
+ if (fd < 0) {
+ LOG(ERROR) << "Invalid source fd: " << fd;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ const size_t fileSize = lseek(fd, 0, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+
+ mSampleReader = MediaSampleReaderNDK::createFromFd(fd, 0 /* offset */, fileSize);
+
+ if (mSampleReader == nullptr) {
+ LOG(ERROR) << "Unable to parse source fd: " << fd;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ const size_t trackCount = mSampleReader->getTrackCount();
+ for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
+ AMediaFormat* trackFormat = mSampleReader->getTrackFormat(static_cast<int>(trackIndex));
+ if (trackFormat == nullptr) {
+ LOG(ERROR) << "Track #" << trackIndex << " has no format";
+ return AMEDIA_ERROR_MALFORMED;
+ }
+
+ mSourceTrackFormats.emplace_back(trackFormat, &AMediaFormat_delete);
+ }
+
+ return AMEDIA_OK;
+}
+
+std::vector<std::shared_ptr<AMediaFormat>> MediaTranscoder::getTrackFormats() const {
+ // Return a deep copy of the formats to avoid the caller modifying our internal formats.
+ std::vector<std::shared_ptr<AMediaFormat>> trackFormats;
+ for (const std::shared_ptr<AMediaFormat>& sourceFormat : mSourceTrackFormats) {
+ AMediaFormat* copy = AMediaFormat_new();
+ AMediaFormat_copy(copy, sourceFormat.get());
+ trackFormats.emplace_back(copy, &AMediaFormat_delete);
+ }
+ return trackFormats;
+}
+
+media_status_t MediaTranscoder::configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat) {
+ if (mSampleReader == nullptr) {
+ LOG(ERROR) << "Source must be configured before tracks";
+ return AMEDIA_ERROR_INVALID_OPERATION;
+ } else if (trackIndex >= mSourceTrackFormats.size()) {
+ LOG(ERROR) << "Track index " << trackIndex
+ << " is out of bounds. Track count: " << mSourceTrackFormats.size();
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ std::shared_ptr<MediaTrackTranscoder> transcoder;
+ std::shared_ptr<AMediaFormat> format;
+
+ if (trackFormat == nullptr) {
+ transcoder = std::make_shared<PassthroughTrackTranscoder>(shared_from_this());
+ } else {
+ const char* srcMime = nullptr;
+ if (!AMediaFormat_getString(mSourceTrackFormats[trackIndex].get(), AMEDIAFORMAT_KEY_MIME,
+ &srcMime)) {
+ LOG(ERROR) << "Source track #" << trackIndex << " has no mime type";
+ return AMEDIA_ERROR_MALFORMED;
+ }
+
+ if (strncmp(srcMime, "video/", 6) != 0) {
+ LOG(ERROR) << "Only video tracks are supported for transcoding. Unable to configure "
+ "track #"
+ << trackIndex << " with mime " << srcMime;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ const char* dstMime = nullptr;
+ if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &dstMime)) {
+ if (strncmp(dstMime, "video/", 6) != 0) {
+ LOG(ERROR) << "Unable to convert media types for track #" << trackIndex << ", from "
+ << srcMime << " to " << dstMime;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+ }
+
+ transcoder = VideoTrackTranscoder::create(shared_from_this());
+
+ AMediaFormat* mergedFormat =
+ mergeMediaFormats(mSourceTrackFormats[trackIndex].get(), trackFormat);
+ if (mergedFormat == nullptr) {
+ LOG(ERROR) << "Unable to merge source and destination formats";
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ format = std::shared_ptr<AMediaFormat>(mergedFormat, &AMediaFormat_delete);
+ }
+
+ media_status_t status = transcoder->configure(mSampleReader, trackIndex, format);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Configure track transcoder for track #" << trackIndex << " returned error "
+ << status;
+ return status;
+ }
+
+ mTrackTranscoders.emplace_back(std::move(transcoder));
+ return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::configureDestination(int fd) {
+ if (fd < 0) {
+ LOG(ERROR) << "Invalid destination fd: " << fd;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ if (mSampleWriter != nullptr) {
+ LOG(ERROR) << "Destination is already configured.";
+ return AMEDIA_ERROR_INVALID_OPERATION;
+ }
+
+ mSampleWriter = std::make_unique<MediaSampleWriter>();
+ const bool initOk = mSampleWriter->init(fd, shared_from_this());
+
+ if (!initOk) {
+ LOG(ERROR) << "Unable to initialize sample writer with destination fd: " << fd;
+ mSampleWriter.reset();
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::start() {
+ if (mTrackTranscoders.size() < 1) {
+ LOG(ERROR) << "Unable to start, no tracks are configured.";
+ return AMEDIA_ERROR_INVALID_OPERATION;
+ } else if (mSampleWriter == nullptr) {
+ LOG(ERROR) << "Unable to start, destination is not configured";
+ return AMEDIA_ERROR_INVALID_OPERATION;
+ }
+
+ // Start transcoders
+ for (auto& transcoder : mTrackTranscoders) {
+ bool started = transcoder->start();
+ if (!started) {
+ LOG(ERROR) << "Unable to start track transcoder.";
+ cancel();
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+ }
+ return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcel>* pausedState) {
+ // TODO: write internal states to parcel.
+ *pausedState = std::make_shared<Parcel>();
+ return cancel();
+}
+
+media_status_t MediaTranscoder::resume() {
+ // TODO: restore internal states from parcel.
+ return start();
+}
+
+media_status_t MediaTranscoder::cancel() {
+ bool expected = false;
+ if (!mCancelled.compare_exchange_strong(expected, true)) {
+ // Already cancelled.
+ return AMEDIA_OK;
+ }
+
+ mSampleWriter->stop();
+ for (auto& transcoder : mTrackTranscoders) {
+ transcoder->stop();
+ }
+
+ return AMEDIA_OK;
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/NdkCommon.cpp b/media/libmediatranscoding/transcoder/NdkCommon.cpp
new file mode 100644
index 0000000..67a8e10
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/NdkCommon.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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 "NdkCommon"
+
+#include <log/log.h>
+#include <media/NdkCommon.h>
+
+#include <cstdio>
+#include <cstring>
+#include <utility>
+
+/* TODO(b/153592281)
+ * Note: constants used by the native media tests but not available in media ndk api
+ */
+const char* AMEDIA_MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
+const char* AMEDIA_MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
+const char* AMEDIA_MIMETYPE_VIDEO_AV1 = "video/av01";
+const char* AMEDIA_MIMETYPE_VIDEO_AVC = "video/avc";
+const char* AMEDIA_MIMETYPE_VIDEO_HEVC = "video/hevc";
+const char* AMEDIA_MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
+const char* AMEDIA_MIMETYPE_VIDEO_H263 = "video/3gpp";
+
+/* TODO(b/153592281) */
+const char* TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
+const char* TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
+const char* TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES = "max-bframes";
diff --git a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
new file mode 100644
index 0000000..e2cc6b6
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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 "PassthroughTrackTranscoder"
+
+#include <android-base/logging.h>
+#include <media/PassthroughTrackTranscoder.h>
+
+namespace android {
+
+PassthroughTrackTranscoder::BufferPool::~BufferPool() {
+ for (auto it = mAddressSizeMap.begin(); it != mAddressSizeMap.end(); ++it) {
+ delete[] it->first;
+ }
+}
+
+uint8_t* PassthroughTrackTranscoder::BufferPool::getBufferWithSize(size_t minimumBufferSize)
+ NO_THREAD_SAFETY_ANALYSIS {
+ std::unique_lock lock(mMutex);
+
+ // Wait if maximum number of buffers are allocated but none are free.
+ while (mAddressSizeMap.size() >= mMaxBufferCount && mFreeBufferMap.empty() && !mAborted) {
+ mCondition.wait(lock);
+ }
+
+ if (mAborted) {
+ return nullptr;
+ }
+
+ // Check if the free list contains a large enough buffer.
+ auto it = mFreeBufferMap.lower_bound(minimumBufferSize);
+ if (it != mFreeBufferMap.end()) {
+ uint8_t* buffer = it->second;
+ mFreeBufferMap.erase(it);
+ return buffer;
+ }
+
+ // If the maximum buffer count is reached, remove an existing free buffer.
+ if (mAddressSizeMap.size() >= mMaxBufferCount) {
+ auto it = mFreeBufferMap.begin();
+ mAddressSizeMap.erase(it->second);
+ delete[] it->second;
+ mFreeBufferMap.erase(it);
+ }
+
+ // Allocate a new buffer.
+ uint8_t* buffer = new (std::nothrow) uint8_t[minimumBufferSize];
+ if (buffer == nullptr) {
+ LOG(ERROR) << "Unable to allocate new buffer of size: " << minimumBufferSize;
+ return nullptr;
+ }
+
+ // Add the buffer to the tracking set.
+ mAddressSizeMap.emplace(buffer, minimumBufferSize);
+ return buffer;
+}
+
+void PassthroughTrackTranscoder::BufferPool::returnBuffer(uint8_t* buffer) {
+ std::scoped_lock lock(mMutex);
+
+ if (buffer == nullptr || mAddressSizeMap.find(buffer) == mAddressSizeMap.end()) {
+ LOG(WARNING) << "Ignoring untracked buffer " << buffer;
+ return;
+ }
+
+ mFreeBufferMap.emplace(mAddressSizeMap[buffer], buffer);
+ mCondition.notify_one();
+}
+
+void PassthroughTrackTranscoder::BufferPool::abort() {
+ std::scoped_lock lock(mMutex);
+ mAborted = true;
+ mCondition.notify_all();
+}
+
+media_status_t PassthroughTrackTranscoder::configureDestinationFormat(
+ const std::shared_ptr<AMediaFormat>& destinationFormat __unused) {
+ // Called by MediaTrackTranscoder. Passthrough doesn't care about destination so just return ok.
+ return AMEDIA_OK;
+}
+
+media_status_t PassthroughTrackTranscoder::runTranscodeLoop() {
+ MediaSampleInfo info;
+ std::shared_ptr<MediaSample> sample;
+
+ // Notify the track format as soon as we start. It's same as the source format.
+ notifyTrackFormatAvailable();
+
+ MediaSample::OnSampleReleasedCallback bufferReleaseCallback =
+ [bufferPool = mBufferPool](MediaSample* sample) {
+ bufferPool->returnBuffer(const_cast<uint8_t*>(sample->buffer));
+ };
+
+ // Move samples until EOS is reached or transcoding is stopped.
+ while (!mStopRequested && !mEosFromSource) {
+ media_status_t status = mMediaSampleReader->getSampleInfoForTrack(mTrackIndex, &info);
+
+ if (status == AMEDIA_OK) {
+ uint8_t* buffer = mBufferPool->getBufferWithSize(info.size);
+ if (buffer == nullptr) {
+ if (mStopRequested) {
+ break;
+ }
+
+ LOG(ERROR) << "Unable to get buffer from pool";
+ return AMEDIA_ERROR_IO; // TODO: Custom error codes?
+ }
+
+ sample = MediaSample::createWithReleaseCallback(
+ buffer, 0 /* offset */, 0 /* bufferId */, bufferReleaseCallback);
+
+ status = mMediaSampleReader->readSampleDataForTrack(mTrackIndex, buffer, info.size);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to read next sample data. Aborting transcode.";
+ return status;
+ }
+
+ } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
+ sample = std::make_shared<MediaSample>();
+ mEosFromSource = true;
+ } else {
+ LOG(ERROR) << "Unable to get next sample info. Aborting transcode.";
+ return status;
+ }
+
+ sample->info = info;
+ if (mOutputQueue->enqueue(sample)) {
+ LOG(ERROR) << "Output queue aborted";
+ return AMEDIA_ERROR_IO;
+ }
+
+ mMediaSampleReader->advanceTrack(mTrackIndex);
+ }
+
+ if (mStopRequested && !mEosFromSource) {
+ return AMEDIA_ERROR_UNKNOWN; // TODO: Custom error codes?
+ }
+ return AMEDIA_OK;
+}
+
+void PassthroughTrackTranscoder::abortTranscodeLoop() {
+ mStopRequested = true;
+ mBufferPool->abort();
+}
+
+std::shared_ptr<AMediaFormat> PassthroughTrackTranscoder::getOutputFormat() const {
+ return mSourceFormat;
+}
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
new file mode 100644
index 0000000..65dcad3
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -0,0 +1,484 @@
+/*
+ * 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 "VideoTrackTranscoder"
+
+#include <android-base/logging.h>
+#include <media/VideoTrackTranscoder.h>
+
+namespace android {
+
+// Check that the codec sample flags have the expected NDK meaning.
+static_assert(SAMPLE_FLAG_CODEC_CONFIG == AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG,
+ "Sample flag mismatch: CODEC_CONFIG");
+static_assert(SAMPLE_FLAG_END_OF_STREAM == AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM,
+ "Sample flag mismatch: END_OF_STREAM");
+static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
+ "Sample flag mismatch: PARTIAL_FRAME");
+
+// Color format defined by surface. (See MediaCodecInfo.CodecCapabilities#COLOR_FormatSurface.)
+static constexpr int32_t kColorFormatSurface = 0x7f000789;
+// Default key frame interval in seconds.
+static constexpr float kDefaultKeyFrameIntervalSeconds = 1.0f;
+
+template <typename T>
+void VideoTrackTranscoder::BlockingQueue<T>::push(T const& value, bool front) {
+ {
+ std::unique_lock<std::mutex> lock(mMutex);
+ if (front) {
+ mQueue.push_front(value);
+ } else {
+ mQueue.push_back(value);
+ }
+ }
+ mCondition.notify_one();
+}
+
+template <typename T>
+T VideoTrackTranscoder::BlockingQueue<T>::pop() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (mQueue.empty()) {
+ mCondition.wait(lock);
+ }
+ T value = mQueue.front();
+ mQueue.pop_front();
+ return value;
+}
+
+// The CodecWrapper class is used to let AMediaCodec instances outlive the transcoder object itself
+// by giving the codec a weak pointer to the transcoder. Codecs wrapped in this object are kept
+// alive by the transcoder and the codec's outstanding buffers. Once the transcoder stops and all
+// output buffers have been released by downstream components the codec will also be released.
+class VideoTrackTranscoder::CodecWrapper {
+public:
+ CodecWrapper(AMediaCodec* codec, const std::weak_ptr<VideoTrackTranscoder>& transcoder)
+ : mCodec(codec), mTranscoder(transcoder), mCodecStarted(false) {}
+ ~CodecWrapper() {
+ if (mCodecStarted) {
+ AMediaCodec_stop(mCodec);
+ }
+ AMediaCodec_delete(mCodec);
+ }
+
+ AMediaCodec* getCodec() { return mCodec; }
+ std::shared_ptr<VideoTrackTranscoder> getTranscoder() const { return mTranscoder.lock(); };
+ void setStarted() { mCodecStarted = true; }
+
+private:
+ AMediaCodec* mCodec;
+ std::weak_ptr<VideoTrackTranscoder> mTranscoder;
+ bool mCodecStarted;
+};
+
+// Dispatch responses to codec callbacks onto the message queue.
+struct AsyncCodecCallbackDispatch {
+ static void onAsyncInputAvailable(AMediaCodec* codec, void* userdata, int32_t index) {
+ VideoTrackTranscoder::CodecWrapper* wrapper =
+ static_cast<VideoTrackTranscoder::CodecWrapper*>(userdata);
+ if (auto transcoder = wrapper->getTranscoder()) {
+ if (codec == transcoder->mDecoder) {
+ transcoder->mCodecMessageQueue.push(
+ [transcoder, index] { transcoder->enqueueInputSample(index); });
+ }
+ }
+ }
+
+ static void onAsyncOutputAvailable(AMediaCodec* codec, void* userdata, int32_t index,
+ AMediaCodecBufferInfo* bufferInfoPtr) {
+ VideoTrackTranscoder::CodecWrapper* wrapper =
+ static_cast<VideoTrackTranscoder::CodecWrapper*>(userdata);
+ AMediaCodecBufferInfo bufferInfo = *bufferInfoPtr;
+ if (auto transcoder = wrapper->getTranscoder()) {
+ transcoder->mCodecMessageQueue.push([transcoder, index, codec, bufferInfo] {
+ if (codec == transcoder->mDecoder) {
+ transcoder->transferBuffer(index, bufferInfo);
+ } else if (codec == transcoder->mEncoder->getCodec()) {
+ transcoder->dequeueOutputSample(index, bufferInfo);
+ }
+ });
+ }
+ }
+
+ static void onAsyncFormatChanged(AMediaCodec* codec, void* userdata, AMediaFormat* format) {
+ VideoTrackTranscoder::CodecWrapper* wrapper =
+ static_cast<VideoTrackTranscoder::CodecWrapper*>(userdata);
+ if (auto transcoder = wrapper->getTranscoder()) {
+ const char* kCodecName = (codec == transcoder->mDecoder ? "Decoder" : "Encoder");
+ LOG(DEBUG) << kCodecName << " format changed: " << AMediaFormat_toString(format);
+ if (codec == transcoder->mEncoder->getCodec()) {
+ transcoder->mCodecMessageQueue.push(
+ [transcoder, format] { transcoder->updateTrackFormat(format); });
+ }
+ }
+ }
+
+ static void onAsyncError(AMediaCodec* codec, void* userdata, media_status_t error,
+ int32_t actionCode, const char* detail) {
+ LOG(ERROR) << "Error from codec " << codec << ", userdata " << userdata << ", error "
+ << error << ", action " << actionCode << ", detail " << detail;
+ VideoTrackTranscoder::CodecWrapper* wrapper =
+ static_cast<VideoTrackTranscoder::CodecWrapper*>(userdata);
+ if (auto transcoder = wrapper->getTranscoder()) {
+ transcoder->mCodecMessageQueue.push(
+ [transcoder, error] {
+ transcoder->mStatus = error;
+ transcoder->mStopRequested = true;
+ },
+ true);
+ }
+ }
+};
+
+// static
+std::shared_ptr<VideoTrackTranscoder> VideoTrackTranscoder::create(
+ const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback) {
+ return std::shared_ptr<VideoTrackTranscoder>(new VideoTrackTranscoder(transcoderCallback));
+}
+
+VideoTrackTranscoder::~VideoTrackTranscoder() {
+ if (mDecoder != nullptr) {
+ AMediaCodec_delete(mDecoder);
+ }
+
+ if (mSurface != nullptr) {
+ ANativeWindow_release(mSurface);
+ }
+}
+
+// Creates and configures the codecs.
+media_status_t VideoTrackTranscoder::configureDestinationFormat(
+ const std::shared_ptr<AMediaFormat>& destinationFormat) {
+ media_status_t status = AMEDIA_OK;
+
+ if (destinationFormat == nullptr) {
+ LOG(ERROR) << "Destination format is null, use passthrough transcoder";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ AMediaFormat* encoderFormat = AMediaFormat_new();
+ if (!encoderFormat || AMediaFormat_copy(encoderFormat, destinationFormat.get()) != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to copy destination format";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ float tmp;
+ if (!AMediaFormat_getFloat(encoderFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &tmp)) {
+ AMediaFormat_setFloat(encoderFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+ kDefaultKeyFrameIntervalSeconds);
+ }
+ AMediaFormat_setInt32(encoderFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, kColorFormatSurface);
+
+ // Always encode without rotation. The rotation degree will be transferred directly to
+ // MediaSampleWriter track format, and MediaSampleWriter will call AMediaMuxer_setOrientationHint.
+ AMediaFormat_setInt32(encoderFormat, AMEDIAFORMAT_KEY_ROTATION, 0);
+
+ mDestinationFormat = std::shared_ptr<AMediaFormat>(encoderFormat, &AMediaFormat_delete);
+
+ // Create and configure the encoder.
+ const char* destinationMime = nullptr;
+ bool ok = AMediaFormat_getString(mDestinationFormat.get(), AMEDIAFORMAT_KEY_MIME,
+ &destinationMime);
+ if (!ok) {
+ LOG(ERROR) << "Destination MIME type is required for transcoding.";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ AMediaCodec* encoder = AMediaCodec_createEncoderByType(destinationMime);
+ if (encoder == nullptr) {
+ LOG(ERROR) << "Unable to create encoder for type " << destinationMime;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+ mEncoder = std::make_shared<CodecWrapper>(encoder, shared_from_this());
+
+ status = AMediaCodec_configure(mEncoder->getCodec(), mDestinationFormat.get(),
+ NULL /* surface */, NULL /* crypto */,
+ AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to configure video encoder: " << status;
+ return status;
+ }
+
+ status = AMediaCodec_createInputSurface(mEncoder->getCodec(), &mSurface);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to create an encoder input surface: %d" << status;
+ return status;
+ }
+
+ // Create and configure the decoder.
+ const char* sourceMime = nullptr;
+ ok = AMediaFormat_getString(mSourceFormat.get(), AMEDIAFORMAT_KEY_MIME, &sourceMime);
+ if (!ok) {
+ LOG(ERROR) << "Source MIME type is required for transcoding.";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ mDecoder = AMediaCodec_createDecoderByType(sourceMime);
+ if (mDecoder == nullptr) {
+ LOG(ERROR) << "Unable to create decoder for type " << sourceMime;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ status = AMediaCodec_configure(mDecoder, mSourceFormat.get(), mSurface, NULL /* crypto */,
+ 0 /* flags */);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to configure video decoder: " << status;
+ return status;
+ }
+
+ // Configure codecs to run in async mode.
+ AMediaCodecOnAsyncNotifyCallback asyncCodecCallbacks = {
+ .onAsyncInputAvailable = AsyncCodecCallbackDispatch::onAsyncInputAvailable,
+ .onAsyncOutputAvailable = AsyncCodecCallbackDispatch::onAsyncOutputAvailable,
+ .onAsyncFormatChanged = AsyncCodecCallbackDispatch::onAsyncFormatChanged,
+ .onAsyncError = AsyncCodecCallbackDispatch::onAsyncError};
+
+ // Note: The decoder does not need its own wrapper because its lifetime is tied to the
+ // transcoder. But the same callbacks are reused for decoder and encoder so we pass the encoder
+ // wrapper as userdata here but never read the codec from it in the callback.
+ status = AMediaCodec_setAsyncNotifyCallback(mDecoder, asyncCodecCallbacks, mEncoder.get());
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to set decoder to async mode: " << status;
+ return status;
+ }
+
+ status = AMediaCodec_setAsyncNotifyCallback(mEncoder->getCodec(), asyncCodecCallbacks,
+ mEncoder.get());
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to set encoder to async mode: " << status;
+ return status;
+ }
+
+ return AMEDIA_OK;
+}
+
+void VideoTrackTranscoder::enqueueInputSample(int32_t bufferIndex) {
+ media_status_t status = AMEDIA_OK;
+
+ if (mEosFromSource) {
+ return;
+ }
+
+ status = mMediaSampleReader->getSampleInfoForTrack(mTrackIndex, &mSampleInfo);
+ if (status != AMEDIA_OK && status != AMEDIA_ERROR_END_OF_STREAM) {
+ LOG(ERROR) << "Error getting next sample info: " << status;
+ mStatus = status;
+ return;
+ }
+ const bool endOfStream = (status == AMEDIA_ERROR_END_OF_STREAM);
+
+ if (!endOfStream) {
+ size_t bufferSize = 0;
+ uint8_t* sourceBuffer = AMediaCodec_getInputBuffer(mDecoder, bufferIndex, &bufferSize);
+ if (sourceBuffer == nullptr) {
+ LOG(ERROR) << "Decoder returned a NULL input buffer.";
+ mStatus = AMEDIA_ERROR_UNKNOWN;
+ return;
+ } else if (bufferSize < mSampleInfo.size) {
+ LOG(ERROR) << "Decoder returned an input buffer that is smaller than the sample.";
+ mStatus = AMEDIA_ERROR_UNKNOWN;
+ return;
+ }
+
+ status = mMediaSampleReader->readSampleDataForTrack(mTrackIndex, sourceBuffer,
+ mSampleInfo.size);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to read next sample data. Aborting transcode.";
+ mStatus = status;
+ return;
+ }
+
+ mMediaSampleReader->advanceTrack(mTrackIndex);
+ } else {
+ LOG(DEBUG) << "EOS from source.";
+ mEosFromSource = true;
+ }
+
+ status = AMediaCodec_queueInputBuffer(mDecoder, bufferIndex, 0, mSampleInfo.size,
+ mSampleInfo.presentationTimeUs, mSampleInfo.flags);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to queue input buffer for decode: " << status;
+ mStatus = status;
+ return;
+ }
+}
+
+void VideoTrackTranscoder::transferBuffer(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo) {
+ if (bufferIndex >= 0) {
+ bool needsRender = bufferInfo.size > 0;
+ AMediaCodec_releaseOutputBuffer(mDecoder, bufferIndex, needsRender);
+ }
+
+ if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ LOG(DEBUG) << "EOS from decoder.";
+ media_status_t status = AMediaCodec_signalEndOfInputStream(mEncoder->getCodec());
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "SignalEOS on encoder returned error: " << status;
+ mStatus = status;
+ }
+ }
+}
+
+void VideoTrackTranscoder::dequeueOutputSample(int32_t bufferIndex,
+ AMediaCodecBufferInfo bufferInfo) {
+ if (bufferIndex >= 0) {
+ size_t sampleSize = 0;
+ uint8_t* buffer =
+ AMediaCodec_getOutputBuffer(mEncoder->getCodec(), bufferIndex, &sampleSize);
+
+ MediaSample::OnSampleReleasedCallback bufferReleaseCallback =
+ [encoder = mEncoder](MediaSample* sample) {
+ AMediaCodec_releaseOutputBuffer(encoder->getCodec(), sample->bufferId,
+ false /* render */);
+ };
+
+ std::shared_ptr<MediaSample> sample = MediaSample::createWithReleaseCallback(
+ buffer, bufferInfo.offset, bufferIndex, bufferReleaseCallback);
+ sample->info.size = bufferInfo.size;
+ sample->info.flags = bufferInfo.flags;
+ sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
+
+ const bool aborted = mOutputQueue->enqueue(sample);
+ if (aborted) {
+ LOG(ERROR) << "Output sample queue was aborted. Stopping transcode.";
+ mStatus = AMEDIA_ERROR_IO; // TODO: Define custom error codes?
+ return;
+ }
+ } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+ AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mEncoder->getCodec());
+ LOG(DEBUG) << "Encoder output format changed: " << AMediaFormat_toString(newFormat);
+ }
+
+ if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ LOG(DEBUG) << "EOS from encoder.";
+ mEosFromEncoder = true;
+ }
+}
+
+void VideoTrackTranscoder::updateTrackFormat(AMediaFormat* outputFormat) {
+ if (mActualOutputFormat != nullptr) {
+ LOG(WARNING) << "Ignoring duplicate format change.";
+ return;
+ }
+
+ AMediaFormat* formatCopy = AMediaFormat_new();
+ if (!formatCopy || AMediaFormat_copy(formatCopy, outputFormat) != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to copy outputFormat";
+ AMediaFormat_delete(formatCopy);
+ mStatus = AMEDIA_ERROR_INVALID_PARAMETER;
+ return;
+ }
+
+ // Generate the actual track format for muxer based on the encoder output format,
+ // since many vital information comes in the encoder format (eg. CSD).
+ // Transfer necessary fields from the user-configured track format (derived from
+ // source track format and user transcoding request) where needed.
+
+ // Transfer SAR settings:
+ // If mDestinationFormat has SAR set, it means the original source has SAR specified
+ // at container level. This is supposed to override any SAR settings in the bitstream,
+ // thus should always be transferred to the container of the transcoded file.
+ int32_t sarWidth, sarHeight;
+ if (AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_SAR_WIDTH, &sarWidth) &&
+ (sarWidth > 0) &&
+ AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_SAR_HEIGHT, &sarHeight) &&
+ (sarHeight > 0)) {
+ AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_SAR_WIDTH, sarWidth);
+ AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_SAR_HEIGHT, sarHeight);
+ }
+ // Transfer DAR settings.
+ int32_t displayWidth, displayHeight;
+ if (AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_DISPLAY_WIDTH, &displayWidth) &&
+ (displayWidth > 0) &&
+ AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_DISPLAY_HEIGHT,
+ &displayHeight) &&
+ (displayHeight > 0)) {
+ AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_DISPLAY_WIDTH, displayWidth);
+ AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_DISPLAY_HEIGHT, displayHeight);
+ }
+
+ // Transfer rotation settings.
+ // Note that muxer itself doesn't take rotation from the track format. It requires
+ // AMediaMuxer_setOrientationHint to set the rotation. Here we pass the rotation to
+ // MediaSampleWriter using the track format. MediaSampleWriter will then call
+ // AMediaMuxer_setOrientationHint as needed.
+ int32_t rotation;
+ if (AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_ROTATION, &rotation) &&
+ (rotation != 0)) {
+ AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_ROTATION, rotation);
+ }
+
+ // Transfer track duration.
+ // Preserve the source track duration by sending it to MediaSampleWriter.
+ int64_t durationUs;
+ if (AMediaFormat_getInt64(mSourceFormat.get(), AMEDIAFORMAT_KEY_DURATION, &durationUs) &&
+ durationUs > 0) {
+ AMediaFormat_setInt64(formatCopy, AMEDIAFORMAT_KEY_DURATION, durationUs);
+ }
+
+ // TODO: transfer other fields as required.
+
+ mActualOutputFormat = std::shared_ptr<AMediaFormat>(formatCopy, &AMediaFormat_delete);
+
+ notifyTrackFormatAvailable();
+}
+
+media_status_t VideoTrackTranscoder::runTranscodeLoop() {
+ // Push start decoder and encoder as two messages, so that these are subject to the
+ // stop request as well. If the job is cancelled (or paused) immediately after start,
+ // we don't need to waste time start then stop the codecs.
+ mCodecMessageQueue.push([this] {
+ media_status_t status = AMediaCodec_start(mDecoder);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to start video decoder: " << status;
+ mStatus = status;
+ }
+ });
+
+ mCodecMessageQueue.push([this] {
+ media_status_t status = AMediaCodec_start(mEncoder->getCodec());
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to start video encoder: " << status;
+ mStatus = status;
+ }
+ mEncoder->setStarted();
+ });
+
+ // Process codec events until EOS is reached, transcoding is stopped or an error occurs.
+ while (!mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
+ std::function<void()> message = mCodecMessageQueue.pop();
+ message();
+ }
+
+ // Return error if transcoding was stopped before it finished.
+ if (mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
+ mStatus = AMEDIA_ERROR_UNKNOWN; // TODO: Define custom error codes?
+ }
+
+ AMediaCodec_stop(mDecoder);
+ return mStatus;
+}
+
+void VideoTrackTranscoder::abortTranscodeLoop() {
+ // Push abort message to the front of the codec event queue.
+ mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
+}
+
+std::shared_ptr<AMediaFormat> VideoTrackTranscoder::getOutputFormat() const {
+ return mActualOutputFormat;
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/benchmark/Android.bp b/media/libmediatranscoding/transcoder/benchmark/Android.bp
new file mode 100644
index 0000000..507c943
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/benchmark/Android.bp
@@ -0,0 +1,6 @@
+cc_test {
+ name: "MediaTranscoderBenchmark",
+ srcs: ["MediaTranscoderBenchmark.cpp"],
+ shared_libs: ["libmediatranscoder", "libmediandk"],
+ static_libs: ["libgoogle-benchmark"],
+}
diff --git a/media/libmediatranscoding/transcoder/benchmark/MediaTranscoderBenchmark.cpp b/media/libmediatranscoding/transcoder/benchmark/MediaTranscoderBenchmark.cpp
new file mode 100644
index 0000000..a680ea3
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/benchmark/MediaTranscoderBenchmark.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+/**
+ * Native media transcoder library benchmark tests.
+ *
+ * How to run the benchmark:
+ *
+ * 1. Download the media assets from http://go/transcodingbenchmark and push the directory
+ * ("TranscodingBenchmark") to /data/local/tmp.
+ *
+ * 2. Compile the benchmark and sync to device:
+ * $ mm -j72 && adb sync
+ *
+ * 3. Run:
+ * $ adb shell /data/nativetest64/MediaTranscoderBenchmark/MediaTranscoderBenchmark
+ */
+
+#include <benchmark/benchmark.h>
+#include <fcntl.h>
+#include <media/MediaTranscoder.h>
+
+using namespace android;
+
+class TranscoderCallbacks : public MediaTranscoder::CallbackInterface {
+public:
+ virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mFinished = true;
+ mCondition.notify_all();
+ }
+
+ virtual void onError(const MediaTranscoder* transcoder __unused,
+ media_status_t error) override {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mFinished = true;
+ mStatus = error;
+ mCondition.notify_all();
+ }
+
+ virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
+ int32_t progress __unused) override {}
+
+ virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
+ const std::shared_ptr<const Parcel>& pausedState
+ __unused) override {}
+
+ bool waitForTranscodingFinished() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mFinished) {
+ if (mCondition.wait_for(lock, std::chrono::minutes(5)) == std::cv_status::timeout) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ media_status_t mStatus = AMEDIA_OK;
+
+private:
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ bool mFinished = false;
+};
+
+static void TranscodeMediaFile(benchmark::State& state, const std::string& srcFileName,
+ const std::string& dstFileName, bool includeAudio) {
+ // Default bitrate
+ static constexpr int32_t kVideoBitRate = 20 * 1000 * 1000; // 20Mbs
+ // Write-only, create file if non-existent.
+ static constexpr int kDstOpenFlags = O_WRONLY | O_CREAT;
+ // User R+W permission.
+ static constexpr int kDstFileMode = S_IRUSR | S_IWUSR;
+ // Asset directory
+ static const std::string kAssetDirectory = "/data/local/tmp/TranscodingBenchmark/";
+
+ int srcFd = 0;
+ int dstFd = 0;
+
+ std::string srcPath = kAssetDirectory + srcFileName;
+ std::string dstPath = kAssetDirectory + dstFileName;
+
+ auto callbacks = std::make_shared<TranscoderCallbacks>();
+ media_status_t status = AMEDIA_OK;
+
+ if ((srcFd = open(srcPath.c_str(), O_RDONLY)) < 0) {
+ state.SkipWithError("Unable to open source file");
+ goto exit;
+ }
+ if ((dstFd = open(dstPath.c_str(), kDstOpenFlags, kDstFileMode)) < 0) {
+ state.SkipWithError("Unable to open destination file");
+ goto exit;
+ }
+
+ for (auto _ : state) {
+ auto transcoder = MediaTranscoder::create(callbacks, nullptr);
+
+ status = transcoder->configureSource(srcFd);
+ if (status != AMEDIA_OK) {
+ state.SkipWithError("Unable to configure transcoder source");
+ goto exit;
+ }
+
+ status = transcoder->configureDestination(dstFd);
+ if (status != AMEDIA_OK) {
+ state.SkipWithError("Unable to configure transcoder destination");
+ goto exit;
+ }
+
+ std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats();
+ for (int i = 0; i < trackFormats.size(); ++i) {
+ AMediaFormat* srcFormat = trackFormats[i].get();
+ AMediaFormat* dstFormat = nullptr;
+
+ const char* mime = nullptr;
+ if (!AMediaFormat_getString(srcFormat, AMEDIAFORMAT_KEY_MIME, &mime)) {
+ state.SkipWithError("Source track format does not have MIME type");
+ goto exit;
+ }
+
+ if (strncmp(mime, "video/", 6) == 0) {
+ dstFormat = AMediaFormat_new();
+ AMediaFormat_setInt32(dstFormat, AMEDIAFORMAT_KEY_BIT_RATE, kVideoBitRate);
+
+ int32_t frameCount;
+ if (AMediaFormat_getInt32(srcFormat, AMEDIAFORMAT_KEY_FRAME_COUNT, &frameCount)) {
+ state.counters["VideoFrameRate"] =
+ benchmark::Counter(frameCount, benchmark::Counter::kIsRate);
+ }
+ } else if (!includeAudio && strncmp(mime, "audio/", 6) == 0) {
+ continue;
+ }
+
+ status = transcoder->configureTrackFormat(i, dstFormat);
+ if (dstFormat != nullptr) {
+ AMediaFormat_delete(dstFormat);
+ }
+ if (status != AMEDIA_OK) {
+ state.SkipWithError("Unable to configure track");
+ goto exit;
+ }
+ }
+
+ status = transcoder->start();
+ if (status != AMEDIA_OK) {
+ state.SkipWithError("Unable to start transcoder");
+ goto exit;
+ }
+
+ if (!callbacks->waitForTranscodingFinished()) {
+ state.SkipWithError("Transcoder timed out");
+ goto exit;
+ }
+ if (callbacks->mStatus != AMEDIA_OK) {
+ state.SkipWithError("Transcoder error when running");
+ goto exit;
+ }
+ }
+
+exit:
+ if (srcFd > 0) close(srcFd);
+ if (dstFd > 0) close(dstFd);
+}
+
+// Benchmark registration wrapper for transcoding.
+#define TRANSCODER_BENCHMARK(func) \
+ BENCHMARK(func)->UseRealTime()->MeasureProcessCPUTime()->Unit(benchmark::kMillisecond)
+
+static void BM_TranscodeAvc2AvcAudioVideo2AudioVideo(benchmark::State& state) {
+ TranscodeMediaFile(state, "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4",
+ "video_1920x1080_3648frame_h264_22Mbps_30fps_aac_transcoded_AV.mp4",
+ true /* includeAudio */);
+}
+
+static void BM_TranscodeAvc2AvcAudioVideo2Video(benchmark::State& state) {
+ TranscodeMediaFile(state, "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4",
+ "video_1920x1080_3648frame_h264_22Mbps_30fps_aac_transcoded_V.mp4",
+ false /* includeAudio */);
+}
+
+static void BM_TranscodeAvc2AvcVideo2Video(benchmark::State& state) {
+ TranscodeMediaFile(state, "video_1920x1080_3648frame_h264_22Mbps_30fps.mp4",
+ "video_1920x1080_3648frame_h264_22Mbps_30fps_transcoded_V.mp4",
+ false /* includeAudio */);
+}
+
+TRANSCODER_BENCHMARK(BM_TranscodeAvc2AvcAudioVideo2AudioVideo);
+TRANSCODER_BENCHMARK(BM_TranscodeAvc2AvcAudioVideo2Video);
+TRANSCODER_BENCHMARK(BM_TranscodeAvc2AvcVideo2Video);
+
+BENCHMARK_MAIN();
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSample.h b/media/libmediatranscoding/transcoder/include/media/MediaSample.h
new file mode 100644
index 0000000..8a239a6
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSample.h
@@ -0,0 +1,133 @@
+/*
+ * 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_MEDIA_SAMPLE_H
+#define ANDROID_MEDIA_SAMPLE_H
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+
+namespace android {
+
+/**
+ * Media sample flags.
+ * These flags purposely match the media NDK's buffer and extractor flags with one exception. The
+ * NDK extractor's flag for encrypted samples (AMEDIAEXTRACTOR_SAMPLE_FLAG_ENCRYPTED) is equal to 2,
+ * i.e. the same as SAMPLE_FLAG_CODEC_CONFIG below and NDK's AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG.
+ * Sample producers based on the NDK's extractor is responsible for catching those values.
+ * Note that currently the media transcoder does not support encrypted samples.
+ */
+enum : uint32_t {
+ SAMPLE_FLAG_SYNC_SAMPLE = 1,
+ SAMPLE_FLAG_CODEC_CONFIG = 2,
+ SAMPLE_FLAG_END_OF_STREAM = 4,
+ SAMPLE_FLAG_PARTIAL_FRAME = 8,
+};
+
+/**
+ * MediaSampleInfo is an object that carries information about a compressed media sample without
+ * holding any sample data.
+ */
+struct MediaSampleInfo {
+ /** The sample's presentation timestamp in microseconds. */
+ int64_t presentationTimeUs = 0;
+
+ /** The size of the compressed sample data in bytes. */
+ size_t size = 0;
+
+ /** Sample flags. */
+ uint32_t flags = 0;
+};
+
+/**
+ * MediaSample holds a compressed media sample in memory.
+ */
+struct MediaSample {
+ /**
+ * Callback to notify that a media sample is about to be released, giving the creator a chance
+ * to reclaim the data buffer backing the sample. Once this callback returns, the media sample
+ * instance *will* be released so it cannot be used outside of the callback. To enable the
+ * callback, create the media sample with {@link #createWithReleaseCallback}.
+ * @param sample The sample to be released.
+ */
+ using OnSampleReleasedCallback = std::function<void(MediaSample* sample)>;
+
+ /**
+ * Creates a new media sample instance with a registered release callback. The release callback
+ * will get called right before the media sample is released giving the creator a chance to
+ * reclaim the buffer.
+ * @param buffer Byte buffer containing the sample's compressed data.
+ * @param dataOffset Offset, in bytes, to the sample's compressed data inside the buffer.
+ * @param bufferId Buffer identifier that can be used to identify the buffer on release.
+ * @param releaseCallback The sample release callback.
+ * @return A new media sample instance.
+ */
+ static std::shared_ptr<MediaSample> createWithReleaseCallback(
+ uint8_t* buffer, size_t dataOffset, uint32_t bufferId,
+ OnSampleReleasedCallback releaseCallback) {
+ MediaSample* sample = new MediaSample(buffer, dataOffset, bufferId, releaseCallback);
+ return std::shared_ptr<MediaSample>(
+ sample, std::bind(&MediaSample::releaseSample, std::placeholders::_1));
+ }
+
+ /**
+ * Byte buffer containing the sample's compressed data. The media sample instance does not take
+ * ownership of the buffer and will not automatically release the memory, but the caller can
+ * register a release callback by creating the media sample with
+ * {@link #createWithReleaseCallback}.
+ */
+ const uint8_t* buffer = nullptr;
+
+ /** Offset, in bytes, to the sample's compressed data inside the buffer. */
+ size_t dataOffset = 0;
+
+ /**
+ * Buffer identifier. This identifier is likely only meaningful to the sample data producer and
+ * can be used for reclaiming the buffer once a consumer is done processing it.
+ */
+ uint32_t bufferId = 0xBAADF00D;
+
+ /** Media sample information. */
+ MediaSampleInfo info;
+
+ MediaSample() = default;
+
+private:
+ MediaSample(uint8_t* buffer, size_t dataOffset, uint32_t bufferId,
+ OnSampleReleasedCallback releaseCallback)
+ : buffer(buffer),
+ dataOffset(dataOffset),
+ bufferId(bufferId),
+ mReleaseCallback(releaseCallback){};
+
+ static void releaseSample(MediaSample* sample) {
+ if (sample->mReleaseCallback != nullptr) {
+ sample->mReleaseCallback(sample);
+ }
+ delete sample;
+ }
+
+ // Do not allow copying to prevent dangling pointers in the copied object after the original is
+ // released.
+ MediaSample(const MediaSample&) = delete;
+ MediaSample& operator=(const MediaSample&) = delete;
+
+ const OnSampleReleasedCallback mReleaseCallback = nullptr;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleQueue.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleQueue.h
new file mode 100644
index 0000000..dc22423
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleQueue.h
@@ -0,0 +1,66 @@
+/*
+ * 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_MEDIA_SAMPLE_QUEUE_H
+#define ANDROID_MEDIA_SAMPLE_QUEUE_H
+
+#include <media/MediaSample.h>
+#include <utils/Mutex.h>
+
+#include <memory>
+#include <mutex>
+#include <queue>
+
+namespace android {
+
+/**
+ * MediaSampleQueue asynchronously connects a producer and a consumer of media samples.
+ * Media samples flows through the queue in FIFO order. If the queue is empty the consumer will be
+ * blocked until a new media sample is added or until the producer aborts the queue operation.
+ */
+class MediaSampleQueue {
+public:
+ /**
+ * Enqueues a media sample at the end of the queue and notifies potentially waiting consumers.
+ * If the queue has previously been aborted this method does nothing.
+ * @param sample The media sample to enqueue.
+ * @return True if the queue has been aborted.
+ */
+ bool enqueue(const std::shared_ptr<MediaSample>& sample);
+
+ /**
+ * Removes the next media sample from the queue and returns it. If the queue has previously been
+ * aborted this method returns null. Note that this method will block while the queue is empty.
+ * @param[out] sample The next media sample in the queue.
+ * @return True if the queue has been aborted.
+ */
+ bool dequeue(std::shared_ptr<MediaSample>* sample /* nonnull */);
+
+ /**
+ * Aborts the queue operation. This clears the queue and notifies waiting consumers. After the
+ * has been aborted it is not possible to enqueue more samples, and dequeue will return null.
+ */
+ void abort();
+
+private:
+ std::queue<std::shared_ptr<MediaSample>> mSampleQueue GUARDED_BY(mMutex);
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ bool mAborted GUARDED_BY(mMutex) = false;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_QUEUE_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleReader.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleReader.h
new file mode 100644
index 0000000..acebac2
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleReader.h
@@ -0,0 +1,100 @@
+/*
+ * 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_MEDIA_SAMPLE_READER_H
+#define ANDROID_MEDIA_SAMPLE_READER_H
+
+#include <media/MediaSample.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+
+namespace android {
+
+/**
+ * MediaSampleReader is an interface for reading media samples from a container.
+ * MediaSampleReader allows for reading samples from multiple tracks independently of each other
+ * while preserving the order of samples within each individual track.
+ * MediaSampleReader implementations are thread safe and can be used by multiple threads
+ * concurrently. But note that MediaSampleReader only maintains one state per track so concurrent
+ * usage of the same track from multiple threads has no benefit.
+ */
+class MediaSampleReader {
+public:
+ /**
+ * Returns the file format of the media container as a AMediaFormat.
+ * The caller is responsible for releasing the format when finished with it using
+ * AMediaFormat_delete().
+ * @return The file media format.
+ */
+ virtual AMediaFormat* getFileFormat() = 0;
+
+ /**
+ * Returns the number of tracks in the media container.
+ * @return The number of tracks.
+ */
+ virtual size_t getTrackCount() const = 0;
+
+ /**
+ * Returns the media format of a specific track as a AMediaFormat.
+ * The caller is responsible for releasing the format when finished with it using
+ * AMediaFormat_delete().
+ * @param trackIndex The track index (zero-based).
+ * @return The track media format.
+ */
+ virtual AMediaFormat* getTrackFormat(int trackIndex) = 0;
+
+ /**
+ * Returns the sample information for the current sample in the specified track.
+ * @param trackIndex The track index (zero-based).
+ * @param info Pointer to a MediaSampleInfo object where the sample information is written.
+ * @return AMEDIA_OK on success, AMEDIA_ERROR_END_OF_STREAM if there are no more samples to read
+ * from the track and AMEDIA_ERROR_INVALID_PARAMETER if trackIndex is out of bounds or the
+ * info pointer is NULL. Other AMEDIA_ERROR_* return values may not be recoverable.
+ */
+ virtual media_status_t getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) = 0;
+
+ /**
+ * Reads the current sample's data into the supplied buffer.
+ * @param trackIndex The track index (zero-based).
+ * @param buffer The buffer to write the sample's data to.
+ * @param bufferSize The size of the supplied buffer.
+ * @return AMEDIA_OK on success, AMEDIA_ERROR_END_OF_STREAM if there are no more samples to read
+ * from the track and AMEDIA_ERROR_INVALID_PARAMETER if trackIndex is out of bounds, if the
+ * buffer pointer is NULL or if bufferSize is too small for the sample. Other AMEDIA_ERROR_*
+ * return values may not be recoverable.
+ */
+ virtual media_status_t readSampleDataForTrack(int trackIndex, uint8_t* buffer,
+ size_t bufferSize) = 0;
+
+ /**
+ * Advance the specified track to the next sample.
+ * @param trackIndex The track index (zero-based).
+ */
+ virtual void advanceTrack(int trackIndex) = 0;
+
+ /** Destructor. */
+ virtual ~MediaSampleReader() = default;
+
+ /** Constructor. */
+ MediaSampleReader() = default;
+
+private:
+ MediaSampleReader(const MediaSampleReader&) = delete;
+ MediaSampleReader& operator=(const MediaSampleReader&) = delete;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_READER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleReaderNDK.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleReaderNDK.h
new file mode 100644
index 0000000..2dc9029
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleReaderNDK.h
@@ -0,0 +1,112 @@
+/*
+ * 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_MEDIA_SAMPLE_READER_NDK_H
+#define ANDROID_MEDIA_SAMPLE_READER_NDK_H
+
+#include <media/MediaSampleReader.h>
+#include <media/NdkMediaExtractor.h>
+
+#include <memory>
+#include <mutex>
+#include <vector>
+
+namespace android {
+
+/**
+ * MediaSampleReaderNDK is a concrete implementation of the MediaSampleReader interface based on the
+ * media NDK extractor.
+ */
+class MediaSampleReaderNDK : public MediaSampleReader {
+public:
+ /**
+ * Creates a new MediaSampleReaderNDK instance wrapped in a shared pointer.
+ * @param fd Source file descriptor. The caller is responsible for closing the fd and it is safe
+ * to do so when this method returns.
+ * @param offset Source data offset.
+ * @param size Source data size.
+ * @return A shared pointer referencing the new MediaSampleReaderNDK instance on success, or an
+ * empty shared pointer if an error occurred.
+ */
+ static std::shared_ptr<MediaSampleReader> createFromFd(int fd, size_t offset, size_t size);
+
+ AMediaFormat* getFileFormat() override;
+ size_t getTrackCount() const override;
+ AMediaFormat* getTrackFormat(int trackIndex) override;
+ media_status_t getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) override;
+ media_status_t readSampleDataForTrack(int trackIndex, uint8_t* buffer,
+ size_t bufferSize) override;
+ void advanceTrack(int trackIndex) override;
+
+ virtual ~MediaSampleReaderNDK() override;
+
+private:
+ /**
+ * Creates a new MediaSampleReaderNDK object from an AMediaExtractor. The extractor needs to be
+ * initialized with a valid data source before attempting to create a MediaSampleReaderNDK.
+ * @param extractor The initialized media extractor.
+ */
+ MediaSampleReaderNDK(AMediaExtractor* extractor);
+ media_status_t init();
+
+ AMediaExtractor* mExtractor = nullptr;
+ std::mutex mExtractorMutex;
+ const size_t mTrackCount;
+
+ int mExtractorTrackIndex = -1;
+ uint64_t mExtractorSampleIndex = 0;
+
+ /**
+ * SamplePosition describes the position of a single sample in the media file using its
+ * timestamp and index in the file.
+ */
+ struct SamplePosition {
+ uint64_t index = 0;
+ int64_t timeStampUs = 0;
+ bool isSet = false;
+
+ void set(uint64_t sampleIndex, int64_t sampleTimeUs) {
+ index = sampleIndex;
+ timeStampUs = sampleTimeUs;
+ isSet = true;
+ }
+
+ void reset() { isSet = false; }
+ };
+
+ /**
+ * SampleCursor keeps track of the sample position for a specific track. When the track is
+ * advanced, previous is set to current, current to next and next is reset. As the extractor
+ * advances over the combined timeline of tracks, it updates current and next for the track it
+ * points to if they are not already set.
+ */
+ struct SampleCursor {
+ SamplePosition previous;
+ SamplePosition current;
+ SamplePosition next;
+ };
+
+ /** Samples cursor for each track in the file. */
+ std::vector<SampleCursor> mTrackCursors;
+
+ bool advanceExtractor_l();
+ media_status_t positionExtractorForTrack_l(int trackIndex);
+ media_status_t seekExtractorBackwards_l(int64_t targetTimeUs, int targetTrackIndex,
+ uint64_t targetSampleIndex);
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_READER_NDK_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
new file mode 100644
index 0000000..92ddc2f
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
@@ -0,0 +1,191 @@
+/*
+ * 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_MEDIA_SAMPLE_WRITER_H
+#define ANDROID_MEDIA_SAMPLE_WRITER_H
+
+#include <media/MediaSampleQueue.h>
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+#include <utils/Mutex.h>
+
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+namespace android {
+
+/**
+ * Muxer interface used by MediaSampleWriter.
+ * Methods in this interface are guaranteed to be called sequentially by MediaSampleWriter.
+ */
+class MediaSampleWriterMuxerInterface {
+public:
+ /**
+ * Adds a new track to the muxer.
+ * @param trackFormat Format of the new track.
+ * @return A non-negative track index on success, or a negative number on failure.
+ */
+ virtual ssize_t addTrack(AMediaFormat* trackFormat) = 0;
+
+ /** Starts the muxer. */
+ virtual media_status_t start() = 0;
+ /**
+ * Writes sample data to a previously added track.
+ * @param trackIndex Index of the track the sample data belongs to.
+ * @param data The sample data.
+ * @param info The sample information.
+ * @return The number of bytes written.
+ */
+ virtual media_status_t writeSampleData(size_t trackIndex, const uint8_t* data,
+ const AMediaCodecBufferInfo* info) = 0;
+
+ /** Stops the muxer. */
+ virtual media_status_t stop() = 0;
+ virtual ~MediaSampleWriterMuxerInterface() = default;
+};
+
+/**
+ * MediaSampleWriter writes samples in interleaved segments of a configurable duration.
+ * Each track have its own MediaSampleQueue from which samples are dequeued by the sample writer in
+ * output order. The dequeued samples are written to an instance of the writer's muxer interface.
+ * The default muxer interface implementation is based directly on AMediaMuxer.
+ */
+class MediaSampleWriter {
+public:
+ /** The default segment length. */
+ static constexpr uint32_t kDefaultTrackSegmentLengthUs = 1 * 1000 * 1000; // 1 sec.
+
+ /** Callback interface. */
+ class CallbackInterface {
+ public:
+ /**
+ * Sample writer finished. The finished callback is only called after the sample writer has
+ * been successfully started.
+ */
+ virtual void onFinished(const MediaSampleWriter* writer, media_status_t status) = 0;
+
+ /** Sample writer progress update in percent. */
+ virtual void onProgressUpdate(const MediaSampleWriter* writer, int32_t progress) = 0;
+
+ virtual ~CallbackInterface() = default;
+ };
+
+ /**
+ * Constructor with custom segment length.
+ * @param trackSegmentLengthUs The segment length to use for this MediaSampleWriter.
+ */
+ MediaSampleWriter(uint32_t trackSegmentLengthUs)
+ : mTrackSegmentLengthUs(trackSegmentLengthUs), mMuxer(nullptr), mState(UNINITIALIZED){};
+
+ /** Constructor using the default segment length. */
+ MediaSampleWriter() : MediaSampleWriter(kDefaultTrackSegmentLengthUs){};
+
+ /** Destructor. */
+ ~MediaSampleWriter();
+
+ /**
+ * Initializes the sample writer with its default muxer implementation. MediaSampleWriter needs
+ * to be initialized before tracks are added and can only be initialized once.
+ * @param fd An open file descriptor to write to. The caller is responsible for closing this
+ * file descriptor and it is safe to do so once this method returns.
+ * @param callbacks Client callback object that gets called by the sample writer.
+ * @return True if the writer was successfully initialized.
+ */
+ bool init(int fd, const std::weak_ptr<CallbackInterface>& callbacks /* nonnull */);
+
+ /**
+ * Initializes the sample writer with a custom muxer interface implementation.
+ * @param muxer The custom muxer interface implementation.
+ * @param @param callbacks Client callback object that gets called by the sample writer.
+ * @return True if the writer was successfully initialized.
+ */
+ bool init(const std::shared_ptr<MediaSampleWriterMuxerInterface>& muxer /* nonnull */,
+ const std::weak_ptr<CallbackInterface>& callbacks /* nonnull */);
+
+ /**
+ * Adds a new track to the sample writer. Tracks must be added after the sample writer has been
+ * initialized and before it is started.
+ * @param sampleQueue The MediaSampleQueue to pull samples from.
+ * @param trackFormat The format of the track to add.
+ * @return True if the track was successfully added.
+ */
+ bool addTrack(const std::shared_ptr<MediaSampleQueue>& sampleQueue /* nonnull */,
+ const std::shared_ptr<AMediaFormat>& trackFormat /* nonnull */);
+
+ /**
+ * Starts the sample writer. The sample writer will start processing samples and writing them to
+ * its muxer on an internal thread. MediaSampleWriter can only be started once.
+ * @return True if the sample writer was successfully started.
+ */
+ bool start();
+
+ /**
+ * Stops the sample writer. If the sample writer is not yet finished its operation will be
+ * aborted and an error value will be returned to the client in the callback supplied to
+ * {@link #start}. If the sample writer has already finished and the client callback has fired
+ * the writer has already automatically stopped and there is no need to call stop manually. Once
+ * the sample writer has been stopped it cannot be restarted.
+ * @return True if the sample writer was successfully stopped on this call. False if the sample
+ * writer was already stopped or was never started.
+ */
+ bool stop();
+
+private:
+ media_status_t writeSamples();
+ media_status_t runWriterLoop();
+
+ struct TrackRecord {
+ TrackRecord(const std::shared_ptr<MediaSampleQueue>& sampleQueue, size_t trackIndex,
+ int64_t durationUs, bool isVideo)
+ : mSampleQueue(sampleQueue),
+ mTrackIndex(trackIndex),
+ mDurationUs(durationUs),
+ mFirstSampleTimeUs(0),
+ mPrevSampleTimeUs(0),
+ mFirstSampleTimeSet(false),
+ mReachedEos(false),
+ mIsVideo(isVideo) {}
+
+ std::shared_ptr<MediaSampleQueue> mSampleQueue;
+ const size_t mTrackIndex;
+ int64_t mDurationUs;
+ int64_t mFirstSampleTimeUs;
+ int64_t mPrevSampleTimeUs;
+ bool mFirstSampleTimeSet;
+ bool mReachedEos;
+ bool mIsVideo;
+ };
+
+ const uint32_t mTrackSegmentLengthUs;
+ std::weak_ptr<CallbackInterface> mCallbacks;
+ std::shared_ptr<MediaSampleWriterMuxerInterface> mMuxer;
+ std::vector<TrackRecord> mTracks;
+ std::thread mThread;
+
+ std::mutex mStateMutex;
+ enum : int {
+ UNINITIALIZED,
+ INITIALIZED,
+ STARTED,
+ STOPPED,
+ } mState GUARDED_BY(mStateMutex);
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_WRITER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
new file mode 100644
index 0000000..60a9139
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -0,0 +1,131 @@
+/*
+ * 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_MEDIA_TRACK_TRANSCODER_H
+#define ANDROID_MEDIA_TRACK_TRANSCODER_H
+
+#include <media/MediaSampleQueue.h>
+#include <media/MediaSampleReader.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+#include <utils/Mutex.h>
+
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+namespace android {
+
+class MediaTrackTranscoderCallback;
+
+/**
+ * Base class for all track transcoders. MediaTrackTranscoder operates asynchronously on an internal
+ * thread and communicates through a MediaTrackTranscoderCallback instance. Transcoded samples are
+ * enqueued on the MediaTrackTranscoder's output queue. Samples need to be dequeued from the output
+ * queue or the transcoder will run out of buffers and stall. Once the consumer is done with a
+ * transcoded sample it is the consumer's responsibility to as soon as possible release all
+ * references to that sample in order to return the buffer to the transcoder. MediaTrackTranscoder
+ * is an abstract class and instances are created through one of the concrete subclasses.
+ *
+ * The base class MediaTrackTranscoder is responsible for thread and state management and guarantees
+ * that operations {configure, start, stop} are sent to the derived class in correct order.
+ * MediaTrackTranscoder is also responsible for delivering callback notifications once the
+ * transcoder has been successfully started.
+ */
+class MediaTrackTranscoder {
+public:
+ /**
+ * Configures the track transcoder with an input MediaSampleReader and a destination format.
+ * A track transcoder have to be configured before it is started.
+ * @param mediaSampleReader The MediaSampleReader to read input samples from.
+ * @param trackIndex The index of the track to transcode in mediaSampleReader.
+ * @param destinationFormat The destination format.
+ * @return AMEDIA_OK if the track transcoder was successfully configured.
+ */
+ media_status_t configure(const std::shared_ptr<MediaSampleReader>& mediaSampleReader,
+ int trackIndex,
+ const std::shared_ptr<AMediaFormat>& destinationFormat);
+
+ /**
+ * Starts the track transcoder. Once started the track transcoder have to be stopped by calling
+ * {@link #stop}, even after completing successfully. Start should only be called once.
+ * @return True if the track transcoder started, or false if it had already been started.
+ */
+ bool start();
+
+ /**
+ * Stops the track transcoder. Once the transcoding has been stopped it cannot be restarted
+ * again. It is safe to call stop multiple times.
+ * @return True if the track transcoder stopped, or false if it was already stopped.
+ */
+ bool stop();
+
+ /**
+ * Retrieves the track transcoder's output sample queue.
+ * @return The output sample queue.
+ */
+ std::shared_ptr<MediaSampleQueue> getOutputQueue() const;
+
+ /**
+ * Retrieves the track transcoder's final output format. The output is available after the
+ * track transcoder has been successfully configured.
+ * @return The track output format.
+ */
+ virtual std::shared_ptr<AMediaFormat> getOutputFormat() const = 0;
+
+ virtual ~MediaTrackTranscoder() = default;
+
+protected:
+ MediaTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
+ : mOutputQueue(std::make_shared<MediaSampleQueue>()),
+ mTranscoderCallback(transcoderCallback){};
+
+ // Called by subclasses when the actual track format becomes available.
+ void notifyTrackFormatAvailable();
+
+ // configureDestinationFormat needs to be implemented by subclasses, and gets called on an
+ // external thread before start.
+ virtual media_status_t configureDestinationFormat(
+ const std::shared_ptr<AMediaFormat>& destinationFormat) = 0;
+
+ // runTranscodeLoop needs to be implemented by subclasses, and gets called on
+ // MediaTrackTranscoder's internal thread when the track transcoder is started.
+ virtual media_status_t runTranscodeLoop() = 0;
+
+ // abortTranscodeLoop needs to be implemented by subclasses, and should request transcoding to
+ // be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
+ virtual void abortTranscodeLoop() = 0;
+
+ std::shared_ptr<MediaSampleQueue> mOutputQueue;
+ std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+ int mTrackIndex;
+ std::shared_ptr<AMediaFormat> mSourceFormat;
+
+private:
+ const std::weak_ptr<MediaTrackTranscoderCallback> mTranscoderCallback;
+ std::mutex mStateMutex;
+ std::thread mTranscodingThread GUARDED_BY(mStateMutex);
+ enum {
+ UNINITIALIZED,
+ CONFIGURED,
+ STARTED,
+ STOPPED,
+ } mState GUARDED_BY(mStateMutex) = UNINITIALIZED;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRACK_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
new file mode 100644
index 0000000..654171e
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
@@ -0,0 +1,53 @@
+/*
+ * 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_MEDIA_TRACK_TRANSCODER_CALLBACK_H
+#define ANDROID_MEDIA_TRACK_TRANSCODER_CALLBACK_H
+
+#include <media/NdkMediaError.h>
+
+namespace android {
+
+class MediaTrackTranscoder;
+
+/** Callback interface for MediaTrackTranscoder. */
+class MediaTrackTranscoderCallback {
+public:
+ /**
+ * Called when the MediaTrackTranscoder's actual track format becomes available.
+ * @param transcoder The MediaTrackTranscoder whose track format becomes available.
+ */
+ virtual void onTrackFormatAvailable(const MediaTrackTranscoder* transcoder);
+ /**
+ * Called when the MediaTrackTranscoder instance have finished transcoding all media samples
+ * successfully.
+ * @param transcoder The MediaTrackTranscoder that finished the transcoding.
+ */
+ virtual void onTrackFinished(const MediaTrackTranscoder* transcoder);
+
+ /**
+ * Called when the MediaTrackTranscoder instance encountered an error it could not recover from.
+ * @param transcoder The MediaTrackTranscoder that encountered the error.
+ * @param status The non-zero error code describing the encountered error.
+ */
+ virtual void onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status);
+
+protected:
+ virtual ~MediaTrackTranscoderCallback() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRACK_TRANSCODER_CALLBACK_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
new file mode 100644
index 0000000..8d96867
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -0,0 +1,152 @@
+/*
+ * 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_MEDIA_TRANSCODER_H
+#define ANDROID_MEDIA_TRANSCODER_H
+
+#include <media/MediaSampleWriter.h>
+#include <media/MediaTrackTranscoderCallback.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+#include <utils/Mutex.h>
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <unordered_set>
+
+namespace android {
+
+class MediaSampleReader;
+class Parcel;
+
+class MediaTranscoder : public std::enable_shared_from_this<MediaTranscoder>,
+ public MediaTrackTranscoderCallback,
+ public MediaSampleWriter::CallbackInterface {
+public:
+ /** Callbacks from transcoder to client. */
+ class CallbackInterface {
+ public:
+ /** Transcoder finished successfully. */
+ virtual void onFinished(const MediaTranscoder* transcoder) = 0;
+
+ /** Transcoder encountered an unrecoverable error. */
+ virtual void onError(const MediaTranscoder* transcoder, media_status_t error) = 0;
+
+ /** Transcoder progress update reported in percent from 0 to 100. */
+ virtual void onProgressUpdate(const MediaTranscoder* transcoder, int32_t progress) = 0;
+
+ /**
+ * Transcoder lost codec resources and paused operations. The client can resume transcoding
+ * again when resources are available by either:
+ * 1) Calling resume on the same MediaTranscoder instance.
+ * 2) Creating a new MediaTranscoding instance with the paused state and then calling
+ * resume.
+ */
+ virtual void onCodecResourceLost(const MediaTranscoder* transcoder,
+ const std::shared_ptr<const Parcel>& pausedState) = 0;
+
+ virtual ~CallbackInterface() = default;
+ };
+
+ /**
+ * Creates a new MediaTranscoder instance. If the supplied paused state is valid, the transcoder
+ * will be initialized with the paused state and be ready to be resumed right away. It is not
+ * possible to change any configurations on a paused transcoder.
+ */
+ static std::shared_ptr<MediaTranscoder> create(
+ const std::shared_ptr<CallbackInterface>& callbacks,
+ const std::shared_ptr<const Parcel>& pausedState = nullptr);
+
+ /** Configures source from path fd. */
+ media_status_t configureSource(int fd);
+
+ /** Gets the media formats of all tracks in the file. */
+ std::vector<std::shared_ptr<AMediaFormat>> getTrackFormats() const;
+
+ /**
+ * Configures transcoding of a track. Tracks that are not configured will not present in the
+ * final transcoded file, i.e. tracks will be dropped by default. Passing nullptr for
+ * trackFormat means the track will be copied unchanged ("passthrough") to the destination.
+ * Track configurations must be done after the source has been configured.
+ * Note: trackFormat is not modified but cannot be const.
+ */
+ media_status_t configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat);
+
+ /** Configures destination from fd. */
+ media_status_t configureDestination(int fd);
+
+ /** Starts transcoding. No configurations can be made once the transcoder has started. */
+ media_status_t start();
+
+ /**
+ * Pauses transcoding. The transcoder's paused state is returned through pausedState. The
+ * paused state is only needed for resuming transcoding with a new MediaTranscoder instance. The
+ * caller can resume transcoding with the current MediaTranscoder instance at any time by
+ * calling resume(). It is not required to cancel a paused transcoder. The paused state is
+ * independent and the caller can always initialize a new transcoder instance with the same
+ * paused state. If the caller wishes to abandon a paused transcoder's operation they can
+ * release the transcoder instance, clear the paused state and delete the partial destination
+ * file. The caller can optionally call cancel to let the transcoder clean up the partial
+ * destination file.
+ *
+ * TODO: use NDK AParcel instead
+ * libbinder shouldn't be used by mainline modules. When transcoding goes mainline
+ * it needs to be replaced by stable AParcel.
+ */
+ media_status_t pause(std::shared_ptr<const Parcel>* pausedState);
+
+ /** Resumes a paused transcoding. */
+ media_status_t resume();
+
+ /** Cancels the transcoding. Once canceled the transcoding can not be restarted. Client
+ * will be responsible for cleaning up the abandoned file. */
+ media_status_t cancel();
+
+ virtual ~MediaTranscoder() = default;
+
+private:
+ MediaTranscoder(const std::shared_ptr<CallbackInterface>& callbacks);
+
+ // MediaTrackTranscoderCallback
+ virtual void onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) override;
+ virtual void onTrackFinished(const MediaTrackTranscoder* transcoder) override;
+ virtual void onTrackError(const MediaTrackTranscoder* transcoder,
+ media_status_t status) override;
+ // ~MediaTrackTranscoderCallback
+
+ // MediaSampleWriter::CallbackInterface
+ virtual void onFinished(const MediaSampleWriter* writer, media_status_t status) override;
+ virtual void onProgressUpdate(const MediaSampleWriter* writer, int32_t progress) override;
+ // ~MediaSampleWriter::CallbackInterface
+
+ void onSampleWriterFinished(media_status_t status);
+ void sendCallback(media_status_t status);
+
+ std::shared_ptr<CallbackInterface> mCallbacks;
+ std::shared_ptr<MediaSampleReader> mSampleReader;
+ std::unique_ptr<MediaSampleWriter> mSampleWriter;
+ std::vector<std::shared_ptr<AMediaFormat>> mSourceTrackFormats;
+ std::vector<std::shared_ptr<MediaTrackTranscoder>> mTrackTranscoders;
+ std::mutex mTracksAddedMutex;
+ std::unordered_set<const MediaTrackTranscoder*> mTracksAdded GUARDED_BY(mTracksAddedMutex);
+
+ std::atomic_bool mCallbackSent = false;
+ std::atomic_bool mCancelled = false;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/NdkCommon.h b/media/libmediatranscoding/transcoder/include/media/NdkCommon.h
new file mode 100644
index 0000000..dcba812
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/NdkCommon.h
@@ -0,0 +1,56 @@
+/*
+ * 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_MEDIA_TRANSCODING_NDK_COMMON_H
+#define ANDROID_MEDIA_TRANSCODING_NDK_COMMON_H
+
+#include <media/NdkMediaFormat.h>
+
+extern const char* AMEDIA_MIMETYPE_VIDEO_VP8;
+extern const char* AMEDIA_MIMETYPE_VIDEO_VP9;
+extern const char* AMEDIA_MIMETYPE_VIDEO_AV1;
+extern const char* AMEDIA_MIMETYPE_VIDEO_AVC;
+extern const char* AMEDIA_MIMETYPE_VIDEO_HEVC;
+extern const char* AMEDIA_MIMETYPE_VIDEO_MPEG4;
+extern const char* AMEDIA_MIMETYPE_VIDEO_H263;
+
+// TODO(b/146420990)
+// TODO: make MediaTranscoder use the consts from this header.
+typedef enum {
+ OUTPUT_FORMAT_START = 0,
+ OUTPUT_FORMAT_MPEG_4 = OUTPUT_FORMAT_START,
+ OUTPUT_FORMAT_WEBM = OUTPUT_FORMAT_START + 1,
+ OUTPUT_FORMAT_THREE_GPP = OUTPUT_FORMAT_START + 2,
+ OUTPUT_FORMAT_HEIF = OUTPUT_FORMAT_START + 3,
+ OUTPUT_FORMAT_OGG = OUTPUT_FORMAT_START + 4,
+ OUTPUT_FORMAT_LIST_END = OUTPUT_FORMAT_START + 4,
+} MuxerFormat;
+
+// Color formats supported by encoder - should mirror supportedColorList
+// from MediaCodecConstants.h (are these going to be deprecated)
+static constexpr int COLOR_FormatYUV420SemiPlanar = 21;
+static constexpr int COLOR_FormatYUV420Flexible = 0x7F420888;
+static constexpr int COLOR_FormatSurface = 0x7f000789;
+
+// constants not defined in NDK
+extern const char* TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME;
+extern const char* TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE;
+extern const char* TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES;
+static constexpr int TBD_AMEDIACODEC_BUFFER_FLAG_KEY_FRAME = 0x1;
+
+static constexpr int kBitrateModeConstant = 2;
+
+#endif // ANDROID_MEDIA_TRANSCODING_NDK_COMMON_H
diff --git a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
new file mode 100644
index 0000000..b9491ed
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
@@ -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.
+ */
+
+#ifndef ANDROID_PASSTHROUGH_TRACK_TRANSCODER_H
+#define ANDROID_PASSTHROUGH_TRACK_TRANSCODER_H
+
+#include <media/MediaTrackTranscoder.h>
+#include <media/NdkMediaFormat.h>
+
+#include <condition_variable>
+#include <map>
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+
+/**
+ * Track transcoder for passthrough mode. Passthrough mode copies sample data from a track unchanged
+ * from source file to destination file. This track transcoder uses an internal pool of buffers.
+ * When the maximum number of buffers are allocated and all of them are waiting on the output queue
+ * the transcoder will stall until samples are dequeued from the output queue and released.
+ */
+class PassthroughTrackTranscoder : public MediaTrackTranscoder {
+public:
+ /** Maximum number of buffers to be allocated at a given time. */
+ static constexpr int kMaxBufferCountDefault = 16;
+
+ PassthroughTrackTranscoder(
+ const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
+ : MediaTrackTranscoder(transcoderCallback),
+ mBufferPool(std::make_shared<BufferPool>(kMaxBufferCountDefault)){};
+ virtual ~PassthroughTrackTranscoder() override = default;
+
+private:
+ friend class BufferPoolTests;
+
+ /** Class to pool and reuse buffers. */
+ class BufferPool {
+ public:
+ explicit BufferPool(int maxBufferCount) : mMaxBufferCount(maxBufferCount){};
+ ~BufferPool();
+
+ /**
+ * Retrieve a buffer from the pool. Buffers are allocated on demand. This method will block
+ * if the maximum number of buffers is reached and there are no free buffers available.
+ * @param minimumBufferSize The minimum size of the buffer.
+ * @return The buffer or nullptr if allocation failed or the pool was aborted.
+ */
+ uint8_t* getBufferWithSize(size_t minimumBufferSize);
+
+ /**
+ * Return a buffer to the pool.
+ * @param buffer The buffer to return.
+ */
+ void returnBuffer(uint8_t* buffer);
+
+ /** Wakes up threads waiting on buffers and prevents new buffers from being returned. */
+ void abort();
+
+ private:
+ // Maximum number of active buffers at a time.
+ const int mMaxBufferCount;
+
+ // Map containing all tracked buffers.
+ std::unordered_map<uint8_t*, size_t> mAddressSizeMap GUARDED_BY(mMutex);
+
+ // Map containing the currently free buffers.
+ std::multimap<size_t, uint8_t*> mFreeBufferMap GUARDED_BY(mMutex);
+
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ bool mAborted GUARDED_BY(mMutex) = false;
+ };
+
+ // MediaTrackTranscoder
+ media_status_t runTranscodeLoop() override;
+ void abortTranscodeLoop() override;
+ media_status_t configureDestinationFormat(
+ const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+ std::shared_ptr<AMediaFormat> getOutputFormat() const override;
+ // ~MediaTrackTranscoder
+
+ std::shared_ptr<BufferPool> mBufferPool;
+ bool mEosFromSource = false;
+ std::atomic_bool mStopRequested = false;
+};
+
+} // namespace android
+#endif // ANDROID_PASSTHROUGH_TRACK_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
new file mode 100644
index 0000000..0a7bf33
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -0,0 +1,99 @@
+/*
+ * 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_VIDEO_TRACK_TRANSCODER_H
+#define ANDROID_VIDEO_TRACK_TRANSCODER_H
+
+#include <android/native_window.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaFormat.h>
+
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+
+namespace android {
+
+/**
+ * Track transcoder for video tracks. VideoTrackTranscoder uses AMediaCodec from the Media NDK
+ * internally. The two media codecs are run in asynchronous mode and shares uncompressed buffers
+ * using a native surface (ANativeWindow). Codec callback events are placed on a message queue and
+ * serviced in order on the transcoding thread managed by MediaTrackTranscoder.
+ */
+class VideoTrackTranscoder : public std::enable_shared_from_this<VideoTrackTranscoder>,
+ public MediaTrackTranscoder {
+public:
+ static std::shared_ptr<VideoTrackTranscoder> create(
+ const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback);
+
+ virtual ~VideoTrackTranscoder() override;
+
+private:
+ friend struct AsyncCodecCallbackDispatch;
+
+ // Minimal blocking queue used as a message queue by VideoTrackTranscoder.
+ template <typename T>
+ class BlockingQueue {
+ public:
+ void push(T const& value, bool front = false);
+ T pop();
+
+ private:
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ std::deque<T> mQueue;
+ };
+ class CodecWrapper;
+
+ VideoTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
+ : MediaTrackTranscoder(transcoderCallback){};
+
+ // MediaTrackTranscoder
+ media_status_t runTranscodeLoop() override;
+ void abortTranscodeLoop() override;
+ media_status_t configureDestinationFormat(
+ const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+ std::shared_ptr<AMediaFormat> getOutputFormat() const override;
+ // ~MediaTrackTranscoder
+
+ // Enqueues an input sample with the decoder.
+ void enqueueInputSample(int32_t bufferIndex);
+
+ // Moves a decoded buffer from the decoder's output to the encoder's input.
+ void transferBuffer(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo);
+
+ // Dequeues an encoded buffer from the encoder and adds it to the output queue.
+ void dequeueOutputSample(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo);
+
+ // Updates the video track's actual format based on encoder output format.
+ void updateTrackFormat(AMediaFormat* outputFormat);
+
+ AMediaCodec* mDecoder = nullptr;
+ std::shared_ptr<CodecWrapper> mEncoder;
+ ANativeWindow* mSurface = nullptr;
+ bool mEosFromSource = false;
+ bool mEosFromEncoder = false;
+ bool mStopRequested = false;
+ media_status_t mStatus = AMEDIA_OK;
+ MediaSampleInfo mSampleInfo;
+ BlockingQueue<std::function<void()>> mCodecMessageQueue;
+ std::shared_ptr<AMediaFormat> mDestinationFormat;
+ std::shared_ptr<AMediaFormat> mActualOutputFormat;
+};
+
+} // namespace android
+#endif // ANDROID_VIDEO_TRACK_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/tests/Android.bp b/media/libmediatranscoding/transcoder/tests/Android.bp
new file mode 100644
index 0000000..4160c30
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/Android.bp
@@ -0,0 +1,83 @@
+// Unit tests for libmediatranscoder.
+
+filegroup {
+ name: "test_assets",
+ srcs: ["assets/*"],
+}
+
+cc_defaults {
+ name: "testdefaults",
+
+ header_libs: [
+ "libbase_headers",
+ "libmedia_headers",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "libmediandk",
+ "libmediatranscoder",
+ "libutils",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ data: [":test_assets"],
+ test_config_template: "AndroidTestTemplate.xml",
+ test_suites: ["device-tests", "TranscoderTests"],
+}
+
+// MediaSampleReaderNDK unit test
+cc_test {
+ name: "MediaSampleReaderNDKTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaSampleReaderNDKTests.cpp"],
+}
+
+// MediaSampleQueue unit test
+cc_test {
+ name: "MediaSampleQueueTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaSampleQueueTests.cpp"],
+}
+
+// MediaTrackTranscoder unit test
+cc_test {
+ name: "MediaTrackTranscoderTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaTrackTranscoderTests.cpp"],
+ shared_libs: ["libbinder_ndk"],
+}
+
+// VideoTrackTranscoder unit test
+cc_test {
+ name: "VideoTrackTranscoderTests",
+ defaults: ["testdefaults"],
+ srcs: ["VideoTrackTranscoderTests.cpp"],
+}
+
+// PassthroughTrackTranscoder unit test
+cc_test {
+ name: "PassthroughTrackTranscoderTests",
+ defaults: ["testdefaults"],
+ srcs: ["PassthroughTrackTranscoderTests.cpp"],
+ shared_libs: ["libcrypto"],
+}
+
+// MediaSampleWriter unit test
+cc_test {
+ name: "MediaSampleWriterTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaSampleWriterTests.cpp"],
+}
+
+// MediaTranscoder unit test
+cc_test {
+ name: "MediaTranscoderTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaTranscoderTests.cpp"],
+}
diff --git a/media/libmediatranscoding/transcoder/tests/AndroidTestTemplate.xml b/media/libmediatranscoding/transcoder/tests/AndroidTestTemplate.xml
new file mode 100644
index 0000000..a9a7e2e
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/AndroidTestTemplate.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Unit test configuration for {MODULE}">
+ <option name="test-suite-tag" value="TranscoderTests" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="false" />
+ <option name="push-file"
+ key="assets"
+ value="/data/local/tmp/TranscodingTestAssets" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
+
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleQueueTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleQueueTests.cpp
new file mode 100644
index 0000000..2046ca0
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleQueueTests.cpp
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+// Unit Test for MediaSampleQueue
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaSampleQueueTests"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleQueue.h>
+
+#include <thread>
+
+namespace android {
+
+/** Duration to use when delaying threads to order operations. */
+static constexpr int64_t kThreadDelayDurationMs = 100;
+
+class MediaSampleQueueTests : public ::testing::Test {
+public:
+ MediaSampleQueueTests() { LOG(DEBUG) << "MediaSampleQueueTests created"; }
+ ~MediaSampleQueueTests() { LOG(DEBUG) << "MediaSampleQueueTests destroyed"; }
+};
+
+static std::shared_ptr<MediaSample> newSample(uint32_t id) {
+ return MediaSample::createWithReleaseCallback(nullptr /* buffer */, 0 /* offset */, id,
+ nullptr /* callback */);
+}
+
+TEST_F(MediaSampleQueueTests, TestSequentialDequeueOrder) {
+ LOG(DEBUG) << "TestSequentialDequeueOrder Starts";
+
+ static constexpr int kNumSamples = 4;
+ MediaSampleQueue sampleQueue;
+
+ // Enqueue loop.
+ for (int i = 0; i < kNumSamples; ++i) {
+ sampleQueue.enqueue(newSample(i));
+ }
+
+ // Dequeue loop.
+ for (int i = 0; i < kNumSamples; ++i) {
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_NE(sample, nullptr);
+ EXPECT_EQ(sample->bufferId, i);
+ EXPECT_FALSE(aborted);
+ }
+}
+
+TEST_F(MediaSampleQueueTests, TestInterleavedDequeueOrder) {
+ LOG(DEBUG) << "TestInterleavedDequeueOrder Starts";
+
+ static constexpr int kNumSamples = 4;
+ MediaSampleQueue sampleQueue;
+
+ // Enqueue and dequeue.
+ for (int i = 0; i < kNumSamples; ++i) {
+ sampleQueue.enqueue(newSample(i));
+
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_NE(sample, nullptr);
+ EXPECT_EQ(sample->bufferId, i);
+ EXPECT_FALSE(aborted);
+ }
+}
+
+TEST_F(MediaSampleQueueTests, TestBlockingDequeue) {
+ LOG(DEBUG) << "TestBlockingDequeue Starts";
+
+ MediaSampleQueue sampleQueue;
+
+ std::thread enqueueThread([&sampleQueue] {
+ // Note: This implementation is a bit racy. Any amount of sleep will not guarantee that the
+ // main thread will be blocked on the sample queue by the time this thread calls enqueue.
+ // But we can say with high confidence that it will and the test will not fail regardless.
+ std::this_thread::sleep_for(std::chrono::milliseconds(kThreadDelayDurationMs));
+ sampleQueue.enqueue(newSample(1));
+ });
+
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_NE(sample, nullptr);
+ EXPECT_EQ(sample->bufferId, 1);
+ EXPECT_FALSE(aborted);
+
+ enqueueThread.join();
+}
+
+TEST_F(MediaSampleQueueTests, TestDequeueBufferRelease) {
+ LOG(DEBUG) << "TestDequeueBufferRelease Starts";
+
+ static constexpr int kNumSamples = 4;
+ std::vector<bool> bufferReleased(kNumSamples, false);
+
+ MediaSample::OnSampleReleasedCallback callback = [&bufferReleased](MediaSample* sample) {
+ bufferReleased[sample->bufferId] = true;
+ };
+
+ MediaSampleQueue sampleQueue;
+ for (int i = 0; i < kNumSamples; ++i) {
+ bool aborted = sampleQueue.enqueue(
+ MediaSample::createWithReleaseCallback(nullptr, 0, i, callback));
+ EXPECT_FALSE(aborted);
+ }
+
+ for (int i = 0; i < kNumSamples; ++i) {
+ EXPECT_FALSE(bufferReleased[i]);
+ }
+
+ for (int i = 0; i < kNumSamples; ++i) {
+ {
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_NE(sample, nullptr);
+ EXPECT_EQ(sample->bufferId, i);
+ EXPECT_FALSE(bufferReleased[i]);
+ EXPECT_FALSE(aborted);
+ }
+
+ for (int j = 0; j < kNumSamples; ++j) {
+ EXPECT_EQ(bufferReleased[j], j <= i);
+ }
+ }
+}
+
+TEST_F(MediaSampleQueueTests, TestAbortBufferRelease) {
+ LOG(DEBUG) << "TestAbortBufferRelease Starts";
+
+ static constexpr int kNumSamples = 4;
+ std::vector<bool> bufferReleased(kNumSamples, false);
+
+ MediaSample::OnSampleReleasedCallback callback = [&bufferReleased](MediaSample* sample) {
+ bufferReleased[sample->bufferId] = true;
+ };
+
+ MediaSampleQueue sampleQueue;
+ for (int i = 0; i < kNumSamples; ++i) {
+ bool aborted = sampleQueue.enqueue(
+ MediaSample::createWithReleaseCallback(nullptr, 0, i, callback));
+ EXPECT_FALSE(aborted);
+ }
+
+ for (int i = 0; i < kNumSamples; ++i) {
+ EXPECT_FALSE(bufferReleased[i]);
+ }
+
+ sampleQueue.abort();
+
+ for (int i = 0; i < kNumSamples; ++i) {
+ EXPECT_TRUE(bufferReleased[i]);
+ }
+}
+
+TEST_F(MediaSampleQueueTests, TestNonEmptyAbort) {
+ LOG(DEBUG) << "TestNonEmptyAbort Starts";
+
+ MediaSampleQueue sampleQueue;
+ bool aborted = sampleQueue.enqueue(newSample(1));
+ EXPECT_FALSE(aborted);
+
+ sampleQueue.abort();
+
+ std::shared_ptr<MediaSample> sample;
+ aborted = sampleQueue.dequeue(&sample);
+ EXPECT_TRUE(aborted);
+ EXPECT_EQ(sample, nullptr);
+
+ aborted = sampleQueue.enqueue(sample);
+ EXPECT_TRUE(aborted);
+}
+
+TEST_F(MediaSampleQueueTests, TestEmptyAbort) {
+ LOG(DEBUG) << "TestEmptyAbort Starts";
+
+ MediaSampleQueue sampleQueue;
+ sampleQueue.abort();
+
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_TRUE(aborted);
+ EXPECT_EQ(sample, nullptr);
+
+ aborted = sampleQueue.enqueue(sample);
+ EXPECT_TRUE(aborted);
+}
+
+TEST_F(MediaSampleQueueTests, TestBlockingAbort) {
+ LOG(DEBUG) << "TestBlockingAbort Starts";
+
+ MediaSampleQueue sampleQueue;
+
+ std::thread abortingThread([&sampleQueue] {
+ // Note: This implementation is a bit racy. Any amount of sleep will not guarantee that the
+ // main thread will be blocked on the sample queue by the time this thread calls abort.
+ // But we can say with high confidence that it will and the test will not fail regardless.
+ std::this_thread::sleep_for(std::chrono::milliseconds(kThreadDelayDurationMs));
+ sampleQueue.abort();
+ });
+
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_TRUE(aborted);
+ EXPECT_EQ(sample, nullptr);
+
+ abortingThread.join();
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp
new file mode 100644
index 0000000..805095e
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+// Unit Test for MediaSampleReaderNDK
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaSampleReaderNDKTests"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <utils/Timers.h>
+
+// TODO(b/153453392): Test more asset types and validate sample data from readSampleDataForTrack.
+
+namespace android {
+
+#define SEC_TO_USEC(s) ((s)*1000 * 1000)
+
+class MediaSampleReaderNDKTests : public ::testing::Test {
+public:
+ MediaSampleReaderNDKTests() { LOG(DEBUG) << "MediaSampleReaderNDKTests created"; }
+
+ void SetUp() override {
+ LOG(DEBUG) << "MediaSampleReaderNDKTests set up";
+ const char* sourcePath =
+ "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+ mExtractor = AMediaExtractor_new();
+ ASSERT_NE(mExtractor, nullptr);
+
+ mSourceFd = open(sourcePath, O_RDONLY);
+ ASSERT_GT(mSourceFd, 0);
+
+ mFileSize = lseek(mSourceFd, 0, SEEK_END);
+ lseek(mSourceFd, 0, SEEK_SET);
+
+ media_status_t status =
+ AMediaExtractor_setDataSourceFd(mExtractor, mSourceFd, 0, mFileSize);
+ ASSERT_EQ(status, AMEDIA_OK);
+
+ mTrackCount = AMediaExtractor_getTrackCount(mExtractor);
+ for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ AMediaExtractor_selectTrack(mExtractor, trackIndex);
+ }
+ }
+
+ void initExtractorTimestamps() {
+ // Save all sample timestamps, per track, as reported by the extractor.
+ mExtractorTimestamps.resize(mTrackCount);
+ do {
+ const int trackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ const int64_t sampleTime = AMediaExtractor_getSampleTime(mExtractor);
+
+ mExtractorTimestamps[trackIndex].push_back(sampleTime);
+ } while (AMediaExtractor_advance(mExtractor));
+ }
+
+ void TearDown() override {
+ LOG(DEBUG) << "MediaSampleReaderNDKTests tear down";
+ AMediaExtractor_delete(mExtractor);
+ close(mSourceFd);
+ }
+
+ ~MediaSampleReaderNDKTests() { LOG(DEBUG) << "MediaSampleReaderNDKTests destroyed"; }
+
+ AMediaExtractor* mExtractor = nullptr;
+ size_t mTrackCount;
+ int mSourceFd;
+ size_t mFileSize;
+ std::vector<std::vector<int64_t>> mExtractorTimestamps;
+};
+
+TEST_F(MediaSampleReaderNDKTests, TestSampleTimes) {
+ LOG(DEBUG) << "TestSampleTimes Starts";
+
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(mSourceFd, 0, mFileSize);
+ ASSERT_TRUE(sampleReader);
+
+ MediaSampleInfo info;
+ int trackEosCount = 0;
+ std::vector<bool> trackReachedEos(mTrackCount, false);
+ std::vector<std::vector<int64_t>> readerTimestamps(mTrackCount);
+
+ // Initialize the extractor timestamps.
+ initExtractorTimestamps();
+
+ // Read 5s of each track at a time.
+ const int64_t chunkDurationUs = SEC_TO_USEC(5);
+ int64_t chunkEndTimeUs = chunkDurationUs;
+
+ // Loop until all tracks have reached End Of Stream.
+ while (trackEosCount < mTrackCount) {
+ for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ if (trackReachedEos[trackIndex]) continue;
+
+ // Advance current track to next chunk end time.
+ do {
+ media_status_t status = sampleReader->getSampleInfoForTrack(trackIndex, &info);
+ if (status != AMEDIA_OK) {
+ ASSERT_EQ(status, AMEDIA_ERROR_END_OF_STREAM);
+ ASSERT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) != 0);
+ trackReachedEos[trackIndex] = true;
+ trackEosCount++;
+ break;
+ }
+ ASSERT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+ readerTimestamps[trackIndex].push_back(info.presentationTimeUs);
+ sampleReader->advanceTrack(trackIndex);
+ } while (info.presentationTimeUs < chunkEndTimeUs);
+ }
+ chunkEndTimeUs += chunkDurationUs;
+ }
+
+ for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ LOG(DEBUG) << "Track " << trackIndex << ", comparing "
+ << readerTimestamps[trackIndex].size() << " samples.";
+ ASSERT_EQ(readerTimestamps[trackIndex].size(), mExtractorTimestamps[trackIndex].size());
+ for (size_t sampleIndex = 0; sampleIndex < readerTimestamps[trackIndex].size();
+ sampleIndex++) {
+ ASSERT_EQ(readerTimestamps[trackIndex][sampleIndex],
+ mExtractorTimestamps[trackIndex][sampleIndex]);
+ }
+ }
+}
+
+TEST_F(MediaSampleReaderNDKTests, TestInvalidFd) {
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(0, 0, mFileSize);
+ ASSERT_TRUE(sampleReader == nullptr);
+
+ sampleReader = MediaSampleReaderNDK::createFromFd(-1, 0, mFileSize);
+ ASSERT_TRUE(sampleReader == nullptr);
+}
+
+TEST_F(MediaSampleReaderNDKTests, TestZeroSize) {
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(mSourceFd, 0, 0);
+ ASSERT_TRUE(sampleReader == nullptr);
+}
+
+TEST_F(MediaSampleReaderNDKTests, TestInvalidOffset) {
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(mSourceFd, mFileSize, mFileSize);
+ ASSERT_TRUE(sampleReader == nullptr);
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
new file mode 100644
index 0000000..c82ec28
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
@@ -0,0 +1,612 @@
+/*
+ * 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.
+ */
+
+// Unit Test for MediaSampleWriter
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaSampleWriterTests"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleQueue.h>
+#include <media/MediaSampleWriter.h>
+#include <media/NdkMediaExtractor.h>
+
+#include <condition_variable>
+#include <list>
+#include <mutex>
+
+namespace android {
+
+/** Muxer interface to enable MediaSampleWriter testing. */
+class TestMuxer : public MediaSampleWriterMuxerInterface {
+public:
+ // MuxerInterface
+ ssize_t addTrack(AMediaFormat* trackFormat) override {
+ mEventQueue.push_back(AddTrack(trackFormat));
+ return mTrackCount++;
+ }
+ media_status_t start() override {
+ mEventQueue.push_back(Start());
+ return AMEDIA_OK;
+ }
+
+ media_status_t writeSampleData(size_t trackIndex, const uint8_t* data,
+ const AMediaCodecBufferInfo* info) override {
+ mEventQueue.push_back(WriteSample(trackIndex, data, info));
+ return AMEDIA_OK;
+ }
+ media_status_t stop() override {
+ mEventQueue.push_back(Stop());
+ return AMEDIA_OK;
+ }
+ // ~MuxerInterface
+
+ struct Event {
+ enum { NoEvent, AddTrack, Start, WriteSample, Stop } type = NoEvent;
+ const AMediaFormat* format = nullptr;
+ size_t trackIndex = 0;
+ const uint8_t* data = nullptr;
+ AMediaCodecBufferInfo info{};
+ };
+
+ static constexpr Event NoEvent = {Event::NoEvent, nullptr, 0, nullptr, {}};
+
+ static Event AddTrack(const AMediaFormat* format) {
+ return {.type = Event::AddTrack, .format = format};
+ }
+
+ static Event Start() { return {.type = Event::Start}; }
+ static Event Stop() { return {.type = Event::Stop}; }
+
+ static Event WriteSample(size_t trackIndex, const uint8_t* data,
+ const AMediaCodecBufferInfo* info) {
+ return {.type = Event::WriteSample, .trackIndex = trackIndex, .data = data, .info = *info};
+ }
+
+ const Event& popEvent() {
+ if (mEventQueue.empty()) {
+ mPoppedEvent = NoEvent;
+ } else {
+ mPoppedEvent = *mEventQueue.begin();
+ mEventQueue.pop_front();
+ }
+ return mPoppedEvent;
+ }
+
+private:
+ Event mPoppedEvent;
+ std::list<Event> mEventQueue;
+ ssize_t mTrackCount = 0;
+};
+
+bool operator==(const AMediaCodecBufferInfo& lhs, const AMediaCodecBufferInfo& rhs) {
+ return lhs.offset == rhs.offset && lhs.size == rhs.size &&
+ lhs.presentationTimeUs == rhs.presentationTimeUs && lhs.flags == rhs.flags;
+}
+
+bool operator==(const TestMuxer::Event& lhs, const TestMuxer::Event& rhs) {
+ return lhs.type == rhs.type && lhs.format == rhs.format && lhs.trackIndex == rhs.trackIndex &&
+ lhs.data == rhs.data && lhs.info == rhs.info;
+}
+
+/** Represents a media source file. */
+class TestMediaSource {
+public:
+ void init() {
+ static const char* sourcePath =
+ "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+ mExtractor = AMediaExtractor_new();
+ ASSERT_NE(mExtractor, nullptr);
+
+ int sourceFd = open(sourcePath, O_RDONLY);
+ ASSERT_GT(sourceFd, 0);
+
+ off_t fileSize = lseek(sourceFd, 0, SEEK_END);
+ lseek(sourceFd, 0, SEEK_SET);
+
+ media_status_t status = AMediaExtractor_setDataSourceFd(mExtractor, sourceFd, 0, fileSize);
+ ASSERT_EQ(status, AMEDIA_OK);
+ close(sourceFd);
+
+ mTrackCount = AMediaExtractor_getTrackCount(mExtractor);
+ ASSERT_GT(mTrackCount, 1);
+ for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ AMediaFormat* trackFormat = AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
+ ASSERT_NE(trackFormat, nullptr);
+
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+ if (strncmp(mime, "video/", 6) == 0) {
+ mVideoTrackIndex = trackIndex;
+ } else if (strncmp(mime, "audio/", 6) == 0) {
+ mAudioTrackIndex = trackIndex;
+ }
+
+ mTrackFormats.push_back(
+ std::shared_ptr<AMediaFormat>(trackFormat, &AMediaFormat_delete));
+
+ AMediaExtractor_selectTrack(mExtractor, trackIndex);
+ }
+ EXPECT_GE(mVideoTrackIndex, 0);
+ EXPECT_GE(mAudioTrackIndex, 0);
+ }
+
+ void reset() const {
+ media_status_t status = AMediaExtractor_seekTo(mExtractor, 0 /* seekPosUs */,
+ AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
+ ASSERT_EQ(status, AMEDIA_OK);
+ }
+
+ AMediaExtractor* mExtractor = nullptr;
+ size_t mTrackCount = 0;
+ std::vector<std::shared_ptr<AMediaFormat>> mTrackFormats;
+ int mVideoTrackIndex = -1;
+ int mAudioTrackIndex = -1;
+};
+
+class TestCallbacks : public MediaSampleWriter::CallbackInterface {
+public:
+ TestCallbacks(bool expectSuccess = true) : mExpectSuccess(expectSuccess) {}
+
+ bool hasFinished() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ return mFinished;
+ }
+
+ // MediaSampleWriter::CallbackInterface
+ virtual void onFinished(const MediaSampleWriter* writer __unused,
+ media_status_t status) override {
+ std::unique_lock<std::mutex> lock(mMutex);
+ EXPECT_FALSE(mFinished);
+ if (mExpectSuccess) {
+ EXPECT_EQ(status, AMEDIA_OK);
+ } else {
+ EXPECT_NE(status, AMEDIA_OK);
+ }
+ mFinished = true;
+ mCondition.notify_all();
+ }
+
+ virtual void onProgressUpdate(const MediaSampleWriter* writer __unused,
+ int32_t progress) override {
+ EXPECT_GT(progress, mLastProgress);
+ EXPECT_GE(progress, 0);
+ EXPECT_LE(progress, 100);
+
+ mLastProgress = progress;
+ mProgressUpdateCount++;
+ }
+ // ~MediaSampleWriter::CallbackInterface
+
+ void waitForWritingFinished() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mFinished) {
+ mCondition.wait(lock);
+ }
+ }
+
+ uint32_t getProgressUpdateCount() const { return mProgressUpdateCount; }
+
+private:
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ bool mFinished = false;
+ bool mExpectSuccess;
+ int32_t mLastProgress = -1;
+ uint32_t mProgressUpdateCount = 0;
+};
+
+class MediaSampleWriterTests : public ::testing::Test {
+public:
+ MediaSampleWriterTests() { LOG(DEBUG) << "MediaSampleWriterTests created"; }
+ ~MediaSampleWriterTests() { LOG(DEBUG) << "MediaSampleWriterTests destroyed"; }
+
+ static const TestMediaSource& getMediaSource() {
+ static TestMediaSource sMediaSource;
+ static std::once_flag sOnceToken;
+
+ std::call_once(sOnceToken, [] { sMediaSource.init(); });
+
+ sMediaSource.reset();
+ return sMediaSource;
+ }
+
+ static std::shared_ptr<MediaSample> newSample(int64_t ptsUs, uint32_t flags, size_t size,
+ size_t offset, const uint8_t* buffer) {
+ auto sample = std::make_shared<MediaSample>();
+ sample->info.presentationTimeUs = ptsUs;
+ sample->info.flags = flags;
+ sample->info.size = size;
+ sample->dataOffset = offset;
+ sample->buffer = buffer;
+ return sample;
+ }
+
+ static std::shared_ptr<MediaSample> newSampleEos() {
+ return newSample(0, SAMPLE_FLAG_END_OF_STREAM, 0, 0, nullptr);
+ }
+
+ static std::shared_ptr<MediaSample> newSampleWithPts(int64_t ptsUs) {
+ static uint32_t sampleCount = 0;
+
+ // Use sampleCount to get a unique dummy sample.
+ uint32_t sampleId = ++sampleCount;
+ return newSample(ptsUs, 0, sampleId, sampleId, reinterpret_cast<const uint8_t*>(sampleId));
+ }
+
+ void SetUp() override {
+ LOG(DEBUG) << "MediaSampleWriterTests set up";
+ mTestMuxer = std::make_shared<TestMuxer>();
+ mSampleQueue = std::make_shared<MediaSampleQueue>();
+ }
+
+ void TearDown() override {
+ LOG(DEBUG) << "MediaSampleWriterTests tear down";
+ mTestMuxer.reset();
+ mSampleQueue.reset();
+ }
+
+protected:
+ std::shared_ptr<TestMuxer> mTestMuxer;
+ std::shared_ptr<MediaSampleQueue> mSampleQueue;
+ std::shared_ptr<TestCallbacks> mTestCallbacks = std::make_shared<TestCallbacks>();
+};
+
+TEST_F(MediaSampleWriterTests, TestAddTrackWithoutInit) {
+ const TestMediaSource& mediaSource = getMediaSource();
+
+ MediaSampleWriter writer{};
+ EXPECT_FALSE(writer.addTrack(mSampleQueue, mediaSource.mTrackFormats[0]));
+}
+
+TEST_F(MediaSampleWriterTests, TestStartWithoutInit) {
+ MediaSampleWriter writer{};
+ EXPECT_FALSE(writer.start());
+}
+
+TEST_F(MediaSampleWriterTests, TestStartWithoutTracks) {
+ MediaSampleWriter writer{};
+ EXPECT_TRUE(writer.init(mTestMuxer, mTestCallbacks));
+ EXPECT_FALSE(writer.start());
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
+}
+
+TEST_F(MediaSampleWriterTests, TestAddInvalidTrack) {
+ MediaSampleWriter writer{};
+ EXPECT_TRUE(writer.init(mTestMuxer, mTestCallbacks));
+
+ EXPECT_FALSE(writer.addTrack(mSampleQueue, nullptr));
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
+
+ const TestMediaSource& mediaSource = getMediaSource();
+ EXPECT_FALSE(writer.addTrack(nullptr, mediaSource.mTrackFormats[0]));
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
+}
+
+TEST_F(MediaSampleWriterTests, TestDoubleStartStop) {
+ MediaSampleWriter writer{};
+
+ std::shared_ptr<TestCallbacks> callbacks =
+ std::make_shared<TestCallbacks>(false /* expectSuccess */);
+ EXPECT_TRUE(writer.init(mTestMuxer, callbacks));
+
+ const TestMediaSource& mediaSource = getMediaSource();
+ EXPECT_TRUE(writer.addTrack(mSampleQueue, mediaSource.mTrackFormats[0]));
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(mediaSource.mTrackFormats[0].get()));
+
+ ASSERT_TRUE(writer.start());
+ EXPECT_FALSE(writer.start());
+
+ EXPECT_TRUE(writer.stop());
+ EXPECT_TRUE(callbacks->hasFinished());
+ EXPECT_FALSE(writer.stop());
+}
+
+TEST_F(MediaSampleWriterTests, TestStopWithoutStart) {
+ MediaSampleWriter writer{};
+ EXPECT_TRUE(writer.init(mTestMuxer, mTestCallbacks));
+
+ const TestMediaSource& mediaSource = getMediaSource();
+ EXPECT_TRUE(writer.addTrack(mSampleQueue, mediaSource.mTrackFormats[0]));
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(mediaSource.mTrackFormats[0].get()));
+
+ EXPECT_FALSE(writer.stop());
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
+}
+
+TEST_F(MediaSampleWriterTests, TestStartWithoutCallback) {
+ MediaSampleWriter writer{};
+
+ std::weak_ptr<MediaSampleWriter::CallbackInterface> unassignedWp;
+ EXPECT_FALSE(writer.init(mTestMuxer, unassignedWp));
+
+ std::shared_ptr<MediaSampleWriter::CallbackInterface> unassignedSp;
+ EXPECT_FALSE(writer.init(mTestMuxer, unassignedSp));
+
+ const TestMediaSource& mediaSource = getMediaSource();
+ EXPECT_FALSE(writer.addTrack(mSampleQueue, mediaSource.mTrackFormats[0]));
+ ASSERT_FALSE(writer.start());
+}
+
+TEST_F(MediaSampleWriterTests, TestProgressUpdate) {
+ static constexpr uint32_t kSegmentLengthUs = 1;
+ const TestMediaSource& mediaSource = getMediaSource();
+
+ MediaSampleWriter writer{kSegmentLengthUs};
+ EXPECT_TRUE(writer.init(mTestMuxer, mTestCallbacks));
+
+ std::shared_ptr<AMediaFormat> videoFormat =
+ std::shared_ptr<AMediaFormat>(AMediaFormat_new(), &AMediaFormat_delete);
+ AMediaFormat_copy(videoFormat.get(),
+ mediaSource.mTrackFormats[mediaSource.mVideoTrackIndex].get());
+
+ AMediaFormat_setInt64(videoFormat.get(), AMEDIAFORMAT_KEY_DURATION, 100);
+ EXPECT_TRUE(writer.addTrack(mSampleQueue, videoFormat));
+ ASSERT_TRUE(writer.start());
+
+ for (int64_t pts = 0; pts < 100; ++pts) {
+ mSampleQueue->enqueue(newSampleWithPts(pts));
+ }
+ mSampleQueue->enqueue(newSampleEos());
+ mTestCallbacks->waitForWritingFinished();
+
+ EXPECT_EQ(mTestCallbacks->getProgressUpdateCount(), 100);
+}
+
+TEST_F(MediaSampleWriterTests, TestInterleaving) {
+ static constexpr uint32_t kSegmentLength = MediaSampleWriter::kDefaultTrackSegmentLengthUs;
+
+ MediaSampleWriter writer{kSegmentLength};
+ EXPECT_TRUE(writer.init(mTestMuxer, mTestCallbacks));
+
+ // Use two tracks for this test.
+ static constexpr int kNumTracks = 2;
+ std::shared_ptr<MediaSampleQueue> sampleQueues[kNumTracks];
+ std::vector<std::pair<std::shared_ptr<MediaSample>, size_t>> interleavedSamples;
+ const TestMediaSource& mediaSource = getMediaSource();
+
+ for (int trackIdx = 0; trackIdx < kNumTracks; ++trackIdx) {
+ sampleQueues[trackIdx] = std::make_shared<MediaSampleQueue>();
+
+ auto trackFormat = mediaSource.mTrackFormats[trackIdx % mediaSource.mTrackCount];
+ EXPECT_TRUE(writer.addTrack(sampleQueues[trackIdx], trackFormat));
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(trackFormat.get()));
+ }
+
+ // Create samples in the expected interleaved order for easy verification.
+ auto addSampleToTrackWithPts = [&interleavedSamples, &sampleQueues](int trackIndex,
+ int64_t pts) {
+ auto sample = newSampleWithPts(pts);
+ sampleQueues[trackIndex]->enqueue(sample);
+ interleavedSamples.emplace_back(sample, trackIndex);
+ };
+
+ addSampleToTrackWithPts(0, 0);
+ addSampleToTrackWithPts(0, kSegmentLength / 2);
+ addSampleToTrackWithPts(0, kSegmentLength); // Track 0 reached 1st segment end
+
+ addSampleToTrackWithPts(1, 0);
+ addSampleToTrackWithPts(1, kSegmentLength); // Track 1 reached 1st segment end
+
+ addSampleToTrackWithPts(0, kSegmentLength * 2); // Track 0 reached 2nd segment end
+
+ addSampleToTrackWithPts(1, kSegmentLength + 1);
+ addSampleToTrackWithPts(1, kSegmentLength * 2); // Track 1 reached 2nd segment end
+
+ addSampleToTrackWithPts(0, kSegmentLength * 2 + 1);
+
+ for (int trackIndex = 0; trackIndex < kNumTracks; ++trackIndex) {
+ sampleQueues[trackIndex]->enqueue(newSampleEos());
+ }
+
+ // Start the writer.
+ ASSERT_TRUE(writer.start());
+
+ // Wait for writer to complete.
+ mTestCallbacks->waitForWritingFinished();
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Start());
+
+ // Verify sample order.
+ for (auto entry : interleavedSamples) {
+ auto sample = entry.first;
+ auto trackIndex = entry.second;
+
+ const TestMuxer::Event& event = mTestMuxer->popEvent();
+ EXPECT_EQ(event.type, TestMuxer::Event::WriteSample);
+ EXPECT_EQ(event.trackIndex, trackIndex);
+ EXPECT_EQ(event.data, sample->buffer);
+ EXPECT_EQ(event.info.offset, sample->dataOffset);
+ EXPECT_EQ(event.info.size, sample->info.size);
+ EXPECT_EQ(event.info.presentationTimeUs, sample->info.presentationTimeUs);
+ EXPECT_EQ(event.info.flags, sample->info.flags);
+ }
+
+ // Verify EOS samples.
+ for (int trackIndex = 0; trackIndex < kNumTracks; ++trackIndex) {
+ auto trackFormat = mediaSource.mTrackFormats[trackIndex % mediaSource.mTrackCount];
+ int64_t duration = 0;
+ AMediaFormat_getInt64(trackFormat.get(), AMEDIAFORMAT_KEY_DURATION, &duration);
+
+ const AMediaCodecBufferInfo info = {0, 0, duration, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM};
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::WriteSample(trackIndex, nullptr, &info));
+ }
+
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
+ EXPECT_TRUE(writer.stop());
+ EXPECT_TRUE(mTestCallbacks->hasFinished());
+}
+
+TEST_F(MediaSampleWriterTests, TestAbortInputQueue) {
+ MediaSampleWriter writer{};
+ std::shared_ptr<TestCallbacks> callbacks =
+ std::make_shared<TestCallbacks>(false /* expectSuccess */);
+ EXPECT_TRUE(writer.init(mTestMuxer, callbacks));
+
+ // Use two tracks for this test.
+ static constexpr int kNumTracks = 2;
+ std::shared_ptr<MediaSampleQueue> sampleQueues[kNumTracks];
+ const TestMediaSource& mediaSource = getMediaSource();
+
+ for (int trackIdx = 0; trackIdx < kNumTracks; ++trackIdx) {
+ sampleQueues[trackIdx] = std::make_shared<MediaSampleQueue>();
+
+ auto trackFormat = mediaSource.mTrackFormats[trackIdx % mediaSource.mTrackCount];
+ EXPECT_TRUE(writer.addTrack(sampleQueues[trackIdx], trackFormat));
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(trackFormat.get()));
+ }
+
+ // Start the writer.
+ ASSERT_TRUE(writer.start());
+
+ // Abort the input queues and wait for the writer to complete.
+ for (int trackIdx = 0; trackIdx < kNumTracks; ++trackIdx) {
+ sampleQueues[trackIdx]->abort();
+ }
+
+ callbacks->waitForWritingFinished();
+
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Start());
+ EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
+ EXPECT_TRUE(writer.stop());
+}
+
+// Convenience function for reading a sample from an AMediaExtractor represented as a MediaSample.
+static std::shared_ptr<MediaSample> readSampleAndAdvance(AMediaExtractor* extractor,
+ size_t* trackIndexOut) {
+ int trackIndex = AMediaExtractor_getSampleTrackIndex(extractor);
+ if (trackIndex < 0) {
+ return nullptr;
+ }
+
+ if (trackIndexOut != nullptr) {
+ *trackIndexOut = trackIndex;
+ }
+
+ ssize_t sampleSize = AMediaExtractor_getSampleSize(extractor);
+ int64_t sampleTimeUs = AMediaExtractor_getSampleTime(extractor);
+ uint32_t flags = AMediaExtractor_getSampleFlags(extractor);
+
+ size_t bufferSize = static_cast<size_t>(sampleSize);
+ uint8_t* buffer = new uint8_t[bufferSize];
+
+ ssize_t dataRead = AMediaExtractor_readSampleData(extractor, buffer, bufferSize);
+ EXPECT_EQ(dataRead, sampleSize);
+
+ auto sample = MediaSample::createWithReleaseCallback(
+ buffer, 0 /* offset */, 0 /* id */, [buffer](MediaSample*) { delete[] buffer; });
+ sample->info.size = bufferSize;
+ sample->info.presentationTimeUs = sampleTimeUs;
+ sample->info.flags = flags;
+
+ (void)AMediaExtractor_advance(extractor);
+ return sample;
+}
+
+TEST_F(MediaSampleWriterTests, TestDefaultMuxer) {
+ // Write samples straight from an extractor and validate output file.
+ static const char* destinationPath =
+ "/data/local/tmp/MediaSampleWriterTests_TestDefaultMuxer_output.MP4";
+ const int destinationFd =
+ open(destinationPath, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IROTH);
+ ASSERT_GT(destinationFd, 0);
+
+ // Initialize writer.
+ MediaSampleWriter writer{};
+ EXPECT_TRUE(writer.init(destinationFd, mTestCallbacks));
+ close(destinationFd);
+
+ // Add tracks.
+ const TestMediaSource& mediaSource = getMediaSource();
+ std::vector<std::shared_ptr<MediaSampleQueue>> inputQueues;
+
+ for (size_t trackIndex = 0; trackIndex < mediaSource.mTrackCount; trackIndex++) {
+ inputQueues.push_back(std::make_shared<MediaSampleQueue>());
+ EXPECT_TRUE(
+ writer.addTrack(inputQueues[trackIndex], mediaSource.mTrackFormats[trackIndex]));
+ }
+
+ // Start the writer.
+ ASSERT_TRUE(writer.start());
+
+ // Enqueue samples and finally End Of Stream.
+ std::shared_ptr<MediaSample> sample;
+ size_t trackIndex;
+ while ((sample = readSampleAndAdvance(mediaSource.mExtractor, &trackIndex)) != nullptr) {
+ inputQueues[trackIndex]->enqueue(sample);
+ }
+ for (trackIndex = 0; trackIndex < mediaSource.mTrackCount; trackIndex++) {
+ inputQueues[trackIndex]->enqueue(newSampleEos());
+ }
+
+ // Wait for writer.
+ mTestCallbacks->waitForWritingFinished();
+ EXPECT_TRUE(writer.stop());
+
+ // Compare output file with source.
+ mediaSource.reset();
+
+ AMediaExtractor* extractor = AMediaExtractor_new();
+ ASSERT_NE(extractor, nullptr);
+
+ int sourceFd = open(destinationPath, O_RDONLY);
+ ASSERT_GT(sourceFd, 0);
+
+ off_t fileSize = lseek(sourceFd, 0, SEEK_END);
+ lseek(sourceFd, 0, SEEK_SET);
+
+ media_status_t status = AMediaExtractor_setDataSourceFd(extractor, sourceFd, 0, fileSize);
+ ASSERT_EQ(status, AMEDIA_OK);
+ close(sourceFd);
+
+ size_t trackCount = AMediaExtractor_getTrackCount(extractor);
+ EXPECT_EQ(trackCount, mediaSource.mTrackCount);
+
+ for (size_t trackIndex = 0; trackIndex < trackCount; trackIndex++) {
+ AMediaFormat* trackFormat = AMediaExtractor_getTrackFormat(extractor, trackIndex);
+ ASSERT_NE(trackFormat, nullptr);
+
+ AMediaExtractor_selectTrack(extractor, trackIndex);
+ }
+
+ // Compare samples.
+ std::shared_ptr<MediaSample> sample1 = readSampleAndAdvance(mediaSource.mExtractor, nullptr);
+ std::shared_ptr<MediaSample> sample2 = readSampleAndAdvance(extractor, nullptr);
+
+ while (sample1 != nullptr && sample2 != nullptr) {
+ EXPECT_EQ(sample1->info.presentationTimeUs, sample2->info.presentationTimeUs);
+ EXPECT_EQ(sample1->info.size, sample2->info.size);
+ EXPECT_EQ(sample1->info.flags, sample2->info.flags);
+
+ EXPECT_EQ(memcmp(sample1->buffer, sample2->buffer, sample1->info.size), 0);
+
+ sample1 = readSampleAndAdvance(mediaSource.mExtractor, nullptr);
+ sample2 = readSampleAndAdvance(extractor, nullptr);
+ }
+ EXPECT_EQ(sample1, nullptr);
+ EXPECT_EQ(sample2, nullptr);
+
+ AMediaExtractor_delete(extractor);
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
new file mode 100644
index 0000000..502d5aa
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -0,0 +1,317 @@
+/*
+ * 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.
+ */
+
+// Unit Test for MediaTrackTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTrackTranscoderTests"
+
+#include <android-base/logging.h>
+#include <android/binder_process.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/PassthroughTrackTranscoder.h>
+#include <media/VideoTrackTranscoder.h>
+
+#include "TrackTranscoderTestUtils.h"
+
+namespace android {
+
+/** TrackTranscoder types to test. */
+enum TrackTranscoderType {
+ VIDEO,
+ PASSTHROUGH,
+};
+
+class MediaTrackTranscoderTests : public ::testing::TestWithParam<TrackTranscoderType> {
+public:
+ MediaTrackTranscoderTests() { LOG(DEBUG) << "MediaTrackTranscoderTests created"; }
+
+ void SetUp() override {
+ LOG(DEBUG) << "MediaTrackTranscoderTests set up";
+
+ // Need to start a thread pool to prevent AMediaExtractor binder calls from starving
+ // (b/155663561).
+ ABinderProcess_startThreadPool();
+
+ mCallback = std::make_shared<TestCallback>();
+
+ switch (GetParam()) {
+ case VIDEO:
+ mTranscoder = VideoTrackTranscoder::create(mCallback);
+ break;
+ case PASSTHROUGH:
+ mTranscoder = std::make_shared<PassthroughTrackTranscoder>(mCallback);
+ break;
+ }
+ ASSERT_NE(mTranscoder, nullptr);
+ mTranscoderOutputQueue = mTranscoder->getOutputQueue();
+
+ initSampleReader();
+ }
+
+ void initSampleReader() {
+ const char* sourcePath =
+ "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+ const int sourceFd = open(sourcePath, O_RDONLY);
+ ASSERT_GT(sourceFd, 0);
+
+ const size_t fileSize = lseek(sourceFd, 0, SEEK_END);
+ lseek(sourceFd, 0, SEEK_SET);
+
+ mMediaSampleReader = MediaSampleReaderNDK::createFromFd(sourceFd, 0 /* offset */, fileSize);
+ ASSERT_NE(mMediaSampleReader, nullptr);
+ close(sourceFd);
+
+ for (size_t trackIndex = 0; trackIndex < mMediaSampleReader->getTrackCount();
+ ++trackIndex) {
+ AMediaFormat* trackFormat = mMediaSampleReader->getTrackFormat(trackIndex);
+ ASSERT_NE(trackFormat, nullptr);
+
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+ ASSERT_NE(mime, nullptr);
+
+ if (GetParam() == VIDEO && strncmp(mime, "video/", 6) == 0) {
+ mTrackIndex = trackIndex;
+
+ mSourceFormat = std::shared_ptr<AMediaFormat>(
+ trackFormat, std::bind(AMediaFormat_delete, std::placeholders::_1));
+ ASSERT_NE(mSourceFormat, nullptr);
+
+ mDestinationFormat =
+ TrackTranscoderTestUtils::getDefaultVideoDestinationFormat(trackFormat);
+ ASSERT_NE(mDestinationFormat, nullptr);
+ break;
+ } else if (GetParam() == PASSTHROUGH && strncmp(mime, "audio/", 6) == 0) {
+ // TODO(lnilsson): Test metadata track passthrough after hkuang@ provides sample.
+ mTrackIndex = trackIndex;
+
+ mSourceFormat = std::shared_ptr<AMediaFormat>(
+ trackFormat, std::bind(AMediaFormat_delete, std::placeholders::_1));
+ ASSERT_NE(mSourceFormat, nullptr);
+ break;
+ }
+
+ AMediaFormat_delete(trackFormat);
+ }
+
+ ASSERT_NE(mSourceFormat, nullptr);
+ }
+
+ // Drains the transcoder's output queue in a loop.
+ void drainOutputSampleQueue() {
+ mSampleQueueDrainThread = std::thread{[this] {
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = false;
+ do {
+ aborted = mTranscoderOutputQueue->dequeue(&sample);
+ } while (!aborted && !(sample->info.flags & SAMPLE_FLAG_END_OF_STREAM));
+ mQueueWasAborted = aborted;
+ mGotEndOfStream =
+ sample != nullptr && (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) != 0;
+ }};
+ }
+
+ void joinDrainThread() {
+ if (mSampleQueueDrainThread.joinable()) {
+ mSampleQueueDrainThread.join();
+ }
+ }
+ void TearDown() override {
+ LOG(DEBUG) << "MediaTrackTranscoderTests tear down";
+ joinDrainThread();
+ }
+
+ ~MediaTrackTranscoderTests() { LOG(DEBUG) << "MediaTrackTranscoderTests destroyed"; }
+
+protected:
+ std::shared_ptr<MediaTrackTranscoder> mTranscoder;
+ std::shared_ptr<MediaSampleQueue> mTranscoderOutputQueue;
+ std::shared_ptr<TestCallback> mCallback;
+
+ std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+ int mTrackIndex;
+
+ std::shared_ptr<AMediaFormat> mSourceFormat;
+ std::shared_ptr<AMediaFormat> mDestinationFormat;
+
+ std::thread mSampleQueueDrainThread;
+ bool mQueueWasAborted = false;
+ bool mGotEndOfStream = false;
+};
+
+TEST_P(MediaTrackTranscoderTests, WaitNormalOperation) {
+ LOG(DEBUG) << "Testing WaitNormalOperation";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(mTranscoder->start());
+ drainOutputSampleQueue();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+ joinDrainThread();
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_FALSE(mQueueWasAborted);
+ EXPECT_TRUE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, StopNormalOperation) {
+ LOG(DEBUG) << "Testing StopNormalOperation";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_TRUE(mTranscoder->start());
+ EXPECT_TRUE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, StartWithoutConfigure) {
+ LOG(DEBUG) << "Testing StartWithoutConfigure";
+ EXPECT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, StopWithoutStart) {
+ LOG(DEBUG) << "Testing StopWithoutStart";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_FALSE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, DoubleStartStop) {
+ LOG(DEBUG) << "Testing DoubleStartStop";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_TRUE(mTranscoder->start());
+ EXPECT_FALSE(mTranscoder->start());
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_FALSE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, DoubleConfigure) {
+ LOG(DEBUG) << "Testing DoubleConfigure";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_ERROR_UNSUPPORTED);
+}
+
+TEST_P(MediaTrackTranscoderTests, ConfigureAfterFail) {
+ LOG(DEBUG) << "Testing ConfigureAfterFail";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, -1, mDestinationFormat),
+ AMEDIA_ERROR_INVALID_PARAMETER);
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+}
+
+TEST_P(MediaTrackTranscoderTests, RestartAfterStop) {
+ LOG(DEBUG) << "Testing RestartAfterStop";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_TRUE(mTranscoder->start());
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, RestartAfterFinish) {
+ LOG(DEBUG) << "Testing RestartAfterFinish";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(mTranscoder->start());
+ drainOutputSampleQueue();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+ joinDrainThread();
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_FALSE(mTranscoder->start());
+ EXPECT_FALSE(mQueueWasAborted);
+ EXPECT_TRUE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, AbortOutputQueue) {
+ LOG(DEBUG) << "Testing AbortOutputQueue";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(mTranscoder->start());
+ mTranscoderOutputQueue->abort();
+ drainOutputSampleQueue();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_ERROR_IO);
+ joinDrainThread();
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_TRUE(mQueueWasAborted);
+ EXPECT_FALSE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, HoldSampleAfterTranscoderRelease) {
+ LOG(DEBUG) << "Testing HoldSampleAfterTranscoderRelease";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(mTranscoder->start());
+
+ std::shared_ptr<MediaSample> sample;
+ EXPECT_FALSE(mTranscoderOutputQueue->dequeue(&sample));
+
+ drainOutputSampleQueue();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+ joinDrainThread();
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_FALSE(mQueueWasAborted);
+ EXPECT_TRUE(mGotEndOfStream);
+
+ mTranscoder.reset();
+ mTranscoderOutputQueue.reset();
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
+ sample.reset();
+}
+
+TEST_P(MediaTrackTranscoderTests, HoldSampleAfterTranscoderStop) {
+ LOG(DEBUG) << "Testing HoldSampleAfterTranscoderStop";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(mTranscoder->start());
+
+ std::shared_ptr<MediaSample> sample;
+ EXPECT_FALSE(mTranscoderOutputQueue->dequeue(&sample));
+ EXPECT_TRUE(mTranscoder->stop());
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
+ sample.reset();
+}
+
+TEST_P(MediaTrackTranscoderTests, NullSampleReader) {
+ LOG(DEBUG) << "Testing NullSampleReader";
+ std::shared_ptr<MediaSampleReader> nullSampleReader;
+ EXPECT_NE(mTranscoder->configure(nullSampleReader, mTrackIndex, mDestinationFormat), AMEDIA_OK);
+ ASSERT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, InvalidTrackIndex) {
+ LOG(DEBUG) << "Testing InvalidTrackIndex";
+ EXPECT_NE(mTranscoder->configure(mMediaSampleReader, -1, mDestinationFormat), AMEDIA_OK);
+ EXPECT_NE(mTranscoder->configure(mMediaSampleReader, mMediaSampleReader->getTrackCount(),
+ mDestinationFormat),
+ AMEDIA_OK);
+}
+
+}; // namespace android
+
+using namespace android;
+
+INSTANTIATE_TEST_SUITE_P(MediaTrackTranscoderTestsAll, MediaTrackTranscoderTests,
+ ::testing::Values(VIDEO, PASSTHROUGH));
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
new file mode 100644
index 0000000..e68eaac
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+// Unit Test for MediaTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscoderTests"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/MediaTranscoder.h>
+#include <media/NdkCommon.h>
+
+namespace android {
+
+#define DEFINE_FORMAT_VALUE_EQUAL_FUNC(_type, _typeName) \
+ static bool equal##_typeName(const char* key, AMediaFormat* src, AMediaFormat* dst) { \
+ _type srcVal, dstVal; \
+ bool srcPresent = AMediaFormat_get##_typeName(src, key, &srcVal); \
+ bool dstPresent = AMediaFormat_get##_typeName(dst, key, &dstVal); \
+ return (srcPresent == dstPresent) && (!srcPresent || (srcVal == dstVal)); \
+ }
+
+DEFINE_FORMAT_VALUE_EQUAL_FUNC(int64_t, Int64);
+DEFINE_FORMAT_VALUE_EQUAL_FUNC(int32_t, Int32);
+
+struct FormatVerifierEntry {
+ const char* key;
+ std::function<bool(const char*, AMediaFormat*, AMediaFormat*)> equal;
+};
+
+static const FormatVerifierEntry kFieldsToPreserve[] = {
+ {AMEDIAFORMAT_KEY_DURATION, equalInt64}, {AMEDIAFORMAT_KEY_WIDTH, equalInt32},
+ {AMEDIAFORMAT_KEY_HEIGHT, equalInt32}, {AMEDIAFORMAT_KEY_FRAME_RATE, equalInt32},
+ {AMEDIAFORMAT_KEY_FRAME_COUNT, equalInt32}, {AMEDIAFORMAT_KEY_DISPLAY_WIDTH, equalInt32},
+ {AMEDIAFORMAT_KEY_DISPLAY_HEIGHT, equalInt32}, {AMEDIAFORMAT_KEY_SAR_WIDTH, equalInt32},
+ {AMEDIAFORMAT_KEY_SAR_HEIGHT, equalInt32}, {AMEDIAFORMAT_KEY_ROTATION, equalInt32},
+};
+
+class TestCallbacks : public MediaTranscoder::CallbackInterface {
+public:
+ virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
+ std::unique_lock<std::mutex> lock(mMutex);
+ EXPECT_FALSE(mFinished);
+ mFinished = true;
+ mCondition.notify_all();
+ }
+
+ virtual void onError(const MediaTranscoder* transcoder __unused,
+ media_status_t error) override {
+ std::unique_lock<std::mutex> lock(mMutex);
+ EXPECT_NE(error, AMEDIA_OK);
+ EXPECT_FALSE(mFinished);
+ mFinished = true;
+ mStatus = error;
+ mCondition.notify_all();
+ }
+
+ virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
+ int32_t progress __unused) override {}
+
+ virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
+ const std::shared_ptr<const Parcel>& pausedState
+ __unused) override {}
+
+ void waitForTranscodingFinished() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mFinished) {
+ mCondition.wait(lock);
+ }
+ }
+
+ media_status_t mStatus = AMEDIA_OK;
+
+private:
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ bool mFinished = false;
+};
+
+// Write-only, create file if non-existent, don't overwrite existing file.
+static constexpr int kOpenFlags = O_WRONLY | O_CREAT | O_EXCL;
+// User R+W permission.
+static constexpr int kFileMode = S_IRUSR | S_IWUSR;
+
+class MediaTranscoderTests : public ::testing::Test {
+public:
+ MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests created"; }
+ ~MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests destroyed"; }
+
+ void SetUp() override {
+ LOG(DEBUG) << "MediaTranscoderTests set up";
+ mCallbacks = std::make_shared<TestCallbacks>();
+ }
+
+ void TearDown() override {
+ LOG(DEBUG) << "MediaTranscoderTests tear down";
+ mCallbacks.reset();
+ }
+
+ void deleteFile(const char* path) { unlink(path); }
+
+ using FormatConfigurationCallback = std::function<AMediaFormat*(AMediaFormat*)>;
+ media_status_t transcodeHelper(const char* srcPath, const char* destPath,
+ FormatConfigurationCallback formatCallback) {
+ auto transcoder = MediaTranscoder::create(mCallbacks, nullptr);
+ EXPECT_NE(transcoder, nullptr);
+
+ const int srcFd = open(srcPath, O_RDONLY);
+ EXPECT_EQ(transcoder->configureSource(srcFd), AMEDIA_OK);
+
+ std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats();
+ EXPECT_GT(trackFormats.size(), 0);
+
+ for (int i = 0; i < trackFormats.size(); ++i) {
+ AMediaFormat* format = formatCallback(trackFormats[i].get());
+ EXPECT_EQ(transcoder->configureTrackFormat(i, format), AMEDIA_OK);
+
+ // Save original video track format for verification.
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
+ if (strncmp(mime, "video/", 6) == 0) {
+ mSourceVideoFormat = trackFormats[i];
+ }
+
+ if (format != nullptr) {
+ AMediaFormat_delete(format);
+ }
+ }
+ deleteFile(destPath);
+ const int dstFd = open(destPath, kOpenFlags, kFileMode);
+ EXPECT_EQ(transcoder->configureDestination(dstFd), AMEDIA_OK);
+
+ media_status_t startStatus = transcoder->start();
+ EXPECT_EQ(startStatus, AMEDIA_OK);
+ if (startStatus == AMEDIA_OK) {
+ mCallbacks->waitForTranscodingFinished();
+ }
+ close(srcFd);
+ close(dstFd);
+
+ return mCallbacks->mStatus;
+ }
+
+ void testTranscodeVideo(const char* srcPath, const char* destPath, const char* dstMime) {
+ const int32_t kBitRate = 8 * 1000 * 1000; // 8Mbs
+
+ EXPECT_EQ(
+ transcodeHelper(
+ srcPath, destPath,
+ [dstMime](AMediaFormat* sourceFormat) {
+ AMediaFormat* format = nullptr;
+ const char* mime = nullptr;
+ AMediaFormat_getString(sourceFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+
+ if (strncmp(mime, "video/", 6) == 0) {
+ format = AMediaFormat_new();
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
+
+ if (dstMime != nullptr) {
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, dstMime);
+ }
+ }
+ return format;
+ }),
+ AMEDIA_OK);
+
+ if (dstMime != nullptr) {
+ std::vector<FormatVerifierEntry> extraVerifiers = {
+ {AMEDIAFORMAT_KEY_MIME,
+ [dstMime](const char* key, AMediaFormat* src __unused, AMediaFormat* dst) {
+ const char* mime = nullptr;
+ AMediaFormat_getString(dst, key, &mime);
+ return !strcmp(mime, dstMime);
+ }},
+ };
+ verifyOutputFormat(destPath, &extraVerifiers);
+ } else {
+ verifyOutputFormat(destPath);
+ }
+ }
+
+ void verifyOutputFormat(const char* destPath,
+ const std::vector<FormatVerifierEntry>* extraVerifiers = nullptr) {
+ int dstFd = open(destPath, O_RDONLY);
+ EXPECT_GT(dstFd, 0);
+ ssize_t fileSize = lseek(dstFd, 0, SEEK_END);
+ lseek(dstFd, 0, SEEK_SET);
+
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(dstFd, 0, fileSize);
+ ASSERT_NE(sampleReader, nullptr);
+
+ std::shared_ptr<AMediaFormat> videoFormat;
+ const size_t trackCount = sampleReader->getTrackCount();
+ for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
+ AMediaFormat* trackFormat = sampleReader->getTrackFormat(static_cast<int>(trackIndex));
+ if (trackFormat != nullptr) {
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+
+ if (strncmp(mime, "video/", 6) == 0) {
+ LOG(INFO) << "Track # " << trackIndex << ": "
+ << AMediaFormat_toString(trackFormat);
+ videoFormat = std::shared_ptr<AMediaFormat>(trackFormat, &AMediaFormat_delete);
+ break;
+ }
+ }
+ }
+
+ EXPECT_NE(videoFormat, nullptr);
+
+ LOG(INFO) << "source video format: " << AMediaFormat_toString(mSourceVideoFormat.get());
+ LOG(INFO) << "transcoded video format: " << AMediaFormat_toString(videoFormat.get());
+
+ for (int i = 0; i < (sizeof(kFieldsToPreserve) / sizeof(kFieldsToPreserve[0])); ++i) {
+ EXPECT_TRUE(kFieldsToPreserve[i].equal(kFieldsToPreserve[i].key,
+ mSourceVideoFormat.get(), videoFormat.get()))
+ << "Failed at key " << kFieldsToPreserve[i].key;
+ }
+
+ if (extraVerifiers != nullptr) {
+ for (int i = 0; i < extraVerifiers->size(); ++i) {
+ const FormatVerifierEntry& entry = (*extraVerifiers)[i];
+ EXPECT_TRUE(entry.equal(entry.key, mSourceVideoFormat.get(), videoFormat.get()));
+ }
+ }
+
+ close(dstFd);
+ }
+
+ std::shared_ptr<TestCallbacks> mCallbacks;
+ std::shared_ptr<AMediaFormat> mSourceVideoFormat;
+};
+
+TEST_F(MediaTranscoderTests, TestPassthrough) {
+ const char* srcPath = "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_Passthrough.MP4";
+
+ EXPECT_EQ(transcodeHelper(srcPath, destPath, [](AMediaFormat*) { return nullptr; }), AMEDIA_OK);
+
+ verifyOutputFormat(destPath);
+}
+
+TEST_F(MediaTranscoderTests, TestVideoTranscode_AvcToAvc_Basic) {
+ const char* srcPath = "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_AvcToAvc_Basic.MP4";
+ testTranscodeVideo(srcPath, destPath, nullptr /*dstMime*/);
+}
+
+TEST_F(MediaTranscoderTests, TestVideoTranscode_HevcToAvc_Basic) {
+ const char* srcPath = "/data/local/tmp/TranscodingTestAssets/jets_hevc_1280x720_20Mbps.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_HevcToAvc_Basic.MP4";
+ testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
+}
+
+TEST_F(MediaTranscoderTests, TestVideoTranscode_HevcToAvc_Rotation) {
+ const char* srcPath =
+ "/data/local/tmp/TranscodingTestAssets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_HevcToAvc_Rotation.MP4";
+ testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
new file mode 100644
index 0000000..b79f58c
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
@@ -0,0 +1,306 @@
+/*
+ * 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.
+ */
+
+// Unit Test for PassthroughTrackTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "PassthroughTrackTranscoderTests"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/PassthroughTrackTranscoder.h>
+#include <openssl/md5.h>
+
+#include <vector>
+
+#include "TrackTranscoderTestUtils.h"
+
+namespace android {
+
+class PassthroughTrackTranscoderTests : public ::testing::Test {
+public:
+ PassthroughTrackTranscoderTests() { LOG(DEBUG) << "PassthroughTrackTranscoderTests created"; }
+
+ void SetUp() override { LOG(DEBUG) << "PassthroughTrackTranscoderTests set up"; }
+
+ void initSourceAndExtractor() {
+ const char* sourcePath =
+ "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+ mExtractor = AMediaExtractor_new();
+ ASSERT_NE(mExtractor, nullptr);
+
+ mSourceFd = open(sourcePath, O_RDONLY);
+ ASSERT_GT(mSourceFd, 0);
+
+ mSourceFileSize = lseek(mSourceFd, 0, SEEK_END);
+ lseek(mSourceFd, 0, SEEK_SET);
+
+ media_status_t status =
+ AMediaExtractor_setDataSourceFd(mExtractor, mSourceFd, 0, mSourceFileSize);
+ ASSERT_EQ(status, AMEDIA_OK);
+
+ const size_t trackCount = AMediaExtractor_getTrackCount(mExtractor);
+ for (size_t trackIndex = 0; trackIndex < trackCount; trackIndex++) {
+ AMediaFormat* trackFormat = AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
+ ASSERT_NE(trackFormat, nullptr);
+
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+ ASSERT_NE(mime, nullptr);
+
+ if (strncmp(mime, "audio/", 6) == 0) {
+ mTrackIndex = trackIndex;
+ AMediaExtractor_selectTrack(mExtractor, trackIndex);
+ break;
+ }
+
+ AMediaFormat_delete(trackFormat);
+ }
+ }
+
+ void TearDown() override {
+ LOG(DEBUG) << "PassthroughTrackTranscoderTests tear down";
+ if (mExtractor != nullptr) {
+ AMediaExtractor_delete(mExtractor);
+ mExtractor = nullptr;
+ }
+ if (mSourceFd > 0) {
+ close(mSourceFd);
+ mSourceFd = -1;
+ }
+ }
+
+ ~PassthroughTrackTranscoderTests() {
+ LOG(DEBUG) << "PassthroughTrackTranscoderTests destroyed";
+ }
+
+ int mSourceFd = -1;
+ size_t mSourceFileSize;
+ int mTrackIndex;
+ AMediaExtractor* mExtractor = nullptr;
+};
+
+/** Helper class for comparing sample data using checksums. */
+class SampleID {
+public:
+ SampleID(const uint8_t* sampleData, ssize_t sampleSize) : mSize{sampleSize} {
+ MD5_CTX md5Ctx;
+ MD5_Init(&md5Ctx);
+ MD5_Update(&md5Ctx, sampleData, sampleSize);
+ MD5_Final(mChecksum, &md5Ctx);
+ }
+
+ bool operator==(const SampleID& rhs) const {
+ return mSize == rhs.mSize && memcmp(mChecksum, rhs.mChecksum, MD5_DIGEST_LENGTH) == 0;
+ }
+
+ uint8_t mChecksum[MD5_DIGEST_LENGTH];
+ ssize_t mSize;
+};
+
+/**
+ * Tests that the output samples of PassthroughTrackTranscoder are identical to the source samples
+ * and in correct order.
+ */
+TEST_F(PassthroughTrackTranscoderTests, SampleEquality) {
+ LOG(DEBUG) << "Testing SampleEquality";
+
+ ssize_t bufferSize = 1024;
+ auto buffer = std::make_unique<uint8_t[]>(bufferSize);
+
+ initSourceAndExtractor();
+
+ // Loop through all samples of a track and store size and checksums.
+ std::vector<SampleID> sampleChecksums;
+
+ int64_t sampleTime = AMediaExtractor_getSampleTime(mExtractor);
+ while (sampleTime != -1) {
+ if (AMediaExtractor_getSampleTrackIndex(mExtractor) == mTrackIndex) {
+ ssize_t sampleSize = AMediaExtractor_getSampleSize(mExtractor);
+ if (bufferSize < sampleSize) {
+ bufferSize = sampleSize;
+ buffer = std::make_unique<uint8_t[]>(bufferSize);
+ }
+
+ ssize_t bytesRead =
+ AMediaExtractor_readSampleData(mExtractor, buffer.get(), bufferSize);
+ ASSERT_EQ(bytesRead, sampleSize);
+
+ SampleID sampleId{buffer.get(), sampleSize};
+ sampleChecksums.push_back(sampleId);
+ }
+
+ AMediaExtractor_advance(mExtractor);
+ sampleTime = AMediaExtractor_getSampleTime(mExtractor);
+ }
+
+ // Create and start the transcoder.
+ std::shared_ptr<TestCallback> callback = std::make_shared<TestCallback>();
+ PassthroughTrackTranscoder transcoder{callback};
+
+ std::shared_ptr<MediaSampleReader> mediaSampleReader =
+ MediaSampleReaderNDK::createFromFd(mSourceFd, 0, mSourceFileSize);
+ EXPECT_NE(mediaSampleReader, nullptr);
+
+ EXPECT_EQ(transcoder.configure(mediaSampleReader, mTrackIndex, nullptr /* destinationFormat */),
+ AMEDIA_OK);
+ ASSERT_TRUE(transcoder.start());
+
+ // Pull transcoder's output samples and compare against input checksums.
+ uint64_t sampleCount = 0;
+ std::shared_ptr<MediaSample> sample;
+ std::shared_ptr<MediaSampleQueue> outputQueue = transcoder.getOutputQueue();
+ while (!outputQueue->dequeue(&sample)) {
+ ASSERT_NE(sample, nullptr);
+
+ if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
+ break;
+ }
+
+ SampleID sampleId{sample->buffer, static_cast<ssize_t>(sample->info.size)};
+ EXPECT_TRUE(sampleId == sampleChecksums[sampleCount]);
+ ++sampleCount;
+ }
+
+ EXPECT_EQ(sampleCount, sampleChecksums.size());
+ EXPECT_TRUE(transcoder.stop());
+}
+
+/** Class for testing PassthroughTrackTranscoder's built in buffer pool. */
+class BufferPoolTests : public ::testing::Test {
+public:
+ static constexpr int kMaxBuffers = 5;
+
+ void SetUp() override {
+ LOG(DEBUG) << "BufferPoolTests set up";
+ mBufferPool = std::make_shared<PassthroughTrackTranscoder::BufferPool>(kMaxBuffers);
+ }
+
+ void TearDown() override {
+ LOG(DEBUG) << "BufferPoolTests tear down";
+ mBufferPool.reset();
+ }
+
+ std::shared_ptr<PassthroughTrackTranscoder::BufferPool> mBufferPool;
+};
+
+TEST_F(BufferPoolTests, BufferReuse) {
+ LOG(DEBUG) << "Testing BufferReuse";
+
+ uint8_t* buffer1 = mBufferPool->getBufferWithSize(10);
+ EXPECT_NE(buffer1, nullptr);
+
+ uint8_t* buffer2 = mBufferPool->getBufferWithSize(10);
+ EXPECT_NE(buffer2, nullptr);
+ EXPECT_NE(buffer2, buffer1);
+
+ mBufferPool->returnBuffer(buffer1);
+
+ uint8_t* buffer3 = mBufferPool->getBufferWithSize(10);
+ EXPECT_NE(buffer3, nullptr);
+ EXPECT_NE(buffer3, buffer2);
+ EXPECT_EQ(buffer3, buffer1);
+
+ mBufferPool->returnBuffer(buffer2);
+
+ uint8_t* buffer4 = mBufferPool->getBufferWithSize(10);
+ EXPECT_NE(buffer4, nullptr);
+ EXPECT_NE(buffer4, buffer1);
+ EXPECT_EQ(buffer4, buffer2);
+}
+
+TEST_F(BufferPoolTests, SmallestAvailableBuffer) {
+ LOG(DEBUG) << "Testing SmallestAvailableBuffer";
+
+ uint8_t* buffer1 = mBufferPool->getBufferWithSize(10);
+ EXPECT_NE(buffer1, nullptr);
+
+ uint8_t* buffer2 = mBufferPool->getBufferWithSize(15);
+ EXPECT_NE(buffer2, nullptr);
+ EXPECT_NE(buffer2, buffer1);
+
+ uint8_t* buffer3 = mBufferPool->getBufferWithSize(20);
+ EXPECT_NE(buffer3, nullptr);
+ EXPECT_NE(buffer3, buffer1);
+ EXPECT_NE(buffer3, buffer2);
+
+ mBufferPool->returnBuffer(buffer1);
+ mBufferPool->returnBuffer(buffer2);
+ mBufferPool->returnBuffer(buffer3);
+
+ uint8_t* buffer4 = mBufferPool->getBufferWithSize(11);
+ EXPECT_NE(buffer4, nullptr);
+ EXPECT_EQ(buffer4, buffer2);
+
+ uint8_t* buffer5 = mBufferPool->getBufferWithSize(11);
+ EXPECT_NE(buffer5, nullptr);
+ EXPECT_EQ(buffer5, buffer3);
+}
+
+TEST_F(BufferPoolTests, AddAfterAbort) {
+ LOG(DEBUG) << "Testing AddAfterAbort";
+
+ uint8_t* buffer1 = mBufferPool->getBufferWithSize(10);
+ EXPECT_NE(buffer1, nullptr);
+ mBufferPool->returnBuffer(buffer1);
+
+ mBufferPool->abort();
+ uint8_t* buffer2 = mBufferPool->getBufferWithSize(10);
+ EXPECT_EQ(buffer2, nullptr);
+}
+
+TEST_F(BufferPoolTests, MaximumBuffers) {
+ LOG(DEBUG) << "Testing MaximumBuffers";
+
+ static constexpr size_t kBufferBaseSize = 10;
+ std::unordered_map<uint8_t*, size_t> addressSizeMap;
+
+ // Get kMaxBuffers * 2 new buffers with increasing size.
+ // (Note: Once kMaxBuffers have been allocated, the pool will delete old buffers to accommodate
+ // new ones making the deleted buffers free to be reused by the system's heap memory allocator.
+ // So we cannot test that each new pointer is unique here.)
+ for (int i = 0; i < kMaxBuffers * 2; i++) {
+ size_t size = kBufferBaseSize + i;
+ uint8_t* buffer = mBufferPool->getBufferWithSize(size);
+ EXPECT_NE(buffer, nullptr);
+ addressSizeMap[buffer] = size;
+ mBufferPool->returnBuffer(buffer);
+ }
+
+ // Verify that the pool now contains the kMaxBuffers largest buffers allocated above and that
+ // the buffer of matching size is returned.
+ for (int i = kMaxBuffers; i < kMaxBuffers * 2; i++) {
+ size_t size = kBufferBaseSize + i;
+ uint8_t* buffer = mBufferPool->getBufferWithSize(size);
+ EXPECT_NE(buffer, nullptr);
+
+ auto it = addressSizeMap.find(buffer);
+ ASSERT_NE(it, addressSizeMap.end());
+ EXPECT_EQ(it->second, size);
+ mBufferPool->returnBuffer(buffer);
+ }
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/README.md b/media/libmediatranscoding/transcoder/tests/README.md
new file mode 100644
index 0000000..59417b0
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/README.md
@@ -0,0 +1,14 @@
+## Transcoder Testing ##
+---
+#### Transcoder unit tests :
+To run all transcoder unit tests, run the supplied script from this folder:
+
+```
+./build_and_run_all_unit_tests.sh
+```
+
+To run individual unit test modules, use atest:
+
+```
+atest MediaSampleReaderNDK
+```
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
new file mode 100644
index 0000000..54b42fe
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -0,0 +1,91 @@
+/*
+ * 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 <media/MediaTrackTranscoder.h>
+#include <media/MediaTrackTranscoderCallback.h>
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+
+namespace android {
+
+//
+// This file contains test utilities used by more than one track transcoder test.
+//
+
+class TrackTranscoderTestUtils {
+public:
+ static std::shared_ptr<AMediaFormat> getDefaultVideoDestinationFormat(
+ AMediaFormat* sourceFormat) {
+ // Default video destination format setup.
+ static constexpr float kFrameRate = 30.0f;
+ static constexpr float kIFrameInterval = 30.0f;
+ static constexpr int32_t kBitRate = 2 * 1000 * 1000;
+ static constexpr int32_t kColorFormatSurface = 0x7f000789;
+
+ AMediaFormat* destinationFormat = AMediaFormat_new();
+ AMediaFormat_copy(destinationFormat, sourceFormat);
+ AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_FRAME_RATE, kFrameRate);
+ AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+ kIFrameInterval);
+ AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
+ AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
+ kColorFormatSurface);
+
+ return std::shared_ptr<AMediaFormat>(destinationFormat,
+ std::bind(AMediaFormat_delete, std::placeholders::_1));
+ }
+};
+
+class TestCallback : public MediaTrackTranscoderCallback {
+public:
+ TestCallback() = default;
+ ~TestCallback() = default;
+
+ // MediaTrackTranscoderCallback
+ void onTrackFormatAvailable(const MediaTrackTranscoder* transcoder __unused) {}
+
+ void onTrackFinished(const MediaTrackTranscoder* transcoder __unused) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mTranscodingFinished = true;
+ mCv.notify_all();
+ }
+
+ void onTrackError(const MediaTrackTranscoder* transcoder __unused, media_status_t status) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mTranscodingFinished = true;
+ mStatus = status;
+ mCv.notify_all();
+ }
+ // ~MediaTrackTranscoderCallback
+
+ media_status_t waitUntilFinished() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mTranscodingFinished) {
+ mCv.wait(lock);
+ }
+ return mStatus;
+ }
+
+private:
+ media_status_t mStatus = AMEDIA_OK;
+ std::mutex mMutex;
+ std::condition_variable mCv;
+ bool mTranscodingFinished = false;
+};
+
+}; // namespace android
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
new file mode 100644
index 0000000..5f2cd12
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+// Unit Test for VideoTrackTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VideoTrackTranscoderTests"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/VideoTrackTranscoder.h>
+#include <utils/Timers.h>
+
+#include "TrackTranscoderTestUtils.h"
+
+namespace android {
+
+// TODO(b/155304421): Implement more advanced video specific tests:
+// - Codec conversions (HEVC -> AVC).
+// - Bitrate validation.
+// - Output frame validation through PSNR.
+
+class VideoTrackTranscoderTests : public ::testing::Test {
+public:
+ VideoTrackTranscoderTests() { LOG(DEBUG) << "VideoTrackTranscoderTests created"; }
+
+ void SetUp() override {
+ LOG(DEBUG) << "VideoTrackTranscoderTests set up";
+ const char* sourcePath =
+ "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+ const int sourceFd = open(sourcePath, O_RDONLY);
+ ASSERT_GT(sourceFd, 0);
+
+ const off_t fileSize = lseek(sourceFd, 0, SEEK_END);
+ lseek(sourceFd, 0, SEEK_SET);
+
+ mMediaSampleReader = MediaSampleReaderNDK::createFromFd(sourceFd, 0, fileSize);
+ ASSERT_NE(mMediaSampleReader, nullptr);
+ close(sourceFd);
+
+ for (size_t trackIndex = 0; trackIndex < mMediaSampleReader->getTrackCount();
+ ++trackIndex) {
+ AMediaFormat* trackFormat = mMediaSampleReader->getTrackFormat(trackIndex);
+ ASSERT_NE(trackFormat, nullptr);
+
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+ ASSERT_NE(mime, nullptr);
+
+ if (strncmp(mime, "video/", 6) == 0) {
+ mTrackIndex = trackIndex;
+
+ mSourceFormat = std::shared_ptr<AMediaFormat>(
+ trackFormat, std::bind(AMediaFormat_delete, std::placeholders::_1));
+ ASSERT_NE(mSourceFormat, nullptr);
+
+ mDestinationFormat =
+ TrackTranscoderTestUtils::getDefaultVideoDestinationFormat(trackFormat);
+ ASSERT_NE(mDestinationFormat, nullptr);
+ break;
+ }
+
+ AMediaFormat_delete(trackFormat);
+ }
+
+ ASSERT_NE(mSourceFormat, nullptr);
+ }
+
+ void TearDown() override { LOG(DEBUG) << "VideoTrackTranscoderTests tear down"; }
+
+ ~VideoTrackTranscoderTests() { LOG(DEBUG) << "VideoTrackTranscoderTests destroyed"; }
+
+ std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+ int mTrackIndex;
+ std::shared_ptr<AMediaFormat> mSourceFormat;
+ std::shared_ptr<AMediaFormat> mDestinationFormat;
+};
+
+TEST_F(VideoTrackTranscoderTests, SampleSanity) {
+ LOG(DEBUG) << "Testing SampleSanity";
+ std::shared_ptr<TestCallback> callback = std::make_shared<TestCallback>();
+ auto transcoder = VideoTrackTranscoder::create(callback);
+
+ EXPECT_EQ(transcoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(transcoder->start());
+
+ std::shared_ptr<MediaSampleQueue> outputQueue = transcoder->getOutputQueue();
+ std::thread sampleConsumerThread{[&outputQueue] {
+ uint64_t sampleCount = 0;
+ std::shared_ptr<MediaSample> sample;
+ while (!outputQueue->dequeue(&sample)) {
+ ASSERT_NE(sample, nullptr);
+ const uint32_t flags = sample->info.flags;
+
+ if (sampleCount == 0) {
+ // Expect first sample to be a codec config.
+ EXPECT_TRUE((flags & SAMPLE_FLAG_CODEC_CONFIG) != 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_SYNC_SAMPLE) == 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_PARTIAL_FRAME) == 0);
+ } else if (sampleCount == 1) {
+ // Expect second sample to be a sync sample.
+ EXPECT_TRUE((flags & SAMPLE_FLAG_CODEC_CONFIG) == 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_SYNC_SAMPLE) != 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+ }
+
+ if (!(flags & SAMPLE_FLAG_END_OF_STREAM)) {
+ // Expect a valid buffer unless it is EOS.
+ EXPECT_NE(sample->buffer, nullptr);
+ EXPECT_NE(sample->bufferId, 0xBAADF00D);
+ EXPECT_GT(sample->info.size, 0);
+ }
+
+ ++sampleCount;
+ if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
+ break;
+ }
+ sample.reset();
+ }
+ }};
+
+ EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
+ EXPECT_TRUE(transcoder->stop());
+
+ sampleConsumerThread.join();
+}
+
+// VideoTrackTranscoder needs a valid destination format.
+TEST_F(VideoTrackTranscoderTests, NullDestinationFormat) {
+ LOG(DEBUG) << "Testing NullDestinationFormat";
+ std::shared_ptr<TestCallback> callback = std::make_shared<TestCallback>();
+ std::shared_ptr<AMediaFormat> nullFormat;
+
+ auto transcoder = VideoTrackTranscoder::create(callback);
+ EXPECT_EQ(transcoder->configure(mMediaSampleReader, 0 /* trackIndex */, nullFormat),
+ AMEDIA_ERROR_INVALID_PARAMETER);
+}
+
+TEST_F(VideoTrackTranscoderTests, LingeringEncoder) {
+ struct {
+ void wait() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mSignaled) {
+ mCondition.wait(lock);
+ }
+ }
+
+ void signal() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mSignaled = true;
+ mCondition.notify_all();
+ }
+
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ bool mSignaled = false;
+ } semaphore;
+
+ auto callback = std::make_shared<TestCallback>();
+ auto transcoder = VideoTrackTranscoder::create(callback);
+
+ EXPECT_EQ(transcoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(transcoder->start());
+
+ std::shared_ptr<MediaSampleQueue> outputQueue = transcoder->getOutputQueue();
+ std::vector<std::shared_ptr<MediaSample>> samples;
+ std::thread sampleConsumerThread([&outputQueue, &samples, &semaphore] {
+ std::shared_ptr<MediaSample> sample;
+ while (samples.size() < 10 && !outputQueue->dequeue(&sample)) {
+ ASSERT_NE(sample, nullptr);
+ samples.push_back(sample);
+
+ if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
+ break;
+ }
+ sample.reset();
+ }
+
+ semaphore.signal();
+ });
+
+ // Wait for the encoder to output samples before stopping and releasing the transcoder.
+ semaphore.wait();
+
+ EXPECT_TRUE(transcoder->stop());
+ transcoder.reset();
+ sampleConsumerThread.join();
+
+ // Return buffers to the codec so that it can resume processing, but keep one buffer to avoid
+ // the codec being released.
+ samples.resize(1);
+
+ // Wait for async codec events.
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
new file mode 100755
index 0000000..241178d
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# Run tests in this directory.
+#
+
+if [ "$SYNC_FINISHED" != true ]; then
+ if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+ fi
+
+ # ensure we have mm
+ . $ANDROID_BUILD_TOP/build/envsetup.sh
+
+ mm
+
+ echo "waiting for device"
+
+ adb root && adb wait-for-device remount && adb sync
+fi
+
+# Push the files onto the device.
+. $ANDROID_BUILD_TOP/frameworks/av/media/libmediatranscoding/tests/assets/push_assets.sh
+
+echo "========================================"
+
+echo "testing MediaSampleReaderNDK"
+adb shell /data/nativetest64/MediaSampleReaderNDKTests/MediaSampleReaderNDKTests
+
+echo "testing MediaSampleQueue"
+adb shell /data/nativetest64/MediaSampleQueueTests/MediaSampleQueueTests
+
+echo "testing MediaTrackTranscoder"
+adb shell /data/nativetest64/MediaTrackTranscoderTests/MediaTrackTranscoderTests
+
+echo "testing VideoTrackTranscoder"
+adb shell /data/nativetest64/VideoTrackTranscoderTests/VideoTrackTranscoderTests
+
+echo "testing PassthroughTrackTranscoder"
+adb shell /data/nativetest64/PassthroughTrackTranscoderTests/PassthroughTrackTranscoderTests
+
+echo "testing MediaSampleWriter"
+adb shell /data/nativetest64/MediaSampleWriterTests/MediaSampleWriterTests
+
+echo "testing MediaTranscoder"
+adb shell /data/nativetest64/MediaTranscoderTests/MediaTranscoderTests
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 63ab654..6941198 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -555,6 +555,7 @@
mShutdownInProgress(false),
mExplicitShutdown(false),
mIsLegacyVP9Decoder(false),
+ mIsStreamCorruptFree(false),
mEncoderDelay(0),
mEncoderPadding(0),
mRotationDegrees(0),
@@ -2323,6 +2324,12 @@
mChannelMaskPresent = false;
}
+ int32_t isCorruptFree = 0;
+ if (msg->findInt32("corrupt-free", &isCorruptFree)) {
+ mIsStreamCorruptFree = isCorruptFree == 1 ? true : false;
+ ALOGV("corrupt-free=[%d]", mIsStreamCorruptFree);
+ }
+
int32_t maxInputSize;
if (msg->findInt32("max-input-size", &maxInputSize)) {
err = setMinBufferSize(kPortIndexInput, (size_t)maxInputSize);
@@ -4127,6 +4134,29 @@
ALOGI("setupVideoEncoder succeeded");
}
+ // Video should be encoded as stand straight because RTP protocol
+ // can provide rotation information only if CVO is supported.
+ // This needs to be added to support non-CVO case for video streaming scenario.
+ int32_t rotation = 0;
+ if (msg->findInt32("rotation-degrees", &rotation)) {
+ OMX_CONFIG_ROTATIONTYPE config;
+ InitOMXParams(&config);
+ config.nPortIndex = kPortIndexOutput;
+ status_t err = mOMXNode->getConfig(
+ (OMX_INDEXTYPE)OMX_IndexConfigCommonRotate, &config, sizeof(config));
+ if (err != OK) {
+ ALOGW("Failed to getConfig of OMX_IndexConfigCommonRotate(err %d)", err);
+ }
+ config.nRotation = rotation;
+ err = mOMXNode->setConfig(
+ (OMX_INDEXTYPE)OMX_IndexConfigCommonRotate, &config, sizeof(config));
+
+ ALOGD("Applying encoder-rotation=[%d] to video encoder.", config.nRotation);
+ if (err != OK) {
+ ALOGW("Failed to setConfig of OMX_IndexConfigCommonRotate(err %d)", err);
+ }
+ }
+
return err;
}
@@ -5974,6 +6004,12 @@
return false;
}
+ if (mCodec->mIsStreamCorruptFree && data1 == (OMX_U32)OMX_ErrorStreamCorrupt) {
+ ALOGV("[%s] handle OMX_ErrorStreamCorrupt as a normal operation",
+ mCodec->mComponentName.c_str());
+ return true;
+ }
+
ALOGE("[%s] ERROR(0x%08x)", mCodec->mComponentName.c_str(), data1);
// verify OMX component sends back an error we expect.
@@ -6081,6 +6117,13 @@
return;
}
+ int32_t cvo;
+ if (mCodec->mNativeWindow != NULL && buffer != NULL &&
+ buffer->meta()->findInt32("cvo", &cvo)) {
+ ALOGV("cvo(%d) found in buffer #%u", cvo, bufferID);
+ setNativeWindowRotation(mCodec->mNativeWindow.get(), cvo);
+ }
+
info->mStatus = BufferInfo::OWNED_BY_US;
info->mData = buffer;
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..c180edf 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -2,10 +2,27 @@
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",
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
cc_library_static {
name: "libstagefright_esds",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: ["ESDS.cpp"],
@@ -23,10 +40,21 @@
"libstagefright_foundation",
"libutils"
],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
cc_library_static {
name: "libstagefright_metadatautils",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: ["MetaDataUtils.cpp"],
@@ -42,9 +70,18 @@
},
header_libs: [
+ "libaudioclient_headers",
"libstagefright_foundation_headers",
+ "media_ndk_headers",
],
- shared_libs: ["libmediandk"],
+
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
export_include_dirs: ["include"],
}
@@ -94,6 +131,11 @@
cc_library_static {
name: "libstagefright_mpeg2extractor",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: [
"Utils.cpp",
@@ -111,8 +153,10 @@
header_libs: [
"libaudioclient_headers",
- "libmedia_headers",
+ "libbase_headers",
+ "libmedia_datasource_headers",
"media_ndk_headers",
+ "media_plugin_headers",
],
cflags: [
@@ -129,6 +173,18 @@
"signed-integer-overflow",
],
},
+
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ linux: {
+ cflags: [
+ "-DDISABLE_AUDIO_SYSTEM_OFFLOAD",
+ ],
+ }
+ },
}
cc_library_shared {
@@ -252,6 +308,7 @@
"libutils",
"libmedia_helper",
"libsfplugin_ccodec",
+ "libsfplugin_ccodec_utils",
"libstagefright_codecbase",
"libstagefright_foundation",
"libstagefright_omx_utils",
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 9b3f420..bcf418a 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -46,88 +46,6 @@
static const int64_t CAMERA_SOURCE_TIMEOUT_NS = 3000000000LL;
-struct CameraSourceListener : public CameraListener {
- explicit CameraSourceListener(const sp<CameraSource> &source);
-
- virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
- virtual void postData(int32_t msgType, const sp<IMemory> &dataPtr,
- camera_frame_metadata_t *metadata);
-
- virtual void postDataTimestamp(
- nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
-
- virtual void postRecordingFrameHandleTimestamp(nsecs_t timestamp, native_handle_t* handle);
-
- virtual void postRecordingFrameHandleTimestampBatch(
- const std::vector<nsecs_t>& timestamps,
- const std::vector<native_handle_t*>& handles);
-
-protected:
- virtual ~CameraSourceListener();
-
-private:
- wp<CameraSource> mSource;
-
- CameraSourceListener(const CameraSourceListener &);
- CameraSourceListener &operator=(const CameraSourceListener &);
-};
-
-CameraSourceListener::CameraSourceListener(const sp<CameraSource> &source)
- : mSource(source) {
-}
-
-CameraSourceListener::~CameraSourceListener() {
-}
-
-void CameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) {
- UNUSED_UNLESS_VERBOSE(msgType);
- UNUSED_UNLESS_VERBOSE(ext1);
- UNUSED_UNLESS_VERBOSE(ext2);
- ALOGV("notify(%d, %d, %d)", msgType, ext1, ext2);
-}
-
-void CameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr,
- camera_frame_metadata_t * /* metadata */) {
- ALOGV("postData(%d, ptr:%p, size:%zu)",
- msgType, dataPtr->unsecurePointer(), dataPtr->size());
-
- sp<CameraSource> source = mSource.promote();
- if (source.get() != NULL) {
- source->dataCallback(msgType, dataPtr);
- }
-}
-
-void CameraSourceListener::postDataTimestamp(
- nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) {
-
- sp<CameraSource> source = mSource.promote();
- if (source.get() != NULL) {
- source->dataCallbackTimestamp(timestamp/1000, msgType, dataPtr);
- }
-}
-
-void CameraSourceListener::postRecordingFrameHandleTimestamp(nsecs_t timestamp,
- native_handle_t* handle) {
- sp<CameraSource> source = mSource.promote();
- if (source.get() != nullptr) {
- source->recordingFrameHandleCallbackTimestamp(timestamp/1000, handle);
- }
-}
-
-void CameraSourceListener::postRecordingFrameHandleTimestampBatch(
- const std::vector<nsecs_t>& timestamps,
- const std::vector<native_handle_t*>& handles) {
- sp<CameraSource> source = mSource.promote();
- if (source.get() != nullptr) {
- int n = timestamps.size();
- std::vector<nsecs_t> modifiedTimestamps(n);
- for (int i = 0; i < n; i++) {
- modifiedTimestamps[i] = timestamps[i] / 1000;
- }
- source->recordingFrameHandleCallbackTimestampBatch(modifiedTimestamps, handles);
- }
-}
-
static int32_t getColorFormat(const char* colorFormat) {
if (!colorFormat) {
ALOGE("Invalid color format");
@@ -169,16 +87,6 @@
return -1;
}
-CameraSource *CameraSource::Create(const String16 &clientName) {
- Size size;
- size.width = -1;
- size.height = -1;
-
- sp<hardware::ICamera> camera;
- return new CameraSource(camera, NULL, 0, clientName, Camera::USE_CALLING_UID,
- Camera::USE_CALLING_PID, size, -1, NULL, false);
-}
-
// static
CameraSource *CameraSource::CreateFromCamera(
const sp<hardware::ICamera>& camera,
@@ -189,12 +97,10 @@
pid_t clientPid,
Size videoSize,
int32_t frameRate,
- const sp<IGraphicBufferProducer>& surface,
- bool storeMetaDataInVideoBuffers) {
+ const sp<IGraphicBufferProducer>& surface) {
CameraSource *source = new CameraSource(camera, proxy, cameraId,
- clientName, clientUid, clientPid, videoSize, frameRate, surface,
- storeMetaDataInVideoBuffers);
+ clientName, clientUid, clientPid, videoSize, frameRate, surface);
return source;
}
@@ -207,8 +113,7 @@
pid_t clientPid,
Size videoSize,
int32_t frameRate,
- const sp<IGraphicBufferProducer>& surface,
- bool storeMetaDataInVideoBuffers)
+ const sp<IGraphicBufferProducer>& surface)
: mCameraFlags(0),
mNumInputBuffers(0),
mVideoFrameRate(-1),
@@ -231,8 +136,7 @@
mInitCheck = init(camera, proxy, cameraId,
clientName, clientUid, clientPid,
- videoSize, frameRate,
- storeMetaDataInVideoBuffers);
+ videoSize, frameRate);
if (mInitCheck != OK) releaseCamera();
}
@@ -531,15 +435,13 @@
uid_t clientUid,
pid_t clientPid,
Size videoSize,
- int32_t frameRate,
- bool storeMetaDataInVideoBuffers) {
+ int32_t frameRate) {
ALOGV("init");
status_t err = OK;
int64_t token = IPCThreadState::self()->clearCallingIdentity();
err = initWithCameraAccess(camera, proxy, cameraId, clientName, clientUid, clientPid,
- videoSize, frameRate,
- storeMetaDataInVideoBuffers);
+ videoSize, frameRate);
IPCThreadState::self()->restoreCallingIdentity(token);
return err;
}
@@ -626,8 +528,7 @@
uid_t clientUid,
pid_t clientPid,
Size videoSize,
- int32_t frameRate,
- bool storeMetaDataInVideoBuffers) {
+ int32_t frameRate) {
ALOGV("initWithCameraAccess");
status_t err = OK;
@@ -667,24 +568,12 @@
CHECK_EQ((status_t)OK, mCamera->setPreviewTarget(mSurface));
}
- // By default, store real data in video buffers.
- mVideoBufferMode = hardware::ICamera::VIDEO_BUFFER_MODE_DATA_CALLBACK_YUV;
- if (storeMetaDataInVideoBuffers) {
- if (OK == mCamera->setVideoBufferMode(hardware::ICamera::VIDEO_BUFFER_MODE_BUFFER_QUEUE)) {
- mVideoBufferMode = hardware::ICamera::VIDEO_BUFFER_MODE_BUFFER_QUEUE;
- } else if (OK == mCamera->setVideoBufferMode(
- hardware::ICamera::VIDEO_BUFFER_MODE_DATA_CALLBACK_METADATA)) {
- mVideoBufferMode = hardware::ICamera::VIDEO_BUFFER_MODE_DATA_CALLBACK_METADATA;
- }
- }
-
- if (mVideoBufferMode == hardware::ICamera::VIDEO_BUFFER_MODE_DATA_CALLBACK_YUV) {
- err = mCamera->setVideoBufferMode(hardware::ICamera::VIDEO_BUFFER_MODE_DATA_CALLBACK_YUV);
- if (err != OK) {
- ALOGE("%s: Setting video buffer mode to VIDEO_BUFFER_MODE_DATA_CALLBACK_YUV failed: "
- "%s (err=%d)", __FUNCTION__, strerror(-err), err);
- return err;
- }
+ // Use buffer queue to receive video buffers from camera
+ err = mCamera->setVideoBufferMode(hardware::ICamera::VIDEO_BUFFER_MODE_BUFFER_QUEUE);
+ if (err != OK) {
+ ALOGE("%s: Setting video buffer mode to VIDEO_BUFFER_MODE_BUFFER_QUEUE failed: "
+ "%s (err=%d)", __FUNCTION__, strerror(-err), err);
+ return err;
}
int64_t glitchDurationUs = (1000000LL / mVideoFrameRate);
@@ -724,54 +613,26 @@
int64_t token = IPCThreadState::self()->clearCallingIdentity();
status_t err;
- if (mVideoBufferMode == hardware::ICamera::VIDEO_BUFFER_MODE_BUFFER_QUEUE) {
- // Initialize buffer queue.
- err = initBufferQueue(mVideoSize.width, mVideoSize.height, mEncoderFormat,
- (android_dataspace_t)mEncoderDataSpace,
- mNumInputBuffers > 0 ? mNumInputBuffers : 1);
- if (err != OK) {
- ALOGE("%s: Failed to initialize buffer queue: %s (err=%d)", __FUNCTION__,
- strerror(-err), err);
- return err;
- }
- } else {
- if (mNumInputBuffers > 0) {
- err = mCamera->sendCommand(
- CAMERA_CMD_SET_VIDEO_BUFFER_COUNT, mNumInputBuffers, 0);
-
- // This could happen for CameraHAL1 clients; thus the failure is
- // not a fatal error
- if (err != OK) {
- ALOGW("Failed to set video buffer count to %d due to %d",
- mNumInputBuffers, err);
- }
- }
-
- err = mCamera->sendCommand(
- CAMERA_CMD_SET_VIDEO_FORMAT, mEncoderFormat, mEncoderDataSpace);
-
- // This could happen for CameraHAL1 clients; thus the failure is
- // not a fatal error
- if (err != OK) {
- ALOGW("Failed to set video encoder format/dataspace to %d, %d due to %d",
- mEncoderFormat, mEncoderDataSpace, err);
- }
-
- // Create memory heap to store buffers as VideoNativeMetadata.
- createVideoBufferMemoryHeap(sizeof(VideoNativeHandleMetadata), kDefaultVideoBufferCount);
+ // Initialize buffer queue.
+ err = initBufferQueue(mVideoSize.width, mVideoSize.height, mEncoderFormat,
+ (android_dataspace_t)mEncoderDataSpace,
+ mNumInputBuffers > 0 ? mNumInputBuffers : 1);
+ if (err != OK) {
+ ALOGE("%s: Failed to initialize buffer queue: %s (err=%d)", __FUNCTION__,
+ strerror(-err), err);
+ return err;
}
+ // Start data flow
err = OK;
if (mCameraFlags & FLAGS_HOT_CAMERA) {
mCamera->unlock();
mCamera.clear();
- if ((err = mCameraRecordingProxy->startRecording(
- new ProxyListener(this))) != OK) {
+ if ((err = mCameraRecordingProxy->startRecording()) != OK) {
ALOGE("Failed to start recording, received error: %s (%d)",
strerror(-err), err);
}
} else {
- mCamera->setListener(new CameraSourceListener(this));
mCamera->startRecording();
if (!mCamera->recordingEnabled()) {
err = -EINVAL;
@@ -836,7 +697,6 @@
}
} else {
if (mCamera != 0) {
- mCamera->setListener(NULL);
mCamera->stopRecording();
}
}
@@ -935,97 +795,31 @@
void CameraSource::releaseRecordingFrame(const sp<IMemory>& frame) {
ALOGV("releaseRecordingFrame");
- if (mVideoBufferMode == hardware::ICamera::VIDEO_BUFFER_MODE_BUFFER_QUEUE) {
- // Return the buffer to buffer queue in VIDEO_BUFFER_MODE_BUFFER_QUEUE mode.
- ssize_t offset;
- size_t size;
- sp<IMemoryHeap> heap = frame->getMemory(&offset, &size);
- if (heap->getHeapID() != mMemoryHeapBase->getHeapID()) {
- ALOGE("%s: Mismatched heap ID, ignoring release (got %x, expected %x)", __FUNCTION__,
- heap->getHeapID(), mMemoryHeapBase->getHeapID());
- return;
- }
-
- VideoNativeMetadata *payload = reinterpret_cast<VideoNativeMetadata*>(
- (uint8_t*)heap->getBase() + offset);
-
- // Find the corresponding buffer item for the native window buffer.
- ssize_t index = mReceivedBufferItemMap.indexOfKey(payload->pBuffer);
- if (index == NAME_NOT_FOUND) {
- ALOGE("%s: Couldn't find buffer item for %p", __FUNCTION__, payload->pBuffer);
- return;
- }
-
- BufferItem buffer = mReceivedBufferItemMap.valueAt(index);
- mReceivedBufferItemMap.removeItemsAt(index);
- mVideoBufferConsumer->releaseBuffer(buffer);
- mMemoryBases.push_back(frame);
- mMemoryBaseAvailableCond.signal();
- } else {
- native_handle_t* handle = nullptr;
-
- // Check if frame contains a VideoNativeHandleMetadata.
- if (frame->size() == sizeof(VideoNativeHandleMetadata)) {
- // TODO: Using unsecurePointer() has some associated security pitfalls
- // (see declaration for details).
- // Either document why it is safe in this case or address the
- // issue (e.g. by copying).
- VideoNativeHandleMetadata *metadata =
- (VideoNativeHandleMetadata*)(frame->unsecurePointer());
- if (metadata->eType == kMetadataBufferTypeNativeHandleSource) {
- handle = metadata->pHandle;
- }
- }
-
- if (handle != nullptr) {
- ssize_t offset;
- size_t size;
- sp<IMemoryHeap> heap = frame->getMemory(&offset, &size);
- if (heap->getHeapID() != mMemoryHeapBase->getHeapID()) {
- ALOGE("%s: Mismatched heap ID, ignoring release (got %x, expected %x)",
- __FUNCTION__, heap->getHeapID(), mMemoryHeapBase->getHeapID());
- return;
- }
- uint32_t batchSize = 0;
- {
- Mutex::Autolock autoLock(mBatchLock);
- if (mInflightBatchSizes.size() > 0) {
- batchSize = mInflightBatchSizes[0];
- }
- }
- if (batchSize == 0) { // return buffers one by one
- // Frame contains a VideoNativeHandleMetadata. Send the handle back to camera.
- releaseRecordingFrameHandle(handle);
- mMemoryBases.push_back(frame);
- mMemoryBaseAvailableCond.signal();
- } else { // Group buffers in batch then return
- Mutex::Autolock autoLock(mBatchLock);
- mInflightReturnedHandles.push_back(handle);
- mInflightReturnedMemorys.push_back(frame);
- if (mInflightReturnedHandles.size() == batchSize) {
- releaseRecordingFrameHandleBatch(mInflightReturnedHandles);
-
- mInflightBatchSizes.pop_front();
- mInflightReturnedHandles.clear();
- for (const auto& mem : mInflightReturnedMemorys) {
- mMemoryBases.push_back(mem);
- mMemoryBaseAvailableCond.signal();
- }
- mInflightReturnedMemorys.clear();
- }
- }
-
- } else if (mCameraRecordingProxy != nullptr) {
- // mCamera is created by application. Return the frame back to camera via camera
- // recording proxy.
- mCameraRecordingProxy->releaseRecordingFrame(frame);
- } else if (mCamera != nullptr) {
- // mCamera is created by CameraSource. Return the frame directly back to camera.
- int64_t token = IPCThreadState::self()->clearCallingIdentity();
- mCamera->releaseRecordingFrame(frame);
- IPCThreadState::self()->restoreCallingIdentity(token);
- }
+ // Return the buffer to buffer queue in VIDEO_BUFFER_MODE_BUFFER_QUEUE mode.
+ ssize_t offset;
+ size_t size;
+ sp<IMemoryHeap> heap = frame->getMemory(&offset, &size);
+ if (heap->getHeapID() != mMemoryHeapBase->getHeapID()) {
+ ALOGE("%s: Mismatched heap ID, ignoring release (got %x, expected %x)", __FUNCTION__,
+ heap->getHeapID(), mMemoryHeapBase->getHeapID());
+ return;
}
+
+ VideoNativeMetadata *payload = reinterpret_cast<VideoNativeMetadata*>(
+ (uint8_t*)heap->getBase() + offset);
+
+ // Find the corresponding buffer item for the native window buffer.
+ ssize_t index = mReceivedBufferItemMap.indexOfKey(payload->pBuffer);
+ if (index == NAME_NOT_FOUND) {
+ ALOGE("%s: Couldn't find buffer item for %p", __FUNCTION__, payload->pBuffer);
+ return;
+ }
+
+ BufferItem buffer = mReceivedBufferItemMap.valueAt(index);
+ mReceivedBufferItemMap.removeItemsAt(index);
+ mVideoBufferConsumer->releaseBuffer(buffer);
+ mMemoryBases.push_back(frame);
+ mMemoryBaseAvailableCond.signal();
}
void CameraSource::releaseQueuedFrames() {
@@ -1181,152 +975,6 @@
return false;
}
-void CameraSource::dataCallbackTimestamp(int64_t timestampUs,
- int32_t msgType __unused, const sp<IMemory> &data) {
- ALOGV("dataCallbackTimestamp: timestamp %lld us", (long long)timestampUs);
- Mutex::Autolock autoLock(mLock);
-
- if (shouldSkipFrameLocked(timestampUs)) {
- releaseOneRecordingFrame(data);
- return;
- }
-
- ++mNumFramesReceived;
-
- CHECK(data != NULL && data->size() > 0);
- mFramesReceived.push_back(data);
- int64_t timeUs = mStartTimeUs + (timestampUs - mFirstFrameTimeUs);
- mFrameTimes.push_back(timeUs);
- ALOGV("initial delay: %" PRId64 ", current time stamp: %" PRId64,
- mStartTimeUs, timeUs);
- mFrameAvailableCondition.signal();
-}
-
-void CameraSource::releaseRecordingFrameHandle(native_handle_t* handle) {
- if (mCameraRecordingProxy != nullptr) {
- mCameraRecordingProxy->releaseRecordingFrameHandle(handle);
- } else if (mCamera != nullptr) {
- int64_t token = IPCThreadState::self()->clearCallingIdentity();
- mCamera->releaseRecordingFrameHandle(handle);
- IPCThreadState::self()->restoreCallingIdentity(token);
- } else {
- native_handle_close(handle);
- native_handle_delete(handle);
- }
-}
-
-void CameraSource::releaseRecordingFrameHandleBatch(const std::vector<native_handle_t*>& handles) {
- if (mCameraRecordingProxy != nullptr) {
- mCameraRecordingProxy->releaseRecordingFrameHandleBatch(handles);
- } else if (mCamera != nullptr) {
- int64_t token = IPCThreadState::self()->clearCallingIdentity();
- mCamera->releaseRecordingFrameHandleBatch(handles);
- IPCThreadState::self()->restoreCallingIdentity(token);
- } else {
- for (auto& handle : handles) {
- native_handle_close(handle);
- native_handle_delete(handle);
- }
- }
-}
-
-void CameraSource::recordingFrameHandleCallbackTimestamp(int64_t timestampUs,
- native_handle_t* handle) {
- ALOGV("%s: timestamp %lld us", __FUNCTION__, (long long)timestampUs);
- Mutex::Autolock autoLock(mLock);
- if (handle == nullptr) return;
-
- if (shouldSkipFrameLocked(timestampUs)) {
- releaseRecordingFrameHandle(handle);
- return;
- }
-
- while (mMemoryBases.empty()) {
- if (mMemoryBaseAvailableCond.waitRelative(mLock, kMemoryBaseAvailableTimeoutNs) ==
- TIMED_OUT) {
- ALOGW("Waiting on an available memory base timed out. Dropping a recording frame.");
- releaseRecordingFrameHandle(handle);
- return;
- }
- }
-
- ++mNumFramesReceived;
-
- sp<IMemory> data = *mMemoryBases.begin();
- mMemoryBases.erase(mMemoryBases.begin());
-
- // Wrap native handle in sp<IMemory> so it can be pushed to mFramesReceived.
- VideoNativeHandleMetadata *metadata = (VideoNativeHandleMetadata*)(data->unsecurePointer());
- metadata->eType = kMetadataBufferTypeNativeHandleSource;
- metadata->pHandle = handle;
-
- mFramesReceived.push_back(data);
- int64_t timeUs = mStartTimeUs + (timestampUs - mFirstFrameTimeUs);
- mFrameTimes.push_back(timeUs);
- ALOGV("initial delay: %" PRId64 ", current time stamp: %" PRId64, mStartTimeUs, timeUs);
- mFrameAvailableCondition.signal();
-}
-
-void CameraSource::recordingFrameHandleCallbackTimestampBatch(
- const std::vector<int64_t>& timestampsUs,
- const std::vector<native_handle_t*>& handles) {
- size_t n = timestampsUs.size();
- if (n != handles.size()) {
- ALOGE("%s: timestampsUs(%zu) and handles(%zu) size mismatch!",
- __FUNCTION__, timestampsUs.size(), handles.size());
- }
-
- Mutex::Autolock autoLock(mLock);
- int batchSize = 0;
- for (size_t i = 0; i < n; i++) {
- int64_t timestampUs = timestampsUs[i];
- native_handle_t* handle = handles[i];
-
- ALOGV("%s: timestamp %lld us", __FUNCTION__, (long long)timestampUs);
- if (handle == nullptr) continue;
-
- if (shouldSkipFrameLocked(timestampUs)) {
- releaseRecordingFrameHandle(handle);
- continue;
- }
-
- while (mMemoryBases.empty()) {
- if (mMemoryBaseAvailableCond.waitRelative(mLock, kMemoryBaseAvailableTimeoutNs) ==
- TIMED_OUT) {
- ALOGW("Waiting on an available memory base timed out. Dropping a recording frame.");
- releaseRecordingFrameHandle(handle);
- continue;
- }
- }
- ++batchSize;
- ++mNumFramesReceived;
- sp<IMemory> data = *mMemoryBases.begin();
- mMemoryBases.erase(mMemoryBases.begin());
-
- // Wrap native handle in sp<IMemory> so it can be pushed to mFramesReceived.
- // TODO: Using unsecurePointer() has some associated security pitfalls
- // (see declaration for details).
- // Either document why it is safe in this case or address the
- // issue (e.g. by copying).
- VideoNativeHandleMetadata *metadata = (VideoNativeHandleMetadata*)(data->unsecurePointer());
- metadata->eType = kMetadataBufferTypeNativeHandleSource;
- metadata->pHandle = handle;
-
- mFramesReceived.push_back(data);
- int64_t timeUs = mStartTimeUs + (timestampUs - mFirstFrameTimeUs);
- mFrameTimes.push_back(timeUs);
- ALOGV("initial delay: %" PRId64 ", current time stamp: %" PRId64, mStartTimeUs, timeUs);
-
- }
- if (batchSize > 0) {
- Mutex::Autolock autoLock(mBatchLock);
- mInflightBatchSizes.push_back(batchSize);
- }
- for (int i = 0; i < batchSize; i++) {
- mFrameAvailableCondition.signal();
- }
-}
-
CameraSource::BufferQueueListener::BufferQueueListener(const sp<BufferItemConsumer>& consumer,
const sp<CameraSource>& cameraSource) {
mConsumer = consumer;
@@ -1417,41 +1065,7 @@
MetadataBufferType CameraSource::metaDataStoredInVideoBuffers() const {
ALOGV("metaDataStoredInVideoBuffers");
- // Output buffers will contain metadata if camera sends us buffer in metadata mode or via
- // buffer queue.
- switch (mVideoBufferMode) {
- case hardware::ICamera::VIDEO_BUFFER_MODE_DATA_CALLBACK_METADATA:
- return kMetadataBufferTypeNativeHandleSource;
- case hardware::ICamera::VIDEO_BUFFER_MODE_BUFFER_QUEUE:
- return kMetadataBufferTypeANWBuffer;
- default:
- return kMetadataBufferTypeInvalid;
- }
-}
-
-CameraSource::ProxyListener::ProxyListener(const sp<CameraSource>& source) {
- mSource = source;
-}
-
-void CameraSource::ProxyListener::dataCallbackTimestamp(
- nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) {
- mSource->dataCallbackTimestamp(timestamp / 1000, msgType, dataPtr);
-}
-
-void CameraSource::ProxyListener::recordingFrameHandleCallbackTimestamp(nsecs_t timestamp,
- native_handle_t* handle) {
- mSource->recordingFrameHandleCallbackTimestamp(timestamp / 1000, handle);
-}
-
-void CameraSource::ProxyListener::recordingFrameHandleCallbackTimestampBatch(
- const std::vector<int64_t>& timestampsUs,
- const std::vector<native_handle_t*>& handles) {
- int n = timestampsUs.size();
- std::vector<nsecs_t> modifiedTimestamps(n);
- for (int i = 0; i < n; i++) {
- modifiedTimestamps[i] = timestampsUs[i] / 1000;
- }
- mSource->recordingFrameHandleCallbackTimestampBatch(modifiedTimestamps, handles);
+ return kMetadataBufferTypeANWBuffer;
}
void CameraSource::DeathNotifier::binderDied(const wp<IBinder>& who __unused) {
diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp
index e0a6eb3..50a512f 100644
--- a/media/libstagefright/CameraSourceTimeLapse.cpp
+++ b/media/libstagefright/CameraSourceTimeLapse.cpp
@@ -45,15 +45,13 @@
Size videoSize,
int32_t videoFrameRate,
const sp<IGraphicBufferProducer>& surface,
- int64_t timeBetweenFrameCaptureUs,
- bool storeMetaDataInVideoBuffers) {
+ int64_t timeBetweenFrameCaptureUs) {
CameraSourceTimeLapse *source = new
CameraSourceTimeLapse(camera, proxy, cameraId,
clientName, clientUid, clientPid,
videoSize, videoFrameRate, surface,
- timeBetweenFrameCaptureUs,
- storeMetaDataInVideoBuffers);
+ timeBetweenFrameCaptureUs);
if (source != NULL) {
if (source->initCheck() != OK) {
@@ -74,11 +72,9 @@
Size videoSize,
int32_t videoFrameRate,
const sp<IGraphicBufferProducer>& surface,
- int64_t timeBetweenFrameCaptureUs,
- bool storeMetaDataInVideoBuffers)
+ int64_t timeBetweenFrameCaptureUs)
: CameraSource(camera, proxy, cameraId, clientName, clientUid, clientPid,
- videoSize, videoFrameRate, surface,
- storeMetaDataInVideoBuffers),
+ videoSize, videoFrameRate, surface),
mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate),
mLastTimeLapseFrameRealTimestampUs(0),
mSkipCurrentFrame(false) {
@@ -173,12 +169,6 @@
ALOGV("signalBufferReturned");
Mutex::Autolock autoLock(mQuickStopLock);
if (mQuickStop && (buffer == mLastReadBufferCopy)) {
- if (metaDataStoredInVideoBuffers() == kMetadataBufferTypeNativeHandleSource) {
- native_handle_t* handle = (
- (VideoNativeHandleMetadata*)(mLastReadBufferCopy->data()))->pHandle;
- native_handle_close(handle);
- native_handle_delete(handle);
- }
buffer->setObserver(NULL);
buffer->release();
mLastReadBufferCopy = NULL;
@@ -191,8 +181,7 @@
void createMediaBufferCopy(
const MediaBufferBase& sourceBuffer,
int64_t frameTime,
- MediaBufferBase **newBuffer,
- int32_t videoBufferMode) {
+ MediaBufferBase **newBuffer) {
ALOGV("createMediaBufferCopy");
size_t sourceSize = sourceBuffer.size();
@@ -203,19 +192,13 @@
(*newBuffer)->meta_data().setInt64(kKeyTime, frameTime);
- if (videoBufferMode == kMetadataBufferTypeNativeHandleSource) {
- ((VideoNativeHandleMetadata*)((*newBuffer)->data()))->pHandle =
- native_handle_clone(
- ((VideoNativeHandleMetadata*)(sourceBuffer.data()))->pHandle);
- }
}
void CameraSourceTimeLapse::fillLastReadBufferCopy(MediaBufferBase& sourceBuffer) {
ALOGV("fillLastReadBufferCopy");
int64_t frameTime;
CHECK(sourceBuffer.meta_data().findInt64(kKeyTime, &frameTime));
- createMediaBufferCopy(sourceBuffer, frameTime, &mLastReadBufferCopy,
- metaDataStoredInVideoBuffers());
+ createMediaBufferCopy(sourceBuffer, frameTime, &mLastReadBufferCopy);
mLastReadBufferCopy->add_ref();
mLastReadBufferCopy->setObserver(this);
}
@@ -240,19 +223,6 @@
}
}
-sp<IMemory> CameraSourceTimeLapse::createIMemoryCopy(
- const sp<IMemory> &source_data) {
-
- ALOGV("createIMemoryCopy");
- size_t source_size = source_data->size();
- void* source_pointer = source_data->unsecurePointer();
-
- sp<MemoryHeapBase> newMemoryHeap = new MemoryHeapBase(source_size);
- sp<MemoryBase> newMemory = new MemoryBase(newMemoryHeap, 0, source_size);
- memcpy(newMemory->unsecurePointer(), source_pointer, source_size);
- return newMemory;
-}
-
bool CameraSourceTimeLapse::skipCurrentFrame(int64_t /* timestampUs */) {
ALOGV("skipCurrentFrame");
if (mSkipCurrentFrame) {
@@ -318,31 +288,6 @@
return false;
}
-void CameraSourceTimeLapse::dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
- const sp<IMemory> &data) {
- ALOGV("dataCallbackTimestamp");
- mSkipCurrentFrame = skipFrameAndModifyTimeStamp(×tampUs);
- CameraSource::dataCallbackTimestamp(timestampUs, msgType, data);
-}
-
-void CameraSourceTimeLapse::recordingFrameHandleCallbackTimestamp(int64_t timestampUs,
- native_handle_t* handle) {
- ALOGV("recordingFrameHandleCallbackTimestamp");
- mSkipCurrentFrame = skipFrameAndModifyTimeStamp(×tampUs);
- CameraSource::recordingFrameHandleCallbackTimestamp(timestampUs, handle);
-}
-
-void CameraSourceTimeLapse::recordingFrameHandleCallbackTimestampBatch(
- const std::vector<int64_t>& timestampsUs,
- const std::vector<native_handle_t*>& handles) {
- ALOGV("recordingFrameHandleCallbackTimestampBatch");
- int n = timestampsUs.size();
- for (int i = 0; i < n; i++) {
- // Don't do batching for CameraSourceTimeLapse for now
- recordingFrameHandleCallbackTimestamp(timestampsUs[i], handles[i]);
- }
-}
-
void CameraSourceTimeLapse::processBufferQueueFrame(BufferItem& buffer) {
ALOGV("processBufferQueueFrame");
int64_t timestampUs = buffer.mTimestamp / 1000;
diff --git a/media/libstagefright/FrameDecoder.cpp b/media/libstagefright/FrameDecoder.cpp
index 734f5bb..965b6dd 100644
--- a/media/libstagefright/FrameDecoder.cpp
+++ b/media/libstagefright/FrameDecoder.cpp
@@ -19,6 +19,7 @@
#include "include/FrameDecoder.h"
#include "include/FrameCaptureLayer.h"
+#include "include/HevcUtils.h"
#include <binder/MemoryBase.h>
#include <binder/MemoryHeapBase.h>
#include <gui/Surface.h>
@@ -456,7 +457,8 @@
const sp<IMediaSource> &source)
: FrameDecoder(componentName, trackMeta, source),
mFrame(NULL),
- mIsAvcOrHevc(false),
+ mIsAvc(false),
+ mIsHevc(false),
mSeekMode(MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC),
mTargetTimeUs(-1LL),
mDefaultSampleDurationUs(0) {
@@ -479,8 +481,8 @@
return NULL;
}
- mIsAvcOrHevc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)
- || !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC);
+ mIsAvc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+ mIsHevc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC);
if (frameTimeUs < 0) {
int64_t thumbNailTime = -1ll;
@@ -543,8 +545,10 @@
ALOGV("Seeking closest: targetTimeUs=%lld", (long long)mTargetTimeUs);
}
- if (mIsAvcOrHevc && !isSeekingClosest
- && IsIDR(codecBuffer->data(), codecBuffer->size())) {
+ if (!isSeekingClosest
+ && ((mIsAvc && IsIDR(codecBuffer->data(), codecBuffer->size()))
+ || (mIsHevc && IsIDR(
+ codecBuffer->data(), codecBuffer->size())))) {
// Only need to decode one IDR frame, unless we're seeking with CLOSEST
// option, in which case we need to actually decode to targetTimeUs.
*flags |= MediaCodec::BUFFER_FLAG_EOS;
diff --git a/media/libstagefright/HevcUtils.cpp b/media/libstagefright/HevcUtils.cpp
index 482a1a7..aac656a 100644
--- a/media/libstagefright/HevcUtils.cpp
+++ b/media/libstagefright/HevcUtils.cpp
@@ -30,9 +30,14 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/Utils.h>
+#define UNUSED_PARAM __attribute__((unused))
+
namespace android {
-static const uint8_t kHevcNalUnitTypes[5] = {
+static const uint8_t kHevcNalUnitTypes[8] = {
+ kHevcNalUnitTypeCodedSliceIdr,
+ kHevcNalUnitTypeCodedSliceIdrNoLP,
+ kHevcNalUnitTypeCodedSliceCra,
kHevcNalUnitTypeVps,
kHevcNalUnitTypeSps,
kHevcNalUnitTypePps,
@@ -83,6 +88,7 @@
}
if (err != OK) {
+ ALOGE("error parsing VPS or SPS or PPS");
return err;
}
@@ -375,7 +381,7 @@
}
status_t HevcParameterSets::parsePps(
- const uint8_t* data __unused, size_t size __unused) {
+ const uint8_t* data UNUSED_PARAM, size_t size UNUSED_PARAM) {
return OK;
}
@@ -488,4 +494,26 @@
return OK;
}
+bool HevcParameterSets::IsHevcIDR(const uint8_t *data, size_t size) {
+ bool foundIDR = false;
+ const uint8_t *nalStart;
+ size_t nalSize;
+ while (!foundIDR && getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
+ if (nalSize == 0) {
+ ALOGE("Encountered zero-length HEVC NAL");
+ return false;
+ }
+
+ uint8_t nalType = (nalStart[0] & 0x7E) >> 1;
+ switch(nalType) {
+ case kHevcNalUnitTypeCodedSliceIdr:
+ case kHevcNalUnitTypeCodedSliceIdrNoLP:
+ case kHevcNalUnitTypeCodedSliceCra:
+ foundIDR = true;
+ break;
+ }
+ }
+
+ return foundIDR;
+}
} // namespace android
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index af8096d..7c2a5ff 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
@@ -510,6 +542,7 @@
mNumGrids = 0;
mNextItemId = kItemIdBase;
mHasRefs = false;
+ mResetStatus = OK;
mPreAllocFirstTime = true;
mPrevAllTracksTotalMetaDataSizeEstimate = 0;
@@ -534,7 +567,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 +777,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 +828,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 +851,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 +946,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 +1001,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;
@@ -953,6 +1028,11 @@
return OK;
}
+status_t MPEG4Writer::stop() {
+ // If reset was in progress, wait for it to complete.
+ return reset(true, true);
+}
+
status_t MPEG4Writer::pause() {
ALOGW("MPEG4Writer: pause is not supported");
return ERROR_UNSUPPORTED;
@@ -961,6 +1041,7 @@
status_t MPEG4Writer::stopWriterThread() {
ALOGV("Stopping writer thread");
if (!mWriterThreadStarted) {
+ ALOGD("Writer thread not started");
return OK;
}
{
@@ -970,12 +1051,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,30 +1116,61 @@
writeInt32(0x40000000); // w
}
-void MPEG4Writer::release() {
- ALOGD("release()");
- if (mPreAllocationEnabled) {
- truncatePreAllocation();
+void MPEG4Writer::printWriteDurations() {
+ if (mWriteDurationPQ.empty()) {
+ return;
}
- int retVal = fsync(mFd);
- WARN_UNLESS(retVal == 0, "fsync retVal:%d", retVal);
- retVal = close(mFd);
- WARN_UNLESS(retVal == 0, "close mFd retVal :%d", retVal);
+ std::string writeDurationsString =
+ "Top " + std::to_string(mWriteDurationPQ.size()) + " write durations(microseconds):";
+ uint8_t i = 0;
+ while (!mWriteDurationPQ.empty()) {
+ writeDurationsString +=
+ " #" + std::to_string(++i) + ":" + std::to_string(mWriteDurationPQ.top().count());
+ mWriteDurationPQ.pop();
+ }
+ ALOGD("%s", writeDurationsString.c_str());
+}
+
+status_t MPEG4Writer::release() {
+ ALOGD("release()");
+ status_t err = OK;
+ if (!truncatePreAllocation()) {
+ if (err == OK) { err = ERROR_IO; }
+ }
+ 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;
+
+ printWriteDurations();
+
+ return err;
}
-void MPEG4Writer::finishCurrentSession() {
- reset(false /* stopSource */);
+status_t MPEG4Writer::finishCurrentSession() {
+ ALOGV("finishCurrentSession");
+ /* Don't wait if reset is in progress already, that avoids deadlock
+ * as finishCurrentSession() is called from control looper thread.
+ */
+ return reset(false, false);
}
status_t MPEG4Writer::switchFd() {
@@ -1065,7 +1181,7 @@
}
if (mNextFd == -1) {
- ALOGW("No FileDescripter for next recording");
+ ALOGW("No FileDescriptor for next recording");
return INVALID_OPERATION;
}
@@ -1076,20 +1192,45 @@
return err;
}
-status_t MPEG4Writer::reset(bool stopSource) {
+status_t MPEG4Writer::reset(bool stopSource, bool waitForAnyPreviousCallToComplete) {
ALOGD("reset()");
- std::lock_guard<std::mutex> l(mResetMutex);
+ std::unique_lock<std::mutex> lk(mResetMutex, std::defer_lock);
+ if (waitForAnyPreviousCallToComplete) {
+ /* stop=>reset from client needs the return value of reset call, hence wait here
+ * if a reset was in process already.
+ */
+ lk.lock();
+ } else if (!lk.try_lock()) {
+ /* Internal reset from control looper thread shouldn't wait for any reset in
+ * process already.
+ */
+ return INVALID_OPERATION;
+ }
+
+ if (mResetStatus != OK) {
+ /* Don't have to proceed if reset has finished with an error before.
+ * If there was no error before, proceeding reset would be harmless, as the
+ * the call would return from the mInitCheck condition below.
+ */
+ return mResetStatus;
+ }
+
if (mInitCheck != OK) {
- return OK;
+ mResetStatus = OK;
+ return mResetStatus;
} 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;
+ }
+ mResetStatus = retErr;
+ return mResetStatus;
}
}
@@ -1100,8 +1241,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 +1260,6 @@
}
}
-
if (nonImageTrackCount > 1) {
ALOGD("Duration from tracks range is [%" PRId64 ", %" PRId64 "] us",
minDurationUs, maxDurationUs);
@@ -1126,15 +1267,18 @@
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;
+ mResetStatus = err;
+ return mResetStatus;
}
// Fix up the size of the 'mdat' chunk.
@@ -1174,6 +1318,7 @@
} else {
ALOGI("The mp4 file will not be streamable.");
}
+ ALOGI("MOOV atom was written to the file");
}
mWriteBoxToMemory = false;
@@ -1186,8 +1331,13 @@
CHECK(mBoxes.empty());
- release();
- return err;
+ status_t errRelease = release();
+ // Prioritize the error that occurred before release().
+ if (err == OK) {
+ err = errRelease;
+ }
+ mResetStatus = err;
+ return mResetStatus;
}
/*
@@ -1354,7 +1504,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 +1588,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;
}
@@ -1500,7 +1646,17 @@
void MPEG4Writer::writeOrPostError(int fd, const void* buf, size_t count) {
if (mWriteSeekErr == true)
return;
+
+ auto beforeTP = std::chrono::high_resolution_clock::now();
ssize_t bytesWritten = ::write(fd, buf, count);
+ auto afterTP = std::chrono::high_resolution_clock::now();
+ auto writeDuration =
+ std::chrono::duration_cast<std::chrono::microseconds>(afterTP - beforeTP).count();
+ mWriteDurationPQ.emplace(writeDuration);
+ if (mWriteDurationPQ.size() > kWriteDurationsCount) {
+ mWriteDurationPQ.pop();
+ }
+
/* Write as much as possible during stop() execution when there was an error
* (mWriteSeekErr == true) in the previous call to write() or lseek64().
*/
@@ -1512,9 +1668,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 +1687,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 +1924,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 +1940,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 +1951,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 +2063,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 +2073,7 @@
mStarted(false),
mGotStartKeyFrame(false),
mIsMalformed(false),
- mTrackId(trackId),
+ mTrackId(aTrackId),
mTrackDurationUs(0),
mEstimatedTrackSizeBytes(0),
mSamplesHaveSameSize(true),
@@ -2086,25 +2250,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");
}
}
@@ -2321,27 +2488,40 @@
int fd = mNextFd;
mNextFd = -1;
mLock.unlock();
- finishCurrentSession();
- initInternal(fd, false /*isFirstSession*/);
- start(mStartMeta.get());
- mSwitchPending = false;
- notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED, 0);
+ if (finishCurrentSession() == OK) {
+ initInternal(fd, false /*isFirstSession*/);
+ status_t status = start(mStartMeta.get());
+ mSwitchPending = false;
+ if (status == OK) {
+ notify(MEDIA_RECORDER_EVENT_INFO,
+ MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED, 0);
+ }
+ }
break;
}
- // ::write() or lseek64() wasn't a success, file could be malformed
- case kWhatHandleIOError: {
- ALOGE("kWhatHandleIOError");
- // Stop tracks' threads and main writer thread.
- notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_RECORDER_ERROR_UNKNOWN, ERROR_MALFORMED);
- stop();
+ /* ::write() or lseek64() wasn't a success, file could be malformed.
+ * Or fallocate() failed. reset() and notify client on both the cases.
+ */
+ case kWhatFallocateError: // fallthrough
+ case kWhatIOError: {
+ int32_t err;
+ CHECK(msg->findInt32("err", &err));
+ // If reset already in process, don't wait for it complete to avoid deadlock.
+ reset(true, false);
+ //TODO: new MEDIA_RECORDER_ERROR_**** instead MEDIA_RECORDER_ERROR_UNKNOWN ?
+ 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);
- stop();
+ /* Response to kWhatNoIOErrorSoFar would be OK always as of now.
+ * Responding with other options could be added later if required.
+ */
+ case kWhatNoIOErrorSoFar: {
+ ALOGV("kWhatNoIOErrorSoFar");
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", OK);
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+ response->postReply(replyID);
break;
}
default:
@@ -2706,11 +2886,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 +3034,7 @@
}
if (nextStartCode == NULL) {
+ ALOGE("nextStartCode is null");
return ERROR_MALFORMED;
}
@@ -3126,11 +3312,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 +3367,7 @@
}
++count;
+
int32_t isCodecConfig;
if (buffer->meta_data().findInt32(kKeyIsCodecConfig, &isCodecConfig)
&& isCodecConfig) {
@@ -3204,7 +3391,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 +3400,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 +3440,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 +3480,7 @@
updateTrackSizeEstimate();
if (mOwner->exceedsFileSizeLimit()) {
+ copy->release();
if (mOwner->switchFd() != OK) {
ALOGW("Recorded file size exceeds limit %" PRId64 "bytes",
mOwner->mMaxFileSizeLimitBytes);
@@ -3301,16 +3491,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 +3781,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 +3854,7 @@
return err;
}
-bool MPEG4Writer::Track::isTrackMalFormed() const {
+bool MPEG4Writer::Track::isTrackMalFormed() {
if (mIsMalformed) {
return true;
}
@@ -3674,23 +3863,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 +3899,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 +3953,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 +4131,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 +4437,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();
@@ -4550,10 +4745,18 @@
// This is useful if the pixel is not square
void MPEG4Writer::Track::writePaspBox() {
- mOwner->beginBox("pasp");
- mOwner->writeInt32(1 << 16); // hspacing
- mOwner->writeInt32(1 << 16); // vspacing
- mOwner->endBox(); // pasp
+ // Do not write 'pasp' box unless the track format specifies it.
+ // According to ISO/IEC 14496-12 (ISO base media file format), 'pasp' box
+ // is optional. If present, it overrides the SAR from the video CSD. Only
+ // set it if the track format specifically requests that.
+ int32_t hSpacing, vSpacing;
+ if (mMeta->findInt32(kKeySARWidth, &hSpacing) && (hSpacing > 0)
+ && mMeta->findInt32(kKeySARHeight, &vSpacing) && (vSpacing > 0)) {
+ mOwner->beginBox("pasp");
+ mOwner->writeInt32(hSpacing); // hspacing
+ mOwner->writeInt32(vSpacing); // vspacing
+ mOwner->endBox(); // pasp
+ }
}
int64_t MPEG4Writer::Track::getStartTimeOffsetTimeUs() const {
diff --git a/media/libstagefright/MediaAdapter.cpp b/media/libstagefright/MediaAdapter.cpp
index f1b6e8c..5a2a910 100644
--- a/media/libstagefright/MediaAdapter.cpp
+++ b/media/libstagefright/MediaAdapter.cpp
@@ -114,6 +114,13 @@
return -EINVAL;
}
+ /* As mAdapterLock is unlocked while waiting for signalBufferReturned,
+ * a new buffer for the same track could be pushed from another thread
+ * in the client process, mBufferGatingMutex will help to hold that
+ * until the previous buffer is processed.
+ */
+ std::unique_lock<std::mutex> lk(mBufferGatingMutex);
+
Mutex::Autolock autoLock(mAdapterLock);
if (!mStarted) {
ALOGE("pushBuffer called before start");
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 11f2f38..e975ee6 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,7 +57,9 @@
#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/MediaCodecConstants.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaFilter.h>
@@ -93,9 +97,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 +624,6 @@
mFlags(0),
mStickyError(OK),
mSoftRenderer(NULL),
- mMetricsHandle(0),
mIsVideo(false),
mVideoWidth(0),
mVideoHeight(0),
@@ -677,6 +683,8 @@
mIndexOfFirstFrameWhenLowLatencyOn = -1;
mInputBufferCounter = 0;
}
+
+ mLifetimeStartNs = systemTime(SYSTEM_TIME_MONOTONIC);
}
void MediaCodec::updateMediametrics() {
@@ -685,7 +693,6 @@
return;
}
-
if (mLatencyHist.getCount() != 0 ) {
mediametrics_setInt64(mMetricsHandle, kCodecLatencyMax, mLatencyHist.getMax());
mediametrics_setInt64(mMetricsHandle, kCodecLatencyMin, mLatencyHist.getMin());
@@ -701,6 +708,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 +750,6 @@
}
}
-
// spit the data (if any) into the supplied analytics record
if (recentHist.getCount()!= 0 ) {
mediametrics_setInt64(item, kCodecRecentLatencyMax, recentHist.getMax());
@@ -1472,9 +1483,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);
}
@@ -2305,8 +2316,10 @@
if (mSurface != nullptr && !mAllowFrameDroppingBySurface) {
// signal frame dropping mode in the input format as this may also be
// meaningful and confusing for an encoder in a transcoder scenario
- mInputFormat->setInt32("allow-frame-drop", mAllowFrameDroppingBySurface);
+ mInputFormat->setInt32(KEY_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 +2333,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 +2345,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 +2497,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 +2691,16 @@
}
mResourceManagerProxy->removeClient();
+ mReleaseSurface.reset();
if (mReplyID != nullptr) {
(new AMessage)->postReply(mReplyID);
}
+ if (mAsyncReleaseCompleteNotification != nullptr) {
+ flushMediametrics();
+ mAsyncReleaseCompleteNotification->post();
+ mAsyncReleaseCompleteNotification.clear();
+ }
break;
}
@@ -2764,7 +2820,7 @@
}
if (obj != NULL) {
- if (!format->findInt32("allow-frame-drop", &mAllowFrameDroppingBySurface)) {
+ if (!format->findInt32(KEY_ALLOW_FRAME_DROP, &mAllowFrameDroppingBySurface)) {
// allow frame dropping by surface by default
mAllowFrameDroppingBySurface = true;
}
@@ -3031,22 +3087,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 +3120,11 @@
pushBlankBuffersToNativeWindow(mSurface.get());
}
- if (async) {
+ if (asyncNotify != nullptr) {
mResourceManagerProxy->markClientForPendingRemoval();
(new AMessage)->postReply(mReplyID);
mReplyID = 0;
+ mAsyncReleaseCompleteNotification = asyncNotify;
}
break;
@@ -3146,7 +3204,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 +3496,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 +3561,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 +3625,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 +3686,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 +3758,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 +3804,7 @@
}
status_t err = OK;
- if (hasCryptoOrDescrambler()) {
+ if (hasCryptoOrDescrambler() && !c2Buffer && !memory) {
AString *errorDetailMsg;
CHECK(msg->findPointer("errorDetailMsg", (void **)&errorDetailMsg));
@@ -3725,6 +3842,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/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index 1395c27..bc656a2 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -435,6 +435,30 @@
buffer->release();
}
+status_t MediaCodecSource::setEncodingBitrate(int32_t bitRate) {
+ ALOGV("setEncodingBitrate (%d)", bitRate);
+
+ if (mEncoder == NULL) {
+ ALOGW("setEncodingBitrate (%d) : mEncoder is null", bitRate);
+ return BAD_VALUE;
+ }
+
+ sp<AMessage> params = new AMessage;
+ params->setInt32("video-bitrate", bitRate);
+
+ return mEncoder->setParameters(params);
+}
+
+status_t MediaCodecSource::requestIDRFrame() {
+ if (mEncoder == NULL) {
+ ALOGW("requestIDRFrame : mEncoder is null");
+ return BAD_VALUE;
+ } else {
+ mEncoder->requestIDRFrame();
+ return OK;
+ }
+}
+
MediaCodecSource::MediaCodecSource(
const sp<ALooper> &looper,
const sp<AMessage> &outputFormat,
diff --git a/media/libstagefright/MediaExtractorFactory.cpp b/media/libstagefright/MediaExtractorFactory.cpp
index c6e753d..7c981b3 100644
--- a/media/libstagefright/MediaExtractorFactory.cpp
+++ b/media/libstagefright/MediaExtractorFactory.cpp
@@ -59,7 +59,7 @@
sp<IMediaExtractor> ex;
mediaExService->makeExtractor(
CreateIDataSourceFromDataSource(source),
- mime ? std::make_unique<std::string>(mime) : nullptr,
+ mime ? std::optional<std::string>(mime) : std::nullopt,
&ex);
return ex;
} else {
diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index 1cb45ac..8bbffd4 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 {
@@ -184,16 +175,23 @@
status_t MediaMuxer::writeSampleData(const sp<ABuffer> &buffer, size_t trackIndex,
int64_t timeUs, uint32_t flags) {
- Mutex::Autolock autoLock(mMuxerLock);
-
if (buffer.get() == NULL) {
ALOGE("WriteSampleData() get an NULL buffer.");
return -EINVAL;
}
-
- if (mState != STARTED) {
- ALOGE("WriteSampleData() is called in invalid state %d", mState);
- return INVALID_OPERATION;
+ {
+ /* As MediaMuxer's writeSampleData handles inputs from multiple tracks,
+ * limited the scope of mMuxerLock to this inner block so that the
+ * current track's buffer does not wait until the completion
+ * of processing of previous buffer of the same or another track.
+ * It's the responsibility of individual track - MediaAdapter object
+ * to gate its buffers.
+ */
+ Mutex::Autolock autoLock(mMuxerLock);
+ if (mState != STARTED) {
+ ALOGE("WriteSampleData() is called in invalid state %d", mState);
+ return INVALID_OPERATION;
+ }
}
if (trackIndex >= mTrackList.size()) {
@@ -229,29 +227,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/SurfaceUtils.cpp b/media/libstagefright/SurfaceUtils.cpp
index 4c94baa..85ff474 100644
--- a/media/libstagefright/SurfaceUtils.cpp
+++ b/media/libstagefright/SurfaceUtils.cpp
@@ -165,6 +165,22 @@
ALOGW_IF(err != 0, "failed to set cta861_3 metadata on surface (%d)", err);
}
+status_t setNativeWindowRotation(
+ ANativeWindow *nativeWindow /* nonnull */, int rotation) {
+
+ int transform = 0;
+ if ((rotation % 90) == 0) {
+ switch ((rotation / 90) & 3) {
+ case 1: transform = HAL_TRANSFORM_ROT_90; break;
+ case 2: transform = HAL_TRANSFORM_ROT_180; break;
+ case 3: transform = HAL_TRANSFORM_ROT_270; break;
+ default: transform = 0; break;
+ }
+ }
+
+ return native_window_set_buffers_transform(nativeWindow, transform);
+}
+
status_t pushBlankBuffersToNativeWindow(ANativeWindow *nativeWindow /* nonnull */) {
status_t err = NO_ERROR;
ANativeWindowBuffer* anb = NULL;
diff --git a/media/libstagefright/TEST_MAPPING b/media/libstagefright/TEST_MAPPING
index 8b36ea5..3dceef7 100644
--- a/media/libstagefright/TEST_MAPPING
+++ b/media/libstagefright/TEST_MAPPING
@@ -1,5 +1,12 @@
{
"presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ // { "name": "HEVCUtilsUnitTest" },
+ //{ "name": "ExtractorFactoryTest" },
+ // writerTest fails about 5 out of 66
+ // in addition to needing the download ability
+ //{ "name": "writerTest" },
+
{
"name": "CtsMediaTestCases",
"options": [
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index a1e4d43..d67874f 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -1135,7 +1135,7 @@
// assertion, let's be lenient for now...
// CHECK((ptr[4] >> 2) == 0x3f); // reserved
- size_t lengthSize __unused = 1 + (ptr[4] & 3);
+ // we can get lengthSize value from 1 + (ptr[4] & 3)
// commented out check below as H264_QVGA_500_NO_AUDIO.3gp
// violates it...
@@ -2178,7 +2178,11 @@
}
// Check if offload is possible for given format, stream type, sample rate,
// bit rate, duration, video and streaming
+#ifdef DISABLE_AUDIO_SYSTEM_OFFLOAD
+ return false;
+#else
return AudioSystem::isOffloadSupported(info);
+#endif
}
HLSTime::HLSTime(const sp<AMessage>& meta) :
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/bqhelper/TEST_MAPPING b/media/libstagefright/bqhelper/TEST_MAPPING
new file mode 100644
index 0000000..c7f2fd8
--- /dev/null
+++ b/media/libstagefright/bqhelper/TEST_MAPPING
@@ -0,0 +1,6 @@
+// mappings for frameworks/av/media/libstagefright/bqhelper
+{
+ "presubmit": [
+ { "name": "FrameDropper_test"}
+ ]
+}
diff --git a/media/libstagefright/bqhelper/include/media/stagefright/bqhelper/FrameDropper.h b/media/libstagefright/bqhelper/include/media/stagefright/bqhelper/FrameDropper.h
index 4e83059..7e2909a 100644
--- a/media/libstagefright/bqhelper/include/media/stagefright/bqhelper/FrameDropper.h
+++ b/media/libstagefright/bqhelper/include/media/stagefright/bqhelper/FrameDropper.h
@@ -30,6 +30,8 @@
FrameDropper();
// maxFrameRate required to be positive.
+ // maxFrameRate negative causes shouldDrop() to always return false
+ // maxFrameRate == 0 is illegal
status_t setMaxFrameRate(float maxFrameRate);
// Returns false if max frame rate has not been set via setMaxFrameRate.
diff --git a/media/libstagefright/bqhelper/tests/Android.bp b/media/libstagefright/bqhelper/tests/Android.bp
index 2fbc3d6..3897689 100644
--- a/media/libstagefright/bqhelper/tests/Android.bp
+++ b/media/libstagefright/bqhelper/tests/Android.bp
@@ -1,5 +1,6 @@
cc_test {
name: "FrameDropper_test",
+ test_suites: ["device-tests"],
srcs: ["FrameDropper_test.cpp"],
diff --git a/media/libstagefright/bqhelper/tests/FrameDropper_test.cpp b/media/libstagefright/bqhelper/tests/FrameDropper_test.cpp
index 55ae77c..b18067f 100644
--- a/media/libstagefright/bqhelper/tests/FrameDropper_test.cpp
+++ b/media/libstagefright/bqhelper/tests/FrameDropper_test.cpp
@@ -110,7 +110,7 @@
};
TEST_F(FrameDropperTest, TestInvalidMaxFrameRate) {
- EXPECT_NE(OK, mFrameDropper->setMaxFrameRate(-1.0));
+ EXPECT_EQ(OK, mFrameDropper->setMaxFrameRate(-1.0));
EXPECT_NE(OK, mFrameDropper->setMaxFrameRate(0));
}
diff --git a/media/libstagefright/codecs/amrnb/TEST_MAPPING b/media/libstagefright/codecs/amrnb/TEST_MAPPING
new file mode 100644
index 0000000..2909099
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/TEST_MAPPING
@@ -0,0 +1,11 @@
+// mappings for frameworks/av/media/libstagefright/codecs/amrnb
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ // { "name": "AmrnbDecoderTest"},
+
+ // TODO(b/148094059): unit tests not allowed to download content
+ // { "name": "AmrnbEncoderTest"}
+
+ ]
+}
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/dec/test/Android.bp b/media/libstagefright/codecs/amrnb/dec/test/Android.bp
index 7a95cfa..91c9f86 100644
--- a/media/libstagefright/codecs/amrnb/dec/test/Android.bp
+++ b/media/libstagefright/codecs/amrnb/dec/test/Android.bp
@@ -17,6 +17,7 @@
cc_test {
name: "AmrnbDecoderTest",
gtest: true,
+ test_suites: ["device-tests"],
srcs: [
"AmrnbDecoderTest.cpp",
diff --git a/media/libstagefright/codecs/amrnb/dec/test/AndroidTest.xml b/media/libstagefright/codecs/amrnb/dec/test/AndroidTest.xml
new file mode 100644
index 0000000..1a9e678
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/test/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Test module config for Amr-nb Decoder unit test">
+ <option name="test-suite-tag" value="AmrnbDecoderTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="AmrnbDecoderTest->/data/local/tmp/AmrnbDecoderTest" />
+ <option name="push-file"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/codecs/amrnb/dec/test/AmrnbDecoderTest.zip?unzip=true"
+ value="/data/local/tmp/AmrnbDecoderTestRes/" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="AmrnbDecoderTest" />
+ <option name="native-test-flag" value="-P /data/local/tmp/AmrnbDecoderTestRes/" />
+ </test>
+</configuration>
diff --git a/media/libstagefright/codecs/amrnb/dec/test/README.md b/media/libstagefright/codecs/amrnb/dec/test/README.md
index 62e13ae..e9073e4 100644
--- a/media/libstagefright/codecs/amrnb/dec/test/README.md
+++ b/media/libstagefright/codecs/amrnb/dec/test/README.md
@@ -22,13 +22,18 @@
adb push ${OUT}/data/nativetest/AmrnbDecoderTest/AmrnbDecoderTest /data/local/tmp/
```
-The resource file for the tests is taken from [here](https://drive.google.com/drive/folders/13cM4tAaVFrmr-zGFqaAzFBbKs75pnm9b). Push these files into device for testing.
-Download amr-nb folder and push all the files in this folder to /data/local/tmp/ on the device.
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/codecs/amrnb/dec/test/AmrnbDecoderTest.zip). Download, unzip and push these files into device for testing.
+
```
-adb push amr-nb/. /data/local/tmp/
+adb push AmrnbDecoderTestRes/. /data/local/tmp/
```
usage: AmrnbDecoderTest -P \<path_to_folder\>
```
-adb shell /data/local/tmp/AmrnbDecoderTest -P /data/local/tmp/
+adb shell /data/local/tmp/AmrnbDecoderTest -P /data/local/tmp/AmrnbDecoderTestRes/
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest AmrnbDecoderTest -- --enable-module-dynamic-download=true
```
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/amrnb/enc/test/Android.bp b/media/libstagefright/codecs/amrnb/enc/test/Android.bp
index e8982fe..7e1b561 100644
--- a/media/libstagefright/codecs/amrnb/enc/test/Android.bp
+++ b/media/libstagefright/codecs/amrnb/enc/test/Android.bp
@@ -17,6 +17,7 @@
cc_test {
name: "AmrnbEncoderTest",
gtest: true,
+ test_suites: ["device-tests"],
srcs: [
"AmrnbEncoderTest.cpp",
diff --git a/media/libstagefright/codecs/amrnb/fuzzer/Android.bp b/media/libstagefright/codecs/amrnb/fuzzer/Android.bp
index 54de1cc..c1eaa53 100644
--- a/media/libstagefright/codecs/amrnb/fuzzer/Android.bp
+++ b/media/libstagefright/codecs/amrnb/fuzzer/Android.bp
@@ -34,4 +34,10 @@
enabled: false,
},
},
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
}
diff --git a/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp b/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
index d4e7e5c..c7a7378 100644
--- a/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
+++ b/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
@@ -26,8 +26,10 @@
constexpr int32_t kBitsPerSample = 16;
constexpr int32_t kOutputBufferSize = kSamplesPerFrame * kBitsPerSample / 8;
const bitstream_format kBitStreamFormats[2] = {MIME_IETF, IF2};
-const int32_t kLocalWmfDecBytesPerFrame[8] = {12, 13, 15, 17, 19, 20, 26, 31};
-const int32_t kLocalIf2DecBytesPerFrame[8] = {13, 14, 16, 18, 19, 21, 26, 31};
+const int32_t kLocalWmfDecBytesPerFrame[16] = {12, 13, 15, 17, 19, 20, 26, 31,
+ 5, 6, 5, 5, 0, 0, 0, 0};
+const int32_t kLocalIf2DecBytesPerFrame[16] = {13, 14, 16, 18, 19, 21, 26, 31,
+ 13, 14, 16, 18, 19, 21, 26, 31};
class Codec {
public:
@@ -52,7 +54,7 @@
bitstream_format bitsreamFormat = kBitStreamFormats[bit];
int32_t frameSize = 0;
/* Find frame type */
- Frame_Type_3GPP frameType = static_cast<Frame_Type_3GPP>((mode >> 3) & 0x07);
+ Frame_Type_3GPP frameType = static_cast<Frame_Type_3GPP>((mode >> 3) & 0x0f);
++data;
--size;
if (bit) {
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/TEST_MAPPING b/media/libstagefright/codecs/amrwb/TEST_MAPPING
new file mode 100644
index 0000000..3d58ba2
--- /dev/null
+++ b/media/libstagefright/codecs/amrwb/TEST_MAPPING
@@ -0,0 +1,8 @@
+// mappings for frameworks/av/media/libstagefright/codecs/amrwb
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ // { "name": "AmrwbDecoderTest"}
+
+ ]
+}
diff --git a/media/libstagefright/codecs/amrwb/fuzzer/Android.bp b/media/libstagefright/codecs/amrwb/fuzzer/Android.bp
index 46f77e3..7106a30 100644
--- a/media/libstagefright/codecs/amrwb/fuzzer/Android.bp
+++ b/media/libstagefright/codecs/amrwb/fuzzer/Android.bp
@@ -32,4 +32,10 @@
enabled: false,
},
},
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
}
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/amrwbenc/TEST_MAPPING b/media/libstagefright/codecs/amrwbenc/TEST_MAPPING
new file mode 100644
index 0000000..d53d665
--- /dev/null
+++ b/media/libstagefright/codecs/amrwbenc/TEST_MAPPING
@@ -0,0 +1,8 @@
+// mappings for frameworks/av/media/libstagefright/codecs/amrwbenc
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ // { "name": "AmrwbEncoderTest"}
+
+ ]
+}
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/flac/enc/SoftFlacEncoder.cpp b/media/libstagefright/codecs/flac/enc/SoftFlacEncoder.cpp
index 3add006..078c8e3 100644
--- a/media/libstagefright/codecs/flac/enc/SoftFlacEncoder.cpp
+++ b/media/libstagefright/codecs/flac/enc/SoftFlacEncoder.cpp
@@ -399,29 +399,31 @@
mEncoderWriteData = true;
mEncoderReturnedEncodedData = false;
mEncoderReturnedNbBytes = 0;
- mCurrentInputTimeStamp = inHeader->nTimeStamp;
+ if (inHeader->nFilledLen) {
+ mCurrentInputTimeStamp = inHeader->nTimeStamp;
- const unsigned nbInputFrames = inHeader->nFilledLen / frameSize;
- const unsigned nbInputSamples = inHeader->nFilledLen / sampleSize;
+ const unsigned nbInputFrames = inHeader->nFilledLen / frameSize;
+ const unsigned nbInputSamples = inHeader->nFilledLen / sampleSize;
- if (inputFloat) {
- CHECK_LE(nbInputSamples, kNumSamplesPerFrame * kMaxChannels);
- const float * const pcmFloat = reinterpret_cast<float *>(inHeader->pBuffer);
- memcpy_to_q8_23_from_float_with_clamp(
- mInputBufferPcm32, pcmFloat, nbInputSamples);
- } else {
- // note nbInputSamples may be 2x as large for pcm16 data.
- CHECK_LE(nbInputSamples, kNumSamplesPerFrame * kMaxChannels * 2);
- const int16_t * const pcm16 = reinterpret_cast<int16_t *>(inHeader->pBuffer);
- for (unsigned i = 0; i < nbInputSamples; ++i) {
- mInputBufferPcm32[i] = (FLAC__int32) pcm16[i];
+ if (inputFloat) {
+ CHECK_LE(nbInputSamples, kNumSamplesPerFrame * kMaxChannels);
+ const float * const pcmFloat = reinterpret_cast<float *>(inHeader->pBuffer);
+ memcpy_to_q8_23_from_float_with_clamp(
+ mInputBufferPcm32, pcmFloat, nbInputSamples);
+ } else {
+ // note nbInputSamples may be 2x as large for pcm16 data.
+ CHECK_LE(nbInputSamples, kNumSamplesPerFrame * kMaxChannels * 2);
+ const int16_t * const pcm16 = reinterpret_cast<int16_t *>(inHeader->pBuffer);
+ for (unsigned i = 0; i < nbInputSamples; ++i) {
+ mInputBufferPcm32[i] = (FLAC__int32) pcm16[i];
+ }
}
+ ALOGV(" about to encode %u samples per channel", nbInputFrames);
+ ok = FLAC__stream_encoder_process_interleaved(
+ mFlacStreamEncoder,
+ mInputBufferPcm32,
+ nbInputFrames /*samples per channel*/ );
}
- ALOGV(" about to encode %u samples per channel", nbInputFrames);
- ok = FLAC__stream_encoder_process_interleaved(
- mFlacStreamEncoder,
- mInputBufferPcm32,
- nbInputFrames /*samples per channel*/ );
inInfo->mOwnedByUs = false;
inQueue.erase(inQueue.begin());
@@ -434,7 +436,15 @@
OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
if (ok) {
- if (mEncoderReturnedEncodedData && (mEncoderReturnedNbBytes != 0)) {
+ ALOGV("encoded %d, bytes %lld, eos %d", mEncoderReturnedEncodedData,
+ (long long )mEncoderReturnedNbBytes, mSawInputEOS);
+ if (mSawInputEOS && !mEncoderReturnedEncodedData) {
+ ALOGV("finishing encoder");
+ mSentOutputEOS = true;
+ FLAC__stream_encoder_finish(mFlacStreamEncoder);
+ outHeader->nFlags = OMX_BUFFERFLAG_EOS;
+ }
+ if (mSawInputEOS || mEncoderReturnedEncodedData) {
ALOGV(" dequeueing buffer on output port after writing data");
outInfo->mOwnedByUs = false;
outQueue.erase(outQueue.begin());
@@ -442,23 +452,6 @@
notifyFillBufferDone(outHeader);
outHeader = NULL;
mEncoderReturnedEncodedData = false;
- } else {
- ALOGV(" encoder process_interleaved returned without data to write");
- if (mSawInputEOS) {
- ALOGV("finishing encoder");
- mSentOutputEOS = true;
- FLAC__stream_encoder_finish(mFlacStreamEncoder);
- if (mEncoderReturnedEncodedData && (mEncoderReturnedNbBytes != 0)) {
- ALOGV(" dequeueing residual buffer on output port after writing data");
- outInfo->mOwnedByUs = false;
- outQueue.erase(outQueue.begin());
- outInfo = NULL;
- outHeader->nFlags = OMX_BUFFERFLAG_EOS;
- notifyFillBufferDone(outHeader);
- outHeader = NULL;
- mEncoderReturnedEncodedData = false;
- }
- }
}
} else {
ALOGE(" error encountered during encoding");
diff --git a/media/libstagefright/codecs/gsm/dec/SoftGSM.h b/media/libstagefright/codecs/gsm/dec/SoftGSM.h
index ef86915..d5885a6 100644
--- a/media/libstagefright/codecs/gsm/dec/SoftGSM.h
+++ b/media/libstagefright/codecs/gsm/dec/SoftGSM.h
@@ -20,9 +20,7 @@
#include <media/stagefright/omx/SimpleSoftOMXComponent.h>
-extern "C" {
#include "gsm.h"
-}
namespace android {
diff --git a/media/libstagefright/codecs/m4v_h263/TEST_MAPPING b/media/libstagefright/codecs/m4v_h263/TEST_MAPPING
new file mode 100644
index 0000000..6b42847
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/TEST_MAPPING
@@ -0,0 +1,16 @@
+// mappings for frameworks/av/media/libstagefright/codecs/m4v_h263
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+
+ // the decoder reports something bad about an unexpected newline in the *config file
+ // and the config file looks like the AndroidTest.xml file that we put in there.
+ // I don't get this from the Encoder -- and I don't see any substantive difference
+ // between decode and encode AndroidTest.xml files -- except that encode does NOT
+ // finish with a newline.
+ // strange.
+ // { "name": "Mpeg4H263DecoderTest"},
+ // { "name": "Mpeg4H263EncoderTest"}
+
+ ]
+}
diff --git a/media/libstagefright/codecs/m4v_h263/dec/Android.bp b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
index c67dc2b..7a33c54 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
@@ -1,23 +1,23 @@
cc_library_static {
name: "libstagefright_m4vh263dec",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
+ host_supported: true,
shared_libs: ["liblog"],
srcs: [
- "src/adaptive_smooth_no_mmx.cpp",
"src/bitstream.cpp",
"src/block_idct.cpp",
"src/cal_dc_scaler.cpp",
- "src/chvr_filter.cpp",
- "src/chv_filter.cpp",
"src/combined_decode.cpp",
"src/conceal.cpp",
"src/datapart_decode.cpp",
"src/dcac_prediction.cpp",
"src/dec_pred_intra_dc.cpp",
- "src/deringing_chroma.cpp",
- "src/deringing_luma.cpp",
- "src/find_min_max.cpp",
"src/get_pred_adv_b_add.cpp",
"src/get_pred_outside.cpp",
"src/idct.cpp",
@@ -26,9 +26,6 @@
"src/mb_utils.cpp",
"src/packet_util.cpp",
"src/post_filter.cpp",
- "src/post_proc_semaphore.cpp",
- "src/pp_semaphore_chroma_inter.cpp",
- "src/pp_semaphore_luma.cpp",
"src/pvdec_api.cpp",
"src/scaling_tab.cpp",
"src/vlc_decode.cpp",
@@ -38,11 +35,6 @@
"src/zigzag_tab.cpp",
],
- header_libs: [
- "media_plugin_headers",
- "libstagefright_headers"
- ],
-
local_include_dirs: ["src"],
export_include_dirs: ["include"],
@@ -58,6 +50,12 @@
],
cfi: true,
},
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
//###############################################################################
diff --git a/media/libstagefright/codecs/m4v_h263/dec/include/mp4dec_api.h b/media/libstagefright/codecs/m4v_h263/dec/include/mp4dec_api.h
index 1f404ce..06aee07 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/include/mp4dec_api.h
+++ b/media/libstagefright/codecs/m4v_h263/dec/include/mp4dec_api.h
@@ -42,13 +42,6 @@
#define OSCL_EXPORT_REF /* empty */
#endif
-/* flag for post-processing 4/25/00 */
-
-#ifdef DEC_NOPOSTPROC
-#undef PV_POSTPROC_ON /* enable compilation of post-processing code */
-#else
-#define PV_POSTPROC_ON
-#endif
#define PV_NO_POST_PROC 0
#define PV_DEBLOCK 1
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/adaptive_smooth_no_mmx.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/adaptive_smooth_no_mmx.cpp
deleted file mode 100644
index e2761eb..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/adaptive_smooth_no_mmx.cpp
+++ /dev/null
@@ -1,421 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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.
- * -------------------------------------------------------------------
- */
-/*
-
- Description: Separated modules into one function per file and put into
- new template.
-
- Description: Optimizing C code and adding comments. Also changing variable
- names to make them more meaningful.
-
- Who: Date:
- Description:
-
-------------------------------------------------------------------------------
- INPUT AND OUTPUT DEFINITIONS
-
- Inputs:
-
- Rec_Y = pointer to 0th position in buffer containing luminance values
- of type uint8.
- y_start = value of y coordinate of type int that specifies the first
- row of pixels to be used in the filter algorithm.
- x_start = value of x coordinate of type int that specifies the first
- column of pixels to be used in the filter algorithm.
- y_blk_start = value of the y coordinate of type int that specifies the
- row of pixels which contains the start of a block. The row
- specified by y_blk_start+BLK_SIZE is the last row of pixels
- that are used in the filter algorithm.
- x_blk_start = value of the x coordinate of type int that specifies the
- column of pixels which contains the start of a block. The
- column specified by x_blk_start+BLK_SIZE is the last column of
- pixels that are used in the filter algorithm.
- thr = value of type int that is compared to the elements in Rec_Y to
- determine if a particular value in Rec_Y will be modified by
- the filter or not
- width = value of type int that specifies the width of the display
- in pixels (or pels, equivalently).
- max_diff = value of type int that specifies the value that may be added
- or subtracted from the pixel in Rec_Y that is being filtered
- if the filter algorithm decides to change that particular
- pixel's luminance value.
-
-
- Local Stores/Buffers/Pointers Needed:
- None
-
- Global Stores/Buffers/Pointers Needed:
- None
-
- Outputs:
- None
-
- Pointers and Buffers Modified:
- Buffer pointed to by Rec_Y is modified with the filtered
- luminance values.
-
- Local Stores Modified:
- None
-
- Global Stores Modified:
- None
-
-------------------------------------------------------------------------------
- FUNCTION DESCRIPTION
-
- This function implements a motion compensated noise filter using adaptive
- weighted averaging of luminance values. *Rec_Y contains the luminance values
- that are being filtered.
-
- The picture below depicts a 3x3 group of pixel luminance values. The "u", "c",
- and "l" stand for "upper", "center" and "lower", respectively. The location
- of pelc0 is specified by x_start and y_start in the 1-D array "Rec_Y" as
- follows (assuming x_start=0):
-
- location of pelc0 = [(y_start+1) * width] + x_start
-
- Moving up or down 1 row (moving from pelu2 to pelc2, for example) is done by
- incrementing or decrementing "width" elements within Rec_Y.
-
- The coordinates of the upper left hand corner of a block (not the group of
- 9 pixels depicted in the figure below) is specified by
- (y_blk_start, x_blk_start). The width and height of the block is BLKSIZE.
- (y_start,x_start) may be specified independently of (y_blk_start, x_blk_start).
-
- (y_start,x_start)
- -----------|--------------------------
- | | | | |
- | X | pelu1 | pelu2 |
- | pelu0 | | |
- | | | |
- --------------------------------------
- | | | |
- | pelc0 | pelc1 | pelc2 |
- | | | |
- | | | |
- --------------------------------------
- | | | |
- | pell0 | pell1 | pell2 |
- | | | |
- | | | |
- --------------------------------------
-
- The filtering of the luminance values is achieved by comparing the 9
- luminance values to a threshold value ("thr") and then changing the
- luminance value of pelc1 if all of the values are above or all of the values
- are below the threshold. The amount that the luminance value is changed
- depends on a weighted sum of the 9 luminance values. The position of Pelc1
- is then advanced to the right by one (as well as all of the surrounding pixels)
- and the same calculation is performed again for the luminance value of the new
- Pelc1. This continues row-wise until pixels in the last row of the block are
- filtered.
-
-
-------------------------------------------------------------------------------
- REQUIREMENTS
-
- None.
-
-------------------------------------------------------------------------------
- REFERENCES
-
- ..\corelibs\decoder\common\src\post_proc.c
-
-------------------------------------------------------------------------------
- PSEUDO-CODE
-
-------------------------------------------------------------------------------
- RESOURCES USED
- When the code is written for a specific target processor the
- the resources used should be documented below.
-
- STACK USAGE: [stack count for this module] + [variable to represent
- stack usage for each subroutine called]
-
- where: [stack usage variable] = stack usage for [subroutine
- name] (see [filename].ext)
-
- DATA MEMORY USED: x words
-
- PROGRAM MEMORY USED: x words
-
- CLOCK CYCLES: [cycle count equation for this module] + [variable
- used to represent cycle count for each subroutine
- called]
-
- where: [cycle count variable] = cycle count for [subroutine
- name] (see [filename].ext)
-
-------------------------------------------------------------------------------
-*/
-
-
-/*----------------------------------------------------------------------------
-; INCLUDES
-----------------------------------------------------------------------------*/
-#include "mp4dec_lib.h"
-#include "post_proc.h"
-#include "mp4def.h"
-
-#define OSCL_DISABLE_WARNING_CONV_POSSIBLE_LOSS_OF_DATA
-
-/*----------------------------------------------------------------------------
-; MACROS
-; Define module specific macros here
-----------------------------------------------------------------------------*/
-
-
-/*----------------------------------------------------------------------------
-; DEFINES
-; Include all pre-processor statements here. Include conditional
-; compile variables also.
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL FUNCTION DEFINITIONS
-; Function Prototype declaration
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL STORE/BUFFER/POINTER DEFINITIONS
-; Variable declaration - defined here and used outside this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL FUNCTION REFERENCES
-; Declare functions defined elsewhere and referenced in this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL GLOBAL STORE/BUFFER/POINTER REFERENCES
-; Declare variables used in this module but defined elsewhere
-----------------------------------------------------------------------------*/
-#ifdef PV_POSTPROC_ON
-/*----------------------------------------------------------------------------
-; FUNCTION CODE
-----------------------------------------------------------------------------*/
-void AdaptiveSmooth_NoMMX(
- uint8 *Rec_Y, /* i/o */
- int y_start, /* i */
- int x_start, /* i */
- int y_blk_start, /* i */
- int x_blk_start, /* i */
- int thr, /* i */
- int width, /* i */
- int max_diff /* i */
-)
-{
-
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int sign_v[15];
- int sum_v[15];
- int *sum_V_ptr;
- int *sign_V_ptr;
- uint8 pelu;
- uint8 pelc;
- uint8 pell;
- uint8 *pelp;
- uint8 oldrow[15];
- int sum;
- int sum1;
- uint8 *Rec_Y_ptr;
- int32 addr_v;
- int row_cntr;
- int col_cntr;
-
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- /* first row
- */
- addr_v = (int32)(y_start + 1) * width; /* y coord of 1st element in the row /
- /containing pelc pixel / */
- Rec_Y_ptr = &Rec_Y[addr_v + x_start]; /* initializing pointer to
- / pelc0 position */
- sum_V_ptr = &sum_v[0]; /* initializing pointer to 0th element of array
- / that will contain weighted sums of pixel
- / luminance values */
- sign_V_ptr = &sign_v[0]; /* initializing pointer to 0th element of
- / array that will contain sums that indicate
- / how many of the 9 pixels are above or below
- / the threshold value (thr) */
- pelp = &oldrow[0]; /* initializing pointer to the 0th element of array
- / that will contain current values of pelc that
- / are saved and used as values of pelu when the
- / next row of pixels are filtered */
-
- pelu = *(Rec_Y_ptr - width); /* assigning value of pelu0 to pelu */
- *pelp++ = pelc = *Rec_Y_ptr; /* assigning value of pelc0 to pelc and
- / storing this value in pelp which
- / will be used as value of pelu0 when
- / next row is filtered */
- pell = *(Rec_Y_ptr + width); /* assigning value of pell0 to pell */
- Rec_Y_ptr++; /* advancing pointer from pelc0 to pelc1 */
- *sum_V_ptr++ = pelu + (pelc << 1) + pell; /* weighted sum of pelu0,
- / pelc0 and pell0 */
- /* sum of 0's and 1's (0 if pixel value is below thr, 1 if value
- /is above thr) */
- *sign_V_ptr++ = INDEX(pelu, thr) + INDEX(pelc, thr) + INDEX(pell, thr);
-
-
- pelu = *(Rec_Y_ptr - width); /* assigning value of pelu1 to pelu */
- *pelp++ = pelc = *Rec_Y_ptr; /* assigning value of pelc1 to pelc and
- / storing this value in pelp which
- / will be used as the value of pelu1 when
- / next row is filtered */
- pell = *(Rec_Y_ptr + width); /* assigning value of pell1 to pell */
- Rec_Y_ptr++; /* advancing pointer from pelc1 to pelc2 */
- *sum_V_ptr++ = pelu + (pelc << 1) + pell; /* weighted sum of pelu1,
- / pelc1 and pell1 */
- /* sum of 0's and 1's (0 if pixel value is below thr, 1 if value
- /is above thr) */
- *sign_V_ptr++ = INDEX(pelu, thr) + INDEX(pelc, thr) + INDEX(pell, thr);
-
- /* The loop below performs the filtering for the first row of
- / pixels in the region. It steps across the remaining pixels in
- / the row and alters the luminance value of pelc1 if necessary,
- / depending on the luminance values of the adjacent pixels*/
-
- for (col_cntr = (x_blk_start + BLKSIZE - 1) - x_start; col_cntr > 0; col_cntr--)
- {
- pelu = *(Rec_Y_ptr - width); /* assigning value of pelu2 to
- / pelu */
- *pelp++ = pelc = *Rec_Y_ptr; /* assigning value of pelc2 to pelc
- / and storing this value in pelp
- / which will be used as value of pelu2
- / when next row is filtered */
- pell = *(Rec_Y_ptr + width); /* assigning value of pell2 to pell */
-
- /* weighted sum of pelu1, pelc1 and pell1 */
- *sum_V_ptr = pelu + (pelc << 1) + pell;
- /* sum of 0's and 1's (0 if pixel value is below thr,
- /1 if value is above thr) */
- *sign_V_ptr = INDEX(pelu, thr) + INDEX(pelc, thr) +
- INDEX(pell, thr);
- /* the value of sum1 indicates how many of the 9 pixels'
- /luminance values are above or equal to thr */
- sum1 = *(sign_V_ptr - 2) + *(sign_V_ptr - 1) + *sign_V_ptr;
-
- /* alter the luminance value of pelc1 if all 9 luminance values
- /are above or equal to thr or if all 9 values are below thr */
- if (sum1 == 0 || sum1 == 9)
- {
- /* sum is a weighted average of the 9 pixel luminance
- /values */
- sum = (*(sum_V_ptr - 2) + (*(sum_V_ptr - 1) << 1) +
- *sum_V_ptr + 8) >> 4;
-
- Rec_Y_ptr--; /* move pointer back to pelc1 */
- /* If luminance value of pelc1 is larger than
- / sum by more than max_diff, then subract max_diff
- / from luminance value of pelc1*/
- if ((int)(*Rec_Y_ptr - sum) > max_diff)
- {
- sum = *Rec_Y_ptr - max_diff;
- }
- /* If luminance value of pelc1 is smaller than
- / sum by more than max_diff, then add max_diff
- / to luminance value of pelc1*/
- else if ((int)(*Rec_Y_ptr - sum) < -max_diff)
- {
- sum = *Rec_Y_ptr + max_diff;
- }
- *Rec_Y_ptr++ = sum; /* assign value of sum to pelc1
- and advance pointer to pelc2 */
- }
- Rec_Y_ptr++; /* advance pointer to new value of pelc2
- / old pelc2 is now treated as pelc1*/
- sum_V_ptr++; /* pointer is advanced so next weighted sum may
- / be saved */
- sign_V_ptr++; /* pointer is advanced so next sum of 0's and
- / 1's may be saved */
- }
-
- /* The nested loops below perform the filtering for the remaining rows */
-
- addr_v = (y_start + 2) * width; /* advance addr_v to the next row
- / (corresponding to pell0)*/
- /* The outer loop steps throught the rows. */
- for (row_cntr = (y_blk_start + BLKSIZE) - (y_start + 2); row_cntr > 0; row_cntr--)
- {
- Rec_Y_ptr = &Rec_Y[addr_v + x_start]; /* advance pointer to
- /the old pell0, which has become the new pelc0 */
- addr_v += width; /* move addr_v down 1 row */
- sum_V_ptr = &sum_v[0]; /* re-initializing pointer */
- sign_V_ptr = &sign_v[0]; /* re-initilaizing pointer */
- pelp = &oldrow[0]; /* re-initializing pointer */
-
- pelu = *pelp; /* setting pelu0 to old value of pelc0 */
- *pelp++ = pelc = *Rec_Y_ptr;
- pell = *(Rec_Y_ptr + width);
- Rec_Y_ptr++;
- *sum_V_ptr++ = pelu + (pelc << 1) + pell;
- *sign_V_ptr++ = INDEX(pelu, thr) + INDEX(pelc, thr) +
- INDEX(pell, thr);
-
- pelu = *pelp; /* setting pelu1 to old value of pelc1 */
- *pelp++ = pelc = *Rec_Y_ptr;
- pell = *(Rec_Y_ptr + width);
- Rec_Y_ptr++;
- *sum_V_ptr++ = pelu + (pelc << 1) + pell;
- *sign_V_ptr++ = INDEX(pelu, thr) + INDEX(pelc, thr) +
- INDEX(pell, thr);
- /* The inner loop steps through the columns */
- for (col_cntr = (x_blk_start + BLKSIZE - 1) - x_start; col_cntr > 0; col_cntr--)
- {
- pelu = *pelp; /* setting pelu2 to old value of pelc2 */
- *pelp++ = pelc = *Rec_Y_ptr;
- pell = *(Rec_Y_ptr + width);
-
- *sum_V_ptr = pelu + (pelc << 1) + pell;
- *sign_V_ptr = INDEX(pelu, thr) + INDEX(pelc, thr) +
- INDEX(pell, thr);
-
- sum1 = *(sign_V_ptr - 2) + *(sign_V_ptr - 1) + *sign_V_ptr;
- /* the "if" statement below is the same as the one in
- / the first loop */
- if (sum1 == 0 || sum1 == 9)
- {
- sum = (*(sum_V_ptr - 2) + (*(sum_V_ptr - 1) << 1) +
- *sum_V_ptr + 8) >> 4;
-
- Rec_Y_ptr--;
- if ((int)(*Rec_Y_ptr - sum) > max_diff)
- {
- sum = *Rec_Y_ptr - max_diff;
- }
- else if ((int)(*Rec_Y_ptr - sum) < -max_diff)
- {
- sum = *Rec_Y_ptr + max_diff;
- }
- *Rec_Y_ptr++ = (uint8) sum;
- }
- Rec_Y_ptr++;
- sum_V_ptr++;
- sign_V_ptr++;
- }
- }
-
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return;
-}
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/chv_filter.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/chv_filter.cpp
deleted file mode 100644
index 6593b48..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/chv_filter.cpp
+++ /dev/null
@@ -1,654 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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.
- * -------------------------------------------------------------------
- */
-/*
-------------------------------------------------------------------------------
- INPUT AND OUTPUT DEFINITIONS
-
- Inputs:
- [input_variable_name] = [description of the input to module, its type
- definition, and length (when applicable)]
-
- Local Stores/Buffers/Pointers Needed:
- [local_store_name] = [description of the local store, its type
- definition, and length (when applicable)]
- [local_buffer_name] = [description of the local buffer, its type
- definition, and length (when applicable)]
- [local_ptr_name] = [description of the local pointer, its type
- definition, and length (when applicable)]
-
- Global Stores/Buffers/Pointers Needed:
- [global_store_name] = [description of the global store, its type
- definition, and length (when applicable)]
- [global_buffer_name] = [description of the global buffer, its type
- definition, and length (when applicable)]
- [global_ptr_name] = [description of the global pointer, its type
- definition, and length (when applicable)]
-
- Outputs:
- [return_variable_name] = [description of data/pointer returned
- by module, its type definition, and length
- (when applicable)]
-
- Pointers and Buffers Modified:
- [variable_bfr_ptr] points to the [describe where the
- variable_bfr_ptr points to, its type definition, and length
- (when applicable)]
- [variable_bfr] contents are [describe the new contents of
- variable_bfr]
-
- Local Stores Modified:
- [local_store_name] = [describe new contents, its type
- definition, and length (when applicable)]
-
- Global Stores Modified:
- [global_store_name] = [describe new contents, its type
- definition, and length (when applicable)]
-
-------------------------------------------------------------------------------
- FUNCTION DESCRIPTION
-
- For fast Deblock filtering
- Newer version (macroblock based processing)
-
-------------------------------------------------------------------------------
- REQUIREMENTS
-
- [List requirements to be satisfied by this module.]
-
-------------------------------------------------------------------------------
- REFERENCES
-
- [List all references used in designing this module.]
-
-------------------------------------------------------------------------------
- PSEUDO-CODE
-
-------------------------------------------------------------------------------
- RESOURCES USED
- When the code is written for a specific target processor the
- the resources used should be documented below.
-
- STACK USAGE: [stack count for this module] + [variable to represent
- stack usage for each subroutine called]
-
- where: [stack usage variable] = stack usage for [subroutine
- name] (see [filename].ext)
-
- DATA MEMORY USED: x words
-
- PROGRAM MEMORY USED: x words
-
- CLOCK CYCLES: [cycle count equation for this module] + [variable
- used to represent cycle count for each subroutine
- called]
-
- where: [cycle count variable] = cycle count for [subroutine
- name] (see [filename].ext)
-
-------------------------------------------------------------------------------
-*/
-
-
-/*----------------------------------------------------------------------------
-; INCLUDES
-----------------------------------------------------------------------------*/
-#include "mp4dec_lib.h"
-#include "post_proc.h"
-
-#define OSCL_DISABLE_WARNING_CONV_POSSIBLE_LOSS_OF_DATA
-
-/*----------------------------------------------------------------------------
-; MACROS
-; Define module specific macros here
-----------------------------------------------------------------------------*/
-//#define FILTER_LEN_8
-
-/*----------------------------------------------------------------------------
-; DEFINES
-; Include all pre-processor statements here. Include conditional
-; compile variables also.
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL FUNCTION DEFINITIONS
-; Function Prototype declaration
-
-----------------------------------------------------------------------------
-; LOCAL STORE/BUFFER/POINTER DEFINITIONS
-; Variable declaration - defined here and used outside this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL FUNCTION REFERENCES
-; Declare functions defined elsewhere and referenced in this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL GLOBAL STORE/BUFFER/POINTER REFERENCES
-; Declare variables used in this module but defined elsewhere
-----------------------------------------------------------------------------*/
-#ifdef PV_POSTPROC_ON
-
-/*************************************************************************
- Function prototype : void CombinedHorzVertFilter( uint8 *rec,
- int width,
- int height,
- int *QP_store,
- int chr,
- uint8 *pp_mod)
- Parameters :
- rec : pointer to the decoded frame buffer.
- width : width of decoded frame.
- height : height of decoded frame
- QP_store: pointer to the array of QP corresponding to the decoded frame.
- It had only one value for each MB.
- chr : luma or color indication
- == 0 luma
- == 1 color
- pp_mod : The semphore used for deblocking
-
- Remark : The function do the deblocking on decoded frames.
- First based on the semaphore info., it is divided into hard and soft filtering.
- To differentiate real and fake edge, it then check the difference with QP to
- decide whether to do the filtering or not.
-
-*************************************************************************/
-
-
-/*----------------------------------------------------------------------------
-; FUNCTION CODE
-----------------------------------------------------------------------------*/
-void CombinedHorzVertFilter(
- uint8 *rec,
- int width,
- int height,
- int16 *QP_store,
- int chr,
- uint8 *pp_mod)
-{
-
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int br, bc, mbr, mbc;
- int QP = 1;
- uint8 *ptr, *ptr_e;
- int pp_w, pp_h;
- int brwidth;
-
- int jVal0, jVal1, jVal2;
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- pp_w = (width >> 3);
- pp_h = (height >> 3);
-
- for (mbr = 0; mbr < pp_h; mbr += 2) /* row of blocks */
- {
- brwidth = mbr * pp_w; /* number of blocks above current block row */
- for (mbc = 0; mbc < pp_w; mbc += 2) /* col of blocks */
- {
- if (!chr)
- QP = QP_store[(brwidth>>2) + (mbc>>1)]; /* QP is per MB based value */
-
- /********* for each block **************/
- /****************** Horiz. Filtering ********************/
- for (br = mbr + 1; br < mbr + 3; br++) /* 2x2 blocks */
- {
- brwidth += pp_w; /* number of blocks above & left current block row */
- /* the profile on ARM920T shows separate these two boundary check is faster than combine them */
- if (br < pp_h) /* boundary : don't do it on the lowest row block */
- for (bc = mbc; bc < mbc + 2; bc++)
- {
- /****** check boundary for deblocking ************/
- if (bc < pp_w) /* boundary : don't do it on the most right col block */
- {
- ptr = rec + (brwidth << 6) + (bc << 3);
- jVal0 = brwidth + bc;
- if (chr) QP = QP_store[jVal0];
-
- ptr_e = ptr + 8; /* pointer to where the loop ends */
-
- if (((pp_mod[jVal0]&0x02)) && ((pp_mod[jVal0-pp_w]&0x02)))
- {
- /* Horiz Hard filter */
- do
- {
- jVal0 = *(ptr - width); /* C */
- jVal1 = *ptr; /* D */
- jVal2 = jVal1 - jVal0;
-
- if (((jVal2 > 0) && (jVal2 < (QP << 1)))
- || ((jVal2 < 0) && (jVal2 > -(QP << 1)))) /* (D-C) compared with 2QP */
- {
- /* differentiate between real and fake edge */
- jVal0 = ((jVal0 + jVal1) >> 1); /* (D+C)/2 */
- *(ptr - width) = (uint8)(jVal0); /* C */
- *ptr = (uint8)(jVal0); /* D */
-
- jVal0 = *(ptr - (width << 1)); /* B */
- jVal1 = *(ptr + width); /* E */
- jVal2 = jVal1 - jVal0; /* E-B */
-
- if (jVal2 > 0)
- {
- jVal0 += ((jVal2 + 3) >> 2);
- jVal1 -= ((jVal2 + 3) >> 2);
- *(ptr - (width << 1)) = (uint8)jVal0; /* store B */
- *(ptr + width) = (uint8)jVal1; /* store E */
- }
- else if (jVal2)
- {
- jVal0 -= ((3 - jVal2) >> 2);
- jVal1 += ((3 - jVal2) >> 2);
- *(ptr - (width << 1)) = (uint8)jVal0; /* store B */
- *(ptr + width) = (uint8)jVal1; /* store E */
- }
-
- jVal0 = *(ptr - (width << 1) - width); /* A */
- jVal1 = *(ptr + (width << 1)); /* F */
- jVal2 = jVal1 - jVal0; /* (F-A) */
-
- if (jVal2 > 0)
- {
- jVal0 += ((jVal2 + 7) >> 3);
- jVal1 -= ((jVal2 + 7) >> 3);
- *(ptr - (width << 1) - width) = (uint8)(jVal0);
- *(ptr + (width << 1)) = (uint8)(jVal1);
- }
- else if (jVal2)
- {
- jVal0 -= ((7 - jVal2) >> 3);
- jVal1 += ((7 - jVal2) >> 3);
- *(ptr - (width << 1) - width) = (uint8)(jVal0);
- *(ptr + (width << 1)) = (uint8)(jVal1);
- }
- }/* a3_0 > 2QP */
- }
- while (++ptr < ptr_e);
- }
- else /* Horiz soft filter*/
- {
- do
- {
- jVal0 = *(ptr - width); /* B */
- jVal1 = *ptr; /* C */
- jVal2 = jVal1 - jVal0; /* C-B */
-
- if (((jVal2 > 0) && (jVal2 < (QP)))
- || ((jVal2 < 0) && (jVal2 > -(QP)))) /* (C-B) compared with QP */
- {
-
- jVal0 = ((jVal0 + jVal1) >> 1); /* (B+C)/2 cannot overflow; ceil() */
- *(ptr - width) = (uint8)(jVal0); /* B = (B+C)/2 */
- *ptr = (uint8)jVal0; /* C = (B+C)/2 */
-
- jVal0 = *(ptr - (width << 1)); /* A */
- jVal1 = *(ptr + width); /* D */
- jVal2 = jVal1 - jVal0; /* D-A */
-
-
- if (jVal2 > 0)
- {
- jVal1 -= ((jVal2 + 7) >> 3);
- jVal0 += ((jVal2 + 7) >> 3);
- *(ptr - (width << 1)) = (uint8)jVal0; /* A */
- *(ptr + width) = (uint8)jVal1; /* D */
- }
- else if (jVal2)
- {
- jVal1 += ((7 - jVal2) >> 3);
- jVal0 -= ((7 - jVal2) >> 3);
- *(ptr - (width << 1)) = (uint8)jVal0; /* A */
- *(ptr + width) = (uint8)jVal1; /* D */
- }
- }
- }
- while (++ptr < ptr_e);
- } /* Soft filter*/
- }/* boundary checking*/
- }/*bc*/
- }/*br*/
- brwidth -= (pp_w << 1);
- /****************** Vert. Filtering ********************/
- for (br = mbr; br < mbr + 2; br++)
- {
- if (br < pp_h)
- for (bc = mbc + 1; bc < mbc + 3; bc++)
- {
- /****** check boundary for deblocking ************/
- if (bc < pp_w)
- {
- ptr = rec + (brwidth << 6) + (bc << 3);
- jVal0 = brwidth + bc;
- if (chr) QP = QP_store[jVal0];
-
- ptr_e = ptr + (width << 3);
-
- if (((pp_mod[jVal0-1]&0x01)) && ((pp_mod[jVal0]&0x01)))
- {
- /* Vert Hard filter */
- do
- {
- jVal1 = *ptr; /* D */
- jVal0 = *(ptr - 1); /* C */
- jVal2 = jVal1 - jVal0; /* D-C */
-
- if (((jVal2 > 0) && (jVal2 < (QP << 1)))
- || ((jVal2 < 0) && (jVal2 > -(QP << 1))))
- {
- jVal1 = (jVal0 + jVal1) >> 1; /* (C+D)/2 */
- *ptr = jVal1;
- *(ptr - 1) = jVal1;
-
- jVal1 = *(ptr + 1); /* E */
- jVal0 = *(ptr - 2); /* B */
- jVal2 = jVal1 - jVal0; /* E-B */
-
- if (jVal2 > 0)
- {
- jVal1 -= ((jVal2 + 3) >> 2); /* E = E -(E-B)/4 */
- jVal0 += ((jVal2 + 3) >> 2); /* B = B +(E-B)/4 */
- *(ptr + 1) = jVal1;
- *(ptr - 2) = jVal0;
- }
- else if (jVal2)
- {
- jVal1 += ((3 - jVal2) >> 2); /* E = E -(E-B)/4 */
- jVal0 -= ((3 - jVal2) >> 2); /* B = B +(E-B)/4 */
- *(ptr + 1) = jVal1;
- *(ptr - 2) = jVal0;
- }
-
- jVal1 = *(ptr + 2); /* F */
- jVal0 = *(ptr - 3); /* A */
-
- jVal2 = jVal1 - jVal0; /* (F-A) */
-
- if (jVal2 > 0)
- {
- jVal1 -= ((jVal2 + 7) >> 3); /* F -= (F-A)/8 */
- jVal0 += ((jVal2 + 7) >> 3); /* A += (F-A)/8 */
- *(ptr + 2) = jVal1;
- *(ptr - 3) = jVal0;
- }
- else if (jVal2)
- {
- jVal1 -= ((jVal2 - 7) >> 3); /* F -= (F-A)/8 */
- jVal0 += ((jVal2 - 7) >> 3); /* A += (F-A)/8 */
- *(ptr + 2) = jVal1;
- *(ptr - 3) = jVal0;
- }
- } /* end of ver hard filetering */
- }
- while ((ptr += width) < ptr_e);
- }
- else /* Vert soft filter*/
- {
- do
- {
- jVal1 = *ptr; /* C */
- jVal0 = *(ptr - 1); /* B */
- jVal2 = jVal1 - jVal0;
-
- if (((jVal2 > 0) && (jVal2 < (QP)))
- || ((jVal2 < 0) && (jVal2 > -(QP))))
- {
-
- jVal1 = (jVal0 + jVal1 + 1) >> 1;
- *ptr = jVal1; /* C */
- *(ptr - 1) = jVal1; /* B */
-
- jVal1 = *(ptr + 1); /* D */
- jVal0 = *(ptr - 2); /* A */
- jVal2 = (jVal1 - jVal0); /* D- A */
-
- if (jVal2 > 0)
- {
- jVal1 -= (((jVal2) + 7) >> 3); /* D -= (D-A)/8 */
- jVal0 += (((jVal2) + 7) >> 3); /* A += (D-A)/8 */
- *(ptr + 1) = jVal1;
- *(ptr - 2) = jVal0;
-
- }
- else if (jVal2)
- {
- jVal1 += ((7 - (jVal2)) >> 3); /* D -= (D-A)/8 */
- jVal0 -= ((7 - (jVal2)) >> 3); /* A += (D-A)/8 */
- *(ptr + 1) = jVal1;
- *(ptr - 2) = jVal0;
- }
- }
- }
- while ((ptr += width) < ptr_e);
- } /* Soft filter*/
- } /* boundary*/
- } /*bc*/
- brwidth += pp_w;
- }/*br*/
- brwidth -= (pp_w << 1);
- }/*mbc*/
- brwidth += (pp_w << 1);
- }/*mbr*/
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return;
-}
-void CombinedHorzVertFilter_NoSoftDeblocking(
- uint8 *rec,
- int width,
- int height,
- int16 *QP_store,
- int chr,
- uint8 *pp_mod)
-{
-
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int br, bc, mbr, mbc;
- int QP = 1;
- uint8 *ptr, *ptr_e;
- int pp_w, pp_h;
- int brwidth;
-
- int jVal0, jVal1, jVal2;
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- pp_w = (width >> 3);
- pp_h = (height >> 3);
-
- for (mbr = 0; mbr < pp_h; mbr += 2) /* row of blocks */
- {
- brwidth = mbr * pp_w; /* number of blocks above current block row */
- for (mbc = 0; mbc < pp_w; mbc += 2) /* col of blocks */
- {
- if (!chr)
- QP = QP_store[(brwidth>>2) + (mbc>>1)]; /* QP is per MB based value */
-
- /********* for each block **************/
- /****************** Horiz. Filtering ********************/
- for (br = mbr + 1; br < mbr + 3; br++) /* 2x2 blocks */
- {
- brwidth += pp_w; /* number of blocks above & left current block row */
- /* the profile on ARM920T shows separate these two boundary check is faster than combine them */
- if (br < pp_h) /* boundary : don't do it on the lowest row block */
- for (bc = mbc; bc < mbc + 2; bc++)
- {
- /****** check boundary for deblocking ************/
- if (bc < pp_w) /* boundary : don't do it on the most right col block */
- {
- ptr = rec + (brwidth << 6) + (bc << 3);
- jVal0 = brwidth + bc;
- if (chr) QP = QP_store[jVal0];
-
- ptr_e = ptr + 8; /* pointer to where the loop ends */
-
- if (((pp_mod[jVal0]&0x02)) && ((pp_mod[jVal0-pp_w]&0x02)))
- {
- /* Horiz Hard filter */
- do
- {
- jVal0 = *(ptr - width); /* C */
- jVal1 = *ptr; /* D */
- jVal2 = jVal1 - jVal0;
-
- if (((jVal2 > 0) && (jVal2 < (QP << 1)))
- || ((jVal2 < 0) && (jVal2 > -(QP << 1)))) /* (D-C) compared with 2QP */
- {
- /* differentiate between real and fake edge */
- jVal0 = ((jVal0 + jVal1) >> 1); /* (D+C)/2 */
- *(ptr - width) = (uint8)(jVal0); /* C */
- *ptr = (uint8)(jVal0); /* D */
-
- jVal0 = *(ptr - (width << 1)); /* B */
- jVal1 = *(ptr + width); /* E */
- jVal2 = jVal1 - jVal0; /* E-B */
-
- if (jVal2 > 0)
- {
- jVal0 += ((jVal2 + 3) >> 2);
- jVal1 -= ((jVal2 + 3) >> 2);
- *(ptr - (width << 1)) = (uint8)jVal0; /* store B */
- *(ptr + width) = (uint8)jVal1; /* store E */
- }
- else if (jVal2)
- {
- jVal0 -= ((3 - jVal2) >> 2);
- jVal1 += ((3 - jVal2) >> 2);
- *(ptr - (width << 1)) = (uint8)jVal0; /* store B */
- *(ptr + width) = (uint8)jVal1; /* store E */
- }
-
- jVal0 = *(ptr - (width << 1) - width); /* A */
- jVal1 = *(ptr + (width << 1)); /* F */
- jVal2 = jVal1 - jVal0; /* (F-A) */
-
- if (jVal2 > 0)
- {
- jVal0 += ((jVal2 + 7) >> 3);
- jVal1 -= ((jVal2 + 7) >> 3);
- *(ptr - (width << 1) - width) = (uint8)(jVal0);
- *(ptr + (width << 1)) = (uint8)(jVal1);
- }
- else if (jVal2)
- {
- jVal0 -= ((7 - jVal2) >> 3);
- jVal1 += ((7 - jVal2) >> 3);
- *(ptr - (width << 1) - width) = (uint8)(jVal0);
- *(ptr + (width << 1)) = (uint8)(jVal1);
- }
- }/* a3_0 > 2QP */
- }
- while (++ptr < ptr_e);
- }
-
- }/* boundary checking*/
- }/*bc*/
- }/*br*/
- brwidth -= (pp_w << 1);
- /****************** Vert. Filtering ********************/
- for (br = mbr; br < mbr + 2; br++)
- {
- if (br < pp_h)
- for (bc = mbc + 1; bc < mbc + 3; bc++)
- {
- /****** check boundary for deblocking ************/
- if (bc < pp_w)
- {
- ptr = rec + (brwidth << 6) + (bc << 3);
- jVal0 = brwidth + bc;
- if (chr) QP = QP_store[jVal0];
-
- ptr_e = ptr + (width << 3);
-
- if (((pp_mod[jVal0-1]&0x01)) && ((pp_mod[jVal0]&0x01)))
- {
- /* Vert Hard filter */
- do
- {
- jVal1 = *ptr; /* D */
- jVal0 = *(ptr - 1); /* C */
- jVal2 = jVal1 - jVal0; /* D-C */
-
- if (((jVal2 > 0) && (jVal2 < (QP << 1)))
- || ((jVal2 < 0) && (jVal2 > -(QP << 1))))
- {
- jVal1 = (jVal0 + jVal1) >> 1; /* (C+D)/2 */
- *ptr = jVal1;
- *(ptr - 1) = jVal1;
-
- jVal1 = *(ptr + 1); /* E */
- jVal0 = *(ptr - 2); /* B */
- jVal2 = jVal1 - jVal0; /* E-B */
-
- if (jVal2 > 0)
- {
- jVal1 -= ((jVal2 + 3) >> 2); /* E = E -(E-B)/4 */
- jVal0 += ((jVal2 + 3) >> 2); /* B = B +(E-B)/4 */
- *(ptr + 1) = jVal1;
- *(ptr - 2) = jVal0;
- }
- else if (jVal2)
- {
- jVal1 += ((3 - jVal2) >> 2); /* E = E -(E-B)/4 */
- jVal0 -= ((3 - jVal2) >> 2); /* B = B +(E-B)/4 */
- *(ptr + 1) = jVal1;
- *(ptr - 2) = jVal0;
- }
-
- jVal1 = *(ptr + 2); /* F */
- jVal0 = *(ptr - 3); /* A */
-
- jVal2 = jVal1 - jVal0; /* (F-A) */
-
- if (jVal2 > 0)
- {
- jVal1 -= ((jVal2 + 7) >> 3); /* F -= (F-A)/8 */
- jVal0 += ((jVal2 + 7) >> 3); /* A += (F-A)/8 */
- *(ptr + 2) = jVal1;
- *(ptr - 3) = jVal0;
- }
- else if (jVal2)
- {
- jVal1 -= ((jVal2 - 7) >> 3); /* F -= (F-A)/8 */
- jVal0 += ((jVal2 - 7) >> 3); /* A += (F-A)/8 */
- *(ptr + 2) = jVal1;
- *(ptr - 3) = jVal0;
- }
- } /* end of ver hard filetering */
- }
- while ((ptr += width) < ptr_e);
- }
-
- } /* boundary*/
- } /*bc*/
- brwidth += pp_w;
- }/*br*/
- brwidth -= (pp_w << 1);
- }/*mbc*/
- brwidth += (pp_w << 1);
- }/*mbr*/
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return;
-}
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/chvr_filter.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/chvr_filter.cpp
deleted file mode 100644
index 795cf71..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/chvr_filter.cpp
+++ /dev/null
@@ -1,565 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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 "mp4dec_lib.h"
-#include "post_proc.h"
-
-#ifdef PV_POSTPROC_ON
-
-void CombinedHorzVertRingFilter(
- uint8 *rec,
- int width,
- int height,
- int16 *QP_store,
- int chr,
- uint8 *pp_mod)
-{
-
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int index, counter;
- int br, bc, incr, mbr, mbc;
- int QP = 1;
- int v[5];
- uint8 *ptr, *ptr_c, *ptr_n;
- int w1, w2, w3, w4;
- int pp_w, pp_h, brwidth;
- int sum, delta;
- int a3_0, a3_1, a3_2, A3_0;
- /* for Deringing Threshold approach (MPEG4)*/
- int max_diff, thres, v0, h0, min_blk, max_blk;
- int cnthflag;
-
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- /* Calculate the width and height of the area in blocks (divide by 8) */
- pp_w = (width >> 3);
- pp_h = (height >> 3);
-
- /* Set up various values needed for updating pointers into rec */
- w1 = width; /* Offset to next row in pixels */
- w2 = width << 1; /* Offset to two rows in pixels */
- w3 = w1 + w2; /* Offset to three rows in pixels */
- w4 = w2 << 1; /* Offset to four rows in pixels */
- incr = width - BLKSIZE; /* Offset to next row after processing block */
-
- /* Work through the area hortizontally by two rows per step */
- for (mbr = 0; mbr < pp_h; mbr += 2)
- {
- /* brwidth contains the block number of the leftmost block
- * of the current row */
- brwidth = mbr * pp_w;
-
- /* Work through the area vertically by two columns per step */
- for (mbc = 0; mbc < pp_w; mbc += 2)
- {
- /* if the data is luminance info, get the correct
- * quantization paramenter. One parameter per macroblock */
- if (!chr)
- {
- /* brwidth/4 is the macroblock number and mbc/2 is the macroblock col number*/
- QP = QP_store[(brwidth>>2) + (mbc>>1)];
- }
-
- /****************** Horiz. Filtering ********************/
- /* Process four blocks for the filtering */
- /********************************************************/
- /* Loop over two rows of blocks */
- for (br = mbr + 1; br < mbr + 3; br++) /* br is the row counter in blocks */
- {
- /* Set brwidth to the first (leftmost) block number of the next row */
- /* brwidth is used as an index when counting blocks */
- brwidth += pp_w;
-
- /* Loop over two columns of blocks in the row */
- for (bc = mbc; bc < mbc + 2; bc++) /* bc is the column counter in blocks */
- {
- /****** check boundary for deblocking ************/
- /* Execute if the row and column counters are within the area */
- if (br < pp_h && bc < pp_w)
- {
- /* Set the ptr to the first pixel of the first block of the second row
- * brwidth * 64 is the pixel row offset
- * bc * 8 is the pixel column offset */
- ptr = rec + (brwidth << 6) + (bc << 3);
-
- /* Set the index to the current block of the second row counting in blocks */
- index = brwidth + bc;
-
- /* if the data is chrominance info, get the correct
- * quantization paramenter. One parameter per block. */
- if (chr)
- {
- QP = QP_store[index];
- }
-
- /* Execute hard horizontal filter if semaphore for horizontal deblocking
- * is set for the current block and block immediately above it */
- if (((pp_mod[index]&0x02) != 0) && ((pp_mod[index-pp_w]&0x02) != 0))
- { /* Hard filter */
-
- /* Set HorzHflag (bit 4) in the pp_mod location */
- pp_mod[index-pp_w] |= 0x10; /* 4/26/00 reuse pp_mod for HorzHflag*/
-
- /* Filter across the 8 pixels of the block */
- for (index = BLKSIZE; index > 0; index--)
- {
- /* Difference between the current pixel and the pixel above it */
- a3_0 = *ptr - *(ptr - w1);
-
- /* if the magnitude of the difference is greater than the KThH threshold
- * and within the quantization parameter, apply hard filter */
- if ((a3_0 > KThH || a3_0 < -KThH) && a3_0<QP && a3_0> -QP)
- {
- ptr_c = ptr - w3; /* Points to pixel three rows above */
- ptr_n = ptr + w1; /* Points to pixel one row below */
- v[0] = (int)(*(ptr_c - w3));
- v[1] = (int)(*(ptr_c - w2));
- v[2] = (int)(*(ptr_c - w1));
- v[3] = (int)(*ptr_c);
- v[4] = (int)(*(ptr_c + w1));
-
- sum = v[0]
- + v[1]
- + v[2]
- + *ptr_c
- + v[4]
- + (*(ptr_c + w2))
- + (*(ptr_c + w3)); /* Current pixel */
-
- delta = (sum + *ptr_c + 4) >> 3; /* Average pixel values with rounding */
- *(ptr_c) = (uint8) delta;
-
- /* Move pointer down one row of pixels (points to pixel two rows
- * above current pixel) */
- ptr_c += w1;
-
- for (counter = 0; counter < 5; counter++)
- {
- /* Subtract off highest pixel and add in pixel below */
- sum = sum - v[counter] + *ptr_n;
- /* Average the pixel values with rounding */
- delta = (sum + *ptr_c + 4) >> 3;
- *ptr_c = (uint8)(delta);
-
- /* Increment pointers to next pixel row */
- ptr_c += w1;
- ptr_n += w1;
- }
- }
- /* Increment pointer to next pixel */
- ++ptr;
- } /* index*/
- }
- else
- { /* soft filter*/
-
- /* Clear HorzHflag (bit 4) in the pp_mod location */
- pp_mod[index-pp_w] &= 0xef; /* reset 1110,1111 */
-
- for (index = BLKSIZE; index > 0; index--)
- {
- /* Difference between the current pixel and the pixel above it */
- a3_0 = *(ptr) - *(ptr - w1);
-
- /* if the magnitude of the difference is greater than the KTh threshold,
- * apply soft filter */
- if ((a3_0 > KTh || a3_0 < -KTh))
- {
-
- /* Sum of weighted differences */
- a3_0 += ((*(ptr - w2) - *(ptr + w1)) << 1) + (a3_0 << 2);
-
- /* Check if sum is less than the quantization parameter */
- if (PV_ABS(a3_0) < (QP << 3))
- {
- a3_1 = *(ptr - w2) - *(ptr - w3);
- a3_1 += ((*(ptr - w4) - *(ptr - w1)) << 1) + (a3_1 << 2);
-
- a3_2 = *(ptr + w2) - *(ptr + w1);
- a3_2 += ((*(ptr) - *(ptr + w3)) << 1) + (a3_2 << 2);
-
- A3_0 = PV_ABS(a3_0) - PV_MIN(PV_ABS(a3_1), PV_ABS(a3_2));
-
- if (A3_0 > 0)
- {
- A3_0 += A3_0 << 2;
- A3_0 = (A3_0 + 32) >> 6;
- if (a3_0 > 0)
- {
- A3_0 = -A3_0;
- }
-
- delta = (*(ptr - w1) - *(ptr)) >> 1;
- if (delta >= 0)
- {
- if (delta >= A3_0)
- {
- delta = PV_MAX(A3_0, 0);
- }
- }
- else
- {
- if (A3_0 > 0)
- {
- delta = 0;
- }
- else
- {
- delta = PV_MAX(A3_0, delta);
- }
- }
-
- *(ptr - w1) = (uint8)(*(ptr - w1) - delta);
- *(ptr) = (uint8)(*(ptr) + delta);
- }
- } /*threshold*/
- }
- /* Increment pointer to next pixel */
- ++ptr;
- } /*index*/
- } /* Soft filter*/
- }/* boundary checking*/
- }/*bc*/
- }/*br*/
- brwidth -= (pp_w << 1);
-
-
- /****************** Vert. Filtering *********************/
- /* Process four blocks for the filtering */
- /********************************************************/
- /* Loop over two rows of blocks */
- for (br = mbr; br < mbr + 2; br++) /* br is the row counter in blocks */
- {
- for (bc = mbc + 1; bc < mbc + 3; bc++) /* bc is the column counter in blocks */
- {
- /****** check boundary for deblocking ************/
- /* Execute if the row and column counters are within the area */
- if (br < pp_h && bc < pp_w)
- {
- /* Set the ptr to the first pixel of the first block of the second row
- * brwidth * 64 is the pixel row offset
- * bc * 8 is the pixel column offset */
- ptr = rec + (brwidth << 6) + (bc << 3);
-
- /* Set the index to the current block of the second row counting in blocks */
- index = brwidth + bc;
-
- /* if the data is chrominance info, get the correct
- * quantization paramenter. One parameter per block. */
- if (chr)
- {
- QP = QP_store[index];
- }
-
- /* Execute hard vertical filter if semaphore for vertical deblocking
- * is set for the current block and block immediately left of it */
- if (((pp_mod[index-1]&0x01) != 0) && ((pp_mod[index]&0x01) != 0))
- { /* Hard filter */
-
- /* Set VertHflag (bit 5) in the pp_mod location of previous block*/
- pp_mod[index-1] |= 0x20; /* 4/26/00 reuse pp_mod for VertHflag*/
-
- /* Filter across the 8 pixels of the block */
- for (index = BLKSIZE; index > 0; index--)
- {
- /* Difference between the current pixel
- * and the pixel to left of it */
- a3_0 = *ptr - *(ptr - 1);
-
- /* if the magnitude of the difference is greater than the KThH threshold
- * and within the quantization parameter, apply hard filter */
- if ((a3_0 > KThH || a3_0 < -KThH) && a3_0<QP && a3_0> -QP)
- {
- ptr_c = ptr - 3;
- ptr_n = ptr + 1;
- v[0] = (int)(*(ptr_c - 3));
- v[1] = (int)(*(ptr_c - 2));
- v[2] = (int)(*(ptr_c - 1));
- v[3] = (int)(*ptr_c);
- v[4] = (int)(*(ptr_c + 1));
-
- sum = v[0]
- + v[1]
- + v[2]
- + *ptr_c
- + v[4]
- + (*(ptr_c + 2))
- + (*(ptr_c + 3));
-
- delta = (sum + *ptr_c + 4) >> 3;
- *(ptr_c) = (uint8) delta;
-
- /* Move pointer down one pixel to the right */
- ptr_c += 1;
- for (counter = 0; counter < 5; counter++)
- {
- /* Subtract off highest pixel and add in pixel below */
- sum = sum - v[counter] + *ptr_n;
- /* Average the pixel values with rounding */
- delta = (sum + *ptr_c + 4) >> 3;
- *ptr_c = (uint8)(delta);
-
- /* Increment pointers to next pixel */
- ptr_c += 1;
- ptr_n += 1;
- }
- }
- /* Increment pointers to next pixel row */
- ptr += w1;
- } /* index*/
- }
- else
- { /* soft filter*/
-
- /* Clear VertHflag (bit 5) in the pp_mod location */
- pp_mod[index-1] &= 0xdf; /* reset 1101,1111 */
- for (index = BLKSIZE; index > 0; index--)
- {
- /* Difference between the current pixel and the pixel above it */
- a3_0 = *(ptr) - *(ptr - 1);
-
- /* if the magnitude of the difference is greater than the KTh threshold,
- * apply soft filter */
- if ((a3_0 > KTh || a3_0 < -KTh))
- {
-
- /* Sum of weighted differences */
- a3_0 += ((*(ptr - 2) - *(ptr + 1)) << 1) + (a3_0 << 2);
-
- /* Check if sum is less than the quantization parameter */
- if (PV_ABS(a3_0) < (QP << 3))
- {
- a3_1 = *(ptr - 2) - *(ptr - 3);
- a3_1 += ((*(ptr - 4) - *(ptr - 1)) << 1) + (a3_1 << 2);
-
- a3_2 = *(ptr + 2) - *(ptr + 1);
- a3_2 += ((*(ptr) - *(ptr + 3)) << 1) + (a3_2 << 2);
-
- A3_0 = PV_ABS(a3_0) - PV_MIN(PV_ABS(a3_1), PV_ABS(a3_2));
-
- if (A3_0 > 0)
- {
- A3_0 += A3_0 << 2;
- A3_0 = (A3_0 + 32) >> 6;
- if (a3_0 > 0)
- {
- A3_0 = -A3_0;
- }
-
- delta = (*(ptr - 1) - *(ptr)) >> 1;
- if (delta >= 0)
- {
- if (delta >= A3_0)
- {
- delta = PV_MAX(A3_0, 0);
- }
- }
- else
- {
- if (A3_0 > 0)
- {
- delta = 0;
- }
- else
- {
- delta = PV_MAX(A3_0, delta);
- }
- }
-
- *(ptr - 1) = (uint8)(*(ptr - 1) - delta);
- *(ptr) = (uint8)(*(ptr) + delta);
- }
- } /*threshold*/
- }
- ptr += w1;
- } /*index*/
- } /* Soft filter*/
- } /* boundary*/
- } /*bc*/
- /* Increment pointer to next row of pixels */
- brwidth += pp_w;
- }/*br*/
- brwidth -= (pp_w << 1);
-
- /****************** Deringing ***************************/
- /* Process four blocks for the filtering */
- /********************************************************/
- /* Loop over two rows of blocks */
- for (br = mbr; br < mbr + 2; br++)
- {
- /* Loop over two columns of blocks in the row */
- for (bc = mbc; bc < mbc + 2; bc++)
- {
- /* Execute if the row and column counters are within the area */
- if (br < pp_h && bc < pp_w)
- {
- /* Set the index to the current block */
- index = brwidth + bc;
-
- /* Execute deringing if semaphore for deringing (bit-3 of pp_mod)
- * is set for the current block */
- if ((pp_mod[index]&0x04) != 0)
- {
- /* Don't process deringing if on an edge block */
- if (br > 0 && bc > 0 && br < pp_h - 1 && bc < pp_w - 1)
- {
- /* cnthflag = weighted average of HorzHflag of current,
- * one above, previous blocks*/
- cnthflag = ((pp_mod[index] & 0x10) +
- (pp_mod[index-pp_w] & 0x10) +
- ((pp_mod[index-1] >> 1) & 0x10) +
- ((pp_mod[index] >> 1) & 0x10)) >> 4; /* 4/26/00*/
-
- /* Do the deringing if decision flags indicate it's necessary */
- if (cnthflag < 3)
- {
- /* if the data is chrominance info, get the correct
- * quantization paramenter. One parameter per block. */
- if (chr)
- {
- QP = QP_store[index];
- }
-
- /* Set amount to change luminance if it needs to be changed
- * based on quantization parameter */
- max_diff = (QP >> 2) + 4;
-
- /* Set pointer to first pixel of current block */
- ptr = rec + (brwidth << 6) + (bc << 3);
-
- /* Find minimum and maximum value of pixel block */
- FindMaxMin(ptr, &min_blk, &max_blk, incr);
-
- /* threshold determination */
- thres = (max_blk + min_blk + 1) >> 1;
-
- /* If pixel range is greater or equal than DERING_THR, smooth the region */
- if ((max_blk - min_blk) >= DERING_THR) /*smooth 8x8 region*/
-#ifndef NoMMX
- {
- /* smooth all pixels in the block*/
- DeringAdaptiveSmoothMMX(ptr, width, thres, max_diff);
- }
-#else
- {
- /* Setup the starting point of the region to smooth */
- v0 = (br << 3) - 1;
- h0 = (bc << 3) - 1;
-
- /*smooth 8x8 region*/
- AdaptiveSmooth_NoMMX(rec, v0, h0, v0 + 1, h0 + 1, thres, width, max_diff);
- }
-#endif
- }/*cnthflag*/
- } /*dering br==1 or bc==1 (boundary block)*/
- else /* Process the boundary blocks */
- {
- /* Decide to perform deblocking based on the semaphore flags
- * of the neighboring blocks in each case. A certain number of
- * hard filtering flags have to be set in order to signal need
- * for smoothing */
- if (br > 0 && br < pp_h - 1)
- {
- if (bc > 0)
- {
- cnthflag = ((pp_mod[index-pp_w] & 0x10) +
- (pp_mod[index] & 0x10) +
- ((pp_mod[index-1] >> 1) & 0x10)) >> 4;
- }
- else
- {
- cnthflag = ((pp_mod[index] & 0x10) +
- (pp_mod[index-pp_w] & 0x10) +
- ((pp_mod[index] >> 1) & 0x10)) >> 4;
- }
- }
- else if (bc > 0 && bc < pp_w - 1)
- {
- if (br > 0)
- {
- cnthflag = ((pp_mod[index-pp_w] & 0x10) +
- ((pp_mod[index-1] >> 1) & 0x10) +
- ((pp_mod[index] >> 1) & 0x10)) >> 4;
- }
- else
- {
- cnthflag = ((pp_mod[index] & 0x10) +
- ((pp_mod[index-1] >> 1) & 0x10) +
- ((pp_mod[index] >> 1) & 0x10)) >> 4;
- }
- }
- else /* at the corner do default*/
- {
- cnthflag = 0;
- }
-
- /* Do the deringing if decision flags indicate it's necessary */
- if (cnthflag < 2)
- {
-
- /* if the data is chrominance info, get the correct
- * quantization paramenter. One parameter per block. */
- if (chr)
- {
- QP = QP_store[index];
- }
-
- /* Set amount to change luminance if it needs to be changed
- * based on quantization parameter */
- max_diff = (QP >> 2) + 4;
-
- /* Set pointer to first pixel of current block */
- ptr = rec + (brwidth << 6) + (bc << 3);
-
- /* Find minimum and maximum value of pixel block */
- FindMaxMin(ptr, &min_blk, &max_blk, incr);
-
- /* threshold determination */
- thres = (max_blk + min_blk + 1) >> 1;
-
- /* Setup the starting point of the region to smooth
- * This is going to be a 4x4 region */
- v0 = (br << 3) + 1;
- h0 = (bc << 3) + 1;
-
- /* If pixel range is greater or equal than DERING_THR, smooth the region */
- if ((max_blk - min_blk) >= DERING_THR)
- {
- /* Smooth 4x4 region */
- AdaptiveSmooth_NoMMX(rec, v0, h0, v0 - 3, h0 - 3, thres, width, max_diff);
- }
- }/*cnthflag*/
- } /* br==0, bc==0*/
- } /* dering*/
- } /*boundary condition*/
- }/*bc*/
- brwidth += pp_w;
- }/*br*/
- brwidth -= (pp_w << 1);
- }/*mbc*/
- brwidth += (pp_w << 1);
- }/*mbr*/
-
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return ;
-}
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/combined_decode.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/combined_decode.cpp
index 6499233..72cbe83 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/src/combined_decode.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/src/combined_decode.cpp
@@ -544,30 +544,12 @@
int16 DC_coeff;
PV_STATUS status;
-#ifdef PV_POSTPROC_ON
- /* post-processing */
- uint8 *pp_mod[6];
- int TotalMB = video->nTotalMB;
- int MB_in_width = video->nMBPerRow;
-#endif
int y_pos = video->mbnum_row;
int x_pos = video->mbnum_col;
int32 offset = (int32)(y_pos << 4) * width + (x_pos << 4);
/* Decode each 8-by-8 blocks. comp 0 ~ 3 are luminance blocks, 4 ~ 5 */
/* are chrominance blocks. 04/03/2000. */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- {
- /** post-processing ***/
- pp_mod[0] = video->pstprcTypCur + (y_pos << 1) * (MB_in_width << 1) + (x_pos << 1);
- pp_mod[1] = pp_mod[0] + 1;
- pp_mod[2] = pp_mod[0] + (MB_in_width << 1);
- pp_mod[3] = pp_mod[2] + 1;
- pp_mod[4] = video->pstprcTypCur + (TotalMB << 2) + mbnum;
- pp_mod[5] = pp_mod[4] + TotalMB;
- }
-#endif
/* oscl_memset(mblock->block, 0, sizeof(typeMBStore)); Aug 9,2005 */
@@ -645,10 +627,6 @@
}
no_coeff[comp] = ncoeffs[comp];
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[comp] = (uint8) PostProcSemaphore(dataBlock);
-#endif
}
MBlockIDCT(video);
}
@@ -677,20 +655,6 @@
BlockIDCT(c_comp + (comp&2)*(width << 2) + 8*(comp&1), mblock->pred_block + (comp&2)*64 + 8*(comp&1), mblock->block[comp], width, ncoeffs[comp],
mblock->bitmapcol[comp], mblock->bitmaprow[comp]);
-#ifdef PV_POSTPROC_ON
- /* for inter just test for ringing */
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[comp] = (uint8)((ncoeffs[comp] > 3) ? 4 : 0);
-#endif
- }
- else
- {
- /* no IDCT for all zeros blocks 03/28/2002 */
- /* BlockIDCT(); */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[comp] = 0;
-#endif
}
}
@@ -707,20 +671,6 @@
BlockIDCT(video->currVop->uChan + (offset >> 2) + (x_pos << 2), mblock->pred_block + 256, mblock->block[4], width >> 1, ncoeffs[4],
mblock->bitmapcol[4], mblock->bitmaprow[4]);
-#ifdef PV_POSTPROC_ON
- /* for inter just test for ringing */
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[4] = (uint8)((ncoeffs[4] > 3) ? 4 : 0);
-#endif
- }
- else
- {
- /* no IDCT for all zeros blocks 03/28/2002 */
- /* BlockIDCT(); */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[4] = 0;
-#endif
}
(*DC)[5] = mid_gray;
if (CBP & 1)
@@ -731,20 +681,6 @@
BlockIDCT(video->currVop->vChan + (offset >> 2) + (x_pos << 2), mblock->pred_block + 264, mblock->block[5], width >> 1, ncoeffs[5],
mblock->bitmapcol[5], mblock->bitmaprow[5]);
-#ifdef PV_POSTPROC_ON
- /* for inter just test for ringing */
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[5] = (uint8)((ncoeffs[5] > 3) ? 4 : 0);
-#endif
- }
- else
- {
- /* no IDCT for all zeros blocks 03/28/2002 */
- /* BlockIDCT(); */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[5] = 0;
-#endif
}
video->QPMB[mbnum] = QP; /* restore the QP values ANNEX_T*/
#else
@@ -759,20 +695,6 @@
BlockIDCT(c_comp + (comp&2)*(width << 2) + 8*(comp&1), mblock->pred_block + (comp&2)*64 + 8*(comp&1), mblock->block[comp], width, ncoeffs[comp],
mblock->bitmapcol[comp], mblock->bitmaprow[comp]);
-#ifdef PV_POSTPROC_ON
- /* for inter just test for ringing */
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[comp] = (uint8)((ncoeffs[comp] > 3) ? 4 : 0);
-#endif
- }
- else
- {
- /* no IDCT for all zeros blocks 03/28/2002 */
- /* BlockIDCT(); */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[comp] = 0;
-#endif
}
}
@@ -785,20 +707,11 @@
BlockIDCT(video->currVop->uChan + (offset >> 2) + (x_pos << 2), mblock->pred_block + 256, mblock->block[4], width >> 1, ncoeffs[4],
mblock->bitmapcol[4], mblock->bitmaprow[4]);
-#ifdef PV_POSTPROC_ON
- /* for inter just test for ringing */
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[4] = (uint8)((ncoeffs[4] > 3) ? 4 : 0);
-#endif
}
else
{
/* no IDCT for all zeros blocks 03/28/2002 */
/* BlockIDCT(); */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[4] = 0;
-#endif
}
(*DC)[5] = mid_gray;
if (CBP & 1)
@@ -809,20 +722,11 @@
BlockIDCT(video->currVop->vChan + (offset >> 2) + (x_pos << 2), mblock->pred_block + 264, mblock->block[5], width >> 1, ncoeffs[5],
mblock->bitmapcol[5], mblock->bitmaprow[5]);
-#ifdef PV_POSTPROC_ON
- /* for inter just test for ringing */
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[5] = (uint8)((ncoeffs[5] > 3) ? 4 : 0);
-#endif
}
else
{
/* no IDCT for all zeros blocks 03/28/2002 */
/* BlockIDCT(); */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[5] = 0;
-#endif
#endif // PV_ANNEX_IJKT_SUPPORT
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/datapart_decode.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/datapart_decode.cpp
index 00db04b..6071f40 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/src/datapart_decode.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/src/datapart_decode.cpp
@@ -635,29 +635,9 @@
int QP_tmp = QP;
int y_pos = video->mbnum_row;
-#ifdef PV_POSTPROC_ON
- uint8 *pp_mod[6];
- int TotalMB = video->nTotalMB;
- int MB_in_width = video->nMBPerRow;
-#endif
- /*****
- * Decoding of the 6 blocks (depending on transparent pattern)
- *****/
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- {
- /** post-processing ***/
- pp_mod[0] = video->pstprcTypCur + (y_pos << 1) * (MB_in_width << 1) + (x_pos << 1);
- pp_mod[1] = pp_mod[0] + 1;
- pp_mod[2] = pp_mod[0] + (MB_in_width << 1);
- pp_mod[3] = pp_mod[2] + 1;
- pp_mod[4] = video->pstprcTypCur + (TotalMB << 2) + mbnum;
- pp_mod[5] = pp_mod[4] + TotalMB;
- }
-#endif
/* oscl_memset(mblock->block, 0, sizeof(typeMBStore)); Aug 9,2005 */
@@ -698,10 +678,6 @@
/* modified to new semaphore for post-proc */
// Future work:: can be combined in the dequant function
// @todo Deblocking Semaphore for INTRA block
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[comp] = (uint8) PostProcSemaphore(dataBlock);
-#endif
}
MBlockIDCT(video);
}
@@ -738,10 +714,6 @@
}
/* @todo Deblocking Semaphore for INTRA block, for inter just test for ringing */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[comp] = (uint8)((ncoeffs[comp] > 3) ? 4 : 0);
-#endif
}
(*DC)[4] = mid_gray;
@@ -760,10 +732,6 @@
{
ncoeffs[4] = 0;
}
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[4] = (uint8)((ncoeffs[4] > 3) ? 4 : 0);
-#endif
(*DC)[5] = mid_gray;
if (CBP & 1)
{
@@ -780,10 +748,6 @@
{
ncoeffs[5] = 0;
}
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- *pp_mod[5] = (uint8)((ncoeffs[5] > 3) ? 4 : 0);
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/deringing_chroma.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/deringing_chroma.cpp
deleted file mode 100644
index ce779b0..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/deringing_chroma.cpp
+++ /dev/null
@@ -1,215 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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 "mp4dec_lib.h"
-#include "post_proc.h"
-
-#ifdef PV_POSTPROC_ON
-
-void Deringing_Chroma(
- uint8 *Rec_C,
- int width,
- int height,
- int16 *QP_store,
- int,
- uint8 *pp_mod
-)
-{
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int thres;
- int v_blk, h_blk;
- int max_diff;
- int v_pel, h_pel;
- int max_blk, min_blk;
- int v0, h0;
- uint8 *ptr;
- int sum, sum1, incr;
- int32 addr_v;
- int sign_v[10], sum_v[10];
- int *ptr2, *ptr3;
- uint8 pelu, pelc, pell;
- incr = width - BLKSIZE;
-
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- /* chrominance */
- /* Do the first line (7 pixels at a time => Don't use MMX)*/
- for (h_blk = 0; h_blk < width; h_blk += BLKSIZE)
- {
- max_diff = (QP_store[h_blk>>3] >> 2) + 4;
- ptr = &Rec_C[h_blk];
- max_blk = min_blk = *ptr;
- FindMaxMin(ptr, &min_blk, &max_blk, width);
- h0 = ((h_blk - 1) >= 1) ? (h_blk - 1) : 1;
-
- if (max_blk - min_blk >= 4)
- {
- thres = (max_blk + min_blk + 1) >> 1;
-
-
- for (v_pel = 1; v_pel < BLKSIZE - 1; v_pel++)
- {
- addr_v = (int32)v_pel * width;
- ptr = &Rec_C[addr_v + h0 - 1];
- ptr2 = &sum_v[0];
- ptr3 = &sign_v[0];
-
- pelu = *(ptr - width);
- pelc = *ptr;
- pell = *(ptr + width);
- ptr++;
- *ptr2++ = pelu + (pelc << 1) + pell;
- *ptr3++ = INDEX(pelu, thres) + INDEX(pelc, thres) + INDEX(pell, thres);
-
- pelu = *(ptr - width);
- pelc = *ptr;
- pell = *(ptr + width);
- ptr++;
- *ptr2++ = pelu + (pelc << 1) + pell;
- *ptr3++ = INDEX(pelu, thres) + INDEX(pelc, thres) + INDEX(pell, thres);
-
- for (h_pel = h0; h_pel < h_blk + BLKSIZE - 1; h_pel++)
- {
- pelu = *(ptr - width);
- pelc = *ptr;
- pell = *(ptr + width);
-
- *ptr2 = pelu + (pelc << 1) + pell;
- *ptr3 = INDEX(pelu, thres) + INDEX(pelc, thres) + INDEX(pell, thres);
-
- sum1 = *(ptr3 - 2) + *(ptr3 - 1) + *ptr3;
- if (sum1 == 0 || sum1 == 9)
- {
- sum = (*(ptr2 - 2) + (*(ptr2 - 1) << 1) + *ptr2 + 8) >> 4;
-
- ptr--;
- if (PV_ABS(*ptr - sum) > max_diff)
- {
- if (sum > *ptr)
- sum = *ptr + max_diff;
- else
- sum = *ptr - max_diff;
- }
- *ptr++ = (uint8) sum;
- }
- ptr++;
- ptr2++;
- ptr3++;
- }
- }
- }
- }
-
- for (v_blk = BLKSIZE; v_blk < height; v_blk += BLKSIZE)
- {
- v0 = v_blk - 1;
- /* Do the first block (pixels=7 => No MMX) */
- max_diff = (QP_store[((((int32)v_blk*width)>>3))>>3] >> 2) + 4;
- ptr = &Rec_C[(int32)v_blk * width];
- max_blk = min_blk = *ptr;
- FindMaxMin(ptr, &min_blk, &max_blk, incr);
-
- if (max_blk - min_blk >= 4)
- {
- thres = (max_blk + min_blk + 1) >> 1;
-
- for (v_pel = v0; v_pel < v_blk + BLKSIZE - 1; v_pel++)
- {
- addr_v = v_pel * width;
- ptr = &Rec_C[addr_v];
- ptr2 = &sum_v[0];
- ptr3 = &sign_v[0];
-
- pelu = *(ptr - width);
- pelc = *ptr;
- pell = *(ptr + width);
- ptr++;
- *ptr2++ = pelu + (pelc << 1) + pell;
- *ptr3++ = INDEX(pelu, thres) + INDEX(pelc, thres) + INDEX(pell, thres);
-
- pelu = *(ptr - width);
- pelc = *ptr;
- pell = *(ptr + width);
- ptr++;
- *ptr2++ = pelu + (pelc << 1) + pell;
- *ptr3++ = INDEX(pelu, thres) + INDEX(pelc, thres) + INDEX(pell, thres);
-
- for (h_pel = 1; h_pel < BLKSIZE - 1; h_pel++)
- {
- pelu = *(ptr - width);
- pelc = *ptr;
- pell = *(ptr + width);
-
- *ptr2 = pelu + (pelc << 1) + pell;
- *ptr3 = INDEX(pelu, thres) + INDEX(pelc, thres) + INDEX(pell, thres);
-
- sum1 = *(ptr3 - 2) + *(ptr3 - 1) + *ptr3;
- if (sum1 == 0 || sum1 == 9)
- {
- sum = (*(ptr2 - 2) + (*(ptr2 - 1) << 1) + *ptr2 + 8) >> 4;
-
- ptr--;
- if (PV_ABS(*ptr - sum) > max_diff)
- {
- if (sum > *ptr)
- sum = *ptr + max_diff;
- else
- sum = *ptr - max_diff;
- }
- *ptr++ = (uint8) sum;
- }
- ptr++;
- ptr2++;
- ptr3++;
- }
- }
- }
-
-
- /* Do the rest in MMX */
- for (h_blk = BLKSIZE; h_blk < width; h_blk += BLKSIZE)
- {
- if ((pp_mod[(v_blk/8)*(width/8)+h_blk/8]&0x4) != 0)
- {
- max_diff = (QP_store[((((int32)v_blk*width)>>3)+h_blk)>>3] >> 2) + 4;
- ptr = &Rec_C[(int32)v_blk * width + h_blk];
- max_blk = min_blk = *ptr;
- FindMaxMin(ptr, &min_blk, &max_blk, incr);
- h0 = h_blk - 1;
-
- if (max_blk - min_blk >= 4)
- {
- thres = (max_blk + min_blk + 1) >> 1;
-#ifdef NoMMX
- AdaptiveSmooth_NoMMX(Rec_C, v0, h0, v_blk, h_blk, thres, width, max_diff);
-#else
- DeringAdaptiveSmoothMMX(&Rec_C[(int32)v0*width+h0], width, thres, max_diff);
-#endif
- }
- }
- }
- } /* macroblock level */
-
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return;
-}
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/deringing_luma.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/deringing_luma.cpp
deleted file mode 100644
index b5574b4..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/deringing_luma.cpp
+++ /dev/null
@@ -1,231 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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 "mp4dec_lib.h"
-#include "post_proc.h"
-
-#ifdef PV_POSTPROC_ON
-
-void Deringing_Luma(
- uint8 *Rec_Y,
- int width,
- int height,
- int16 *QP_store,
- int,
- uint8 *pp_mod)
-{
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int thres[4], range[4], max_range_blk, max_thres_blk;
- int MB_V, MB_H, BLK_V, BLK_H;
- int v_blk, h_blk;
- int max_diff;
- int max_blk, min_blk;
- int v0, h0;
- uint8 *ptr;
- int thr, blks, incr;
- int mb_indx, blk_indx;
-
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- incr = width - BLKSIZE;
-
- /* Dering the first line of macro blocks */
- for (MB_H = 0; MB_H < width; MB_H += MBSIZE)
- {
- max_diff = (QP_store[(MB_H)>>4] >> 2) + 4;
-
- /* threshold determination */
- max_range_blk = max_thres_blk = 0;
- blks = 0;
-
- for (BLK_V = 0; BLK_V < MBSIZE; BLK_V += BLKSIZE)
- {
- for (BLK_H = 0; BLK_H < MBSIZE; BLK_H += BLKSIZE)
- {
- ptr = &Rec_Y[(int32)(BLK_V) * width + MB_H + BLK_H];
- FindMaxMin(ptr, &min_blk, &max_blk, incr);
-
- thres[blks] = (max_blk + min_blk + 1) >> 1;
- range[blks] = max_blk - min_blk;
-
- if (range[blks] >= max_range_blk)
- {
- max_range_blk = range[blks];
- max_thres_blk = thres[blks];
- }
- blks++;
- }
- }
-
- blks = 0;
- for (v_blk = 0; v_blk < MBSIZE; v_blk += BLKSIZE)
- {
- v0 = ((v_blk - 1) >= 1) ? (v_blk - 1) : 1;
- for (h_blk = MB_H; h_blk < MB_H + MBSIZE; h_blk += BLKSIZE)
- {
- h0 = ((h_blk - 1) >= 1) ? (h_blk - 1) : 1;
-
- /* threshold rearrangement for flat region adjacent to non-flat region */
- if (range[blks]<32 && max_range_blk >= 64)
- thres[blks] = max_thres_blk;
-
- /* threshold rearrangement for deblocking
- (blockiness annoying at DC dominant region) */
- if (max_range_blk >= 16)
- {
- /* adaptive smoothing */
- thr = thres[blks];
-
- AdaptiveSmooth_NoMMX(Rec_Y, v0, h0, v_blk, h_blk,
- thr, width, max_diff);
- }
- blks++;
- } /* block level (Luminance) */
- }
- } /* macroblock level */
-
-
- /* Do the rest of the macro-block-lines */
- for (MB_V = MBSIZE; MB_V < height; MB_V += MBSIZE)
- {
- /* First macro-block */
- max_diff = (QP_store[((((int32)MB_V*width)>>4))>>4] >> 2) + 4;
- /* threshold determination */
- max_range_blk = max_thres_blk = 0;
- blks = 0;
- for (BLK_V = 0; BLK_V < MBSIZE; BLK_V += BLKSIZE)
- {
- for (BLK_H = 0; BLK_H < MBSIZE; BLK_H += BLKSIZE)
- {
- ptr = &Rec_Y[(int32)(MB_V + BLK_V) * width + BLK_H];
- FindMaxMin(ptr, &min_blk, &max_blk, incr);
- thres[blks] = (max_blk + min_blk + 1) >> 1;
- range[blks] = max_blk - min_blk;
-
- if (range[blks] >= max_range_blk)
- {
- max_range_blk = range[blks];
- max_thres_blk = thres[blks];
- }
- blks++;
- }
- }
-
- blks = 0;
- for (v_blk = MB_V; v_blk < MB_V + MBSIZE; v_blk += BLKSIZE)
- {
- v0 = v_blk - 1;
- for (h_blk = 0; h_blk < MBSIZE; h_blk += BLKSIZE)
- {
- h0 = ((h_blk - 1) >= 1) ? (h_blk - 1) : 1;
-
- /* threshold rearrangement for flat region adjacent to non-flat region */
- if (range[blks]<32 && max_range_blk >= 64)
- thres[blks] = max_thres_blk;
-
- /* threshold rearrangement for deblocking
- (blockiness annoying at DC dominant region) */
- if (max_range_blk >= 16)
- {
- /* adaptive smoothing */
- thr = thres[blks];
-
- AdaptiveSmooth_NoMMX(Rec_Y, v0, h0, v_blk, h_blk,
- thr, width, max_diff);
- }
- blks++;
- }
- } /* block level (Luminance) */
-
- /* Rest of the macro-blocks */
- for (MB_H = MBSIZE; MB_H < width; MB_H += MBSIZE)
- {
- max_diff = (QP_store[((((int32)MB_V*width)>>4)+MB_H)>>4] >> 2) + 4;
-
- /* threshold determination */
- max_range_blk = max_thres_blk = 0;
- blks = 0;
-
- mb_indx = (MB_V / 8) * (width / 8) + MB_H / 8;
- for (BLK_V = 0; BLK_V < MBSIZE; BLK_V += BLKSIZE)
- {
- for (BLK_H = 0; BLK_H < MBSIZE; BLK_H += BLKSIZE)
- {
- blk_indx = mb_indx + (BLK_V / 8) * width / 8 + BLK_H / 8;
- /* Update based on pp_mod only */
- if ((pp_mod[blk_indx]&0x4) != 0)
- {
- ptr = &Rec_Y[(int32)(MB_V + BLK_V) * width + MB_H + BLK_H];
- FindMaxMin(ptr, &min_blk, &max_blk, incr);
- thres[blks] = (max_blk + min_blk + 1) >> 1;
- range[blks] = max_blk - min_blk;
-
- if (range[blks] >= max_range_blk)
- {
- max_range_blk = range[blks];
- max_thres_blk = thres[blks];
- }
- }
- blks++;
- }
- }
-
- blks = 0;
- for (v_blk = MB_V; v_blk < MB_V + MBSIZE; v_blk += BLKSIZE)
- {
- v0 = v_blk - 1;
- mb_indx = (v_blk / 8) * (width / 8);
- for (h_blk = MB_H; h_blk < MB_H + MBSIZE; h_blk += BLKSIZE)
- {
- h0 = h_blk - 1;
- blk_indx = mb_indx + h_blk / 8;
- if ((pp_mod[blk_indx]&0x4) != 0)
- {
- /* threshold rearrangement for flat region adjacent to non-flat region */
- if (range[blks]<32 && max_range_blk >= 64)
- thres[blks] = max_thres_blk;
-
- /* threshold rearrangement for deblocking
- (blockiness annoying at DC dominant region) */
- if (max_range_blk >= 16)
- {
- /* adaptive smoothing */
- thr = thres[blks];
-#ifdef NoMMX
- AdaptiveSmooth_NoMMX(Rec_Y, v0, h0, v_blk, h_blk,
- thr, width, max_diff);
-#else
- DeringAdaptiveSmoothMMX(&Rec_Y[v0*width+h0],
- width, thr, max_diff);
-#endif
- }
- }
- blks++;
- }
- } /* block level (Luminance) */
- } /* macroblock level */
- } /* macroblock level */
-
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return;
-}
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/find_min_max.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/find_min_max.cpp
deleted file mode 100644
index 1ac88a1..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/find_min_max.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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.
- * -------------------------------------------------------------------
- */
-/*
-------------------------------------------------------------------------------
- INPUT AND OUTPUT DEFINITIONS
-
- Inputs:
- input_ptr = pointer to the buffer containing values of type UChar
- in a 2D block of data.
- min_ptr = pointer to the minimum value of type Int to be found in a
- square block of size BLKSIZE contained in 2D block of data.
- max_ptr = pointer to the maximum value of type Int to be found in a
- square block of size BLKSIZE contained in 2D block of data.
- incr = value of type Int representing the width of 2D block of data.
-
- Local Stores/Buffers/Pointers Needed:
- None
-
- Global Stores/Buffers/Pointers Needed:
- None
-
- Outputs:
- None
-
- Pointers and Buffers Modified:
- min_ptr points to the found minimum value in the square block of
- size BLKSIZE contained in 2D block of data.
-
- max_ptr points to the found maximum value in the square block of
- size BLKSIZE contained in 2D block of data.
-
- Local Stores Modified:
- None
-
- Global Stores Modified:
- None
-
-------------------------------------------------------------------------------
- FUNCTION DESCRIPTION
-
- This function finds the maximum and the minimum values in a square block of
- data of size BLKSIZE * BLKSIZE. The data is contained in the buffer which
- represents a 2D block of data that is larger than BLKSIZE * BLKSIZE.
- This is illustrated below.
-
- mem loc x + 00h -> o o o o o o o o o o o o o o o o
- mem loc x + 10h -> o o o o o X X X X X X X X o o o
- mem loc x + 20h -> o o o o o X X X X X X X X o o o
- mem loc x + 30h -> o o o o o X X X X X X X X o o o
- mem loc x + 40h -> o o o o o X X X X X X X X o o o
- mem loc x + 50h -> o o o o o X X X X X X X X o o o
- mem loc x + 60h -> o o o o o X X X X X X X X o o o
- mem loc x + 70h -> o o o o o X X X X X X X X o o o
- mem loc x + 80h -> o o o o o X X X X X X X X o o o
- mem loc x + 90h -> o o o o o o o o o o o o o o o o
- mem loc x + A0h -> o o o o o o o o o o o o o o o o
- mem loc x + B0h -> o o o o o o o o o o o o o o o o
-
-For illustration purposes, the diagram assumes that BLKSIZE is equal to 8
-but this is not a requirement. In this diagram, the buffer starts at
-location x but the input pointer, input_ptr, passed into this function
-would be the first row of data to be searched which is at x + 15h. The
-value of incr passed onto this function represents the amount the input_ptr
-needs to be incremented to point to the next row of data.
-
-This function compares each value in a row to the current maximum and
-minimum. After each row, input_ptr is incremented to point to the next row.
-This is repeated until all rows have been processed. When the search is
-complete the location pointed to by min_ptr contains the minimum value
-found and the location pointed to by max_ptr contains the maximum value found.
-
-------------------------------------------------------------------------------
-*/
-
-
-/*----------------------------------------------------------------------------
-; INCLUDES
-----------------------------------------------------------------------------*/
-#include "mp4dec_lib.h"
-#include "post_proc.h"
-
-/*----------------------------------------------------------------------------
-; MACROS
-; Define module specific macros here
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; DEFINES
-; Include all pre-processor statements here. Include conditional
-; compile variables also.
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL FUNCTION DEFINITIONS
-; Function Prototype declaration
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL STORE/BUFFER/POINTER DEFINITIONS
-; Variable declaration - defined here and used outside this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL FUNCTION REFERENCES
-; Declare functions defined elsewhere and referenced in this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL GLOBAL STORE/BUFFER/POINTER REFERENCES
-; Declare variables used in this module but defined elsewhere
-----------------------------------------------------------------------------*/
-
-#ifdef PV_POSTPROC_ON
-/*----------------------------------------------------------------------------
-; FUNCTION CODE
-----------------------------------------------------------------------------*/
-void FindMaxMin(
- uint8 *input_ptr,
- int *min_ptr,
- int *max_ptr,
- int incr)
-{
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- uint i, j;
- int min, max;
-
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- max = min = *input_ptr;
- /* incr = incr - BLKSIZE; */ /* 09/06/2001, already passed in as width - BLKSIZE */
-
- for (i = BLKSIZE; i > 0; i--)
- {
- for (j = BLKSIZE; j > 0; j--)
- {
- if (*input_ptr > max)
- {
- max = *input_ptr;
- }
- else if (*input_ptr < min)
- {
- min = *input_ptr;
- }
- input_ptr += 1;
- }
-
- /* set pointer to the beginning of the next row*/
- input_ptr += incr;
- }
-
- *max_ptr = max;
- *min_ptr = min;
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return;
-}
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/mb_motion_comp.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/mb_motion_comp.cpp
index 877723d..79760f5 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/src/mb_motion_comp.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/src/mb_motion_comp.cpp
@@ -154,14 +154,6 @@
int xpred, ypred;
int xsum;
int round1;
-#ifdef PV_POSTPROC_ON // 2/14/2001
- /* Total number of pixels in the VOL */
- int32 size = (int32) video->nTotalMB << 8;
- uint8 *pp_dec_y, *pp_dec_u;
- int ll[4];
- int tmp = 0;
- uint8 msk_deblock = 0;
-#endif
/*----------------------------------------------------------------------------
; Function body here
----------------------------------------------------------------------------*/
@@ -404,43 +396,6 @@
/* Call function to set de-blocking and de-ringing */
/* semaphores for luminance */
-#ifdef PV_POSTPROC_ON
- if (video->postFilterType != PV_NO_POST_PROC)
- {
- if (mode&INTER_1VMASK)
- {
- pp_dec_y = video->pstprcTypCur + imv;
- ll[0] = 1;
- ll[1] = mvwidth - 1;
- ll[2] = 1;
- ll[3] = -mvwidth - 1;
- msk_deblock = pp_semaphore_luma(xpred, ypred, pp_dec_y,
- video->pstprcTypPrv, ll, &tmp, px[0], py[0], mvwidth,
- width, height);
-
- pp_dec_u = video->pstprcTypCur + (size >> 6) +
- ((imv + (xpos >> 3)) >> 2);
-
- pp_semaphore_chroma_inter(xpred, ypred, pp_dec_u,
- video->pstprcTypPrv, dx, dy, mvwidth, height, size,
- tmp, msk_deblock);
- }
- else
- {
- /* Post-processing mode (MBM_INTER8) */
- /* deblocking and deringing) */
- pp_dec_y = video->pstprcTypCur + imv;
- *pp_dec_y = 4;
- *(pp_dec_y + 1) = 4;
- *(pp_dec_y + mvwidth) = 4;
- *(pp_dec_y + mvwidth + 1) = 4;
- pp_dec_u = video->pstprcTypCur + (size >> 6) +
- ((imv + (xpos >> 3)) >> 2);
- *pp_dec_u = 4;
- pp_dec_u[size>>8] = 4;
- }
- }
-#endif
/* xpred and ypred calculation for Chrominance is */
@@ -566,13 +521,6 @@
PIXEL *cv_comp, *cv_prev;
int width, width_uv;
int32 offset;
-#ifdef PV_POSTPROC_ON // 2/14/2001
- int imv;
- int32 size = (int32) video->nTotalMB << 8;
- uint8 *pp_dec_y, *pp_dec_u;
- uint8 *pp_prev1;
- int mvwidth = video->nMBPerRow << 1;
-#endif
width = video->width;
width_uv = width >> 1;
@@ -609,28 +557,6 @@
PutSKIPPED_B(cv_comp, cv_prev, width_uv);
/* 10/24/2000 post_processing semaphore generation */
-#ifdef PV_POSTPROC_ON // 2/14/2001
- if (video->postFilterType != PV_NO_POST_PROC)
- {
- imv = (offset >> 6) - (xpos >> 6) + (xpos >> 3);
- /* Post-processing mode (copy previous MB) */
- pp_prev1 = video->pstprcTypPrv + imv;
- pp_dec_y = video->pstprcTypCur + imv;
- *pp_dec_y = *pp_prev1;
- *(pp_dec_y + 1) = *(pp_prev1 + 1);
- *(pp_dec_y + mvwidth) = *(pp_prev1 + mvwidth);
- *(pp_dec_y + mvwidth + 1) = *(pp_prev1 + mvwidth + 1);
-
- /* chrominance */
- /*4*MB_in_width*MB_in_height*/
- pp_prev1 = video->pstprcTypPrv + (size >> 6) +
- ((imv + (xpos >> 3)) >> 2);
- pp_dec_u = video->pstprcTypCur + (size >> 6) +
- ((imv + (xpos >> 3)) >> 2);
- *pp_dec_u = *pp_prev1;
- pp_dec_u[size>>8] = pp_prev1[size>>8];
- }
-#endif
/*----------------------------------------------------------------------------
; Return nothing or data or data pointer
----------------------------------------------------------------------------*/
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/mp4dec_lib.h b/media/libstagefright/codecs/m4v_h263/dec/src/mp4dec_lib.h
index 9cd4edc..ce6f9c3 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/src/mp4dec_lib.h
+++ b/media/libstagefright/codecs/m4v_h263/dec/src/mp4dec_lib.h
@@ -170,37 +170,6 @@
/*--------------------------------------------------------------------------*/
/* defined in pp_semaphore_chroma_inter.c */
-#ifdef PV_POSTPROC_ON
- void pp_semaphore_chroma_inter(
- int xpred, /* i */
- int ypred, /* i */
- uint8 *pp_dec_u, /* i/o */
- uint8 *pstprcTypPrv, /* i */
- int dx, /* i */
- int dy, /* i */
- int mvwidth, /* i */
- int height, /* i */
- int32 size, /* i */
- int mv_loc, /* i */
- uint8 msk_deblock /* i */
- );
-
- /*--------------------------------------------------------------------------*/
- /* defined in pp_semaphore_luma.c */
- uint8 pp_semaphore_luma(
- int xpred, /* i */
- int ypred, /* i */
- uint8 *pp_dec_y, /* i/o */
- uint8 *pstprcTypPrv, /* i */
- int *ll, /* i */
- int *mv_loc, /* i/o */
- int dx, /* i */
- int dy, /* i */
- int mvwidth, /* i */
- int width, /* i */
- int height /* i */
- );
-#endif
/*--------------------------------------------------------------------------*/
/* defined in get_pred_adv_mb_add.c */
int GetPredAdvancedMB(
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/post_filter.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/post_filter.cpp
index b36050c..37a03a0 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/src/post_filter.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/src/post_filter.cpp
@@ -24,152 +24,6 @@
const static int STRENGTH_tab[] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12};
#endif
-#ifdef PV_POSTPROC_ON
-/*----------------------------------------------------------------------------
-; FUNCTION CODE
-----------------------------------------------------------------------------*/
-void PostFilter(
- VideoDecData *video,
- int filter_type,
- uint8 *output)
-{
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- uint8 *pp_mod;
- int16 *QP_store;
- int combined_with_deblock_filter;
- int nTotalMB = video->nTotalMB;
- int width, height;
- int32 size;
- int softDeblocking;
- uint8 *decodedFrame = video->videoDecControls->outputFrame;
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- width = video->width;
- height = video->height;
- size = (int32)width * height;
-
- oscl_memcpy(output, decodedFrame, size);
- oscl_memcpy(output + size, decodedFrame + size, (size >> 2));
- oscl_memcpy(output + size + (size >> 2), decodedFrame + size + (size >> 2), (size >> 2));
-
- if (filter_type == 0)
- return;
-
- /* The softDecoding cutoff corresponds to ~93000 bps for QCIF 15fps clip */
- if (PVGetDecBitrate(video->videoDecControls) > (100*video->frameRate*(size >> 12))) // MC_sofDeblock
- softDeblocking = FALSE;
- else
- softDeblocking = TRUE;
-
- combined_with_deblock_filter = filter_type & PV_DEBLOCK;
- QP_store = video->QPMB;
-
- /* Luma */
- pp_mod = video->pstprcTypCur;
-
- if ((filter_type & PV_DEBLOCK) && (filter_type & PV_DERING))
- {
- CombinedHorzVertRingFilter(output, width, height, QP_store, 0, pp_mod);
- }
- else
- {
- if (filter_type & PV_DEBLOCK)
- {
- if (softDeblocking)
- {
- CombinedHorzVertFilter(output, width, height,
- QP_store, 0, pp_mod);
- }
- else
- {
- CombinedHorzVertFilter_NoSoftDeblocking(output, width, height,
- QP_store, 0, pp_mod);
- }
- }
- if (filter_type & PV_DERING)
- {
- Deringing_Luma(output, width, height, QP_store,
- combined_with_deblock_filter, pp_mod);
-
- }
- }
-
- /* Chroma */
-
- pp_mod += (nTotalMB << 2);
- output += size;
-
- if ((filter_type & PV_DEBLOCK) && (filter_type & PV_DERING))
- {
- CombinedHorzVertRingFilter(output, (int)(width >> 1), (int)(height >> 1), QP_store, (int) 1, pp_mod);
- }
- else
- {
- if (filter_type & PV_DEBLOCK)
- {
- if (softDeblocking)
- {
- CombinedHorzVertFilter(output, (int)(width >> 1),
- (int)(height >> 1), QP_store, (int) 1, pp_mod);
- }
- else
- {
- CombinedHorzVertFilter_NoSoftDeblocking(output, (int)(width >> 1),
- (int)(height >> 1), QP_store, (int) 1, pp_mod);
- }
- }
- if (filter_type & PV_DERING)
- {
- Deringing_Chroma(output, (int)(width >> 1),
- (int)(height >> 1), QP_store,
- combined_with_deblock_filter, pp_mod);
- }
- }
-
- pp_mod += nTotalMB;
- output += (size >> 2);
-
- if ((filter_type & PV_DEBLOCK) && (filter_type & PV_DERING))
- {
- CombinedHorzVertRingFilter(output, (int)(width >> 1), (int)(height >> 1), QP_store, (int) 1, pp_mod);
- }
- else
- {
- if (filter_type & PV_DEBLOCK)
- {
- if (softDeblocking)
- {
- CombinedHorzVertFilter(output, (int)(width >> 1),
- (int)(height >> 1), QP_store, (int) 1, pp_mod);
- }
- else
- {
- CombinedHorzVertFilter_NoSoftDeblocking(output, (int)(width >> 1),
- (int)(height >> 1), QP_store, (int) 1, pp_mod);
- }
- }
- if (filter_type & PV_DERING)
- {
- Deringing_Chroma(output, (int)(width >> 1),
- (int)(height >> 1), QP_store,
- combined_with_deblock_filter, pp_mod);
- }
- }
-
- /* swap current pp_mod to prev_frame pp_mod */
- pp_mod = video->pstprcTypCur;
- video->pstprcTypCur = video->pstprcTypPrv;
- video->pstprcTypPrv = pp_mod;
-
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return;
-}
-#endif
#ifdef PV_ANNEX_IJKT_SUPPORT
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/post_proc_semaphore.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/post_proc_semaphore.cpp
deleted file mode 100644
index 3abc6be..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/post_proc_semaphore.cpp
+++ /dev/null
@@ -1,247 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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.
- * -------------------------------------------------------------------
- */
-/*
-------------------------------------------------------------------------------
- INPUT AND OUTPUT DEFINITIONS
-
- Inputs:
- q_block = pointer to buffer of inverse quantized DCT coefficients of type
- int for intra-VOP mode or buffer of residual data of type int
- for inter-VOP mode
-
- Local Stores/Buffers/Pointers Needed:
- None
-
- Global Stores/Buffers/Pointers Needed:
- None
-
- Outputs:
- postmode = post processing semaphore with the vertical deblocking,
- horizontal deblocking, and deringing bits set up accordingly
-
- Pointers and Buffers Modified:
- None
-
- Local Stores Modified:
- None
-
- Global Stores Modified:
- None
-
-------------------------------------------------------------------------------
- FUNCTION DESCRIPTION
-
- This function sets up the postmode semaphore based on the contents of the
- buffer pointed to by q_block. The function starts out with the assumption
- that all entries of q_block, except for the first entry (q_block[0]), are
- zero. This case can induce horizontal and vertical blocking artifacts,
- therefore, both horizontal and vertical deblocking bits are enabled.
-
- The following conditions are tested when setting up the horizontal/vertical
- deblocking and deringing bits:
- 1. When only the elements of the top row of the B_SIZE x B_SIZE block
- (q_block[n], n = 0,..., B_SIZE-1) are non-zero, vertical blocking artifacts
- may result, therefore, only the vertical deblocking bit is enabled.
- Otherwise, the vertical deblocking bit is disabled.
- 2. When only the elements of the far left column of the B_SIZE x B_SIZE block
- (q_block[n*B_SIZE], n = 0, ..., B_SIZE-1) are non-zero, horizontal blocking
- artifacts may result, therefore, only the horizontal deblocking bit is
- enabled. Otherwise, the horizontal deblocking bit is disabled.
- 3. If any non-zero elements exist in positions other than q_block[0],
- q_block[1], or q_block[B_SIZE], the deringing bit is enabled. Otherwise,
- it is disabled.
-
- The 3 least significant bits of postmode defines vertical or horizontal
- deblocking and deringing.
-
- The valid values are shown below:
- -------------------------------------------------------
- | Type | Enabled | Disabled |
- -------------------------------------------------------
- | Vertical Deblocking (Bit #0) | 1 | 0 |
- -------------------------------------------------------
- | Horizontal Deblocking (Bit #1) | 1 | 0 |
- -------------------------------------------------------
- | Deringing (Bit #2) | 1 | 0 |
- -------------------------------------------------------
-
-*/
-
-
-/*----------------------------------------------------------------------------
-; INCLUDES
-----------------------------------------------------------------------------*/
-#include "mp4dec_lib.h"
-#include "mp4def.h"
-#include "post_proc.h"
-
-/*----------------------------------------------------------------------------
-; MACROS
-; Define module specific macros here
-----------------------------------------------------------------------------*/
-
-
-/*----------------------------------------------------------------------------
-; DEFINES
-; Include all pre-processor statements here. Include conditional
-; compile variables also.
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL FUNCTION DEFINITIONS
-; Function Prototype declaration
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL STORE/BUFFER/POINTER DEFINITIONS
-; Variable declaration - defined here and used outside this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL FUNCTION REFERENCES
-; Declare functions defined elsewhere and referenced in this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL GLOBAL STORE/BUFFER/POINTER REFERENCES
-; Declare variables used in this module but defined elsewhere
-----------------------------------------------------------------------------*/
-#ifdef PV_POSTPROC_ON
-/*----------------------------------------------------------------------------
-; FUNCTION CODE
-----------------------------------------------------------------------------*/
-int PostProcSemaphore(
- int16 *q_block)
-{
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int i, j;
-
- /* Set default value to vertical and horizontal deblocking enabled */
- /* Initial assumption is that only q_block[0] element is non-zero, */
- /* therefore, vertical and horizontal deblocking bits are set to 1 */
- int postmode = 0x3;
-
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- /* Vertical deblocking bit is enabled when only the entire top row of */
- /* the B_SIZE x B_SIZE block, i.e., q_block[n], n = 0,..., B_SIZE-1, */
- /* are non-zero. Since initial assumption is that all elements, except */
- /* q_block[0], is zero, we need to check the remaining elements in the */
- /* top row to determine if all or some are non-zero. */
- if (q_block[1] != 0)
- {
- /* At this point, q_block[0] and q_block[1] are non-zero, while */
- /* q_block[n], n = 2,..., B_SIZE-1, are zero. Therefore, we */
- /* need to disable vertical deblocking */
- postmode &= 0xE;
- }
-
- for (i = 2; i < B_SIZE; i++)
- {
- if (q_block[i])
- {
- /* Check if q_block[n], n = 2,..., B_SIZE-1, are non-zero.*/
- /* If any of them turn out to be non-zero, we need to */
- /* disable vertical deblocking. */
- postmode &= 0xE;
-
- /* Deringing is enabled if any nonzero elements exist in */
- /* positions other than q_block[0], q_block[1] or */
- /* q_block[B_SIZE]. */
- postmode |= 0x4;
-
- break;
- }
- }
-
- /* Horizontal deblocking bit is enabled when only the entire far */
- /* left column, i.e., q_block[n*B_SIZE], n = 0, ..., B_SIZE-1, */
- /* are non-zero. Since initial assumption is that all elements, */
- /* except q_block[0], is zero, we need to check the remaining */
- /* elements in the far left column to determine if all or some */
- /* are non-zero. */
- if (q_block[B_SIZE])
- {
- /* At this point, only q_block[0] and q_block[B_SIZE] are non-zero, */
- /* while q_block[n*B_SIZE], n = 2, 3,..., B_SIZE-1, are zero. */
- /* Therefore, we need to disable horizontal deblocking. */
- postmode &= 0xD;
- }
-
- for (i = 16; i < NCOEFF_BLOCK; i += B_SIZE)
- {
- if (q_block[i])
- {
- /* Check if q_block[n], n = 2*B_SIZE,...,(B_SIZE-1)*B_SIZE, */
- /* are non-zero. If any of them turn out to be non-zero, */
- /* we need to disable horizontal deblocking. */
- postmode &= 0xD;
-
- /* Deringing is enabled if any nonzero elements exist in */
- /* positions other than q_block[0], q_block[1] or */
- /* q_block[B_SIZE]. */
- postmode |= 0x4;
-
- break;
- }
- }
-
- /* At this point, only the first row and far left column elements */
- /* have been tested. If deringing bit is still not set at this */
- /* point, check the rest of q_block to determine if the elements */
- /* are non-zero. If all elements, besides q_block[0], q_block[1], */
- /* or q_block[B_SIZE] are non-zero, deringing bit must be set */
- if ((postmode & 0x4) == 0)
- {
- for (i = 1; i < B_SIZE; i++)
- {
- for (j = 1; j < B_SIZE; j++)
- {
- if (q_block[(i<<3)+j])
- {
- /* At this point, q_block[0] and another q_block */
- /* element are non-zero, therefore, we need to */
- /* disable vertical and horizontal deblocking */
- postmode &= 0xC;
-
- /* Deringing is enabled if any nonzero elements exist in */
- /* positions other than q_block[0], q_block[1] or */
- /* q_block[B_SIZE]. */
- postmode |= 0x4;
-
- /* Set outer FOR loop count to B_SIZE to get out of */
- /* outer FOR loop */
- i = B_SIZE;
-
- /* Get out of inner FOR loop */
- break;
- }
- }
- }
- }
-
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return (postmode);
-}
-
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/pp_semaphore_chroma_inter.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/pp_semaphore_chroma_inter.cpp
deleted file mode 100644
index 7c20222..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/pp_semaphore_chroma_inter.cpp
+++ /dev/null
@@ -1,262 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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.
- * -------------------------------------------------------------------
- */
-/*
-------------------------------------------------------------------------------
- INPUT AND OUTPUT DEFINITIONS
-
- Inputs:
- xpred = x-axis coordinate of the block used for prediction (int)
- ypred = y-axis coordinate of the block used for prediction (int)
- pp_dec_u = pointer to the post processing semaphore for chrominance
- (uint8)
- pstprcTypPrv = pointer the previous frame's post processing type
- (uint8)
- dx = horizontal component of the motion vector (int)
- dy = vertical component of the motion vector (int)
- mvwidth = number of blocks per row in the luminance VOP (int)
- height = luminance VOP height in pixels (int)
- size = total number of pixel in the current luminance VOP (int)
- mv_loc = flag indicating location of the motion compensated
- (x,y) position with respect to the luminance MB (int);
- 0 -> inside MB, 1 -> outside MB
- msk_deblock = flag indicating whether to perform deblocking
- (msk_deblock = 0) or not (msk_deblock = 1) (uint8)
-
- Local Stores/Buffers/Pointers Needed:
- None
-
- Global Stores/Buffers/Pointers Needed:
- None
-
- Outputs:
- None
-
- Pointers and Buffers Modified:
- pp_dec_u contents are the updated semaphore propagation data
-
- Local Stores Modified:
- None
-
- Global Stores Modified:
- None
-
-------------------------------------------------------------------------------
- FUNCTION DESCRIPTION
-
- This functions performs post processing semaphore propagation processing
- after chrominance prediction in interframe processing mode.
-
-*/
-
-
-/*----------------------------------------------------------------------------
-; INCLUDES
-----------------------------------------------------------------------------*/
-#include "mp4dec_api.h"
-#include "mp4def.h"
-
-/*----------------------------------------------------------------------------
-; MACROS
-; Define module specific macros here
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; DEFINES
-; Include all pre-processor statements here. Include conditional
-; compile variables also.
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL FUNCTION DEFINITIONS
-; Function Prototype declaration
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL STORE/BUFFER/POINTER DEFINITIONS
-; Variable declaration - defined here and used outside this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL FUNCTION REFERENCES
-; Declare functions defined elsewhere and referenced in this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL GLOBAL STORE/BUFFER/POINTER REFERENCES
-; Declare variables used in this module but defined elsewhere
-----------------------------------------------------------------------------*/
-#ifdef PV_POSTPROC_ON
-#ifdef __cplusplus
-extern "C"
-{
-#endif
- /*----------------------------------------------------------------------------
- ; FUNCTION CODE
- ----------------------------------------------------------------------------*/
- void pp_semaphore_chroma_inter(
- int xpred, /* i */
- int ypred, /* i */
- uint8 *pp_dec_u, /* i/o */
- uint8 *pstprcTypPrv, /* i */
- int dx, /* i */
- int dy, /* i */
- int mvwidth, /* i */
- int height, /* i */
- int32 size, /* i */
- int mv_loc, /* i */
- uint8 msk_deblock /* i */
- )
- {
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int mmvy, mmvx, nmvy, nmvx;
- uint8 *pp_prev1, *pp_prev2, *pp_prev3, *pp_prev4;
-
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
-
- /* 09/28/2000, modify semaphore propagation to */
- /* accommodate smart indexing */
- mmvx = xpred >> 4; /* block x coor */
- nmvx = mmvx;
-
- mmvy = ypred >> 4; /* block y coor */
- nmvy = mmvy;
-
- /* Check if MV is outside the frame */
- if (mv_loc == 1)
- {
- /* Perform boundary check */
- if (nmvx < 0)
- {
- nmvx = 0;
- }
- else if (nmvx > mvwidth - 1)
- {
- nmvx = mvwidth - 1;
- }
-
- if (nmvy < 0)
- {
- nmvy = 0;
- }
- else if (nmvy > (height >> 4) - 1)
- {
- nmvy = (height >> 4) - 1;
- }
- }
-
- /* Calculate pointer to first chrominance b semaphores in */
- /* pstprcTypPrv, i.e., first chrominance b semaphore is in */
- /* (pstprcTypPrv + (size>>6)). */
- /* Since total number of chrominance blocks per row in a VOP */
- /* is half of the total number of luminance blocks per row in a */
- /* VOP, we use (mvwidth >> 1) when calculating the row offset. */
- pp_prev1 = pstprcTypPrv + (size >> 6) + nmvx + nmvy * (mvwidth >> 1) ;
-
- /* Check if MV is a multiple of 16 */
- /* 1/5/01, make sure it doesn't go out of bound */
- if (((dy&0xF) != 0) && (mmvy + 1 < (height >> 4) - 1))
- { /* dy is not a multiple of 16 */
-
- /* pp_prev3 is the block below pp_prev1 block */
- pp_prev3 = pp_prev1 + (mvwidth >> 1);
- }
- else
- { /* dy is a multiple of 16 */
- pp_prev3 = pp_prev1;
- }
-
- /* 1/5/01, make sure it doesn't go out of bound */
- if (((dx&0xF) != 0) && (mmvx + 1 < (mvwidth >> 1) - 1))
- { /* dx is not a multiple of 16 */
-
- /* pp_prev2 is the block to the right of pp_prev1 block */
- pp_prev2 = pp_prev1 + 1;
-
- /* pp_prev4 is the block to the right of the block */
- /* below pp_prev1 block */
- pp_prev4 = pp_prev3 + 1;
- }
- else
- { /* dx is a multiple of 16 */
-
- pp_prev2 = pp_prev1;
- pp_prev4 = pp_prev3;
- }
-
- /* Advance offset to location of first Chrominance R semaphore in */
- /* pstprcTypPrv. Since the number of pixels in a Chrominance VOP */
- /* is (number of pixels in Luminance VOP/4), and there are 64 */
- /* pixels in an 8x8 Chrominance block, the offset can be */
- /* calculated as: */
- /* mv_loc = (number of pixels in Luminance VOP/(4*64)) */
- /* = size/256 = size>>8 */
- mv_loc = (size >> 8);
-
- /* 11/3/00, change the propagation for deblocking */
- if (msk_deblock == 0)
- {
-
- /* Deblocking semaphore propagation for Chrominance */
- /* b semaphores */
- *(pp_dec_u) = 0;
-
- /* Advance offset to point to Chrominance r semaphores */
- pp_dec_u += mv_loc;
-
- /* Deblocking semaphore propagation for Chrominance */
- /* r semaphores */
- *(pp_dec_u) = 0;
- }
- else
- {
- /* Deringing semaphore propagation for Chrominance B block */
- if ((*(pp_dec_u)&4) == 0)
- {
- *(pp_dec_u) |= ((*(pp_prev1) | *(pp_prev2) |
- *(pp_prev3) | *(pp_prev4)) & 0x4);
- }
-
- /* Advance offset to point to Chrominance r semaphores */
- pp_dec_u += mv_loc;
- pp_prev1 += mv_loc;
- pp_prev2 += mv_loc;
- pp_prev3 += mv_loc;
- pp_prev4 += mv_loc;
-
- /* Deringing semaphore propagation for Chrominance R */
- if ((*(pp_dec_u)&4) == 0)
- {
- *(pp_dec_u) |= ((*(pp_prev1) | *(pp_prev2) |
- *(pp_prev3) | *(pp_prev4)) & 0x4);
- }
- }
-
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return;
- }
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/pp_semaphore_luma.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/pp_semaphore_luma.cpp
deleted file mode 100644
index b3a1ebd..0000000
--- a/media/libstagefright/codecs/m4v_h263/dec/src/pp_semaphore_luma.cpp
+++ /dev/null
@@ -1,378 +0,0 @@
-/* ------------------------------------------------------------------
- * Copyright (C) 1998-2009 PacketVideo
- *
- * 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.
- * -------------------------------------------------------------------
- */
-/*
-------------------------------------------------------------------------------
- INPUT AND OUTPUT DEFINITIONS
-
- Inputs:
- xpred = x-axis coordinate of the MB used for prediction (int)
- ypred = y-axis coordinate of the MB used for prediction (int)
- pp_dec_y = pointer to the post processing semaphore for current
- luminance frame (uint8)
- pstprcTypPrv = pointer the previous frame's post processing type
- (uint8)
- ll = pointer to the buffer (int)
- mv_loc = flag indicating location of the motion compensated
- (x,y) position with respect to the luminance MB (int);
- 0 -> inside MB, 1 -> outside MB
- dx = horizontal component of the motion vector (int)
- dy = vertical component of the motion vector (int)
- mvwidth = number of blocks per row (int)
- width = luminance VOP width in pixels (int)
- height = luminance VOP height in pixels (int)
-
- Local Stores/Buffers/Pointers Needed:
- None
-
- Global Stores/Buffers/Pointers Needed:
- None
-
- Outputs:
- msk_deblock = flag that indicates whether deblocking is to be
- performed (msk_deblock = 0) or not (msk_deblock =
- 1) (uint8)
-
- Pointers and Buffers Modified:
- pp_dec_y contents are the updated semapohore propagation data
-
- Local Stores Modified:
- None
-
- Global Stores Modified:
- None
-
-------------------------------------------------------------------------------
- FUNCTION DESCRIPTION
-
- This functions performs post processing semaphore propagation processing
- after luminance prediction.
-
-*/
-
-
-/*----------------------------------------------------------------------------
-; INCLUDES
-----------------------------------------------------------------------------*/
-#include "mp4dec_api.h"
-#include "mp4def.h"
-
-/*----------------------------------------------------------------------------
-; MACROS
-; Define module specific macros here
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; DEFINES
-; Include all pre-processor statements here. Include conditional
-; compile variables also.
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL FUNCTION DEFINITIONS
-; Function Prototype declaration
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; LOCAL STORE/BUFFER/POINTER DEFINITIONS
-; Variable declaration - defined here and used outside this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL FUNCTION REFERENCES
-; Declare functions defined elsewhere and referenced in this module
-----------------------------------------------------------------------------*/
-
-/*----------------------------------------------------------------------------
-; EXTERNAL GLOBAL STORE/BUFFER/POINTER REFERENCES
-; Declare variables used in this module but defined elsewhere
-----------------------------------------------------------------------------*/
-#ifdef PV_POSTPROC_ON
-#ifdef __cplusplus
-extern "C"
-{
-#endif
- /*----------------------------------------------------------------------------
- ; FUNCTION CODE
- ----------------------------------------------------------------------------*/
- uint8 pp_semaphore_luma(
- int xpred, /* i */
- int ypred, /* i */
- uint8 *pp_dec_y, /* i/o */
- uint8 *pstprcTypPrv, /* i */
- int *ll, /* i */
- int *mv_loc, /* i/o */
- int dx, /* i */
- int dy, /* i */
- int mvwidth, /* i */
- int width, /* i */
- int height /* i */
- )
- {
- /*----------------------------------------------------------------------------
- ; Define all local variables
- ----------------------------------------------------------------------------*/
- int kk, mmvy, mmvx, nmvx, nmvy;
- uint8 *pp_prev1, *pp_prev2, *pp_prev3, *pp_prev4;
- uint8 msk_deblock = 0; /* 11/3/00 */
-
- /*----------------------------------------------------------------------------
- ; Function body here
- ----------------------------------------------------------------------------*/
- /* Interframe Processing - 1 MV per MB */
-
- /* check whether the MV points outside the frame */
- if (xpred >= 0 && xpred <= ((width << 1) - (2*MB_SIZE)) && ypred >= 0 &&
- ypred <= ((height << 1) - (2*MB_SIZE)))
- { /*****************************/
- /* (x,y) is inside the frame */
- /*****************************/
-
- /* 10/24/2000 post_processing semaphore */
- /* generation */
-
- /* 10/23/2000 no boundary checking*/
- *mv_loc = 0;
-
- /* Calculate block x coordinate. Divide by 16 is for */
- /* converting half-pixel resolution to block */
- mmvx = xpred >> 4;
-
- /* Calculate block y coordinate. Divide by 16 is for */
- /* converting half-pixel resolution to block */
- mmvy = ypred >> 4;
-
- /* Find post processing semaphore location for block */
- /* used for prediction, i.e., */
- /* pp_prev1 = &pstprcTypPrv[mmvy*mvwidth][mmvx] */
- pp_prev1 = pstprcTypPrv + mmvx + mmvy * mvwidth;
-
- /* Check if MV is a multiple of 16 */
- if ((dx&0xF) != 0)
- { /* dx is not a multiple of 16 */
-
- /* pp_prev2 is the block to the right of */
- /* pp_prev1 block */
- pp_prev2 = pp_prev1 + 1;
-
- if ((dy&0xF) != 0)
- { /* dy is not a multiple of 16 */
-
- /* pp_prev3 is the block below */
- /* pp_prev1 block */
- pp_prev3 = pp_prev1 + mvwidth;
- }
- else
- { /* dy is a multiple of 16 */
-
- pp_prev3 = pp_prev1;
- }
-
- /* pp_prev4 is the block to the right of */
- /* pp_prev3 block. */
- pp_prev4 = pp_prev3 + 1;
- }
- else
- { /* dx is a multiple of 16 */
-
- pp_prev2 = pp_prev1;
-
- if ((dy&0xF) != 0)
- { /* dy is not a multiple of 16 */
-
- /* pp_prev3 is the block below */
- /* pp_prev1 block. */
- pp_prev3 = pp_prev1 + mvwidth;
- }
- else
- { /* dy is a multiple of 16 */
-
- pp_prev3 = pp_prev1;
- msk_deblock = 0x3;
- }
-
- pp_prev4 = pp_prev3;
- }
-
- /* Perform post processing semaphore propagation for each */
- /* of the 4 blocks in a MB. */
- for (kk = 0; kk < 4; kk++)
- {
- /* Deringing semaphore propagation */
- if ((*(pp_dec_y) & 4) == 0)
- {
- *(pp_dec_y) |= ((*(pp_prev1) | *(pp_prev2) |
- *(pp_prev3) | *(pp_prev4)) & 0x4);
- }
- /* Deblocking semaphore propagation */
- /* 11/3/00, change the propagation for deblocking */
- if (msk_deblock == 0)
- {
- *(pp_dec_y) = 0;
- }
-
- pp_dec_y += ll[kk];
- pp_prev1 += ll[kk];
- pp_prev2 += ll[kk];
- pp_prev3 += ll[kk];
- pp_prev4 += ll[kk];
- }
-
- }
- else
- { /******************************/
- /* (x,y) is outside the frame */
- /******************************/
-
- /* 10/24/2000 post_processing semaphore */
- /* generation */
-
- /* 10/23/2000 boundary checking*/
- *mv_loc = 1;
-
- /* Perform post processing semaphore propagation for each */
- /* of the 4 blocks in a MB. */
- for (kk = 0; kk < 4; kk++)
- {
- /* Calculate block x coordinate and round (?). */
- /* Divide by 16 is for converting half-pixel */
- /* resolution to block. */
- mmvx = (xpred + ((kk & 1) << 3)) >> 4;
- nmvx = mmvx;
-
- /* Calculate block y coordinate and round (?). */
- /* Divide by 16 is for converting half-pixel */
- /* resolution to block. */
- mmvy = (ypred + ((kk & 2) << 2)) >> 4;
- nmvy = mmvy;
-
- /* Perform boundary checking */
- if (nmvx < 0)
- {
- nmvx = 0;
- }
- else if (nmvx > mvwidth - 1)
- {
- nmvx = mvwidth - 1;
- }
-
- if (nmvy < 0)
- {
- nmvy = 0;
- }
- else if (nmvy > (height >> 3) - 1)
- {
- nmvy = (height >> 3) - 1;
- }
-
- /* Find post processing semaphore location for block */
- /* used for prediction, i.e., */
- /* pp_prev1 = &pstprcTypPrv[nmvy*mvwidth][nmvx] */
- pp_prev1 = pstprcTypPrv + nmvx + nmvy * mvwidth;
-
- /* Check if x component of MV is a multiple of 16 */
- /* and check if block x coordinate is out of bounds */
- if (((dx&0xF) != 0) && (mmvx + 1 < mvwidth - 1))
- { /* dx is not a multiple of 16 and the block */
- /* x coordinate is within the bounds */
-
- /* pp_prev2 is the block to the right of */
- /* pp_prev1 block */
- pp_prev2 = pp_prev1 + 1;
-
- /* Check if y component of MV is a multiple */
- /* of 16 and check if block y coordinate is */
- /* out of bounds */
- if (((dy&0xF) != 0) && (mmvy + 1 < (height >> 3) - 1))
- { /* dy is not a multiple of 16 and */
- /* the block y coordinate is */
- /* within the bounds */
-
- /* pp_prev3 is the block below */
- /* pp_prev1 block */
- pp_prev3 = pp_prev1 + mvwidth;
-
- /* all prediction are from different blocks */
- msk_deblock = 0x3;
- }
- else
- { /* dy is a multiple of 16 or the block */
- /* y coordinate is out of bounds */
-
- pp_prev3 = pp_prev1;
- }
-
- /* pp_prev4 is the block to the right of */
- /* pp_prev3 block. */
- pp_prev4 = pp_prev3 + 1;
- }
- else
- { /* dx is a multiple of 16 or the block x */
- /* coordinate is out of bounds */
-
- pp_prev2 = pp_prev1;
-
- /* Check if y component of MV is a multiple */
- /* of 16 and check if block y coordinate is */
- /* out of bounds */
- if (((dy&0xF) != 0) && (mmvy + 1 < (height >> 3) - 1))
- { /* dy is not a multiple of 16 and */
- /* the block y coordinate is */
- /* within the bounds */
-
- /* pp_prev3 is the block below */
- /* pp_prev1 block. */
- pp_prev3 = pp_prev1 + mvwidth;
- }
- else
- { /* dy is a multiple of 16 or the block */
- /* y coordinate is out of bounds */
-
- pp_prev3 = pp_prev1;
- }
-
- pp_prev4 = pp_prev3;
- }
-
- /* Deringing semaphore propagation */
- if ((*(pp_dec_y)&4) == 0)
- {
- *(pp_dec_y) |= ((*(pp_prev1) |
- *(pp_prev2) | *(pp_prev3) |
- *(pp_prev4)) & 0x4);
- }
- /* Deblocking semaphore propagation */
- /* 11/3/00, change the propaga= */
- /* tion for deblocking */
- if (msk_deblock == 0)
- {
- *(pp_dec_y) = 0;
- }
-
- pp_dec_y += ll[kk];
- }
- }
-
- /*----------------------------------------------------------------------------
- ; Return nothing or data or data pointer
- ----------------------------------------------------------------------------*/
- return (msk_deblock);
- }
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/pvdec_api.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/pvdec_api.cpp
index 9c0fcfa..b0828e4 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/src/pvdec_api.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/src/pvdec_api.cpp
@@ -512,60 +512,6 @@
video->memoryUsage += (sizeof(MOT) * 8 * nTotalMB);
#endif
-#ifdef PV_POSTPROC_ON
- /* Allocating space for post-processing Mode */
-#ifdef DEC_INTERNAL_MEMORY_OPT
- video->pstprcTypCur = IMEM_pstprcTypCur;
- video->memoryUsage += (nTotalMB * 6);
- if (video->pstprcTypCur == NULL)
- {
- status = PV_FALSE;
- }
- else
- {
- oscl_memset(video->pstprcTypCur, 0, 4*nTotalMB + 2*nTotalMB);
- }
-
- video->pstprcTypPrv = IMEM_pstprcTypPrv;
- video->memoryUsage += (nTotalMB * 6);
- if (video->pstprcTypPrv == NULL)
- {
- status = PV_FALSE;
- }
- else
- {
- oscl_memset(video->pstprcTypPrv, 0, nTotalMB*6);
- }
-
-#else
- if (nTotalMB > INT32_MAX / 6) {
- return PV_FALSE;
- }
- video->pstprcTypCur = (uint8 *) oscl_malloc(nTotalMB * 6);
- video->memoryUsage += (nTotalMB * 6);
- if (video->pstprcTypCur == NULL)
- {
- status = PV_FALSE;
- }
- else
- {
- oscl_memset(video->pstprcTypCur, 0, 4*nTotalMB + 2*nTotalMB);
- }
-
- video->pstprcTypPrv = (uint8 *) oscl_malloc(nTotalMB * 6);
- video->memoryUsage += (nTotalMB * 6);
- if (video->pstprcTypPrv == NULL)
- {
- status = PV_FALSE;
- }
- else
- {
- oscl_memset(video->pstprcTypPrv, 0, nTotalMB*6);
- }
-
-#endif
-
-#endif
/* initialize the decoder library */
video->prevVop->predictionType = I_VOP;
@@ -631,10 +577,6 @@
#ifdef DEC_INTERNAL_MEMORY_OPT
if (video)
{
-#ifdef PV_POSTPROC_ON
- video->pstprcTypCur = NULL;
- video->pstprcTypPrv = NULL;
-#endif
video->acPredFlag = NULL;
video->sliceNo = NULL;
@@ -699,10 +641,6 @@
if (video)
{
-#ifdef PV_POSTPROC_ON
- if (video->pstprcTypCur) oscl_free(video->pstprcTypCur);
- if (video->pstprcTypPrv) oscl_free(video->pstprcTypPrv);
-#endif
if (video->predDC) oscl_free(video->predDC);
video->predDCAC_row = NULL;
if (video->predDCAC_col) oscl_free(video->predDCAC_col);
@@ -830,7 +768,10 @@
OSCL_EXPORT_REF void PVSetPostProcType(VideoDecControls *decCtrl, int mode)
{
VideoDecData *video = (VideoDecData *)decCtrl->videoDecoderData;
- video->postFilterType = mode;
+ if (mode != 0) {
+ ALOGE("Post processing filters are not supported");
+ }
+ video->postFilterType = 0;
}
@@ -1621,43 +1562,8 @@
void PVDecPostProcess(VideoDecControls *decCtrl, uint8 *outputYUV)
{
uint8 *outputBuffer;
-#ifdef PV_POSTPROC_ON
- VideoDecData *video = (VideoDecData *) decCtrl->videoDecoderData;
- int32 tmpvar;
- if (outputYUV)
- {
- outputBuffer = outputYUV;
- }
- else
- {
- if (video->postFilterType)
- {
- outputBuffer = video->currVop->yChan;
- }
- else
- {
- outputBuffer = decCtrl->outputFrame;
- }
- }
-
- if (video->postFilterType)
- {
- /* Post-processing, */
- PostFilter(video, video->postFilterType, outputBuffer);
- }
- else
- {
- if (outputYUV)
- {
- /* Copy decoded frame to the output buffer. */
- tmpvar = (int32)video->width * video->height;
- oscl_memcpy(outputBuffer, decCtrl->outputFrame, tmpvar*3 / 2); /* 3/3/01 */
- }
- }
-#else
outputBuffer = decCtrl->outputFrame;
outputYUV;
-#endif
decCtrl->outputFrame = outputBuffer;
return;
}
diff --git a/media/libstagefright/codecs/m4v_h263/dec/test/Android.bp b/media/libstagefright/codecs/m4v_h263/dec/test/Android.bp
index 655491a..9c753e6 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/test/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/dec/test/Android.bp
@@ -17,6 +17,7 @@
cc_test {
name: "Mpeg4H263DecoderTest",
gtest: true,
+ test_suites: ["device-tests"],
srcs: [
"Mpeg4H263DecoderTest.cpp",
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/m4v_h263/enc/test/Android.bp b/media/libstagefright/codecs/m4v_h263/enc/test/Android.bp
index b9a8117..d2982da 100644
--- a/media/libstagefright/codecs/m4v_h263/enc/test/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/enc/test/Android.bp
@@ -17,6 +17,7 @@
cc_test {
name: "Mpeg4H263EncoderTest",
gtest: true,
+ test_suites: ["device-tests"],
srcs : [ "Mpeg4H263EncoderTest.cpp" ],
diff --git a/media/libstagefright/codecs/m4v_h263/fuzzer/Android.bp b/media/libstagefright/codecs/m4v_h263/fuzzer/Android.bp
new file mode 100644
index 0000000..56fc782
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/fuzzer/Android.bp
@@ -0,0 +1,72 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+cc_fuzz {
+ name: "mpeg4_dec_fuzzer",
+ host_supported: true,
+ srcs: [
+ "mpeg4_h263_dec_fuzzer.cpp",
+ ],
+ static_libs: [
+ "libstagefright_m4vh263dec",
+ "liblog",
+ ],
+ cflags: [
+ "-DOSCL_IMPORT_REF=",
+ "-DMPEG4",
+ ],
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
+
+cc_fuzz {
+ name: "h263_dec_fuzzer",
+ host_supported: true,
+ srcs: [
+ "mpeg4_h263_dec_fuzzer.cpp",
+ ],
+ static_libs: [
+ "libstagefright_m4vh263dec",
+ "liblog",
+ ],
+ cflags: [
+ "-DOSCL_IMPORT_REF=",
+ ],
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
diff --git a/media/libstagefright/codecs/m4v_h263/fuzzer/README.md b/media/libstagefright/codecs/m4v_h263/fuzzer/README.md
new file mode 100644
index 0000000..c2a4f69
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/fuzzer/README.md
@@ -0,0 +1,57 @@
+# Fuzzer for libstagefright_m4vh263dec decoder
+
+## Plugin Design Considerations
+The fuzzer plugin for MPEG4/H263 is designed based on the understanding of the
+codec and tries to achieve the following:
+
+##### Maximize code coverage
+Dict files (dictionary files) are created for MPEG4 and H263 to ensure that the required start
+bytes are present in every input file that goes to the fuzzer.
+This ensures that decoder does not reject any input file in the first check
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the codec using a loop.
+ * If the decode operation was successful, the input is advanced by the number of bytes consumed
+ in the decode call.
+ * If the decode operation was un-successful, the input is advanced by 1 byte so that the fuzzer
+ can proceed to feed the next frame.
+
+This ensures that the plugin tolerates any kind of input (empty, huge, malformed, etc)
+and doesnt `exit()` on any input and thereby increasing the chance of identifying vulnerabilities.
+
+##### Other considerations
+ * Two fuzzer binaries - mpeg4_dec_fuzzer and h263_dec_fuzzer are generated based on the presence
+ of a flag - 'MPEG4'
+ * The number of decode calls are kept to a maximum of 100 so that the fuzzer does not timeout.
+
+## Build
+
+This describes steps to build mpeg4_dec_fuzzer and h263_dec_fuzzer binary.
+
+### Android
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) mpeg4_dec_fuzzer
+ $ mm -j$(nproc) h263_dec_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some MPEG4 or H263 files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/mpeg4_dec_fuzzer/mpeg4_dec_fuzzer CORPUS_DIR
+ $ adb shell /data/fuzz/arm64/h263_dec_fuzzer/h263_dec_fuzzer CORPUS_DIR
+```
+To run on host
+```
+ $ $ANDROID_HOST_OUT/fuzz/x86_64/mpeg4_dec_fuzzer/mpeg4_dec_fuzzer CORPUS_DIR
+ $ $ANDROID_HOST_OUT/fuzz/x86_64/h263_dec_fuzzer/h263_dec_fuzzer CORPUS_DIR
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/media/libstagefright/codecs/m4v_h263/fuzzer/h263_dec_fuzzer.dict b/media/libstagefright/codecs/m4v_h263/fuzzer/h263_dec_fuzzer.dict
new file mode 100644
index 0000000..591d37e
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/fuzzer/h263_dec_fuzzer.dict
@@ -0,0 +1,2 @@
+# Start code (bytes 0-3)
+kw1="\x00\x00\x80\x02"
diff --git a/media/libstagefright/codecs/m4v_h263/fuzzer/mpeg4_dec_fuzzer.dict b/media/libstagefright/codecs/m4v_h263/fuzzer/mpeg4_dec_fuzzer.dict
new file mode 100644
index 0000000..76241a6
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/fuzzer/mpeg4_dec_fuzzer.dict
@@ -0,0 +1,2 @@
+# Start code (bytes 0-3)
+kw1="\x00\x00\x01\xB0"
diff --git a/media/libstagefright/codecs/m4v_h263/fuzzer/mpeg4_h263_dec_fuzzer.cpp b/media/libstagefright/codecs/m4v_h263/fuzzer/mpeg4_h263_dec_fuzzer.cpp
new file mode 100644
index 0000000..912c821
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/fuzzer/mpeg4_h263_dec_fuzzer.cpp
@@ -0,0 +1,205 @@
+/******************************************************************************
+ *
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+#include "mp4dec_api.h"
+#define MPEG4_MAX_WIDTH 1920
+#define MPEG4_MAX_HEIGHT 1080
+#define H263_MAX_WIDTH 352
+#define H263_MAX_HEIGHT 288
+#define DEFAULT_WIDTH 352
+#define DEFAULT_HEIGHT 288
+
+constexpr size_t kMaxNumDecodeCalls = 100;
+constexpr uint8_t kNumOutputBuffers = 2;
+constexpr int kLayer = 1;
+
+struct tagvideoDecControls;
+
+/* == ceil(num / den) * den. T must be integer type, alignment must be positive power of 2 */
+template <class T, class U>
+inline static const T align(const T &num, const U &den) {
+ return (num + (T)(den - 1)) & (T) ~(den - 1);
+}
+
+class Codec {
+ public:
+ Codec() = default;
+ ~Codec() { deInitDecoder(); }
+ bool initDecoder();
+ bool allocOutputBuffer(size_t outputBufferSize);
+ void freeOutputBuffer();
+ void handleResolutionChange();
+ void decodeFrames(const uint8_t *data, size_t size);
+ void deInitDecoder();
+
+ private:
+ tagvideoDecControls *mDecHandle = nullptr;
+ uint8_t *mOutputBuffer[kNumOutputBuffers];
+ bool mInitialized = false;
+ bool mFramesConfigured = false;
+#ifdef MPEG4
+ MP4DecodingMode mInputMode = MPEG4_MODE;
+ size_t mMaxWidth = MPEG4_MAX_WIDTH;
+ size_t mMaxHeight = MPEG4_MAX_HEIGHT;
+#else
+ MP4DecodingMode mInputMode = H263_MODE;
+ size_t mMaxWidth = H263_MAX_WIDTH;
+ size_t mMaxHeight = H263_MAX_HEIGHT;
+#endif
+ uint32_t mNumSamplesOutput = 0;
+ uint32_t mWidth = DEFAULT_WIDTH;
+ uint32_t mHeight = DEFAULT_HEIGHT;
+};
+
+bool Codec::initDecoder() {
+ mDecHandle = new tagvideoDecControls;
+ if (!mDecHandle) {
+ return false;
+ }
+ memset(mDecHandle, 0, sizeof(tagvideoDecControls));
+ return true;
+}
+
+bool Codec::allocOutputBuffer(size_t outputBufferSize) {
+ for (uint8_t i = 0; i < kNumOutputBuffers; ++i) {
+ if (!mOutputBuffer[i]) {
+ mOutputBuffer[i] = static_cast<uint8_t *>(malloc(outputBufferSize));
+ if (!mOutputBuffer[i]) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void Codec::freeOutputBuffer() {
+ for (uint8_t i = 0; i < kNumOutputBuffers; ++i) {
+ if (mOutputBuffer[i]) {
+ free(mOutputBuffer[i]);
+ mOutputBuffer[i] = nullptr;
+ }
+ }
+}
+
+void Codec::handleResolutionChange() {
+ int32_t dispWidth, dispHeight;
+ PVGetVideoDimensions(mDecHandle, &dispWidth, &dispHeight);
+
+ int32_t bufWidth, bufHeight;
+ PVGetBufferDimensions(mDecHandle, &bufWidth, &bufHeight);
+
+ if (dispWidth != mWidth || dispHeight != mHeight) {
+ mWidth = dispWidth;
+ mHeight = dispHeight;
+ }
+}
+
+void Codec::decodeFrames(const uint8_t *data, size_t size) {
+ size_t outputBufferSize = align(mMaxWidth, 16) * align(mMaxHeight, 16) * 3 / 2;
+ uint8_t *start_code = const_cast<uint8_t *>(data);
+ static const uint8_t volInfo[] = {0x00, 0x00, 0x01, 0xB0};
+ bool volHeader = memcmp(start_code, volInfo, 4) == 0;
+ if (volHeader) {
+ PVCleanUpVideoDecoder(mDecHandle);
+ mInitialized = false;
+ }
+
+ if (!mInitialized) {
+ uint8_t *volData[1]{};
+ int32_t volSize = 0;
+
+ if (volHeader) { /* removed some codec config part */
+ volData[0] = const_cast<uint8_t *>(data);
+ volSize = size;
+ }
+
+ if (!PVInitVideoDecoder(mDecHandle, volData, &volSize, kLayer, mMaxWidth, mMaxHeight,
+ mInputMode)) {
+ return;
+ }
+ mInitialized = true;
+ MP4DecodingMode actualMode = PVGetDecBitstreamMode(mDecHandle);
+ if (mInputMode != actualMode) {
+ return;
+ }
+
+ PVSetPostProcType(mDecHandle, 0);
+ }
+ size_t yFrameSize = sizeof(uint8) * mDecHandle->size;
+ if (outputBufferSize < yFrameSize * 3 / 2) {
+ return;
+ }
+ if (!allocOutputBuffer(outputBufferSize)) {
+ return;
+ }
+ size_t numDecodeCalls = 0;
+ while ((size > 0) && (numDecodeCalls < kMaxNumDecodeCalls)) {
+ if (!mFramesConfigured) {
+ PVSetReferenceYUV(mDecHandle, mOutputBuffer[1]);
+ mFramesConfigured = true;
+ }
+
+ // Need to check if header contains new info, e.g., width/height, etc.
+ VopHeaderInfo header_info;
+ uint32_t useExtTimestamp = (numDecodeCalls == 0);
+ int32_t tempSize = (int32_t)size;
+ uint8_t *bitstreamTmp = const_cast<uint8_t *>(data);
+ uint32_t timestamp = 0;
+ if (PVDecodeVopHeader(mDecHandle, &bitstreamTmp, ×tamp, &tempSize, &header_info,
+ &useExtTimestamp, mOutputBuffer[mNumSamplesOutput & 1]) != PV_TRUE) {
+ return;
+ }
+
+ handleResolutionChange();
+
+ PVDecodeVopBody(mDecHandle, &tempSize);
+ uint32_t bytesConsumed = 1;
+ if (size > tempSize) {
+ bytesConsumed = size - tempSize;
+ }
+ data += bytesConsumed;
+ size -= bytesConsumed;
+ ++mNumSamplesOutput;
+ ++numDecodeCalls;
+ }
+ freeOutputBuffer();
+}
+
+void Codec::deInitDecoder() {
+ PVCleanUpVideoDecoder(mDecHandle);
+ delete mDecHandle;
+ mDecHandle = nullptr;
+ mInitialized = false;
+ freeOutputBuffer();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < 4) {
+ return 0;
+ }
+ Codec *codec = new Codec();
+ if (!codec) {
+ return 0;
+ }
+ if (codec->initDecoder()) {
+ codec->decodeFrames(data, size);
+ }
+ delete codec;
+ return 0;
+}
diff --git a/media/libstagefright/codecs/mp3dec/Android.bp b/media/libstagefright/codecs/mp3dec/Android.bp
index b630524..316d63c 100644
--- a/media/libstagefright/codecs/mp3dec/Android.bp
+++ b/media/libstagefright/codecs/mp3dec/Android.bp
@@ -1,7 +1,9 @@
cc_library_static {
name: "libstagefright_mp3dec",
vendor_available: true,
+ min_sdk_version: "29",
+ host_supported:true,
srcs: [
"src/pvmp3_normalize.cpp",
"src/pvmp3_alias_reduction.cpp",
@@ -72,6 +74,12 @@
"-DOSCL_UNUSED_ARG(x)=(void)(x)",
"-Werror",
],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
//###############################################################################
diff --git a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
index 80083f7..b5d32ed 100644
--- a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
+++ b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
@@ -307,6 +307,20 @@
if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
mSawInputEos = true;
+ if (mIsFirst && !inHeader->nFilledLen) {
+ ALOGV("empty first EOS");
+ outHeader->nFilledLen = 0;
+ outHeader->nTimeStamp = inHeader->nTimeStamp;
+ outHeader->nFlags = OMX_BUFFERFLAG_EOS;
+ mSignalledOutputEos = true;
+ outInfo->mOwnedByUs = false;
+ outQueue.erase(outQueue.begin());
+ notifyFillBufferDone(outHeader);
+ inInfo->mOwnedByUs = false;
+ inQueue.erase(inQueue.begin());
+ notifyEmptyBufferDone(inHeader);
+ return;
+ }
}
mConfig->pInputBuffer =
diff --git a/media/libstagefright/codecs/mp3dec/TEST_MAPPING b/media/libstagefright/codecs/mp3dec/TEST_MAPPING
new file mode 100644
index 0000000..b237d65
--- /dev/null
+++ b/media/libstagefright/codecs/mp3dec/TEST_MAPPING
@@ -0,0 +1,7 @@
+// mappings for frameworks/av/media/libstagefright/codecs/mp3dec
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ // { "name": "Mp3DecoderTest"}
+ ]
+}
diff --git a/media/libstagefright/codecs/mp3dec/fuzzer/Android.bp b/media/libstagefright/codecs/mp3dec/fuzzer/Android.bp
new file mode 100644
index 0000000..79fa1e9
--- /dev/null
+++ b/media/libstagefright/codecs/mp3dec/fuzzer/Android.bp
@@ -0,0 +1,39 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+cc_fuzz {
+ name: "mp3_dec_fuzzer",
+ host_supported: true,
+
+ static_libs: [
+ "libstagefright_mp3dec",
+ ],
+
+ srcs: [
+ "mp3_dec_fuzzer.cpp",
+ ],
+
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
diff --git a/media/libstagefright/codecs/mp3dec/fuzzer/README.md b/media/libstagefright/codecs/mp3dec/fuzzer/README.md
new file mode 100644
index 0000000..09dd5c3
--- /dev/null
+++ b/media/libstagefright/codecs/mp3dec/fuzzer/README.md
@@ -0,0 +1,56 @@
+# Fuzzer for libstagefright_mp3dec decoder
+
+## Plugin Design Considerations
+The fuzzer plugin for mp3 decoder is designed based on the understanding of the
+codec and tries to achieve the following:
+
+##### Maximize code coverage
+
+This fuzzer makes use of the following config parameters:
+1. Equalizer type (parameter name: `equalizerType`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `equalizerType` | 0. `flat ` 1. `bass_boost ` 2. `rock ` 3. `pop ` 4. `jazz ` 5. `classical ` 6. `talk ` 7. `flat_ ` | Bits 0, 1 and 2 of first byte of input stream |
+| `crcEnabled` | 0. `false ` 1. `true `| Bit 0 of second byte of input stream |
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the codec using a loop.
+ * If the decode operation was successful, the input is advanced by the number
+ of bytes used by the decoder.
+ * If the decode operation was un-successful, the input is advanced by 1 byte
+ till it reaches a valid frame or end of stream.
+
+This ensures that the plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesnt `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build mp3_dec_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) mp3_dec_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some mp3 files to that folder.
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/mp3_dec_fuzzer/mp3_dec_fuzzer CORPUS_DIR
+```
+To run on host
+```
+ $ $ANDROID_HOST_OUT/fuzz/x86_64/mp3_dec_fuzzer/mp3_dec_fuzzer CORPUS_DIR
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/media/libstagefright/codecs/mp3dec/fuzzer/mp3_dec_fuzzer.cpp b/media/libstagefright/codecs/mp3dec/fuzzer/mp3_dec_fuzzer.cpp
new file mode 100644
index 0000000..847c8c4
--- /dev/null
+++ b/media/libstagefright/codecs/mp3dec/fuzzer/mp3_dec_fuzzer.cpp
@@ -0,0 +1,237 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include <stdlib.h>
+#include <algorithm>
+
+#include <pvmp3decoder_api.h>
+
+constexpr int kMaxFrameSamples = 4608;
+constexpr int kMaxChannels = 2;
+constexpr e_equalization kEqualizerTypes[] = {flat, bass_boost, rock, pop,
+ jazz, classical, talk, flat_};
+
+static bool parseMp3Header(uint32_t header, size_t *frame_size,
+ uint32_t *out_sampling_rate = nullptr, uint32_t *out_channels = nullptr,
+ uint32_t *out_bitrate = nullptr, uint32_t *out_num_samples = nullptr) {
+ *frame_size = 0;
+ if (out_sampling_rate) *out_sampling_rate = 0;
+ if (out_channels) *out_channels = 0;
+ if (out_bitrate) *out_bitrate = 0;
+ if (out_num_samples) *out_num_samples = 0;
+
+ if ((header & 0xffe00000) != 0xffe00000) {
+ return false;
+ }
+ unsigned version = (header >> 19) & 3;
+ if (version == 0x01) {
+ return false;
+ }
+ unsigned layer = (header >> 17) & 3;
+ if (layer == 0x00) {
+ return false;
+ }
+ unsigned bitrate_index = (header >> 12) & 0x0f;
+ if (bitrate_index == 0 || bitrate_index == 0x0f) {
+ return false;
+ }
+ unsigned sampling_rate_index = (header >> 10) & 3;
+ if (sampling_rate_index == 3) {
+ return false;
+ }
+ static const int kSamplingRateV1[] = {44100, 48000, 32000};
+ int sampling_rate = kSamplingRateV1[sampling_rate_index];
+ if (version == 2 /* V2 */) {
+ sampling_rate /= 2;
+ } else if (version == 0 /* V2.5 */) {
+ sampling_rate /= 4;
+ }
+
+ unsigned padding = (header >> 9) & 1;
+
+ if (layer == 3) { // layer I
+ static const int kBitrateV1[] = {32, 64, 96, 128, 160, 192, 224,
+ 256, 288, 320, 352, 384, 416, 448};
+ static const int kBitrateV2[] = {32, 48, 56, 64, 80, 96, 112,
+ 128, 144, 160, 176, 192, 224, 256};
+
+ int bitrate =
+ (version == 3 /* V1 */) ? kBitrateV1[bitrate_index - 1] : kBitrateV2[bitrate_index - 1];
+
+ if (out_bitrate) {
+ *out_bitrate = bitrate;
+ }
+ *frame_size = (12000 * bitrate / sampling_rate + padding) * 4;
+ if (out_num_samples) {
+ *out_num_samples = 384;
+ }
+ } else { // layer II or III
+ static const int kBitrateV1L2[] = {32, 48, 56, 64, 80, 96, 112,
+ 128, 160, 192, 224, 256, 320, 384};
+ static const int kBitrateV1L3[] = {32, 40, 48, 56, 64, 80, 96,
+ 112, 128, 160, 192, 224, 256, 320};
+ static const int kBitrateV2[] = {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160};
+ int bitrate;
+ if (version == 3 /* V1 */) {
+ bitrate =
+ (layer == 2 /* L2 */) ? kBitrateV1L2[bitrate_index - 1] : kBitrateV1L3[bitrate_index - 1];
+
+ if (out_num_samples) {
+ *out_num_samples = 1152;
+ }
+ } else { // V2 (or 2.5)
+ bitrate = kBitrateV2[bitrate_index - 1];
+ if (out_num_samples) {
+ *out_num_samples = (layer == 1 /* L3 */) ? 576 : 1152;
+ }
+ }
+
+ if (out_bitrate) {
+ *out_bitrate = bitrate;
+ }
+
+ if (version == 3 /* V1 */) {
+ *frame_size = 144000 * bitrate / sampling_rate + padding;
+ } else { // V2 or V2.5
+ size_t tmp = (layer == 1 /* L3 */) ? 72000 : 144000;
+ *frame_size = tmp * bitrate / sampling_rate + padding;
+ }
+ }
+
+ if (out_sampling_rate) {
+ *out_sampling_rate = sampling_rate;
+ }
+
+ if (out_channels) {
+ int channel_mode = (header >> 6) & 3;
+ *out_channels = (channel_mode == 3) ? 1 : 2;
+ }
+
+ return true;
+}
+
+static uint32_t U32_AT(const uint8_t *ptr) {
+ return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
+}
+
+static bool checkHeader(uint8 *header, size_t inSize) {
+ size_t frameSize;
+ size_t totalInSize = 0;
+ bool isValidBuffer = false;
+
+ while (totalInSize + 4 < inSize) {
+ isValidBuffer = true;
+ uint32_t val = U32_AT(header + totalInSize);
+ if (!parseMp3Header(val, &frameSize, nullptr, nullptr, nullptr, nullptr)) {
+ return false;
+ }
+ totalInSize += frameSize;
+ }
+
+ return (isValidBuffer);
+}
+
+class Codec {
+ public:
+ Codec() = default;
+ ~Codec() { deInitDecoder(); }
+
+ bool initDecoder();
+ void decodeFrames(uint8_t *data, size_t size);
+ void deInitDecoder();
+
+ private:
+ tPVMP3DecoderExternal *mConfig = nullptr;
+ void *mDecoderBuf = nullptr;
+};
+
+bool Codec::initDecoder() {
+ mConfig = new tPVMP3DecoderExternal{};
+ if (!mConfig) {
+ return false;
+ }
+ size_t decoderBufSize = pvmp3_decoderMemRequirements();
+ mDecoderBuf = malloc(decoderBufSize);
+ if (!mDecoderBuf) {
+ return false;
+ }
+ memset(mDecoderBuf, 0x0, decoderBufSize);
+ pvmp3_InitDecoder(mConfig, mDecoderBuf);
+ return true;
+}
+
+void Codec::decodeFrames(uint8_t *data, size_t size) {
+ uint8_t equalizerTypeValue = (data[0] & 0x7);
+ mConfig->equalizerType = kEqualizerTypes[equalizerTypeValue];
+ mConfig->crcEnabled = data[1] & 0x1;
+
+ while (size > 0) {
+ bool status = checkHeader(data, size);
+ if (!status) {
+ size--;
+ data++;
+ continue;
+ }
+ size_t outBufSize = kMaxFrameSamples * kMaxChannels;
+ size_t usedBytes = 0;
+ int16_t outputBuf[outBufSize];
+ mConfig->inputBufferCurrentLength = size;
+ mConfig->inputBufferUsedLength = 0;
+ mConfig->inputBufferMaxLength = 0;
+ mConfig->pInputBuffer = data;
+ mConfig->pOutputBuffer = outputBuf;
+ mConfig->outputFrameSize = outBufSize / sizeof(int16_t);
+
+ ERROR_CODE decoderErr;
+ decoderErr = pvmp3_framedecoder(mConfig, mDecoderBuf);
+ if (decoderErr != NO_DECODING_ERROR) {
+ size--;
+ data++;
+ } else {
+ usedBytes = std::min((int32_t)size, mConfig->inputBufferUsedLength);
+ size -= usedBytes;
+ data += usedBytes;
+ }
+ }
+}
+
+void Codec::deInitDecoder() {
+ if (mDecoderBuf) {
+ free(mDecoderBuf);
+ mDecoderBuf = nullptr;
+ }
+ delete mConfig;
+ mConfig = nullptr;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < 4) {
+ return 0;
+ }
+ Codec *codec = new Codec();
+ if (!codec) {
+ return 0;
+ }
+ if (codec->initDecoder()) {
+ codec->decodeFrames(const_cast<uint8_t *>(data), size);
+ }
+ delete codec;
+ return 0;
+}
diff --git a/media/libstagefright/codecs/mp3dec/test/Android.bp b/media/libstagefright/codecs/mp3dec/test/Android.bp
index 0ff8b12..6b92ae9 100644
--- a/media/libstagefright/codecs/mp3dec/test/Android.bp
+++ b/media/libstagefright/codecs/mp3dec/test/Android.bp
@@ -17,6 +17,7 @@
cc_test {
name: "Mp3DecoderTest",
gtest: true,
+ test_suites: ["device-tests"],
srcs: [
"mp3reader.cpp",
diff --git a/media/libstagefright/flac/dec/Android.bp b/media/libstagefright/flac/dec/Android.bp
index d65a663..b63353c 100644
--- a/media/libstagefright/flac/dec/Android.bp
+++ b/media/libstagefright/flac/dec/Android.bp
@@ -1,6 +1,8 @@
cc_library {
name: "libstagefright_flacdec",
vendor_available: true,
+ min_sdk_version: "29",
+ host_supported: true,
srcs: [
"FLACDecoder.cpp",
@@ -32,6 +34,13 @@
],
header_libs: [
- "libmedia_headers",
+ "libstagefright_foundation_headers",
+ "libstagefright_headers",
],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
diff --git a/media/libstagefright/flac/dec/FLACDecoder.cpp b/media/libstagefright/flac/dec/FLACDecoder.cpp
index cef0bc6..f5e9532 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.cpp
+++ b/media/libstagefright/flac/dec/FLACDecoder.cpp
@@ -433,7 +433,7 @@
if (mBuffer == nullptr) {
mBufferDataSize = 0;
mBufferLen = 0;
- ALOGE("decodeOneFrame: failed to allocate memory for input buffer");
+ ALOGE("addDataToBuffer: failed to allocate memory for input buffer");
return NO_MEMORY;
}
mBufferLen = mBufferDataSize + inBufferLen;
diff --git a/media/libstagefright/foundation/ALooperRoster.cpp b/media/libstagefright/foundation/ALooperRoster.cpp
index 8a7c3eb..0a4e598 100644
--- a/media/libstagefright/foundation/ALooperRoster.cpp
+++ b/media/libstagefright/foundation/ALooperRoster.cpp
@@ -166,7 +166,7 @@
}
s.append("\n");
}
- write(fd, s.string(), s.size());
+ (void)write(fd, s.string(), s.size());
}
} // namespace android
diff --git a/media/libstagefright/foundation/AString.cpp b/media/libstagefright/foundation/AString.cpp
index 4bd186c..8722e14 100644
--- a/media/libstagefright/foundation/AString.cpp
+++ b/media/libstagefright/foundation/AString.cpp
@@ -387,10 +387,14 @@
va_start(ap, format);
char *buffer;
- vasprintf(&buffer, format, ap);
+ int bufferSize = vasprintf(&buffer, format, ap);
va_end(ap);
+ if(bufferSize < 0) {
+ return AString();
+ }
+
AString result(buffer);
free(buffer);
diff --git a/media/libstagefright/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index 9fe879e..ebf1035 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 {
@@ -11,6 +12,7 @@
vndk: {
enabled: true,
},
+ host_supported: true,
double_loadable: true,
include_dirs: [
"frameworks/av/include",
@@ -24,7 +26,6 @@
],
header_libs: [
- "libhardware_headers",
"libstagefright_foundation_headers",
"media_ndk_headers",
"media_plugin_headers",
@@ -85,6 +86,9 @@
"-DNO_IMEMORY",
],
},
+ darwin: {
+ enabled: false,
+ },
},
clang: true,
@@ -101,11 +105,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/foundation/TEST_MAPPING b/media/libstagefright/foundation/TEST_MAPPING
index 3301c4b..0d6a6da 100644
--- a/media/libstagefright/foundation/TEST_MAPPING
+++ b/media/libstagefright/foundation/TEST_MAPPING
@@ -1,5 +1,10 @@
+// mappings for frameworks/av/media/libstagefright/foundation
{
"presubmit": [
- { "name": "sf_foundation_test" }
+ // TODO(b/148094059): unit tests not allowed to download content
+ //{ "name": "OpusHeaderTest" },
+
+ { "name": "sf_foundation_test" },
+ { "name": "MetaDataBaseUnitTest"}
]
}
diff --git a/media/libstagefright/foundation/avc_utils.cpp b/media/libstagefright/foundation/avc_utils.cpp
index f53d2c9..9d6887c 100644
--- a/media/libstagefright/foundation/avc_utils.cpp
+++ b/media/libstagefright/foundation/avc_utils.cpp
@@ -559,11 +559,9 @@
CHECK_NE(video_object_type_indication,
0x21u /* Fine Granularity Scalable */);
- unsigned video_object_layer_verid __unused;
- unsigned video_object_layer_priority __unused;
if (br.getBits(1)) {
- video_object_layer_verid = br.getBits(4);
- video_object_layer_priority = br.getBits(3);
+ br.skipBits(4); //video_object_layer_verid
+ br.skipBits(3); //video_object_layer_priority
}
unsigned aspect_ratio_info = br.getBits(4);
if (aspect_ratio_info == 0x0f /* extended PAR */) {
@@ -622,7 +620,7 @@
unsigned video_object_layer_height = br.getBits(13);
CHECK(br.getBits(1)); // marker_bit
- unsigned interlaced __unused = br.getBits(1);
+ br.skipBits(1); // interlaced
*width = video_object_layer_width;
*height = video_object_layer_height;
@@ -668,7 +666,7 @@
return false;
}
- unsigned protection __unused = (header >> 16) & 1;
+ // we can get protection value from (header >> 16) & 1
unsigned bitrate_index = (header >> 12) & 0x0f;
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/ADebug.h b/media/libstagefright/foundation/include/media/stagefright/foundation/ADebug.h
index ab17a02..e4b99bf 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/ADebug.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/ADebug.h
@@ -148,7 +148,8 @@
static char *GetDebugName(const char *name);
inline static bool isExperimentEnabled(
- const char *name __unused /* nonnull */, bool allow __unused = true) {
+ const char *name __attribute__((unused)) /* nonnull */,
+ bool allow __attribute__((unused)) = true) {
#ifdef ENABLE_STAGEFRIGHT_EXPERIMENTS
if (!strcmp(name, "legacy-adaptive")) {
return getExperimentFlag(allow, name, 2, 1); // every other day
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/AUtils.h b/media/libstagefright/foundation/include/media/stagefright/foundation/AUtils.h
index af6b357..3b646dc 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/AUtils.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/AUtils.h
@@ -63,7 +63,7 @@
template<class T>
void ENSURE_UNSIGNED_TYPE() {
- T TYPE_MUST_BE_UNSIGNED[(T)-1 < 0 ? -1 : 0] __unused;
+ T TYPE_MUST_BE_UNSIGNED[(T)-1 < 0 ? -1 : 0] __attribute__((unused));
}
// needle is in range [hayStart, hayStart + haySize)
diff --git a/media/libstagefright/foundation/tests/AVCUtils/AVCUtilsTestEnvironment.h b/media/libstagefright/foundation/tests/AVCUtils/AVCUtilsTestEnvironment.h
new file mode 100644
index 0000000..b28a7bc
--- /dev/null
+++ b/media/libstagefright/foundation/tests/AVCUtils/AVCUtilsTestEnvironment.h
@@ -0,0 +1,73 @@
+/*
+ * 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 __AVC_UTILS_TEST_ENVIRONMENT_H__
+#define __AVC_UTILS_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class AVCUtilsTestEnvironment : public::testing::Environment {
+ public:
+ AVCUtilsTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int AVCUtilsTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"path", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P': {
+ setRes(optarg);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __AVC_UTILS_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/foundation/tests/AVCUtils/AVCUtilsUnitTest.cpp b/media/libstagefright/foundation/tests/AVCUtils/AVCUtilsUnitTest.cpp
new file mode 100644
index 0000000..77a8599
--- /dev/null
+++ b/media/libstagefright/foundation/tests/AVCUtils/AVCUtilsUnitTest.cpp
@@ -0,0 +1,411 @@
+/*
+ * 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 "AVCUtilsUnitTest"
+#include <utils/Log.h>
+
+#include <fstream>
+
+#include "media/stagefright/foundation/ABitReader.h"
+#include "media/stagefright/foundation/avc_utils.h"
+
+#include "AVCUtilsTestEnvironment.h"
+
+constexpr size_t kSmallBufferSize = 2;
+constexpr uint8_t kSPSmask = 0x1f;
+constexpr uint8_t kSPSStartCode = 0x07;
+constexpr uint8_t kConfigVersion = 0x01;
+
+using namespace android;
+
+static AVCUtilsTestEnvironment *gEnv = nullptr;
+
+class MpegAudioUnitTest
+ : public ::testing::TestWithParam<
+ tuple</*audioHeader*/ uint32_t, /*frameSize*/ int32_t, /*sampleRate*/ int32_t,
+ /*numChannels*/ int32_t, /*bitRate*/ int32_t, /*numSamples*/ int32_t>> {};
+
+class VOLDimensionTest
+ : public ::testing::TestWithParam<
+ tuple</*fileName*/ string, /*volWidth*/ int32_t, /*volHeight*/ int32_t>> {};
+
+class AVCUtils {
+ public:
+ bool SetUpAVCUtils(string fileName, string infoFileName) {
+ mInputFile = gEnv->getRes() + fileName;
+ mInputFileStream.open(mInputFile, ifstream::in);
+ if (!mInputFileStream.is_open()) return false;
+
+ mInfoFile = gEnv->getRes() + infoFileName;
+ mInfoFileStream.open(mInfoFile, ifstream::in);
+ if (!mInputFileStream.is_open()) return false;
+ return true;
+ }
+
+ ~AVCUtils() {
+ if (mInputFileStream.is_open()) mInputFileStream.close();
+ if (mInfoFileStream.is_open()) mInfoFileStream.close();
+ }
+
+ string mInputFile;
+ string mInfoFile;
+
+ ifstream mInputFileStream;
+ ifstream mInfoFileStream;
+};
+
+class AVCDimensionTest
+ : public AVCUtils,
+ public ::testing::TestWithParam<
+ tuple</*fileName*/ string, /*infoFileName*/ string,
+ /*avcWidth*/ size_t, /*avcHeight*/ size_t, /*numberOfNALUnits*/ int32_t>> {
+ public:
+ virtual void SetUp() override {
+ tuple<string, string, size_t, size_t, size_t> params = GetParam();
+ string fileName = get<0>(params);
+ string infoFileName = get<1>(params);
+ AVCUtils::SetUpAVCUtils(fileName, infoFileName);
+
+ mFrameWidth = get<2>(params);
+ mFrameHeight = get<3>(params);
+ mNalUnitsExpected = get<4>(params);
+ }
+
+ size_t mFrameWidth;
+ size_t mFrameHeight;
+ int32_t mNalUnitsExpected;
+};
+
+class AvccBoxTest : public AVCDimensionTest {
+ public:
+ virtual void SetUp() override { AVCDimensionTest::SetUp(); }
+};
+
+class AVCFrameTest
+ : public AVCUtils,
+ public ::testing::TestWithParam<pair</*fileName*/ string, /*infoFileName*/ string>> {
+ public:
+ virtual void SetUp() override {
+ string fileName = GetParam().first;
+ string infoFileName = GetParam().second;
+ AVCUtils::SetUpAVCUtils(fileName, infoFileName);
+ }
+};
+
+TEST_P(MpegAudioUnitTest, AudioProfileTest) {
+ tuple<uint32_t, size_t, int, int, int, int> params = GetParam();
+ uint32_t header = get<0>(params);
+
+ size_t audioFrameSize = get<1>(params);
+ int audioSampleRate = get<2>(params);
+ int audioNumChannels = get<3>(params);
+ int audioBitRate = get<4>(params);
+ int audioNumSamples = get<5>(params);
+
+ size_t frameSize = 0;
+ int sampleRate = 0;
+ int numChannels = 0;
+ int bitRate = 0;
+ int numSamples = 0;
+
+ bool status = GetMPEGAudioFrameSize(header, &frameSize, &sampleRate, &numChannels, &bitRate,
+ &numSamples);
+ ASSERT_TRUE(status) << "Failed to get Audio properties";
+
+ ASSERT_EQ(frameSize, audioFrameSize) << "Wrong frame size found";
+
+ ASSERT_EQ(sampleRate, audioSampleRate) << "Wrong sample rate found";
+
+ ASSERT_EQ(numChannels, audioNumChannels) << "Wrong number of channels found";
+
+ ASSERT_EQ(bitRate, audioBitRate) << "Wrong bit rate found";
+
+ ASSERT_EQ(numSamples, audioNumSamples) << "Wrong number of samples found";
+}
+
+TEST_P(VOLDimensionTest, DimensionTest) {
+ tuple<string, int32_t, int32_t> params = GetParam();
+ string inputFile = gEnv->getRes() + get<0>(params);
+ ifstream inputFileStream;
+ inputFileStream.open(inputFile, ifstream::in);
+ ASSERT_TRUE(inputFileStream.is_open()) << "Failed to open: " << inputFile;
+
+ struct stat buf;
+ int8_t err = stat(inputFile.c_str(), &buf);
+ ASSERT_EQ(err, 0) << "Failed to get information for file: " << inputFile;
+
+ size_t fileSize = buf.st_size;
+ ASSERT_NE(fileSize, 0) << "Invalid file size found";
+
+ const uint8_t *volBuffer = new uint8_t[fileSize];
+ ASSERT_NE(volBuffer, nullptr) << "Failed to allocate VOL buffer of size: " << fileSize;
+
+ inputFileStream.read((char *)(volBuffer), fileSize);
+ ASSERT_EQ(inputFileStream.gcount(), fileSize)
+ << "Failed to read complete file, bytes read: " << inputFileStream.gcount();
+
+ int32_t width = get<1>(params);
+ int32_t height = get<2>(params);
+ int32_t volWidth = -1;
+ int32_t volHeight = -1;
+
+ bool status = ExtractDimensionsFromVOLHeader(volBuffer, fileSize, &volWidth, &volHeight);
+ ASSERT_TRUE(status)
+ << "Failed to get VOL dimensions from function: ExtractDimensionsFromVOLHeader()";
+
+ ASSERT_EQ(volWidth, width) << "Expected width: " << width << "Found: " << volWidth;
+
+ ASSERT_EQ(volHeight, height) << "Expected height: " << height << "Found: " << volHeight;
+
+ delete[] volBuffer;
+}
+
+TEST_P(AVCDimensionTest, DimensionTest) {
+ int32_t numNalUnits = 0;
+ int32_t avcWidth = -1;
+ int32_t avcHeight = -1;
+ string line;
+ string type;
+ size_t chunkLength;
+ while (getline(mInfoFileStream, line)) {
+ istringstream stringLine(line);
+ stringLine >> type >> chunkLength;
+ ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
+
+ const uint8_t *data = new uint8_t[chunkLength];
+ ASSERT_NE(data, nullptr) << "Failed to create a data buffer of size: " << chunkLength;
+
+ const uint8_t *nalStart;
+ size_t nalSize;
+
+ mInputFileStream.read((char *)data, chunkLength);
+ ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
+ << "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
+
+ size_t smallBufferSize = kSmallBufferSize;
+ const uint8_t *sanityData = new uint8_t[smallBufferSize];
+ memcpy((void *)sanityData, (void *)data, smallBufferSize);
+
+ status_t result = getNextNALUnit(&sanityData, &smallBufferSize, &nalStart, &nalSize, true);
+ ASSERT_EQ(result, -EAGAIN) << "Invalid result found when wrong NAL unit passed";
+
+ while (!getNextNALUnit(&data, &chunkLength, &nalStart, &nalSize, true)) {
+ numNalUnits++;
+ // Check if it's an SPS
+ if ((nalStart[0] & kSPSmask) != kSPSStartCode) continue;
+ ASSERT_TRUE(nalSize > 0) << "NAL unit size must be greater than 0";
+
+ sp<ABuffer> spsBuffer = new ABuffer(nalSize);
+ ASSERT_NE(spsBuffer, nullptr) << "ABuffer returned null for size: " << nalSize;
+
+ memcpy(spsBuffer->data(), nalStart, nalSize);
+ FindAVCDimensions(spsBuffer, &avcWidth, &avcHeight);
+ spsBuffer.clear();
+ ASSERT_EQ(avcWidth, mFrameWidth)
+ << "Expected width: " << mFrameWidth << "Found: " << avcWidth;
+
+ ASSERT_EQ(avcHeight, mFrameHeight)
+ << "Expected height: " << mFrameHeight << "Found: " << avcHeight;
+ }
+ delete[] data;
+ }
+ if (mNalUnitsExpected < 0) {
+ ASSERT_GT(numNalUnits, 0) << "Failed to find an NAL Unit";
+ } else {
+ ASSERT_EQ(numNalUnits, mNalUnitsExpected)
+ << "Expected number of NAL units: " << mNalUnitsExpected
+ << " found: " << numNalUnits;
+ }
+}
+
+TEST_P(AvccBoxTest, AvccBoxValidationTest) {
+ int32_t avcWidth = -1;
+ int32_t avcHeight = -1;
+ int32_t accessUnitLength = 0;
+ int32_t profile = -1;
+ int32_t level = -1;
+ string line;
+ string type;
+ size_t chunkLength;
+ while (getline(mInfoFileStream, line)) {
+ istringstream stringLine(line);
+ stringLine >> type >> chunkLength;
+
+ if (type.compare("SPS") && type.compare("PPS")) continue;
+ ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
+
+ accessUnitLength += chunkLength;
+
+ if (!type.compare("SPS")) {
+ const uint8_t *data = new uint8_t[chunkLength];
+ ASSERT_NE(data, nullptr) << "Failed to create a data buffer of size: " << chunkLength;
+
+ const uint8_t *nalStart;
+ size_t nalSize;
+
+ mInputFileStream.read((char *)data, (uint32_t)chunkLength);
+ ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
+ << "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
+
+ while (!getNextNALUnit(&data, &chunkLength, &nalStart, &nalSize, true)) {
+ // Check if it's an SPS
+ ASSERT_TRUE(nalSize > 0 && (nalStart[0] & kSPSmask) == kSPSStartCode)
+ << "Failed to get SPS";
+
+ ASSERT_GE(nalSize, 4) << "SPS size must be greater than or equal to 4";
+
+ profile = nalStart[1];
+ level = nalStart[3];
+ }
+ delete[] data;
+ }
+ }
+ const uint8_t *accessUnitData = new uint8_t[accessUnitLength];
+ ASSERT_NE(accessUnitData, nullptr) << "Failed to create a buffer of size: " << accessUnitLength;
+
+ mInputFileStream.seekg(0, ios::beg);
+ mInputFileStream.read((char *)accessUnitData, accessUnitLength);
+ ASSERT_EQ(mInputFileStream.gcount(), accessUnitLength)
+ << "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
+
+ sp<ABuffer> accessUnit = new ABuffer(accessUnitLength);
+ ASSERT_NE(accessUnit, nullptr)
+ << "Failed to create an android data buffer of size: " << accessUnitLength;
+
+ memcpy(accessUnit->data(), accessUnitData, accessUnitLength);
+ sp<ABuffer> csdDataBuffer = MakeAVCCodecSpecificData(accessUnit, &avcWidth, &avcHeight);
+ ASSERT_NE(csdDataBuffer, nullptr) << "No data returned from MakeAVCCodecSpecificData()";
+
+ ASSERT_EQ(avcWidth, mFrameWidth) << "Expected width: " << mFrameWidth << "Found: " << avcWidth;
+
+ ASSERT_EQ(avcHeight, mFrameHeight)
+ << "Expected height: " << mFrameHeight << "Found: " << avcHeight;
+
+ uint8_t *csdData = csdDataBuffer->data();
+ ASSERT_EQ(*csdData, kConfigVersion) << "Invalid configuration version";
+
+ ASSERT_GE(csdDataBuffer->size(), 4) << "CSD data size must be greater than or equal to 4";
+
+ ASSERT_EQ(*(csdData + 1), profile)
+ << "Expected AVC profile: " << profile << " found: " << *(csdData + 1);
+
+ ASSERT_EQ(*(csdData + 3), level)
+ << "Expected AVC level: " << level << " found: " << *(csdData + 3);
+ csdDataBuffer.clear();
+ delete[] accessUnitData;
+ accessUnit.clear();
+}
+
+TEST_P(AVCFrameTest, FrameTest) {
+ string line;
+ string type;
+ size_t chunkLength;
+ int32_t frameLayerID;
+ while (getline(mInfoFileStream, line)) {
+ uint32_t layerID = 0;
+ istringstream stringLine(line);
+ stringLine >> type >> chunkLength >> frameLayerID;
+ ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
+
+ char *data = new char[chunkLength];
+ ASSERT_NE(data, nullptr) << "Failed to allocation data buffer of size: " << chunkLength;
+
+ mInputFileStream.read(data, chunkLength);
+ ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
+ << "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
+
+ if (!type.compare("IDR")) {
+ bool isIDR = IsIDR((uint8_t *)data, chunkLength);
+ ASSERT_TRUE(isIDR);
+
+ layerID = FindAVCLayerId((uint8_t *)data, chunkLength);
+ ASSERT_EQ(layerID, frameLayerID) << "Wrong layer ID found";
+ } else if (!type.compare("P") || !type.compare("B")) {
+ sp<ABuffer> accessUnit = new ABuffer(chunkLength);
+ ASSERT_NE(accessUnit, nullptr) << "Unable to create access Unit";
+
+ memcpy(accessUnit->data(), data, chunkLength);
+ bool isReferenceFrame = IsAVCReferenceFrame(accessUnit);
+ ASSERT_TRUE(isReferenceFrame);
+
+ accessUnit.clear();
+ layerID = FindAVCLayerId((uint8_t *)data, chunkLength);
+ ASSERT_EQ(layerID, frameLayerID) << "Wrong layer ID found";
+ }
+ delete[] data;
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, MpegAudioUnitTest,
+ ::testing::Values(make_tuple(0xFFFB9204, 418, 44100, 2, 128, 1152),
+ make_tuple(0xFFFB7604, 289, 48000, 2, 96, 1152),
+ make_tuple(0xFFFE5604, 164, 48000, 2, 160, 384)));
+
+// Info File contains the type and length for each chunk/frame
+INSTANTIATE_TEST_SUITE_P(
+ AVCUtilsTestAll, AVCDimensionTest,
+ ::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
+ "crowd_8x8p50f32_200kbps_bp.info", 8, 8, 11),
+ make_tuple("crowd_640x360p24f300_1000kbps_bp.h264",
+ "crowd_640x360p24f300_1000kbps_bp.info", 640, 360, 11),
+ make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
+ "crowd_1280x720p30f300_5000kbps_bp.info", 1280, 720, 12),
+ make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
+ "crowd_1920x1080p50f300_12000kbps_bp.info", 1920, 1080, 14),
+ make_tuple("crowd_3840x2160p60f300_68000kbps_bp.h264",
+ "crowd_3840x2160p60f300_68000kbps_bp.info", 3840, 2160, 14)));
+
+// Info File contains the type and length for each chunk/frame
+INSTANTIATE_TEST_SUITE_P(
+ AVCUtilsTestAll, AvccBoxTest,
+ ::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
+ "crowd_8x8p50f32_200kbps_bp.info", 8, 8, 11),
+ make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
+ "crowd_1280x720p30f300_5000kbps_bp.info", 1280, 720, 12),
+ make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
+ "crowd_1920x1080p50f300_12000kbps_bp.info", 1920, 1080, 14)));
+
+// Info File contains the type and length for each chunk/frame
+INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, VOLDimensionTest,
+ ::testing::Values(make_tuple("volData_720_480", 720, 480),
+ make_tuple("volData_1280_720", 1280, 720),
+ make_tuple("volData_1920_1080", 1920, 1080)));
+
+// Info File contains the type, length and layer ID for each chunk/frame
+INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, AVCFrameTest,
+ ::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
+ "crowd_8x8p50f32_200kbps_bp.info"),
+ make_tuple("crowd_640x360p24f300_1000kbps_bp.h264",
+ "crowd_640x360p24f300_1000kbps_bp.info"),
+ make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
+ "crowd_1280x720p30f300_5000kbps_bp.info"),
+ make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
+ "crowd_1920x1080p50f300_12000kbps_bp.info"),
+ make_tuple("crowd_3840x2160p60f300_68000kbps_bp.h264",
+ "crowd_3840x2160p60f300_68000kbps_bp.info")));
+
+int main(int argc, char **argv) {
+ gEnv = new AVCUtilsTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/foundation/tests/AVCUtils/Android.bp b/media/libstagefright/foundation/tests/AVCUtils/Android.bp
new file mode 100644
index 0000000..5d0e481
--- /dev/null
+++ b/media/libstagefright/foundation/tests/AVCUtils/Android.bp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+cc_test {
+ name: "AVCUtilsUnitTest",
+ gtest: true,
+
+ srcs: [
+ "AVCUtilsUnitTest.cpp",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "liblog",
+ ],
+
+ static_libs: [
+ "libstagefright",
+ "libstagefright_foundation",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/libstagefright/foundation",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/foundation/tests/AVCUtils/AndroidTest.xml b/media/libstagefright/foundation/tests/AVCUtils/AndroidTest.xml
new file mode 100644
index 0000000..6a088a8
--- /dev/null
+++ b/media/libstagefright/foundation/tests/AVCUtils/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Test module config for AVC Utils unit tests">
+ <option name="test-suite-tag" value="AVCUtilsUnitTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="false" />
+ <option name="push" value="AVCUtilsUnitTest->/data/local/tmp/AVCUtilsUnitTest" />
+ <option name="push-file"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/foundation/tests/AVCUtils/AVCUtilsUnitTest.zip?unzip=true"
+ value="/data/local/tmp/AVCUtilsUnitTest/" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="AVCUtilsUnitTest" />
+ <option name="native-test-flag" value="-P /data/local/tmp/AVCUtilsUnitTest/" />
+ </test>
+</configuration>
diff --git a/media/libstagefright/foundation/tests/AVCUtils/README.md b/media/libstagefright/foundation/tests/AVCUtils/README.md
new file mode 100644
index 0000000..609d72e
--- /dev/null
+++ b/media/libstagefright/foundation/tests/AVCUtils/README.md
@@ -0,0 +1,39 @@
+## Media Testing ##
+---
+#### AVCUtils Test
+The AVC Utility Unit Test Suite validates the avc_utils librariy available in libstagefright/foundation.
+
+Run the following steps to build the test suite:
+```
+m AVCUtilsUnitTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/AVCUtilsUnitTest/AVCUtilsUnitTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/AVCUtilsUnitTest/AVCUtilsUnitTest /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/foundation/tests/AVCUtils/AVCUtilsUnitTest.zip). Download, unzip and push these files into device for testing.
+
+```
+adb push AVCUtilsUnitTest /data/local/tmp/
+```
+
+usage: AVCUtilsUnitTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/AVCUtilsUnitTest -P /data/local/tmp/AVCUtilsUnitTest/
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest AVCUtilsUnitTest -- --enable-module-dynamic-download=true
+```
diff --git a/media/libstagefright/foundation/tests/Android.bp b/media/libstagefright/foundation/tests/Android.bp
index f2157c9..9e67189 100644
--- a/media/libstagefright/foundation/tests/Android.bp
+++ b/media/libstagefright/foundation/tests/Android.bp
@@ -25,3 +25,32 @@
"Utils_test.cpp",
],
}
+
+cc_test {
+ name: "MetaDataBaseUnitTest",
+ test_suites: ["device-tests"],
+ gtest: true,
+
+ srcs: [
+ "MetaDataBaseUnitTest.cpp",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "liblog",
+ ],
+
+ static_libs: [
+ "libstagefright",
+ "libstagefright_foundation",
+ ],
+
+ header_libs: [
+ "libmedia_headers",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/media/libstagefright/foundation/tests/MetaDataBaseUnitTest.cpp b/media/libstagefright/foundation/tests/MetaDataBaseUnitTest.cpp
new file mode 100644
index 0000000..0aed4d2
--- /dev/null
+++ b/media/libstagefright/foundation/tests/MetaDataBaseUnitTest.cpp
@@ -0,0 +1,288 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fstream>
+
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaDataBase.h>
+
+constexpr int32_t kWidth1 = 1920;
+constexpr int32_t kHeight1 = 1080;
+constexpr int32_t kWidth2 = 1280;
+constexpr int32_t kHeight2 = 920;
+constexpr int32_t kWidth3 = 720;
+constexpr int32_t kHeight3 = 480;
+constexpr int32_t kProfile = 1;
+constexpr int32_t kLevel = 1;
+constexpr int32_t kPlatformValue = 1;
+
+// Rectangle margins
+constexpr int32_t kLeft = 100;
+constexpr int32_t kTop = 100;
+constexpr int32_t kRight = 100;
+constexpr int32_t kBottom = 100;
+
+constexpr int64_t kDurationUs = 60000000;
+
+constexpr float kCaptureRate = 30.0;
+
+namespace android {
+
+class MetaDataBaseUnitTest : public ::testing::Test {};
+
+TEST_F(MetaDataBaseUnitTest, CreateMetaDataBaseTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ // Testing copy constructor
+ MetaDataBase *metaDataCopy = metaData;
+ ASSERT_NE(metaDataCopy, nullptr) << "Failed to create meta data copy";
+
+ delete metaData;
+}
+
+TEST_F(MetaDataBaseUnitTest, SetAndFindDataTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ // Setting the different key-value pair type for first time, overwrite
+ // expected to be false
+ bool status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ ASSERT_FALSE(status) << "Initializing kKeyMIMEType, overwrite is expected to be false";
+
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Initializing kKeyWidth, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyHeight, kHeight1);
+ ASSERT_FALSE(status) << "Initializing kKeyHeight, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyVideoProfile, kProfile);
+ ASSERT_FALSE(status) << "Initializing kKeyVideoProfile, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyVideoLevel, kLevel);
+ ASSERT_FALSE(status) << "Initializing kKeyVideoLevel, overwrite is expected to be false";
+
+ status = metaData->setInt64(kKeyDuration, kDurationUs);
+ ASSERT_FALSE(status) << "Initializing kKeyDuration, overwrite is expected to be false";
+
+ status = metaData->setFloat(kKeyCaptureFramerate, kCaptureRate);
+ ASSERT_FALSE(status) << "Initializing kKeyCaptureFramerate, overwrite is expected to be false";
+
+ const int32_t *platform = &kPlatformValue;
+ status = metaData->setPointer(kKeyPlatformPrivate, (void *)platform);
+ ASSERT_FALSE(status) << "Initializing kKeyPlatformPrivate, overwrite is expected to be false";
+
+ status = metaData->setRect(kKeyCropRect, kLeft, kTop, kRight, kBottom);
+ ASSERT_FALSE(status) << "Initializing kKeyCropRect, overwrite is expected to be false";
+
+ // Dump to log for reference
+ metaData->dumpToLog();
+
+ // Find the data which was set
+ const char *mime;
+ status = metaData->findCString(kKeyMIMEType, &mime);
+ ASSERT_TRUE(status) << "kKeyMIMEType key does not exists in metadata";
+ ASSERT_STREQ(mime, MEDIA_MIMETYPE_VIDEO_AVC) << "Incorrect mime type returned";
+
+ int32_t width, height, profile, level;
+ status = metaData->findInt32(kKeyWidth, &width);
+ ASSERT_TRUE(status) << "kKeyWidth key does not exists in metadata";
+ ASSERT_EQ(width, kWidth1) << "Incorrect value of width returned";
+
+ status = metaData->findInt32(kKeyHeight, &height);
+ ASSERT_TRUE(status) << "kKeyHeight key does not exists in metadata";
+ ASSERT_EQ(height, kHeight1) << "Incorrect value of height returned";
+
+ status = metaData->findInt32(kKeyVideoProfile, &profile);
+ ASSERT_TRUE(status) << "kKeyVideoProfile key does not exists in metadata";
+ ASSERT_EQ(profile, kProfile) << "Incorrect value of profile returned";
+
+ status = metaData->findInt32(kKeyVideoLevel, &level);
+ ASSERT_TRUE(status) << "kKeyVideoLevel key does not exists in metadata";
+ ASSERT_EQ(level, kLevel) << "Incorrect value of level returned";
+
+ int64_t duration;
+ status = metaData->findInt64(kKeyDuration, &duration);
+ ASSERT_TRUE(status) << "kKeyDuration key does not exists in metadata";
+ ASSERT_EQ(duration, kDurationUs) << "Incorrect value of duration returned";
+
+ float frameRate;
+ status = metaData->findFloat(kKeyCaptureFramerate, &frameRate);
+ ASSERT_TRUE(status) << "kKeyCaptureFramerate key does not exists in metadata";
+ ASSERT_EQ(frameRate, kCaptureRate) << "Incorrect value of captureFrameRate returned";
+
+ int32_t top, bottom, left, right;
+ status = metaData->findRect(kKeyCropRect, &left, &top, &right, &bottom);
+ ASSERT_TRUE(status) << "kKeyCropRect key does not exists in metadata";
+ ASSERT_EQ(left, kLeft) << "Incorrect value of left margin returned";
+ ASSERT_EQ(top, kTop) << "Incorrect value of top margin returned";
+ ASSERT_EQ(right, kRight) << "Incorrect value of right margin returned";
+ ASSERT_EQ(bottom, kBottom) << "Incorrect value of bottom margin returned";
+
+ void *platformValue;
+ status = metaData->findPointer(kKeyPlatformPrivate, &platformValue);
+ ASSERT_TRUE(status) << "kKeyPlatformPrivate key does not exists in metadata";
+ ASSERT_EQ(platformValue, &kPlatformValue) << "Incorrect value of pointer returned";
+
+ // Check for the key which is not added to metadata
+ int32_t angle;
+ status = metaData->findInt32(kKeyRotation, &angle);
+ ASSERT_FALSE(status) << "Value for an invalid key is returned when the key is not set";
+
+ delete (metaData);
+}
+
+TEST_F(MetaDataBaseUnitTest, OverWriteFunctionalityTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ // set/set/read to check first overwrite operation
+ bool status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ ASSERT_FALSE(status) << "Initializing kKeyMIMEType, overwrite is expected to be false";
+ // Overwrite the value
+ status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+ ASSERT_TRUE(status) << "Setting kKeyMIMEType again, overwrite is expected to be true";
+ // Check the value
+ const char *mime;
+ status = metaData->findCString(kKeyMIMEType, &mime);
+ ASSERT_TRUE(status) << "kKeyMIMEType key does not exists in metadata";
+ ASSERT_STREQ(mime, MEDIA_MIMETYPE_VIDEO_HEVC) << "Mime value is not overwritten";
+
+ // set/set/set/read to check second overwrite operation
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Initializing kKeyWidth, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyHeight, kHeight1);
+ ASSERT_FALSE(status) << "Initializing kKeyHeight, overwrite is expected to be false";
+ // Overwrite the value
+ status = metaData->setInt32(kKeyWidth, kWidth2);
+ ASSERT_TRUE(status) << "Setting kKeyWidth again, overwrite is expected to be true";
+ status = metaData->setInt32(kKeyHeight, kHeight2);
+ ASSERT_TRUE(status) << "Setting kKeyHeight again, overwrite is expected to be true";
+ // Overwrite the value again
+ status = metaData->setInt32(kKeyWidth, kWidth3);
+ ASSERT_TRUE(status) << "Setting kKeyWidth again, overwrite is expected to be true";
+ status = metaData->setInt32(kKeyHeight, kHeight3);
+ ASSERT_TRUE(status) << "Setting kKeyHeight again, overwrite is expected to be true";
+ // Check the value
+ int32_t width, height;
+ status = metaData->findInt32(kKeyWidth, &width);
+ ASSERT_TRUE(status) << "kKeyWidth key does not exists in metadata";
+ ASSERT_EQ(width, kWidth3) << "Value of width is not overwritten";
+
+ status = metaData->findInt32(kKeyHeight, &height);
+ ASSERT_TRUE(status) << "kKeyHeight key does not exists in metadata";
+ ASSERT_EQ(height, kHeight3) << "Value of height is not overwritten";
+
+ delete (metaData);
+}
+
+TEST_F(MetaDataBaseUnitTest, RemoveKeyTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ bool status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ ASSERT_FALSE(status) << "Initializing kKeyMIMEType, overwrite is expected to be false";
+ // Query the key
+ status = metaData->hasData(kKeyMIMEType);
+ ASSERT_TRUE(status) << "MetaData does not have the mime key";
+
+ status = metaData->remove(kKeyMIMEType);
+ ASSERT_TRUE(status) << "Failed to remove the kKeyMIMEType key";
+
+ // Query the key
+ status = metaData->hasData(kKeyMIMEType);
+ ASSERT_FALSE(status) << "MetaData has mime key after removing it, expected to be false";
+
+ // Remove the non existing key
+ status = metaData->remove(kKeyMIMEType);
+ ASSERT_FALSE(status) << "Removed the non existing key";
+
+ // Check overwriting the removed key
+ metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+ ASSERT_FALSE(status) << "Overwrite should be false since the key was removed";
+
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Initializing kKeyWidth, overwrite is expected to be false";
+
+ // Clear whole metadata
+ metaData->clear();
+
+ // Check finding key after clearing the metadata
+ int32_t width;
+ status = metaData->findInt32(kKeyWidth, &width);
+ ASSERT_FALSE(status) << "MetaData found kKeyWidth key after clearing all the items in it, "
+ "expected to be false";
+
+ // Query the key
+ status = metaData->hasData(kKeyWidth);
+ ASSERT_FALSE(status)
+ << "MetaData has width key after clearing all the items in it, expected to be false";
+
+ status = metaData->hasData(kKeyMIMEType);
+ ASSERT_FALSE(status)
+ << "MetaData has mime key after clearing all the items in it, expected to be false";
+
+ // Check removing key after clearing the metadata
+ status = metaData->remove(kKeyMIMEType);
+ ASSERT_FALSE(status) << "Removed the key, after clearing the metadata";
+
+ // Checking set after clearing the metadata
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Overwrite should be false since the metadata was cleared";
+
+ metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+ ASSERT_FALSE(status) << "Overwrite should be false since the metadata was cleared";
+
+ delete (metaData);
+}
+
+TEST_F(MetaDataBaseUnitTest, ConvertToStringTest) {
+ MetaDataBase *metaData = new MetaDataBase();
+ ASSERT_NE(metaData, nullptr) << "Failed to create meta data";
+
+ String8 info = metaData->toString();
+ ASSERT_EQ(info.length(), 0) << "Empty MetaData length is non-zero: " << info.length();
+
+ bool status = metaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ ASSERT_FALSE(status) << "Initializing kKeyMIMEType, overwrite is expected to be false";
+
+ status = metaData->setInt32(kKeyWidth, kWidth1);
+ ASSERT_FALSE(status) << "Initializing kKeyWidth, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyHeight, kHeight1);
+ ASSERT_FALSE(status) << "Initializing kKeyHeight, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyVideoProfile, kProfile);
+ ASSERT_FALSE(status) << "Initializing kKeyVideoProfile, overwrite is expected to be false";
+ status = metaData->setInt32(kKeyVideoLevel, kLevel);
+ ASSERT_FALSE(status) << "Initializing kKeyVideoLevel, overwrite is expected to be false";
+
+ info = metaData->toString();
+ ASSERT_GT(info.length(), 0) << "MetaData contains no information";
+
+ // Dump to log for reference
+ metaData->dumpToLog();
+
+ // Clear whole metadata
+ metaData->clear();
+
+ info = metaData->toString();
+ ASSERT_EQ(info.length(), 0) << "MetaData length is non-zero after clearing it: "
+ << info.length();
+
+ delete (metaData);
+}
+
+} // namespace android
diff --git a/media/libstagefright/foundation/tests/OpusHeader/Android.bp b/media/libstagefright/foundation/tests/OpusHeader/Android.bp
new file mode 100644
index 0000000..c1251a8
--- /dev/null
+++ b/media/libstagefright/foundation/tests/OpusHeader/Android.bp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+cc_test {
+ name: "OpusHeaderTest",
+ gtest: true,
+
+ srcs: [
+ "OpusHeaderTest.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ ],
+
+ static_libs: [
+ "libstagefright_foundation",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ },
+}
diff --git a/media/libstagefright/foundation/tests/OpusHeader/AndroidTest.xml b/media/libstagefright/foundation/tests/OpusHeader/AndroidTest.xml
new file mode 100644
index 0000000..afee16a
--- /dev/null
+++ b/media/libstagefright/foundation/tests/OpusHeader/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Test module config for opus header unit tests">
+ <option name="test-suite-tag" value="OpusHeaderTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="OpusHeaderTest->/data/local/tmp/OpusHeaderTest" />
+ <option name="push-file"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/foundation/tests/OpusHeader/OpusHeader.zip?unzip=true"
+ value="/data/local/tmp/OpusHeaderTestRes/" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="OpusHeaderTest" />
+ <option name="native-test-flag" value="-P /data/local/tmp/OpusHeaderTestRes/" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/media/libstagefright/foundation/tests/OpusHeader/OpusHeaderTest.cpp b/media/libstagefright/foundation/tests/OpusHeader/OpusHeaderTest.cpp
new file mode 100644
index 0000000..e39c915
--- /dev/null
+++ b/media/libstagefright/foundation/tests/OpusHeader/OpusHeaderTest.cpp
@@ -0,0 +1,342 @@
+/*
+ * 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 "OpusHeaderTest"
+#include <utils/Log.h>
+
+#include <fstream>
+#include <stdio.h>
+#include <string.h>
+
+#include <media/stagefright/foundation/OpusHeader.h>
+
+#include "OpusHeaderTestEnvironment.h"
+
+using namespace android;
+
+#define OUTPUT_FILE_NAME "/data/local/tmp/OpusOutput"
+
+// Opus in WebM is a well-known, yet under-documented, format. The codec private data
+// of the track is an Opus Ogg header (https://tools.ietf.org/html/rfc7845#section-5.1)
+// channel mapping offset in opus header
+constexpr size_t kOpusHeaderStreamMapOffset = 21;
+constexpr size_t kMaxOpusHeaderSize = 100;
+// AOPUSHDR + AOPUSHDRLength +
+// (8 + 8 ) +
+// Header(csd) + num_streams + num_coupled + 1
+// (19 + 1 + 1 + 1) +
+// AOPUSDLY + AOPUSDLYLength + DELAY + AOPUSPRL + AOPUSPRLLength + PRL
+// (8 + 8 + 8 + 8 + 8 + 8)
+// = 86
+constexpr size_t kOpusHeaderChannelMapOffset = 86;
+constexpr uint32_t kOpusSampleRate = 48000;
+constexpr uint64_t kOpusSeekPrerollNs = 80000000;
+constexpr int64_t kNsecPerSec = 1000000000ll;
+
+// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
+// mappings for up to 8 channels. This information is part of the Vorbis I
+// Specification:
+// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
+constexpr int kMaxChannels = 8;
+constexpr uint8_t kOpusChannelMap[kMaxChannels][kMaxChannels] = {
+ {0},
+ {0, 1},
+ {0, 2, 1},
+ {0, 1, 2, 3},
+ {0, 4, 1, 2, 3},
+ {0, 4, 1, 2, 3, 5},
+ {0, 4, 1, 2, 3, 5, 6},
+ {0, 6, 1, 2, 3, 4, 5, 7},
+};
+
+static OpusHeaderTestEnvironment *gEnv = nullptr;
+
+class OpusHeaderTest {
+ public:
+ OpusHeaderTest() : mInputBuffer(nullptr) {}
+
+ ~OpusHeaderTest() {
+ if (mEleStream.is_open()) mEleStream.close();
+ if (mInputBuffer) {
+ free(mInputBuffer);
+ mInputBuffer = nullptr;
+ }
+ }
+ ifstream mEleStream;
+ uint8_t *mInputBuffer;
+};
+
+class OpusHeaderParseTest : public OpusHeaderTest,
+ public ::testing::TestWithParam<
+ tuple<string /* InputFileName */, int32_t /* ChannelCount */,
+ bool /* isHeaderValid */, bool /* isCodecDelayValid */,
+ bool /* isSeekPreRollValid */, bool /* isInputValid */>> {
+};
+
+class OpusHeaderWriteTest
+ : public OpusHeaderTest,
+ public ::testing::TestWithParam<tuple<int32_t /* ChannelCount */, int32_t /* skipSamples */,
+ string /* referenceFile */>> {};
+
+TEST_P(OpusHeaderWriteTest, WriteTest) {
+ tuple<int32_t, int32_t, string> params = GetParam();
+ OpusHeader writtenHeader;
+ memset(&writtenHeader, 0, sizeof(writtenHeader));
+ int32_t channels = get<0>(params);
+ writtenHeader.channels = channels;
+ writtenHeader.num_streams = channels;
+ writtenHeader.channel_mapping = ((channels > 8) ? 255 : (channels > 2));
+ int32_t skipSamples = get<1>(params);
+ string referenceFileName = gEnv->getRes() + get<2>(params);
+ writtenHeader.skip_samples = skipSamples;
+ uint64_t codecDelayNs = skipSamples * kNsecPerSec / kOpusSampleRate;
+ uint8_t headerData[kMaxOpusHeaderSize];
+ int32_t headerSize = WriteOpusHeaders(writtenHeader, kOpusSampleRate, headerData,
+ sizeof(headerData), codecDelayNs, kOpusSeekPrerollNs);
+ ASSERT_GT(headerSize, 0) << "failed to generate Opus header";
+ ASSERT_LE(headerSize, kMaxOpusHeaderSize)
+ << "Invalid header written. Header size can't exceed kMaxOpusHeaderSize";
+
+ ofstream ostrm;
+ ostrm.open(OUTPUT_FILE_NAME, ofstream::binary);
+ ASSERT_TRUE(ostrm.is_open()) << "Failed to open output file " << OUTPUT_FILE_NAME;
+ ostrm.write(reinterpret_cast<char *>(headerData), headerSize);
+ ostrm.close();
+
+ mEleStream.open(referenceFileName, ifstream::binary);
+ ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open referenceFileName " << get<2>(params);
+
+ struct stat buf;
+ int32_t statStatus = stat(referenceFileName.c_str(), &buf);
+ ASSERT_EQ(statStatus, 0) << "Unable to get file properties";
+
+ size_t fileSize = buf.st_size;
+ mInputBuffer = (uint8_t *)malloc(fileSize);
+ ASSERT_NE(mInputBuffer, nullptr) << "Insufficient memory. Malloc failed for size " << fileSize;
+
+ mEleStream.read(reinterpret_cast<char *>(mInputBuffer), fileSize);
+ ASSERT_EQ(mEleStream.gcount(), fileSize) << "mEleStream.gcount() != bytesCount";
+
+ ASSERT_EQ(fileSize, headerSize)
+ << "Mismatch in size between header generated and reference header";
+ int32_t match = memcmp(reinterpret_cast<char *>(mInputBuffer),
+ reinterpret_cast<char *>(headerData), fileSize);
+ ASSERT_EQ(match, 0) << "Opus header does not match reference file: " << referenceFileName;
+
+ size_t opusHeadSize = 0;
+ size_t codecDelayBufSize = 0;
+ size_t seekPreRollBufSize = 0;
+ void *opusHeadBuf = nullptr;
+ void *codecDelayBuf = nullptr;
+ void *seekPreRollBuf = nullptr;
+ bool status = GetOpusHeaderBuffers(headerData, headerSize, &opusHeadBuf, &opusHeadSize,
+ &codecDelayBuf, &codecDelayBufSize, &seekPreRollBuf,
+ &seekPreRollBufSize);
+ ASSERT_TRUE(status) << "Encountered error in GetOpusHeaderBuffers";
+
+ uint64_t value = *((uint64_t *)codecDelayBuf);
+ ASSERT_EQ(value, codecDelayNs);
+
+ value = *((uint64_t *)seekPreRollBuf);
+ ASSERT_EQ(value, kOpusSeekPrerollNs);
+
+ OpusHeader parsedHeader;
+ status = ParseOpusHeader((uint8_t *)opusHeadBuf, opusHeadSize, &parsedHeader);
+ ASSERT_TRUE(status) << "Encountered error while Parsing Opus Header.";
+
+ ASSERT_EQ(writtenHeader.channels, parsedHeader.channels)
+ << "Invalid header generated. Mismatch between channel counts";
+
+ ASSERT_EQ(writtenHeader.skip_samples, parsedHeader.skip_samples)
+ << "Mismatch between no of skipSamples written "
+ "and no of skipSamples got after parsing";
+
+ ASSERT_EQ(writtenHeader.channel_mapping, parsedHeader.channel_mapping)
+ << "Mismatch between channelMapping written "
+ "and channelMapping got after parsing";
+
+ if (parsedHeader.channel_mapping) {
+ ASSERT_GT(parsedHeader.channels, 2);
+ ASSERT_EQ(writtenHeader.num_streams, parsedHeader.num_streams)
+ << "Invalid header generated. Mismatch between channel counts";
+
+ ASSERT_EQ(writtenHeader.num_coupled, parsedHeader.num_coupled)
+ << "Invalid header generated. Mismatch between channel counts";
+
+ ASSERT_EQ(parsedHeader.num_coupled + parsedHeader.num_streams, parsedHeader.channels);
+
+ ASSERT_LE(parsedHeader.num_coupled, parsedHeader.num_streams)
+ << "Invalid header generated. Number of coupled streams cannot be greater than "
+ "number "
+ "of streams.";
+
+ ASSERT_EQ(headerSize, kOpusHeaderChannelMapOffset + writtenHeader.channels)
+ << "Invalid header written. Header size should be equal to 86 + "
+ "writtenHeader.channels";
+
+ uint8_t mappedChannelNumber;
+ for (int32_t channelNumber = 0; channelNumber < channels; channelNumber++) {
+ mappedChannelNumber = *(reinterpret_cast<uint8_t *>(opusHeadBuf) +
+ kOpusHeaderStreamMapOffset + channelNumber);
+ ASSERT_LT(mappedChannelNumber, channels) << "Invalid header generated. Channel mapping "
+ "cannot be greater than channel count.";
+
+ ASSERT_EQ(mappedChannelNumber, kOpusChannelMap[channels - 1][channelNumber])
+ << "Invalid header generated. Channel mapping is not as per specification.";
+ }
+ } else {
+ ASSERT_LE(parsedHeader.channels, 2);
+ }
+}
+
+TEST_P(OpusHeaderParseTest, ParseTest) {
+ tuple<string, int32_t, bool, bool, bool, bool> params = GetParam();
+ string inputFileName = gEnv->getRes() + get<0>(params);
+ mEleStream.open(inputFileName, ifstream::binary);
+ ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open inputfile " << get<0>(params);
+ bool isHeaderValid = get<2>(params);
+ bool isCodecDelayValid = get<3>(params);
+ bool isSeekPreRollValid = get<4>(params);
+ bool isInputValid = get<5>(params);
+
+ struct stat buf;
+ stat(inputFileName.c_str(), &buf);
+ size_t fileSize = buf.st_size;
+ mInputBuffer = (uint8_t *)malloc(fileSize);
+ ASSERT_NE(mInputBuffer, nullptr) << "Insufficient memory. Malloc failed for size " << fileSize;
+
+ mEleStream.read(reinterpret_cast<char *>(mInputBuffer), fileSize);
+ ASSERT_EQ(mEleStream.gcount(), fileSize) << "mEleStream.gcount() != bytesCount";
+
+ OpusHeader header;
+ size_t opusHeadSize = 0;
+ size_t codecDelayBufSize = 0;
+ size_t seekPreRollBufSize = 0;
+ void *opusHeadBuf = nullptr;
+ void *codecDelayBuf = nullptr;
+ void *seekPreRollBuf = nullptr;
+ bool status = GetOpusHeaderBuffers(mInputBuffer, fileSize, &opusHeadBuf, &opusHeadSize,
+ &codecDelayBuf, &codecDelayBufSize, &seekPreRollBuf,
+ &seekPreRollBufSize);
+ if (!isHeaderValid) {
+ ASSERT_EQ(opusHeadBuf, nullptr);
+ } else {
+ ASSERT_NE(opusHeadBuf, nullptr);
+ }
+ if (!isCodecDelayValid) {
+ ASSERT_EQ(codecDelayBuf, nullptr);
+ } else {
+ ASSERT_NE(codecDelayBuf, nullptr);
+ }
+ if (!isSeekPreRollValid) {
+ ASSERT_EQ(seekPreRollBuf, nullptr);
+ } else {
+ ASSERT_NE(seekPreRollBuf, nullptr);
+ }
+ if (!status) {
+ ASSERT_FALSE(isInputValid) << "GetOpusHeaderBuffers failed";
+ return;
+ }
+
+ status = ParseOpusHeader((uint8_t *)opusHeadBuf, opusHeadSize, &header);
+
+ if (status) {
+ ASSERT_TRUE(isInputValid) << "Parse opus header didn't fail for invalid input";
+ } else {
+ ASSERT_FALSE(isInputValid);
+ return;
+ }
+
+ int32_t channels = get<1>(params);
+ ASSERT_EQ(header.channels, channels) << "Parser returned invalid channel count";
+ ASSERT_LE(header.channels, kMaxChannels);
+
+ ASSERT_LE(header.num_coupled, header.num_streams)
+ << "Invalid header generated. Number of coupled streams cannot be greater than number "
+ "of streams.";
+
+ ASSERT_EQ(header.num_coupled + header.num_streams, header.channels);
+
+ if (header.channel_mapping) {
+ uint8_t mappedChannelNumber;
+ for (int32_t channelNumber = 0; channelNumber < channels; channelNumber++) {
+ mappedChannelNumber = *(reinterpret_cast<uint8_t *>(opusHeadBuf) +
+ kOpusHeaderStreamMapOffset + channelNumber);
+ ASSERT_LT(mappedChannelNumber, channels)
+ << "Invalid header. Channel mapping cannot be greater than channel count.";
+
+ ASSERT_EQ(mappedChannelNumber, kOpusChannelMap[channels - 1][channelNumber])
+ << "Invalid header generated. Channel mapping "
+ "is not as per specification.";
+ }
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ OpusHeaderTestAll, OpusHeaderWriteTest,
+ ::testing::Values(make_tuple(1, 312, "output_channels_1skipSamples_312.opus"),
+ make_tuple(2, 312, "output_channels_2skipSamples_312.opus"),
+ make_tuple(5, 312, "output_channels_5skipSamples_312.opus"),
+ make_tuple(6, 312, "output_channels_6skipSamples_312.opus"),
+ make_tuple(1, 0, "output_channels_1skipSamples_0.opus"),
+ make_tuple(2, 0, "output_channels_2skipSamples_0.opus"),
+ make_tuple(5, 0, "output_channels_5skipSamples_0.opus"),
+ make_tuple(6, 0, "output_channels_6skipSamples_0.opus"),
+ make_tuple(1, 624, "output_channels_1skipSamples_624.opus"),
+ make_tuple(2, 624, "output_channels_2skipSamples_624.opus"),
+ make_tuple(5, 624, "output_channels_5skipSamples_624.opus"),
+ make_tuple(6, 624, "output_channels_6skipSamples_624.opus")));
+
+INSTANTIATE_TEST_SUITE_P(
+ OpusHeaderTestAll, OpusHeaderParseTest,
+ ::testing::Values(
+ make_tuple("2ch_valid_size83B.opus", 2, true, true, true, true),
+ make_tuple("3ch_valid_size88B.opus", 3, true, true, true, true),
+ make_tuple("5ch_valid.opus", 5, true, false, false, true),
+ make_tuple("6ch_valid.opus", 6, true, false, false, true),
+ make_tuple("1ch_valid.opus", 1, true, false, false, true),
+ make_tuple("2ch_valid.opus", 2, true, false, false, true),
+ make_tuple("3ch_invalid_size.opus", 3, true, true, true, false),
+ make_tuple("3ch_invalid_streams.opus", 3, true, true, true, false),
+ make_tuple("5ch_invalid_channelmapping.opus", 5, true, false, false, false),
+ make_tuple("5ch_invalid_coupledstreams.opus", 5, true, false, false, false),
+ make_tuple("6ch_invalid_channelmapping.opus", 6, true, false, false, false),
+ make_tuple("9ch_invalid_channels.opus", 9, true, true, true, false),
+ make_tuple("2ch_invalid_header.opus", 2, false, false, false, false),
+ make_tuple("2ch_invalid_headerlength_16.opus", 2, false, false, false, false),
+ make_tuple("2ch_invalid_headerlength_256.opus", 2, false, false, false, false),
+ make_tuple("2ch_invalid_size.opus", 2, false, false, false, false),
+ make_tuple("3ch_invalid_channelmapping_0.opus", 3, true, true, true, false),
+ make_tuple("3ch_invalid_coupledstreams.opus", 3, true, true, true, false),
+ make_tuple("3ch_invalid_headerlength.opus", 3, true, true, true, false),
+ make_tuple("3ch_invalid_headerSize1.opus", 3, false, false, false, false),
+ make_tuple("3ch_invalid_headerSize2.opus", 3, false, false, false, false),
+ make_tuple("3ch_invalid_headerSize3.opus", 3, false, false, false, false),
+ make_tuple("3ch_invalid_nodelay.opus", 3, false, false, false, false),
+ make_tuple("3ch_invalid_nopreroll.opus", 3, false, false, false, false)));
+
+int main(int argc, char **argv) {
+ gEnv = new OpusHeaderTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGD("Opus Header Test Result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/foundation/tests/OpusHeader/OpusHeaderTestEnvironment.h b/media/libstagefright/foundation/tests/OpusHeader/OpusHeaderTestEnvironment.h
new file mode 100644
index 0000000..d0163c3
--- /dev/null
+++ b/media/libstagefright/foundation/tests/OpusHeader/OpusHeaderTestEnvironment.h
@@ -0,0 +1,73 @@
+/*
+ * 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 __OPUS_HEADER_TEST_ENVIRONMENT_H__
+#define __OPUS_HEADER_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class OpusHeaderTestEnvironment : public ::testing::Environment {
+ public:
+ OpusHeaderTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int OpusHeaderTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"path", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P': {
+ setRes(optarg);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __OPUS_HEADER_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/foundation/tests/OpusHeader/README.md b/media/libstagefright/foundation/tests/OpusHeader/README.md
new file mode 100644
index 0000000..860c827
--- /dev/null
+++ b/media/libstagefright/foundation/tests/OpusHeader/README.md
@@ -0,0 +1,39 @@
+## Media Testing ##
+---
+#### Opus Header
+The OpusHeader Test Suite validates the OPUS header available in libstagefright.
+
+Run the following steps to build the test suite:
+```
+m OpusHeaderTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/OpusHeaderTest/OpusHeaderTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/OpusHeaderTest/OpusHeaderTest /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/foundation/tests/OpusHeader/OpusHeader.zip). Download, unzip and push these files into device for testing.
+
+```
+adb push OpusHeader /data/local/tmp/
+```
+
+usage: OpusHeaderTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/OpusHeaderTest -P /data/local/tmp/OpusHeader/
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest OpusHeaderTest -- --enable-module-dynamic-download=true
+```
diff --git a/media/libstagefright/foundation/tests/colorutils/Android.bp b/media/libstagefright/foundation/tests/colorutils/Android.bp
new file mode 100644
index 0000000..d77f405
--- /dev/null
+++ b/media/libstagefright/foundation/tests/colorutils/Android.bp
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+cc_test {
+ name: "ColorUtilsTest",
+ gtest: true,
+
+ srcs: [
+ "ColorUtilsTest.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libmediandk",
+ ],
+
+ static_libs: [
+ "libstagefright_foundation",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/foundation/tests/colorutils/ColorUtilsTest.cpp b/media/libstagefright/foundation/tests/colorutils/ColorUtilsTest.cpp
new file mode 100644
index 0000000..0d802b4
--- /dev/null
+++ b/media/libstagefright/foundation/tests/colorutils/ColorUtilsTest.cpp
@@ -0,0 +1,773 @@
+/*
+ * 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 "ColorUtilsTest"
+#include <utils/Log.h>
+
+#include <gtest/gtest.h>
+
+#include <stdio.h>
+
+#include <media/NdkMediaFormat.h>
+#include <media/NdkMediaFormatPriv.h>
+#include <media/stagefright/MediaCodecConstants.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ColorUtils.h>
+
+const size_t kHDRBufferSize = 25;
+const uint16_t kHDRInfoTestValue1 = 420;
+const uint16_t kHDRInfoTestValue2 = 42069;
+
+using namespace android;
+
+typedef ColorAspects CA;
+
+class ColorRangeTest : public ::testing::TestWithParam</* ColorRange */ CA::Range> {
+ public:
+ ColorRangeTest() { mRange = GetParam(); };
+
+ CA::Range mRange;
+};
+
+class ColorTransferTest : public ::testing::TestWithParam</* ColorTransfer */ CA::Transfer> {
+ public:
+ ColorTransferTest() { mTransfer = GetParam(); };
+
+ CA::Transfer mTransfer;
+};
+
+class ColorStandardTest : public ::testing::TestWithParam<std::pair<
+ /* Primaries */ CA::Primaries,
+ /* MatrixCoeffs */ CA::MatrixCoeffs>> {
+ public:
+ ColorStandardTest() {
+ mPrimaries = GetParam().first;
+ mMatrixCoeffs = GetParam().second;
+ };
+
+ CA::Primaries mPrimaries;
+ CA::MatrixCoeffs mMatrixCoeffs;
+};
+
+class IsoToPlatformAspectsTest : public ::testing::TestWithParam<std::tuple<
+ /* Primaries */ CA::Primaries,
+ /* Transfer */ CA::Transfer,
+ /* MatrixCoeffs */ CA::MatrixCoeffs,
+ /* Standard */ int32_t,
+ /* Transfer */ int32_t>> {
+ public:
+ IsoToPlatformAspectsTest() {
+ mPrimaries = std::get<0>(GetParam());
+ mTransfer = std::get<1>(GetParam());
+ mMatrixCoeffs = std::get<2>(GetParam());
+ mPlatformStandard = std::get<3>(GetParam());
+ mPlatformTransfer = std::get<4>(GetParam());
+ };
+
+ CA::Primaries mPrimaries;
+ CA::Transfer mTransfer;
+ CA::MatrixCoeffs mMatrixCoeffs;
+ int32_t mPlatformStandard;
+ int32_t mPlatformTransfer;
+};
+
+class ColorAspectsTest : public ::testing::TestWithParam<std::tuple<
+ /* Primaries */ CA::Primaries,
+ /* ColorTransfer */ CA::Transfer,
+ /* MatrixCoeffs */ CA::MatrixCoeffs,
+ /* ColorRange */ CA::Range,
+ /* ColorStandard */ CA::Standard>> {
+ public:
+ ColorAspectsTest() {
+ mPrimaries = std::get<0>(GetParam());
+ mTransfer = std::get<1>(GetParam());
+ mMatrixCoeffs = std::get<2>(GetParam());
+ mRange = std::get<3>(GetParam());
+ mStandard = std::get<4>(GetParam());
+ };
+
+ CA::Primaries mPrimaries;
+ CA::Transfer mTransfer;
+ CA::MatrixCoeffs mMatrixCoeffs;
+ CA::Range mRange;
+ CA::Standard mStandard;
+};
+
+class DefaultColorAspectsTest : public ::testing::TestWithParam<std::tuple<
+ /* Width */ int32_t,
+ /* Height */ int32_t,
+ /* Primaries */ CA::Primaries,
+ /* MatrixCoeffs */ CA::MatrixCoeffs>> {
+ public:
+ DefaultColorAspectsTest() {
+ mWidth = std::get<0>(GetParam());
+ mHeight = std::get<1>(GetParam());
+ mPrimaries = std::get<2>(GetParam());
+ mMatrixCoeffs = std::get<3>(GetParam());
+ };
+
+ int32_t mWidth;
+ int32_t mHeight;
+ CA::Primaries mPrimaries;
+ CA::MatrixCoeffs mMatrixCoeffs;
+};
+
+class DataSpaceTest : public ::testing::TestWithParam<std::tuple<
+ /* ColorRange */ CA::Range,
+ /* Primaries */ CA::Primaries,
+ /* ColorTransfer */ CA::Transfer,
+ /* MatrixCoeffs */ CA::MatrixCoeffs,
+ /* v0_android_dataspace */ android_dataspace,
+ /* android_dataspace */ android_dataspace>> {
+ public:
+ DataSpaceTest() {
+ mRange = std::get<0>(GetParam());
+ mPrimaries = std::get<1>(GetParam());
+ mTransfer = std::get<2>(GetParam());
+ mMatrixCoeffs = std::get<3>(GetParam());
+ mDataSpaceV0 = std::get<4>(GetParam());
+ mDataSpace = std::get<5>(GetParam());
+ };
+
+ CA::Range mRange;
+ CA::Primaries mPrimaries;
+ CA::Transfer mTransfer;
+ CA::MatrixCoeffs mMatrixCoeffs;
+ android_dataspace mDataSpaceV0;
+ android_dataspace mDataSpace;
+};
+
+TEST_P(ColorRangeTest, WrapColorRangeTest) {
+ int32_t range = ColorUtils::wrapColorAspectsIntoColorRange(mRange);
+ CA::Range unwrappedRange;
+ status_t status = ColorUtils::unwrapColorAspectsFromColorRange(range, &unwrappedRange);
+ ASSERT_EQ(status, OK) << "unwrapping ColorAspects from ColorRange failed";
+ EXPECT_EQ(unwrappedRange, mRange) << "Returned ColorRange doesn't match";
+ ALOGV("toString test: Range: %s", asString(mRange, "default"));
+}
+
+TEST_P(ColorTransferTest, WrapColorTransferTest) {
+ int32_t transfer = ColorUtils::wrapColorAspectsIntoColorTransfer(mTransfer);
+ CA::Transfer unwrappedTransfer;
+ status_t status = ColorUtils::unwrapColorAspectsFromColorTransfer(transfer, &unwrappedTransfer);
+ ASSERT_EQ(status, OK) << "unwrapping ColorAspects from ColorTransfer failed";
+ EXPECT_EQ(unwrappedTransfer, mTransfer) << "Returned ColorTransfer doesn't match";
+ ALOGV("toString test: Transfer: %s", asString(mTransfer, "default"));
+}
+
+TEST_P(ColorStandardTest, WrapColorStandardTest) {
+ int32_t standard = ColorUtils::wrapColorAspectsIntoColorStandard(mPrimaries, mMatrixCoeffs);
+ CA::Primaries unwrappedPrimaries;
+ CA::MatrixCoeffs unwrappedMatrixCoeffs;
+ status_t status = ColorUtils::unwrapColorAspectsFromColorStandard(standard, &unwrappedPrimaries,
+ &unwrappedMatrixCoeffs);
+ ASSERT_EQ(status, OK) << "unwrapping ColorAspects from ColorStandard failed";
+ EXPECT_EQ(unwrappedPrimaries, mPrimaries) << "Returned primaries doesn't match";
+ EXPECT_EQ(unwrappedMatrixCoeffs, mMatrixCoeffs) << "Returned matrixCoeffs doesn't match";
+}
+
+TEST_P(ColorAspectsTest, PlatformAspectsTest) {
+ CA aspects;
+ aspects.mRange = mRange;
+ aspects.mPrimaries = mPrimaries;
+ aspects.mTransfer = mTransfer;
+ aspects.mMatrixCoeffs = mMatrixCoeffs;
+
+ int32_t range = -1;
+ int32_t standard = -1;
+ int32_t transfer = -1;
+ status_t status = ColorUtils::convertCodecColorAspectsToPlatformAspects(aspects, &range,
+ &standard, &transfer);
+ ASSERT_EQ(status, OK) << "Conversion of ColorAspects to PlatformAspects failed";
+
+ CA returnedAspects;
+ status = ColorUtils::convertPlatformColorAspectsToCodecAspects(range, standard, transfer,
+ returnedAspects);
+ ASSERT_EQ(status, OK) << "Conversion of PlatformAspects to ColorAspects failed";
+ EXPECT_EQ(returnedAspects.mRange, aspects.mRange)
+ << "range mismatch for conversion between PlatformAspects";
+ EXPECT_EQ(returnedAspects.mPrimaries, aspects.mPrimaries)
+ << "primaries mismatch for conversion between PlatformAspects";
+ EXPECT_EQ(returnedAspects.mTransfer, aspects.mTransfer)
+ << "transfer mismatch for conversion between PlatformAspects";
+ EXPECT_EQ(returnedAspects.mMatrixCoeffs, aspects.mMatrixCoeffs)
+ << "matrixCoeffs mismatch for conversion between PlatformAspects";
+}
+
+TEST_P(ColorAspectsTest, IsoAspectsTest) {
+ CA aspects;
+ aspects.mRange = mRange;
+ aspects.mPrimaries = mPrimaries;
+ aspects.mTransfer = mTransfer;
+ aspects.mMatrixCoeffs = mMatrixCoeffs;
+
+ int32_t primaries = -1;
+ int32_t colorTransfer = -1;
+ int32_t matrixCoeffs = -1;
+ bool fullRange = false;
+ ColorUtils::convertCodecColorAspectsToIsoAspects(aspects, &primaries, &colorTransfer,
+ &matrixCoeffs, &fullRange);
+
+ CA returnedAspects;
+ ColorUtils::convertIsoColorAspectsToCodecAspects(primaries, colorTransfer, matrixCoeffs,
+ fullRange, returnedAspects);
+ EXPECT_EQ(returnedAspects.mRange, aspects.mRange)
+ << "range mismatch for conversion between IsoAspects";
+ EXPECT_EQ(returnedAspects.mPrimaries, aspects.mPrimaries)
+ << "primaries mismatch for conversion between IsoAspects";
+ EXPECT_EQ(returnedAspects.mTransfer, aspects.mTransfer)
+ << "transfer mismatch for conversion between IsoAspects";
+ EXPECT_EQ(returnedAspects.mMatrixCoeffs, aspects.mMatrixCoeffs)
+ << "matrixCoeffs mismatch for conversion between IsoAspects";
+}
+
+TEST_P(IsoToPlatformAspectsTest, IsoAspectsToPlatformAspectsTest) {
+ CA aspects;
+ aspects.mPrimaries = mPrimaries;
+ aspects.mTransfer = mTransfer;
+ aspects.mMatrixCoeffs = mMatrixCoeffs;
+
+ int32_t isoPrimaries = -1;
+ int32_t isoTransfer = -1;
+ int32_t isoMatrixCoeffs = -1;
+ bool fullrange = false;
+ ColorUtils::convertCodecColorAspectsToIsoAspects(aspects, &isoPrimaries, &isoTransfer,
+ &isoMatrixCoeffs, &fullrange);
+
+ int32_t range = -1;
+ int32_t standard = -1;
+ int32_t transfer = -1;
+ ColorUtils::convertIsoColorAspectsToPlatformAspects(isoPrimaries, isoTransfer, isoMatrixCoeffs,
+ fullrange, &range, &standard, &transfer);
+ if (fullrange) {
+ EXPECT_EQ(range, ColorUtils::kColorRangeFull)
+ << "range incorrect converting to PlatformAspects";
+ }
+ EXPECT_EQ(standard, mPlatformStandard) << "standard incorrect converting to PlatformAspects";
+ EXPECT_EQ(transfer, mPlatformTransfer) << "transfer incorrect converting to PlatformAspects";
+}
+
+TEST_P(ColorAspectsTest, PackColorAspectsTest) {
+ CA aspects;
+ aspects.mRange = mRange;
+ aspects.mPrimaries = mPrimaries;
+ aspects.mTransfer = mTransfer;
+ aspects.mMatrixCoeffs = mMatrixCoeffs;
+ uint32_t packedColorAspects = ColorUtils::packToU32(aspects);
+
+ CA unpackedAspects = ColorUtils::unpackToColorAspects(packedColorAspects);
+ EXPECT_EQ(unpackedAspects.mRange, mRange) << "range mismatch after unpacking";
+ EXPECT_EQ(unpackedAspects.mPrimaries, mPrimaries) << "primaries mismatch after unpacking";
+ EXPECT_EQ(unpackedAspects.mTransfer, mTransfer) << "transfer mismatch after unpacking";
+ EXPECT_EQ(unpackedAspects.mMatrixCoeffs, mMatrixCoeffs)
+ << "matrixCoeffs mismatch after unpacking";
+ ALOGV("toString test: Standard: %s", asString(mStandard, "default"));
+}
+
+TEST_P(DefaultColorAspectsTest, DefaultColorAspectsTest) {
+ CA aspects;
+ aspects.mRange = CA::RangeUnspecified;
+ aspects.mPrimaries = CA::PrimariesUnspecified;
+ aspects.mMatrixCoeffs = CA::MatrixUnspecified;
+ aspects.mTransfer = CA::TransferUnspecified;
+
+ ColorUtils::setDefaultCodecColorAspectsIfNeeded(aspects, mWidth, mHeight);
+ EXPECT_EQ(aspects.mRange, CA::RangeLimited) << "range not set to default";
+ EXPECT_EQ(aspects.mPrimaries, mPrimaries) << "primaries not set to default";
+ EXPECT_EQ(aspects.mMatrixCoeffs, mMatrixCoeffs) << "matrixCoeffs not set to default";
+ EXPECT_EQ(aspects.mTransfer, CA::TransferSMPTE170M) << "transfer not set to default";
+}
+
+TEST_P(DataSpaceTest, DataSpaceTest) {
+ CA aspects;
+ aspects.mRange = mRange;
+ aspects.mPrimaries = mPrimaries;
+ aspects.mTransfer = mTransfer;
+ aspects.mMatrixCoeffs = mMatrixCoeffs;
+
+ android_dataspace dataSpace = ColorUtils::getDataSpaceForColorAspects(aspects, false);
+ EXPECT_EQ(dataSpace, mDataSpace) << "Returned incorrect dataspace";
+
+ bool status = ColorUtils::convertDataSpaceToV0(dataSpace);
+ ASSERT_TRUE(status) << "Returned v0 dataspace is not aspect-only";
+ EXPECT_EQ(dataSpace, mDataSpaceV0) << "Returned incorrect v0 dataspace";
+}
+
+TEST(ColorUtilsUnitTest, AspectsChangedTest) {
+ CA origAspects;
+ origAspects.mRange = CA::Range::RangeFull;
+ origAspects.mPrimaries = CA::Primaries::PrimariesBT709_5;
+ origAspects.mTransfer = CA::Transfer::TransferLinear;
+ origAspects.mMatrixCoeffs = CA::MatrixCoeffs::MatrixBT709_5;
+
+ CA aspects;
+ aspects.mRange = CA::Range::RangeFull;
+ aspects.mPrimaries = CA::Primaries::PrimariesBT709_5;
+ aspects.mTransfer = CA::Transfer::TransferLinear;
+ aspects.mMatrixCoeffs = CA::MatrixCoeffs::MatrixBT709_5;
+
+ bool status = ColorUtils::checkIfAspectsChangedAndUnspecifyThem(aspects, origAspects);
+ ASSERT_FALSE(status) << "ColorAspects comparison check failed";
+
+ aspects.mRange = CA::Range::RangeLimited;
+ status = ColorUtils::checkIfAspectsChangedAndUnspecifyThem(aspects, origAspects);
+ ASSERT_TRUE(status) << "ColorAspects comparison check failed";
+ EXPECT_EQ(aspects.mRange, CA::Range::RangeUnspecified) << "range should have been unspecified";
+ aspects.mRange = CA::Range::RangeFull;
+
+ aspects.mTransfer = CA::Transfer::TransferSRGB;
+ status = ColorUtils::checkIfAspectsChangedAndUnspecifyThem(aspects, origAspects);
+ ASSERT_TRUE(status) << "ColorAspects comparison check failed";
+ EXPECT_EQ(aspects.mTransfer, CA::Transfer::TransferUnspecified)
+ << "transfer should have been unspecified";
+ aspects.mTransfer = CA::Transfer::TransferLinear;
+
+ aspects.mPrimaries = CA::Primaries::PrimariesBT2020;
+ status = ColorUtils::checkIfAspectsChangedAndUnspecifyThem(aspects, origAspects, true);
+ ASSERT_TRUE(status) << "ColorAspects comparison check failed";
+ EXPECT_EQ(aspects.mPrimaries, CA::Primaries::PrimariesUnspecified)
+ << "primaries should have been unspecified";
+ EXPECT_EQ(aspects.mMatrixCoeffs, CA::MatrixCoeffs::MatrixUnspecified)
+ << "matrixCoeffs should have been unspecified";
+
+ aspects.mMatrixCoeffs = CA::MatrixCoeffs::MatrixSMPTE240M;
+ status = ColorUtils::checkIfAspectsChangedAndUnspecifyThem(aspects, origAspects, true);
+ ASSERT_TRUE(status) << "ColorAspects comparison check failed";
+ EXPECT_EQ(aspects.mPrimaries, CA::Primaries::PrimariesUnspecified)
+ << "primaries should have been unspecified";
+ EXPECT_EQ(aspects.mMatrixCoeffs, CA::MatrixCoeffs::MatrixUnspecified)
+ << "matrixCoeffs should have been unspecified";
+}
+
+TEST(ColorUtilsUnitTest, ColorConfigFromFormatTest) {
+ int range = -1;
+ int standard = -1;
+ int transfer = -1;
+ sp<AMessage> format = new AMessage();
+ ASSERT_NE(format, nullptr) << "failed to create AMessage";
+ ColorUtils::getColorConfigFromFormat(format, &range, &standard, &transfer);
+ EXPECT_EQ(range | standard | transfer, 0) << "color config didn't default to 0";
+
+ format->setInt32(KEY_COLOR_RANGE, CA::Range::RangeFull);
+ format->setInt32(KEY_COLOR_STANDARD, CA::Standard::StandardBT709);
+ format->setInt32(KEY_COLOR_TRANSFER, CA::Transfer::TransferLinear);
+ ColorUtils::getColorConfigFromFormat(format, &range, &standard, &transfer);
+ EXPECT_EQ(range, CA::Range::RangeFull) << "range mismatch";
+ EXPECT_EQ(standard, CA::Standard::StandardBT709) << "standard mismatch";
+ EXPECT_EQ(transfer, CA::Transfer::TransferLinear) << "transfer mismatch";
+
+ range = standard = transfer = -1;
+ sp<AMessage> copyFormat = new AMessage();
+ ASSERT_NE(copyFormat, nullptr) << "failed to create AMessage";
+ ColorUtils::copyColorConfig(format, copyFormat);
+ bool status = copyFormat->findInt32(KEY_COLOR_RANGE, &range);
+ ASSERT_TRUE(status) << "ColorConfig range entry missing";
+ status = copyFormat->findInt32(KEY_COLOR_STANDARD, &standard);
+ ASSERT_TRUE(status) << "ColorConfig standard entry missing";
+ status = copyFormat->findInt32(KEY_COLOR_TRANSFER, &transfer);
+ ASSERT_TRUE(status) << "ColorConfig transfer entry missing";
+ EXPECT_EQ(range, CA::Range::RangeFull) << "range mismatch";
+ EXPECT_EQ(standard, CA::Standard::StandardBT709) << "standard mismatch";
+ EXPECT_EQ(transfer, CA::Transfer::TransferLinear) << "transfer mismatchd";
+
+ range = standard = transfer = -1;
+ ColorUtils::getColorConfigFromFormat(copyFormat, &range, &standard, &transfer);
+ EXPECT_EQ(range, CA::Range::RangeFull) << "range mismatch";
+ EXPECT_EQ(standard, CA::Standard::StandardBT709) << "standard mismatch";
+ EXPECT_EQ(transfer, CA::Transfer::TransferLinear) << "transfer mismatch";
+}
+
+TEST_P(ColorAspectsTest, FormatTest) {
+ CA aspects;
+ sp<AMessage> format = new AMessage();
+ ASSERT_NE(format, nullptr) << "failed to create AMessage";
+ ColorUtils::setColorAspectsIntoFormat(aspects, format, true);
+
+ CA returnedAspects;
+ ColorUtils::getColorAspectsFromFormat(format, returnedAspects);
+ EXPECT_EQ(returnedAspects.mRange, aspects.mRange) << "range mismatch";
+ EXPECT_EQ(returnedAspects.mPrimaries, aspects.mPrimaries) << "primaries mismatch";
+ EXPECT_EQ(returnedAspects.mTransfer, aspects.mTransfer) << "transfer mismatch";
+ EXPECT_EQ(returnedAspects.mMatrixCoeffs, aspects.mMatrixCoeffs) << "matrixCoeffs mismatch";
+
+ aspects.mRange = mRange;
+ aspects.mPrimaries = mPrimaries;
+ aspects.mTransfer = mTransfer;
+ aspects.mMatrixCoeffs = mMatrixCoeffs;
+ ColorUtils::setColorAspectsIntoFormat(aspects, format);
+
+ memset(&returnedAspects, 0, sizeof(returnedAspects));
+ ColorUtils::getColorAspectsFromFormat(format, returnedAspects);
+ EXPECT_EQ(returnedAspects.mRange, aspects.mRange) << "range mismatch";
+ EXPECT_EQ(returnedAspects.mPrimaries, aspects.mPrimaries) << "primaries mismatch";
+ EXPECT_EQ(returnedAspects.mTransfer, aspects.mTransfer) << "transfer mismatch";
+ EXPECT_EQ(returnedAspects.mMatrixCoeffs, aspects.mMatrixCoeffs) << "matrixCoeffs mismatch";
+}
+
+TEST(ColorUtilsUnitTest, HDRStaticInfoTest) {
+ sp<AMessage> format = new AMessage();
+ ASSERT_NE(format, nullptr) << "failed to create AMessage";
+
+ HDRStaticInfo returnedInfoHDR;
+ bool status = ColorUtils::getHDRStaticInfoFromFormat(format, &returnedInfoHDR);
+ ASSERT_FALSE(status) << "HDR info should be absent in empty format";
+
+ HDRStaticInfo infoHDR;
+ infoHDR.sType1.mMaxDisplayLuminance = kHDRInfoTestValue2;
+ infoHDR.sType1.mMinDisplayLuminance = kHDRInfoTestValue1;
+ infoHDR.sType1.mMaxContentLightLevel = kHDRInfoTestValue2;
+ infoHDR.sType1.mMaxFrameAverageLightLevel = kHDRInfoTestValue1;
+ infoHDR.sType1.mR.x = kHDRInfoTestValue1;
+ infoHDR.sType1.mR.y = kHDRInfoTestValue2;
+ infoHDR.sType1.mG.x = kHDRInfoTestValue1;
+ infoHDR.sType1.mG.y = kHDRInfoTestValue2;
+ infoHDR.sType1.mB.x = kHDRInfoTestValue1;
+ infoHDR.sType1.mB.y = kHDRInfoTestValue2;
+ infoHDR.sType1.mW.x = kHDRInfoTestValue1;
+ infoHDR.sType1.mW.y = kHDRInfoTestValue2;
+ ColorUtils::setHDRStaticInfoIntoFormat(infoHDR, format);
+
+ status = ColorUtils::getHDRStaticInfoFromFormat(format, &returnedInfoHDR);
+ ASSERT_TRUE(status) << "Failed to get HDR info from format";
+ ASSERT_EQ(0, memcmp(&returnedInfoHDR, &infoHDR, sizeof(infoHDR))) << " HDRStaticInfo mismatch";
+
+ AMediaFormat *mediaFormat = AMediaFormat_new();
+ ASSERT_NE(mediaFormat, nullptr) << "Unable to create AMediaFormat";
+ ColorUtils::setHDRStaticInfoIntoAMediaFormat(infoHDR, mediaFormat);
+ memset(&returnedInfoHDR, 0, sizeof(returnedInfoHDR));
+ status = ColorUtils::getHDRStaticInfoFromFormat(mediaFormat->mFormat, &returnedInfoHDR);
+ AMediaFormat_delete(mediaFormat);
+ ASSERT_TRUE(status) << "Failed to get HDR info from media format";
+ ASSERT_EQ(0, memcmp(&returnedInfoHDR, &infoHDR, sizeof(infoHDR))) << " HDRStaticInfo mismatch";
+}
+
+TEST(ColorUtilsUnitTest, SanityTest) {
+ CA::Primaries unmappedPrimaries = (CA::Primaries)(CA::Primaries::PrimariesOther + 1);
+ CA::MatrixCoeffs unmappedMatrixCoeffs = (CA::MatrixCoeffs)(CA::MatrixOther + 1);
+ int32_t colorStandard =
+ ColorUtils::wrapColorAspectsIntoColorStandard(unmappedPrimaries, CA::MatrixUnspecified);
+ EXPECT_EQ(colorStandard, ColorUtils::kColorStandardUnspecified)
+ << "Standard unspecified expected";
+ colorStandard =
+ ColorUtils::wrapColorAspectsIntoColorStandard(CA::PrimariesOther, unmappedMatrixCoeffs);
+ EXPECT_EQ(colorStandard, ColorUtils::kColorStandardUnspecified)
+ << "Standard unspecified expected";
+ colorStandard = ColorUtils::wrapColorAspectsIntoColorStandard(CA::PrimariesBT601_6_525,
+ CA::MatrixBT2020);
+ EXPECT_GE(colorStandard, ColorUtils::kColorStandardExtendedStart)
+ << "Standard greater than extended start expected";
+ unmappedPrimaries = (CA::Primaries)(CA::Primaries::PrimariesBT2020 + 1);
+ unmappedMatrixCoeffs = (CA::MatrixCoeffs)(CA::MatrixBT2020Constant + 1);
+ colorStandard =
+ ColorUtils::wrapColorAspectsIntoColorStandard(unmappedPrimaries, unmappedMatrixCoeffs);
+ EXPECT_GE(colorStandard, ColorUtils::kColorStandardExtendedStart)
+ << "Standard greater than extended start expected";
+
+ CA aspects;
+ int32_t colorRange = -1;
+ colorStandard = -1;
+ int32_t colorTransfer = -1;
+ aspects.mPrimaries = (CA::Primaries)(CA::Primaries::PrimariesOther + 1);
+ status_t status = ColorUtils::convertCodecColorAspectsToPlatformAspects(
+ aspects, &colorRange, &colorStandard, &colorTransfer);
+ EXPECT_NE(status, OK) << "invalid colorAspects value accepted";
+
+ int32_t colorPrimaries = -1;
+ colorTransfer = -1;
+ int32_t colorMatrixCoeffs = -1;
+ bool fullRange = false;
+ aspects.mPrimaries = CA::PrimariesOther;
+ aspects.mTransfer = CA::TransferOther;
+ aspects.mMatrixCoeffs = CA::MatrixOther;
+ ColorUtils::convertCodecColorAspectsToIsoAspects(aspects, &colorPrimaries, &colorTransfer,
+ &colorMatrixCoeffs, &fullRange);
+ CA returnedAspects;
+ ColorUtils::convertIsoColorAspectsToCodecAspects(colorPrimaries, colorTransfer,
+ colorMatrixCoeffs, fullRange, returnedAspects);
+ EXPECT_EQ(returnedAspects.mPrimaries, CA::PrimariesUnspecified)
+ << "expected unspecified Primaries";
+ EXPECT_EQ(returnedAspects.mTransfer, CA::TransferUnspecified)
+ << "expected unspecified Transfer";
+ EXPECT_EQ(returnedAspects.mMatrixCoeffs, CA::MatrixUnspecified)
+ << "expected unspecified MatrixCoeffs";
+
+ // invalid values, other value equals 0xFF
+ colorPrimaries = CA::PrimariesOther;
+ colorTransfer = CA::TransferOther;
+ colorMatrixCoeffs = CA::MatrixOther;
+ fullRange = false;
+ memset(&returnedAspects, 0, sizeof(returnedAspects));
+ ColorUtils::convertIsoColorAspectsToCodecAspects(colorPrimaries, colorTransfer,
+ colorMatrixCoeffs, fullRange, returnedAspects);
+ EXPECT_EQ(returnedAspects.mPrimaries, CA::PrimariesUnspecified)
+ << "expected unspecified Primaries";
+ EXPECT_EQ(returnedAspects.mTransfer, CA::TransferUnspecified)
+ << "expected unspecified Transfer";
+ EXPECT_EQ(returnedAspects.mMatrixCoeffs, CA::MatrixUnspecified)
+ << "expected unspecified MatrixCoeffs";
+
+ CA::Primaries primaries = CA::PrimariesUnspecified;
+ CA::MatrixCoeffs matrixCoeffs = CA::MatrixUnspecified;
+ status = ColorUtils::unwrapColorAspectsFromColorStandard(ColorUtils::kColorStandardVendorStart,
+ &primaries, &matrixCoeffs);
+ EXPECT_EQ(status, OK) << "unwrapping aspects from color standard failed";
+
+ primaries = CA::PrimariesUnspecified;
+ matrixCoeffs = CA::MatrixUnspecified;
+ status = ColorUtils::unwrapColorAspectsFromColorStandard(
+ ColorUtils::kColorStandardVendorStart * 4, &primaries, &matrixCoeffs);
+ EXPECT_NE(status, OK) << "unwrapping aspects from color standard failed";
+
+ colorRange = ColorUtils::wrapColorAspectsIntoColorRange((CA::Range)(CA::RangeOther + 1));
+ EXPECT_EQ(colorRange, ColorUtils::kColorRangeUnspecified) << "expected unspecified color range";
+
+ CA::Range range;
+ status = ColorUtils::unwrapColorAspectsFromColorRange(
+ ColorUtils::kColorRangeVendorStart + CA::RangeOther + 1, &range);
+ EXPECT_NE(status, OK) << "invalid range value accepted";
+ EXPECT_EQ(range, CA::RangeOther) << "returned unexpected range value";
+
+ colorTransfer =
+ ColorUtils::wrapColorAspectsIntoColorTransfer((CA::Transfer)(CA::TransferOther + 1));
+ EXPECT_EQ(colorTransfer, ColorUtils::kColorTransferUnspecified)
+ << "expected unspecified color transfer";
+
+ CA::Transfer transfer;
+ status = ColorUtils::unwrapColorAspectsFromColorTransfer(
+ ColorUtils::kColorTransferVendorStart + CA::TransferOther + 1, &transfer);
+ EXPECT_NE(status, OK) << "invalid transfer value accepted";
+ EXPECT_EQ(transfer, CA::TransferOther) << "expected other color transfer";
+}
+
+TEST(ColorUtilsUnitTest, HDRInfoSanityTest) {
+ HDRStaticInfo hdrInfo;
+ sp<AMessage> format = new AMessage();
+ ASSERT_NE(format, nullptr) << "failed to create AMessage";
+
+ bool boolStatus = ColorUtils::getHDRStaticInfoFromFormat(format, &hdrInfo);
+ EXPECT_FALSE(boolStatus) << "HDRStaticInfo should not be present";
+
+ sp<ABuffer> invalidSizeHDRInfoBuffer = new ABuffer(kHDRBufferSize - 1);
+ ASSERT_NE(invalidSizeHDRInfoBuffer, nullptr) << "failed to create ABuffer";
+ format->setBuffer(KEY_HDR_STATIC_INFO, invalidSizeHDRInfoBuffer);
+ memset(&hdrInfo, 0, sizeof(hdrInfo));
+ boolStatus = ColorUtils::getHDRStaticInfoFromFormat(format, &hdrInfo);
+ EXPECT_FALSE(boolStatus) << "incorrect HDRStaticInfo buffer accepted";
+
+ sp<ABuffer> invalidHDRInfoBuffer = new ABuffer(kHDRBufferSize);
+ ASSERT_NE(invalidHDRInfoBuffer, nullptr) << "failed to create ABuffer";
+ uint8_t *data = invalidHDRInfoBuffer->data();
+ *data = HDRStaticInfo::kType1 + 1;
+ format->setBuffer(KEY_HDR_STATIC_INFO, invalidHDRInfoBuffer);
+ memset(&hdrInfo, 0, sizeof(hdrInfo));
+ boolStatus = ColorUtils::getHDRStaticInfoFromFormat(format, &hdrInfo);
+ EXPECT_FALSE(boolStatus) << "incorrect HDRStaticInfo buffer accepted";
+
+ CA aspects;
+ format->setInt32(KEY_COLOR_RANGE, ColorUtils::kColorRangeVendorStart + CA::RangeOther + 1);
+ format->setInt32(KEY_COLOR_STANDARD, CA::Standard::StandardBT709);
+ format->setInt32(KEY_COLOR_TRANSFER, CA::Transfer::TransferLinear);
+ ColorUtils::getColorAspectsFromFormat(format, aspects);
+ EXPECT_EQ(aspects.mRange, CA::RangeOther) << "unexpected range";
+}
+
+TEST(ColorUtilsUnitTest, DataSpaceSanityTest) {
+ CA aspects;
+ aspects.mRange = CA::RangeUnspecified;
+ aspects.mPrimaries = CA::PrimariesUnspecified;
+ aspects.mMatrixCoeffs = CA::MatrixUnspecified;
+ aspects.mTransfer = CA::TransferUnspecified;
+ android_dataspace dataSpace = ColorUtils::getDataSpaceForColorAspects(aspects, true);
+ EXPECT_EQ(dataSpace, 0) << "expected invalid dataspace";
+ aspects.mPrimaries = CA::PrimariesUnspecified;
+ aspects.mMatrixCoeffs = CA::MatrixBT2020Constant;
+ dataSpace = ColorUtils::getDataSpaceForColorAspects(aspects, true);
+ EXPECT_NE(dataSpace, 0) << "unexpected value";
+}
+
+INSTANTIATE_TEST_SUITE_P(ColorUtilsUnitTest, ColorRangeTest,
+ ::testing::Values(
+ // ColorRange
+ CA::Range::RangeLimited, CA::Range::RangeFull,
+ CA::Range::RangeUnspecified, CA::Range::RangeOther));
+
+INSTANTIATE_TEST_SUITE_P(ColorUtilsUnitTest, ColorTransferTest,
+ ::testing::Values(
+ // ColorTransfer
+ CA::Transfer::TransferUnspecified, CA::Transfer::TransferLinear,
+ CA::Transfer::TransferSRGB, CA::Transfer::TransferSMPTE170M,
+ CA::Transfer::TransferGamma22, CA::Transfer::TransferGamma28,
+ CA::Transfer::TransferST2084, CA::Transfer::TransferHLG,
+ CA::Transfer::TransferSMPTE240M, CA::Transfer::TransferXvYCC,
+ CA::Transfer::TransferBT1361, CA::Transfer::TransferST428,
+ CA::Transfer::TransferOther));
+
+INSTANTIATE_TEST_SUITE_P(
+ ColorUtilsUnitTest, ColorStandardTest,
+ ::testing::Values(
+ // Primaries, MatrixCoeffs
+ std::make_pair(CA::Primaries::PrimariesUnspecified,
+ CA::MatrixCoeffs::MatrixUnspecified),
+ std::make_pair(CA::Primaries::PrimariesBT709_5,
+ CA::MatrixCoeffs::MatrixBT709_5),
+ std::make_pair(CA::Primaries::PrimariesBT601_6_625,
+ CA::MatrixCoeffs::MatrixBT601_6),
+ std::make_pair(CA::Primaries::PrimariesBT601_6_625,
+ CA::MatrixCoeffs::MatrixBT709_5),
+ std::make_pair(CA::Primaries::PrimariesBT601_6_525,
+ CA::MatrixCoeffs::MatrixBT601_6),
+ std::make_pair(CA::Primaries::PrimariesBT601_6_525,
+ CA::MatrixCoeffs::MatrixSMPTE240M),
+ std::make_pair(CA::Primaries::PrimariesBT2020,
+ CA::MatrixCoeffs::MatrixBT2020),
+ std::make_pair(CA::Primaries::PrimariesBT2020,
+ CA::MatrixCoeffs::MatrixBT2020Constant),
+ std::make_pair(CA::Primaries::PrimariesBT470_6M,
+ CA::MatrixCoeffs::MatrixBT470_6M),
+ std::make_pair(CA::Primaries::PrimariesGenericFilm,
+ CA::MatrixCoeffs::MatrixBT2020)));
+
+INSTANTIATE_TEST_SUITE_P(
+ ColorUtilsUnitTest, ColorAspectsTest,
+ ::testing::Values(
+ // Primaries, ColorTransfer, MatrixCoeffs, ColorRange, ColorStandard
+ std::make_tuple(CA::Primaries::PrimariesUnspecified,
+ CA::Transfer::TransferUnspecified,
+ CA::MatrixCoeffs::MatrixUnspecified, CA::Range::RangeFull,
+ CA::Standard::StandardUnspecified),
+ std::make_tuple(CA::Primaries::PrimariesBT709_5, CA::Transfer::TransferLinear,
+ CA::MatrixCoeffs::MatrixBT709_5, CA::Range::RangeFull,
+ CA::Standard::StandardBT709),
+ std::make_tuple(CA::Primaries::PrimariesBT601_6_625, CA::Transfer::TransferSRGB,
+ CA::MatrixCoeffs::MatrixBT601_6, CA::Range::RangeFull,
+ CA::Standard::StandardUnspecified),
+ std::make_tuple(CA::Primaries::PrimariesBT601_6_625,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT709_5,
+ CA::Range::RangeFull, CA::Standard::StandardUnspecified),
+ std::make_tuple(CA::Primaries::PrimariesBT601_6_525, CA::Transfer::TransferGamma22,
+ CA::MatrixCoeffs::MatrixBT601_6, CA::Range::RangeFull,
+ CA::Standard::StandardUnspecified),
+ std::make_tuple(CA::Primaries::PrimariesBT601_6_525, CA::Transfer::TransferGamma28,
+ CA::MatrixCoeffs::MatrixSMPTE240M, CA::Range::RangeFull,
+ CA::Standard::StandardBT470M),
+ std::make_tuple(CA::Primaries::PrimariesBT2020, CA::Transfer::TransferST2084,
+ CA::MatrixCoeffs::MatrixBT2020, CA::Range::RangeFull,
+ CA::Standard::StandardBT601_525),
+ std::make_tuple(CA::Primaries::PrimariesBT2020, CA::Transfer::TransferHLG,
+ CA::MatrixCoeffs::MatrixBT2020Constant, CA::Range::RangeFull,
+ CA::Standard::StandardBT601_525),
+ std::make_tuple(CA::Primaries::PrimariesBT470_6M, CA::Transfer::TransferLinear,
+ CA::MatrixCoeffs::MatrixBT470_6M, CA::Range::RangeFull,
+ CA::Standard::StandardUnspecified),
+ std::make_tuple(CA::Primaries::PrimariesGenericFilm, CA::Transfer::TransferLinear,
+ CA::MatrixCoeffs::MatrixBT2020, CA::Range::RangeFull,
+ CA::Standard::StandardBT601_625)));
+
+INSTANTIATE_TEST_SUITE_P(
+ ColorUtilsUnitTest, IsoToPlatformAspectsTest,
+ ::testing::Values(
+ // Primaries, Transfer, MatrixCoeffs, Standard, Transfer
+ std::make_tuple(CA::PrimariesUnspecified, CA::TransferUnspecified,
+ CA::MatrixUnspecified, ColorUtils::kColorStandardUnspecified,
+ ColorUtils::kColorTransferUnspecified),
+ std::make_tuple(CA::PrimariesBT709_5, CA::TransferLinear, CA::MatrixBT709_5,
+ ColorUtils::kColorStandardBT709, ColorUtils::kColorTransferLinear),
+ std::make_tuple(CA::PrimariesBT601_6_625, CA::TransferSRGB, CA::MatrixBT601_6,
+ ColorUtils::kColorStandardBT601_625,
+ ColorUtils::kColorTransferSRGB),
+ std::make_tuple(CA::PrimariesBT601_6_625, CA::TransferSMPTE170M, CA::MatrixBT709_5,
+ ColorUtils::kColorStandardBT601_625_Unadjusted,
+ ColorUtils::kColorTransferSMPTE_170M),
+ std::make_tuple(CA::PrimariesBT601_6_525, CA::TransferGamma22, CA::MatrixBT601_6,
+ ColorUtils::kColorStandardBT601_525,
+ ColorUtils::kColorTransferGamma22),
+ std::make_tuple(CA::PrimariesBT601_6_525, CA::TransferGamma28, CA::MatrixSMPTE240M,
+ ColorUtils::kColorStandardBT601_525_Unadjusted,
+ ColorUtils::kColorTransferGamma28),
+ std::make_tuple(CA::PrimariesBT2020, CA::TransferST2084, CA::MatrixBT2020,
+ ColorUtils::kColorStandardBT2020, ColorUtils::kColorTransferST2084),
+ std::make_tuple(CA::PrimariesBT2020, CA::TransferHLG, CA::MatrixBT2020Constant,
+ ColorUtils::kColorStandardBT2020Constant,
+ ColorUtils::kColorTransferHLG),
+ std::make_tuple(CA::PrimariesBT470_6M, CA::TransferUnspecified, CA::MatrixBT470_6M,
+ ColorUtils::kColorStandardBT470M,
+ ColorUtils::kColorTransferUnspecified),
+ std::make_tuple(CA::PrimariesGenericFilm, CA::TransferLinear, CA::MatrixBT2020,
+ ColorUtils::kColorStandardFilm, ColorUtils::kColorTransferLinear)));
+
+INSTANTIATE_TEST_SUITE_P(
+ ColorUtilsUnitTest, DefaultColorAspectsTest,
+ ::testing::Values(
+ // Width, Height, Primaries, MatrixCoeffs
+ std::make_tuple(3840, 3840, CA::PrimariesBT2020, CA::MatrixBT2020),
+ std::make_tuple(720, 576, CA::PrimariesBT601_6_625, CA::MatrixBT601_6),
+ std::make_tuple(480, 360, CA::PrimariesBT601_6_525, CA::MatrixBT601_6),
+ std::make_tuple(480, 1920, CA::PrimariesBT709_5, CA::MatrixBT709_5)));
+
+INSTANTIATE_TEST_SUITE_P(
+ ColorUtilsUnitTest, DataSpaceTest,
+ ::testing::Values(
+ // ColorRange, Primaries, ColorTransfer, MatrixCoeffs, v0_android_dataspace,
+ // android_dataspace
+ std::make_tuple(CA::Range::RangeFull, CA::Primaries::PrimariesBT709_5,
+ CA::Transfer::TransferSRGB, CA::MatrixCoeffs::MatrixBT709_5,
+ HAL_DATASPACE_V0_SRGB, HAL_DATASPACE_SRGB),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT709_5,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT709_5,
+ HAL_DATASPACE_V0_BT709, HAL_DATASPACE_BT709),
+ std::make_tuple(CA::Range::RangeFull, CA::Primaries::PrimariesBT709_5,
+ CA::Transfer::TransferLinear, CA::MatrixCoeffs::MatrixBT709_5,
+ HAL_DATASPACE_V0_SRGB_LINEAR, HAL_DATASPACE_SRGB_LINEAR),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT601_6_525,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT601_6,
+ HAL_DATASPACE_V0_BT601_525, HAL_DATASPACE_BT601_525),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT601_6_625,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT601_6,
+ HAL_DATASPACE_V0_BT601_625, HAL_DATASPACE_BT601_625),
+ std::make_tuple(CA::Range::RangeFull, CA::Primaries::PrimariesBT601_6_625,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT601_6,
+ HAL_DATASPACE_V0_JFIF, HAL_DATASPACE_JFIF),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT709_5,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT470_6M,
+ HAL_DATASPACE_V0_BT601_625, HAL_DATASPACE_BT601_625),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT709_5,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT601_6,
+ HAL_DATASPACE_V0_BT601_625, HAL_DATASPACE_BT601_625),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT709_5,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixSMPTE240M,
+ HAL_DATASPACE_V0_BT709, HAL_DATASPACE_BT709),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT709_5,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT2020,
+ HAL_DATASPACE_V0_BT709, HAL_DATASPACE_BT709),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT709_5,
+ CA::Transfer::TransferSMPTE170M,
+ CA::MatrixCoeffs::MatrixBT2020Constant, HAL_DATASPACE_V0_BT601_525,
+ HAL_DATASPACE_BT601_525),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT601_6_625,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT470_6M,
+ HAL_DATASPACE_V0_BT601_625, HAL_DATASPACE_BT601_625),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT601_6_625,
+ CA::Transfer::TransferSMPTE170M,
+ CA::MatrixCoeffs::MatrixBT2020Constant, HAL_DATASPACE_V0_BT601_525,
+ HAL_DATASPACE_BT601_525),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT601_6_525,
+ CA::Transfer::TransferSMPTE170M, CA::MatrixCoeffs::MatrixBT470_6M,
+ HAL_DATASPACE_V0_BT601_525, HAL_DATASPACE_BT601_525),
+ std::make_tuple(CA::Range::RangeLimited, CA::Primaries::PrimariesBT601_6_525,
+ CA::Transfer::TransferSMPTE170M,
+ CA::MatrixCoeffs::MatrixBT2020Constant, HAL_DATASPACE_V0_BT601_525,
+ HAL_DATASPACE_BT601_525)));
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..e34504d 100644
--- a/media/libstagefright/id3/Android.bp
+++ b/media/libstagefright/id3/Android.bp
@@ -1,10 +1,13 @@
cc_library_static {
name: "libstagefright_id3",
+ min_sdk_version: "29",
srcs: ["ID3.cpp"],
header_libs: [
- "libmedia_headers",
+ "libmedia_datasource_headers",
+ "libstagefright_foundation_headers",
+ "libstagefright_headers",
"media_ndk_headers",
],
@@ -18,6 +21,12 @@
],
cfi: true,
},
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
//###############################################################################
diff --git a/media/libstagefright/id3/TEST_MAPPING b/media/libstagefright/id3/TEST_MAPPING
new file mode 100644
index 0000000..e4454c1
--- /dev/null
+++ b/media/libstagefright/id3/TEST_MAPPING
@@ -0,0 +1,20 @@
+// frameworks/av/media/libstagefright/id3
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ //{ "name": "ID3Test" },
+
+ // this doesn't seem to run any tests.
+ // but: cts-tradefed run -m CtsMediaTestCases -t android.media.cts.MediaMetadataRetrieverTest
+ // does run he 32 and 64 bit tests, but not the instant tests
+ // but all I know is that with 'atest', it's not running
+ {
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.MediaMetadataRetrieverTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/media/libstagefright/id3/test/AndroidTest.xml b/media/libstagefright/id3/test/AndroidTest.xml
index 6c6697d..d6ea470 100644
--- a/media/libstagefright/id3/test/AndroidTest.xml
+++ b/media/libstagefright/id3/test/AndroidTest.xml
@@ -19,7 +19,7 @@
<option name="cleanup" value="true" />
<option name="push" value="ID3Test->/data/local/tmp/ID3Test" />
<option name="push-file"
- key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/id3/test/ID3Test.zip?unzip=true"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/id3/test/ID3Test-1.1.zip?unzip=true"
value="/data/local/tmp/ID3TestRes/" />
</target_preparer>
diff --git a/media/libstagefright/id3/test/ID3Test.cpp b/media/libstagefright/id3/test/ID3Test.cpp
index cd5cd9e..8db83cb 100644
--- a/media/libstagefright/id3/test/ID3Test.cpp
+++ b/media/libstagefright/id3/test/ID3Test.cpp
@@ -51,7 +51,7 @@
while (!it.done()) {
String8 id;
it.getID(&id);
- ASSERT_GT(id.length(), 0) << "No ID tag found! \n";
+ ASSERT_GT(id.length(), 0) << "Found an ID3 tag of 0 size";
ALOGV("Found ID tag: %s\n", String8(id).c_str());
it.next();
}
@@ -66,8 +66,8 @@
DataSourceHelper helper(file->wrap());
ID3 tag(&helper);
ASSERT_TRUE(tag.isValid()) << "No valid ID3 tag found for " << path.c_str() << "\n";
- ASSERT_TRUE(tag.version() >= versionNumber)
- << "Expected version: " << tag.version() << " Found version: " << versionNumber;
+ ASSERT_EQ(tag.version(), versionNumber)
+ << "Found version: " << tag.version() << " Expected version: " << versionNumber;
}
TEST_P(ID3textTagTest, TextTagTest) {
@@ -81,17 +81,34 @@
ASSERT_TRUE(tag.isValid()) << "No valid ID3 tag found for " << path.c_str() << "\n";
int countTextFrames = 0;
ID3::Iterator it(tag, nullptr);
- while (!it.done()) {
- String8 id;
- it.getID(&id);
- ASSERT_GT(id.length(), 0);
- if (id[0] == 'T') {
- String8 text;
- countTextFrames++;
- it.getString(&text);
- ALOGV("Found text frame %s : %s \n", id.string(), text.string());
+ if (tag.version() != ID3::ID3_V1 && tag.version() != ID3::ID3_V1_1) {
+ while (!it.done()) {
+ String8 id;
+ it.getID(&id);
+ ASSERT_GT(id.length(), 0) << "Found an ID3 tag of 0 size";
+ if (id[0] == 'T') {
+ String8 text;
+ countTextFrames++;
+ it.getString(&text);
+ ALOGV("Found text frame %s : %s \n", id.string(), text.string());
+ }
+ it.next();
}
- it.next();
+ } else {
+ while (!it.done()) {
+ String8 id;
+ String8 text;
+ it.getID(&id);
+ ASSERT_GT(id.length(), 0) << "Found an ID3 tag of 0 size";
+ it.getString(&text);
+ // if the tag has a value
+ if (strcmp(text.string(), "")) {
+ countTextFrames++;
+ ALOGV("ID: %s\n", id.c_str());
+ ALOGV("Text string: %s\n", text.string());
+ }
+ it.next();
+ }
}
ASSERT_EQ(countTextFrames, numTextFrames)
<< "Expected " << numTextFrames << " text frames, found " << countTextFrames;
@@ -114,7 +131,7 @@
if (data) {
ALOGV("Found album art: size = %zu mime = %s \n", dataSize, mime.string());
}
- ASSERT_NE(data, nullptr) << "Expected album art, found none!" << path;
+ ASSERT_NE(data, nullptr) << "Expected album art, found none! " << path;
} else {
ASSERT_EQ(data, nullptr) << "Found album art when expected none!";
}
@@ -137,7 +154,7 @@
while (!it.done()) {
String8 id;
it.getID(&id);
- ASSERT_GT(id.length(), 0);
+ ASSERT_GT(id.length(), 0) << "Found an ID3 tag of 0 size";
// Check if the tag is an "APIC/PIC" tag.
if (String8(id) == "APIC" || String8(id) == "PIC") {
count++;
@@ -150,7 +167,7 @@
hexdump(data, dataSize > 128 ? 128 : dataSize);
#endif
}
- ASSERT_NE(data, nullptr) << "Expected album art, found none!" << path;
+ ASSERT_NE(data, nullptr) << "Expected album art, found none! " << path;
}
it.next();
}
@@ -159,56 +176,67 @@
}
INSTANTIATE_TEST_SUITE_P(id3TestAll, ID3tagTest,
- ::testing::Values("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3",
- "bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3",
- "bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3",
- "bbb_44100hz_2ch_128kbps_mp3_5mins.mp3",
- "bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3",
- "bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3",
- "bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3",
- "bbb_44100hz_2ch_128kbps_mp3_30sec_moreTextFrames.mp3"));
+ ::testing::Values("bbb_1sec_v23.mp3",
+ "bbb_1sec_1_image.mp3",
+ "bbb_1sec_2_image.mp3",
+ "bbb_2sec_v24.mp3",
+ "bbb_2sec_1_image.mp3",
+ "bbb_2sec_2_image.mp3",
+ "bbb_2sec_largeSize.mp3",
+ "bbb_1sec_v23_3tags.mp3",
+ "bbb_1sec_v1_5tags.mp3",
+ "bbb_2sec_v24_unsynchronizedOneFrame.mp3",
+ "bbb_2sec_v24_unsynchronizedAllFrames.mp3"));
INSTANTIATE_TEST_SUITE_P(
id3TestAll, ID3versionTest,
- ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", 4),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3", 4),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3", 4),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins.mp3", 4),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3", 4),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3", 4),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3", 4),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_moreTextFrames.mp3", 4)));
+ ::testing::Values(make_pair("bbb_1sec_v23.mp3", ID3::ID3_V2_3),
+ make_pair("bbb_1sec_1_image.mp3", ID3::ID3_V2_3),
+ make_pair("bbb_1sec_2_image.mp3", ID3::ID3_V2_3),
+ make_pair("bbb_2sec_v24.mp3", ID3::ID3_V2_4),
+ make_pair("bbb_2sec_1_image.mp3", ID3::ID3_V2_4),
+ make_pair("bbb_2sec_2_image.mp3", ID3::ID3_V2_4),
+ make_pair("bbb_2sec_largeSize.mp3", ID3::ID3_V2_4),
+ make_pair("bbb_1sec_v23_3tags.mp3", ID3::ID3_V2_3),
+ make_pair("bbb_1sec_v1_5tags.mp3", ID3::ID3_V1_1),
+ make_pair("bbb_1sec_v1_3tags.mp3", ID3::ID3_V1_1),
+ make_pair("bbb_2sec_v24_unsynchronizedOneFrame.mp3", ID3::ID3_V2_4),
+ make_pair("bbb_2sec_v24_unsynchronizedAllFrames.mp3", ID3::ID3_V2_4)));
INSTANTIATE_TEST_SUITE_P(
id3TestAll, ID3textTagTest,
- ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_moreTextFrames.mp3", 5)));
+ ::testing::Values(
+ make_pair("bbb_1sec_v23.mp3", 1),
+ make_pair("bbb_1sec_1_image.mp3", 1),
+ make_pair("bbb_1sec_2_image.mp3", 1),
+ make_pair("bbb_2sec_v24.mp3", 1),
+ make_pair("bbb_2sec_1_image.mp3", 1),
+ make_pair("bbb_2sec_2_image.mp3", 1),
+ make_pair("bbb_2sec_largeSize.mp3", 1),
+ make_pair("bbb_1sec_v23_3tags.mp3", 3),
+ make_pair("bbb_1sec_v1_5tags.mp3", 5),
+ make_pair("bbb_1sec_v1_3tags.mp3", 3),
+ make_pair("bbb_2sec_v24_unsynchronizedOneFrame.mp3", 3),
+ make_pair("bbb_2sec_v24_unsynchronizedAllFrames.mp3", 3)));
-INSTANTIATE_TEST_SUITE_P(
- id3TestAll, ID3albumArtTest,
- ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", false),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3", true),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3", true),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins.mp3", false),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3", true),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3", true),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3", true)));
+INSTANTIATE_TEST_SUITE_P(id3TestAll, ID3albumArtTest,
+ ::testing::Values(make_pair("bbb_1sec_v23.mp3", false),
+ make_pair("bbb_1sec_1_image.mp3", true),
+ make_pair("bbb_1sec_2_image.mp3", true),
+ make_pair("bbb_2sec_v24.mp3", false),
+ make_pair("bbb_2sec_1_image.mp3", true),
+ make_pair("bbb_2sec_2_image.mp3", true),
+ make_pair("bbb_2sec_largeSize.mp3", true),
+ make_pair("bbb_1sec_v1_5tags.mp3", false)));
-INSTANTIATE_TEST_SUITE_P(
- id3TestAll, ID3multiAlbumArtTest,
- ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", 0),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins.mp3", 0),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3", 1),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3", 2),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3", 2),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3", 3)));
+INSTANTIATE_TEST_SUITE_P(id3TestAll, ID3multiAlbumArtTest,
+ ::testing::Values(make_pair("bbb_1sec_v23.mp3", 0),
+ make_pair("bbb_2sec_v24.mp3", 0),
+ make_pair("bbb_1sec_1_image.mp3", 1),
+ make_pair("bbb_2sec_1_image.mp3", 1),
+ make_pair("bbb_1sec_2_image.mp3", 2),
+ make_pair("bbb_2sec_2_image.mp3", 2),
+ make_pair("bbb_2sec_largeSize.mp3", 3)));
int main(int argc, char **argv) {
gEnv = new ID3TestEnvironment();
diff --git a/media/libstagefright/include/FrameDecoder.h b/media/libstagefright/include/FrameDecoder.h
index 19ae0e3..bca7f01 100644
--- a/media/libstagefright/include/FrameDecoder.h
+++ b/media/libstagefright/include/FrameDecoder.h
@@ -135,7 +135,8 @@
private:
sp<FrameCaptureLayer> mCaptureLayer;
VideoFrame *mFrame;
- bool mIsAvcOrHevc;
+ bool mIsAvc;
+ bool mIsHevc;
MediaSource::ReadOptions::SeekMode mSeekMode;
int64_t mTargetTimeUs;
List<int64_t> mSampleDurations;
diff --git a/media/libstagefright/include/HevcUtils.h b/media/libstagefright/include/HevcUtils.h
index 0f59631..d2a86eb 100644
--- a/media/libstagefright/include/HevcUtils.h
+++ b/media/libstagefright/include/HevcUtils.h
@@ -30,6 +30,10 @@
namespace android {
enum {
+ kHevcNalUnitTypeCodedSliceIdr = 19,
+ kHevcNalUnitTypeCodedSliceIdrNoLP = 20,
+ kHevcNalUnitTypeCodedSliceCra = 21,
+
kHevcNalUnitTypeVps = 32,
kHevcNalUnitTypeSps = 33,
kHevcNalUnitTypePps = 34,
@@ -92,6 +96,7 @@
status_t makeHvcc(uint8_t *hvcc, size_t *hvccSize, size_t nalSizeLength);
Info getInfo() const { return mInfo; }
+ static bool IsHevcIDR(const uint8_t *data, size_t size);
private:
status_t parseVps(const uint8_t* data, size_t size);
diff --git a/media/libstagefright/include/media/stagefright/ACodec.h b/media/libstagefright/include/media/stagefright/ACodec.h
index 83e92b9..cc40f76 100644
--- a/media/libstagefright/include/media/stagefright/ACodec.h
+++ b/media/libstagefright/include/media/stagefright/ACodec.h
@@ -272,6 +272,7 @@
bool mShutdownInProgress;
bool mExplicitShutdown;
bool mIsLegacyVP9Decoder;
+ bool mIsStreamCorruptFree;
// If "mKeepComponentAllocated" we only transition back to Loaded state
// and do not release the component instance.
diff --git a/media/libstagefright/include/media/stagefright/CameraSource.h b/media/libstagefright/include/media/stagefright/CameraSource.h
index 6f0d3b5..16e7d89 100644
--- a/media/libstagefright/include/media/stagefright/CameraSource.h
+++ b/media/libstagefright/include/media/stagefright/CameraSource.h
@@ -23,7 +23,6 @@
#include <media/stagefright/MediaBuffer.h>
#include <camera/android/hardware/ICamera.h>
#include <camera/ICameraRecordingProxy.h>
-#include <camera/ICameraRecordingProxyListener.h>
#include <camera/CameraParameters.h>
#include <gui/BufferItemConsumer.h>
#include <utils/List.h>
@@ -40,17 +39,6 @@
class CameraSource : public MediaSource, public MediaBufferObserver {
public:
/**
- * Factory method to create a new CameraSource using the current
- * settings (such as video size, frame rate, color format, etc)
- * from the default camera.
- *
- * @param clientName The package/process name of the client application.
- * This is used for permissions checking.
- * @return NULL on error.
- */
- static CameraSource *Create(const String16 &clientName);
-
- /**
* Factory method to create a new CameraSource.
*
* @param camera the video input frame data source. If it is NULL,
@@ -89,8 +77,7 @@
pid_t clientPid,
Size videoSize,
int32_t frameRate,
- const sp<IGraphicBufferProducer>& surface,
- bool storeMetaDataInVideoBuffers = true);
+ const sp<IGraphicBufferProducer>& surface);
virtual ~CameraSource();
@@ -132,26 +119,6 @@
protected:
/**
- * The class for listening to BnCameraRecordingProxyListener. This is used to receive video
- * buffers in VIDEO_BUFFER_MODE_DATA_CALLBACK_YUV and VIDEO_BUFFER_MODE_DATA_CALLBACK_METADATA
- * mode. When a frame is available, CameraSource::dataCallbackTimestamp() will be called.
- */
- class ProxyListener: public BnCameraRecordingProxyListener {
- public:
- ProxyListener(const sp<CameraSource>& source);
- virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
- const sp<IMemory> &data);
- virtual void recordingFrameHandleCallbackTimestamp(int64_t timestampUs,
- native_handle_t* handle);
- virtual void recordingFrameHandleCallbackTimestampBatch(
- const std::vector<int64_t>& timestampsUs,
- const std::vector<native_handle_t*>& handles);
-
- private:
- sp<CameraSource> mSource;
- };
-
- /**
* The class for listening to BufferQueue's onFrameAvailable. This is used to receive video
* buffers in VIDEO_BUFFER_MODE_BUFFER_QUEUE mode. When a frame is available,
* CameraSource::processBufferQueueFrame() will be called.
@@ -213,32 +180,15 @@
CameraSource(const sp<hardware::ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
int32_t cameraId, const String16& clientName, uid_t clientUid, pid_t clientPid,
Size videoSize, int32_t frameRate,
- const sp<IGraphicBufferProducer>& surface,
- bool storeMetaDataInVideoBuffers);
+ const sp<IGraphicBufferProducer>& surface);
virtual status_t startCameraRecording();
virtual void releaseRecordingFrame(const sp<IMemory>& frame);
- virtual void releaseRecordingFrameHandle(native_handle_t* handle);
- // stagefright recorder not using this for now
- virtual void releaseRecordingFrameHandleBatch(const std::vector<native_handle_t*>& handles);
// Returns true if need to skip the current frame.
// Called from dataCallbackTimestamp.
virtual bool skipCurrentFrame(int64_t /*timestampUs*/) {return false;}
- // Callback called when still camera raw data is available.
- virtual void dataCallback(int32_t /*msgType*/, const sp<IMemory>& /*data*/) {}
-
- virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
- const sp<IMemory> &data);
-
- virtual void recordingFrameHandleCallbackTimestamp(int64_t timestampUs,
- native_handle_t* handle);
-
- virtual void recordingFrameHandleCallbackTimestampBatch(
- const std::vector<int64_t>& timestampsUs,
- const std::vector<native_handle_t*>& handles);
-
// Process a buffer item received in BufferQueueListener.
virtual void processBufferQueueFrame(BufferItem& buffer);
@@ -261,9 +211,6 @@
int64_t mGlitchDurationThresholdUs;
bool mCollectStats;
- // The mode video buffers are received from camera. One of VIDEO_BUFFER_MODE_*.
- int32_t mVideoBufferMode;
-
static const uint32_t kDefaultVideoBufferCount = 32;
/**
@@ -297,12 +244,12 @@
status_t init(const sp<hardware::ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
int32_t cameraId, const String16& clientName, uid_t clientUid, pid_t clientPid,
- Size videoSize, int32_t frameRate, bool storeMetaDataInVideoBuffers);
+ Size videoSize, int32_t frameRate);
status_t initWithCameraAccess(
const sp<hardware::ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
int32_t cameraId, const String16& clientName, uid_t clientUid, pid_t clientPid,
- Size videoSize, int32_t frameRate, bool storeMetaDataInVideoBuffers);
+ Size videoSize, int32_t frameRate);
// Initialize the buffer queue used in VIDEO_BUFFER_MODE_BUFFER_QUEUE mode.
status_t initBufferQueue(uint32_t width, uint32_t height, uint32_t format,
diff --git a/media/libstagefright/include/media/stagefright/CameraSourceTimeLapse.h b/media/libstagefright/include/media/stagefright/CameraSourceTimeLapse.h
index 533e33b..3c311cf 100644
--- a/media/libstagefright/include/media/stagefright/CameraSourceTimeLapse.h
+++ b/media/libstagefright/include/media/stagefright/CameraSourceTimeLapse.h
@@ -45,8 +45,7 @@
Size videoSize,
int32_t videoFrameRate,
const sp<IGraphicBufferProducer>& surface,
- int64_t timeBetweenTimeLapseFrameCaptureUs,
- bool storeMetaDataInVideoBuffers = true);
+ int64_t timeBetweenTimeLapseFrameCaptureUs);
virtual ~CameraSourceTimeLapse();
@@ -122,8 +121,7 @@
Size videoSize,
int32_t videoFrameRate,
const sp<IGraphicBufferProducer>& surface,
- int64_t timeBetweenTimeLapseFrameCaptureUs,
- bool storeMetaDataInVideoBuffers = true);
+ int64_t timeBetweenTimeLapseFrameCaptureUs);
// Wrapper over CameraSource::signalBufferReturned() to implement quick stop.
// It only handles the case when mLastReadBufferCopy is signalled. Otherwise
@@ -137,33 +135,6 @@
// frame needs to be skipped and this function just returns the value of mSkipCurrentFrame.
virtual bool skipCurrentFrame(int64_t timestampUs);
- // In the video camera case calls skipFrameAndModifyTimeStamp() to modify
- // timestamp and set mSkipCurrentFrame.
- // Then it calls the base CameraSource::dataCallbackTimestamp()
- // This will be called in VIDEO_BUFFER_MODE_DATA_CALLBACK_YUV and
- // VIDEO_BUFFER_MODE_DATA_CALLBACK_METADATA mode.
- virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
- const sp<IMemory> &data);
-
- // In the video camera case calls skipFrameAndModifyTimeStamp() to modify
- // timestamp and set mSkipCurrentFrame.
- // Then it calls the base CameraSource::recordingFrameHandleCallbackTimestamp() or
- // CameraSource::recordingFrameHandleCallbackTimestampBatch()
- // This will be called in VIDEO_BUFFER_MODE_DATA_CALLBACK_METADATA mode when
- // the metadata is VideoNativeHandleMetadata.
- virtual void recordingFrameHandleCallbackTimestamp(int64_t timestampUs,
- native_handle_t* handle);
-
- // In the video camera case calls skipFrameAndModifyTimeStamp() to modify
- // timestamp and set mSkipCurrentFrame.
- // Then it calls the base CameraSource::recordingFrameHandleCallbackTimestamp() or
- // CameraSource::recordingFrameHandleCallbackTimestampBatch()
- // This will be called in VIDEO_BUFFER_MODE_DATA_CALLBACK_METADATA mode when
- // the metadata is VideoNativeHandleMetadata.
- virtual void recordingFrameHandleCallbackTimestampBatch(
- const std::vector<int64_t>& timestampsUs,
- const std::vector<native_handle_t*>& handles);
-
// Process a buffer item received in CameraSource::BufferQueueListener.
// This will be called in VIDEO_BUFFER_MODE_BUFFER_QUEUE mode.
virtual void processBufferQueueFrame(BufferItem& buffer);
@@ -187,9 +158,6 @@
// Wrapper to enter threadTimeLapseEntry()
static void *ThreadTimeLapseWrapper(void *me);
- // Creates a copy of source_data into a new memory of final type MemoryBase.
- sp<IMemory> createIMemoryCopy(const sp<IMemory> &source_data);
-
CameraSourceTimeLapse(const CameraSourceTimeLapse &);
CameraSourceTimeLapse &operator=(const CameraSourceTimeLapse &);
};
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 34a7d55..2582ed0 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -27,6 +27,7 @@
#include <media/stagefright/foundation/AHandlerReflector.h>
#include <media/stagefright/foundation/ALooper.h>
#include <mutex>
+#include <queue>
namespace android {
@@ -45,7 +46,7 @@
// Returns INVALID_OPERATION if there is no source or track.
virtual status_t start(MetaData *param = NULL);
- virtual status_t stop() { return reset(); }
+ virtual status_t stop();
virtual status_t pause();
virtual bool reachedEOS();
virtual status_t dump(int fd, const Vector<String16>& args);
@@ -85,9 +86,10 @@
friend struct AHandlerReflector<MPEG4Writer>;
enum {
- kWhatSwitch = 'swch',
- kWhatHandleIOError = 'ioer',
- kWhatHandleFallocateError = 'faer'
+ kWhatSwitch = 'swch',
+ kWhatIOError = 'ioer',
+ kWhatFallocateError = 'faer',
+ kWhatNoIOErrorSoFar = 'noie'
};
int mFd;
@@ -124,12 +126,19 @@
bool mWriteSeekErr;
bool mFallocateErr;
bool mPreAllocationEnabled;
+ status_t mResetStatus;
+ // Queue to hold top long write durations
+ std::priority_queue<std::chrono::microseconds, std::vector<std::chrono::microseconds>,
+ std::greater<std::chrono::microseconds>> mWriteDurationPQ;
+ const uint8_t kWriteDurationsCount = 5;
sp<ALooper> mLooper;
sp<AHandlerReflector<MPEG4Writer> > mReflector;
Mutex mLock;
+ // Serialize reset calls from client of MPEG4Writer and MP4WtrCtrlHlpLooper.
std::mutex mResetMutex;
+ // Serialize preallocation calls from different track threads.
std::mutex mFallocMutex;
bool mPreAllocFirstTime; // Pre-allocate space for file and track headers only once per file.
uint64_t mPrevAllTracksTotalMetaDataSizeEstimate;
@@ -148,6 +157,7 @@
int64_t estimateMoovBoxSize(int32_t bitRate);
int64_t estimateFileLevelMetaSize(MetaData *params);
void writeCachedBoxToFile(const char *type);
+ void printWriteDurations();
struct Chunk {
Track *mTrack; // Owner
@@ -231,7 +241,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 +297,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);
@@ -296,7 +307,7 @@
void writeGeoDataBox();
void writeLatitude(int degreex10000);
void writeLongitude(int degreex10000);
- void finishCurrentSession();
+ status_t finishCurrentSession();
void addDeviceMeta();
void writeHdlr(const char *handlerType);
@@ -310,7 +321,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,9 +338,9 @@
void writeFileLevelMetaBox();
void sendSessionSummary();
- void release();
+ status_t release();
status_t switchFd();
- status_t reset(bool stopSource = true);
+ status_t reset(bool stopSource = true, bool waitForAnyPreviousCallToComplete = true);
static uint32_t getMpeg4Time();
diff --git a/media/libstagefright/include/media/stagefright/MediaAdapter.h b/media/libstagefright/include/media/stagefright/MediaAdapter.h
index 177a9e9..c7d7765 100644
--- a/media/libstagefright/include/media/stagefright/MediaAdapter.h
+++ b/media/libstagefright/include/media/stagefright/MediaAdapter.h
@@ -58,6 +58,7 @@
private:
Mutex mAdapterLock;
+ std::mutex mBufferGatingMutex;
// Make sure the read() wait for the incoming buffer.
Condition mBufferReadCond;
// Make sure the pushBuffer() wait for the current buffer consumed.
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 63a9dad..1f8e780 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -81,6 +81,13 @@
BUFFER_FLAG_MUXER_DATA = 16,
};
+ enum CVODegree {
+ CVO_DEGREE_0 = 0,
+ CVO_DEGREE_90 = 90,
+ CVO_DEGREE_180 = 180,
+ CVO_DEGREE_270 = 270,
+ };
+
enum {
CB_INPUT_AVAILABLE = 1,
CB_OUTPUT_AVAILABLE = 2,
@@ -139,7 +146,7 @@
// object.
status_t release();
- status_t releaseAsync();
+ status_t releaseAsync(const sp<AMessage> ¬ify);
status_t flush();
@@ -371,7 +378,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 +390,7 @@
sp<AMessage> mInputFormat;
sp<AMessage> mCallback;
sp<AMessage> mOnFrameRenderedNotification;
+ sp<AMessage> mAsyncReleaseCompleteNotification;
sp<ResourceManagerServiceProxy> mResourceManagerProxy;
@@ -514,6 +523,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/MediaCodecConstants.h b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
index 178d334..9c67338 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
@@ -744,6 +744,7 @@
constexpr char KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT[] = "aac-max-output-channel_count";
constexpr char KEY_AAC_PROFILE[] = "aac-profile";
constexpr char KEY_AAC_SBR_MODE[] = "aac-sbr-mode";
+constexpr char KEY_ALLOW_FRAME_DROP[] = "allow-frame-drop";
constexpr char KEY_AUDIO_SESSION_ID[] = "audio-session-id";
constexpr char KEY_BIT_RATE[] = "bitrate";
constexpr char KEY_BITRATE_MODE[] = "bitrate-mode";
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecSource.h b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
index 2f98af1..0f7b535 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecSource.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
@@ -64,12 +64,15 @@
// MediaBufferObserver
virtual void signalBufferReturned(MediaBufferBase *buffer);
+ virtual status_t setEncodingBitrate(int32_t bitRate /* bps */);
// for AHandlerReflector
void onMessageReceived(const sp<AMessage> &msg);
+ status_t requestIDRFrame();
+
protected:
virtual ~MediaCodecSource();
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..17b1abf 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;
@@ -55,23 +54,30 @@
virtual void setStartTimeOffsetMs(int /*ms*/) {}
virtual int32_t getStartTimeOffsetMs() const { return 0; }
virtual status_t setNextFd(int /*fd*/) { return INVALID_OPERATION; }
+ virtual void updateCVODegrees(int32_t /*cvoDegrees*/) {}
+ virtual void updatePayloadType(int32_t /*payloadType*/) {}
+ virtual void updateSocketNetwork(int64_t /*socketNetwork*/) {}
+ virtual uint32_t getSequenceNum() { return 0; }
protected:
virtual ~MediaWriter() {}
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..173b701 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataBase.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataBase.h
@@ -239,8 +239,23 @@
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)
+
+ kKeySps = 'sSps', // int32_t, indicates that a buffer is sps (value ignored).
+ kKeyPps = 'sPps', // int32_t, indicates that a buffer is pps (value ignored).
+ kKeySelfID = 'sfid', // int32_t, source ID to identify itself on RTP protocol.
+ kKeyPayloadType = 'pTyp', // int32_t, SDP negotiated payload type.
+ kKeyRtpExtMap = 'extm', // int32_t, rtp extension ID for cvo on RTP protocol.
+ kKeyRtpCvoDegrees = 'cvod', // int32_t, rtp cvo degrees as per 3GPP 26.114.
+ kKeyRtpDscp = 'dscp', // int32_t, DSCP(Differentiated services codepoint) of RFC 2474.
+ kKeySocketNetwork = 'sNet', // int64_t, socket will be bound to network handle.
};
enum {
diff --git a/media/libstagefright/include/media/stagefright/RemoteDataSource.h b/media/libstagefright/include/media/stagefright/RemoteDataSource.h
index d82be8a..d605cda 100644
--- a/media/libstagefright/include/media/stagefright/RemoteDataSource.h
+++ b/media/libstagefright/include/media/stagefright/RemoteDataSource.h
@@ -41,6 +41,11 @@
close();
}
virtual sp<IMemory> getIMemory() {
+ Mutex::Autolock lock(mLock);
+ if (mMemory.get() == nullptr) {
+ ALOGE("getIMemory() failed, mMemory is nullptr");
+ return nullptr;
+ }
return mMemory;
}
virtual ssize_t readAt(off64_t offset, size_t size) {
@@ -48,19 +53,35 @@
if (size > kBufferSize) {
size = kBufferSize;
}
+
+ Mutex::Autolock lock(mLock);
+ if (mSource.get() == nullptr) {
+ ALOGE("readAt() failed, mSource is nullptr");
+ return 0;
+ }
return mSource->readAt(offset, mMemory->unsecurePointer(), size);
}
virtual status_t getSize(off64_t *size) {
+ Mutex::Autolock lock(mLock);
+ if (mSource.get() == nullptr) {
+ ALOGE("getSize() failed, mSource is nullptr");
+ return INVALID_OPERATION;
+ }
return mSource->getSize(size);
}
virtual void close() {
// Protect strong pointer assignments. This also can be called from the binder
// clean-up procedure which is running on a separate thread.
- Mutex::Autolock lock(mCloseLock);
+ Mutex::Autolock lock(mLock);
mSource = nullptr;
mMemory = nullptr;
}
virtual uint32_t getFlags() {
+ Mutex::Autolock lock(mLock);
+ if (mSource.get() == nullptr) {
+ ALOGE("getSize() failed, mSource is nullptr");
+ return 0;
+ }
return mSource->flags();
}
virtual String8 toString() {
@@ -75,9 +96,10 @@
sp<IMemory> mMemory;
sp<DataSource> mSource;
String8 mName;
- Mutex mCloseLock;
+ Mutex mLock;
explicit RemoteDataSource(const sp<DataSource> &source) {
+ Mutex::Autolock lock(mLock);
mSource = source;
sp<MemoryDealer> memoryDealer = new MemoryDealer(kBufferSize, "RemoteDataSource");
mMemory = memoryDealer->allocate(kBufferSize);
diff --git a/media/libstagefright/include/media/stagefright/SurfaceUtils.h b/media/libstagefright/include/media/stagefright/SurfaceUtils.h
index ae55c65..35b3fa2 100644
--- a/media/libstagefright/include/media/stagefright/SurfaceUtils.h
+++ b/media/libstagefright/include/media/stagefright/SurfaceUtils.h
@@ -38,6 +38,8 @@
int width, int height, int format, int rotation, int usage, bool reconnect);
void setNativeWindowHdrMetadata(
ANativeWindow *nativeWindow /* nonnull */, HDRStaticInfo *info /* nonnull */);
+status_t setNativeWindowRotation(
+ ANativeWindow *nativeWindow /* nonnull */, int rotation);
status_t pushBlankBuffersToNativeWindow(ANativeWindow *nativeWindow /* nonnull */);
status_t nativeWindowConnect(ANativeWindow *surface, const char *reason);
status_t nativeWindowDisconnect(ANativeWindow *surface, const char *reason);
diff --git a/media/libstagefright/mpeg2ts/Android.bp b/media/libstagefright/mpeg2ts/Android.bp
index 42afea3..5d697f7 100644
--- a/media/libstagefright/mpeg2ts/Android.bp
+++ b/media/libstagefright/mpeg2ts/Android.bp
@@ -1,12 +1,11 @@
-cc_library_static {
- name: "libstagefright_mpeg2support",
+cc_defaults {
+ name: "libstagefright_mpeg2support_defaults",
srcs: [
"AnotherPacketSource.cpp",
"ATSParser.cpp",
"CasManager.cpp",
"ESQueue.cpp",
- "HlsSampleDecryptor.cpp",
],
include_dirs: [
@@ -28,7 +27,6 @@
},
shared_libs: [
- "libcrypto",
"libhidlmemory",
"android.hardware.cas.native@1.0",
"android.hidl.memory@1.0",
@@ -36,9 +34,10 @@
],
header_libs: [
- "libmedia_headers",
+ "libmedia_datasource_headers",
"libaudioclient_headers",
"media_ndk_headers",
+ "libstagefright_foundation_headers",
],
export_include_dirs: ["."],
@@ -46,4 +45,41 @@
whole_static_libs: [
"libstagefright_metadatautils",
],
+
+ min_sdk_version: "29",
+
+ host_supported: true,
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
+
+
+cc_library_static {
+ name: "libstagefright_mpeg2support",
+ defaults: [
+ "libstagefright_mpeg2support_defaults",
+ ],
+ cflags: [
+ "-DENABLE_CRYPTO",
+ ],
+ shared_libs: [
+ "libcrypto",
+ ],
+ srcs: [
+ "HlsSampleDecryptor.cpp",
+ ],
+}
+
+cc_library_static {
+ name: "libstagefright_mpeg2support_nocrypto",
+ defaults: [
+ "libstagefright_mpeg2support_defaults",
+ ],
+ apex_available: [
+ "com.android.media",
+ ],
}
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 4bb21fa..801dba1 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -36,7 +36,7 @@
#include <inttypes.h>
#include <netinet/in.h>
-#ifndef __ANDROID_APEX__
+#ifdef ENABLE_CRYPTO
#include "HlsSampleDecryptor.h"
#endif
@@ -55,10 +55,10 @@
// Create the decryptor anyway since we don't know the use-case unless key is provided
// Won't decrypt if key info not available (e.g., scanner/extractor just parsing ts files)
mSampleDecryptor = isSampleEncrypted() ?
-#ifdef __ANDROID_APEX__
- new SampleDecryptor
-#else
+#ifdef ENABLE_CRYPTO
new HlsSampleDecryptor
+#else
+ new SampleDecryptor
#endif
: NULL;
}
@@ -172,29 +172,26 @@
return 0;
}
- unsigned bsmod __unused = bits.getBits(3);
+ bits.skipBits(3); // bsmod
unsigned acmod = bits.getBits(3);
- unsigned cmixlev __unused = 0;
- unsigned surmixlev __unused = 0;
- unsigned dsurmod __unused = 0;
if ((acmod & 1) > 0 && acmod != 1) {
if (bits.numBitsLeft() < 2) {
return 0;
}
- cmixlev = bits.getBits(2);
+ bits.skipBits(2); //cmixlev
}
if ((acmod & 4) > 0) {
if (bits.numBitsLeft() < 2) {
return 0;
}
- surmixlev = bits.getBits(2);
+ bits.skipBits(2); //surmixlev
}
if (acmod == 2) {
if (bits.numBitsLeft() < 2) {
return 0;
}
- dsurmod = bits.getBits(2);
+ bits.skipBits(2); //dsurmod
}
if (bits.numBitsLeft() < 1) {
@@ -269,7 +266,7 @@
samplingRate = samplingRateTable2[fscod2];
} else {
samplingRate = samplingRateTable[fscod];
- unsigned numblkscod __unused = bits.getBits(2);
+ bits.skipBits(2); // numblkscod
}
unsigned acmod = bits.getBits(3);
@@ -1087,7 +1084,7 @@
}
unsigned numAUs = bits.getBits(8);
bits.skipBits(8);
- unsigned quantization_word_length __unused = bits.getBits(2);
+ bits.skipBits(2); // quantization_word_length
unsigned audio_sampling_frequency = bits.getBits(3);
unsigned num_channels = bits.getBits(3);
diff --git a/media/libstagefright/mpeg2ts/TEST_MAPPING b/media/libstagefright/mpeg2ts/TEST_MAPPING
new file mode 100644
index 0000000..b25d732
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/TEST_MAPPING
@@ -0,0 +1,7 @@
+// frameworks/av/media/libstagefright/mpeg2ts
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ //{ "name": "Mpeg2tsUnitTest" }
+ ]
+}
diff --git a/media/libstagefright/mpeg2ts/test/Android.bp b/media/libstagefright/mpeg2ts/test/Android.bp
new file mode 100644
index 0000000..d8b0304
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/test/Android.bp
@@ -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.
+ */
+
+cc_test{
+ name: "Mpeg2tsUnitTest",
+ gtest: true,
+ test_suites: ["device-tests"],
+
+ srcs: [
+ "Mpeg2tsUnitTest.cpp"
+ ],
+
+ shared_libs: [
+ "android.hardware.cas@1.0",
+ "android.hardware.cas.native@1.0",
+ "android.hidl.token@1.0-utils",
+ "android.hidl.allocator@1.0",
+ "libcrypto",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ "libmedia",
+ "libbinder",
+ "libbinder_ndk",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libdatasource",
+ "libstagefright",
+ "libstagefright_foundation",
+ "libstagefright_metadatautils",
+ "libstagefright_mpeg2support",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/",
+ "frameworks/av/media/libstagefright/",
+ ],
+
+ header_libs: [
+ "libmedia_headers",
+ "libaudioclient_headers",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/mpeg2ts/test/AndroidTest.xml b/media/libstagefright/mpeg2ts/test/AndroidTest.xml
new file mode 100644
index 0000000..ac1294d
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/test/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Test module config for Mpeg2ts unit tests">
+ <option name="test-suite-tag" value="Mpeg2tsUnitTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="Mpeg2tsUnitTest->/data/local/tmp/Mpeg2tsUnitTest" />
+ <option name="push-file"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/mpeg2ts/test/Mpeg2tsUnitTest.zip?unzip=true"
+ value="/data/local/tmp/Mpeg2tsUnitTestRes/" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="Mpeg2tsUnitTest" />
+ <option name="native-test-flag" value="-P /data/local/tmp/Mpeg2tsUnitTestRes/" />
+ </test>
+</configuration>
diff --git a/media/libstagefright/mpeg2ts/test/Mpeg2tsUnitTest.cpp b/media/libstagefright/mpeg2ts/test/Mpeg2tsUnitTest.cpp
new file mode 100644
index 0000000..79c233b
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/test/Mpeg2tsUnitTest.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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 "Mpeg2tsUnitTest"
+
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <sys/stat.h>
+
+#include <datasource/FileSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaDataBase.h>
+#include <media/stagefright/foundation/AUtils.h>
+
+#include "mpeg2ts/ATSParser.h"
+#include "mpeg2ts/AnotherPacketSource.h"
+
+#include "Mpeg2tsUnitTestEnvironment.h"
+
+constexpr size_t kTSPacketSize = 188;
+constexpr uint16_t kPIDMask = 0x1FFF;
+// Max value of PID which is also used for Null packets
+constexpr uint16_t kPIDMaxValue = 8191;
+constexpr uint8_t kTSSyncByte = 0x47;
+constexpr uint8_t kVideoPresent = 0x01;
+constexpr uint8_t kAudioPresent = 0x02;
+constexpr uint8_t kMetaDataPresent = 0x04;
+
+static Mpeg2tsUnitTestEnvironment *gEnv = nullptr;
+
+using namespace android;
+
+class Mpeg2tsUnitTest
+ : public ::testing ::TestWithParam<
+ tuple</*fileName*/ string, /*sourceType*/ char, /*numSource*/ uint16_t>> {
+ public:
+ Mpeg2tsUnitTest()
+ : mInputBuffer(nullptr), mSource(nullptr), mFpInput(nullptr), mParser(nullptr) {}
+
+ ~Mpeg2tsUnitTest() {
+ if (mInputBuffer) free(mInputBuffer);
+ if (mFpInput) fclose(mFpInput);
+ mSource.clear();
+ }
+
+ void SetUp() override {
+ mOffset = 0;
+ mNumDataSource = 0;
+ tuple<string, char, uint16_t> params = GetParam();
+ char sourceType = get<1>(params);
+ /* mSourceType = 0b x x x x x M A V
+ / | \
+ metaData audio video */
+ mMediaType = (sourceType & 0x07);
+ mNumDataSource = get<2>(params);
+ string inputFile = gEnv->getRes() + get<0>(params);
+ mFpInput = fopen(inputFile.c_str(), "rb");
+ ASSERT_NE(mFpInput, nullptr) << "Failed to open file: " << inputFile;
+
+ struct stat buf;
+ int8_t err = stat(inputFile.c_str(), &buf);
+ ASSERT_EQ(err, 0) << "Failed to get information for file: " << inputFile;
+
+ long fileSize = buf.st_size;
+ mTotalPackets = fileSize / kTSPacketSize;
+ int32_t fd = fileno(mFpInput);
+ ASSERT_GE(fd, 0) << "Failed to get the integer file descriptor";
+
+ mSource = new FileSource(dup(fd), 0, buf.st_size);
+ ASSERT_NE(mSource, nullptr) << "Failed to get the data source!";
+
+ mParser = new ATSParser();
+ ASSERT_NE(mParser, nullptr) << "Unable to create ATS parser!";
+ mInputBuffer = (uint8_t *)malloc(kTSPacketSize);
+ ASSERT_NE(mInputBuffer, nullptr) << "Failed to allocate memory for TS packet!";
+ }
+
+ uint64_t mOffset;
+ uint64_t mTotalPackets;
+ uint16_t mNumDataSource;
+
+ int8_t mMediaType;
+
+ uint8_t *mInputBuffer;
+ string mInputFile;
+ sp<DataSource> mSource;
+ FILE *mFpInput;
+ ATSParser *mParser;
+};
+
+TEST_P(Mpeg2tsUnitTest, MediaInfoTest) {
+ bool videoFound = false;
+ bool audioFound = false;
+ bool metaDataFound = false;
+ bool syncPointPresent = false;
+
+ int16_t totalDataSource = 0;
+ int32_t val32 = 0;
+ uint8_t numDataSource = 0;
+ uint8_t packet[kTSPacketSize];
+ ssize_t numBytesRead = -1;
+
+ ATSParser::SyncEvent event(mOffset);
+ static const ATSParser::SourceType mediaType[] = {ATSParser::VIDEO, ATSParser::AUDIO,
+ ATSParser::META, ATSParser::NUM_SOURCE_TYPES};
+ const uint32_t nMediaTypes = sizeof(mediaType) / sizeof(mediaType[0]);
+
+ while ((numBytesRead = mSource->readAt(mOffset, packet, kTSPacketSize)) == kTSPacketSize) {
+ ASSERT_TRUE(packet[0] == kTSSyncByte) << "Sync byte error!";
+
+ // pid is 13 bits
+ uint16_t pid = (packet[1] + (packet[2] << 8)) & kPIDMask;
+ ASSERT_TRUE(pid <= kPIDMaxValue) << "Invalid PID: " << pid;
+
+ status_t err = mParser->feedTSPacket(packet, kTSPacketSize, &event);
+ ASSERT_EQ(err, (status_t)OK) << "Unable to feed TS packet!";
+
+ mOffset += numBytesRead;
+ for (int i = 0; i < nMediaTypes; i++) {
+ if (mParser->hasSource(mediaType[i])) {
+ switch (mediaType[i]) {
+ case ATSParser::VIDEO:
+ videoFound = true;
+ break;
+ case ATSParser::AUDIO:
+ audioFound = true;
+ break;
+ case ATSParser::META:
+ metaDataFound = true;
+ break;
+ case ATSParser::NUM_SOURCE_TYPES:
+ numDataSource = 3;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if (videoFound && audioFound && metaDataFound && (numDataSource == 3)) break;
+ }
+
+ for (int i = 0; i < nMediaTypes; i++) {
+ ATSParser::SourceType currentMediaType = mediaType[i];
+ if (mParser->hasSource(currentMediaType)) {
+ if (event.hasReturnedData()) {
+ syncPointPresent = true;
+ sp<AnotherPacketSource> syncPacketSource = event.getMediaSource();
+ ASSERT_NE(syncPacketSource, nullptr)
+ << "Cannot get sync source for media type: " << currentMediaType;
+
+ status_t err = syncPacketSource->start();
+ ASSERT_EQ(err, (status_t)OK) << "Error returned while starting!";
+
+ sp<MetaData> format = syncPacketSource->getFormat();
+ ASSERT_NE(format, nullptr) << "Unable to get the format of the source packet!";
+
+ MediaBufferBase *buf;
+ syncPacketSource->read(&buf, nullptr);
+ ASSERT_NE(buf, nullptr) << "Failed to read sync packet source data";
+
+ MetaDataBase &inMeta = buf->meta_data();
+ bool status = inMeta.findInt32(kKeyIsSyncFrame, &val32);
+ ASSERT_EQ(status, true) << "Sync frame key is not set";
+
+ status = inMeta.findInt32(kKeyCryptoMode, &val32);
+ ASSERT_EQ(status, false) << "Invalid packet, found scrambled packets!";
+
+ err = syncPacketSource->stop();
+ ASSERT_EQ(err, (status_t)OK) << "Error returned while stopping!";
+ }
+ sp<AnotherPacketSource> packetSource = mParser->getSource(currentMediaType);
+ ASSERT_NE(packetSource, nullptr)
+ << "Cannot get source for media type: " << currentMediaType;
+
+ status_t err = packetSource->start();
+ ASSERT_EQ(err, (status_t)OK) << "Error returned while starting!";
+ sp<MetaData> format = packetSource->getFormat();
+ ASSERT_NE(format, nullptr) << "Unable to get the format of the packet!";
+
+ err = packetSource->stop();
+ ASSERT_EQ(err, (status_t)OK) << "Error returned while stopping!";
+ }
+ }
+
+ ASSERT_EQ(videoFound, bool(mMediaType & kVideoPresent)) << "No Video packets found!";
+ ASSERT_EQ(audioFound, bool(mMediaType & kAudioPresent)) << "No Audio packets found!";
+ ASSERT_EQ(metaDataFound, bool(mMediaType & kMetaDataPresent)) << "No meta data found!";
+
+ if (videoFound || audioFound) {
+ ASSERT_TRUE(syncPointPresent) << "No sync points found for audio/video";
+ }
+
+ if (videoFound) totalDataSource += 1;
+ if (audioFound) totalDataSource += 1;
+ if (metaDataFound) totalDataSource += 1;
+
+ ASSERT_TRUE(totalDataSource == mNumDataSource)
+ << "Expected " << mNumDataSource << " data sources, found " << totalDataSource;
+ if (numDataSource == 3) {
+ ASSERT_EQ(numDataSource, mNumDataSource)
+ << "Expected " << mNumDataSource << " data sources, found " << totalDataSource;
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ infoTest, Mpeg2tsUnitTest,
+ ::testing::Values(make_tuple("crowd_1920x1080_25fps_6700kbps_h264.ts", 0x01, 1),
+ make_tuple("segment000001.ts", 0x03, 2),
+ make_tuple("bbb_44100hz_2ch_128kbps_mp3_5mins.ts", 0x02, 1)));
+
+int32_t main(int argc, char **argv) {
+ gEnv = new Mpeg2tsUnitTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ uint8_t status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("Mpeg2tsUnit Test Result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/mpeg2ts/test/Mpeg2tsUnitTestEnvironment.h b/media/libstagefright/mpeg2ts/test/Mpeg2tsUnitTestEnvironment.h
new file mode 100644
index 0000000..9e41db7
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/test/Mpeg2tsUnitTestEnvironment.h
@@ -0,0 +1,73 @@
+/*
+ * 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 __MPEG2TS_UNIT_TEST_ENVIRONMENT_H__
+#define __MPEG2TS_UNIT_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class Mpeg2tsUnitTestEnvironment : public::testing::Environment {
+ public:
+ Mpeg2tsUnitTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int Mpeg2tsUnitTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"path", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P': {
+ setRes(optarg);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __MPEG2TS_UNIT_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/mpeg2ts/test/README.md b/media/libstagefright/mpeg2ts/test/README.md
new file mode 100644
index 0000000..237ce72
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/test/README.md
@@ -0,0 +1,38 @@
+## Media Testing ##
+---
+#### Mpeg2TS Unit Test :
+The Mpeg2TS Unit Test Suite validates the functionality of the libraries present in Mpeg2TS.
+
+Run the following steps to build the test suite:
+```
+mmm frameworks/av/media/libstagefright/mpeg2ts/test/
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+
+adb push ${OUT}/data/nativetest64/Mpeg2tsUnitTest/Mpeg2tsUnitTest /data/local/tmp/
+
+To test 32-bit binary push binaries from nativetest.
+
+adb push ${OUT}/data/nativetest/Mpeg2tsUnitTest/Mpeg2tsUnitTest /data/local/tmp/
+
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/mpeg2ts/test/Mpeg2tsUnitTest.zip ).
+Download, unzip and push these files into device for testing.
+
+```
+adb push Mpeg2tsUnitTestRes/. /data/local/tmp/
+```
+
+usage: Mpeg2tsUnitTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/Mpeg2tsUnitTest -P /data/local/tmp/Mpeg2tsUnitTestRes/
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest Mpeg2tsUnitTest -- --enable-module-dynamic-download=true
+```
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
index 7d217eb..f7bf3ba 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
@@ -67,7 +67,7 @@
int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
Message tMsg;
tMsg.type = Message::Type::EVENT;
- tMsg.fence = native_handle_create(0, 0);
+ tMsg.fence.setTo(native_handle_create(0, 0), /* shouldOwn = */ true);
tMsg.data.eventData.event = uint32_t(OMX_EventDataSpaceChanged);
tMsg.data.eventData.data1 = dataSpace;
tMsg.data.eventData.data2 = aspects;
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/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp
index 4bc67e8..0164040 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.cpp
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -51,7 +51,66 @@
return NOT_ENOUGH_DATA;
}
+ sp<ABuffer> buffer = *queue->begin();
+ int32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", &rtpTime));
+ int64_t startTime = source->mFirstSysTime / 1000;
+ int64_t nowTime = ALooper::GetNowUs() / 1000;
+ int64_t playedTime = nowTime - startTime;
+ int32_t playedTimeRtp = source->mFirstRtpTime +
+ (((uint32_t)playedTime) * (source->mClockRate / 1000));
+ const int32_t jitterTime = source->mClockRate / 5; // 200ms
+ int32_t expiredTimeInJb = rtpTime + jitterTime;
+ bool isExpired = expiredTimeInJb <= (playedTimeRtp);
+ bool isTooLate200 = expiredTimeInJb < (playedTimeRtp - jitterTime);
+ bool isTooLate300 = expiredTimeInJb < (playedTimeRtp - (jitterTime * 3 / 2));
+
+ if (mShowQueue && mShowQueueCnt < 20) {
+ showCurrentQueue(queue);
+ ALOGD("start=%lld, now=%lld, played=%lld", (long long)startTime,
+ (long long)nowTime, (long long)playedTime);
+ ALOGD("rtp-time(JB)=%d, played-rtp-time(JB)=%d, expired-rtp-time(JB)=%d isExpired=%d",
+ rtpTime, playedTimeRtp, expiredTimeInJb, isExpired);
+ mShowQueueCnt++;
+ }
+
+ ALOGV("start=%lld, now=%lld, played=%lld", (long long)startTime,
+ (long long)nowTime, (long long)playedTime);
+ ALOGV("rtp-time(JB)=%d, played-rtp-time(JB)=%d, expired-rtp-time(JB)=%d isExpired=%d",
+ rtpTime, playedTimeRtp, expiredTimeInJb, isExpired);
+
+ if (!isExpired) {
+ ALOGV("buffering in jitter buffer.");
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (isTooLate200)
+ ALOGW("=== WARNING === buffer arrived 200ms late. === WARNING === ");
+
+ if (isTooLate300) {
+ ALOGW("buffer arrived too late. 300ms..");
+ ALOGW("start=%lld, now=%lld, played=%lld", (long long)startTime,
+ (long long)nowTime, (long long)playedTime);
+ ALOGW("rtp-time(JB)=%d, plyed-rtp-time(JB)=%d, exp-rtp-time(JB)=%d diff=%lld isExpired=%d",
+ rtpTime, playedTimeRtp, expiredTimeInJb,
+ ((long long)playedTimeRtp) - expiredTimeInJb, isExpired);
+ ALOGW("expected Seq. NO =%d", buffer->int32Data());
+
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ CHECK((*it)->meta()->findInt32("rtp-time", &rtpTime));
+ if (rtpTime + jitterTime >= playedTimeRtp) {
+ mNextExpectedSeqNo = (*it)->int32Data();
+ break;
+ }
+ it++;
+ }
+ source->noticeAbandonBuffer();
+ }
+
if (mNextExpectedSeqNoValid) {
+ int32_t size = queue->size();
+ int32_t cnt = 0;
List<sp<ABuffer> >::iterator it = queue->begin();
while (it != queue->end()) {
if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
@@ -59,14 +118,19 @@
}
it = queue->erase(it);
+ cnt++;
}
+ if (cnt > 0) {
+ source->noticeAbandonBuffer(cnt);
+ ALOGW("delete %d of %d buffers", cnt, size);
+ }
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
}
- sp<ABuffer> buffer = *queue->begin();
+ buffer = *queue->begin();
if (!mNextExpectedSeqNoValid) {
mNextExpectedSeqNoValid = true;
@@ -290,6 +354,7 @@
unit->data()[0] = (nri << 5) | nalType;
size_t offset = 1;
+ int32_t cvo = -1;
List<sp<ABuffer> >::iterator it = queue->begin();
for (size_t i = 0; i < totalCount; ++i) {
const sp<ABuffer> &buffer = *it;
@@ -300,6 +365,8 @@
#endif
memcpy(unit->data() + offset, buffer->data() + 2, buffer->size() - 2);
+
+ buffer->meta()->findInt32("cvo", &cvo);
offset += buffer->size() - 2;
it = queue->erase(it);
@@ -307,6 +374,10 @@
unit->setRange(0, totalSize);
+ if (cvo >= 0) {
+ unit->meta()->setInt32("cvo", cvo);
+ }
+
addSingleNALUnit(unit);
ALOGV("successfully assembled a NAL unit from fragments.");
@@ -327,6 +398,7 @@
sp<ABuffer> accessUnit = new ABuffer(totalSize);
size_t offset = 0;
+ int32_t cvo = -1;
for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
it != mNALUnits.end(); ++it) {
memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4);
@@ -335,6 +407,8 @@
sp<ABuffer> nal = *it;
memcpy(accessUnit->data() + offset, nal->data(), nal->size());
offset += nal->size();
+
+ nal->meta()->findInt32("cvo", &cvo);
}
CopyTimes(accessUnit, *mNALUnits.begin());
@@ -343,6 +417,9 @@
printf(mAccessUnitDamaged ? "X" : ".");
fflush(stdout);
#endif
+ if (cvo >= 0) {
+ accessUnit->meta()->setInt32("cvo", cvo);
+ }
if (mAccessUnitDamaged) {
accessUnit->meta()->setInt32("damaged", true);
diff --git a/media/libstagefright/rtsp/AHEVCAssembler.cpp b/media/libstagefright/rtsp/AHEVCAssembler.cpp
new file mode 100644
index 0000000..93869fb
--- /dev/null
+++ b/media/libstagefright/rtsp/AHEVCAssembler.cpp
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2010 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 "AHEVCAssembler"
+#include <utils/Log.h>
+
+#include "AHEVCAssembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+#include <stdint.h>
+
+#define H265_NALU_MASK 0x3F
+#define H265_NALU_VPS 0x20
+#define H265_NALU_SPS 0x21
+#define H265_NALU_PPS 0x22
+#define H265_NALU_AP 0x30
+#define H265_NALU_FU 0x31
+#define H265_NALU_PACI 0x32
+
+
+namespace android {
+
+// static
+AHEVCAssembler::AHEVCAssembler(const sp<AMessage> ¬ify)
+ : mNotifyMsg(notify),
+ mAccessUnitRTPTime(0),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0),
+ mAccessUnitDamaged(false) {
+
+ ALOGV("Constructor");
+}
+
+AHEVCAssembler::~AHEVCAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AHEVCAssembler::addNALUnit(
+ const sp<ARTPSource> &source) {
+ List<sp<ABuffer> > *queue = source->queue();
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ sp<ABuffer> buffer = *queue->begin();
+ int32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", &rtpTime));
+ int64_t startTime = source->mFirstSysTime / 1000;
+ int64_t nowTime = ALooper::GetNowUs() / 1000;
+ int64_t playedTime = nowTime - startTime;
+ int32_t playedTimeRtp = source->mFirstRtpTime +
+ (((uint32_t)playedTime) * (source->mClockRate / 1000));
+ int32_t expiredTimeInJb = rtpTime + (source->mClockRate / 5);
+ bool isExpired = expiredTimeInJb <= (playedTimeRtp);
+ ALOGV("start=%lld, now=%lld, played=%lld", (long long)startTime,
+ (long long)nowTime, (long long)playedTime);
+ ALOGV("rtp-time(JB)=%d, played-rtp-time(JB)=%d, expired-rtp-time(JB)=%d isExpired=%d",
+ rtpTime, playedTimeRtp, expiredTimeInJb, isExpired);
+
+ if (!isExpired) {
+ ALOGV("buffering in jitter buffer.");
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mNextExpectedSeqNoValid) {
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+ break;
+ }
+
+ it = queue->erase(it);
+ }
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+ }
+
+ buffer = *queue->begin();
+
+ if (!mNextExpectedSeqNoValid) {
+ mNextExpectedSeqNoValid = true;
+ mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+ } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+ ALOGV("Not the sequence number I expected");
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if (size < 1 || (data[0] & 0x80)) {
+ // Corrupt.
+
+ ALOGV("Ignoring corrupt buffer.");
+ queue->erase(queue->begin());
+
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ unsigned nalType = (data[0] >> 1) & H265_NALU_MASK;
+ if (nalType > 0 && nalType < H265_NALU_AP) {
+ addSingleNALUnit(buffer);
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return OK;
+ } else if (nalType == H265_NALU_FU) {
+ // FU-A
+ return addFragmentedNALUnit(queue);
+ } else if (nalType == H265_NALU_AP) {
+ // STAP-A
+ bool success = addSingleTimeAggregationPacket(buffer);
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return success ? OK : MALFORMED_PACKET;
+ } else if (nalType == 0) {
+ ALOGV("Ignoring undefined nal type.");
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return OK;
+ } else {
+ ALOGV("Ignoring unsupported buffer (nalType=%d)", nalType);
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return MALFORMED_PACKET;
+ }
+}
+
+void AHEVCAssembler::addSingleNALUnit(const sp<ABuffer> &buffer) {
+ ALOGV("addSingleNALUnit of size %zu", buffer->size());
+#if !LOG_NDEBUG
+ hexdump(buffer->data(), buffer->size());
+#endif
+
+ uint32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ if (!mNALUnits.empty() && rtpTime != mAccessUnitRTPTime) {
+ submitAccessUnit();
+ }
+ mAccessUnitRTPTime = rtpTime;
+
+ mNALUnits.push_back(buffer);
+}
+
+bool AHEVCAssembler::addSingleTimeAggregationPacket(const sp<ABuffer> &buffer) {
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if (size < 3) {
+ ALOGV("Discarding too small STAP-A packet.");
+ return false;
+ }
+
+ ++data;
+ --size;
+ while (size >= 2) {
+ size_t nalSize = (data[0] << 8) | data[1];
+
+ if (size < nalSize + 2) {
+ ALOGV("Discarding malformed STAP-A packet.");
+ return false;
+ }
+
+ sp<ABuffer> unit = new ABuffer(nalSize);
+ memcpy(unit->data(), &data[2], nalSize);
+
+ CopyTimes(unit, buffer);
+
+ addSingleNALUnit(unit);
+
+ data += 2 + nalSize;
+ size -= 2 + nalSize;
+ }
+
+ if (size != 0) {
+ ALOGV("Unexpected padding at end of STAP-A packet.");
+ }
+
+ return true;
+}
+
+ARTPAssembler::AssemblyStatus AHEVCAssembler::addFragmentedNALUnit(
+ List<sp<ABuffer> > *queue) {
+ CHECK(!queue->empty());
+
+ sp<ABuffer> buffer = *queue->begin();
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ CHECK(size > 0);
+ /* H265 payload header is 16 bit
+ 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |F| Type | Layer ID | TID |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ unsigned indicator = (data[0] >> 1);
+
+ CHECK((indicator & H265_NALU_MASK) == H265_NALU_FU);
+
+ if (size < 3) {
+ ALOGV("Ignoring malformed FU buffer (size = %zu)", size);
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ if (!(data[2] & 0x80)) {
+ // Start bit not set on the first buffer.
+
+ ALOGV("Start bit not set on first buffer");
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ /* FU INDICATOR HDR
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+
+ |S|E| Type |
+ +-+-+-+-+-+-+-+-+
+ */
+ uint32_t nalType = data[2] & H265_NALU_MASK;
+ uint32_t tid = data[1] & 0x7;
+ ALOGV("nalType =%u, tid =%u", nalType, tid);
+
+ uint32_t expectedSeqNo = (uint32_t)buffer->int32Data() + 1;
+ size_t totalSize = size - 3;
+ size_t totalCount = 1;
+ bool complete = false;
+
+ if (data[2] & 0x40) {
+ // Huh? End bit also set on the first buffer.
+
+ ALOGV("Grrr. This isn't fragmented at all.");
+
+ complete = true;
+ } else {
+ List<sp<ABuffer> >::iterator it = ++queue->begin();
+ while (it != queue->end()) {
+ ALOGV("sequence length %zu", totalCount);
+
+ const sp<ABuffer> &buffer = *it;
+
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if ((uint32_t)buffer->int32Data() != expectedSeqNo) {
+ ALOGV("sequence not complete, expected seqNo %d, got %d",
+ expectedSeqNo, (uint32_t)buffer->int32Data());
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ if (size < 3
+ || ((data[0] >> 1) & H265_NALU_MASK) != indicator
+ || (data[2] & H265_NALU_MASK) != nalType
+ || (data[2] & 0x80)) {
+ ALOGV("Ignoring malformed FU buffer.");
+
+ // Delete the whole start of the FU.
+
+ it = queue->begin();
+ for (size_t i = 0; i <= totalCount; ++i) {
+ it = queue->erase(it);
+ }
+
+ mNextExpectedSeqNo = expectedSeqNo + 1;
+
+ return MALFORMED_PACKET;
+ }
+
+ totalSize += size - 3;
+ ++totalCount;
+
+ expectedSeqNo = expectedSeqNo + 1;
+
+ if (data[2] & 0x40) {
+ // This is the last fragment.
+ complete = true;
+ break;
+ }
+
+ ++it;
+ }
+ }
+
+ if (!complete) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ mNextExpectedSeqNo = expectedSeqNo;
+
+ // We found all the fragments that make up the complete NAL unit.
+
+ // Leave room for the header. So far totalSize did not include the
+ // header byte.
+ totalSize += 2;
+
+ sp<ABuffer> unit = new ABuffer(totalSize);
+ CopyTimes(unit, *queue->begin());
+
+ unit->data()[0] = (nalType << 1);
+ unit->data()[1] = tid;
+
+ size_t offset = 2;
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ for (size_t i = 0; i < totalCount; ++i) {
+ const sp<ABuffer> &buffer = *it;
+
+ ALOGV("piece #%zu/%zu", i + 1, totalCount);
+#if !LOG_NDEBUG
+ hexdump(buffer->data(), buffer->size());
+#endif
+
+ memcpy(unit->data() + offset, buffer->data() + 3, buffer->size() - 3);
+ offset += buffer->size() - 3;
+
+ it = queue->erase(it);
+ }
+
+ unit->setRange(0, totalSize);
+
+ addSingleNALUnit(unit);
+
+ ALOGV("successfully assembled a NAL unit from fragments.");
+
+ return OK;
+}
+
+void AHEVCAssembler::submitAccessUnit() {
+ CHECK(!mNALUnits.empty());
+
+ ALOGV("Access unit complete (%zu nal units)", mNALUnits.size());
+
+ size_t totalSize = 0;
+ for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
+ it != mNALUnits.end(); ++it) {
+ totalSize += 4 + (*it)->size();
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ size_t offset = 0;
+ for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
+ it != mNALUnits.end(); ++it) {
+ memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4);
+ offset += 4;
+
+ sp<ABuffer> nal = *it;
+ memcpy(accessUnit->data() + offset, nal->data(), nal->size());
+ offset += nal->size();
+ }
+
+ CopyTimes(accessUnit, *mNALUnits.begin());
+
+#if 0
+ printf(mAccessUnitDamaged ? "X" : ".");
+ fflush(stdout);
+#endif
+
+ if (mAccessUnitDamaged) {
+ accessUnit->meta()->setInt32("damaged", true);
+ }
+
+ mNALUnits.clear();
+ mAccessUnitDamaged = false;
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setBuffer("access-unit", accessUnit);
+ msg->post();
+}
+
+ARTPAssembler::AssemblyStatus AHEVCAssembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ AssemblyStatus status = addNALUnit(source);
+ if (status == MALFORMED_PACKET) {
+ mAccessUnitDamaged = true;
+ }
+ return status;
+}
+
+void AHEVCAssembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ ALOGV("packetLost (expected %d)", mNextExpectedSeqNo);
+
+ ++mNextExpectedSeqNo;
+
+ mAccessUnitDamaged = true;
+}
+
+void AHEVCAssembler::onByeReceived() {
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setInt32("eos", true);
+ msg->post();
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/AHEVCAssembler.h b/media/libstagefright/rtsp/AHEVCAssembler.h
new file mode 100644
index 0000000..cc20622
--- /dev/null
+++ b/media/libstagefright/rtsp/AHEVCAssembler.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 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 A_HEVC_ASSEMBLER_H_
+
+#define A_HEVC_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+struct AHEVCAssembler : public ARTPAssembler {
+ AHEVCAssembler(const sp<AMessage> ¬ify);
+
+protected:
+ virtual ~AHEVCAssembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void onByeReceived();
+ virtual void packetLost();
+
+private:
+ sp<AMessage> mNotifyMsg;
+
+ uint32_t mAccessUnitRTPTime;
+ bool mNextExpectedSeqNoValid;
+ uint32_t mNextExpectedSeqNo;
+ bool mAccessUnitDamaged;
+ List<sp<ABuffer> > mNALUnits;
+
+ AssemblyStatus addNALUnit(const sp<ARTPSource> &source);
+ void addSingleNALUnit(const sp<ABuffer> &buffer);
+ AssemblyStatus addFragmentedNALUnit(List<sp<ABuffer> > *queue);
+ bool addSingleTimeAggregationPacket(const sp<ABuffer> &buffer);
+
+ void submitAccessUnit();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AHEVCAssembler);
+};
+
+} // namespace android
+
+#endif // A_HEVC_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
index 574bd7a..8f4df8e 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -454,6 +454,17 @@
mFormat->setInt32(kKeyWidth, width);
mFormat->setInt32(kKeyHeight, height);
+ } else if (!strncmp(desc.c_str(), "H265/", 5)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+
+ int32_t width, height;
+ if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
+ width = -1;
+ height = -1;
+ }
+
+ mFormat->setInt32(kKeyWidth, width);
+ mFormat->setInt32(kKeyHeight, height);
} else if (!strncmp(desc.c_str(), "H263-2000/", 10)
|| !strncmp(desc.c_str(), "H263-1998/", 10)) {
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
diff --git a/media/libstagefright/rtsp/ARTPAssembler.cpp b/media/libstagefright/rtsp/ARTPAssembler.cpp
index befc226..52aa3a0 100644
--- a/media/libstagefright/rtsp/ARTPAssembler.cpp
+++ b/media/libstagefright/rtsp/ARTPAssembler.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#define LOG_TAG "ARTPAssembler"
#include "ARTPAssembler.h"
#include <media/stagefright/foundation/ABuffer.h>
@@ -21,12 +22,16 @@
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AMessage.h>
+#include <android-base/properties.h>
+
#include <stdint.h>
namespace android {
ARTPAssembler::ARTPAssembler()
- : mFirstFailureTimeUs(-1) {
+ : mShowQueueCnt(0),
+ mFirstFailureTimeUs(-1) {
+ mShowQueue = android::base::GetBoolProperty("debug.stagefright.rtp", false);
}
void ARTPAssembler::onPacketReceived(const sp<ARTPSource> &source) {
@@ -141,4 +146,15 @@
return accessUnit;
}
+void ARTPAssembler::showCurrentQueue(List<sp<ABuffer> > *queue) {
+ AString temp("Queue elem size : ");
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ temp.append((*it)->size());
+ temp.append(" \t");
+ it++;
+ }
+ ALOGD("%s",temp.c_str());
+};
+
} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPAssembler.h b/media/libstagefright/rtsp/ARTPAssembler.h
index 4082d4c..191f08e 100644
--- a/media/libstagefright/rtsp/ARTPAssembler.h
+++ b/media/libstagefright/rtsp/ARTPAssembler.h
@@ -56,6 +56,11 @@
static sp<ABuffer> MakeCompoundFromPackets(
const List<sp<ABuffer> > &frames);
+ void showCurrentQueue(List<sp<ABuffer> > *queue);
+
+ bool mShowQueue;
+ int32_t mShowQueueCnt;
+
private:
int64_t mFirstFailureTimeUs;
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
index 6a4706d..1346c9a 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -30,6 +30,8 @@
#include <media/stagefright/foundation/AString.h>
#include <media/stagefright/foundation/hexdump.h>
+#include <android/multinetwork.h>
+
#include <arpa/inet.h>
#include <sys/socket.h>
@@ -53,6 +55,7 @@
const int64_t ARTPConnection::kSelectTimeoutUs = 1000LL;
struct ARTPConnection::StreamInfo {
+ bool isIPv6;
int mRTPSocket;
int mRTCPSocket;
sp<ASessionDescription> mSessionDesc;
@@ -63,14 +66,19 @@
int64_t mNumRTCPPacketsReceived;
int64_t mNumRTPPacketsReceived;
struct sockaddr_in mRemoteRTCPAddr;
+ struct sockaddr_in6 mRemoteRTCPAddr6;
bool mIsInjected;
+
+ // RTCP Extension for CVO
+ int mCVOExtMap; // will be set to 0 if cvo is not negotiated in sdp
};
ARTPConnection::ARTPConnection(uint32_t flags)
: mFlags(flags),
mPollEventPending(false),
- mLastReceiverReportTimeUs(-1) {
+ mLastReceiverReportTimeUs(-1),
+ mLastBitrateReportTimeUs(-1) {
}
ARTPConnection::~ARTPConnection() {
@@ -145,6 +153,117 @@
TRESPASS();
}
+// static
+void ARTPConnection::MakeRTPSocketPair(
+ int *rtpSocket, int *rtcpSocket, const char *localIp, const char *remoteIp,
+ unsigned localPort, unsigned remotePort, int64_t socketNetwork) {
+ bool isIPv6 = false;
+ if (strchr(localIp, ':') != NULL)
+ isIPv6 = true;
+
+ *rtpSocket = socket(isIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(*rtpSocket, 0);
+
+ bumpSocketBufferSize(*rtpSocket);
+
+ *rtcpSocket = socket(isIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(*rtcpSocket, 0);
+
+ if (socketNetwork != 0) {
+ ALOGD("trying to bind rtp socket(%d) to network(%llu).",
+ *rtpSocket, (unsigned long long)socketNetwork);
+
+ int result = android_setsocknetwork((net_handle_t)socketNetwork, *rtpSocket);
+ if (result != 0) {
+ ALOGW("failed(%d) to bind rtp socket(%d) to network(%llu)",
+ result, *rtpSocket, (unsigned long long)socketNetwork);
+ }
+ result = android_setsocknetwork((net_handle_t)socketNetwork, *rtcpSocket);
+ if (result != 0) {
+ ALOGW("failed(%d) to bind rtcp socket(%d) to network(%llu)",
+ result, *rtcpSocket, (unsigned long long)socketNetwork);
+ }
+ }
+
+ bumpSocketBufferSize(*rtcpSocket);
+
+ struct sockaddr *addr;
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+
+ if (isIPv6) {
+ addr = (struct sockaddr *)&addr6;
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, localIp, &addr6.sin6_addr);
+ addr6.sin6_port = htons((uint16_t)localPort);
+ } else {
+ addr = (struct sockaddr *)&addr4;
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = AF_INET;
+ addr4.sin_addr.s_addr = inet_addr(localIp);
+ addr4.sin_port = htons((uint16_t)localPort);
+ }
+
+ int sockopt = 1;
+ setsockopt(*rtpSocket, SOL_SOCKET, SO_REUSEADDR, (int *)&sockopt, sizeof(sockopt));
+ setsockopt(*rtcpSocket, SOL_SOCKET, SO_REUSEADDR, (int *)&sockopt, sizeof(sockopt));
+
+ int sizeSockSt = isIPv6 ? sizeof(addr6) : sizeof(addr4);
+
+ if (bind(*rtpSocket, addr, sizeSockSt) == 0) {
+ ALOGI("rtp socket successfully binded. addr=%s:%d", localIp, localPort);
+ } else {
+ ALOGE("failed to bind rtp socket addr=%s:%d err=%s", localIp, localPort, strerror(errno));
+ return;
+ }
+
+ if (isIPv6)
+ addr6.sin6_port = htons(localPort + 1);
+ else
+ addr4.sin_port = htons(localPort + 1);
+
+ if (bind(*rtcpSocket, addr, sizeSockSt) == 0) {
+ ALOGI("rtcp socket successfully binded. addr=%s:%d", localIp, localPort + 1);
+ } else {
+ ALOGE("failed to bind rtcp socket addr=%s:%d err=%s", localIp,
+ localPort + 1, strerror(errno));
+ }
+
+ // Re uses addr variable as remote addr.
+ if (isIPv6) {
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, remoteIp, &addr6.sin6_addr);
+ addr6.sin6_port = htons((uint16_t)remotePort);
+ } else {
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = AF_INET;
+ addr4.sin_addr.s_addr = inet_addr(remoteIp);
+ addr4.sin_port = htons((uint16_t)remotePort);
+ }
+ if (connect(*rtpSocket, addr, sizeSockSt) == 0) {
+ ALOGI("rtp socket successfully connected to remote=%s:%d", remoteIp, remotePort);
+ } else {
+ ALOGE("failed to connect rtp socket to remote addr=%s:%d err=%s", remoteIp,
+ remotePort, strerror(errno));
+ return;
+ }
+
+ if (isIPv6)
+ addr6.sin6_port = htons(remotePort + 1);
+ else
+ addr4.sin_port = htons(remotePort + 1);
+
+ if (connect(*rtcpSocket, addr, sizeSockSt) == 0) {
+ ALOGI("rtcp socket successfully connected to remote=%s:%d", remoteIp, remotePort + 1);
+ } else {
+ ALOGE("failed to connect rtcp socket addr=%s:%d err=%s", remoteIp,
+ remotePort + 1, strerror(errno));
+ return;
+ }
+}
+
void ARTPConnection::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatAddStream:
@@ -204,6 +323,19 @@
info->mNumRTCPPacketsReceived = 0;
info->mNumRTPPacketsReceived = 0;
memset(&info->mRemoteRTCPAddr, 0, sizeof(info->mRemoteRTCPAddr));
+ memset(&info->mRemoteRTCPAddr6, 0, sizeof(info->mRemoteRTCPAddr6));
+
+ sp<ASessionDescription> sessionDesc = info->mSessionDesc;
+ info->mCVOExtMap = 0;
+ for (size_t i = 1; i < sessionDesc->countTracks(); ++i) {
+ int32_t cvoExtMap;
+ if (sessionDesc->getCvoExtMap(i, &cvoExtMap)) {
+ info->mCVOExtMap = cvoExtMap;
+ ALOGI("urn:3gpp:video-orientation(cvo) found as extmap:%d", info->mCVOExtMap);
+ } else {
+ ALOGI("urn:3gpp:video-orientation(cvo) not found :%d", info->mCVOExtMap);
+ }
+ }
if (!injected) {
postPollEvent();
@@ -295,6 +427,12 @@
if (err == -ECONNRESET) {
// socket failure, this stream is dead, Jim.
+ sp<AMessage> notify = it->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 400);
+ notify->setInt32("feedback-type", 1);
+ notify->setInt32("sender", it->mSources.valueAt(0)->getSelfID());
+ notify->post();
ALOGW("failed to receive RTP/RTCP datagram.");
it = mStreams.erase(it);
@@ -306,6 +444,8 @@
}
int64_t nowUs = ALooper::GetNowUs();
+ checkRxBitrate(nowUs);
+
if (mLastReceiverReportTimeUs <= 0
|| mLastReceiverReportTimeUs + 5000000LL <= nowUs) {
sp<ABuffer> buffer = new ABuffer(kMaxUDPSize);
@@ -340,13 +480,7 @@
if (buffer->size() > 0) {
ALOGV("Sending RR...");
- ssize_t n;
- do {
- n = sendto(
- s->mRTCPSocket, buffer->data(), buffer->size(), 0,
- (const struct sockaddr *)&s->mRemoteRTCPAddr,
- sizeof(s->mRemoteRTCPAddr));
- } while (n < 0 && errno == EINTR);
+ ssize_t n = send(s, buffer);
if (n <= 0) {
ALOGW("failed to send RTCP receiver report (%s).",
@@ -377,9 +511,21 @@
sp<ABuffer> buffer = new ABuffer(65536);
+ struct sockaddr *pRemoteRTCPAddr;
+ int sizeSockSt;
+ if (s->isIPv6) {
+ pRemoteRTCPAddr = (struct sockaddr *)&s->mRemoteRTCPAddr6;
+ sizeSockSt = sizeof(struct sockaddr_in6);
+ } else {
+ pRemoteRTCPAddr = (struct sockaddr *)&s->mRemoteRTCPAddr;
+ sizeSockSt = sizeof(struct sockaddr_in);
+ }
socklen_t remoteAddrLen =
(!receiveRTP && s->mNumRTCPPacketsReceived == 0)
- ? sizeof(s->mRemoteRTCPAddr) : 0;
+ ? sizeSockSt : 0;
+
+ if (mFlags & kViLTEConnection)
+ remoteAddrLen = 0;
ssize_t nbytes;
do {
@@ -388,8 +534,9 @@
buffer->data(),
buffer->capacity(),
0,
- remoteAddrLen > 0 ? (struct sockaddr *)&s->mRemoteRTCPAddr : NULL,
+ remoteAddrLen > 0 ? pRemoteRTCPAddr : NULL,
remoteAddrLen > 0 ? &remoteAddrLen : NULL);
+ mCumulativeBytes += nbytes;
} while (nbytes < 0 && errno == EINTR);
if (nbytes <= 0) {
@@ -410,6 +557,36 @@
return err;
}
+ssize_t ARTPConnection::send(const StreamInfo *info, const sp<ABuffer> buffer) {
+ struct sockaddr* pRemoteRTCPAddr;
+ int sizeSockSt;
+
+ /* It seems this isIPv6 variable is useless.
+ * We should remove it to prevent confusion */
+ if (info->isIPv6) {
+ pRemoteRTCPAddr = (struct sockaddr *)&info->mRemoteRTCPAddr6;
+ sizeSockSt = sizeof(struct sockaddr_in6);
+ } else {
+ pRemoteRTCPAddr = (struct sockaddr *)&info->mRemoteRTCPAddr;
+ sizeSockSt = sizeof(struct sockaddr_in);
+ }
+
+ if (mFlags & kViLTEConnection) {
+ ALOGV("ViLTE RTCP");
+ pRemoteRTCPAddr = NULL;
+ sizeSockSt = 0;
+ }
+
+ ssize_t n;
+ do {
+ n = sendto(
+ info->mRTCPSocket, buffer->data(), buffer->size(), 0,
+ pRemoteRTCPAddr, sizeSockSt);
+ } while (n < 0 && errno == EINTR);
+
+ return n;
+}
+
status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) {
if (s->mNumRTPPacketsReceived++ == 0) {
sp<AMessage> notify = s->mNotifyMsg->dup();
@@ -431,6 +608,11 @@
return -1;
}
+ if ((data[1] & 0x7f) == 20 /* decimal */) {
+ // Unassigned payload type
+ return -1;
+ }
+
if (data[0] & 0x20) {
// Padding present.
@@ -454,6 +636,7 @@
return -1;
}
+ int32_t cvoDegrees = -1;
if (data[0] & 0x10) {
// Header eXtension present.
@@ -473,6 +656,7 @@
return -1;
}
+ parseRTPExt(s, (const uint8_t *)extensionData, extensionLength, &cvoDegrees);
payloadOffset += 4 + extensionLength;
}
@@ -487,6 +671,8 @@
meta->setInt32("rtp-time", rtpTime);
meta->setInt32("PT", data[1] & 0x7f);
meta->setInt32("M", data[1] >> 7);
+ if (cvoDegrees >= 0)
+ meta->setInt32("cvo", cvoDegrees);
buffer->setInt32Data(u16at(&data[2]));
buffer->setRange(payloadOffset, size - payloadOffset);
@@ -496,11 +682,65 @@
return OK;
}
+status_t ARTPConnection::parseRTPExt(StreamInfo *s,
+ const uint8_t *extHeader, size_t extLen, int32_t *cvoDegrees) {
+ if (extLen < 4)
+ return -1;
+
+ uint16_t header = (extHeader[0] << 8) | (extHeader[1]);
+ bool isOnebyteHeader = false;
+
+ if (header == 0xBEDE) {
+ isOnebyteHeader = true;
+ } else if (header == 0x1000) {
+ ALOGW("parseRTPExt: two-byte header is not implemented yet");
+ return -1;
+ } else {
+ ALOGW("parseRTPExt: can not recognize header");
+ return -1;
+ }
+
+ const uint8_t *extPayload = extHeader + 4;
+ extLen -= 4;
+ size_t offset = 0; //start from first payload of rtp extension.
+ // one-byte header parser
+ while (isOnebyteHeader && offset < extLen) {
+ uint8_t extmapId = extPayload[offset] >> 4;
+ uint8_t length = (extPayload[offset] & 0xF) + 1;
+ offset++;
+
+ // padding case
+ if (extmapId == 0)
+ continue;
+
+ uint8_t data[16]; // maximum length value
+ for (uint8_t j = 0; offset + j <= extLen && j < length; j++) {
+ data[j] = extPayload[offset + j];
+ }
+
+ offset += length;
+
+ if (extmapId == s->mCVOExtMap) {
+ *cvoDegrees = (int32_t)data[0];
+ return OK;
+ }
+ }
+
+ return BAD_VALUE;
+}
+
status_t ARTPConnection::parseRTCP(StreamInfo *s, const sp<ABuffer> &buffer) {
if (s->mNumRTCPPacketsReceived++ == 0) {
sp<AMessage> notify = s->mNotifyMsg->dup();
notify->setInt32("first-rtcp", true);
notify->post();
+
+ ALOGI("send first-rtcp event to upper layer as ImsRxNotice");
+ sp<AMessage> imsNotify = s->mNotifyMsg->dup();
+ imsNotify->setInt32("rtcp-event", 1);
+ imsNotify->setInt32("payload-type", 101);
+ imsNotify->setInt32("feedback-type", 0);
+ imsNotify->post();
}
const uint8_t *data = buffer->data();
@@ -551,8 +791,12 @@
break;
case 205: // TSFB (transport layer specific feedback)
+ parseTSFB(s, data, headerLength);
+ break;
case 206: // PSFB (payload specific feedback)
// hexdump(data, headerLength);
+ parsePSFB(s, data, headerLength);
+ ALOGI("RTCP packet type %u of size %zu", (unsigned)data[1], headerLength);
break;
case 203:
@@ -621,6 +865,144 @@
return 0;
}
+status_t ARTPConnection::parseTSFB(
+ StreamInfo *s, const uint8_t *data, size_t size) {
+ if (size < 12) {
+ // broken packet
+ return -1;
+ }
+
+ uint8_t msgType = data[0] & 0x1f;
+ uint32_t id = u32at(&data[4]);
+
+ const uint8_t *ptr = &data[12];
+ size -= 12;
+
+ using namespace std;
+ size_t FCISize;
+ switch(msgType) {
+ case 1: // Generic NACK
+ {
+ FCISize = 4;
+ while (size >= FCISize) {
+ uint16_t PID = u16at(&ptr[0]); // lost packet RTP number
+ uint16_t BLP = u16at(&ptr[2]); // Bitmask of following Lost Packets
+
+ size -= FCISize;
+ ptr += FCISize;
+
+ AString list_of_losts;
+ list_of_losts.append(PID);
+ for (int i=0 ; i<16 ; i++) {
+ bool is_lost = BLP & (0x1 << i);
+ if (is_lost) {
+ list_of_losts.append(", ");
+ list_of_losts.append(PID + i);
+ }
+ }
+ ALOGI("Opponent losts packet of RTP %s", list_of_losts.c_str());
+ }
+ break;
+ }
+ case 3: // TMMBR
+ case 4: // TMMBN
+ {
+ FCISize = 8;
+ while (size >= FCISize) {
+ uint32_t MxTBR = u32at(&ptr[4]);
+ uint32_t MxTBRExp = MxTBR >> 26;
+ uint32_t MxTBRMantissa = (MxTBR >> 9) & 0x01FFFF;
+ uint32_t overhead = MxTBR & 0x01FF;
+
+ size -= FCISize;
+ ptr += FCISize;
+
+ uint32_t bitRate = (1 << MxTBRExp) * MxTBRMantissa;
+
+ if (msgType == 3)
+ ALOGI("Op -> UE Req Tx bitrate : %d X 2^%d = %d",
+ MxTBRMantissa, MxTBRExp, bitRate);
+ else if (msgType == 4)
+ ALOGI("OP -> UE Noti Rx bitrate : %d X 2^%d = %d",
+ MxTBRMantissa, MxTBRExp, bitRate);
+
+ sp<AMessage> notify = s->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 205);
+ notify->setInt32("feedback-type", msgType);
+ notify->setInt32("sender", id);
+ notify->setInt32("bit-rate", bitRate);
+ notify->post();
+ ALOGI("overhead : %d", overhead);
+ }
+ break;
+ }
+ default:
+ {
+ ALOGI("Not supported TSFB type %d", msgType);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+status_t ARTPConnection::parsePSFB(
+ StreamInfo *s, const uint8_t *data, size_t size) {
+ if (size < 12) {
+ // broken packet
+ return -1;
+ }
+
+ uint8_t msgType = data[0] & 0x1f;
+ uint32_t id = u32at(&data[4]);
+
+ const uint8_t *ptr = &data[12];
+ size -= 12;
+
+ using namespace std;
+ switch(msgType) {
+ case 1: // Picture Loss Indication (PLI)
+ {
+ if (size > 0) {
+ // PLI does not need parameters
+ break;
+ };
+ sp<AMessage> notify = s->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 206);
+ notify->setInt32("feedback-type", msgType);
+ notify->setInt32("sender", id);
+ notify->post();
+ ALOGI("PLI detected.");
+ break;
+ }
+ case 4: // Full Intra Request (FIR)
+ {
+ if (size < 4) {
+ break;
+ }
+ uint32_t requestedId = u32at(&ptr[0]);
+ if (requestedId == (uint32_t)mSelfID) {
+ sp<AMessage> notify = s->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 206);
+ notify->setInt32("feedback-type", msgType);
+ notify->setInt32("sender", id);
+ notify->post();
+ ALOGI("FIR detected.");
+ }
+ break;
+ }
+ default:
+ {
+ ALOGI("Not supported PSFB type %d", msgType);
+ break;
+ }
+ }
+
+ return 0;
+}
sp<ARTPSource> ARTPConnection::findSource(StreamInfo *info, uint32_t srcId) {
sp<ARTPSource> source;
ssize_t index = info->mSources.indexOfKey(srcId);
@@ -630,6 +1012,8 @@
source = new ARTPSource(
srcId, info->mSessionDesc, info->mIndex, info->mNotifyMsg);
+ source->setSelfID(mSelfID);
+ source->setMinMaxBitrate(mMinBitrate, mMaxBitrate);
info->mSources.add(srcId, source);
} else {
source = info->mSources.valueAt(index);
@@ -645,6 +1029,78 @@
msg->post();
}
+void ARTPConnection::setSelfID(const uint32_t selfID) {
+ mSelfID = selfID;
+}
+
+void ARTPConnection::setMinMaxBitrate(int32_t min, int32_t max) {
+ mMinBitrate = min;
+ mMaxBitrate = max;
+}
+
+void ARTPConnection::checkRxBitrate(int64_t nowUs) {
+ if (mLastBitrateReportTimeUs <= 0) {
+ mCumulativeBytes = 0;
+ mLastBitrateReportTimeUs = nowUs;
+ }
+ else if (mLastBitrateReportTimeUs + 1000000ll <= nowUs) {
+ int32_t timeDiff = (nowUs - mLastBitrateReportTimeUs) / 1000000ll;
+ int32_t bitrate = mCumulativeBytes * 8 / timeDiff;
+ ALOGI("Actual Rx bitrate : %d bits/sec", bitrate);
+
+ sp<ABuffer> buffer = new ABuffer(kMaxUDPSize);
+ List<StreamInfo>::iterator it = mStreams.begin();
+ while (it != mStreams.end()) {
+ StreamInfo *s = &*it;
+ if (s->mIsInjected) {
+ ++it;
+ continue;
+ }
+
+ if (s->mNumRTCPPacketsReceived == 0) {
+ // We have never received any RTCP packets on this stream,
+ // we don't even know where to send a report.
+ ++it;
+ continue;
+ }
+
+ buffer->setRange(0, 0);
+
+ for (size_t i = 0; i < s->mSources.size(); ++i) {
+ sp<ARTPSource> source = s->mSources.valueAt(i);
+ source->setBitrateData(bitrate, nowUs);
+ source->setTargetBitrate();
+ source->addTMMBR(buffer);
+ if (source->isNeedToDowngrade()) {
+ sp<AMessage> notify = s->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 400);
+ notify->setInt32("feedback-type", 1);
+ notify->setInt32("sender", source->getSelfID());
+ notify->post();
+ }
+ }
+ if (buffer->size() > 0) {
+ ALOGV("Sending TMMBR...");
+
+ ssize_t n = send(s, buffer);
+
+ if (n <= 0) {
+ ALOGW("failed to send RTCP TMMBR (%s).",
+ n == 0 ? "connection gone" : strerror(errno));
+
+ it = mStreams.erase(it);
+ continue;
+ }
+
+ CHECK_EQ(n, (ssize_t)buffer->size());
+ }
+ ++it;
+ }
+ mCumulativeBytes = 0;
+ mLastBitrateReportTimeUs = nowUs;
+ }
+}
void ARTPConnection::onInjectPacket(const sp<AMessage> &msg) {
int32_t index;
CHECK(msg->findInt32("index", &index));
@@ -672,4 +1128,3 @@
}
} // namespace android
-
diff --git a/media/libstagefright/rtsp/ARTPConnection.h b/media/libstagefright/rtsp/ARTPConnection.h
index d5f7c2e..712eec5 100644
--- a/media/libstagefright/rtsp/ARTPConnection.h
+++ b/media/libstagefright/rtsp/ARTPConnection.h
@@ -30,6 +30,7 @@
struct ARTPConnection : public AHandler {
enum Flags {
kRegularlyRequestFIR = 2,
+ kViLTEConnection = 4,
};
explicit ARTPConnection(uint32_t flags = 0);
@@ -44,11 +45,21 @@
void injectPacket(int index, const sp<ABuffer> &buffer);
+ void setSelfID(const uint32_t selfID);
+ void setMinMaxBitrate(int32_t min, int32_t max);
+
// Creates a pair of UDP datagram sockets bound to adjacent ports
// (the rtpSocket is bound to an even port, the rtcpSocket to the
// next higher port).
static void MakePortPair(
int *rtpSocket, int *rtcpSocket, unsigned *rtpPort);
+ // Creates a pair of UDP datagram sockets bound to assigned ip and
+ // ports (the rtpSocket is bound to an even port, the rtcpSocket
+ // to the next higher port).
+ static void MakeRTPSocketPair(
+ int *rtpSocket, int *rtcpSocket,
+ const char *localIp, const char *remoteIp,
+ unsigned localPort, unsigned remotePort, int64_t socketNetwork = 0);
protected:
virtual ~ARTPConnection();
@@ -71,18 +82,30 @@
bool mPollEventPending;
int64_t mLastReceiverReportTimeUs;
+ int64_t mLastBitrateReportTimeUs;
+
+ int32_t mSelfID;
+
+ int32_t mMinBitrate;
+ int32_t mMaxBitrate;
+ int32_t mCumulativeBytes;
void onAddStream(const sp<AMessage> &msg);
void onRemoveStream(const sp<AMessage> &msg);
void onPollStreams();
void onInjectPacket(const sp<AMessage> &msg);
void onSendReceiverReports();
+ void checkRxBitrate(int64_t nowUs);
status_t receive(StreamInfo *info, bool receiveRTP);
+ ssize_t send(const StreamInfo *info, const sp<ABuffer> buffer);
status_t parseRTP(StreamInfo *info, const sp<ABuffer> &buffer);
+ status_t parseRTPExt(StreamInfo *s, const uint8_t *extData, size_t extLen, int32_t *cvoDegrees);
status_t parseRTCP(StreamInfo *info, const sp<ABuffer> &buffer);
status_t parseSR(StreamInfo *info, const uint8_t *data, size_t size);
+ status_t parseTSFB(StreamInfo *info, const uint8_t *data, size_t size);
+ status_t parsePSFB(StreamInfo *info, const uint8_t *data, size_t size);
status_t parseBYE(StreamInfo *info, const uint8_t *data, size_t size);
sp<ARTPSource> findSource(StreamInfo *info, uint32_t id);
diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp
index f5f8128..bbe9d94 100644
--- a/media/libstagefright/rtsp/ARTPSource.cpp
+++ b/media/libstagefright/rtsp/ARTPSource.cpp
@@ -22,6 +22,7 @@
#include "AAMRAssembler.h"
#include "AAVCAssembler.h"
+#include "AHEVCAssembler.h"
#include "AH263Assembler.h"
#include "AMPEG2TSAssembler.h"
#include "AMPEG4AudioAssembler.h"
@@ -35,13 +36,17 @@
namespace android {
-static const uint32_t kSourceID = 0xdeadbeef;
+static uint32_t kSourceID = 0xdeadbeef;
ARTPSource::ARTPSource(
uint32_t id,
const sp<ASessionDescription> &sessionDesc, size_t index,
const sp<AMessage> ¬ify)
- : mID(id),
+ : mFirstSeqNumber(0),
+ mFirstRtpTime(0),
+ mFirstSysTime(0),
+ mClockRate(0),
+ mID(id),
mHighestSeqNumber(0),
mPrevExpected(0),
mBaseSeqNumber(0),
@@ -61,6 +66,9 @@
if (!strncmp(desc.c_str(), "H264/", 5)) {
mAssembler = new AAVCAssembler(notify);
mIssueFIRRequests = true;
+ } else if (!strncmp(desc.c_str(), "H265/", 5)) {
+ mAssembler = new AHEVCAssembler(notify);
+ mIssueFIRRequests = true;
} else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) {
mAssembler = new AMPEG4AudioAssembler(notify, params);
} else if (!strncmp(desc.c_str(), "H263-1998/", 10)
@@ -112,9 +120,16 @@
bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) {
uint32_t seqNum = (uint32_t)buffer->int32Data();
- if (mNumBuffersReceived++ == 0) {
+ if (mNumBuffersReceived++ == 0 && mFirstSysTime == 0) {
+ int32_t firstRtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", &firstRtpTime));
+ mFirstSysTime = ALooper::GetNowUs();
mHighestSeqNumber = seqNum;
mBaseSeqNumber = seqNum;
+ mFirstRtpTime = firstRtpTime;
+ ALOGV("first-rtp arrived: first-rtp-time=%d, sys-time=%lld, seq-num=%u",
+ mFirstRtpTime, (long long)mFirstSysTime, mHighestSeqNumber);
+ mClockRate = 90000;
mQueue.push_back(buffer);
return true;
}
@@ -306,6 +321,97 @@
buffer->setRange(buffer->offset(), buffer->size() + 32);
}
+void ARTPSource::addTMMBR(const sp<ABuffer> &buffer) {
+ if (buffer->size() + 20 > buffer->capacity()) {
+ ALOGW("RTCP buffer too small to accommodate RR.");
+ return;
+ }
+
+ int32_t targetBitrate = mQualManager.getTargetBitrate();
+ if (targetBitrate <= 0)
+ return;
+
+ uint8_t *data = buffer->data() + buffer->size();
+
+ data[0] = 0x80 | 3; // TMMBR
+ data[1] = 205; // TSFB
+ data[2] = 0;
+ data[3] = 4; // total (4+1) * sizeof(int32_t) = 20 bytes
+ data[4] = kSourceID >> 24;
+ data[5] = (kSourceID >> 16) & 0xff;
+ data[6] = (kSourceID >> 8) & 0xff;
+ data[7] = kSourceID & 0xff;
+
+ *(int32_t*)(&data[8]) = 0; // 4 bytes blank
+
+ data[12] = mID >> 24;
+ data[13] = (mID >> 16) & 0xff;
+ data[14] = (mID >> 8) & 0xff;
+ data[15] = mID & 0xff;
+
+ int32_t exp, mantissa;
+
+ // Round off to the nearest 2^4th
+ ALOGI("UE -> Op Req Rx bitrate : %d ", targetBitrate & 0xfffffff0);
+ for (exp=4 ; exp < 32 ; exp++)
+ if (((targetBitrate >> exp) & 0x01) != 0)
+ break;
+ mantissa = targetBitrate >> exp;
+
+ data[16] = ((exp << 2) & 0xfc) | ((mantissa & 0x18000) >> 15);
+ data[17] = (mantissa & 0x07f80) >> 7;
+ data[18] = (mantissa & 0x0007f) << 1;
+ data[19] = 40; // 40 bytes overhead;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 20);
+}
+
+uint32_t ARTPSource::getSelfID() {
+ return kSourceID;
+}
+void ARTPSource::setSelfID(const uint32_t selfID) {
+ kSourceID = selfID;
+}
+
+void ARTPSource::setMinMaxBitrate(int32_t min, int32_t max) {
+ mQualManager.setMinMaxBitrate(min, max);
+}
+
+void ARTPSource::setBitrateData(int32_t bitrate, int64_t time) {
+ mQualManager.setBitrateData(bitrate, time);
+}
+
+void ARTPSource::setTargetBitrate() {
+ uint8_t fraction = 0;
+
+ // According to appendix A.3 in RFC 3550
+ uint32_t expected = mHighestSeqNumber - mBaseSeqNumber + 1;
+ int64_t intervalExpected = expected - mPrevExpected;
+ int64_t intervalReceived = mNumBuffersReceived - mPrevNumBuffersReceived;
+ int64_t intervalPacketLost = intervalExpected - intervalReceived;
+
+ ALOGI("UID %p expectedPkts %lld lostPkts %lld", this, (long long)intervalExpected, (long long)intervalPacketLost);
+
+ if (intervalPacketLost < 0 || intervalExpected == 0)
+ fraction = 0;
+ else if (intervalExpected <= intervalPacketLost)
+ fraction = 255;
+ else
+ fraction = (intervalPacketLost << 8) / intervalExpected;
+
+ mQualManager.setTargetBitrate(fraction, ALooper::GetNowUs(), intervalExpected < 5);
+}
+
+bool ARTPSource::isNeedToReport() {
+ int64_t intervalReceived = mNumBuffersReceived - mPrevNumBuffersReceived;
+ return (intervalReceived > 0) ? true : false;
+}
+
+bool ARTPSource::isNeedToDowngrade() {
+ return mQualManager.isNeedToDowngrade();
+}
+
+void ARTPSource::noticeAbandonBuffer(int cnt) {
+ mNumBuffersReceived -= cnt;
+}
} // namespace android
-
-
diff --git a/media/libstagefright/rtsp/ARTPSource.h b/media/libstagefright/rtsp/ARTPSource.h
index f44e83f..652e753 100644
--- a/media/libstagefright/rtsp/ARTPSource.h
+++ b/media/libstagefright/rtsp/ARTPSource.h
@@ -23,6 +23,7 @@
#include <media/stagefright/foundation/ABase.h>
#include <utils/List.h>
#include <utils/RefBase.h>
+#include <QualManager.h>
namespace android {
@@ -45,8 +46,25 @@
void addReceiverReport(const sp<ABuffer> &buffer);
void addFIR(const sp<ABuffer> &buffer);
+ void addTMMBR(const sp<ABuffer> &buffer);
+ uint32_t getSelfID();
+ void setSelfID(const uint32_t selfID);
+ void setMinMaxBitrate(int32_t min, int32_t max);
+ void setBitrateData(int32_t bitrate, int64_t time);
+ void setTargetBitrate();
+
+ bool isNeedToReport();
+ bool isNeedToDowngrade();
+
+ void noticeAbandonBuffer(int cnt=1);
+
+ int32_t mFirstSeqNumber;
+ int32_t mFirstRtpTime;
+ int64_t mFirstSysTime;
+ int32_t mClockRate;
private:
+
uint32_t mID;
uint32_t mHighestSeqNumber;
uint32_t mPrevExpected;
@@ -66,6 +84,8 @@
sp<AMessage> mNotify;
+ QualManager mQualManager;
+
bool queuePacket(const sp<ABuffer> &buffer);
DISALLOW_EVIL_CONSTRUCTORS(ARTPSource);
diff --git a/media/libstagefright/rtsp/ARTPWriter.cpp b/media/libstagefright/rtsp/ARTPWriter.cpp
index 58d6086..70d34de 100644
--- a/media/libstagefright/rtsp/ARTPWriter.cpp
+++ b/media/libstagefright/rtsp/ARTPWriter.cpp
@@ -35,10 +35,29 @@
#define PT 97
#define PT_STR "97"
+#define H264_NALU_MASK 0x1F
+#define H264_NALU_SPS 0x7
+#define H264_NALU_PPS 0x8
+#define H264_NALU_IFRAME 0x5
+#define H264_NALU_PFRAME 0x1
+
+#define H265_NALU_MASK 0x3F
+#define H265_NALU_VPS 0x40
+#define H265_NALU_SPS 0x42
+#define H265_NALU_PPS 0x44
+
+#define UDP_HEADER_SIZE 8
+#define RTP_HEADER_SIZE 12
+#define RTP_HEADER_EXT_SIZE 1
+#define RTP_FU_HEADER_SIZE 2
+#define RTP_PAYLOAD_ROOM_SIZE 140
+
+
namespace android {
// static const size_t kMaxPacketSize = 65507; // maximum payload in UDP over IP
static const size_t kMaxPacketSize = 1500;
+static char kCNAME[255] = "someone@somewhere";
static int UniformRand(int limit) {
return ((double)rand() * limit) / RAND_MAX;
@@ -50,13 +69,16 @@
mLooper(new ALooper),
mReflector(new AHandlerReflector<ARTPWriter>(this)) {
CHECK_GE(fd, 0);
+ mIsIPv6 = false;
mLooper->setName("rtp writer");
mLooper->registerHandler(mReflector);
mLooper->start();
- mSocket = socket(AF_INET, SOCK_DGRAM, 0);
- CHECK_GE(mSocket, 0);
+ mRTPSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mRTPSocket, 0);
+ mRTCPSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mRTCPSocket, 0);
memset(mRTPAddr.sin_zero, 0, sizeof(mRTPAddr.sin_zero));
mRTPAddr.sin_family = AF_INET;
@@ -72,6 +94,42 @@
mRTCPAddr = mRTPAddr;
mRTCPAddr.sin_port = htons(ntohs(mRTPAddr.sin_port) | 1);
+ mSPSBuf = NULL;
+ mPPSBuf = NULL;
+
+#if LOG_TO_FILES
+ mRTPFd = open(
+ "/data/misc/rtpout.bin",
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0644);
+ CHECK_GE(mRTPFd, 0);
+
+ mRTCPFd = open(
+ "/data/misc/rtcpout.bin",
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0644);
+ CHECK_GE(mRTCPFd, 0);
+#endif
+}
+
+ARTPWriter::ARTPWriter(int fd, String8& localIp, int localPort, String8& remoteIp,
+ int remotePort, uint32_t seqNo)
+ : mFlags(0),
+ mFd(dup(fd)),
+ mLooper(new ALooper),
+ mReflector(new AHandlerReflector<ARTPWriter>(this)) {
+ CHECK_GE(fd, 0);
+ mIsIPv6 = false;
+
+ mLooper->setName("rtp writer");
+ mLooper->registerHandler(mReflector);
+ mLooper->start();
+
+ makeSocketPairAndBind(localIp, localPort, remoteIp , remotePort);
+ mSPSBuf = NULL;
+ mPPSBuf = NULL;
+
+ mSeqNo = seqNo;
#if LOG_TO_FILES
mRTPFd = open(
@@ -89,6 +147,16 @@
}
ARTPWriter::~ARTPWriter() {
+ if (mSPSBuf != NULL) {
+ mSPSBuf->release();
+ mSPSBuf = NULL;
+ }
+
+ if (mPPSBuf != NULL) {
+ mPPSBuf->release();
+ mPPSBuf = NULL;
+ }
+
#if LOG_TO_FILES
close(mRTCPFd);
mRTCPFd = -1;
@@ -97,8 +165,11 @@
mRTPFd = -1;
#endif
- close(mSocket);
- mSocket = -1;
+ close(mRTPSocket);
+ mRTPSocket = -1;
+
+ close(mRTCPSocket);
+ mRTCPSocket = -1;
close(mFd);
mFd = -1;
@@ -114,28 +185,61 @@
return (mFlags & kFlagEOS) != 0;
}
-status_t ARTPWriter::start(MetaData * /* params */) {
+status_t ARTPWriter::start(MetaData * params) {
Mutex::Autolock autoLock(mLock);
if (mFlags & kFlagStarted) {
return INVALID_OPERATION;
}
mFlags &= ~kFlagEOS;
- mSourceID = rand();
- mSeqNo = UniformRand(65536);
- mRTPTimeBase = rand();
+ if (mSourceID == 0)
+ mSourceID = rand();
+ if (mSeqNo == 0)
+ mSeqNo = UniformRand(65536);
+ mRTPTimeBase = 0;
mNumRTPSent = 0;
mNumRTPOctetsSent = 0;
mLastRTPTime = 0;
mLastNTPTime = 0;
+ mOpponentID = 0;
+ mBitrate = 192000;
mNumSRsSent = 0;
+ mRTPCVOExtMap = -1;
+ mRTPCVODegrees = 0;
+ mRTPSockNetwork = 0;
const char *mime;
CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+ int32_t selfID = 0;
+ if (params->findInt32(kKeySelfID, &selfID))
+ mSourceID = selfID;
+
+ int32_t payloadType = 0;
+ if (params->findInt32(kKeyPayloadType, &payloadType))
+ mPayloadType = payloadType;
+
+ int32_t rtpExtMap = 0;
+ if (params->findInt32(kKeyRtpExtMap, &rtpExtMap))
+ mRTPCVOExtMap = rtpExtMap;
+
+ int32_t rtpCVODegrees = 0;
+ if (params->findInt32(kKeyRtpCvoDegrees, &rtpCVODegrees))
+ mRTPCVODegrees = rtpCVODegrees;
+
+ int32_t dscp = 0;
+ if (params->findInt32(kKeyRtpDscp, &dscp))
+ updateSocketDscp(dscp);
+
+ int64_t sockNetwork = 0;
+ if (params->findInt64(kKeySocketNetwork, &sockNetwork))
+ updateSocketNetwork(sockNetwork);
+
mMode = INVALID;
if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
mMode = H264;
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) {
+ mMode = H265;
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_H263)) {
mMode = H263;
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
@@ -173,9 +277,12 @@
return OK;
}
-static void StripStartcode(MediaBufferBase *buffer) {
+// return size of SPS if there is more NAL unit found following to SPS.
+static uint32_t StripStartcode(MediaBufferBase *buffer) {
+ uint32_t nalSize = 0;
+
if (buffer->range_length() < 4) {
- return;
+ return 0;
}
const uint8_t *ptr =
@@ -185,13 +292,65 @@
buffer->set_range(
buffer->range_offset() + 4, buffer->range_length() - 4);
}
+
+ ptr = (const uint8_t *)buffer->data() + buffer->range_offset();
+
+ if (buffer->range_length() > 0 && (*ptr & H264_NALU_MASK) == H264_NALU_SPS) {
+ for (uint32_t i = 1; i + 4 <= buffer->range_length(); i++) {
+
+ if (!memcmp(ptr + i, "\x00\x00\x00\x01", 4)) {
+ // Now, we found one more NAL unit in the media buffer.
+ // Mostly, it will be a PPS.
+ nalSize = i;
+ ALOGV("SPS found. size=%d", nalSize);
+ }
+ }
+ }
+
+ return nalSize;
+}
+
+static void SpsPpsParser(MediaBufferBase *mediaBuffer,
+ MediaBufferBase **spsBuffer, MediaBufferBase **ppsBuffer, uint32_t spsSize) {
+
+ if (mediaBuffer == NULL || mediaBuffer->range_length() < 4)
+ return;
+
+ if ((*spsBuffer) != NULL) {
+ (*spsBuffer)->release();
+ (*spsBuffer) = NULL;
+ }
+
+ if ((*ppsBuffer) != NULL) {
+ (*ppsBuffer)->release();
+ (*ppsBuffer) = NULL;
+ }
+
+ // we got sps/pps but startcode of sps is striped.
+ (*spsBuffer) = MediaBufferBase::Create(spsSize);
+ memcpy((*spsBuffer)->data(),
+ (const uint8_t *)mediaBuffer->data() + mediaBuffer->range_offset(),
+ spsSize);
+
+ int32_t ppsSize = mediaBuffer->range_length() - spsSize - 4 /*startcode*/;
+ if (ppsSize > 0) {
+ (*ppsBuffer) = MediaBufferBase::Create(ppsSize);
+ ALOGV("PPS found. size=%d", (int)ppsSize);
+ mediaBuffer->set_range(mediaBuffer->range_offset() + spsSize + 4 /*startcode*/,
+ mediaBuffer->range_length() - spsSize - 4 /*startcode*/);
+ memcpy((*ppsBuffer)->data(),
+ (const uint8_t *)mediaBuffer->data() + mediaBuffer->range_offset(),
+ ppsSize);
+ }
}
void ARTPWriter::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatStart:
{
- CHECK_EQ(mSource->start(), (status_t)OK);
+ sp<MetaData> meta = new MetaData();
+ meta->setInt64(kKeyTime, 10ll);
+ CHECK_EQ(mSource->start(meta.get()), (status_t)OK);
#if 0
if (mMode == H264) {
@@ -264,6 +423,18 @@
}
}
+void ARTPWriter::setTMMBNInfo(uint32_t opponentID, uint32_t bitrate) {
+ mOpponentID = opponentID;
+ mBitrate = bitrate;
+
+ sp<ABuffer> buffer = new ABuffer(65536);
+ buffer->setRange(0, 0);
+
+ addTMMBN(buffer);
+
+ send(buffer, true /* isRTCP */);
+}
+
void ARTPWriter::onRead(const sp<AMessage> &msg) {
MediaBufferBase *mediaBuf;
status_t err = mSource->read(&mediaBuf);
@@ -280,8 +451,15 @@
ALOGV("read buffer of size %zu", mediaBuf->range_length());
if (mMode == H264) {
+ uint32_t spsSize = 0;
+ if ((spsSize = StripStartcode(mediaBuf)) > 0) {
+ SpsPpsParser(mediaBuf, &mSPSBuf, &mPPSBuf, spsSize);
+ } else {
+ sendAVCData(mediaBuf);
+ }
+ } else if (mMode == H265) {
StripStartcode(mediaBuf);
- sendAVCData(mediaBuf);
+ sendHEVCData(mediaBuf);
} else if (mMode == H263) {
sendH263Data(mediaBuf);
} else if (mMode == AMR_NB || mMode == AMR_WB) {
@@ -309,12 +487,29 @@
}
void ARTPWriter::send(const sp<ABuffer> &buffer, bool isRTCP) {
- ssize_t n = sendto(
- mSocket, buffer->data(), buffer->size(), 0,
- (const struct sockaddr *)(isRTCP ? &mRTCPAddr : &mRTPAddr),
- sizeof(mRTCPAddr));
+ int sizeSockSt;
+ struct sockaddr *remAddr;
- CHECK_EQ(n, (ssize_t)buffer->size());
+ if (mIsIPv6) {
+ sizeSockSt = sizeof(struct sockaddr_in6);
+ if (isRTCP)
+ remAddr = (struct sockaddr *)&mRTCPAddr6;
+ else
+ remAddr = (struct sockaddr *)&mRTPAddr6;
+ } else {
+ sizeSockSt = sizeof(struct sockaddr_in);
+ if (isRTCP)
+ remAddr = (struct sockaddr *)&mRTCPAddr;
+ else
+ remAddr = (struct sockaddr *)&mRTPAddr;
+ }
+
+ ssize_t n = sendto(isRTCP ? mRTCPSocket : mRTPSocket,
+ buffer->data(), buffer->size(), 0, remAddr, sizeSockSt);
+
+ if (n != (ssize_t)buffer->size()) {
+ ALOGW("packets can not be sent. ret=%d, buf=%d", (int)n, (int)buffer->size());
+ }
#if LOG_TO_FILES
int fd = isRTCP ? mRTCPFd : mRTPFd;
@@ -379,7 +574,6 @@
data[offset++] = 1; // CNAME
- static const char *kCNAME = "someone@somewhere";
data[offset++] = strlen(kCNAME);
memcpy(&data[offset], kCNAME, strlen(kCNAME));
@@ -416,9 +610,52 @@
buffer->setRange(buffer->offset(), buffer->size() + offset);
}
+void ARTPWriter::addTMMBN(const sp<ABuffer> &buffer) {
+ if (buffer->size() + 20 > buffer->capacity()) {
+ ALOGW("RTCP buffer too small to accommodate SR.");
+ return;
+ }
+ if (mOpponentID == 0)
+ return;
+
+ uint8_t *data = buffer->data() + buffer->size();
+
+ data[0] = 0x80 | 4; // TMMBN
+ data[1] = 205; // TSFB
+ data[2] = 0;
+ data[3] = 4; // total (4+1) * sizeof(int32_t) = 20 bytes
+ data[4] = mSourceID >> 24;
+ data[5] = (mSourceID >> 16) & 0xff;
+ data[6] = (mSourceID >> 8) & 0xff;
+ data[7] = mSourceID & 0xff;
+
+ *(int32_t*)(&data[8]) = 0; // 4 bytes blank
+
+ data[12] = mOpponentID >> 24;
+ data[13] = (mOpponentID >> 16) & 0xff;
+ data[14] = (mOpponentID >> 8) & 0xff;
+ data[15] = mOpponentID & 0xff;
+
+ int32_t exp, mantissa;
+
+ // Round off to the nearest 2^4th
+ ALOGI("UE -> Op Noti Tx bitrate : %d ", mBitrate & 0xfffffff0);
+ for (exp=4 ; exp < 32 ; exp++)
+ if (((mBitrate >> exp) & 0x01) != 0)
+ break;
+ mantissa = mBitrate >> exp;
+
+ data[16] = ((exp << 2) & 0xfc) | ((mantissa & 0x18000) >> 15);
+ data[17] = (mantissa & 0x07f80) >> 7;
+ data[18] = (mantissa & 0x0007f) << 1;
+ data[19] = 40; // 40 bytes overhead;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 20);
+}
+
// static
uint64_t ARTPWriter::GetNowNTP() {
- uint64_t nowUs = ALooper::GetNowUs();
+ uint64_t nowUs = systemTime(SYSTEM_TIME_REALTIME) / 1000ll;
nowUs += ((70LL * 365 + 17) * 24) * 60 * 60 * 1000000LL;
@@ -463,7 +700,7 @@
sdp.append("m=audio ");
}
- sdp.append(AStringPrintf("%d", ntohs(mRTPAddr.sin_port)));
+ sdp.append(AStringPrintf("%d", mIsIPv6 ? ntohs(mRTPAddr6.sin6_port) : ntohs(mRTPAddr.sin_port)));
sdp.append(
" RTP/AVP " PT_STR "\r\n"
"b=AS 320000\r\n"
@@ -569,24 +806,49 @@
send(buffer, true /* isRTCP */);
}
-void ARTPWriter::sendAVCData(MediaBufferBase *mediaBuf) {
+void ARTPWriter::sendSPSPPSIfIFrame(MediaBufferBase *mediaBuf, int64_t timeUs) {
+ const uint8_t *mediaData =
+ (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+ if (mediaBuf->range_length() == 0
+ || (mediaData[0] & H264_NALU_MASK) != H264_NALU_IFRAME)
+ return;
+
+ if (mSPSBuf != NULL) {
+ mSPSBuf->meta_data().setInt64(kKeyTime, timeUs);
+ mSPSBuf->meta_data().setInt32(kKeySps, 1);
+ sendAVCData(mSPSBuf);
+ }
+
+ if (mPPSBuf != NULL) {
+ mPPSBuf->meta_data().setInt64(kKeyTime, timeUs);
+ mPPSBuf->meta_data().setInt32(kKeyPps, 1);
+ sendAVCData(mPPSBuf);
+ }
+}
+
+void ARTPWriter::sendHEVCData(MediaBufferBase *mediaBuf) {
// 12 bytes RTP header + 2 bytes for the FU-indicator and FU-header.
CHECK_GE(kMaxPacketSize, 12u + 2u);
int64_t timeUs;
CHECK(mediaBuf->meta_data().findInt64(kKeyTime, &timeUs));
- uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100LL);
+ sendSPSPPSIfIFrame(mediaBuf, timeUs);
+
+ uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll);
const uint8_t *mediaData =
(const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
- if (mediaBuf->range_length() + 12 <= buffer->capacity()) {
+
+ if (mediaBuf->range_length() + UDP_HEADER_SIZE + RTP_HEADER_SIZE + RTP_PAYLOAD_ROOM_SIZE
+ <= buffer->capacity()) {
// The data fits into a single packet
uint8_t *data = buffer->data();
data[0] = 0x80;
- data[1] = (1 << 7) | PT; // M-bit
+ data[1] = (1 << 7) | mPayloadType; // M-bit
data[2] = (mSeqNo >> 8) & 0xff;
data[3] = mSeqNo & 0xff;
data[4] = rtpTime >> 24;
@@ -611,21 +873,24 @@
} else {
// FU-A
- unsigned nalType = mediaData[0];
- size_t offset = 1;
+ unsigned nalType = (mediaData[0] >> 1) & H265_NALU_MASK;
+ ALOGV("H265 nalType 0x%x, data[0]=0x%x", nalType, mediaData[0]);
+ size_t offset = 2; //H265 payload header is 16 bit.
bool firstPacket = true;
while (offset < mediaBuf->range_length()) {
size_t size = mediaBuf->range_length() - offset;
bool lastPacket = true;
- if (size + 12 + 2 > buffer->capacity()) {
+ if (size + UDP_HEADER_SIZE + RTP_HEADER_SIZE + RTP_FU_HEADER_SIZE +
+ RTP_PAYLOAD_ROOM_SIZE > buffer->capacity()) {
lastPacket = false;
- size = buffer->capacity() - 12 - 2;
+ size = buffer->capacity() - UDP_HEADER_SIZE - RTP_HEADER_SIZE -
+ RTP_FU_HEADER_SIZE - RTP_PAYLOAD_ROOM_SIZE;
}
uint8_t *data = buffer->data();
data[0] = 0x80;
- data[1] = (lastPacket ? (1 << 7) : 0x00) | PT; // M-bit
+ data[1] = (lastPacket ? (1 << 7) : 0x00) | mPayloadType; // M-bit
data[2] = (mSeqNo >> 8) & 0xff;
data[3] = mSeqNo & 0xff;
data[4] = rtpTime >> 24;
@@ -637,18 +902,39 @@
data[10] = (mSourceID >> 8) & 0xff;
data[11] = mSourceID & 0xff;
- data[12] = 28 | (nalType & 0xe0);
+ /* H265 payload header is 16 bit
+ 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |F| Type | Layer ID | TID |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ ALOGV("H265 payload header 0x%x %x", mediaData[0], mediaData[1]);
+ // excludes Type from 1st byte of H265 payload header.
+ data[12] = mediaData[0] & 0x81;
+ // fills Type as FU (49 == 0x31)
+ data[12] = data[12] | (0x31 << 1);
+ data[13] = mediaData[1];
+
+ ALOGV("H265 FU header 0x%x %x", data[12], data[13]);
CHECK(!firstPacket || !lastPacket);
+ /*
+ FU INDICATOR HDR
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+
+ |S|E| Type |
+ +-+-+-+-+-+-+-+
+ */
- data[13] =
+ data[14] =
(firstPacket ? 0x80 : 0x00)
| (lastPacket ? 0x40 : 0x00)
- | (nalType & 0x1f);
+ | (nalType & H265_NALU_MASK);
+ ALOGV("H265 FU indicator 0x%x", data[14]);
- memcpy(&data[14], &mediaData[offset], size);
+ memcpy(&data[15], &mediaData[offset], size);
- buffer->setRange(0, 14 + size);
+ buffer->setRange(0, 15 + size);
send(buffer, false /* isRTCP */);
@@ -663,6 +949,170 @@
mLastRTPTime = rtpTime;
mLastNTPTime = GetNowNTP();
+
+}
+
+void ARTPWriter::sendAVCData(MediaBufferBase *mediaBuf) {
+ // 12 bytes RTP header + 2 bytes for the FU-indicator and FU-header.
+ CHECK_GE(kMaxPacketSize, 12u + 2u);
+
+ int64_t timeUs;
+ CHECK(mediaBuf->meta_data().findInt64(kKeyTime, &timeUs));
+
+ sendSPSPPSIfIFrame(mediaBuf, timeUs);
+
+ uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100LL);
+
+ const uint8_t *mediaData =
+ (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+ int32_t sps, pps;
+ bool isSpsPps = false;
+ if (mediaBuf->meta_data().findInt32(kKeySps, &sps) ||
+ mediaBuf->meta_data().findInt32(kKeyPps, &pps)) {
+ isSpsPps = true;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
+ if (mediaBuf->range_length() + UDP_HEADER_SIZE + RTP_HEADER_SIZE + RTP_PAYLOAD_ROOM_SIZE
+ <= buffer->capacity()) {
+ // The data fits into a single packet
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ if (mRTPCVOExtMap > 0)
+ data[0] |= 0x10;
+ if (isSpsPps)
+ data[1] = mPayloadType; // Marker bit should not be set in case of sps/pps
+ else
+ data[1] = (1 << 7) | mPayloadType;
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+ int rtpExtIndex = 0;
+ if (mRTPCVOExtMap > 0) {
+ /*
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 0xBE | 0xDE | length=3 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ID | L=0 | data | ID | L=1 | data...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ...data | 0 (pad) | 0 (pad) | ID | L=3 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | data |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ In the one-byte header form of extensions, the 16-bit value required
+ by the RTP specification for a header extension, labeled in the RTP
+ specification as "defined by profile", takes the fixed bit pattern
+ 0xBEDE (the first version of this specification was written on the
+ feast day of the Venerable Bede).
+ */
+ data[12] = 0xBE;
+ data[13] = 0xDE;
+ // put a length of RTP Extension.
+ data[14] = 0x00;
+ data[15] = 0x01;
+ // put extmap of RTP assigned for CVO.
+ data[16] = (mRTPCVOExtMap << 4) | 0x0;
+ // put image degrees as per CVO specification.
+ data[17] = mRTPCVODegrees;
+ data[18] = 0x0;
+ data[19] = 0x0;
+ rtpExtIndex = 8;
+ }
+
+ memcpy(&data[12 + rtpExtIndex],
+ mediaData, mediaBuf->range_length());
+
+ buffer->setRange(0, mediaBuf->range_length() + (12 + rtpExtIndex));
+
+ send(buffer, false /* isRTCP */);
+
+ ++mSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - (12 + rtpExtIndex);
+ } else {
+ // FU-A
+
+ unsigned nalType = mediaData[0];
+ size_t offset = 1;
+
+ bool firstPacket = true;
+ while (offset < mediaBuf->range_length()) {
+ size_t size = mediaBuf->range_length() - offset;
+ bool lastPacket = true;
+ if (size + UDP_HEADER_SIZE + RTP_HEADER_SIZE + RTP_FU_HEADER_SIZE +
+ RTP_PAYLOAD_ROOM_SIZE > buffer->capacity()) {
+ lastPacket = false;
+ size = buffer->capacity() - UDP_HEADER_SIZE - RTP_HEADER_SIZE -
+ RTP_FU_HEADER_SIZE - RTP_PAYLOAD_ROOM_SIZE;
+ }
+
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ if (lastPacket && mRTPCVOExtMap > 0)
+ data[0] |= 0x10;
+ data[1] = (lastPacket ? (1 << 7) : 0x00) | mPayloadType; // M-bit
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+ int rtpExtIndex = 0;
+ if (lastPacket && mRTPCVOExtMap > 0) {
+ data[12] = 0xBE;
+ data[13] = 0xDE;
+ data[14] = 0x00;
+ data[15] = 0x01;
+ data[16] = (mRTPCVOExtMap << 4) | 0x0;
+ data[17] = mRTPCVODegrees;
+ data[18] = 0x0;
+ data[19] = 0x0;
+ rtpExtIndex = 8;
+ }
+
+ data[12 + rtpExtIndex] = 28 | (nalType & 0xe0);
+
+ CHECK(!firstPacket || !lastPacket);
+
+ data[13 + rtpExtIndex] =
+ (firstPacket ? 0x80 : 0x00)
+ | (lastPacket ? 0x40 : 0x00)
+ | (nalType & 0x1f);
+
+ memcpy(&data[14 + rtpExtIndex], &mediaData[offset], size);
+
+ buffer->setRange(0, 14 + rtpExtIndex + size);
+
+ send(buffer, false /* isRTCP */);
+
+ ++mSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - (12 + rtpExtIndex);
+
+ firstPacket = false;
+ offset += size;
+ }
+ }
+
+ mLastRTPTime = rtpTime;
+ mLastNTPTime = GetNowNTP();
}
void ARTPWriter::sendH263Data(MediaBufferBase *mediaBuf) {
@@ -696,7 +1146,7 @@
uint8_t *data = buffer->data();
data[0] = 0x80;
- data[1] = (lastPacket ? 0x80 : 0x00) | PT; // M-bit
+ data[1] = (lastPacket ? 0x80 : 0x00) | mPayloadType; // M-bit
data[2] = (mSeqNo >> 8) & 0xff;
data[3] = mSeqNo & 0xff;
data[4] = rtpTime >> 24;
@@ -727,6 +1177,54 @@
mLastNTPTime = GetNowNTP();
}
+void ARTPWriter::updateCVODegrees(int32_t cvoDegrees) {
+ Mutex::Autolock autoLock(mLock);
+ mRTPCVODegrees = cvoDegrees;
+}
+
+void ARTPWriter::updatePayloadType(int32_t payloadType) {
+ Mutex::Autolock autoLock(mLock);
+ mPayloadType = payloadType;
+}
+
+void ARTPWriter::updateSocketDscp(int32_t dscp) {
+ mRtpLayer3Dscp = dscp << 2;
+
+ /* mRtpLayer3Dscp will be mapped to WMM(Wifi) as per operator's requirement */
+ if (setsockopt(mRTPSocket, IPPROTO_IP, IP_TOS,
+ (int *)&mRtpLayer3Dscp, sizeof(mRtpLayer3Dscp)) < 0) {
+ ALOGE("failed to set dscp on rtpsock. err=%s", strerror(errno));
+ } else {
+ ALOGD("successfully set dscp on rtpsock. opt=%d", mRtpLayer3Dscp);
+ setsockopt(mRTCPSocket, IPPROTO_IP, IP_TOS,
+ (int *)&mRtpLayer3Dscp, sizeof(mRtpLayer3Dscp));
+ ALOGD("successfully set dscp on rtcpsock. opt=%d", mRtpLayer3Dscp);
+ }
+}
+
+void ARTPWriter::updateSocketNetwork(int64_t socketNetwork) {
+ mRTPSockNetwork = (net_handle_t)socketNetwork;
+ ALOGI("trying to bind rtp socket(%d) to network(%llu).",
+ mRTPSocket, (unsigned long long)mRTPSockNetwork);
+
+ int result = android_setsocknetwork(mRTPSockNetwork, mRTPSocket);
+ if (result != 0) {
+ ALOGW("failed(%d) to bind rtp socket(%d) to network(%llu)",
+ result, mRTPSocket, (unsigned long long)mRTPSockNetwork);
+ }
+ result = android_setsocknetwork(mRTPSockNetwork, mRTCPSocket);
+ if (result != 0) {
+ ALOGW("failed(%d) to bind rtcp socket(%d) to network(%llu)",
+ result, mRTCPSocket, (unsigned long long)mRTPSockNetwork);
+ }
+ ALOGI("done. bind rtp socket(%d) to network(%llu)",
+ mRTPSocket, (unsigned long long)mRTPSockNetwork);
+}
+
+uint32_t ARTPWriter::getSequenceNum() {
+ return mSeqNo;
+}
+
static size_t getFrameSize(bool isWide, unsigned FT) {
static const size_t kFrameSizeNB[8] = {
95, 103, 118, 134, 148, 159, 204, 244
@@ -778,7 +1276,7 @@
// The data fits into a single packet
uint8_t *data = buffer->data();
data[0] = 0x80;
- data[1] = PT;
+ data[1] = mPayloadType;
if (mNumRTPSent == 0) {
// Signal start of talk-spurt.
data[1] |= 0x80; // M-bit
@@ -834,5 +1332,80 @@
mLastNTPTime = GetNowNTP();
}
-} // namespace android
+void ARTPWriter::makeSocketPairAndBind(String8& localIp, int localPort,
+ String8& remoteIp, int remotePort) {
+ static char kSomeone[16] = "someone@";
+ int nameLength = strlen(kSomeone);
+ memcpy(kCNAME, kSomeone, nameLength);
+ memcpy(kCNAME + nameLength, localIp.c_str(), localIp.length() + 1);
+ if (localIp.contains(":"))
+ mIsIPv6 = true;
+ else
+ mIsIPv6 = false;
+
+ mRTPSocket = socket(mIsIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mRTPSocket, 0);
+ mRTCPSocket = socket(mIsIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mRTCPSocket, 0);
+
+ int sockopt = 1;
+ setsockopt(mRTPSocket, SOL_SOCKET, SO_REUSEADDR, (int *)&sockopt, sizeof(sockopt));
+ setsockopt(mRTCPSocket, SOL_SOCKET, SO_REUSEADDR, (int *)&sockopt, sizeof(sockopt));
+
+ if (mIsIPv6) {
+ memset(&mLocalAddr6, 0, sizeof(mLocalAddr6));
+ memset(&mRTPAddr6, 0, sizeof(mRTPAddr6));
+ memset(&mRTCPAddr6, 0, sizeof(mRTCPAddr6));
+
+ mLocalAddr6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, localIp.string(), &mLocalAddr6.sin6_addr);
+ mLocalAddr6.sin6_port = htons((uint16_t)localPort);
+
+ mRTPAddr6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, remoteIp.string(), &mRTPAddr6.sin6_addr);
+ mRTPAddr6.sin6_port = htons((uint16_t)remotePort);
+
+ mRTCPAddr6 = mRTPAddr6;
+ mRTCPAddr6.sin6_port = htons((uint16_t)(remotePort + 1));
+ } else {
+ memset(&mLocalAddr, 0, sizeof(mLocalAddr));
+ memset(&mRTPAddr, 0, sizeof(mRTPAddr));
+ memset(&mRTCPAddr, 0, sizeof(mRTCPAddr));
+
+ mLocalAddr.sin_family = AF_INET;
+ mLocalAddr.sin_addr.s_addr = inet_addr(localIp.string());
+ mLocalAddr.sin_port = htons((uint16_t)localPort);
+
+ mRTPAddr.sin_family = AF_INET;
+ mRTPAddr.sin_addr.s_addr = inet_addr(remoteIp.string());
+ mRTPAddr.sin_port = htons((uint16_t)remotePort);
+
+ mRTCPAddr = mRTPAddr;
+ mRTCPAddr.sin_port = htons((uint16_t)(remotePort + 1));
+ }
+
+ struct sockaddr *localAddr = mIsIPv6 ?
+ (struct sockaddr*)&mLocalAddr6 : (struct sockaddr*)&mLocalAddr;
+
+ int sizeSockSt = mIsIPv6 ? sizeof(mLocalAddr6) : sizeof(mLocalAddr);
+
+ if (bind(mRTPSocket, localAddr, sizeSockSt) == -1) {
+ ALOGE("failed to bind rtp %s:%d err=%s", localIp.string(), localPort, strerror(errno));
+ } else {
+ ALOGD("succeed to bind rtp %s:%d", localIp.string(), localPort);
+ }
+
+ if (mIsIPv6)
+ mLocalAddr6.sin6_port = htons((uint16_t)(localPort + 1));
+ else
+ mLocalAddr.sin_port = htons((uint16_t)(localPort + 1));
+
+ if (bind(mRTCPSocket, localAddr, sizeSockSt) == -1) {
+ ALOGE("failed to bind rtcp %s:%d err=%s", localIp.string(), localPort + 1, strerror(errno));
+ } else {
+ ALOGD("succeed to bind rtcp %s:%d", localIp.string(), localPort + 1);
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPWriter.h b/media/libstagefright/rtsp/ARTPWriter.h
index 2f13486..f7e2204 100644
--- a/media/libstagefright/rtsp/ARTPWriter.h
+++ b/media/libstagefright/rtsp/ARTPWriter.h
@@ -27,6 +27,8 @@
#include <arpa/inet.h>
#include <sys/socket.h>
+#include <android/multinetwork.h>
+
#define LOG_TO_FILES 0
namespace android {
@@ -36,14 +38,23 @@
struct ARTPWriter : public MediaWriter {
explicit ARTPWriter(int fd);
+ explicit ARTPWriter(int fd, String8& localIp, int localPort,
+ String8& remoteIp, int remotePort,
+ uint32_t seqNo);
virtual status_t addSource(const sp<MediaSource> &source);
virtual bool reachedEOS();
virtual status_t start(MetaData *params);
virtual status_t stop();
virtual status_t pause();
+ void updateCVODegrees(int32_t cvoDegrees);
+ void updatePayloadType(int32_t payloadType);
+ void updateSocketDscp(int32_t dscp);
+ void updateSocketNetwork(int64_t socketNetwork);
+ uint32_t getSequenceNum();
virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual void setTMMBNInfo(uint32_t opponentID, uint32_t bitrate);
protected:
virtual ~ARTPWriter();
@@ -76,15 +87,26 @@
sp<ALooper> mLooper;
sp<AHandlerReflector<ARTPWriter> > mReflector;
- int mSocket;
+ bool mIsIPv6;
+ int mRTPSocket, mRTCPSocket;
+ struct sockaddr_in mLocalAddr;
struct sockaddr_in mRTPAddr;
struct sockaddr_in mRTCPAddr;
+ struct sockaddr_in6 mLocalAddr6;
+ struct sockaddr_in6 mRTPAddr6;
+ struct sockaddr_in6 mRTCPAddr6;
+ int32_t mRtpLayer3Dscp;
+ net_handle_t mRTPSockNetwork;
AString mProfileLevel;
AString mSeqParamSet;
AString mPicParamSet;
+ MediaBufferBase *mSPSBuf;
+ MediaBufferBase *mPPSBuf;
+
uint32_t mSourceID;
+ uint32_t mPayloadType;
uint32_t mSeqNo;
uint32_t mRTPTimeBase;
uint32_t mNumRTPSent;
@@ -92,10 +114,16 @@
uint32_t mLastRTPTime;
uint64_t mLastNTPTime;
+ uint32_t mOpponentID;
+ uint32_t mBitrate;
+
int32_t mNumSRsSent;
+ int32_t mRTPCVOExtMap;
+ int32_t mRTPCVODegrees;
enum {
INVALID,
+ H265,
H264,
H263,
AMR_NB,
@@ -109,16 +137,20 @@
void addSR(const sp<ABuffer> &buffer);
void addSDES(const sp<ABuffer> &buffer);
+ void addTMMBN(const sp<ABuffer> &buffer);
void makeH264SPropParamSets(MediaBufferBase *buffer);
void dumpSessionDesc();
void sendBye();
+ void sendSPSPPSIfIFrame(MediaBufferBase *mediaBuf, int64_t timeUs);
+ void sendHEVCData(MediaBufferBase *mediaBuf);
void sendAVCData(MediaBufferBase *mediaBuf);
void sendH263Data(MediaBufferBase *mediaBuf);
void sendAMRData(MediaBufferBase *mediaBuf);
void send(const sp<ABuffer> &buffer, bool isRTCP);
+ void makeSocketPairAndBind(String8& localIp, int localPort, String8& remoteIp, int remotePort);
DISALLOW_EVIL_CONSTRUCTORS(ARTPWriter);
};
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index bb66f4c..c33bf3f 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -329,6 +329,7 @@
mPass.clear();
mAuthType = NONE;
mNonce.clear();
+ mRealm.clear();
mState = DISCONNECTED;
}
@@ -911,6 +912,14 @@
CHECK_GE(j, 0);
mNonce.setTo(value, i + 7, j - i - 7);
+
+ i = value.find("realm=");
+ CHECK_GE(i, 0);
+ CHECK_EQ(value.c_str()[i + 6], '\"');
+ j = value.find("\"", i + 7);
+ CHECK_GE(j, 0);
+
+ mRealm.setTo(value, i + 7, j - i - 7);
}
return true;
@@ -993,7 +1002,7 @@
AString A1;
A1.append(mUser);
A1.append(":");
- A1.append("Streaming Server");
+ A1.append(mRealm);
A1.append(":");
A1.append(mPass);
@@ -1029,6 +1038,9 @@
fragment.append("\", ");
fragment.append("response=\"");
fragment.append(digest);
+ fragment.append("\", ");
+ fragment.append("realm=\"");
+ fragment.append(mRealm);
fragment.append("\"");
fragment.append("\r\n");
diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h
index 56b604d..7cdd4c0 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.h
+++ b/media/libstagefright/rtsp/ARTSPConnection.h
@@ -84,6 +84,7 @@
AString mUser, mPass;
AuthType mAuthType;
AString mNonce;
+ AString mRealm;
int mSocket;
int32_t mConnectionID;
int32_t mNextCSeq;
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
index 2b42040..5b5b4b1 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -27,6 +27,8 @@
namespace android {
+constexpr unsigned kDefaultAs = 960; // kbps?
+
ASessionDescription::ASessionDescription()
: mIsValid(false) {
}
@@ -103,7 +105,7 @@
key.setTo(line, 0, colonPos);
if (key == "a=fmtp" || key == "a=rtpmap"
- || key == "a=framesize") {
+ || key == "a=framesize" || key == "a=extmap") {
ssize_t spacePos = line.find(" ", colonPos + 1);
if (spacePos < 0) {
return false;
@@ -201,6 +203,33 @@
return true;
}
+bool ASessionDescription::getCvoExtMap(
+ size_t index, int32_t *cvoExtMap) const {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ AString key, value;
+ *cvoExtMap = 0;
+
+ const Attribs &track = mTracks.itemAt(index);
+ for (size_t i = 0; i < track.size(); i++) {
+ value = track.valueAt(i);
+ if (value.size() > 0 && strcmp(value.c_str(), "urn:3gpp:video-orientation") == 0) {
+ key = track.keyAt(i);
+ break;
+ }
+ }
+
+ if (key.size() > 0) {
+ const char *colonPos = strrchr(key.c_str(), ':');
+ colonPos++;
+ *cvoExtMap = atoi(colonPos);
+ return true;
+ }
+
+ return false;
+}
+
void ASessionDescription::getFormatType(
size_t index, unsigned long *PT,
AString *desc, AString *params) const {
@@ -345,5 +374,74 @@
return *npt2 > *npt1;
}
+// static
+void ASessionDescription::SDPStringFactory(AString &sdp,
+ const char *ip, bool isAudio, unsigned port, unsigned payloadType,
+ unsigned as, const char *codec, const char *fmtp,
+ int32_t width, int32_t height, int32_t cvoExtMap)
+{
+ bool isIPv4 = (AString(ip).find("::") == -1) ? true : false;
+ sdp.clear();
+ sdp.append("v=0\r\n");
+
+ sdp.append("a=range:npt=now-\r\n");
+
+ sdp.append("m=");
+ sdp.append(isAudio ? "audio " : "video ");
+ sdp.append(port);
+ sdp.append(" RTP/AVP ");
+ sdp.append(payloadType);
+ sdp.append("\r\n");
+
+ sdp.append("c= IN IP");
+ if (isIPv4) {
+ sdp.append("4 ");
+ } else {
+ sdp.append("6 ");
+ }
+ sdp.append(ip);
+ sdp.append("\r\n");
+
+ sdp.append("b=AS:");
+ sdp.append(as > 0 ? as : kDefaultAs);
+ sdp.append("\r\n");
+
+ sdp.append("a=rtpmap:");
+ sdp.append(payloadType);
+ sdp.append(" ");
+ sdp.append(codec);
+ sdp.append("/");
+ sdp.append(isAudio ? "8000" : "90000");
+ sdp.append("\r\n");
+
+ if (fmtp != NULL) {
+ sdp.append("a=fmtp:");
+ sdp.append(payloadType);
+ sdp.append(" ");
+ sdp.append(fmtp);
+ sdp.append("\r\n");
+ }
+
+ if (!isAudio && width > 0 && height > 0) {
+ sdp.append("a=framesize:");
+ sdp.append(payloadType);
+ sdp.append(" ");
+ sdp.append(width);
+ sdp.append("-");
+ sdp.append(height);
+ sdp.append("\r\n");
+ }
+
+ if (cvoExtMap > 0) {
+ sdp.append("a=extmap:");
+ sdp.append(cvoExtMap);
+ sdp.append(" ");
+ sdp.append("urn:3gpp:video-orientation");
+ sdp.append("\r\n");
+ }
+
+ ALOGV("SDPStringFactory => %s", sdp.c_str());
+}
+
} // namespace android
diff --git a/media/libstagefright/rtsp/ASessionDescription.h b/media/libstagefright/rtsp/ASessionDescription.h
index b462983..91f5442 100644
--- a/media/libstagefright/rtsp/ASessionDescription.h
+++ b/media/libstagefright/rtsp/ASessionDescription.h
@@ -40,6 +40,8 @@
size_t countTracks() const;
void getFormat(size_t index, AString *value) const;
+ bool getCvoExtMap(size_t index, int32_t *cvoExtMap) const;
+
void getFormatType(
size_t index, unsigned long *PT,
AString *desc, AString *params) const;
@@ -63,6 +65,9 @@
// i.e. we have a fixed duration, otherwise this is live streaming.
static bool parseNTPRange(const char *s, float *npt1, float *npt2);
+ static void SDPStringFactory(AString &sdp, const char *ip, bool isAudio, unsigned port,
+ unsigned payloadType, unsigned as, const char *codec, const char *fmtp = NULL,
+ int32_t width = 0, int32_t height = 0, int32_t cvoExtMap = 0);
protected:
virtual ~ASessionDescription();
diff --git a/media/libstagefright/rtsp/Android.bp b/media/libstagefright/rtsp/Android.bp
index a5a895e..6179142 100644
--- a/media/libstagefright/rtsp/Android.bp
+++ b/media/libstagefright/rtsp/Android.bp
@@ -4,6 +4,7 @@
srcs: [
"AAMRAssembler.cpp",
"AAVCAssembler.cpp",
+ "AHEVCAssembler.cpp",
"AH263Assembler.cpp",
"AMPEG2TSAssembler.cpp",
"AMPEG4AudioAssembler.cpp",
@@ -17,9 +18,11 @@
"ARTSPConnection.cpp",
"ASessionDescription.cpp",
"SDPLoader.cpp",
+ "QualManager.cpp",
],
shared_libs: [
+ "libandroid_net",
"libcrypto",
"libdatasource",
"libmedia",
@@ -28,6 +31,7 @@
include_dirs: [
"frameworks/av/media/libstagefright",
"frameworks/native/include/media/openmax",
+ "frameworks/native/include/android",
],
arch: {
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 7f025a5..0fdf431 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -1032,6 +1032,11 @@
break;
}
+ int32_t rtcpEvent;
+ if (msg->findInt32("rtcp-event", &rtcpEvent)) {
+ break;
+ }
+
++mNumAccessUnitsReceived;
postAccessUnitTimeoutCheck();
diff --git a/media/libstagefright/rtsp/NetworkUtils.cpp b/media/libstagefright/rtsp/NetworkUtils.cpp
index cc36b78..c053be8 100644
--- a/media/libstagefright/rtsp/NetworkUtils.cpp
+++ b/media/libstagefright/rtsp/NetworkUtils.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <unistd.h>
+
//#define LOG_NDEBUG 0
#define LOG_TAG "NetworkUtils"
#include <utils/Log.h>
diff --git a/media/libstagefright/rtsp/QualManager.cpp b/media/libstagefright/rtsp/QualManager.cpp
new file mode 100644
index 0000000..37aa326
--- /dev/null
+++ b/media/libstagefright/rtsp/QualManager.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "QualManager"
+
+#include <algorithm>
+
+#include <sys/prctl.h>
+#include <utils/Log.h>
+
+#include "QualManager.h"
+
+namespace android {
+
+QualManager::Watcher::Watcher(int32_t timeLimit)
+ : Thread(false), mWatching(false), mSwitch(false),
+ mTimeLimit(timeLimit * 1000000LL) // timeLimit ms
+{
+}
+
+bool QualManager::Watcher::isExpired() const
+{
+ return mSwitch;
+}
+
+void QualManager::Watcher::setup() {
+ AutoMutex _l(mMyLock);
+ if (mWatching == false) {
+ mWatching = true;
+ mMyCond.signal();
+ }
+}
+
+void QualManager::Watcher::release() {
+ AutoMutex _l(mMyLock);
+ if (mSwitch) {
+ ALOGW("%s DISARMED", name);
+ mSwitch = false;
+ }
+ if (mWatching == true) {
+ ALOGW("%s DISARMED", name);
+ mWatching = false;
+ mMyCond.signal();
+ }
+}
+
+void QualManager::Watcher::exit() {
+ AutoMutex _l(mMyLock);
+ // The order is important to avoid dead lock.
+ Thread::requestExit();
+ mMyCond.signal();
+}
+
+QualManager::Watcher::~Watcher() {
+ ALOGI("%s thread dead", name);
+}
+
+bool QualManager::Watcher::threadLoop() {
+ AutoMutex _l(mMyLock);
+#if defined(__linux__)
+ prctl(PR_GET_NAME, name, 0, 0, 0);
+#endif
+ while (!exitPending()) {
+ ALOGW("%s Timer init", name);
+ mMyCond.wait(mMyLock); // waits as non-watching state
+ if (exitPending())
+ return false;
+ ALOGW("%s timer BOOM after %d msec", name, (int)(mTimeLimit / 1000000LL));
+ mMyCond.waitRelative(mMyLock, mTimeLimit); // waits as watching satte
+ if (mWatching == true) {
+ mSwitch = true;
+ ALOGW("%s BOOM!!!!", name);
+ }
+ mWatching = false;
+ }
+ return false;
+}
+
+
+QualManager::QualManager()
+ : mMinBitrate(-1), mMaxBitrate(-1),
+ mTargetBitrate(512000), mLastTargetBitrate(-1),
+ mLastSetBitrateTime(0), mIsNewTargetBitrate(false)
+{
+ VFPWatcher = new Watcher(3000); //Very Few Packet Watcher
+ VFPWatcher->run("VeryFewPtk");
+ LBRWatcher = new Watcher(10000); //Low Bit Rate Watcher
+ LBRWatcher->run("LowBitRate");
+}
+
+QualManager::~QualManager() {
+ VFPWatcher->exit();
+ LBRWatcher->exit();
+}
+
+int32_t QualManager::getTargetBitrate() {
+ if (mIsNewTargetBitrate) {
+ mIsNewTargetBitrate = false;
+ mLastTargetBitrate = clampingBitrate(mTargetBitrate);
+ mTargetBitrate = mLastTargetBitrate;
+ return mTargetBitrate;
+ } else {
+ return -1;
+ }
+}
+
+bool QualManager::isNeedToDowngrade() {
+ return LBRWatcher->isExpired();
+}
+
+void QualManager::setTargetBitrate(uint8_t fraction, int64_t nowUs, bool isTooLowPkts) {
+ /* Too Low Packet. Maybe opponent is switching camera.
+ * If this condition goes longer, we should down bitrate.
+ */
+ if (isTooLowPkts) {
+ VFPWatcher->setup();
+ } else {
+ VFPWatcher->release();
+ }
+
+ if ((fraction > (256 * 5 / 100) && !isTooLowPkts) || VFPWatcher->isExpired()) {
+ // loss more than 5% or VFPWatcher BOOMED
+ mTargetBitrate -= mBitrateStep * 3;
+ } else if (fraction <= (256 * 2 /100)) {
+ // loss less than 2%
+ mTargetBitrate += mBitrateStep;
+ }
+
+ if (mTargetBitrate > mMaxBitrate) {
+ mTargetBitrate = mMaxBitrate + mBitrateStep;
+ } else if (mTargetBitrate < mMinBitrate) {
+ LBRWatcher->setup();
+ mTargetBitrate = mMinBitrate - mBitrateStep;
+ }
+
+ if (mLastTargetBitrate != clampingBitrate(mTargetBitrate) ||
+ nowUs - mLastSetBitrateTime > 5000000ll) {
+ mIsNewTargetBitrate = true;
+ mLastSetBitrateTime = nowUs;
+ }
+}
+
+void QualManager::setMinMaxBitrate(int32_t min, int32_t max) {
+ mMinBitrate = min;
+ mMaxBitrate = max;
+ mBitrateStep = (max - min) / 8;
+}
+
+void QualManager::setBitrateData(int32_t bitrate, int64_t /*now*/) {
+ // A bitrate that is considered packetloss also should be good.
+ if (bitrate >= mMinBitrate && mTargetBitrate >= mMinBitrate) {
+ LBRWatcher->release();
+ } else if (bitrate < mMinBitrate){
+ LBRWatcher->setup();
+ }
+}
+
+int32_t QualManager::clampingBitrate(int32_t bitrate) {
+ return std::min(std::max(mMinBitrate, bitrate), mMaxBitrate);
+}
+} // namespace android
diff --git a/media/libstagefright/rtsp/QualManager.h b/media/libstagefright/rtsp/QualManager.h
new file mode 100644
index 0000000..a7dc921
--- /dev/null
+++ b/media/libstagefright/rtsp/QualManager.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef QUAL_MANAGER_H_
+
+#define QUAL_MANAGER_H_
+
+#include <stdint.h>
+#include <utils/Thread.h>
+
+namespace android {
+class QualManager {
+public:
+ QualManager();
+ ~QualManager();
+
+ int32_t getTargetBitrate();
+ bool isNeedToDowngrade();
+
+ void setTargetBitrate(uint8_t fraction, int64_t nowUs, bool isTooLowPkts);
+ void setMinMaxBitrate(int32_t min, int32_t max);
+ void setBitrateData(int32_t bitrate, int64_t now);
+private:
+ class Watcher : public Thread
+ {
+ public:
+ Watcher(int32_t timeLimit);
+
+ void setup();
+ void release();
+ void exit();
+ bool isExpired() const;
+ private:
+ virtual ~Watcher();
+ virtual bool threadLoop();
+
+ char name[32] = {0,};
+
+ Condition mMyCond;
+ Mutex mMyLock;
+
+ bool mWatching;
+ bool mSwitch;
+ const nsecs_t mTimeLimit;
+ };
+ sp<Watcher> VFPWatcher;
+ sp<Watcher> LBRWatcher;
+ int32_t mMinBitrate;
+ int32_t mMaxBitrate;
+ int32_t mBitrateStep;
+
+ int32_t mTargetBitrate;
+ int32_t mLastTargetBitrate;
+ int64_t mLastSetBitrateTime;
+
+ bool mIsNewTargetBitrate;
+
+ int32_t clampingBitrate(int32_t bitrate);
+};
+} //namespace android
+
+#endif // QUAL_MANAGER_H_
diff --git a/media/libstagefright/tests/ESDS/Android.bp b/media/libstagefright/tests/ESDS/Android.bp
new file mode 100644
index 0000000..1ad1a64
--- /dev/null
+++ b/media/libstagefright/tests/ESDS/Android.bp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+cc_test {
+ name: "ESDSTest",
+ gtest: true,
+
+ srcs: [
+ "ESDSTest.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "libdatasource",
+ "liblog",
+ "libmedia",
+ "libstagefright",
+ "libstagefright_foundation",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libstagefright_esds",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/tests/ESDS/AndroidTest.xml b/media/libstagefright/tests/ESDS/AndroidTest.xml
new file mode 100644
index 0000000..a4fbc7f
--- /dev/null
+++ b/media/libstagefright/tests/ESDS/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Test module config for ESDS unit test">
+ <option name="test-suite-tag" value="ESDSTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="ESDSTest->/data/local/tmp/ESDSTest" />
+ <option name="push-file"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/tests/ESDS/ESDSTestRes-1.0.zip?unzip=true"
+ value="/data/local/tmp/ESDSTestRes/" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="ESDSTest" />
+ <option name="native-test-flag" value="-P /data/local/tmp/ESDSTestRes/" />
+ </test>
+</configuration>
diff --git a/media/libstagefright/tests/ESDS/ESDSTest.cpp b/media/libstagefright/tests/ESDS/ESDSTest.cpp
new file mode 100644
index 0000000..101e00c
--- /dev/null
+++ b/media/libstagefright/tests/ESDS/ESDSTest.cpp
@@ -0,0 +1,210 @@
+/*
+ * 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 "ESDSTest"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <fstream>
+
+#include <ESDS.h>
+#include <binder/ProcessState.h>
+#include <datasource/FileSource.h>
+#include <media/stagefright/MediaExtractorFactory.h>
+#include <media/stagefright/MetaData.h>
+
+#include "ESDSTestEnvironment.h"
+
+using namespace android;
+
+static ESDSTestEnvironment *gEnv = nullptr;
+
+struct ESDSParams {
+ const char *inputFile;
+ int32_t objectTypeIndication;
+ const char *codecSpecificInfoData;
+ int32_t codecSpecificInfoDataSize;
+ int32_t bitrateMax;
+ int32_t bitrateAvg;
+};
+
+class ESDSUnitTest : public ::testing::TestWithParam<tuple<
+ /* InputFile */ const char *,
+ /* ObjectTypeIndication */ int32_t,
+ /* CodecSpecificInfoData */ const char *,
+ /* CodecSpecificInfoDataSize */ int32_t,
+ /* BitrateMax */ int32_t,
+ /* BitrateAvg */ int32_t>> {
+ public:
+ ESDSUnitTest() : mESDSData(nullptr) {
+ mESDSParams.inputFile = get<0>(GetParam());
+ mESDSParams.objectTypeIndication = get<1>(GetParam());
+ mESDSParams.codecSpecificInfoData = get<2>(GetParam());
+ mESDSParams.codecSpecificInfoDataSize = get<3>(GetParam());
+ mESDSParams.bitrateMax = get<4>(GetParam());
+ mESDSParams.bitrateAvg = get<5>(GetParam());
+ };
+
+ virtual void TearDown() override {
+ if (mDataSource) mDataSource.clear();
+ if (mInputFp) {
+ fclose(mInputFp);
+ mInputFp = nullptr;
+ }
+ }
+
+ virtual void SetUp() override { ASSERT_NO_FATAL_FAILURE(readESDSData()); }
+ const void *mESDSData;
+ size_t mESDSSize;
+ ESDSParams mESDSParams;
+
+ private:
+ void readESDSData() {
+ string inputFile = gEnv->getRes() + mESDSParams.inputFile;
+ mInputFp = fopen(inputFile.c_str(), "rb");
+ ASSERT_NE(mInputFp, nullptr) << "File open failed for file: " << inputFile;
+ int32_t fd = fileno(mInputFp);
+ ASSERT_GE(fd, 0) << "File descriptor invalid for file: " << inputFile;
+
+ struct stat buf;
+ status_t status = stat(inputFile.c_str(), &buf);
+ ASSERT_EQ(status, 0) << "Failed to get properties of input file: " << mESDSParams.inputFile;
+ size_t fileSize = buf.st_size;
+
+ mDataSource = new FileSource(dup(fd), 0, fileSize);
+ ASSERT_NE(mDataSource, nullptr) << "Unable to create data source for file: " << inputFile;
+
+ sp<IMediaExtractor> extractor = MediaExtractorFactory::Create(mDataSource);
+ if (extractor == nullptr) {
+ mDataSource.clear();
+ ASSERT_TRUE(false) << "Unable to create extractor for file: " << inputFile;
+ }
+
+ size_t numTracks = extractor->countTracks();
+ ASSERT_GT(numTracks, 0) << "No tracks in file: " << inputFile;
+ ASSERT_TRUE(esdsDataPresent(numTracks, extractor))
+ << "Unable to find esds in any track in file: " << inputFile;
+ }
+
+ bool esdsDataPresent(size_t numTracks, sp<IMediaExtractor> extractor) {
+ bool foundESDS = false;
+ uint32_t type;
+ for (size_t i = 0; i < numTracks; ++i) {
+ sp<MetaData> trackMeta = extractor->getTrackMetaData(i);
+ if (trackMeta != nullptr &&
+ trackMeta->findData(kKeyESDS, &type, &mESDSData, &mESDSSize)) {
+ trackMeta->clear();
+ foundESDS = true;
+ break;
+ }
+ }
+ return foundESDS;
+ }
+
+ FILE *mInputFp;
+ sp<DataSource> mDataSource;
+};
+
+TEST_P(ESDSUnitTest, InvalidDataTest) {
+ void *invalidData = calloc(mESDSSize, 1);
+ ASSERT_NE(invalidData, nullptr) << "Unable to allocate memory";
+ ESDS esds(invalidData, mESDSSize);
+ free(invalidData);
+ ASSERT_NE(esds.InitCheck(), OK) << "invalid ESDS data accepted";
+}
+
+TEST(ESDSSanityUnitTest, ConstructorSanityTest) {
+ void *invalidData = malloc(1);
+ ASSERT_NE(invalidData, nullptr) << "Unable to allocate memory";
+ ESDS esds_zero(invalidData, 0);
+ free(invalidData);
+ ASSERT_NE(esds_zero.InitCheck(), OK) << "invalid ESDS data accepted";
+
+ ESDS esds_null(NULL, 0);
+ ASSERT_NE(esds_null.InitCheck(), OK) << "invalid ESDS data accepted";
+}
+
+TEST_P(ESDSUnitTest, CreateAndDestroyTest) {
+ ESDS esds(mESDSData, mESDSSize);
+ ASSERT_EQ(esds.InitCheck(), OK) << "ESDS data invalid";
+}
+
+TEST_P(ESDSUnitTest, ObjectTypeIndicationTest) {
+ ESDS esds(mESDSData, mESDSSize);
+ ASSERT_EQ(esds.InitCheck(), OK) << "ESDS data invalid";
+ uint8_t objectTypeIndication;
+ status_t status = esds.getObjectTypeIndication(&objectTypeIndication);
+ ASSERT_EQ(status, OK) << "ESDS objectTypeIndication data invalid";
+ ASSERT_EQ(objectTypeIndication, mESDSParams.objectTypeIndication)
+ << "ESDS objectTypeIndication data doesn't match";
+}
+
+TEST_P(ESDSUnitTest, CodecSpecificInfoTest) {
+ ESDS esds(mESDSData, mESDSSize);
+ ASSERT_EQ(esds.InitCheck(), OK) << "ESDS data invalid";
+ status_t status;
+ const void *codecSpecificInfo;
+ size_t codecSpecificInfoSize;
+ status = esds.getCodecSpecificInfo(&codecSpecificInfo, &codecSpecificInfoSize);
+ ASSERT_EQ(status, OK) << "ESDS getCodecSpecificInfo data invalid";
+ ASSERT_EQ(mESDSParams.codecSpecificInfoDataSize, codecSpecificInfoSize)
+ << "CodecSpecificInfo data doesn't match";
+ status = memcmp(codecSpecificInfo, mESDSParams.codecSpecificInfoData, codecSpecificInfoSize);
+ ASSERT_EQ(status, 0) << "CodecSpecificInfo data doesn't match";
+}
+
+TEST_P(ESDSUnitTest, GetBitrateTest) {
+ ESDS esds(mESDSData, mESDSSize);
+ ASSERT_EQ(esds.InitCheck(), OK) << "ESDS data invalid";
+ uint32_t bitrateMax;
+ uint32_t bitrateAvg;
+ status_t status = esds.getBitRate(&bitrateMax, &bitrateAvg);
+ ASSERT_EQ(status, OK) << "ESDS bitRate data invalid";
+ ASSERT_EQ(bitrateMax, mESDSParams.bitrateMax) << "ESDS bitrateMax doesn't match";
+ ASSERT_EQ(bitrateAvg, mESDSParams.bitrateAvg) << "ESDS bitrateAvg doesn't match";
+ ASSERT_LE(bitrateAvg, bitrateMax) << "ESDS bitrateMax is less than bitrateAvg";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ESDSUnitTestAll, ESDSUnitTest,
+ ::testing::Values(
+ // InputFile, ObjectTypeIndication, CodecSpecificInfoData,
+ // CodecSpecificInfoDataSize, BitrateMax, BitrateAvg
+ make_tuple("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_22050hz.3gp", 64,
+ "\x13\x90", 2, 131072, 0),
+ make_tuple("video_1280x720_mp4_mpeg2_3000kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+ 97,
+ "\x00\x00\x01\xB3\x50\x02\xD0\x35\xFF\xFF\xE1\xA0\x00\x00\x01\xB5\x15"
+ "\x6A\x00\x01\x00\x00",
+ 22, 3415452, 3415452),
+ make_tuple("video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz.3gp", 64,
+ "\x15\x08", 2, 24576, 0)));
+
+int main(int argc, char **argv) {
+ // MediaExtractor needs binder thread pool
+ ProcessState::self()->startThreadPool();
+ gEnv = new ESDSTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/tests/ESDS/ESDSTestEnvironment.h b/media/libstagefright/tests/ESDS/ESDSTestEnvironment.h
new file mode 100644
index 0000000..4ca2303
--- /dev/null
+++ b/media/libstagefright/tests/ESDS/ESDSTestEnvironment.h
@@ -0,0 +1,72 @@
+/*
+ * 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 __ESDS_TEST_ENVIRONMENT_H__
+#define __ESDS_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class ESDSTestEnvironment : public ::testing::Environment {
+ public:
+ ESDSTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int ESDSTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"res", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P':
+ setRes(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __ESDS_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/tests/ESDS/README.md b/media/libstagefright/tests/ESDS/README.md
new file mode 100644
index 0000000..100fb86
--- /dev/null
+++ b/media/libstagefright/tests/ESDS/README.md
@@ -0,0 +1,40 @@
+## Media Testing ##
+---
+#### ESDS Unit Test :
+The ESDS Unit Test Suite validates the ESDS class available in libstagefright.
+
+Run the following steps to build the test suite:
+```
+m ESDSTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/ESDSTest/ESDSTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/ESDSTest/ESDSTest /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/tests/ESDS/ESDSTestRes-1.0.zip)
+Download, unzip and push these files into device for testing.
+
+```
+adb push ESDSTestRes /data/local/tmp/
+```
+
+usage: ESDSTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/ESDSTest -P /data/local/tmp/ESDSTestRes/
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest ESDSTest -- --enable-module-dynamic-download=true
+```
diff --git a/media/libstagefright/tests/HEVC/Android.bp b/media/libstagefright/tests/HEVC/Android.bp
new file mode 100644
index 0000000..7a6b959
--- /dev/null
+++ b/media/libstagefright/tests/HEVC/Android.bp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+cc_test {
+ name: "HEVCUtilsUnitTest",
+ gtest: true,
+
+ srcs: [
+ "HEVCUtilsUnitTest.cpp",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "liblog",
+ ],
+
+ static_libs: [
+ "libstagefright",
+ "libstagefright_foundation",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/libstagefright",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/tests/HEVC/AndroidTest.xml b/media/libstagefright/tests/HEVC/AndroidTest.xml
new file mode 100644
index 0000000..ff850a2
--- /dev/null
+++ b/media/libstagefright/tests/HEVC/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Test module config for HEVC Utils unit tests">
+ <option name="test-suite-tag" value="HEVCUtilsUnitTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="false" />
+ <option name="push" value="HEVCUtilsUnitTest->/data/local/tmp/HEVCUtilsUnitTest" />
+ <option name="push-file"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/foundation/tests/HEVCUtils/HEVCUtilsUnitTest.zip?unzip=true"
+ value="/data/local/tmp/HEVCUtilsUnitTest/" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="HEVCUtilsUnitTest" />
+ <option name="native-test-flag" value="-P /data/local/tmp/HEVCUtilsUnitTest/" />
+ </test>
+</configuration>
diff --git a/media/libstagefright/tests/HEVC/HEVCUtilsTestEnvironment.h b/media/libstagefright/tests/HEVC/HEVCUtilsTestEnvironment.h
new file mode 100644
index 0000000..e4481e1
--- /dev/null
+++ b/media/libstagefright/tests/HEVC/HEVCUtilsTestEnvironment.h
@@ -0,0 +1,73 @@
+/*
+ * 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 __HEVC_UTILS_TEST_ENVIRONMENT_H__
+#define __HEVC_UTILS_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class HEVCUtilsTestEnvironment : public::testing::Environment {
+ public:
+ HEVCUtilsTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int HEVCUtilsTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"path", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P': {
+ setRes(optarg);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __HEVC_UTILS_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/tests/HEVC/HEVCUtilsUnitTest.cpp b/media/libstagefright/tests/HEVC/HEVCUtilsUnitTest.cpp
new file mode 100644
index 0000000..324a042
--- /dev/null
+++ b/media/libstagefright/tests/HEVC/HEVCUtilsUnitTest.cpp
@@ -0,0 +1,208 @@
+/*
+ * 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 "HevcUtilityTest"
+#include <utils/Log.h>
+
+#include <fstream>
+
+#include <media/stagefright/foundation/ABitReader.h>
+#include "include/HevcUtils.h"
+
+#include "HEVCUtilsTestEnvironment.h"
+
+using namespace android;
+
+// max size of hvcc box is 2 KB
+constexpr uint32_t kHvccBoxMaxSize = 2048;
+constexpr uint32_t kHvccBoxMinSize = 20;
+constexpr uint32_t kVPSCode = 32;
+constexpr uint32_t kSPSCode = 33;
+constexpr uint32_t kPPSCode = 34;
+constexpr uint32_t kNALSizeLength = 2;
+
+static HEVCUtilsTestEnvironment *gEnv = nullptr;
+
+class HEVCUtilsUnitTest
+ : public ::testing::TestWithParam<
+ tuple</*fileName*/ string, /*infoFileName*/ string, /*numVPSNals*/ size_t,
+ /*numSPSNals*/ size_t, /*numPPSNals*/ size_t, /*frameRate*/ int16_t,
+ /*isHdr*/ bool>> {
+ public:
+ ~HEVCUtilsUnitTest() {
+ if (mMediaFileStream.is_open()) mMediaFileStream.close();
+ if (mInfoFileStream.is_open()) mInfoFileStream.close();
+ }
+
+ virtual void SetUp() override {
+ tuple<string, string, size_t, size_t, size_t, int16_t, bool> params = GetParam();
+ string inputMediaFile = gEnv->getRes() + get<0>(params);
+ mMediaFileStream.open(inputMediaFile, ifstream::in);
+ ASSERT_TRUE(mMediaFileStream.is_open()) << "Failed to open media file: " << inputMediaFile;
+
+ string inputInfoFile = gEnv->getRes() + get<1>(params);
+ mInfoFileStream.open(inputInfoFile, ifstream::in);
+ ASSERT_TRUE(mInfoFileStream.is_open()) << "Failed to open info file: " << inputInfoFile;
+
+ mNumVPSNals = get<2>(params);
+ mNumSPSNals = get<3>(params);
+ mNumPPSNals = get<4>(params);
+ mFrameRate = get<5>(params);
+ mIsHDR = get<6>(params);
+ }
+
+ size_t mNumVPSNals;
+ size_t mNumSPSNals;
+ size_t mNumPPSNals;
+ int16_t mFrameRate;
+ bool mIsHDR;
+ ifstream mMediaFileStream;
+ ifstream mInfoFileStream;
+};
+
+TEST_P(HEVCUtilsUnitTest, NALUnitTest) {
+ HevcParameterSets hevcParams;
+
+ string line;
+ int32_t index = 0;
+ status_t err;
+ while (getline(mInfoFileStream, line)) {
+ string type;
+ int32_t chunkLength;
+
+ istringstream stringLine(line);
+ stringLine >> type >> chunkLength;
+ ASSERT_GT(chunkLength, 0) << "Length of data chunk must be greater than 0";
+
+ char *data = (char *)malloc(chunkLength);
+ ASSERT_NE(data, nullptr) << "Failed to allocate data buffer of size: " << chunkLength;
+
+ mMediaFileStream.read(data, chunkLength);
+ ASSERT_EQ(mMediaFileStream.gcount(), chunkLength)
+ << "Failed to read complete file, bytes read: " << mMediaFileStream.gcount();
+
+ // A valid startcode consists of at least two 0x00 bytes followed by 0x01.
+ int32_t offset = 0;
+ for (; offset + 2 < chunkLength; ++offset) {
+ if (data[offset + 2] == 0x01 && data[offset + 1] == 0x00 && data[offset] == 0x00) {
+ break;
+ }
+ }
+ offset += 3;
+ ASSERT_LE(offset, chunkLength) << "NAL unit offset must not exceed the chunk length";
+
+ uint8_t *nalUnit = (uint8_t *)(data + offset);
+ size_t nalUnitLength = chunkLength - offset;
+
+ // Add NAL units only if they're of type: VPS/SPS/PPS/SEI
+ if (!((type.compare("VPS") && type.compare("SPS") && type.compare("PPS") &&
+ type.compare("SEI")))) {
+ err = hevcParams.addNalUnit(nalUnit, nalUnitLength);
+ ASSERT_EQ(err, (status_t)OK)
+ << "Failed to add NAL Unit type: " << type << " Size: " << nalUnitLength;
+
+ size_t sizeNalUnit = hevcParams.getSize(index);
+ ASSERT_EQ(sizeNalUnit, nalUnitLength) << "Invalid size returned for NAL: " << type;
+
+ uint8_t *destination = (uint8_t *)malloc(nalUnitLength);
+ ASSERT_NE(destination, nullptr)
+ << "Failed to allocate buffer of size: " << nalUnitLength;
+
+ bool status = hevcParams.write(index, destination, nalUnitLength);
+ ASSERT_TRUE(status) << "Unable to write NAL Unit data";
+
+ free(destination);
+ index++;
+ } else {
+ err = hevcParams.addNalUnit(nalUnit, nalUnitLength);
+ ASSERT_NE(err, (status_t)OK) << "Invalid NAL Unit added, type: " << type;
+ }
+ free(data);
+ }
+
+ size_t numNalUnits = hevcParams.getNumNalUnitsOfType(kVPSCode);
+ ASSERT_EQ(numNalUnits, mNumVPSNals) << "Wrong number of VPS NAL Units";
+
+ numNalUnits = hevcParams.getNumNalUnitsOfType(kSPSCode);
+ ASSERT_EQ(numNalUnits, mNumSPSNals) << "Wrong number of SPS NAL Units";
+
+ numNalUnits = hevcParams.getNumNalUnitsOfType(kPPSCode);
+ ASSERT_EQ(numNalUnits, mNumPPSNals) << "Wrong number of PPS NAL Units";
+
+ HevcParameterSets::Info info = hevcParams.getInfo();
+ ASSERT_EQ(info & HevcParameterSets::kInfoIsHdr,
+ (mIsHDR ? HevcParameterSets::kInfoIsHdr : HevcParameterSets::kInfoNone))
+ << "Wrong info about HDR";
+
+ ASSERT_EQ(info & HevcParameterSets::kInfoHasColorDescription,
+ (mIsHDR ? HevcParameterSets::kInfoHasColorDescription : HevcParameterSets::kInfoNone))
+ << "Wrong info about color description";
+
+ // an HEVC file starts with VPS, SPS and PPS NAL units in sequence.
+ uint8_t typeNalUnit = hevcParams.getType(0);
+ ASSERT_EQ(typeNalUnit, kHevcNalUnitTypeVps)
+ << "Expected NAL type: 32(VPS), found: " << typeNalUnit;
+
+ typeNalUnit = hevcParams.getType(1);
+ ASSERT_EQ(typeNalUnit, kHevcNalUnitTypeSps)
+ << "Expected NAL type: 33(SPS), found: " << typeNalUnit;
+
+ typeNalUnit = hevcParams.getType(2);
+ ASSERT_EQ(typeNalUnit, kHevcNalUnitTypePps)
+ << "Expected NAL type: 34(PPS), found: " << typeNalUnit;
+
+ size_t hvccBoxSize = kHvccBoxMaxSize;
+ uint8_t *hvcc = (uint8_t *)malloc(kHvccBoxMaxSize);
+ ASSERT_NE(hvcc, nullptr) << "Failed to allocate a hvcc buffer of size: " << kHvccBoxMaxSize;
+
+ err = hevcParams.makeHvcc(hvcc, &hvccBoxSize, kNALSizeLength);
+ ASSERT_EQ(err, (status_t)OK) << "Unable to create hvcc box";
+
+ ASSERT_GT(hvccBoxSize, kHvccBoxMinSize)
+ << "Hvcc box size must be greater than " << kHvccBoxMinSize;
+
+ int16_t frameRate = hvcc[kHvccBoxMinSize - 1] | (hvcc[kHvccBoxMinSize] << 8);
+ if (frameRate != mFrameRate)
+ cout << "[ WARN ] Expected frame rate: " << mFrameRate << " Found: " << frameRate
+ << endl;
+
+ free(hvcc);
+}
+
+// Info File contains the type and length for each chunk/frame
+INSTANTIATE_TEST_SUITE_P(
+ HEVCUtilsUnitTestAll, HEVCUtilsUnitTest,
+ ::testing::Values(make_tuple("crowd_3840x2160p50f300_32500kbps.hevc",
+ "crowd_3840x2160p50f300_32500kbps.info", 1, 1, 1, 50, false),
+ make_tuple("crowd_1920x1080p24f300_4500kbps.hevc",
+ "crowd_1920x1080p24f300_4500kbps.info", 1, 1, 1, 24, false),
+ make_tuple("crowd_1280x720p24f300_3000kbps.hevc",
+ "crowd_1280x720p24f300_3000kbps.info", 1, 1, 1, 24, false),
+ make_tuple("crowd_640x360p24f300_500kbps.hevc",
+ "crowd_640x360p24f300_500kbps.info", 1, 1, 1, 24, false)));
+
+int main(int argc, char **argv) {
+ gEnv = new HEVCUtilsTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/tests/HEVC/README.md b/media/libstagefright/tests/HEVC/README.md
new file mode 100644
index 0000000..fa0e99c
--- /dev/null
+++ b/media/libstagefright/tests/HEVC/README.md
@@ -0,0 +1,39 @@
+## Media Testing ##
+---
+#### HEVC Utils Test
+The HEVC Utility Unit Test Suite validates the HevcUtils library available in libstagefright.
+
+Run the following steps to build the test suite:
+```
+m HEVCUtilsUnitTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/HEVCUtilsUnitTest/HEVCUtilsUnitTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/HEVCUtilsUnitTest/HEVCUtilsUnitTest /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/foundation/tests/HEVCUtils/HEVCUtilsUnitTest.zip). Download, unzip and push these files into device for testing.
+
+```
+adb push HEVCUtilsUnitTest /data/local/tmp/
+```
+
+usage: HEVCUtilsUnitTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/HEVCUtilsUnitTest -P /data/local/tmp/HEVCUtilsUnitTest/
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest HEVCUtilsUnitTest -- --enable-module-dynamic-download=true
+```
diff --git a/media/libstagefright/tests/extractorFactory/Android.bp b/media/libstagefright/tests/extractorFactory/Android.bp
index e3e61d7..26ec507 100644
--- a/media/libstagefright/tests/extractorFactory/Android.bp
+++ b/media/libstagefright/tests/extractorFactory/Android.bp
@@ -17,6 +17,7 @@
cc_test {
name: "ExtractorFactoryTest",
gtest: true,
+ test_suites: ["device-tests"],
srcs: [
"ExtractorFactoryTest.cpp",
diff --git a/media/libstagefright/tests/writer/Android.bp b/media/libstagefright/tests/writer/Android.bp
index 7e169cb..d058ed3 100644
--- a/media/libstagefright/tests/writer/Android.bp
+++ b/media/libstagefright/tests/writer/Android.bp
@@ -28,6 +28,7 @@
"libcutils",
"liblog",
"libutils",
+ "libmedia",
],
static_libs: [
diff --git a/media/libstagefright/tests/writer/AndroidTest.xml b/media/libstagefright/tests/writer/AndroidTest.xml
index d831555..a21be8a 100644
--- a/media/libstagefright/tests/writer/AndroidTest.xml
+++ b/media/libstagefright/tests/writer/AndroidTest.xml
@@ -19,12 +19,13 @@
<option name="cleanup" value="true" />
<option name="push" value="writerTest->/data/local/tmp/writerTest" />
<option name="push-file"
- key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/tests/writer/Writer.zip?unzip=true"
- value="/data/local/tmp/writerTestRes/" />
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/tests/writer/WriterTestRes.zip?unzip=true"
+ value="/data/local/tmp/WriterTestRes/" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="writerTest" />
- <option name="native-test-flag" value="-P /data/local/tmp/writerTestRes/" />
+ <option name="native-test-flag" value="-P /data/local/tmp/WriterTestRes/" />
+ <option name="native-test-flag" value="-C true" />
</test>
</configuration>
diff --git a/media/libstagefright/tests/writer/README.md b/media/libstagefright/tests/writer/README.md
index ae07917..e103613 100644
--- a/media/libstagefright/tests/writer/README.md
+++ b/media/libstagefright/tests/writer/README.md
@@ -19,13 +19,18 @@
adb push ${OUT}/data/nativetest/writerTest/writerTest /data/local/tmp/
-The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/tests/writer/writerTestRes.zip).
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/tests/writer/WriterTestRes.zip).
Download and extract the folder. Push all the files in this folder to /data/local/tmp/ on the device.
```
-adb push writerTestRes /data/local/tmp/
+adb push WriterTestRes /data/local/tmp/
```
-usage: writerTest -P \<path_to_res_folder\>
+usage: writerTest -P \<path_to_res_folder\> -C <remove_output_file>
```
-adb shell /data/local/tmp/writerTest -P /data/local/tmp/
+adb shell /data/local/tmp/writerTest -P /data/local/tmp/WriterTestRes/ -C true
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest writerTest -- --enable-module-dynamic-download=true
```
diff --git a/media/libstagefright/tests/writer/WriterListener.h b/media/libstagefright/tests/writer/WriterListener.h
new file mode 100644
index 0000000..81f0a7c
--- /dev/null
+++ b/media/libstagefright/tests/writer/WriterListener.h
@@ -0,0 +1,45 @@
+/*
+ * 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 WRITER_LISTENER_H_
+#define WRITER_LISTENER_H_
+
+#include <mutex>
+
+#include <media/IMediaRecorderClient.h>
+#include <media/mediarecorder.h>
+
+using namespace android;
+using namespace std;
+
+class WriterListener : public BnMediaRecorderClient {
+ public:
+ WriterListener() : mSignaledSize(false), mSignaledDuration(false) {}
+
+ virtual void notify(int32_t msg, int32_t ext1, int32_t ext2) {
+ ALOGV("msg : %d, ext1 : %d, ext2 : %d", msg, ext1, ext2);
+ if (ext1 == MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
+ mSignaledSize = true;
+ } else if (ext1 == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
+ mSignaledDuration = true;
+ }
+ }
+
+ volatile bool mSignaledSize;
+ volatile bool mSignaledDuration;
+};
+
+#endif // WRITER_LISTENER_H_
diff --git a/media/libstagefright/tests/writer/WriterTest.cpp b/media/libstagefright/tests/writer/WriterTest.cpp
index ff063e3..4c0add4 100644
--- a/media/libstagefright/tests/writer/WriterTest.cpp
+++ b/media/libstagefright/tests/writer/WriterTest.cpp
@@ -49,8 +49,32 @@
int32_t channelCount;
};
+enum inputId {
+ // audio streams
+ AAC_1,
+ AAC_ADTS_1,
+ AMR_NB_1,
+ AMR_WB_1,
+ FLAC_1,
+ OPUS_1,
+ VORBIS_1,
+ // video streams
+ AV1_1,
+ AVC_1,
+ H263_1,
+ HEVC_1,
+ MPEG4_1,
+ VP8_1,
+ VP9_1,
+ // heif stream
+ HEIC_1,
+ UNUSED_ID,
+ UNKNOWN_ID,
+};
+
// LookUpTable of clips and metadata for component testing
static const struct InputData {
+ inputId inpId;
const char *mime;
string inputFile;
string info;
@@ -58,65 +82,73 @@
int32_t secondParam;
bool isAudio;
} kInputData[] = {
- {MEDIA_MIMETYPE_AUDIO_OPUS, "bbb_opus_stereo_128kbps_48000hz.opus",
- "bbb_opus_stereo_128kbps_48000hz.info", 48000, 2, true},
- {MEDIA_MIMETYPE_AUDIO_AAC, "bbb_aac_stereo_128kbps_48000hz.aac",
+ {AAC_1, MEDIA_MIMETYPE_AUDIO_AAC, "bbb_aac_stereo_128kbps_48000hz.aac",
"bbb_aac_stereo_128kbps_48000hz.info", 48000, 2, true},
- {MEDIA_MIMETYPE_AUDIO_AAC_ADTS, "Mps_2_c2_fr1_Sc1_Dc2_0x03_raw.adts",
+ {AAC_ADTS_1, MEDIA_MIMETYPE_AUDIO_AAC_ADTS, "Mps_2_c2_fr1_Sc1_Dc2_0x03_raw.adts",
"Mps_2_c2_fr1_Sc1_Dc2_0x03_raw.info", 48000, 2, true},
- {MEDIA_MIMETYPE_AUDIO_AMR_NB, "sine_amrnb_1ch_12kbps_8000hz.amrnb",
+ {AMR_NB_1, MEDIA_MIMETYPE_AUDIO_AMR_NB, "sine_amrnb_1ch_12kbps_8000hz.amrnb",
"sine_amrnb_1ch_12kbps_8000hz.info", 8000, 1, true},
- {MEDIA_MIMETYPE_AUDIO_AMR_WB, "bbb_amrwb_1ch_14kbps_16000hz.amrwb",
+ {AMR_WB_1, MEDIA_MIMETYPE_AUDIO_AMR_WB, "bbb_amrwb_1ch_14kbps_16000hz.amrwb",
"bbb_amrwb_1ch_14kbps_16000hz.info", 16000, 1, true},
- {MEDIA_MIMETYPE_AUDIO_VORBIS, "bbb_vorbis_stereo_128kbps_48000hz.vorbis",
- "bbb_vorbis_stereo_128kbps_48000hz.info", 48000, 2, true},
- {MEDIA_MIMETYPE_AUDIO_FLAC, "bbb_flac_stereo_680kbps_48000hz.flac",
+ {FLAC_1, MEDIA_MIMETYPE_AUDIO_FLAC, "bbb_flac_stereo_680kbps_48000hz.flac",
"bbb_flac_stereo_680kbps_48000hz.info", 48000, 2, true},
- {MEDIA_MIMETYPE_VIDEO_VP9, "bbb_vp9_176x144_285kbps_60fps.vp9",
- "bbb_vp9_176x144_285kbps_60fps.info", 176, 144, false},
- {MEDIA_MIMETYPE_VIDEO_VP8, "bbb_vp8_176x144_240kbps_60fps.vp8",
- "bbb_vp8_176x144_240kbps_60fps.info", 176, 144, false},
- {MEDIA_MIMETYPE_VIDEO_AVC, "bbb_avc_176x144_300kbps_60fps.h264",
+ {OPUS_1, MEDIA_MIMETYPE_AUDIO_OPUS, "bbb_opus_stereo_128kbps_48000hz.opus",
+ "bbb_opus_stereo_128kbps_48000hz.info", 48000, 2, true},
+ {VORBIS_1, MEDIA_MIMETYPE_AUDIO_VORBIS, "bbb_vorbis_stereo_128kbps_48000hz.vorbis",
+ "bbb_vorbis_stereo_128kbps_48000hz.info", 48000, 2, true},
+
+ {AV1_1, MEDIA_MIMETYPE_VIDEO_AV1, "bbb_av1_176_144.av1", "bbb_av1_176_144.info", 176, 144,
+ false},
+ {AVC_1, MEDIA_MIMETYPE_VIDEO_AVC, "bbb_avc_176x144_300kbps_60fps.h264",
"bbb_avc_176x144_300kbps_60fps.info", 176, 144, false},
- {MEDIA_MIMETYPE_VIDEO_HEVC, "bbb_hevc_176x144_176kbps_60fps.hevc",
- "bbb_hevc_176x144_176kbps_60fps.info", 176, 144, false},
- {MEDIA_MIMETYPE_VIDEO_AV1, "bbb_av1_176_144.av1", "bbb_av1_176_144.info", 176, 144, false},
- {MEDIA_MIMETYPE_VIDEO_H263, "bbb_h263_352x288_300kbps_12fps.h263",
+ {H263_1, MEDIA_MIMETYPE_VIDEO_H263, "bbb_h263_352x288_300kbps_12fps.h263",
"bbb_h263_352x288_300kbps_12fps.info", 352, 288, false},
- {MEDIA_MIMETYPE_VIDEO_MPEG4, "bbb_mpeg4_352x288_512kbps_30fps.m4v",
+ {HEVC_1, MEDIA_MIMETYPE_VIDEO_HEVC, "bbb_hevc_176x144_176kbps_60fps.hevc",
+ "bbb_hevc_176x144_176kbps_60fps.info", 176, 144, false},
+ {MPEG4_1, MEDIA_MIMETYPE_VIDEO_MPEG4, "bbb_mpeg4_352x288_512kbps_30fps.m4v",
"bbb_mpeg4_352x288_512kbps_30fps.info", 352, 288, false},
+ {VP8_1, MEDIA_MIMETYPE_VIDEO_VP8, "bbb_vp8_176x144_240kbps_60fps.vp8",
+ "bbb_vp8_176x144_240kbps_60fps.info", 176, 144, false},
+ {VP9_1, MEDIA_MIMETYPE_VIDEO_VP9, "bbb_vp9_176x144_285kbps_60fps.vp9",
+ "bbb_vp9_176x144_285kbps_60fps.info", 176, 144, false},
+
+ {HEIC_1, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC, "bbb_hevc_176x144_176kbps_60fps.hevc",
+ "bbb_heic_176x144_176kbps_60fps.info", 176, 144, false},
};
-class WriterTest : public ::testing::TestWithParam<pair<string, int32_t>> {
+class WriterTest {
public:
- WriterTest() : mWriter(nullptr), mFileMeta(nullptr), mCurrentTrack(nullptr) {}
+ WriterTest() : mWriter(nullptr), mFileMeta(nullptr) {}
~WriterTest() {
- if (mWriter) {
- mWriter.clear();
- mWriter = nullptr;
- }
if (mFileMeta) {
mFileMeta.clear();
mFileMeta = nullptr;
}
- if (mCurrentTrack) {
- mCurrentTrack.clear();
- mCurrentTrack = nullptr;
+ if (mWriter) {
+ mWriter.clear();
+ mWriter = nullptr;
+ }
+ if (gEnv->cleanUp()) remove(OUTPUT_FILE_NAME);
+
+ for (int32_t idx = 0; idx < kMaxTrackCount; idx++) {
+ mBufferInfo[idx].clear();
+ if (mCurrentTrack[idx]) {
+ mCurrentTrack[idx]->stop();
+ mCurrentTrack[idx].clear();
+ mCurrentTrack[idx] = nullptr;
+ }
+ if (mInputStream[idx].is_open()) mInputStream[idx].close();
}
}
- virtual void SetUp() override {
- mNumCsds = 0;
- mInputFrameId = 0;
+ void setupWriterType(string writerFormat) {
mWriterName = unknown_comp;
mDisableTest = false;
-
static const std::map<std::string, standardWriters> mapWriter = {
{"ogg", OGG}, {"aac", AAC}, {"aac_adts", AAC_ADTS}, {"webm", WEBM},
{"mpeg4", MPEG4}, {"amrnb", AMR_NB}, {"amrwb", AMR_WB}, {"mpeg2Ts", MPEG2TS}};
// Find the component type
- string writerFormat = GetParam().first;
if (mapWriter.find(writerFormat) != mapWriter.end()) {
mWriterName = mapWriter.at(writerFormat);
}
@@ -126,16 +158,11 @@
}
}
- virtual void TearDown() override {
- mBufferInfo.clear();
- if (mInputStream.is_open()) mInputStream.close();
- }
-
- void getInputBufferInfo(string inputFileName, string inputInfo);
+ void getInputBufferInfo(string inputFileName, string inputInfo, int32_t idx = 0);
int32_t createWriter(int32_t fd);
- int32_t addWriterSource(bool isAudio, configFormat params);
+ int32_t addWriterSource(bool isAudio, configFormat params, int32_t idx = 0);
enum standardWriters {
OGG,
@@ -152,32 +179,42 @@
standardWriters mWriterName;
sp<MediaWriter> mWriter;
sp<MetaData> mFileMeta;
- sp<MediaAdapter> mCurrentTrack;
+ sp<MediaAdapter> mCurrentTrack[kMaxTrackCount]{};
bool mDisableTest;
- int32_t mNumCsds;
- int32_t mInputFrameId;
- ifstream mInputStream;
- vector<BufferInfo> mBufferInfo;
+ int32_t mNumCsds[kMaxTrackCount]{};
+ int32_t mInputFrameId[kMaxTrackCount]{};
+ ifstream mInputStream[kMaxTrackCount]{};
+ vector<BufferInfo> mBufferInfo[kMaxTrackCount];
};
-void WriterTest::getInputBufferInfo(string inputFileName, string inputInfo) {
+class WriteFunctionalityTest
+ : public WriterTest,
+ public ::testing::TestWithParam<tuple<string /* writerFormat*/, inputId /* inputId0*/,
+ inputId /* inputId1*/, float /* BufferInterval*/>> {
+ public:
+ virtual void SetUp() override { setupWriterType(get<0>(GetParam())); }
+};
+
+void WriterTest::getInputBufferInfo(string inputFileName, string inputInfo, int32_t idx) {
std::ifstream eleInfo;
eleInfo.open(inputInfo.c_str());
ASSERT_EQ(eleInfo.is_open(), true);
int32_t bytesCount = 0;
uint32_t flags = 0;
int64_t timestamp = 0;
+ int32_t numCsds = 0;
while (1) {
if (!(eleInfo >> bytesCount)) break;
eleInfo >> flags;
eleInfo >> timestamp;
- mBufferInfo.push_back({bytesCount, flags, timestamp});
- if (flags == CODEC_CONFIG_FLAG) mNumCsds++;
+ mBufferInfo[idx].push_back({bytesCount, flags, timestamp});
+ if (flags == CODEC_CONFIG_FLAG) numCsds++;
}
eleInfo.close();
- mInputStream.open(inputFileName.c_str(), std::ifstream::binary);
- ASSERT_EQ(mInputStream.is_open(), true);
+ mNumCsds[idx] = numCsds;
+ mInputStream[idx].open(inputFileName.c_str(), std::ifstream::binary);
+ ASSERT_EQ(mInputStream[idx].is_open(), true);
}
int32_t WriterTest::createWriter(int32_t fd) {
@@ -223,10 +260,10 @@
return 0;
}
-int32_t WriterTest::addWriterSource(bool isAudio, configFormat params) {
- if (mInputFrameId) return -1;
+int32_t WriterTest::addWriterSource(bool isAudio, configFormat params, int32_t idx) {
+ if (mInputFrameId[idx]) return -1;
sp<AMessage> format = new AMessage;
- if (mInputStream.is_open()) {
+ if (mInputStream[idx].is_open()) {
format->setString("mime", params.mime);
if (isAudio) {
format->setInt32("channel-count", params.channelCount);
@@ -235,25 +272,34 @@
format->setInt32("width", params.width);
format->setInt32("height", params.height);
}
-
- int32_t status =
- writeHeaderBuffers(mInputStream, mBufferInfo, mInputFrameId, format, mNumCsds);
- if (status != 0) return -1;
+ if (mNumCsds[idx]) {
+ int32_t status = writeHeaderBuffers(mInputStream[idx], mBufferInfo[idx],
+ mInputFrameId[idx], format, mNumCsds[idx]);
+ if (status != 0) return -1;
+ }
}
+
sp<MetaData> trackMeta = new MetaData;
convertMessageToMetaData(format, trackMeta);
- mCurrentTrack = new MediaAdapter(trackMeta);
- if (mCurrentTrack == nullptr) {
+ mCurrentTrack[idx] = new MediaAdapter(trackMeta);
+ if (mCurrentTrack[idx] == nullptr) {
ALOGE("MediaAdapter returned nullptr");
return -1;
}
- status_t result = mWriter->addSource(mCurrentTrack);
+ status_t result = mWriter->addSource(mCurrentTrack[idx]);
return result;
}
void getFileDetails(string &inputFilePath, string &info, configFormat ¶ms, bool &isAudio,
- int32_t streamIndex = 0) {
- if (streamIndex >= sizeof(kInputData) / sizeof(kInputData[0])) {
+ inputId inpId) {
+ int32_t inputDataSize = sizeof(kInputData) / sizeof(kInputData[0]);
+ int32_t streamIndex = 0;
+ for (; streamIndex < inputDataSize; streamIndex++) {
+ if (inpId == kInputData[streamIndex].inpId) {
+ break;
+ }
+ }
+ if (streamIndex == inputDataSize) {
return;
}
inputFilePath += kInputData[streamIndex].inputFile;
@@ -270,7 +316,7 @@
return;
}
-TEST_P(WriterTest, CreateWriterTest) {
+TEST_P(WriteFunctionalityTest, CreateWriterTest) {
if (mDisableTest) return;
ALOGV("Tests the creation of writers");
@@ -281,14 +327,14 @@
// Creating writer within a test scope. Destructor should be called when the test ends
ASSERT_EQ((status_t)OK, createWriter(fd))
- << "Failed to create writer for output format:" << GetParam().first;
+ << "Failed to create writer for output format:" << get<0>(GetParam());
}
-TEST_P(WriterTest, WriterTest) {
+TEST_P(WriteFunctionalityTest, WriterTest) {
if (mDisableTest) return;
ALOGV("Checks if for a given input, a valid muxed file has been created or not");
- string writerFormat = GetParam().first;
+ string writerFormat = get<0>(GetParam());
string outputFile = OUTPUT_FILE_NAME;
int32_t fd =
open(outputFile.c_str(), O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
@@ -297,35 +343,59 @@
int32_t status = createWriter(fd);
ASSERT_EQ((status_t)OK, status) << "Failed to create writer for output format:" << writerFormat;
- string inputFile = gEnv->getRes();
- string inputInfo = gEnv->getRes();
- configFormat param;
- bool isAudio;
- int32_t inputFileIdx = GetParam().second;
- getFileDetails(inputFile, inputInfo, param, isAudio, inputFileIdx);
- ASSERT_NE(inputFile.compare(gEnv->getRes()), 0) << "No input file specified";
+ inputId inpId[] = {get<1>(GetParam()), get<2>(GetParam())};
+ ASSERT_NE(inpId[0], UNUSED_ID) << "Test expects first inputId to be a valid id";
- ASSERT_NO_FATAL_FAILURE(getInputBufferInfo(inputFile, inputInfo));
- status = addWriterSource(isAudio, param);
- ASSERT_EQ((status_t)OK, status) << "Failed to add source for " << writerFormat << "Writer";
+ int32_t numTracks = 1;
+ if (inpId[1] != UNUSED_ID) {
+ numTracks++;
+ }
+ for (int32_t idx = 0; idx < numTracks; idx++) {
+ string inputFile = gEnv->getRes();
+ string inputInfo = gEnv->getRes();
+ configFormat param;
+ bool isAudio;
+ getFileDetails(inputFile, inputInfo, param, isAudio, inpId[idx]);
+ ASSERT_NE(inputFile.compare(gEnv->getRes()), 0) << "No input file specified";
+
+ ASSERT_NO_FATAL_FAILURE(getInputBufferInfo(inputFile, inputInfo, idx));
+ status = addWriterSource(isAudio, param, idx);
+ ASSERT_EQ((status_t)OK, status) << "Failed to add source for " << writerFormat << "Writer";
+ }
status = mWriter->start(mFileMeta.get());
ASSERT_EQ((status_t)OK, status);
- status = sendBuffersToWriter(mInputStream, mBufferInfo, mInputFrameId, mCurrentTrack, 0,
- mBufferInfo.size());
- ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
- mCurrentTrack->stop();
+ float interval = get<3>(GetParam());
+ ASSERT_LE(interval, 1.0f) << "Buffer interval invalid. Should be less than or equal to 1.0";
+ size_t range = 0;
+ int32_t loopCount = 0;
+ int32_t offset[kMaxTrackCount]{};
+ while (loopCount < ceil(1.0 / interval)) {
+ for (int32_t idx = 0; idx < numTracks; idx++) {
+ range = mBufferInfo[idx].size() * interval;
+ status = sendBuffersToWriter(mInputStream[idx], mBufferInfo[idx], mInputFrameId[idx],
+ mCurrentTrack[idx], offset[idx], range);
+ ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
+ offset[idx] += range;
+ }
+ loopCount++;
+ }
+ for (int32_t idx = 0; idx < kMaxTrackCount; idx++) {
+ if (mCurrentTrack[idx]) {
+ mCurrentTrack[idx]->stop();
+ }
+ }
status = mWriter->stop();
ASSERT_EQ((status_t)OK, status) << "Failed to stop the writer";
close(fd);
}
-TEST_P(WriterTest, PauseWriterTest) {
+TEST_P(WriteFunctionalityTest, PauseWriterTest) {
if (mDisableTest) return;
ALOGV("Validates the pause() api of writers");
- string writerFormat = GetParam().first;
+ string writerFormat = get<0>(GetParam());
string outputFile = OUTPUT_FILE_NAME;
int32_t fd =
open(outputFile.c_str(), O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
@@ -338,8 +408,10 @@
string inputInfo = gEnv->getRes();
configFormat param;
bool isAudio;
- int32_t inputFileIdx = GetParam().second;
- getFileDetails(inputFile, inputInfo, param, isAudio, inputFileIdx);
+ inputId inpId = get<1>(GetParam());
+ ASSERT_NE(inpId, UNUSED_ID) << "Test expects first inputId to be a valid id";
+
+ getFileDetails(inputFile, inputInfo, param, isAudio, inpId);
ASSERT_NE(inputFile.compare(gEnv->getRes()), 0) << "No input file specified";
ASSERT_NO_FATAL_FAILURE(getInputBufferInfo(inputFile, inputInfo));
@@ -348,8 +420,8 @@
status = mWriter->start(mFileMeta.get());
ASSERT_EQ((status_t)OK, status);
- status = sendBuffersToWriter(mInputStream, mBufferInfo, mInputFrameId, mCurrentTrack, 0,
- mBufferInfo.size() / 4);
+ status = sendBuffersToWriter(mInputStream[0], mBufferInfo[0], mInputFrameId[0],
+ mCurrentTrack[0], 0, mBufferInfo[0].size() / 4);
ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
bool isPaused = false;
@@ -359,26 +431,26 @@
isPaused = true;
}
// In the pause state, writers shouldn't write anything. Testing the writers for the same
- int32_t numFramesPaused = mBufferInfo.size() / 4;
- status = sendBuffersToWriter(mInputStream, mBufferInfo, mInputFrameId, mCurrentTrack,
- mInputFrameId, numFramesPaused, isPaused);
+ int32_t numFramesPaused = mBufferInfo[0].size() / 4;
+ status = sendBuffersToWriter(mInputStream[0], mBufferInfo[0], mInputFrameId[0],
+ mCurrentTrack[0], mInputFrameId[0], numFramesPaused, isPaused);
ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
if (isPaused) {
status = mWriter->start(mFileMeta.get());
ASSERT_EQ((status_t)OK, status);
}
- status = sendBuffersToWriter(mInputStream, mBufferInfo, mInputFrameId, mCurrentTrack,
- mInputFrameId, mBufferInfo.size());
+ status = sendBuffersToWriter(mInputStream[0], mBufferInfo[0], mInputFrameId[0],
+ mCurrentTrack[0], mInputFrameId[0], mBufferInfo[0].size());
ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
- mCurrentTrack->stop();
+ mCurrentTrack[0]->stop();
status = mWriter->stop();
ASSERT_EQ((status_t)OK, status) << "Failed to stop the writer";
close(fd);
}
-TEST_P(WriterTest, MultiStartStopPauseTest) {
+TEST_P(WriteFunctionalityTest, MultiStartStopPauseTest) {
// TODO: (b/144821804)
// Enable the test for MPE2TS writer
if (mDisableTest || mWriterName == standardWriters::MPEG2TS) return;
@@ -389,7 +461,7 @@
open(outputFile.c_str(), O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
ASSERT_GE(fd, 0) << "Failed to open output file to dump writer's data";
- string writerFormat = GetParam().first;
+ string writerFormat = get<0>(GetParam());
int32_t status = createWriter(fd);
ASSERT_EQ(status, (status_t)OK) << "Failed to create writer for output format:" << writerFormat;
@@ -397,8 +469,10 @@
string inputInfo = gEnv->getRes();
configFormat param;
bool isAudio;
- int32_t inputFileIdx = GetParam().second;
- getFileDetails(inputFile, inputInfo, param, isAudio, inputFileIdx);
+ inputId inpId = get<1>(GetParam());
+ ASSERT_NE(inpId, UNUSED_ID) << "Test expects first inputId to be a valid id";
+
+ getFileDetails(inputFile, inputInfo, param, isAudio, inpId);
ASSERT_NE(inputFile.compare(gEnv->getRes()), 0) << "No input file specified";
ASSERT_NO_FATAL_FAILURE(getInputBufferInfo(inputFile, inputInfo));
@@ -415,8 +489,8 @@
mWriter->start(mFileMeta.get());
}
- status = sendBuffersToWriter(mInputStream, mBufferInfo, mInputFrameId, mCurrentTrack, 0,
- mBufferInfo.size() / 4);
+ status = sendBuffersToWriter(mInputStream[0], mBufferInfo[0], mInputFrameId[0],
+ mCurrentTrack[0], 0, mBufferInfo[0].size() / 4);
ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
for (int32_t count = 0; count < kMaxCount; count++) {
@@ -425,20 +499,20 @@
}
mWriter->pause();
- int32_t numFramesPaused = mBufferInfo.size() / 4;
- status = sendBuffersToWriter(mInputStream, mBufferInfo, mInputFrameId, mCurrentTrack,
- mInputFrameId, numFramesPaused, true);
+ int32_t numFramesPaused = mBufferInfo[0].size() / 4;
+ status = sendBuffersToWriter(mInputStream[0], mBufferInfo[0], mInputFrameId[0],
+ mCurrentTrack[0], mInputFrameId[0], numFramesPaused, true);
ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
for (int32_t count = 0; count < kMaxCount; count++) {
mWriter->start(mFileMeta.get());
}
- status = sendBuffersToWriter(mInputStream, mBufferInfo, mInputFrameId, mCurrentTrack,
- mInputFrameId, mBufferInfo.size());
+ status = sendBuffersToWriter(mInputStream[0], mBufferInfo[0], mInputFrameId[0],
+ mCurrentTrack[0], mInputFrameId[0], mBufferInfo[0].size());
ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
- mCurrentTrack->stop();
+ mCurrentTrack[0]->stop();
// first stop should succeed.
status = mWriter->stop();
@@ -451,17 +525,183 @@
close(fd);
}
+class ListenerTest
+ : public WriterTest,
+ public ::testing::TestWithParam<tuple<
+ string /* writerFormat*/, inputId /* inputId0*/, inputId /* inputId1*/,
+ float /* FileSizeLimit*/, float /* FileDurationLimit*/, float /* BufferInterval*/>> {
+ public:
+ virtual void SetUp() override { setupWriterType(get<0>(GetParam())); }
+};
+
+TEST_P(ListenerTest, SetMaxFileLimitsTest) {
+ // TODO(b/151892414): Enable test for other writers
+ if (mDisableTest || mWriterName != MPEG4) return;
+ ALOGV("Validates writer when max file limits are set");
+
+ string writerFormat = get<0>(GetParam());
+ string outputFile = OUTPUT_FILE_NAME;
+ int32_t fd =
+ open(outputFile.c_str(), O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
+ ASSERT_GE(fd, 0) << "Failed to open output file to dump writer's data";
+
+ int32_t status = createWriter(fd);
+ ASSERT_EQ((status_t)OK, status) << "Failed to create writer for output format:" << writerFormat;
+
+ inputId inpId[] = {get<1>(GetParam()), get<2>(GetParam())};
+ ASSERT_NE(inpId[0], UNUSED_ID) << "Test expects first inputId to be a valid id";
+
+ size_t inputFileSize = 0;
+ int64_t lastFrameTimeStampUs = INT_MAX;
+ int32_t numTracks = 1;
+ if (inpId[1] != UNUSED_ID) {
+ numTracks++;
+ }
+ for (int32_t idx = 0; idx < numTracks; idx++) {
+ string inputFile = gEnv->getRes();
+ string inputInfo = gEnv->getRes();
+ configFormat param;
+ bool isAudio;
+ getFileDetails(inputFile, inputInfo, param, isAudio, inpId[idx]);
+ ASSERT_NE(inputFile.compare(gEnv->getRes()), 0) << "No input file specified";
+
+ ASSERT_NO_FATAL_FAILURE(getInputBufferInfo(inputFile, inputInfo, idx));
+ status = addWriterSource(isAudio, param, idx);
+ ASSERT_EQ((status_t)OK, status) << "Failed to add source for " << writerFormat << "Writer";
+
+ // Read file properties
+ struct stat buf;
+ status = stat(inputFile.c_str(), &buf);
+ ASSERT_EQ(0, status);
+
+ inputFileSize += buf.st_size;
+ if (lastFrameTimeStampUs > mBufferInfo[idx][mBufferInfo[idx].size() - 1].timeUs) {
+ lastFrameTimeStampUs = mBufferInfo[idx][mBufferInfo[idx].size() - 1].timeUs;
+ }
+ }
+
+ float fileSizeLimit = get<3>(GetParam());
+ float fileDurationLimit = get<4>(GetParam());
+ int64_t maxFileSize = 0;
+ int64_t maxFileDuration = 0;
+ if (fileSizeLimit > 0) {
+ maxFileSize = (int64_t)(fileSizeLimit * inputFileSize);
+ mWriter->setMaxFileSize(maxFileSize);
+ }
+ if (fileDurationLimit > 0) {
+ maxFileDuration = (int64_t)(fileDurationLimit * lastFrameTimeStampUs);
+ mWriter->setMaxFileDuration(maxFileDuration);
+ }
+
+ sp<WriterListener> listener = new WriterListener();
+ ASSERT_NE(listener, nullptr) << "unable to allocate listener";
+
+ mWriter->setListener(listener);
+ status = mWriter->start(mFileMeta.get());
+ ASSERT_EQ((status_t)OK, status);
+
+ float interval = get<5>(GetParam());
+ ASSERT_LE(interval, 1.0f) << "Buffer interval invalid. Should be less than or equal to 1.0";
+
+ size_t range = 0;
+ int32_t loopCount = 0;
+ int32_t offset[kMaxTrackCount]{};
+ while (loopCount < ceil(1.0 / interval)) {
+ for (int32_t idx = 0; idx < numTracks; idx++) {
+ range = mBufferInfo[idx].size() * interval;
+ status = sendBuffersToWriter(mInputStream[idx], mBufferInfo[idx], mInputFrameId[idx],
+ mCurrentTrack[idx], offset[idx], range, false, listener);
+ ASSERT_EQ((status_t)OK, status) << writerFormat << " writer failed";
+ offset[idx] += range;
+ }
+ loopCount++;
+ }
+
+ ASSERT_TRUE(mWriter->reachedEOS()) << "EOS not signalled.";
+
+ for (int32_t idx = 0; idx < kMaxTrackCount; idx++) {
+ if (mCurrentTrack[idx]) {
+ mCurrentTrack[idx]->stop();
+ }
+ }
+
+ status = mWriter->stop();
+ ASSERT_EQ((status_t)OK, status) << "Failed to stop the writer";
+ close(fd);
+
+ if (maxFileSize <= 0) {
+ ASSERT_FALSE(listener->mSignaledSize);
+ } else if (maxFileDuration <= 0) {
+ ASSERT_FALSE(listener->mSignaledDuration);
+ } else if (maxFileSize > 0 && maxFileDuration <= 0) {
+ ASSERT_TRUE(listener->mSignaledSize);
+ } else if (maxFileDuration > 0 && maxFileSize <= 0) {
+ ASSERT_TRUE(listener->mSignaledDuration);
+ } else {
+ ASSERT_TRUE(listener->mSignaledSize || listener->mSignaledDuration);
+ }
+
+ if (maxFileSize > 0) {
+ struct stat buf;
+ status = stat(outputFile.c_str(), &buf);
+ ASSERT_EQ(0, status);
+ ASSERT_LE(buf.st_size, maxFileSize);
+ }
+}
+
+// TODO: (b/150923387)
+// Add WEBM input
+INSTANTIATE_TEST_SUITE_P(ListenerTestAll, ListenerTest,
+ ::testing::Values(make_tuple("aac", AAC_1, UNUSED_ID, 0.6, 0.7, 1),
+ make_tuple("amrnb", AMR_NB_1, UNUSED_ID, 0.2, 0.6, 1),
+ make_tuple("amrwb", AMR_WB_1, UNUSED_ID, 0.5, 0.5, 1),
+ make_tuple("mpeg2Ts", AAC_1, UNUSED_ID, 0.2, 1, 1),
+ make_tuple("mpeg4", AAC_1, UNUSED_ID, 0.4, 0.3, 0.25),
+ make_tuple("ogg", OPUS_1, UNUSED_ID, 0.7, 0.3, 1)));
+
// TODO: (b/144476164)
// Add AAC_ADTS, FLAC, AV1 input
-INSTANTIATE_TEST_SUITE_P(WriterTestAll, WriterTest,
- ::testing::Values(make_pair("ogg", 0), make_pair("webm", 0),
- make_pair("aac", 1), make_pair("mpeg4", 1),
- make_pair("amrnb", 3), make_pair("amrwb", 4),
- make_pair("webm", 5), make_pair("webm", 7),
- make_pair("webm", 8), make_pair("mpeg4", 9),
- make_pair("mpeg4", 10), make_pair("mpeg4", 12),
- make_pair("mpeg4", 13), make_pair("mpeg2Ts", 1),
- make_pair("mpeg2Ts", 9)));
+INSTANTIATE_TEST_SUITE_P(
+ WriterTestAll, WriteFunctionalityTest,
+ ::testing::Values(
+ make_tuple("aac", AAC_1, UNUSED_ID, 1),
+
+ make_tuple("amrnb", AMR_NB_1, UNUSED_ID, 1),
+ make_tuple("amrwb", AMR_WB_1, UNUSED_ID, 1),
+
+ // TODO(b/144902018): Enable test for mpeg2ts
+ // make_tuple("mpeg2Ts", AAC_1, UNUSED_ID, 1),
+ // make_tuple("mpeg2Ts", AVC_1, UNUSED_ID, 1),
+ // TODO(b/156355857): Add multitrack for mpeg2ts
+ // make_tuple("mpeg2Ts", AAC_1, AVC_1, 0.50),
+ // make_tuple("mpeg2Ts", AVC_1, AAC_1, 0.25),
+
+ make_tuple("mpeg4", AAC_1, UNUSED_ID, 1),
+ make_tuple("mpeg4", AMR_NB_1, UNUSED_ID, 1),
+ make_tuple("mpeg4", AMR_WB_1, UNUSED_ID, 1),
+ make_tuple("mpeg4", AVC_1, UNUSED_ID, 1),
+ make_tuple("mpeg4", H263_1, UNUSED_ID, 1),
+ make_tuple("mpeg4", HEIC_1, UNUSED_ID, 1),
+ make_tuple("mpeg4", HEVC_1, UNUSED_ID, 1),
+ make_tuple("mpeg4", MPEG4_1, UNUSED_ID, 1),
+ make_tuple("mpeg4", AAC_1, AVC_1, 0.25),
+ make_tuple("mpeg4", AVC_1, AAC_1, 0.75),
+ make_tuple("mpeg4", AMR_WB_1, AAC_1, 0.75),
+ make_tuple("mpeg4", HEVC_1, AMR_WB_1, 0.25),
+ make_tuple("mpeg4", H263_1, AMR_NB_1, 0.50),
+ make_tuple("mpeg4", MPEG4_1, AAC_1, 0.75),
+ make_tuple("mpeg4", AMR_NB_1, AMR_WB_1, 0.25),
+ make_tuple("mpeg4", H263_1, AMR_NB_1, 0.50),
+ make_tuple("mpeg4", MPEG4_1, HEVC_1, 0.75),
+
+ make_tuple("ogg", OPUS_1, UNUSED_ID, 1),
+
+ make_tuple("webm", OPUS_1, UNUSED_ID, 1),
+ make_tuple("webm", VORBIS_1, UNUSED_ID, 1),
+ make_tuple("webm", VP8_1, UNUSED_ID, 1),
+ make_tuple("webm", VP9_1, UNUSED_ID, 1),
+ make_tuple("webm", VP8_1, OPUS_1, 0.50),
+ make_tuple("webm", VORBIS_1, VP8_1, 0.25)));
int main(int argc, char **argv) {
gEnv = new WriterTestEnvironment();
diff --git a/media/libstagefright/tests/writer/WriterTestEnvironment.h b/media/libstagefright/tests/writer/WriterTestEnvironment.h
index 99e686f..7da0a62 100644
--- a/media/libstagefright/tests/writer/WriterTestEnvironment.h
+++ b/media/libstagefright/tests/writer/WriterTestEnvironment.h
@@ -25,7 +25,7 @@
class WriterTestEnvironment : public ::testing::Environment {
public:
- WriterTestEnvironment() : res("/data/local/tmp/") {}
+ WriterTestEnvironment() : res("/data/local/tmp/"), deleteOutput(true) {}
// Parses the command line arguments
int initFromOptions(int argc, char **argv);
@@ -34,16 +34,21 @@
const string getRes() const { return res; }
+ bool cleanUp() const { return deleteOutput; }
+
private:
string res;
+ bool deleteOutput;
};
int WriterTestEnvironment::initFromOptions(int argc, char **argv) {
- static struct option options[] = {{"res", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+ static struct option options[] = {{"res", required_argument, 0, 'P'},
+ {"cleanUp", optional_argument, 0, 'C'},
+ {0, 0, 0, 0}};
while (true) {
int index = 0;
- int c = getopt_long(argc, argv, "P:", options, &index);
+ int c = getopt_long(argc, argv, "P:C:", options, &index);
if (c == -1) {
break;
}
@@ -52,6 +57,11 @@
case 'P':
setRes(optarg);
break;
+ case 'C':
+ if (!strcmp(optarg, "false")) {
+ deleteOutput = false;
+ }
+ break;
default:
break;
}
@@ -62,7 +72,8 @@
"unrecognized option: %s\n\n"
"usage: %s <gtest options> <test options>\n\n"
"test options are:\n\n"
- "-P, --path: Resource files directory location\n",
+ "-P, --path: Resource files directory location\n"
+ "-C, default:true. Delete output file after test completes\n",
argv[optind ?: 1], argv[0]);
return 2;
}
diff --git a/media/libstagefright/tests/writer/WriterUtility.cpp b/media/libstagefright/tests/writer/WriterUtility.cpp
index f24ccb6..a3043fe 100644
--- a/media/libstagefright/tests/writer/WriterUtility.cpp
+++ b/media/libstagefright/tests/writer/WriterUtility.cpp
@@ -24,9 +24,16 @@
int32_t sendBuffersToWriter(ifstream &inputStream, vector<BufferInfo> &bufferInfo,
int32_t &inputFrameId, sp<MediaAdapter> ¤tTrack, int32_t offset,
- int32_t range, bool isPaused) {
+ int32_t range, bool isPaused, sp<WriterListener> listener) {
while (1) {
if (inputFrameId >= (int)bufferInfo.size() || inputFrameId >= (offset + range)) break;
+ if (listener != nullptr) {
+ if (listener->mSignaledDuration || listener->mSignaledSize) {
+ ALOGV("Max File limit reached. No more buffers will be sent to the writer");
+ break;
+ }
+ }
+
int32_t size = bufferInfo[inputFrameId].size;
char *data = (char *)malloc(size);
if (!data) {
diff --git a/media/libstagefright/tests/writer/WriterUtility.h b/media/libstagefright/tests/writer/WriterUtility.h
index cdd6246..5e79298 100644
--- a/media/libstagefright/tests/writer/WriterUtility.h
+++ b/media/libstagefright/tests/writer/WriterUtility.h
@@ -27,11 +27,11 @@
#include <media/stagefright/MediaAdapter.h>
-using namespace android;
-using namespace std;
+#include "WriterListener.h"
#define CODEC_CONFIG_FLAG 32
+constexpr uint32_t kMaxTrackCount = 2;
constexpr uint32_t kMaxCSDStrlen = 16;
constexpr uint32_t kMaxCount = 20;
@@ -43,7 +43,8 @@
int32_t sendBuffersToWriter(ifstream &inputStream, vector<BufferInfo> &bufferInfo,
int32_t &inputFrameId, sp<MediaAdapter> ¤tTrack, int32_t offset,
- int32_t range, bool isPaused = false);
+ int32_t range, bool isPaused = false,
+ sp<WriterListener> listener = nullptr);
int32_t writeHeaderBuffers(ifstream &inputStream, vector<BufferInfo> &bufferInfo,
int32_t &inputFrameId, sp<AMessage> &format, int32_t numCsds);
diff --git a/media/libstagefright/timedtext/TEST_MAPPING b/media/libstagefright/timedtext/TEST_MAPPING
new file mode 100644
index 0000000..185f824
--- /dev/null
+++ b/media/libstagefright/timedtext/TEST_MAPPING
@@ -0,0 +1,7 @@
+// mappings for frameworks/av/media/libstagefright/timedtext
+{
+ "presubmit": [
+ // TODO(b/148094059): unit tests not allowed to download content
+ //{ "name": "TimedTextUnitTest" }
+ ]
+}
diff --git a/media/libstagefright/timedtext/test/Android.bp b/media/libstagefright/timedtext/test/Android.bp
new file mode 100644
index 0000000..36f8891
--- /dev/null
+++ b/media/libstagefright/timedtext/test/Android.bp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+cc_test {
+ name: "TimedTextUnitTest",
+ gtest: true,
+
+ srcs: [
+ "TimedTextUnitTest.cpp",
+ ],
+
+ static_libs: [
+ "libstagefright_timedtext",
+ "libstagefright_foundation",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/libstagefright",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libmedia",
+ "libbinder",
+ ],
+
+ cflags: [
+ "-Wno-multichar",
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/timedtext/test/AndroidTest.xml b/media/libstagefright/timedtext/test/AndroidTest.xml
new file mode 100644
index 0000000..3654e23
--- /dev/null
+++ b/media/libstagefright/timedtext/test/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Test module config for TimedText unit test">
+ <option name="test-suite-tag" value="TimedTextUnitTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="TimedTextUnitTest->/data/local/tmp/TimedTextUnitTest" />
+ <option name="push-file"
+ key="https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/timedtext/test/TimedTextUnitTest.zip?unzip=true"
+ value="/data/local/tmp/TimedTextUnitTestRes/" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="TimedTextUnitTest" />
+ <option name="native-test-flag" value="-P /data/local/tmp/TimedTextUnitTestRes/" />
+ </test>
+</configuration>
diff --git a/media/libstagefright/timedtext/test/README.md b/media/libstagefright/timedtext/test/README.md
new file mode 100644
index 0000000..3a774bd
--- /dev/null
+++ b/media/libstagefright/timedtext/test/README.md
@@ -0,0 +1,40 @@
+## Media Testing ##
+---
+#### TimedText Unit Test :
+The TimedText Unit Test Suite validates the TextDescription class available in libstagefright.
+
+Run the following steps to build the test suite:
+```
+m TimedTextUnitTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/TimedTextUnitTest/TimedTextUnitTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/TimedTextUnitTest/TimedTextUnitTest /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/libstagefright/timedtext/test/TimedTextUnitTest.zip).
+Download, unzip and push these files into device for testing.
+
+```
+adb push TimedTextUnitTestRes/. /data/local/tmp/
+```
+
+usage: TimedTextUnitTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/TimedTextUnitTest -P /data/local/tmp/TimedTextUnitTestRes/
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest TimedTextUnitTest -- --enable-module-dynamic-download=true
+```
diff --git a/media/libstagefright/timedtext/test/TimedTextTestEnvironment.h b/media/libstagefright/timedtext/test/TimedTextTestEnvironment.h
new file mode 100644
index 0000000..52280c1
--- /dev/null
+++ b/media/libstagefright/timedtext/test/TimedTextTestEnvironment.h
@@ -0,0 +1,72 @@
+/*
+ * 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 __TIMEDTEXT_TEST_ENVIRONMENT_H__
+#define __TIMEDTEXT_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class TimedTextTestEnvironment : public ::testing::Environment {
+ public:
+ TimedTextTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int TimedTextTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"res", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P':
+ setRes(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __TIMEDTEXT_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/timedtext/test/TimedTextUnitTest.cpp b/media/libstagefright/timedtext/test/TimedTextUnitTest.cpp
new file mode 100644
index 0000000..d85ae39
--- /dev/null
+++ b/media/libstagefright/timedtext/test/TimedTextUnitTest.cpp
@@ -0,0 +1,379 @@
+/*
+ * 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 "TimedTextUnitTest"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fstream>
+
+#include <binder/Parcel.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/ByteUtils.h>
+
+#include "timedtext/TextDescriptions.h"
+
+#include "TimedTextTestEnvironment.h"
+
+constexpr int32_t kStartTimeMs = 10000;
+
+enum {
+ // These keys must be in sync with the keys in
+ // frameworks/av/media/libstagefright/timedtext/TextDescriptions.h
+ KEY_DISPLAY_FLAGS = 1,
+ KEY_STYLE_FLAGS = 2,
+ KEY_BACKGROUND_COLOR_RGBA = 3,
+ KEY_HIGHLIGHT_COLOR_RGBA = 4,
+ KEY_SCROLL_DELAY = 5,
+ KEY_WRAP_TEXT = 6,
+ KEY_START_TIME = 7,
+ KEY_STRUCT_BLINKING_TEXT_LIST = 8,
+ KEY_STRUCT_FONT_LIST = 9,
+ KEY_STRUCT_HIGHLIGHT_LIST = 10,
+ KEY_STRUCT_HYPER_TEXT_LIST = 11,
+ KEY_STRUCT_KARAOKE_LIST = 12,
+ KEY_STRUCT_STYLE_LIST = 13,
+ KEY_STRUCT_TEXT_POS = 14,
+ KEY_STRUCT_JUSTIFICATION = 15,
+ KEY_STRUCT_TEXT = 16,
+
+ KEY_GLOBAL_SETTING = 101,
+ KEY_LOCAL_SETTING = 102,
+ KEY_START_CHAR = 103,
+ KEY_END_CHAR = 104,
+ KEY_FONT_ID = 105,
+ KEY_FONT_SIZE = 106,
+ KEY_TEXT_COLOR_RGBA = 107,
+};
+
+struct FontInfo {
+ int32_t displayFlag = -1;
+ int32_t horizontalJustification = -1;
+ int32_t verticalJustification = -1;
+ int32_t rgbaBackground = -1;
+ int32_t leftPos = -1;
+ int32_t topPos = -1;
+ int32_t bottomPos = -1;
+ int32_t rightPos = -1;
+ int32_t startchar = -1;
+ int32_t endChar = -1;
+ int32_t fontId = -1;
+ int32_t faceStyle = -1;
+ int32_t fontSize = -1;
+ int32_t rgbaText = -1;
+ int32_t entryCount = -1;
+};
+
+struct FontRecord {
+ int32_t fontID = -1;
+ int32_t fontNameLength = -1;
+ const uint8_t *font = nullptr;
+};
+
+using namespace android;
+
+static TimedTextTestEnvironment *gEnv = nullptr;
+
+class TimedTextUnitTest : public ::testing::TestWithParam</*filename*/ string> {
+ public:
+ TimedTextUnitTest(){};
+
+ ~TimedTextUnitTest() {
+ if (mEleStream) mEleStream.close();
+ }
+
+ virtual void SetUp() override {
+ mInputFileName = gEnv->getRes() + GetParam();
+ mEleStream.open(mInputFileName, ifstream::binary);
+ ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open " << GetParam();
+
+ struct stat buf;
+ status_t status = stat(mInputFileName.c_str(), &buf);
+ ASSERT_EQ(status, 0) << "Failed to get properties of input file: " << GetParam();
+ mFileSize = buf.st_size;
+ ALOGI("Size of the input file %s = %zu", GetParam().c_str(), mFileSize);
+ }
+
+ string mInputFileName;
+ size_t mFileSize;
+ ifstream mEleStream;
+};
+
+class SRTDescriptionTest : public TimedTextUnitTest {
+ public:
+ virtual void SetUp() override { TimedTextUnitTest::SetUp(); }
+};
+
+class Text3GPPDescriptionTest : public TimedTextUnitTest {
+ public:
+ virtual void SetUp() override { TimedTextUnitTest::SetUp(); }
+};
+
+TEST_P(SRTDescriptionTest, extractSRTDescriptionTest) {
+ char data[mFileSize];
+ mEleStream.read(data, sizeof(data));
+ ASSERT_EQ(mEleStream.gcount(), mFileSize);
+
+ Parcel parcel;
+ int32_t flag = TextDescriptions::OUT_OF_BAND_TEXT_SRT | TextDescriptions::LOCAL_DESCRIPTIONS;
+ status_t status = TextDescriptions::getParcelOfDescriptions((const uint8_t *)data, mFileSize,
+ flag, kStartTimeMs, &parcel);
+ ASSERT_EQ(status, 0) << "getParcelOfDescriptions returned error";
+ ALOGI("Size of the Parcel: %zu", parcel.dataSize());
+ ASSERT_GT(parcel.dataSize(), 0) << "Parcel is empty";
+
+ parcel.setDataPosition(0);
+ int32_t key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_LOCAL_SETTING) << "Parcel has invalid key";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_START_TIME) << "Parcel has invalid start time key";
+ ASSERT_EQ(parcel.readInt32(), kStartTimeMs) << "Parcel has invalid timings";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_TEXT) << "Parcel has invalid struct text key";
+ ASSERT_EQ(parcel.readInt32(), mFileSize) << "Parcel has invalid text data";
+ int32_t fileSize = parcel.readInt32();
+ ASSERT_EQ(fileSize, mFileSize) << "Parcel has invalid file size value";
+ uint8_t tmpData[fileSize];
+ status = parcel.read((void *)tmpData, fileSize);
+ ASSERT_EQ(status, 0) << "Failed to read the data from parcel";
+ // To make sure end of parcel is reached
+ ASSERT_EQ(parcel.dataAvail(), 0) << "Parcel has some data left to read";
+}
+
+// This test uses the properties of tx3g box mentioned in 3GPP Timed Text Format
+// Specification#: 26.245 / Section: 5.16(Sample Description Format)
+// https://www.3gpp.org/ftp/Specs/archive/26_series/26.245/
+
+TEST_P(Text3GPPDescriptionTest, Text3GPPGlobalDescriptionTest) {
+ char data[mFileSize];
+ mEleStream.read(data, sizeof(data));
+ ASSERT_EQ(mEleStream.gcount(), mFileSize);
+
+ const uint8_t *tmpData = (const uint8_t *)data;
+ int32_t remaining = mFileSize;
+ FontInfo fontInfo;
+ vector<FontRecord> fontRecordEntries;
+
+ // Skipping the bytes containing information about the type of subbox(tx3g)
+ tmpData += 16;
+ remaining -= 16;
+
+ fontInfo.displayFlag = U32_AT(tmpData);
+ ALOGI("Display flag: %d", fontInfo.displayFlag);
+ fontInfo.horizontalJustification = tmpData[4];
+ ALOGI("Horizontal Justification: %d", fontInfo.horizontalJustification);
+ fontInfo.verticalJustification = tmpData[5];
+ ALOGI("Vertical Justification: %d", fontInfo.verticalJustification);
+ fontInfo.rgbaBackground =
+ *(tmpData + 6) << 24 | *(tmpData + 7) << 16 | *(tmpData + 8) << 8 | *(tmpData + 9);
+ ALOGI("rgba value of background: %d", fontInfo.rgbaBackground);
+
+ tmpData += 10;
+ remaining -= 10;
+
+ if (remaining >= 8) {
+ fontInfo.leftPos = U16_AT(tmpData);
+ ALOGI("Left: %d", fontInfo.leftPos);
+ fontInfo.topPos = U16_AT(tmpData + 2);
+ ALOGI("Top: %d", fontInfo.topPos);
+ fontInfo.bottomPos = U16_AT(tmpData + 4);
+ ALOGI("Bottom: %d", fontInfo.bottomPos);
+ fontInfo.rightPos = U16_AT(tmpData + 6);
+ ALOGI("Right: %d", fontInfo.rightPos);
+
+ tmpData += 8;
+ remaining -= 8;
+
+ if (remaining >= 12) {
+ fontInfo.startchar = U16_AT(tmpData);
+ ALOGI("Start character: %d", fontInfo.startchar);
+ fontInfo.endChar = U16_AT(tmpData + 2);
+ ALOGI("End character: %d", fontInfo.endChar);
+ fontInfo.fontId = U16_AT(tmpData + 4);
+ ALOGI("Value of font Identifier: %d", fontInfo.fontId);
+ fontInfo.faceStyle = *(tmpData + 6);
+ ALOGI("Face style flag : %d", fontInfo.faceStyle);
+ fontInfo.fontSize = *(tmpData + 7);
+ ALOGI("Size of the font: %d", fontInfo.fontSize);
+ fontInfo.rgbaText = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 | *(tmpData + 10) << 8 |
+ *(tmpData + 11);
+ ALOGI("rgba value of the text: %d", fontInfo.rgbaText);
+
+ tmpData += 12;
+ remaining -= 12;
+
+ if (remaining >= 10) {
+ // Skipping the bytes containing information about the type of subbox(ftab)
+ fontInfo.entryCount = U16_AT(tmpData + 8);
+ ALOGI("Value of entry count: %d", fontInfo.entryCount);
+
+ tmpData += 10;
+ remaining -= 10;
+
+ for (int32_t i = 0; i < fontInfo.entryCount; i++) {
+ if (remaining < 3) break;
+ int32_t tempFontID = U16_AT(tmpData);
+ ALOGI("Font Id: %d", tempFontID);
+ int32_t tempFontNameLength = *(tmpData + 2);
+ ALOGI("Length of font name: %d", tempFontNameLength);
+
+ tmpData += 3;
+ remaining -= 3;
+
+ if (remaining < tempFontNameLength) break;
+ const uint8_t *tmpFont = tmpData;
+ char *tmpFontName = strndup((const char *)tmpFont, tempFontNameLength);
+ ASSERT_NE(tmpFontName, nullptr) << "Font Name is null";
+ ALOGI("FontName = %s", tmpFontName);
+ free(tmpFontName);
+ tmpData += tempFontNameLength;
+ remaining -= tempFontNameLength;
+ fontRecordEntries.push_back({tempFontID, tempFontNameLength, tmpFont});
+ }
+ }
+ }
+ }
+
+ Parcel parcel;
+ int32_t flag = TextDescriptions::IN_BAND_TEXT_3GPP | TextDescriptions::GLOBAL_DESCRIPTIONS;
+ status_t status = TextDescriptions::getParcelOfDescriptions((const uint8_t *)data, mFileSize,
+ flag, kStartTimeMs, &parcel);
+ ASSERT_EQ(status, 0) << "getParcelOfDescriptions returned error";
+ ALOGI("Size of the Parcel: %zu", parcel.dataSize());
+ ASSERT_GT(parcel.dataSize(), 0) << "Parcel is empty";
+
+ parcel.setDataPosition(0);
+ int32_t key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_GLOBAL_SETTING) << "Parcel has invalid key";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_DISPLAY_FLAGS) << "Parcel has invalid DISPLAY FLAGS Key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.displayFlag)
+ << "Parcel has invalid value of display flag";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_JUSTIFICATION) << "Parcel has invalid STRUCT JUSTIFICATION key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.horizontalJustification)
+ << "Parcel has invalid value of Horizontal justification";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.verticalJustification)
+ << "Parcel has invalid value of Vertical justification";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_BACKGROUND_COLOR_RGBA) << "Parcel has invalid BACKGROUND COLOR key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.rgbaBackground)
+ << "Parcel has invalid rgba background color value";
+
+ if (parcel.dataAvail() == 0) {
+ ALOGV("Completed reading the parcel");
+ return;
+ }
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_TEXT_POS) << "Parcel has invalid STRUCT TEXT POSITION key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.leftPos)
+ << "Parcel has invalid rgba background color value";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.topPos)
+ << "Parcel has invalid rgba background color value";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.bottomPos)
+ << "Parcel has invalid rgba background color value";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.rightPos)
+ << "Parcel has invalid rgba background color value";
+
+ if (parcel.dataAvail() == 0) {
+ ALOGV("Completed reading the parcel");
+ return;
+ }
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_STYLE_LIST) << "Parcel has invalid STRUCT STYLE LIST key";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_START_CHAR) << "Parcel has invalid START CHAR key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.startchar)
+ << "Parcel has invalid value of start character";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_END_CHAR) << "Parcel has invalid END CHAR key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.endChar) << "Parcel has invalid value of end character";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_FONT_ID) << "Parcel has invalid FONT ID key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.fontId) << "Parcel has invalid value of font Id";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STYLE_FLAGS) << "Parcel has invalid STYLE FLAGS key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.faceStyle) << "Parcel has invalid value of style flags";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_FONT_SIZE) << "Parcel has invalid FONT SIZE key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.fontSize) << "Parcel has invalid value of font size";
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_TEXT_COLOR_RGBA) << "Parcel has invalid TEXT COLOR RGBA key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.rgbaText) << "Parcel has invalid rgba text color value";
+
+ if (parcel.dataAvail() == 0) {
+ ALOGV("Completed reading the parcel");
+ return;
+ }
+
+ key = parcel.readInt32();
+ ASSERT_EQ(key, KEY_STRUCT_FONT_LIST) << "Parcel has invalid STRUCT FONT LIST key";
+ ASSERT_EQ(parcel.readInt32(), fontInfo.entryCount) << "Parcel has invalid value of entry count";
+ ASSERT_EQ(fontInfo.entryCount, fontRecordEntries.size())
+ << "Array size does not match expected number of entries";
+ for (int32_t i = 0; i < fontInfo.entryCount; i++) {
+ ASSERT_EQ(parcel.readInt32(), fontRecordEntries[i].fontID)
+ << "Parcel has invalid value of font Id";
+ ASSERT_EQ(parcel.readInt32(), fontRecordEntries[i].fontNameLength)
+ << "Parcel has invalid value of font name length";
+ uint8_t fontName[fontRecordEntries[i].fontNameLength];
+ // written with writeByteArray() writes count, then the actual data
+ ASSERT_EQ(parcel.readInt32(), fontRecordEntries[i].fontNameLength);
+ status = parcel.read((void *)fontName, fontRecordEntries[i].fontNameLength);
+ ASSERT_EQ(status, 0) << "Failed to read the font name from parcel";
+ ASSERT_EQ(memcmp(fontName, fontRecordEntries[i].font, fontRecordEntries[i].fontNameLength),
+ 0)
+ << "Parcel has invalid font";
+ }
+ // To make sure end of parcel is reached
+ ASSERT_EQ(parcel.dataAvail(), 0) << "Parcel has some data left to read";
+}
+
+INSTANTIATE_TEST_SUITE_P(TimedTextUnitTestAll, SRTDescriptionTest,
+ ::testing::Values(("sampleTest1.srt"),
+ ("sampleTest2.srt")));
+
+INSTANTIATE_TEST_SUITE_P(TimedTextUnitTestAll, Text3GPPDescriptionTest,
+ ::testing::Values(("tx3gBox1"),
+ ("tx3gBox2")));
+
+int main(int argc, char **argv) {
+ gEnv = new TimedTextTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/xmlparser/MediaCodecsXmlParser.cpp b/media/libstagefright/xmlparser/MediaCodecsXmlParser.cpp
index a232150..3be5e74 100644
--- a/media/libstagefright/xmlparser/MediaCodecsXmlParser.cpp
+++ b/media/libstagefright/xmlparser/MediaCodecsXmlParser.cpp
@@ -1525,7 +1525,7 @@
nodeInfo.attributeList.push_back(Attribute{"rank", rank});
}
nodeList->insert(std::make_pair(
- std::move(order), std::move(nodeInfo)));
+ order, std::move(nodeInfo)));
}
}
}
diff --git a/media/libstagefright/xmlparser/TEST_MAPPING b/media/libstagefright/xmlparser/TEST_MAPPING
new file mode 100644
index 0000000..8626d72
--- /dev/null
+++ b/media/libstagefright/xmlparser/TEST_MAPPING
@@ -0,0 +1,6 @@
+// test mapping for frameworks/av/media/libstagefright/xmlparser
+{
+ "presubmit": [
+ { "name": "XMLParserTest" }
+ ]
+}
diff --git a/media/libstagefright/xmlparser/test/Android.bp b/media/libstagefright/xmlparser/test/Android.bp
new file mode 100644
index 0000000..ba02f84
--- /dev/null
+++ b/media/libstagefright/xmlparser/test/Android.bp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+cc_test {
+ name: "XMLParserTest",
+ test_suites: ["device-tests"],
+ gtest: true,
+
+ srcs: [
+ "XMLParserTest.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libstagefright_xmlparser",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ data: [":xmlparsertest_test_files",],
+
+ sanitize: {
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ },
+}
+
+filegroup {
+ name: "xmlparsertest_test_files",
+ srcs: [
+ "testdata/media_codecs_unit_test.xml",
+ "testdata/media_codecs_unit_test_caller.xml",
+ ],
+}
diff --git a/media/libstagefright/xmlparser/test/AndroidTest.xml b/media/libstagefright/xmlparser/test/AndroidTest.xml
new file mode 100644
index 0000000..2e11b1b
--- /dev/null
+++ b/media/libstagefright/xmlparser/test/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Test module config for xml parser unit test">
+ <option name="test-suite-tag" value="XMLParserTest" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="media_codecs_unit_test.xml->/data/local/tmp/media_codecs_unit_test.xml" />
+ <option name="push" value="media_codecs_unit_test_caller.xml->/data/local/tmp/media_codecs_unit_test_caller.xml" />
+ <option name="push" value="XMLParserTest->/data/local/tmp/XMLParserTest" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="XMLParserTest" />
+ </test>
+</configuration>
diff --git a/media/libstagefright/xmlparser/test/README.md b/media/libstagefright/xmlparser/test/README.md
new file mode 100644
index 0000000..e9363fd
--- /dev/null
+++ b/media/libstagefright/xmlparser/test/README.md
@@ -0,0 +1,33 @@
+## Media Testing ##
+---
+#### XML Parser
+The XMLParser Test Suite validates the XMLParser available in libstagefright.
+
+Run the following steps to build the test suite:
+```
+m XMLParserTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/XMLParserTest/XMLParserTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/XMLParserTest/XMLParserTest /data/local/tmp/
+```
+
+usage: XMLParserTest
+```
+adb shell /data/local/tmp/XMLParserTest
+```
+Alternatively, the test can also be run using atest command.
+
+```
+atest XMLParserTest
+```
diff --git a/media/libstagefright/xmlparser/test/XMLParserTest.cpp b/media/libstagefright/xmlparser/test/XMLParserTest.cpp
new file mode 100644
index 0000000..9ddd374
--- /dev/null
+++ b/media/libstagefright/xmlparser/test/XMLParserTest.cpp
@@ -0,0 +1,392 @@
+/*
+ * 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 "XMLParserTest"
+
+#include <utils/Log.h>
+
+#include <fstream>
+
+#include <media/stagefright/xmlparser/MediaCodecsXmlParser.h>
+
+#include "XMLParserTestEnvironment.h"
+
+#define XML_FILE_NAME "media_codecs_unit_test_caller.xml"
+
+using namespace android;
+
+static XMLParserTestEnvironment *gEnv = nullptr;
+
+struct CodecProperties {
+ string codecName;
+ MediaCodecsXmlParser::CodecProperties codecProp;
+};
+
+struct RoleProperties {
+ string roleName;
+ string typeName;
+ string codecName;
+ bool isEncoder;
+ size_t order;
+ vector<pair<string, string>> attributeMap;
+};
+
+class XMLParseTest : public ::testing::Test {
+ public:
+ ~XMLParseTest() {
+ if (mEleStream.is_open()) mEleStream.close();
+ mInputDataVector.clear();
+ mInputRoleVector.clear();
+ }
+
+ virtual void SetUp() override { setUpDatabase(); }
+
+ void setUpDatabase();
+
+ void setCodecProperties(string codecName, bool isEncoder, int32_t order, set<string> quirkSet,
+ set<string> domainSet, set<string> variantSet, string typeName,
+ vector<pair<string, string>> domain, vector<string> aliases,
+ string rank);
+
+ void setRoleProperties(string roleName, bool isEncoder, int32_t order, string typeName,
+ string codecName, vector<pair<string, string>> domain);
+
+ void setServiceAttribute(map<string, string> serviceAttributeNameValuePair);
+
+ void printCodecMap(const MediaCodecsXmlParser::Codec mcodec);
+
+ void checkRoleMap(int32_t index, bool isEncoder, string typeName, string codecName,
+ vector<pair<string, string>> attrMap);
+
+ bool compareMap(const map<string, string> &lhs, const map<string, string> &rhs);
+
+ ifstream mEleStream;
+ MediaCodecsXmlParser mParser;
+ vector<CodecProperties> mInputDataVector;
+ vector<RoleProperties> mInputRoleVector;
+ map<string, string> mInputServiceAttributeMap;
+};
+
+void XMLParseTest::setUpDatabase() {
+ // The values set below are specific to test vector testdata/media_codecs_unit_test.xml
+ setCodecProperties("test1.decoder", false, 1, {"attribute::disabled", "quirk::quirk1"},
+ {"telephony"}, {}, "audio/mpeg", {}, {"alias1.decoder"}, "4");
+
+ setCodecProperties("test2.decoder", false, 2, {"quirk::quirk1"}, {}, {}, "audio/3gpp", {}, {},
+ "");
+
+ setCodecProperties("test3.decoder", false, 3, {}, {}, {}, "audio/amr-wb",
+ {
+ pair<string, string>("feature-feature1", "feature1Val"),
+ pair<string, string>("feature-feature2", "0"),
+ pair<string, string>("feature-feature3", "0"),
+ },
+ {}, "");
+
+ setCodecProperties("test4.decoder", false, 4, {}, {}, {}, "audio/flac",
+ {pair<string, string>("feature-feature1", "feature1Val")}, {}, "");
+
+ setCodecProperties("test5.decoder", false, 5, {"attribute::attributeQuirk1"}, {}, {},
+ "audio/g711-mlaw", {}, {}, "");
+
+ setCodecProperties("test6.decoder", false, 6, {}, {}, {"variant1", "variant2"},
+ "audio/mp4a-latm",
+ {pair<string, string>("variant1:::variant1Limit1-range",
+ "variant1Limit1Min-variant1Limit1Max"),
+ pair<string, string>("variant1:::variant1Limit2-range",
+ "variant1Limit2Low-variant1Limit2High"),
+ pair<string, string>("variant2:::variant2Limit1", "variant2Limit1Value")},
+ {}, "");
+
+ setCodecProperties(
+ "test7.decoder", false, 7, {}, {}, {}, "audio/vorbis",
+ {
+ pair<string, string>("-min-limit1", "limit1Min"),
+ /*pair<string, string>("limit1-in", "limit1In"),*/
+ pair<string, string>("limit2-range", "limit2Min-limit2Max"),
+ pair<string, string>("limit2-scale", "limit2Scale"),
+ pair<string, string>("limit3-default", "limit3Val3"),
+ pair<string, string>("limit3-ranges", "limit3Val1,limit3Val2,limit3Val3"),
+ },
+ {}, "");
+
+ setCodecProperties("test8.encoder", true, 8, {}, {}, {}, "audio/opus",
+ {pair<string, string>("max-limit1", "limit1Max")}, {}, "");
+
+ setRoleProperties("audio_decoder.mp3", false, 1, "audio/mpeg", "test1.decoder",
+ {pair<string, string>("attribute::disabled", "present"),
+ pair<string, string>("rank", "4")});
+
+ setRoleProperties("audio_decoder.amrnb", false, 2, "audio/3gpp", "test2.decoder", {});
+
+ setRoleProperties("audio_decoder.amrwb", false, 3, "audio/amr-wb", "test3.decoder",
+ {pair<string, string>("feature-feature1", "feature1Val"),
+ pair<string, string>("feature-feature2", "0"),
+ pair<string, string>("feature-feature3", "0")});
+
+ setRoleProperties("audio/flac", false, 4, "audio/flac", "test4.decoder",
+ {pair<string, string>("feature-feature1", "feature1Val")});
+
+ setRoleProperties("audio_decoder.g711mlaw", false, 5, "audio/g711-mlaw", "test5.decoder",
+ {pair<string, string>("attribute::attributeQuirk1", "present")});
+
+ setRoleProperties("audio_decoder.aac", false, 6, "audio/mp4a-latm", "test6.decoder",
+ {pair<string, string>("variant1:::variant1Limit1-range",
+ "variant1Limit1Min-variant1Limit1Max"),
+ pair<string, string>("variant1:::variant1Limit2-range",
+ "variant1Limit2Low-variant1Limit2High"),
+ pair<string, string>("variant2:::variant2Limit1", "variant2Limit1Value")});
+
+ setRoleProperties("audio_decoder.vorbis", false, 7, "audio/vorbis", "test7.decoder",
+ {pair<string, string>("-min-limit1", "limit1Min"),
+ /*pair<string, string>("limit1-in", "limit1In"),*/
+ pair<string, string>("limit2-range", "limit2Min-limit2Max"),
+ pair<string, string>("limit2-scale", "limit2Scale"),
+ pair<string, string>("limit3-default", "limit3Val3"),
+ pair<string, string>("limit3-ranges", "limit3Val1,limit3Val2,limit3Val3")});
+
+ setRoleProperties("audio_encoder.opus", true, 8, "audio/opus", "test8.encoder",
+ {pair<string, string>("max-limit1", "limit1Max")});
+
+ setServiceAttribute(
+ {pair<string, string>("domain-telephony", "0"), pair<string, string>("domain-tv", "0"),
+ pair<string, string>("setting2", "0"), pair<string, string>("variant-variant1", "0")});
+}
+
+bool XMLParseTest::compareMap(const map<string, string> &lhs, const map<string, string> &rhs) {
+ return lhs.size() == rhs.size() && equal(lhs.begin(), lhs.end(), rhs.begin());
+}
+
+void XMLParseTest::setCodecProperties(string codecName, bool isEncoder, int32_t order,
+ set<string> quirkSet, set<string> domainSet,
+ set<string> variantSet, string typeName,
+ vector<pair<string, string>> domain, vector<string> aliases,
+ string rank) {
+ map<string, string> AttributeMapDB;
+ for (const auto &AttrStr : domain) {
+ AttributeMapDB.insert(AttrStr);
+ }
+ map<string, MediaCodecsXmlParser::AttributeMap> TypeMapDataBase;
+ TypeMapDataBase.insert(
+ pair<string, MediaCodecsXmlParser::AttributeMap>(typeName, AttributeMapDB));
+ CodecProperties codecProperty;
+ codecProperty.codecName = codecName;
+ codecProperty.codecProp.isEncoder = isEncoder;
+ codecProperty.codecProp.order = order;
+ codecProperty.codecProp.quirkSet = quirkSet;
+ codecProperty.codecProp.domainSet = domainSet;
+ codecProperty.codecProp.variantSet = variantSet;
+ codecProperty.codecProp.typeMap = TypeMapDataBase;
+ codecProperty.codecProp.aliases = aliases;
+ codecProperty.codecProp.rank = rank;
+ mInputDataVector.push_back(codecProperty);
+}
+
+void XMLParseTest::setRoleProperties(string roleName, bool isEncoder, int32_t order,
+ string typeName, string codecName,
+ vector<pair<string, string>> attributeNameValuePair) {
+ struct RoleProperties roleProperty;
+ roleProperty.roleName = roleName;
+ roleProperty.typeName = typeName;
+ roleProperty.codecName = codecName;
+ roleProperty.isEncoder = isEncoder;
+ roleProperty.order = order;
+ roleProperty.attributeMap = attributeNameValuePair;
+ mInputRoleVector.push_back(roleProperty);
+}
+
+void XMLParseTest::setServiceAttribute(map<string, string> serviceAttributeNameValuePair) {
+ for (const auto &serviceAttrStr : serviceAttributeNameValuePair) {
+ mInputServiceAttributeMap.insert(serviceAttrStr);
+ }
+}
+
+void XMLParseTest::printCodecMap(const MediaCodecsXmlParser::Codec mcodec) {
+ const string &name = mcodec.first;
+ ALOGV("codec name = %s\n", name.c_str());
+ const MediaCodecsXmlParser::CodecProperties &properties = mcodec.second;
+ bool isEncoder = properties.isEncoder;
+ ALOGV("isEncoder = %d\n", isEncoder);
+ size_t order = properties.order;
+ ALOGV("order = %zu\n", order);
+ string rank = properties.rank;
+ ALOGV("rank = %s\n", rank.c_str());
+
+ for (auto &itrQuirkSet : properties.quirkSet) {
+ ALOGV("quirkSet= %s", itrQuirkSet.c_str());
+ }
+
+ for (auto &itrDomainSet : properties.domainSet) {
+ ALOGV("domainSet= %s", itrDomainSet.c_str());
+ }
+
+ for (auto &itrVariantSet : properties.variantSet) {
+ ALOGV("variantSet= %s", itrVariantSet.c_str());
+ }
+
+ map<string, MediaCodecsXmlParser::AttributeMap> TypeMap = properties.typeMap;
+ ALOGV("The TypeMap is :");
+
+ for (auto &itrTypeMap : TypeMap) {
+ ALOGV("itrTypeMap->first\t%s\t", itrTypeMap.first.c_str());
+
+ for (auto &itrAttributeMap : itrTypeMap.second) {
+ ALOGV("AttributeMap->first = %s", itrAttributeMap.first.c_str());
+ ALOGV("AttributeMap->second = %s", itrAttributeMap.second.c_str());
+ }
+ }
+}
+
+void XMLParseTest::checkRoleMap(int32_t index, bool isEncoder, string typeName, string codecName,
+ vector<pair<string, string>> AttributePairMap) {
+ ASSERT_EQ(isEncoder, mInputRoleVector.at(index).isEncoder)
+ << "Invalid RoleMap data. IsEncoder mismatch";
+ ASSERT_EQ(typeName, mInputRoleVector.at(index).typeName)
+ << "Invalid RoleMap data. typeName mismatch";
+ ASSERT_EQ(codecName, mInputRoleVector.at(index).codecName)
+ << "Invalid RoleMap data. codecName mismatch";
+
+ vector<pair<string, string>>::iterator itr_attributeMapDB =
+ (mInputRoleVector.at(index).attributeMap).begin();
+ vector<pair<string, string>>::iterator itr_attributeMap = AttributePairMap.begin();
+ for (; itr_attributeMap != AttributePairMap.end() &&
+ itr_attributeMapDB != mInputRoleVector.at(index).attributeMap.end();
+ ++itr_attributeMap, ++itr_attributeMapDB) {
+ string attributeName = itr_attributeMap->first;
+ string attributeNameDB = itr_attributeMapDB->first;
+ string attributevalue = itr_attributeMap->second;
+ string attributeValueDB = itr_attributeMapDB->second;
+ ASSERT_EQ(attributeName, attributeNameDB)
+ << "Invalid RoleMap data. Attribute name mismatch\t" << attributeName << " != "
+ << "attributeNameDB";
+ ASSERT_EQ(attributevalue, attributeValueDB)
+ << "Invalid RoleMap data. Attribute value mismatch\t" << attributevalue << " != "
+ << "attributeValueDB";
+ }
+}
+
+TEST_F(XMLParseTest, CodecMapParseTest) {
+ string inputFileName = gEnv->getRes() + XML_FILE_NAME;
+ mEleStream.open(inputFileName, ifstream::binary);
+ ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open inputfile " << inputFileName;
+
+ mParser.parseXmlPath(inputFileName);
+ for (const MediaCodecsXmlParser::Codec &mcodec : mParser.getCodecMap()) {
+ printCodecMap(mcodec);
+ const MediaCodecsXmlParser::CodecProperties &properties = mcodec.second;
+ int32_t index = properties.order - 1;
+ ASSERT_GE(index, 0) << "Invalid order";
+ ASSERT_EQ(mInputDataVector.at(index).codecName, mcodec.first.c_str())
+ << "Invalid CodecMap data. codecName mismatch";
+ ASSERT_EQ(properties.isEncoder, mInputDataVector.at(index).codecProp.isEncoder)
+ << "Invalid CodecMap data. isEncoder mismatch";
+ ASSERT_EQ(properties.order, mInputDataVector.at(index).codecProp.order)
+ << "Invalid CodecMap data. order mismatch";
+
+ set<string> quirkSetDB = mInputDataVector.at(index).codecProp.quirkSet;
+ set<string> quirkSet = properties.quirkSet;
+ set<string> quirkDifference;
+ set_difference(quirkSetDB.begin(), quirkSetDB.end(), quirkSet.begin(), quirkSet.end(),
+ inserter(quirkDifference, quirkDifference.end()));
+ ASSERT_EQ(quirkDifference.size(), 0) << "CodecMap:quirk mismatch";
+
+ map<string, MediaCodecsXmlParser::AttributeMap> TypeMapDB =
+ mInputDataVector.at(index).codecProp.typeMap;
+ map<string, MediaCodecsXmlParser::AttributeMap> TypeMap = properties.typeMap;
+ map<string, MediaCodecsXmlParser::AttributeMap>::iterator itr_TypeMapDB = TypeMapDB.begin();
+ map<string, MediaCodecsXmlParser::AttributeMap>::iterator itr_TypeMap = TypeMap.begin();
+
+ ASSERT_EQ(TypeMapDB.size(), TypeMap.size())
+ << "Invalid CodecMap data. Typemap size mismatch";
+
+ for (; itr_TypeMap != TypeMap.end() && itr_TypeMapDB != TypeMapDB.end();
+ ++itr_TypeMap, ++itr_TypeMapDB) {
+ ASSERT_EQ(itr_TypeMap->first, itr_TypeMapDB->first)
+ << "Invalid CodecMap data. type mismatch";
+ bool flag = compareMap(itr_TypeMap->second, itr_TypeMapDB->second);
+ ASSERT_TRUE(flag) << "typeMap mismatch";
+ }
+ ASSERT_EQ(mInputDataVector.at(index).codecProp.rank, properties.rank)
+ << "Invalid CodecMap data. rank mismatch";
+ }
+}
+
+TEST_F(XMLParseTest, RoleMapParseTest) {
+ string inputFileName = gEnv->getRes() + XML_FILE_NAME;
+ mEleStream.open(inputFileName, ifstream::binary);
+ ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open inputfile " << inputFileName;
+
+ mParser.parseXmlPath(inputFileName);
+
+ for (auto &mRole : mParser.getRoleMap()) {
+ typedef pair<string, string> Attribute;
+ const string &roleName = mRole.first;
+ ALOGV("Role map:name = %s\n", roleName.c_str());
+ const MediaCodecsXmlParser::RoleProperties &properties = mRole.second;
+ string type = properties.type;
+ ALOGV("Role map: type = %s\n", type.c_str());
+
+ bool isEncoder = properties.isEncoder;
+ ALOGV("Role map: isEncoder = %d\n", isEncoder);
+
+ multimap<size_t, MediaCodecsXmlParser::NodeInfo> nodeList = properties.nodeList;
+ multimap<size_t, MediaCodecsXmlParser::NodeInfo>::iterator itr_Node;
+ ALOGV("\nThe multimap nodeList is : \n");
+ for (itr_Node = nodeList.begin(); itr_Node != nodeList.end(); ++itr_Node) {
+ ALOGV("itr_Node->first=ORDER=\t%zu\t", itr_Node->first);
+ int32_t index = itr_Node->first - 1;
+ MediaCodecsXmlParser::NodeInfo nodePtr = itr_Node->second;
+ ALOGV("Role map:itr_Node->second.name = %s\n", nodePtr.name.c_str());
+ vector<Attribute> attrList = nodePtr.attributeList;
+ for (auto attrNameValueList = attrList.begin(); attrNameValueList != attrList.end();
+ ++attrNameValueList) {
+ ALOGV("Role map:nodePtr.attributeList->first = %s\n",
+ attrNameValueList->first.c_str());
+ ALOGV("Role map:nodePtr.attributeList->second = %s\n",
+ attrNameValueList->second.c_str());
+ }
+ checkRoleMap(index, isEncoder, properties.type, nodePtr.name.c_str(), attrList);
+ }
+ }
+}
+
+TEST_F(XMLParseTest, ServiceAttributeMapParseTest) {
+ string inputFileName = gEnv->getRes() + XML_FILE_NAME;
+ mEleStream.open(inputFileName, ifstream::binary);
+ ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open inputfile " << inputFileName;
+
+ mParser.parseXmlPath(inputFileName);
+ const auto serviceAttributeMap = mParser.getServiceAttributeMap();
+ for (const auto &attributePair : serviceAttributeMap) {
+ ALOGV("serviceAttribute.key = %s \t serviceAttribute.value = %s",
+ attributePair.first.c_str(), attributePair.second.c_str());
+ }
+ bool flag = compareMap(mInputServiceAttributeMap, serviceAttributeMap);
+ ASSERT_TRUE(flag) << "ServiceMapParseTest: typeMap mismatch";
+}
+
+int main(int argc, char **argv) {
+ gEnv = new XMLParserTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGD("XML Parser Test Result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/xmlparser/test/XMLParserTestEnvironment.h b/media/libstagefright/xmlparser/test/XMLParserTestEnvironment.h
new file mode 100644
index 0000000..61a09e6
--- /dev/null
+++ b/media/libstagefright/xmlparser/test/XMLParserTestEnvironment.h
@@ -0,0 +1,73 @@
+/*
+ * 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 __XML_PARSER_TEST_ENVIRONMENT_H__
+#define __XML_PARSER_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class XMLParserTestEnvironment : public ::testing::Environment {
+ public:
+ XMLParserTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int XMLParserTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"path", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P': {
+ setRes(optarg);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __XML_PARSER_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test.xml b/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test.xml
new file mode 100644
index 0000000..a7299d3
--- /dev/null
+++ b/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- 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.
+-->
+
+<!-- REFERENCE : frameworks/av/media/libstagefright/xmlparser/media_codecs.xsd -->
+<Included>
+ <Settings>
+ <Domain name="telephony" enabled="false" />
+ <Domain name="tv" enabled="false" />
+ <Variant name="variant1" enabled="false" />
+ <Setting name="setting1" value="settingValue1" update="true" />
+ <Setting name="setting2" enabled="false" />
+ </Settings>
+ <Decoders>
+ <!-- entry for enabled, domain, rank and update properties -->
+ <MediaCodec name="test1.decoder" type="audio/mpeg" update="false" domain="telephony" enabled="false" rank="4">
+ <Alias name="alias1.decoder" />
+ <Quirk name="quirk1" value="quirk1Value"/>
+ </MediaCodec>
+ <!-- entry for testing Quirk -->
+ <MediaCodec name="test2.decoder" type="audio/3gpp" enabled="true" >
+ <Quirk name="quirk1" value="quirk1Value"/>
+ </MediaCodec>
+ <!-- entry for testing Feature -->
+ <!-- feature2 takes value 0 (feature with same name takes lower feature's value) -->
+ <!-- feature3 gives value as 0 since it's optional -->
+ <!-- optional="true" required="true" is not a valid combination. -->
+ <!-- optional="false" required="false" is not a valid combination. -->
+ <MediaCodec name="test3.decoder" type="audio/amr-wb" >
+ <Feature name="feature1" value="feature1Val" />
+ <Feature name="feature2" value="feature2Val"/>
+ <Feature name="feature2" />
+ <Feature name="feature3" optional="true" required="false" />
+ </MediaCodec>
+ <!-- entry for testing Type -->
+ <MediaCodec name="test4.decoder">
+ <Type name="audio/flac">
+ <Feature name="feature1" value="feature1Val" />
+ </Type>
+ </MediaCodec>
+ <!-- entry for testing Attribute -->
+ <MediaCodec name="test5.decoder" type="audio/g711-mlaw" >
+ <Attribute name="attributeQuirk1" />
+ </MediaCodec>
+ <!-- entry for testing Variant -->
+ <MediaCodec name="test6.decoder" type="audio/mp4a-latm" variant="variant1,variant2" >
+ <Variant name="variant1">
+ <Limit name="variant1Limit1" min="variant1Limit1Min" max="variant1Limit1Max" />
+ <Limit name="variant1Limit2" range="variant1Limit2Low-variant1Limit2High" />
+ </Variant>
+ <Variant name="variant2">
+ <Limit name="variant2Limit1" value="variant2Limit1Value" />
+ </Variant>
+ </MediaCodec>
+ <!-- entry for testing Limit -->
+ <!-- 'in' is present in xsd file but not handled in MediaCodecsXmlParser -->
+ <MediaCodec name="test7.decoder" type="audio/vorbis" >
+ <Limit name="limit1" in="limit1In" min="limit1Min"/>
+ <Limit name="limit2" min="limit2Min" max="limit2Max" scale="limit2Scale" />
+ <Limit name="limit3" ranges="limit3Val1,limit3Val2,limit3Val3" default="limit3Val3" />
+ </MediaCodec>
+ </Decoders>
+ <Encoders>
+ <MediaCodec name="test8.encoder" type="audio/opus">
+ <Limit name="limit1" max="limit1Max" />
+ </MediaCodec>
+ </Encoders>
+</Included>
diff --git a/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test_caller.xml b/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test_caller.xml
new file mode 100644
index 0000000..d864ce9
--- /dev/null
+++ b/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test_caller.xml
@@ -0,0 +1,4 @@
+<!-- entry for testing Include -->
+<MediaCodecs>
+ <Include href="media_codecs_unit_test.xml" />
+</MediaCodecs>
diff --git a/media/libwatchdog/Android.bp b/media/libwatchdog/Android.bp
index 2aefa7d..f7f0db7 100644
--- a/media/libwatchdog/Android.bp
+++ b/media/libwatchdog/Android.bp
@@ -14,6 +14,7 @@
cc_library {
name: "libwatchdog",
+ host_supported: true,
srcs: [
"Watchdog.cpp",
],
@@ -29,6 +30,12 @@
darwin: {
enabled: false,
},
+ linux_glibc: {
+ cflags: [
+ "-Dsigev_notify_thread_id=_sigev_un._tid",
+ ],
+ },
},
apex_available: ["com.android.media"],
+ min_sdk_version: "29",
}
diff --git a/media/mediaserver/Android.bp b/media/mediaserver/Android.bp
index a968890..8d5c77f 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: [
@@ -33,8 +34,10 @@
"frameworks/av/services/mediaresourcemanager",
],
- // back to 32-bit, b/126502613
- compile_multilib: "32",
+ // mediaserver has only been verified on 32-bit, see b/126502613
+ // use "prefer32" to *only* enable 64-bit builds on 64-bit-only lunch
+ // targets, which allows them to reach 'boot_complete'.
+ compile_multilib: "prefer32",
init_rc: ["mediaserver.rc"],
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/mtp/MtpFfsHandle.cpp b/media/mtp/MtpFfsHandle.cpp
index bd6a6c6..c8b4a03 100644
--- a/media/mtp/MtpFfsHandle.cpp
+++ b/media/mtp/MtpFfsHandle.cpp
@@ -114,11 +114,11 @@
void MtpFfsHandle::advise(int fd) {
for (unsigned i = 0; i < NUM_IO_BUFS; i++) {
if (posix_madvise(mIobuf[i].bufs.data(), MAX_FILE_CHUNK_SIZE,
- POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED) < 0)
+ POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED) != 0)
PLOG(ERROR) << "Failed to madvise";
}
if (posix_fadvise(fd, 0, 0,
- POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED) < 0)
+ POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED) != 0)
PLOG(ERROR) << "Failed to fadvise";
}
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index a04a962..d0e0cc7 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -35,7 +35,21 @@
cc_library_headers {
name: "media_ndk_headers",
vendor_available: true,
- export_include_dirs: ["include"]
+ // TODO(b/153609531): remove when no longer needed.
+ native_bridge_supported: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
+ export_include_dirs: ["include"],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
cc_library_shared {
@@ -71,6 +85,7 @@
],
header_libs: [
+ "jni_headers",
"libmediadrm_headers",
"libmediametrics_headers",
],
@@ -98,6 +113,8 @@
"libnativehelper",
],
+ export_header_lib_headers: ["jni_headers"],
+
export_include_dirs: ["include"],
export_shared_lib_headers: [
@@ -164,6 +181,7 @@
cc_test {
name: "AImageReaderWindowHandleTest",
+ test_suites: ["device-tests"],
srcs: ["tests/AImageReaderWindowHandleTest.cpp"],
shared_libs: [
"libbinder",
@@ -187,3 +205,41 @@
"frameworks/av/media/ndk/",
],
}
+
+cc_library_static {
+ name: "libmediandk_format",
+
+ host_supported: true,
+
+ srcs: [
+ "NdkMediaFormat.cpp",
+ ],
+
+ header_libs: [
+ "libstagefright_foundation_headers",
+ ],
+
+ cflags: [
+ "-DEXPORT=__attribute__((visibility(\"default\")))",
+ "-Werror",
+ "-Wall",
+ ],
+
+ export_include_dirs: ["include"],
+
+ sanitize: {
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ },
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
+ apex_available: ["com.android.media"],
+}
diff --git a/media/ndk/NdkImagePriv.h b/media/ndk/NdkImagePriv.h
index 0e8cbcb..b019448 100644
--- a/media/ndk/NdkImagePriv.h
+++ b/media/ndk/NdkImagePriv.h
@@ -30,6 +30,18 @@
using namespace android;
+// Formats not listed in the public API, but still available to AImageReader
+enum AIMAGE_PRIVATE_FORMATS {
+ /**
+ * Unprocessed implementation-dependent raw
+ * depth measurements, opaque with 16 bit
+ * samples.
+ *
+ */
+
+ AIMAGE_FORMAT_RAW_DEPTH = 0x1002,
+};
+
// TODO: this only supports ImageReader
struct AImage {
AImage(AImageReader* reader, int32_t format, uint64_t usage, BufferItem* buffer,
diff --git a/media/ndk/NdkImageReader.cpp b/media/ndk/NdkImageReader.cpp
index c0ceb3d..5d8f0b8 100644
--- a/media/ndk/NdkImageReader.cpp
+++ b/media/ndk/NdkImageReader.cpp
@@ -21,7 +21,6 @@
#include "NdkImagePriv.h"
#include "NdkImageReaderPriv.h"
-#include <private/media/NdkImage.h>
#include <cutils/atomic.h>
#include <utils/Log.h>
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index ab0cb63..8680641 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -26,9 +26,6 @@
#include <utils/StrongPointer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <android_util_Binder.h>
-
-#include <jni.h>
using namespace android;
diff --git a/media/ndk/TEST_MAPPING b/media/ndk/TEST_MAPPING
new file mode 100644
index 0000000..1a81538
--- /dev/null
+++ b/media/ndk/TEST_MAPPING
@@ -0,0 +1,6 @@
+// mappings for frameworks/av/media/ndk
+{
+ "presubmit": [
+ { "name": "AImageReaderWindowHandleTest" }
+ ]
+}
diff --git a/media/ndk/include/media/NdkMediaCodec.h b/media/ndk/include/media/NdkMediaCodec.h
index 8fb6a87..80d5d50 100644
--- a/media/ndk/include/media/NdkMediaCodec.h
+++ b/media/ndk/include/media/NdkMediaCodec.h
@@ -114,12 +114,12 @@
int32_t actionCode,
const char *detail);
-struct AMediaCodecOnAsyncNotifyCallback {
+typedef struct AMediaCodecOnAsyncNotifyCallback {
AMediaCodecOnAsyncInputAvailable onAsyncInputAvailable;
AMediaCodecOnAsyncOutputAvailable onAsyncOutputAvailable;
AMediaCodecOnAsyncFormatChanged onAsyncFormatChanged;
AMediaCodecOnAsyncError onAsyncError;
-};
+} AMediaCodecOnAsyncNotifyCallback;
#if __ANDROID_API__ >= 21
diff --git a/media/ndk/include/media/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index 14319c4..a1cd9e3 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -36,6 +36,7 @@
#ifndef _NDK_MEDIA_EXTRACTOR_H
#define _NDK_MEDIA_EXTRACTOR_H
+#include <stdbool.h>
#include <sys/cdefs.h>
#include <sys/types.h>
diff --git a/media/ndk/include/media/NdkMediaFormat.h b/media/ndk/include/media/NdkMediaFormat.h
index 49d8b4a..6371de4 100644
--- a/media/ndk/include/media/NdkMediaFormat.h
+++ b/media/ndk/include/media/NdkMediaFormat.h
@@ -36,9 +36,24 @@
#ifndef _NDK_MEDIA_FORMAT_H
#define _NDK_MEDIA_FORMAT_H
+#include <stdbool.h>
#include <sys/cdefs.h>
#include <sys/types.h>
+#ifndef __ANDROID__
+// Value copied from 'bionic/libc/include/android/api-level.h' which is not available on
+// non Android systems. It is set to 10000 which is same as __ANDROID_API_FUTURE__ value.
+#ifndef __ANDROID_API__
+#define __ANDROID_API__ 10000
+#endif
+
+// Value copied from 'bionic/libc/include/android/versioning.h' which is not available on
+// non Android systems
+#ifndef __INTRODUCED_IN
+#define __INTRODUCED_IN(api_level)
+#endif
+#endif
+
#include "NdkMediaError.h"
__BEGIN_DECLS
diff --git a/media/ndk/include/private/media/NdkImage.h b/media/ndk/include/private/media/NdkImage.h
deleted file mode 100644
index 4368a56..0000000
--- a/media/ndk/include/private/media/NdkImage.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _PRIVATE_MEDIA_NDKIMAGE_H_
-#define _PRIVATE_MEDIA_NDKIMAGE_H_
-// Formats not listed in the public API, but still available to AImageReader
-enum AIMAGE_PRIVATE_FORMATS {
- /**
- * Unprocessed implementation-dependent raw
- * depth measurements, opaque with 16 bit
- * samples.
- *
- */
-
- AIMAGE_FORMAT_RAW_DEPTH = 0x1002,
-};
-#endif // _PRIVATE_MEDIA_NDKIMAGE
diff --git a/media/ndk/tests/AImageReaderWindowHandleTest.cpp b/media/ndk/tests/AImageReaderWindowHandleTest.cpp
index 5b65064..27864c2 100644
--- a/media/ndk/tests/AImageReaderWindowHandleTest.cpp
+++ b/media/ndk/tests/AImageReaderWindowHandleTest.cpp
@@ -17,10 +17,10 @@
#include <gtest/gtest.h>
#include <media/NdkImageReader.h>
#include <media/NdkImage.h>
-#include <private/media/NdkImage.h>
#include <mediautils/AImageReaderUtils.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
+#include <NdkImagePriv.h>
#include <NdkImageReaderPriv.h>
#include <vndk/hardware_buffer.h>
#include <memory>
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/EncoderTest.java b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/EncoderTest.java
index 48e1422..4202732 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/EncoderTest.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/EncoderTest.java
@@ -34,6 +34,7 @@
import com.android.media.benchmark.library.Native;
import com.android.media.benchmark.library.Stats;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,40 +58,87 @@
public class EncoderTest {
private static final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private static final String mFileDirPath = mContext.getFilesDir() + "/";
private static final String mInputFilePath = mContext.getString(R.string.input_file_path);
private static final String mOutputFilePath = mContext.getString(R.string.output_file_path);
private static final String mStatsFile =
mContext.getExternalFilesDir(null) + "/Encoder." + System.currentTimeMillis() + ".csv";
private static final String TAG = "EncoderTest";
- private static final long PER_TEST_TIMEOUT_MS = 120000;
private static final boolean DEBUG = false;
private static final boolean WRITE_OUTPUT = false;
+ private static final long PER_TEST_TIMEOUT_MS = 120000;
private static final int ENCODE_DEFAULT_FRAME_RATE = 25;
- private static final int ENCODE_DEFAULT_BIT_RATE = 8000000 /* 8 Mbps */;
- private static final int ENCODE_MIN_BIT_RATE = 600000 /* 600 Kbps */;
+ private static final int ENCODE_DEFAULT_VIDEO_BIT_RATE = 8000000 /* 8 Mbps */;
+ private static final int ENCODE_MIN_VIDEO_BIT_RATE = 600000 /* 600 Kbps */;
private static final int ENCODE_DEFAULT_AUDIO_BIT_RATE = 128000 /* 128 Kbps */;
+ private static int mColorFormat = COLOR_FormatYUV420Flexible;
+ private static File mDecodedFileQcif;
+ private static File mDecodedFileFullHd;
+ private static File mDecodedFileAudio;
private String mInputFile;
+ private String mMime;
+ private int mBitRate;
+ private int mIFrameInterval;
+ private int mWidth;
+ private int mHeight;
+ private int mProfile;
+ private int mLevel;
+ private int mSampleRate;
+ private int mNumChannel;
+ private static final String DECODE_FULLHD_INPUT = "crowd_1920x1080_25fps_4000kbps_h265.mkv";
+ private static final String DECODE_QCIF_INPUT = "crowd_176x144_25fps_6000kbps_mpeg4.mp4";
+ private static final String DECODE_AUDIO_INPUT = "bbb_48000hz_2ch_100kbps_opus_30sec.webm";
+ private static final String DECODE_FULLHD_UNPACKED = "crowd_1920x1080_25fps_4000kbps_h265.yuv";
+ private static final String DECODE_QCIF_UNPACKED = "crowd_176x144_25fps_6000kbps_mpeg4.yuv";
+ private static final String DECODE_AUDIO_UNPACKED = "bbb_48000hz_2ch_100kbps_opus_30sec.raw";
@Parameterized.Parameters
public static Collection<Object[]> inputFiles() {
return Arrays.asList(new Object[][]{
// Audio Test
- {"bbb_44100hz_2ch_128kbps_aac_30sec.mp4"},
- {"bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp"},
- {"bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp"},
- {"bbb_44100hz_2ch_600kbps_flac_30sec.mp4"},
- {"bbb_48000hz_2ch_100kbps_opus_30sec.webm"},
+ // Parameters: Filename, mimeType, bitrate, width, height, iFrameInterval,
+ // profile, level, sampleRate, channelCount
+ {DECODE_AUDIO_UNPACKED, MediaFormat.MIMETYPE_AUDIO_AAC,
+ ENCODE_DEFAULT_AUDIO_BIT_RATE, -1, -1, -1, -1, -1, 44100, 2},
+ {DECODE_AUDIO_UNPACKED, MediaFormat.MIMETYPE_AUDIO_AMR_NB,
+ ENCODE_DEFAULT_AUDIO_BIT_RATE, -1, -1, -1, -1, -1, 8000, 1},
+ {DECODE_AUDIO_UNPACKED, MediaFormat.MIMETYPE_AUDIO_AMR_WB,
+ ENCODE_DEFAULT_AUDIO_BIT_RATE, -1, -1, -1, -1, -1, 16000, 1},
+ {DECODE_AUDIO_UNPACKED, MediaFormat.MIMETYPE_AUDIO_FLAC,
+ ENCODE_DEFAULT_AUDIO_BIT_RATE, -1, -1, -1, -1, -1, 44100, 2},
+ {DECODE_AUDIO_UNPACKED, MediaFormat.MIMETYPE_AUDIO_OPUS,
+ ENCODE_DEFAULT_AUDIO_BIT_RATE, -1, -1, -1, -1, -1, 48000, 2},
+
// Video Test
- {"crowd_1920x1080_25fps_4000kbps_vp8.webm"},
- {"crowd_1920x1080_25fps_6700kbps_h264.ts"},
- {"crowd_1920x1080_25fps_4000kbps_h265.mkv"},
- {"crowd_1920x1080_25fps_4000kbps_vp9.webm"},
- {"crowd_176x144_25fps_6000kbps_mpeg4.mp4"},
- {"crowd_176x144_25fps_6000kbps_h263.3gp"}});
+ // Parameters: Filename, mimeType, bitrate, width, height, iFrameInterval,
+ // profile, level, sampleRate, channelCount
+ {DECODE_FULLHD_UNPACKED, MediaFormat.MIMETYPE_VIDEO_VP8,
+ ENCODE_DEFAULT_VIDEO_BIT_RATE, 1920, 1080, 1, -1, -1, -1, -1},
+ {DECODE_FULLHD_UNPACKED, MediaFormat.MIMETYPE_VIDEO_AVC,
+ ENCODE_DEFAULT_VIDEO_BIT_RATE, 1920, 1080, 1, -1, -1, -1, -1},
+ {DECODE_FULLHD_UNPACKED, MediaFormat.MIMETYPE_VIDEO_HEVC,
+ ENCODE_DEFAULT_VIDEO_BIT_RATE, 1920, 1080, 1, -1, -1, -1, -1},
+ {DECODE_FULLHD_UNPACKED, MediaFormat.MIMETYPE_VIDEO_VP9,
+ ENCODE_DEFAULT_VIDEO_BIT_RATE, 1920, 1080, 1, -1, -1, -1, -1},
+ {DECODE_QCIF_UNPACKED, MediaFormat.MIMETYPE_VIDEO_MPEG4, ENCODE_MIN_VIDEO_BIT_RATE,
+ 176, 144, 1, -1, -1, -1, -1},
+ {DECODE_QCIF_UNPACKED, MediaFormat.MIMETYPE_VIDEO_H263, ENCODE_MIN_VIDEO_BIT_RATE,
+ 176, 144, 1, -1, -1, -1, -1}});
}
- public EncoderTest(String inputFileName) {
- this.mInputFile = inputFileName;
+ public EncoderTest(String filename, String mime, int bitrate, int width, int height,
+ int frameInterval, int profile, int level, int samplerate,
+ int channelCount) {
+ this.mInputFile = filename;
+ this.mMime = mime;
+ this.mBitRate = bitrate;
+ this.mIFrameInterval = frameInterval;
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mProfile = profile;
+ this.mLevel = level;
+ this.mSampleRate = samplerate;
+ this.mNumChannel = channelCount;
}
@BeforeClass
@@ -101,33 +149,36 @@
Log.d(TAG, "Saving Benchmark results in: " + mStatsFile);
}
- @Test(timeout = PER_TEST_TIMEOUT_MS)
- public void testEncoder() throws Exception {
- int status;
- int frameSize;
- //Parameters for video
- int width = 0;
- int height = 0;
- int profile = 0;
- int level = 0;
- int frameRate = 0;
+ @BeforeClass
+ public static void prepareInput() throws IOException {
- //Parameters for audio
- int bitRate = 0;
- int sampleRate = 0;
- int numChannels = 0;
- File inputFile = new File(mInputFilePath + mInputFile);
- assertTrue("Cannot find " + mInputFile + " in directory " + mInputFilePath,
- inputFile.exists());
+ mDecodedFileFullHd = new File(mFileDirPath + DECODE_FULLHD_UNPACKED);
+ int status = decodeFile(mInputFilePath + DECODE_FULLHD_INPUT, mDecodedFileFullHd);
+ assertEquals("Decoder returned error " + status, 0, status);
+
+ mDecodedFileQcif = new File(mFileDirPath + DECODE_QCIF_UNPACKED);
+ status = decodeFile(mInputFilePath + DECODE_QCIF_INPUT, mDecodedFileQcif);
+ assertEquals("Decoder returned error " + status, 0, status);
+
+ mDecodedFileAudio = new File(mFileDirPath + DECODE_AUDIO_UNPACKED);
+ status = decodeFile(mInputFilePath + DECODE_AUDIO_INPUT, mDecodedFileAudio);
+ assertEquals("Decoder returned error " + status, 0, status);
+ }
+
+ private static int decodeFile(String inputFileName, File outputDecodeFile) throws IOException {
+ int status = -1;
+ File inputFile = new File(inputFileName);
+ assertTrue("Cannot open input file " + inputFileName, inputFile.exists());
FileInputStream fileInput = new FileInputStream(inputFile);
FileDescriptor fileDescriptor = fileInput.getFD();
+ FileOutputStream decodeOutputStream = new FileOutputStream(outputDecodeFile);
+
Extractor extractor = new Extractor();
int trackCount = extractor.setUpExtractor(fileDescriptor);
- assertTrue("Extraction failed. No tracks for file: " + mInputFile, (trackCount > 0));
+ assertTrue("Extraction failed. No tracks for the given input file", (trackCount > 0));
ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
ArrayList<MediaCodec.BufferInfo> frameInfo = new ArrayList<>();
for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
- int colorFormat = COLOR_FormatYUV420Flexible;
extractor.selectExtractorTrack(currentTrack);
MediaFormat format = extractor.getFormat(currentTrack);
// Get samples from extractor
@@ -146,163 +197,135 @@
bufInfo.presentationTimeUs + " size = " + bufInfo.size);
}
} while (sampleSize > 0);
- int tid = android.os.Process.myTid();
- File decodedFile = new File(mContext.getFilesDir() + "/decoder_" + tid + ".out");
- FileOutputStream decodeOutputStream = new FileOutputStream(decodedFile);
Decoder decoder = new Decoder();
decoder.setupDecoder(decodeOutputStream);
status = decoder.decode(inputBuffer, frameInfo, false, format, "");
- assertEquals("Decoder returned error " + status + " for file: " + mInputFile, 0,
- status);
MediaFormat decoderFormat = decoder.getFormat();
+ if (decoderFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
+ mColorFormat = decoderFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+ }
decoder.deInitCodec();
extractor.unselectExtractorTrack(currentTrack);
inputBuffer.clear();
frameInfo.clear();
- if (decodeOutputStream != null) {
- decodeOutputStream.close();
- }
- String mime = format.getString(MediaFormat.KEY_MIME);
- ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, true);
- assertTrue("No suitable codecs found for file: " + mInputFile + " track : " +
- currentTrack + " mime: " + mime, (mediaCodecs.size() > 0));
- Boolean[] encodeMode = {true, false};
- /* Encoding the decoder's output */
- for (Boolean asyncMode : encodeMode) {
- for (String codecName : mediaCodecs) {
- FileOutputStream encodeOutputStream = null;
- if (WRITE_OUTPUT) {
- File outEncodeFile = new File(mOutputFilePath + "encoder.out");
- if (outEncodeFile.exists()) {
- assertTrue(" Unable to delete existing file" + outEncodeFile.toString(),
- outEncodeFile.delete());
- }
- assertTrue("Unable to create file to write encoder output: " +
- outEncodeFile.toString(), outEncodeFile.createNewFile());
- encodeOutputStream = new FileOutputStream(outEncodeFile);
- }
- File rawFile = new File(mContext.getFilesDir() + "/decoder_" + tid + ".out");
- assertTrue("Cannot open file to write decoded output", rawFile.exists());
- if (DEBUG) {
- Log.i(TAG, "Path of decoded input file: " + rawFile.toString());
- }
- FileInputStream eleStream = new FileInputStream(rawFile);
- if (mime.startsWith("video/")) {
- width = format.getInteger(MediaFormat.KEY_WIDTH);
- height = format.getInteger(MediaFormat.KEY_HEIGHT);
- if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
- frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
- } else if (frameRate <= 0) {
- frameRate = ENCODE_DEFAULT_FRAME_RATE;
- }
- if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
- bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE);
- } else if (bitRate <= 0) {
- if (mime.contains("video/3gpp") || mime.contains("video/mp4v-es")) {
- bitRate = ENCODE_MIN_BIT_RATE;
- } else {
- bitRate = ENCODE_DEFAULT_BIT_RATE;
- }
- }
- if (format.containsKey(MediaFormat.KEY_PROFILE)) {
- profile = format.getInteger(MediaFormat.KEY_PROFILE);
- }
- if (format.containsKey(MediaFormat.KEY_PROFILE)) {
- level = format.getInteger(MediaFormat.KEY_LEVEL);
- }
- if (decoderFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
- colorFormat = decoderFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
- }
- } else {
- sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
- numChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
- if (decoderFormat.containsKey(MediaFormat.KEY_BIT_RATE)) {
- bitRate = decoderFormat.getInteger(MediaFormat.KEY_BIT_RATE);
- } else {
- bitRate = ENCODE_DEFAULT_AUDIO_BIT_RATE;
- }
- }
- /*Setup Encode Format*/
- MediaFormat encodeFormat;
- if (mime.startsWith("video/")) {
- frameSize = width * height * 3 / 2;
- encodeFormat = MediaFormat.createVideoFormat(mime, width, height);
- encodeFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
- encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
- encodeFormat.setInteger(MediaFormat.KEY_PROFILE, profile);
- encodeFormat.setInteger(MediaFormat.KEY_LEVEL, level);
- encodeFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
- encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, frameSize);
- encodeFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
- } else {
- encodeFormat = MediaFormat.createAudioFormat(mime, sampleRate, numChannels);
- encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
- frameSize = 4096;
- }
- Encoder encoder = new Encoder();
- encoder.setupEncoder(encodeOutputStream, eleStream);
- status = encoder.encode(codecName, encodeFormat, mime, frameRate, sampleRate,
- frameSize, asyncMode);
- encoder.deInitEncoder();
- assertEquals(
- codecName + " encoder returned error " + status + " for " + "file:" +
- " " + mInputFile, 0, status);
- encoder.dumpStatistics(mInputFile, codecName, (asyncMode ? "async" : "sync"),
- extractor.getClipDuration(), mStatsFile);
- Log.i(TAG, "Encoding complete for file: " + mInputFile + " with codec: " +
- codecName + " for aSyncMode = " + asyncMode);
- encoder.resetEncoder();
- eleStream.close();
- if (encodeOutputStream != null) {
- encodeOutputStream.close();
- }
-
- }
- }
- //Cleanup temporary input file
- if (decodedFile.exists()) {
- assertTrue(" Unable to delete decoded file" + decodedFile.toString(),
- decodedFile.delete());
- Log.i(TAG, "Successfully deleted decoded file");
- }
}
extractor.deinitExtractor();
fileInput.close();
+ decodeOutputStream.close();
+ return status;
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
- public void testNativeEncoder() throws Exception {
- File inputFile = new File(mInputFilePath + mInputFile);
- assertTrue("Cannot find " + mInputFile + " in directory " + mInputFilePath,
- inputFile.exists());
- int tid = android.os.Process.myTid();
- final String mDecodedFile = mContext.getFilesDir() + "/decoder_" + tid + ".out";
- FileInputStream fileInput = new FileInputStream(inputFile);
- FileDescriptor fileDescriptor = fileInput.getFD();
- Extractor extractor = new Extractor();
- int trackCount = extractor.setUpExtractor(fileDescriptor);
- assertTrue("Extraction failed. No tracks for file: ", trackCount > 0);
- for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
- extractor.selectExtractorTrack(currentTrack);
- MediaFormat format = extractor.getFormat(currentTrack);
- String mime = format.getString(MediaFormat.KEY_MIME);
- ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, true);
- // Encoding the decoder's output
+ public void testEncoder() throws Exception {
+ int status;
+ int frameSize;
+
+ ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mMime, true);
+ assertTrue("No suitable codecs found for mimetype: " + mMime, (mediaCodecs.size() > 0));
+ Boolean[] encodeMode = {true, false};
+ // Encoding the decoded input file
+ for (Boolean asyncMode : encodeMode) {
for (String codecName : mediaCodecs) {
- Native nativeEncoder = new Native();
- int status = nativeEncoder
- .Encode(mInputFilePath, mInputFile, mDecodedFile, mStatsFile, codecName);
+ FileOutputStream encodeOutputStream = null;
+ if (WRITE_OUTPUT) {
+ File outEncodeFile = new File(mOutputFilePath + "encoder.out");
+ if (outEncodeFile.exists()) {
+ assertTrue(" Unable to delete existing file" + outEncodeFile.toString(),
+ outEncodeFile.delete());
+ }
+ assertTrue("Unable to create file to write encoder output: " +
+ outEncodeFile.toString(), outEncodeFile.createNewFile());
+ encodeOutputStream = new FileOutputStream(outEncodeFile);
+ }
+ File rawFile = new File(mFileDirPath + mInputFile);
+ assertTrue("Cannot open decoded input file", rawFile.exists());
+ if (DEBUG) {
+ Log.i(TAG, "Path of decoded input file: " + rawFile.toString());
+ }
+ FileInputStream eleStream = new FileInputStream(rawFile);
+ // Setup Encode Format
+ MediaFormat encodeFormat;
+ if (mMime.startsWith("video/")) {
+ frameSize = mWidth * mHeight * 3 / 2;
+ encodeFormat = MediaFormat.createVideoFormat(mMime, mWidth, mHeight);
+ encodeFormat.setInteger(MediaFormat.KEY_FRAME_RATE, ENCODE_DEFAULT_FRAME_RATE);
+ encodeFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, mIFrameInterval);
+ encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+ encodeFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);
+ if (mProfile != -1 && mLevel != -1) {
+ encodeFormat.setInteger(MediaFormat.KEY_PROFILE, mProfile);
+ encodeFormat.setInteger(MediaFormat.KEY_LEVEL, mLevel);
+ }
+ } else {
+ frameSize = 4096;
+ encodeFormat = MediaFormat.createAudioFormat(mMime, mSampleRate, mNumChannel);
+ encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+ }
+ Encoder encoder = new Encoder();
+ encoder.setupEncoder(encodeOutputStream, eleStream);
+ status = encoder.encode(codecName, encodeFormat, mMime, ENCODE_DEFAULT_FRAME_RATE,
+ mSampleRate, frameSize, asyncMode);
+ encoder.deInitEncoder();
assertEquals(
- codecName + " encoder returned error " + status + " for " + "file:" + " " +
- mInputFile, 0, status);
+ codecName + " encoder returned error " + status + " for " + "mime:" + " " +
+ mMime, 0, status);
+ String inputReference;
+ long durationUs;
+ if (mMime.startsWith("video/")) {
+ inputReference =
+ mInputFile + "_" + mWidth + "x" + mHeight + "_" + mBitRate + "bps";
+ durationUs = (((eleStream.getChannel().size() + frameSize - 1) / frameSize) /
+ ENCODE_DEFAULT_FRAME_RATE) * 1000000;
+ } else {
+ inputReference = mInputFile + "_" + mSampleRate + "hz_" + mNumChannel + "ch_" +
+ mBitRate + "bps";
+ durationUs =
+ (eleStream.getChannel().size() / (mSampleRate * mNumChannel)) * 1000000;
+ }
+ encoder.dumpStatistics(inputReference, codecName, (asyncMode ? "async" : "sync"),
+ durationUs, mStatsFile);
+ Log.i(TAG, "Encoding complete for mime: " + mMime + " with codec: " + codecName +
+ " for aSyncMode = " + asyncMode);
+ encoder.resetEncoder();
+ eleStream.close();
+ if (encodeOutputStream != null) {
+ encodeOutputStream.close();
+ }
}
}
- File decodedFile = new File(mDecodedFile);
- // Cleanup temporary input file
- if (decodedFile.exists()) {
- assertTrue("Unable to delete - " + mDecodedFile, decodedFile.delete());
- Log.i(TAG, "Successfully deleted - " + mDecodedFile);
+ }
+
+ @Test(timeout = PER_TEST_TIMEOUT_MS)
+ public void testNativeEncoder() {
+ ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mMime, true);
+ assertTrue("No suitable codecs found for mimetype: " + mMime, (mediaCodecs.size() > 0));
+ for (String codecName : mediaCodecs) {
+ Native nativeEncoder = new Native();
+ int status = nativeEncoder
+ .Encode(mFileDirPath, mInputFile, mStatsFile, codecName, mMime, mBitRate,
+ mColorFormat, mIFrameInterval, mWidth, mHeight, mProfile, mLevel,
+ mSampleRate, mNumChannel);
+ assertEquals(codecName + " encoder returned error " + status + " for " + "mime:" + " " +
+ mMime, 0, status);
}
- fileInput.close();
+ }
+
+ @AfterClass
+ public static void deleteDecodedFiles() {
+ if (mDecodedFileFullHd.exists()) {
+ assertTrue(" Unable to delete decoded file" + mDecodedFileFullHd.toString(),
+ mDecodedFileFullHd.delete());
+ Log.i(TAG, "Successfully deleted decoded file" + mDecodedFileFullHd.toString());
+ }
+ if (mDecodedFileQcif.exists()) {
+ assertTrue(" Unable to delete decoded file" + mDecodedFileQcif.toString(),
+ mDecodedFileQcif.delete());
+ Log.i(TAG, "Successfully deleted decoded file" + mDecodedFileQcif.toString());
+ }
+ if (mDecodedFileAudio.exists()) {
+ assertTrue(" Unable to delete decoded file" + mDecodedFileAudio.toString(),
+ mDecodedFileAudio.delete());
+ Log.i(TAG, "Successfully deleted decoded file" + mDecodedFileAudio.toString());
+ }
}
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeEncoder.cpp b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeEncoder.cpp
index 1277c8b..2f658e3 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeEncoder.cpp
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeEncoder.cpp
@@ -30,189 +30,81 @@
#include <stdio.h>
constexpr int32_t ENCODE_DEFAULT_FRAME_RATE = 25;
-constexpr int32_t ENCODE_DEFAULT_AUDIO_BIT_RATE = 128000 /* 128 Kbps */;
-constexpr int32_t ENCODE_DEFAULT_BIT_RATE = 8000000 /* 8 Mbps */;
-constexpr int32_t ENCODE_MIN_BIT_RATE = 600000 /* 600 Kbps */;
extern "C" JNIEXPORT int JNICALL Java_com_android_media_benchmark_library_Native_Encode(
- JNIEnv *env, jobject thiz, jstring jFilePath, jstring jFileName, jstring jOutFilePath,
- jstring jStatsFile, jstring jCodecName) {
+ JNIEnv *env, jobject thiz, jstring jFilePath, jstring jFileName, jstring jStatsFile,
+ jstring jCodecName, jstring jMime, jint jBitRate, jint jColorFormat, jint jFrameInterval,
+ jint jWidth, jint jHeight, jint jProfile, jint jLevel, jint jSampleRate,
+ jint jNumChannels) {
+ UNUSED(thiz);
const char *filePath = env->GetStringUTFChars(jFilePath, nullptr);
const char *fileName = env->GetStringUTFChars(jFileName, nullptr);
- string sFilePath = string(filePath) + string(fileName);
- UNUSED(thiz);
- FILE *inputFp = fopen(sFilePath.c_str(), "rb");
- env->ReleaseStringUTFChars(jFileName, fileName);
- env->ReleaseStringUTFChars(jFilePath, filePath);
- if (!inputFp) {
- ALOGE("Unable to open input file for reading");
+ string inputFile = string(filePath) + string(fileName);
+ const char *codecName = env->GetStringUTFChars(jCodecName, nullptr);
+ string sCodecName = string(codecName);
+ const char *mime = env->GetStringUTFChars(jMime, nullptr);
+
+ ifstream eleStream;
+ eleStream.open(inputFile, ifstream::binary | ifstream::ate);
+ if (!eleStream.is_open()) {
+ ALOGE("%s - File failed to open for reading!", fileName);
+ env->ReleaseStringUTFChars(jFileName, fileName);
return -1;
}
- Decoder *decoder = new Decoder();
- Extractor *extractor = decoder->getExtractor();
- if (!extractor) {
- ALOGE("Extractor creation failed");
- return -1;
- }
+ bool asyncMode[2] = {true, false};
+ for (bool mode : asyncMode) {
+ size_t eleSize = eleStream.tellg();
+ eleStream.seekg(0, ifstream::beg);
- // Read file properties
- struct stat buf;
- stat(sFilePath.c_str(), &buf);
- size_t fileSize = buf.st_size;
- if (fileSize > kMaxBufferSize) {
- ALOGE("File size greater than maximum buffer size");
- return -1;
- }
- int32_t fd = fileno(inputFp);
- int32_t trackCount = extractor->initExtractor(fd, fileSize);
- if (trackCount <= 0) {
- ALOGE("initExtractor failed");
- return -1;
- }
+ // Set encoder params
+ encParameter encParams;
+ encParams.width = jWidth;
+ encParams.height = jHeight;
+ encParams.bitrate = jBitRate;
+ encParams.iFrameInterval = jFrameInterval;
+ encParams.sampleRate = jSampleRate;
+ encParams.numChannels = jNumChannels;
+ encParams.frameRate = ENCODE_DEFAULT_FRAME_RATE;
+ encParams.colorFormat = jColorFormat;
+ encParams.profile = jProfile;
+ encParams.level = jLevel;
- for (int curTrack = 0; curTrack < trackCount; curTrack++) {
- int32_t status = extractor->setupTrackFormat(curTrack);
- if (status != 0) {
- ALOGE("Track Format invalid");
- return -1;
- }
- uint8_t *inputBuffer = (uint8_t *)malloc(fileSize);
- if (!inputBuffer) {
- ALOGE("Insufficient memory");
- return -1;
- }
- vector<AMediaCodecBufferInfo> frameInfo;
- AMediaCodecBufferInfo info;
- uint32_t inputBufferOffset = 0;
-
- // Get frame data
- while (1) {
- status = extractor->getFrameSample(info);
- if (status || !info.size) break;
- // copy the meta data and buffer to be passed to decoder
- if (inputBufferOffset + info.size > kMaxBufferSize) {
- ALOGE("Memory allocated not sufficient");
- free(inputBuffer);
- return -1;
- }
- memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size);
- frameInfo.push_back(info);
- inputBufferOffset += info.size;
- }
- string decName = "";
- const char *outputFilePath = env->GetStringUTFChars(jOutFilePath, nullptr);
- FILE *outFp = fopen(outputFilePath, "wb");
- if (outFp == nullptr) {
- ALOGE("%s - File failed to open for writing!", outputFilePath);
- free(inputBuffer);
- return -1;
- }
- decoder->setupDecoder();
- status = decoder->decode(inputBuffer, frameInfo, decName, false /*asyncMode */, outFp);
+ Encoder *encoder = new Encoder();
+ encoder->setupEncoder();
+ auto status = encoder->encode(sCodecName, eleStream, eleSize, mode, encParams,
+ const_cast<char *>(mime));
if (status != AMEDIA_OK) {
- ALOGE("Decode returned error");
- free(inputBuffer);
+ ALOGE("Encoder returned error");
return -1;
}
-
- AMediaFormat *decoderFormat = decoder->getFormat();
- AMediaFormat *format = extractor->getFormat();
- if (inputBuffer) {
- free(inputBuffer);
- inputBuffer = nullptr;
+ ALOGV("Encoding complete with codec %s for asyncMode = %d", sCodecName.c_str(), mode);
+ encoder->deInitCodec();
+ const char *statsFile = env->GetStringUTFChars(jStatsFile, nullptr);
+ string inputReference;
+ int64_t clipDurationUs;
+ if (!strncmp(mime, "video/", 6)) {
+ inputReference = string(fileName) + "_" + to_string(jWidth) + "x" + to_string(jHeight) +
+ "_" + to_string(jBitRate) + "bps";
+ int32_t frameSize = jWidth * jHeight * 3 / 2;
+ clipDurationUs =
+ (((eleSize + frameSize - 1) / frameSize) / ENCODE_DEFAULT_FRAME_RATE) * 1000000;
+ } else {
+ inputReference = string(fileName) + "_" + to_string(jSampleRate) + "hz_" +
+ to_string(jNumChannels) + "ch_" + to_string(jBitRate) + "bps";
+ clipDurationUs = (eleSize / (jSampleRate * jNumChannels)) * 1000000;
}
- const char *mime = nullptr;
- AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
- if (!mime) {
- ALOGE("Error in AMediaFormat_getString");
- return -1;
- }
- ifstream eleStream;
- eleStream.open(outputFilePath, ifstream::binary | ifstream::ate);
- if (!eleStream.is_open()) {
- ALOGE("%s - File failed to open for reading!", outputFilePath);
- env->ReleaseStringUTFChars(jOutFilePath, outputFilePath);
- return -1;
- }
- const char *codecName = env->GetStringUTFChars(jCodecName, NULL);
- const char *inputReference = env->GetStringUTFChars(jFileName, nullptr);
- string sCodecName = string(codecName);
- string sInputReference = string(inputReference);
-
- bool asyncMode[2] = {true, false};
- for (int i = 0; i < 2; i++) {
- size_t eleSize = eleStream.tellg();
- eleStream.seekg(0, ifstream::beg);
-
- // Get encoder params
- encParameter encParams;
- if (!strncmp(mime, "video/", 6)) {
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &encParams.width);
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &encParams.height);
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &encParams.frameRate);
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &encParams.bitrate);
- if (encParams.bitrate <= 0 || encParams.frameRate <= 0) {
- encParams.frameRate = ENCODE_DEFAULT_FRAME_RATE;
- if (!strcmp(mime, "video/3gpp") || !strcmp(mime, "video/mp4v-es")) {
- encParams.bitrate = ENCODE_MIN_BIT_RATE /* 600 Kbps */;
- } else {
- encParams.bitrate = ENCODE_DEFAULT_BIT_RATE /* 8 Mbps */;
- }
- }
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_PROFILE, &encParams.profile);
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_LEVEL, &encParams.level);
- AMediaFormat_getInt32(decoderFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
- &encParams.colorFormat);
- } else {
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &encParams.sampleRate);
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
- &encParams.numChannels);
- encParams.bitrate = ENCODE_DEFAULT_AUDIO_BIT_RATE;
- }
- Encoder *encoder = new Encoder();
- encoder->setupEncoder();
- status = encoder->encode(sCodecName, eleStream, eleSize, asyncMode[i], encParams,
- (char *)mime);
- if (status != AMEDIA_OK) {
- ALOGE("Encoder returned error");
- return -1;
- }
- ALOGV("Encoding complete with codec %s for asyncMode = %d", sCodecName.c_str(),
- asyncMode[i]);
- encoder->deInitCodec();
- const char *statsFile = env->GetStringUTFChars(jStatsFile, nullptr);
- encoder->dumpStatistics(sInputReference, extractor->getClipDuration(), sCodecName,
- (asyncMode[i] ? "async" : "sync"), statsFile);
- env->ReleaseStringUTFChars(jStatsFile, statsFile);
- encoder->resetEncoder();
- delete encoder;
- encoder = nullptr;
- }
- eleStream.close();
- if (outFp) {
- fclose(outFp);
- outFp = nullptr;
- }
- env->ReleaseStringUTFChars(jFileName, inputReference);
- env->ReleaseStringUTFChars(jCodecName, codecName);
- env->ReleaseStringUTFChars(jOutFilePath, outputFilePath);
- if (format) {
- AMediaFormat_delete(format);
- format = nullptr;
- }
- if (decoderFormat) {
- AMediaFormat_delete(decoderFormat);
- decoderFormat = nullptr;
- }
- decoder->deInitCodec();
- decoder->resetDecoder();
+ encoder->dumpStatistics(inputReference, clipDurationUs, sCodecName,
+ (mode ? "async" : "sync"), statsFile);
+ env->ReleaseStringUTFChars(jStatsFile, statsFile);
+ encoder->resetEncoder();
+ delete encoder;
+ encoder = nullptr;
}
- if (inputFp) {
- fclose(inputFp);
- inputFp = nullptr;
- }
- extractor->deInitExtractor();
- delete decoder;
+ eleStream.close();
+ env->ReleaseStringUTFChars(jFilePath, filePath);
+ env->ReleaseStringUTFChars(jFileName, fileName);
+ env->ReleaseStringUTFChars(jMime, mime);
+ env->ReleaseStringUTFChars(jCodecName, codecName);
return 0;
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Encoder.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Encoder.java
index 45e5574..754cd8e 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Encoder.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Encoder.java
@@ -175,10 +175,10 @@
@Override
public void onError(@NonNull MediaCodec mediaCodec, @NonNull CodecException e) {
- mediaCodec.stop();
- mediaCodec.release();
- Log.e(TAG, "CodecError: " + e.toString());
+ mSignalledError = true;
+ Log.e(TAG, "Codec Error: " + e.toString());
e.printStackTrace();
+ synchronized (mLock) { mLock.notify(); }
}
@Override
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Native.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Native.java
index 38b608a..3e3969c 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Native.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Native.java
@@ -27,6 +27,7 @@
public native int Decode(String inputFilePath, String inputFileName, String statsFile,
String codecName, boolean asyncMode);
- public native int Encode(String inputFilePath, String inputFileName, String outputFilePath,
- String statsFile, String codecName);
+ public native int Encode(String inputFilePath, String inputFileName, String statsFile,
+ String codecName, String mime, int bitRate, int colorFormat, int frameInterval,
+ int width, int height, int profile, int level, int sampleRate, int numChannel);
}
diff --git a/media/tests/benchmark/README.md b/media/tests/benchmark/README.md
index 05fbe6f..047c289 100644
--- a/media/tests/benchmark/README.md
+++ b/media/tests/benchmark/README.md
@@ -1,7 +1,7 @@
# Benchmark tests
Benchmark app analyses the time taken by MediaCodec, MediaExtractor and MediaMuxer for given set of inputs. It is used to benchmark these modules on android devices.
-Benchmark results are emitted to logcat.
+Benchmark results are published as a CSV report.
This page describes steps to run the NDK and SDK layer test.
@@ -10,35 +10,49 @@
mmm frameworks/av/media/tests/benchmark/
```
-# NDK
-
-To run the test suite for measuring performance of the native layer, follow the following steps:
-
-The binaries will be created in the following path : $OUT/data/nativetest64/
-
-adb push $OUT/data/nativetest64/* /data/local/tmp/
-
-Eg. adb push $OUT/data/nativetest64/extractorTest/extractorTest /data/local/tmp/
-
-To run the binary, follow the commands mentioned below under each module.
-
-The resource file for the tests is taken from [here](https://drive.google.com/open?id=1ghMr17BBJ7n0pqbm7oREiTN_MNemJUqy)
+# Resources
+The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/frameworks/av/media/tests/benchmark/MediaBenchmark.zip)
Download the MediaBenchmark.zip file, unzip and push it to /data/local/tmp/ on the device.
```
unzip MediaBenchmark.zip
-adb push MediaBenchmark /data/local/tmp
+adb push MediaBenchmark /data/local/tmp/MediaBenchmark/res/
```
+The resource files are assumed to be at /data/local/tmp/MediaBenchmark/res/. You can use a different location, but you have to modify the rest of the instructions to replace /data/local/tmp/MediaBenchmark/res/ with wherever you chose to put the files.
+
+# NDK CLI Tests
+Note: [Benchmark Application](#BenchmarkApplication) now supports profiling both SDK and NDK APIs and that is the preferred way to benchmark codecs
+
+To run the test suite for measuring performance of the native layer, follow the following steps:
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+
+adb push $OUT/data/nativetest64/* /data/local/tmp/. For example
+
+```
+adb push $OUT/data/nativetest64/extractorTest/extractorTest /data/local/tmp/
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+To test 32-bit binary push binaries from nativetest.
+
+adb push $OUT/data/nativetest/* /data/local/tmp/. For example
+
+```
+adb push $OUT/data/nativetest/extractorTest/extractorTest /data/local/tmp/
+```
+
+To run the binary, follow the commands mentioned below under each module.
+
## Extractor
The test extracts elementary stream and benchmarks the extractors available in NDK.
-The resource files are assumed to be at /data/local/tmp/MediaBenchmark/res/. You can use a different location, but you have to modify the rest of the instructions to replace /data/local/tmp/MediaBenchmark/res/ with wherever you chose to put the files.
-
-The path to these files on the device is required to be given for the test.
-
```
adb shell /data/local/tmp/extractorTest -P /data/local/tmp/MediaBenchmark/res/
```
@@ -47,8 +61,6 @@
The test decodes input stream and benchmarks the decoders available in NDK.
-Setup steps are same as extractor.
-
```
adb shell /data/local/tmp/decoderTest -P /data/local/tmp/MediaBenchmark/res/
```
@@ -57,8 +69,6 @@
The test muxes elementary stream and benchmarks the muxers available in NDK.
-Setup steps are same as extractor.
-
```
adb shell /data/local/tmp/muxerTest -P /data/local/tmp/MediaBenchmark/res/
```
@@ -67,55 +77,82 @@
The test encodes input stream and benchmarks the encoders available in NDK.
-Setup steps are same as extractor.
-
```
adb shell /data/local/tmp/encoderTest -P /data/local/tmp/MediaBenchmark/res/
```
-# SDK
+# <a name="BenchmarkApplication"></a> Benchmark Application
+To run the test suite for measuring performance of the SDK and NDK APIs, follow the following steps:
+Benchmark Application can be run in two ways.
-To run the test suite for measuring performance of the SDK APIs, follow the following steps:
+## Steps to run with atest
+Note that atest command will install Benchmark application and push the required test files to the device as well.
+
+For running all the tests, run the following command
+```
+atest com.android.media.benchmark.tests -- --enable-module-dynamic-download=true
+```
+
+For running the tests individually, run the following atest commands:
+
+```
+atest com.android.media.benchmark.tests.ExtractorTest -- --enable-module-dynamic-download=true
+atest com.android.media.benchmark.tests.DecoderTest -- --enable-module-dynamic-download=true
+atest com.android.media.benchmark.tests.MuxerTest -- --enable-module-dynamic-download=true
+atest com.android.media.benchmark.tests.EncoderTest -- --enable-module-dynamic-download=true
+```
+
+## Steps to run without atest
The apk will be created at the following path:
-$OUT/testcases/MediaBenchmarkTest/arm64/
-To get the resorce files for the test follow instructions given in [NDK](#NDK)
+The 64-bit apk will be created in the following path :
+$OUT/testcases/MediaBenchmarkTest/arm64/
For installing the apk, run the command:
```
adb install -f -r $OUT/testcases/MediaBenchmarkTest/arm64/MediaBenchmarkTest.apk
```
-For running all the tests, run the command:
+The 32-bit apk will be created in the following path :
+$OUT/testcases/MediaBenchmarkTest/arm/
+
+For installing the apk, run the command:
+```
+adb install -f -r $OUT/testcases/MediaBenchmarkTest/arm/MediaBenchmarkTest.apk
+```
+
+To get the resource files for the test follow instructions given in [Resources](#Resources)
+
+For running all the tests, run the following command
```
adb shell am instrument -w -r -e package com.android.media.benchmark.tests com.android.media.benchmark/androidx.test.runner.AndroidJUnitRunner
```
## Extractor
-The test extracts elementary stream and benchmarks the extractors available in SDK.
+The test extracts elementary stream and benchmarks the extractors available in SDK and NDK.
```
adb shell am instrument -w -r -e class 'com.android.media.benchmark.tests.ExtractorTest' com.android.media.benchmark/androidx.test.runner.AndroidJUnitRunner
```
## Decoder
-The test decodes input stream and benchmarks the decoders available in SDK.
+The test decodes input stream and benchmarks the decoders available in SDK and NDK.
```
adb shell am instrument -w -r -e class 'com.android.media.benchmark.tests.DecoderTest' com.android.media.benchmark/androidx.test.runner.AndroidJUnitRunner
```
## Muxer
-The test muxes elementary stream and benchmarks different writers available in SDK.
+The test muxes elementary stream and benchmarks different writers available in SDK and NDK.
```
adb shell am instrument -w -r -e class 'com.android.media.benchmark.tests.MuxerTest' com.android.media.benchmark/androidx.test.runner.AndroidJUnitRunner
```
## Encoder
-The test encodes input stream and benchmarks the encoders available in SDK.
+The test encodes input stream and benchmarks the encoders available in SDK and NDK.
```
adb shell am instrument -w -r -e class 'com.android.media.benchmark.tests.EncoderTest' com.android.media.benchmark/androidx.test.runner.AndroidJUnitRunner
```
@@ -124,24 +161,27 @@
To run the test suite for measuring performance of the codec2 layer, follow the following steps:
The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
To test 64-bit binary push binaries from nativetest64.
adb push $(OUT)/data/nativetest64/* /data/local/tmp/
-Eg. adb push $(OUT)/data/nativetest64/C2DecoderTest/C2DecoderTest /data/local/tmp/
+```
+adb push $(OUT)/data/nativetest64/C2DecoderTest/C2DecoderTest /data/local/tmp/
+```
To test 32-bit binary push binaries from nativetest.
adb push $(OUT)/data/nativetest/* /data/local/tmp/
-Eg. adb push $(OUT)/data/nativetest/C2DecoderTest/C2DecoderTest /data/local/tmp/
+```
+adb push $(OUT)/data/nativetest/C2DecoderTest/C2DecoderTest /data/local/tmp/
+```
-To get the resource files for the test follow instructions given in [NDK](#NDK)
+To get the resource files for the test follow instructions given in [Resources](#Resources)
## C2 Decoder
The test decodes input stream and benchmarks the codec2 decoders available in device.
-Setup steps are same as [extractor](#extractor).
-
```
adb shell /data/local/tmp/C2DecoderTest -P /data/local/tmp/MediaBenchmark/res/
```
@@ -149,8 +189,95 @@
The test encodes input stream and benchmarks the codec2 encoders available in device.
-Setup steps are same as [extractor](#extractor).
-
```
adb shell /data/local/tmp/C2EncoderTest -P /data/local/tmp/MediaBenchmark/res/
```
+
+# Analysis
+
+The benchmark results are stored in a CSV file which can be used for analysis. These results are stored in following format:
+<app directory>/<module_name>.<timestamp>.csv
+
+Note: This timestamp is in nano seconds and will change based on current system time.
+
+To find the location of the CSV file, look for the path in logs. Example log below -
+
+```
+com.android.media.benchmark D/DecoderTest: Saving Benchmark results in: /storage/emulated/0/Android/data/com.android.media.benchmark/files/Decoder.1587732395387.csv
+```
+
+This file can be pulled from the device using "adb pull" command.
+```
+adb pull /storage/emulated/0/Android/data/com.android.media.benchmark/files/Decoder.1587732395387.csv ./Decoder.1587732395387.csv
+```
+
+## CSV Columns
+
+Following columns are available in CSV.
+
+Note: All time values are in nano seconds
+
+1. **currentTime** : The time recorded at the creation of the stats. This may be used to estimate time between consecutive test clips.
+
+2. **fileName**: The file being used as an input for the benchmark test.
+
+3. **operation**: The current operation on the input test vector i.e. Extract/Mux/Encode/Decode.
+
+4. **NDK/SDK**: The target APIs i.e. AMedia vs Media calls for the operation being performed.
+
+5. **sync/async**: This is specific to MediaCodec objects (i.e. Encoder and Decoder). It specifies the mode in which MediaCodec APIs are working. For async mode, callbacks are set. For sync mode, we have to poll the dequeueBuffer APIs to queue and dequeue input output buffers respectively.
+
+6. **setupTime**: The time taken to set up the MediaExtractor/Muxer/Codec instance.
+
+ * MediaCodec: includes setting async/sync mode, configuring with a format and codec.start
+
+ * MediaExtractor: includes AMediaExtractor_new and setDataSource.
+
+ * MediaMuxer: includes creating the object, adding track, and starting the muxer.
+
+7. **destroyTime**: The time taken to stop and close MediaExtractor/Muxer/Codec instance.
+
+8. **minimumTime**: The minimum time taken to extract/mux/encode/decode a frame.
+
+9. **maximumTime**: The maximum time taken to extract/mux/encode/decode a frame.
+
+10. **averageTime**: Average time taken to extract/mux/encode/decode per frame.
+
+ * MediaCodec: computed as the total time taken to encode/decode all frames divided by the number of frames encoded/decoded.
+
+ * MediaExtractor: computed as the total time taken to extract all frames divided by the number of frames extracted.
+
+ * MediaMuxer: computed as the total time taken to mux all frames divided by the number of frames muxed.
+
+11. **timeToProcess1SecContent**: The time required to process one second worth input data.
+
+12. **totalBytesProcessedPerSec**: The number of bytes extracted/muxed/decoded/encoded per second.
+
+13. **timeToFirstFrame**: The time taken to receive the first output frame.
+
+14. **totalSizeInBytes**: The total output size of the operation (in bytes).
+
+15. **totalTime**: The time taken to perform the complete operation (i.e. Extract/Mux/Decode/Encode) for respective test vector.
+
+
+## Muxer
+1. **componentName**: The format of the output Media file. Following muxers are currently supported:
+ * Ogg, Webm, 3gpp, and mp4.
+
+## Decoder
+1. **componentName**: Includes all supported codecs on the device. Aliased components are skipped.
+ * Video: H263, H264, H265, VPx, Mpeg4, Mpeg2, AV1
+ * Audio: AAC, Flac, Opus, MP3, Vorbis, GSM, AMR-NB/WB
+
+## Encoder
+1. **componentName**: Includes all supported codecs on the device. Aliased components are skipped.
+ * Video: H263, H264, H265, VPx, Mpeg4
+ * Audio: AAC, Flac, Opus, AMR-NB/WB
+
+## Common Failures
+On some devices, if a codec isn't supported some tests may report a failure like "codec not found for"
+
+For example: On mobile devices without support for mpeg2 decoder, following failure is observed:
+```
+Unable to create codec by mime: video/mpeg2
+```
diff --git a/media/tests/benchmark/src/native/decoder/C2Decoder.cpp b/media/tests/benchmark/src/native/decoder/C2Decoder.cpp
index 20a1468..46c4a7c 100644
--- a/media/tests/benchmark/src/native/decoder/C2Decoder.cpp
+++ b/media/tests/benchmark/src/native/decoder/C2Decoder.cpp
@@ -156,9 +156,11 @@
mStats->setDeInitTime(timeTaken);
}
-void C2Decoder::dumpStatistics(string inputReference, int64_t durationUs) {
+void C2Decoder::dumpStatistics(string inputReference, int64_t durationUs, string componentName,
+ string statsFile) {
string operation = "c2decode";
- mStats->dumpStatistics(operation, inputReference, durationUs);
+ string mode = "async";
+ mStats->dumpStatistics(operation, inputReference, durationUs, componentName, mode, statsFile);
}
void C2Decoder::resetDecoder() {
diff --git a/media/tests/benchmark/src/native/decoder/C2Decoder.h b/media/tests/benchmark/src/native/decoder/C2Decoder.h
index 4a3eb96..fb35a66 100644
--- a/media/tests/benchmark/src/native/decoder/C2Decoder.h
+++ b/media/tests/benchmark/src/native/decoder/C2Decoder.h
@@ -31,7 +31,8 @@
void deInitCodec();
- void dumpStatistics(string inputReference, int64_t durationUs);
+ void dumpStatistics(string inputReference, int64_t durationUs, string componentName,
+ string statsFile);
void resetDecoder();
diff --git a/media/tests/benchmark/src/native/encoder/C2Encoder.cpp b/media/tests/benchmark/src/native/encoder/C2Encoder.cpp
index 33429ef..6a50d40 100644
--- a/media/tests/benchmark/src/native/encoder/C2Encoder.cpp
+++ b/media/tests/benchmark/src/native/encoder/C2Encoder.cpp
@@ -251,9 +251,11 @@
mStats->setDeInitTime(timeTaken);
}
-void C2Encoder::dumpStatistics(string inputReference, int64_t durationUs) {
+void C2Encoder::dumpStatistics(string inputReference, int64_t durationUs, string componentName,
+ string statsFile) {
string operation = "c2encode";
- mStats->dumpStatistics(operation, inputReference, durationUs);
+ string mode = "async";
+ mStats->dumpStatistics(operation, inputReference, durationUs, componentName, mode, statsFile);
}
void C2Encoder::resetEncoder() {
diff --git a/media/tests/benchmark/src/native/encoder/C2Encoder.h b/media/tests/benchmark/src/native/encoder/C2Encoder.h
index a4ca097..7a021f4 100644
--- a/media/tests/benchmark/src/native/encoder/C2Encoder.h
+++ b/media/tests/benchmark/src/native/encoder/C2Encoder.h
@@ -44,7 +44,8 @@
void deInitCodec();
- void dumpStatistics(string inputReference, int64_t durationUs);
+ void dumpStatistics(string inputReference, int64_t durationUs, string componentName,
+ string statsFile);
void resetEncoder();
diff --git a/media/tests/benchmark/src/native/encoder/Encoder.cpp b/media/tests/benchmark/src/native/encoder/Encoder.cpp
index 26fb1b9..15c479d 100644
--- a/media/tests/benchmark/src/native/encoder/Encoder.cpp
+++ b/media/tests/benchmark/src/native/encoder/Encoder.cpp
@@ -203,13 +203,13 @@
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_WIDTH, mParams.width);
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_HEIGHT, mParams.height);
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_FRAME_RATE, mParams.frameRate);
+ AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, mParams.iFrameInterval);
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_BIT_RATE, mParams.bitrate);
- AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1);
- if (mParams.profile && mParams.level) {
+ AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, mParams.colorFormat);
+ if (mParams.profile != -1 && mParams.level != -1) {
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_PROFILE, mParams.profile);
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_LEVEL, mParams.level);
}
- AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, mParams.colorFormat);
} else {
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, mParams.sampleRate);
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, mParams.numChannels);
diff --git a/media/tests/benchmark/src/native/encoder/Encoder.h b/media/tests/benchmark/src/native/encoder/Encoder.h
index 5ad142b..324317c 100644
--- a/media/tests/benchmark/src/native/encoder/Encoder.h
+++ b/media/tests/benchmark/src/native/encoder/Encoder.h
@@ -23,10 +23,11 @@
#include <queue>
#include <thread>
-#include "media/NdkImage.h"
#include "BenchmarkCommon.h"
#include "Stats.h"
+// constant not defined in NDK api
+constexpr int32_t COLOR_FormatYUV420Flexible = 0x7F420888;
struct encParameter {
int32_t bitrate = -1;
@@ -38,9 +39,10 @@
int32_t width = 0;
int32_t height = 0;
int32_t frameRate = -1;
- int32_t profile = 0;
- int32_t level = 0;
- int32_t colorFormat = AIMAGE_FORMAT_YUV_420_888;
+ int32_t iFrameInterval = 0;
+ int32_t profile = -1;
+ int32_t level = -1;
+ int32_t colorFormat = COLOR_FormatYUV420Flexible;
};
class Encoder : public CallBackHandle {
diff --git a/media/tests/benchmark/tests/BenchmarkTestEnvironment.h b/media/tests/benchmark/tests/BenchmarkTestEnvironment.h
index ae2eee1..4edb048 100644
--- a/media/tests/benchmark/tests/BenchmarkTestEnvironment.h
+++ b/media/tests/benchmark/tests/BenchmarkTestEnvironment.h
@@ -25,7 +25,9 @@
class BenchmarkTestEnvironment : public ::testing::Environment {
public:
- BenchmarkTestEnvironment() : res("/sdcard/media/") {}
+ BenchmarkTestEnvironment()
+ : res("/data/local/tmp/MediaBenchmark/res/"),
+ statsFile("/data/local/tmp/MediaBenchmark/res/stats.csv") {}
// Parses the command line argument
int initFromOptions(int argc, char **argv);
@@ -34,8 +36,15 @@
const string getRes() const { return res; }
+ void setStatsFile(const string module) { statsFile = getRes() + module; }
+
+ const string getStatsFile() const { return statsFile; }
+
+ bool writeStatsHeader();
+
private:
string res;
+ string statsFile;
};
int BenchmarkTestEnvironment::initFromOptions(int argc, char **argv) {
@@ -70,4 +79,26 @@
return 0;
}
+/**
+ * Writes the stats header to a file
+ * <p>
+ * \param statsFile file where the stats data is to be written
+ **/
+bool BenchmarkTestEnvironment::writeStatsHeader() {
+ char statsHeader[] =
+ "currentTime, fileName, operation, componentName, NDK/SDK, sync/async, setupTime, "
+ "destroyTime, minimumTime, maximumTime, averageTime, timeToProcess1SecContent, "
+ "totalBytesProcessedPerSec, timeToFirstFrame, totalSizeInBytes, totalTime\n";
+ FILE *fpStats = fopen(statsFile.c_str(), "w");
+ if(!fpStats) {
+ return false;
+ }
+ int32_t numBytes = fwrite(statsHeader, sizeof(char), sizeof(statsHeader), fpStats);
+ fclose(fpStats);
+ if(numBytes != sizeof(statsHeader)) {
+ return false;
+ }
+ return true;
+}
+
#endif // __BENCHMARK_TEST_ENVIRONMENT_H__
diff --git a/media/tests/benchmark/tests/C2DecoderTest.cpp b/media/tests/benchmark/tests/C2DecoderTest.cpp
index dedc743..85dcbc1 100644
--- a/media/tests/benchmark/tests/C2DecoderTest.cpp
+++ b/media/tests/benchmark/tests/C2DecoderTest.cpp
@@ -136,7 +136,8 @@
mDecoder->deInitCodec();
int64_t durationUs = extractor->getClipDuration();
ALOGV("codec : %s", codecName.c_str());
- mDecoder->dumpStatistics(GetParam().first, durationUs);
+ mDecoder->dumpStatistics(GetParam().first, durationUs, codecName,
+ gEnv->getStatsFile());
mDecoder->resetDecoder();
}
}
@@ -178,6 +179,9 @@
::testing::InitGoogleTest(&argc, argv);
int status = gEnv->initFromOptions(argc, argv);
if (status == 0) {
+ gEnv->setStatsFile("C2Decoder.csv");
+ status = gEnv->writeStatsHeader();
+ ALOGV("Stats file = %d\n", status);
status = RUN_ALL_TESTS();
ALOGV("C2 Decoder Test result = %d\n", status);
}
diff --git a/media/tests/benchmark/tests/C2EncoderTest.cpp b/media/tests/benchmark/tests/C2EncoderTest.cpp
index 98eb17a..b18d856 100644
--- a/media/tests/benchmark/tests/C2EncoderTest.cpp
+++ b/media/tests/benchmark/tests/C2EncoderTest.cpp
@@ -108,7 +108,7 @@
}
string decName = "";
- string outputFileName = "decode.out";
+ string outputFileName = "/data/local/tmp/decode.out";
FILE *outFp = fopen(outputFileName.c_str(), "wb");
ASSERT_NE(outFp, nullptr) << "Unable to open output file" << outputFileName
<< " for dumping decoder's output";
@@ -140,7 +140,8 @@
mEncoder->deInitCodec();
int64_t durationUs = extractor->getClipDuration();
ALOGV("codec : %s", codecName.c_str());
- mEncoder->dumpStatistics(GetParam().first, durationUs);
+ mEncoder->dumpStatistics(GetParam().first, durationUs, codecName,
+ gEnv->getStatsFile());
mEncoder->resetEncoder();
}
}
@@ -180,6 +181,9 @@
::testing::InitGoogleTest(&argc, argv);
int status = gEnv->initFromOptions(argc, argv);
if (status == 0) {
+ gEnv->setStatsFile("C2Encoder.csv");
+ status = gEnv->writeStatsHeader();
+ ALOGV("Stats file = %d\n", status);
status = RUN_ALL_TESTS();
ALOGV("C2 Encoder Test result = %d\n", status);
}
diff --git a/media/tests/benchmark/tests/DecoderTest.cpp b/media/tests/benchmark/tests/DecoderTest.cpp
index 9f96d3b..81ef02a 100644
--- a/media/tests/benchmark/tests/DecoderTest.cpp
+++ b/media/tests/benchmark/tests/DecoderTest.cpp
@@ -84,7 +84,8 @@
decoder->deInitCodec();
ALOGV("codec : %s", codecName.c_str());
string inputReference = get<0>(params);
- decoder->dumpStatistics(inputReference);
+ decoder->dumpStatistics(inputReference, codecName, (asyncMode ? "async" : "sync"),
+ gEnv->getStatsFile());
free(inputBuffer);
decoder->resetDecoder();
}
@@ -179,8 +180,11 @@
::testing::InitGoogleTest(&argc, argv);
int status = gEnv->initFromOptions(argc, argv);
if (status == 0) {
+ gEnv->setStatsFile("Decoder.csv");
+ status = gEnv->writeStatsHeader();
+ ALOGV("Stats file = %d\n", status);
status = RUN_ALL_TESTS();
- ALOGD("Decoder Test result = %d\n", status);
+ ALOGV("Decoder Test result = %d\n", status);
}
return status;
}
\ No newline at end of file
diff --git a/media/tests/benchmark/tests/EncoderTest.cpp b/media/tests/benchmark/tests/EncoderTest.cpp
index dc2a2dd..7e1681d 100644
--- a/media/tests/benchmark/tests/EncoderTest.cpp
+++ b/media/tests/benchmark/tests/EncoderTest.cpp
@@ -23,6 +23,10 @@
#include "Decoder.h"
#include "Encoder.h"
+constexpr int32_t kEncodeDefaultVideoBitRate = 8000000 /* 8 Mbps */;
+constexpr int32_t kEncodeMinVideoBitRate = 600000 /* 600 Kbps */;
+constexpr int32_t kEncodeDefaultAudioBitRate = 128000 /* 128 Kbps */;
+
static BenchmarkTestEnvironment *gEnv = nullptr;
class EncoderTest : public ::testing::TestWithParam<tuple<string, string, bool>> {};
@@ -78,7 +82,7 @@
}
string decName = "";
- string outputFileName = "decode.out";
+ string outputFileName = "/data/local/tmp/decode.out";
FILE *outFp = fopen(outputFileName.c_str(), "wb");
ASSERT_NE(outFp, nullptr) << "Unable to open output file" << outputFileName
<< " for dumping decoder's output";
@@ -86,6 +90,7 @@
decoder->setupDecoder();
status = decoder->decode(inputBuffer, frameInfo, decName, false /*asyncMode */, outFp);
ASSERT_EQ(status, AMEDIA_OK) << "Decode returned error : " << status;
+ AMediaFormat *decoderFormat = decoder->getFormat();
ifstream eleStream;
eleStream.open(outputFileName.c_str(), ifstream::binary | ifstream::ate);
@@ -108,11 +113,13 @@
if (encParams.bitrate <= 0 || encParams.frameRate <= 0) {
encParams.frameRate = 25;
if (!strcmp(mime, "video/3gpp") || !strcmp(mime, "video/mp4v-es")) {
- encParams.bitrate = 600000 /* 600 Kbps */;
+ encParams.bitrate = kEncodeMinVideoBitRate;
} else {
- encParams.bitrate = 8000000 /* 8 Mbps */;
+ encParams.bitrate = kEncodeDefaultVideoBitRate;
}
}
+ AMediaFormat_getInt32(decoderFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
+ &encParams.colorFormat);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_PROFILE, &encParams.profile);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_LEVEL, &encParams.level);
} else {
@@ -120,8 +127,7 @@
&encParams.sampleRate));
ASSERT_TRUE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
&encParams.numChannels));
- encParams.bitrate =
- encParams.sampleRate * encParams.numChannels * 16 /* bitsPerSample */;
+ encParams.bitrate = kEncodeDefaultAudioBitRate;
}
encoder->setupEncoder();
@@ -133,7 +139,8 @@
encoder->deInitCodec();
ALOGV("codec : %s", codecName.c_str());
string inputReference = get<0>(params);
- encoder->dumpStatistics(inputReference, extractor->getClipDuration());
+ encoder->dumpStatistics(inputReference, extractor->getClipDuration(), codecName,
+ (asyncMode ? "async" : "sync"), gEnv->getStatsFile());
eleStream.close();
if (outFp) fclose(outFp);
@@ -141,6 +148,10 @@
AMediaFormat_delete(format);
format = nullptr;
}
+ if (decoderFormat) {
+ AMediaFormat_delete(decoderFormat);
+ decoderFormat = nullptr;
+ }
encoder->resetEncoder();
decoder->deInitCodec();
free(inputBuffer);
@@ -214,8 +225,11 @@
::testing::InitGoogleTest(&argc, argv);
int status = gEnv->initFromOptions(argc, argv);
if (status == 0) {
+ gEnv->setStatsFile("Encoder.csv");
+ status = gEnv->writeStatsHeader();
+ ALOGV("Stats file = %d\n", status);
status = RUN_ALL_TESTS();
- ALOGD("Encoder Test result = %d\n", status);
+ ALOGV("Encoder Test result = %d\n", status);
}
return status;
}
diff --git a/media/tests/benchmark/tests/ExtractorTest.cpp b/media/tests/benchmark/tests/ExtractorTest.cpp
index ad8f1e6..d14d15b 100644
--- a/media/tests/benchmark/tests/ExtractorTest.cpp
+++ b/media/tests/benchmark/tests/ExtractorTest.cpp
@@ -48,8 +48,7 @@
ASSERT_EQ(status, AMEDIA_OK) << "Extraction failed \n";
extractObj->deInitExtractor();
-
- extractObj->dumpStatistics(GetParam().first);
+ extractObj->dumpStatistics(GetParam().first, "", gEnv->getStatsFile());
fclose(inputFp);
delete extractObj;
@@ -79,8 +78,11 @@
::testing::InitGoogleTest(&argc, argv);
int status = gEnv->initFromOptions(argc, argv);
if (status == 0) {
+ gEnv->setStatsFile("Extractor.csv");
+ status = gEnv->writeStatsHeader();
+ ALOGV("Stats file = %d\n", status);
status = RUN_ALL_TESTS();
- ALOGD(" Extractor Test result = %d\n", status);
+ ALOGV("Extractor Test result = %d\n", status);
}
return status;
}
diff --git a/media/tests/benchmark/tests/MuxerTest.cpp b/media/tests/benchmark/tests/MuxerTest.cpp
index fa2635d..991644b 100644
--- a/media/tests/benchmark/tests/MuxerTest.cpp
+++ b/media/tests/benchmark/tests/MuxerTest.cpp
@@ -113,7 +113,7 @@
ASSERT_EQ(status, 0) << "Mux failed";
muxerObj->deInitMuxer();
- muxerObj->dumpStatistics(GetParam().first + "." + fmt.c_str());
+ muxerObj->dumpStatistics(GetParam().first + "." + fmt.c_str(), fmt, gEnv->getStatsFile());
free(inputBuffer);
fclose(outputFp);
muxerObj->resetMuxer();
@@ -151,8 +151,11 @@
::testing::InitGoogleTest(&argc, argv);
int status = gEnv->initFromOptions(argc, argv);
if (status == 0) {
+ gEnv->setStatsFile("Muxer.csv");
+ status = gEnv->writeStatsHeader();
+ ALOGV("Stats file = %d\n", status);
status = RUN_ALL_TESTS();
- ALOGV("Test result = %d\n", status);
+ ALOGV("Muxer Test result = %d\n", status);
}
return status;
}
diff --git a/media/utils/fuzzers/Android.bp b/media/utils/fuzzers/Android.bp
new file mode 100644
index 0000000..ca1123c
--- /dev/null
+++ b/media/utils/fuzzers/Android.bp
@@ -0,0 +1,51 @@
+cc_defaults {
+ name: "libmediautils_fuzzer_defaults",
+ shared_libs: [
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libmediautils",
+ "libutils",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wno-c++2a-extensions",
+ ],
+
+ header_libs: [
+ "bionic_libc_platform_headers",
+ "libmedia_headers",
+ ],
+
+ include_dirs: [
+ // For DEBUGGER_SIGNAL
+ "system/core/debuggerd/include",
+ ],
+}
+
+cc_fuzz {
+ name: "libmediautils_fuzzer_battery_notifier",
+ defaults: ["libmediautils_fuzzer_defaults"],
+ srcs: ["BatteryNotifierFuzz.cpp"],
+}
+
+cc_fuzz {
+ name: "libmediautils_fuzzer_scheduling_policy_service",
+ defaults: ["libmediautils_fuzzer_defaults"],
+ srcs: ["SchedulingPolicyServiceFuzz.cpp"],
+}
+
+cc_fuzz {
+ name: "libmediautils_fuzzer_service_utilities",
+ defaults: ["libmediautils_fuzzer_defaults"],
+ srcs: ["ServiceUtilitiesFuzz.cpp"],
+}
+
+cc_fuzz {
+ name: "libmediautils_fuzzer_time_check",
+ defaults: ["libmediautils_fuzzer_defaults"],
+ srcs: ["TimeCheckFuzz.cpp"],
+}
diff --git a/media/utils/fuzzers/BatteryNotifierFuzz.cpp b/media/utils/fuzzers/BatteryNotifierFuzz.cpp
new file mode 100644
index 0000000..00b3cce
--- /dev/null
+++ b/media/utils/fuzzers/BatteryNotifierFuzz.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 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 <functional>
+#include <string>
+#include <vector>
+
+#include <utils/String8.h>
+
+#include "fuzzer/FuzzedDataProvider.h"
+#include "mediautils/BatteryNotifier.h"
+
+static constexpr int kMaxOperations = 30;
+static constexpr int kMaxStringLength = 500;
+using android::BatteryNotifier;
+
+std::vector<std::function<void(std::string /*flashlight_name*/, std::string /*camera_name*/,
+ uid_t /*video_id*/, uid_t /*audio_id*/, uid_t /*light_id*/,
+ uid_t /*camera_id*/)>>
+ operations = {
+ [](std::string, std::string, uid_t, uid_t, uid_t, uid_t) -> void {
+ BatteryNotifier::getInstance().noteResetVideo();
+ },
+ [](std::string, std::string, uid_t, uid_t, uid_t, uid_t) -> void {
+ BatteryNotifier::getInstance().noteResetAudio();
+ },
+ [](std::string, std::string, uid_t, uid_t, uid_t, uid_t) -> void {
+ BatteryNotifier::getInstance().noteResetFlashlight();
+ },
+ [](std::string, std::string, uid_t, uid_t, uid_t, uid_t) -> void {
+ BatteryNotifier::getInstance().noteResetCamera();
+ },
+ [](std::string, std::string, uid_t video_id, uid_t, uid_t, uid_t) -> void {
+ BatteryNotifier::getInstance().noteStartVideo(video_id);
+ },
+ [](std::string, std::string, uid_t video_id, uid_t, uid_t, uid_t) -> void {
+ BatteryNotifier::getInstance().noteStopVideo(video_id);
+ },
+ [](std::string, std::string, uid_t, uid_t audio_id, uid_t, uid_t) -> void {
+ BatteryNotifier::getInstance().noteStartAudio(audio_id);
+ },
+ [](std::string, std::string, uid_t, uid_t audio_id, uid_t, uid_t) -> void {
+ BatteryNotifier::getInstance().noteStopAudio(audio_id);
+ },
+ [](std::string flashlight_name, std::string, uid_t, uid_t, uid_t light_id, uid_t) -> void {
+ android::String8 name(flashlight_name.c_str());
+ BatteryNotifier::getInstance().noteFlashlightOn(name, light_id);
+ },
+ [](std::string flashlight_name, std::string, uid_t, uid_t, uid_t light_id, uid_t) -> void {
+ android::String8 name(flashlight_name.c_str());
+ BatteryNotifier::getInstance().noteFlashlightOff(name, light_id);
+ },
+ [](std::string, std::string camera_name, uid_t, uid_t, uid_t, uid_t camera_id) -> void {
+ android::String8 name(camera_name.c_str());
+ BatteryNotifier::getInstance().noteStartCamera(name, camera_id);
+ },
+ [](std::string, std::string camera_name, uid_t, uid_t, uid_t, uid_t camera_id) -> void {
+ android::String8 name(camera_name.c_str());
+ BatteryNotifier::getInstance().noteStopCamera(name, camera_id);
+ },
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider data_provider(data, size);
+ std::string camera_name = data_provider.ConsumeRandomLengthString(kMaxStringLength);
+ std::string flashlight_name = data_provider.ConsumeRandomLengthString(kMaxStringLength);
+ uid_t video_id = data_provider.ConsumeIntegral<uid_t>();
+ uid_t audio_id = data_provider.ConsumeIntegral<uid_t>();
+ uid_t light_id = data_provider.ConsumeIntegral<uid_t>();
+ uid_t camera_id = data_provider.ConsumeIntegral<uid_t>();
+ size_t ops_run = 0;
+ while (data_provider.remaining_bytes() > 0 && ops_run++ < kMaxOperations) {
+ uint8_t op = data_provider.ConsumeIntegralInRange<uint8_t>(0, operations.size() - 1);
+ operations[op](flashlight_name, camera_name, video_id, audio_id, light_id, camera_id);
+ }
+ return 0;
+}
diff --git a/media/utils/fuzzers/SchedulingPolicyServiceFuzz.cpp b/media/utils/fuzzers/SchedulingPolicyServiceFuzz.cpp
new file mode 100644
index 0000000..4521853
--- /dev/null
+++ b/media/utils/fuzzers/SchedulingPolicyServiceFuzz.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 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_TAG "BatteryNotifierFuzzer"
+#include <binder/IBatteryStats.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+#include <android/log.h>
+#include <mediautils/SchedulingPolicyService.h>
+#include "fuzzer/FuzzedDataProvider.h"
+using android::IBatteryStats;
+using android::IBinder;
+using android::IInterface;
+using android::IServiceManager;
+using android::sp;
+using android::String16;
+using android::defaultServiceManager;
+using android::requestCpusetBoost;
+using android::requestPriority;
+sp<IBatteryStats> getBatteryService() {
+ sp<IBatteryStats> batteryStatService;
+ const sp<IServiceManager> sm(defaultServiceManager());
+ if (sm != nullptr) {
+ const String16 name("batterystats");
+ batteryStatService = checked_interface_cast<IBatteryStats>(sm->checkService(name));
+ if (batteryStatService == nullptr) {
+ ALOGW("batterystats service unavailable!");
+ return nullptr;
+ }
+ }
+ return batteryStatService;
+}
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider data_provider(data, size);
+ sp<IBatteryStats> batteryStatService = getBatteryService();
+ // There is some state here, but it's mostly focused around thread-safety, so
+ // we won't worry about order.
+ int32_t priority = data_provider.ConsumeIntegral<int32_t>();
+ bool is_for_app = data_provider.ConsumeBool();
+ bool async = data_provider.ConsumeBool();
+ requestPriority(getpid(), gettid(), priority, is_for_app, async);
+ // TODO: Verify and re-enable in AOSP (R).
+ // bool enable = data_provider.ConsumeBool();
+ // We are just using batterystats to avoid the need
+ // to register a new service.
+ // requestCpusetBoost(enable, IInterface::asBinder(batteryStatService));
+ return 0;
+}
+
diff --git a/media/utils/fuzzers/ServiceUtilitiesFuzz.cpp b/media/utils/fuzzers/ServiceUtilitiesFuzz.cpp
new file mode 100644
index 0000000..3d141b5
--- /dev/null
+++ b/media/utils/fuzzers/ServiceUtilitiesFuzz.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright 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 <fcntl.h>
+
+#include <functional>
+
+#include "fuzzer/FuzzedDataProvider.h"
+#include "mediautils/ServiceUtilities.h"
+
+static constexpr int kMaxOperations = 50;
+static constexpr int kMaxStringLen = 256;
+
+const std::vector<std::function<void(FuzzedDataProvider*, android::MediaPackageManager)>>
+ operations = {
+ [](FuzzedDataProvider* data_provider, android::MediaPackageManager pm) -> void {
+ uid_t uid = data_provider->ConsumeIntegral<uid_t>();
+ pm.allowPlaybackCapture(uid);
+ },
+ [](FuzzedDataProvider* data_provider, android::MediaPackageManager pm) -> void {
+ int spaces = data_provider->ConsumeIntegral<int>();
+
+ // Dump everything into /dev/null
+ int fd = open("/dev/null", O_WRONLY);
+ pm.dump(fd, spaces);
+ close(fd);
+ },
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider data_provider(data, size);
+ uid_t uid = data_provider.ConsumeIntegral<uid_t>();
+ pid_t pid = data_provider.ConsumeIntegral<pid_t>();
+
+ // There is not state here, and order is not significant,
+ // so we can simply call all of the target functions
+ android::isServiceUid(uid);
+ android::isAudioServerUid(uid);
+ android::isAudioServerOrSystemServerUid(uid);
+ android::isAudioServerOrMediaServerUid(uid);
+ std::string packageNameStr = data_provider.ConsumeRandomLengthString(kMaxStringLen);
+ android::String16 opPackageName(packageNameStr.c_str());
+ android::recordingAllowed(opPackageName, pid, uid);
+ android::startRecording(opPackageName, pid, uid);
+ android::finishRecording(opPackageName, uid);
+ android::captureAudioOutputAllowed(pid, uid);
+ android::captureMediaOutputAllowed(pid, uid);
+ android::captureHotwordAllowed(opPackageName, pid, uid);
+ android::modifyPhoneStateAllowed(uid, pid);
+ android::bypassInterruptionPolicyAllowed(uid, pid);
+ android::settingsAllowed();
+ android::modifyAudioRoutingAllowed();
+ android::modifyDefaultAudioEffectsAllowed();
+ android::dumpAllowed();
+
+ // MediaPackageManager does have state, so we need the fuzzer to decide order
+ android::MediaPackageManager packageManager;
+ size_t ops_run = 0;
+ while (data_provider.remaining_bytes() > 0 && ops_run++ < kMaxOperations) {
+ uint8_t op = data_provider.ConsumeIntegralInRange<uint8_t>(0, operations.size() - 1);
+ operations[op](&data_provider, packageManager);
+ }
+
+ return 0;
+}
diff --git a/media/utils/fuzzers/TimeCheckFuzz.cpp b/media/utils/fuzzers/TimeCheckFuzz.cpp
new file mode 100644
index 0000000..eeb6ba6
--- /dev/null
+++ b/media/utils/fuzzers/TimeCheckFuzz.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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 <chrono>
+#include <thread>
+
+#include "fuzzer/FuzzedDataProvider.h"
+#include "mediautils/TimeCheck.h"
+
+static constexpr int kMaxStringLen = 256;
+
+// While it might be interesting to test long-running
+// jobs, it seems unlikely it'd lead to the types of crashes
+// we're looking for, and would mean a significant increase in fuzzer time.
+// Therefore, we are setting a low cap.
+static constexpr uint32_t kMaxTimeoutMs = 1000;
+static constexpr uint32_t kMinTimeoutMs = 200;
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider data_provider(data, size);
+
+ // There's essentially 5 operations that we can access in this class
+ // 1. The time it takes to run this operation. As mentioned above,
+ // long-running tasks are not good for fuzzing, but there will be
+ // some change in the run time.
+ uint32_t timeoutMs =
+ data_provider.ConsumeIntegralInRange<uint32_t>(kMinTimeoutMs, kMaxTimeoutMs);
+ uint8_t pid_size = data_provider.ConsumeIntegral<uint8_t>();
+ std::vector<pid_t> pids(pid_size);
+ for (auto& pid : pids) {
+ pid = data_provider.ConsumeIntegral<pid_t>();
+ }
+
+ // 2. We also have setAudioHalPids, which is populated with the pids set
+ // above.
+ android::TimeCheck::setAudioHalPids(pids);
+ std::string name = data_provider.ConsumeRandomLengthString(kMaxStringLen);
+
+ // 3. The constructor, which is fuzzed here:
+ android::TimeCheck timeCheck(name.c_str(), timeoutMs);
+ // We will leave some buffer to avoid sleeping too long
+ uint8_t sleep_amount_ms = data_provider.ConsumeIntegralInRange<uint8_t>(0, timeoutMs / 2);
+
+ // We want to make sure we can cover the time out functionality.
+ if (sleep_amount_ms) {
+ auto ms = std::chrono::milliseconds(sleep_amount_ms);
+ std::this_thread::sleep_for(ms);
+ }
+
+ // 4. Finally, the destructor on timecheck. These seem to be the only factors
+ // in play.
+ return 0;
+}
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index de7ae40..136ae98 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -61,6 +61,7 @@
#include <system/audio_effects/effect_visualizer.h>
#include <system/audio_effects/effect_ns.h>
#include <system/audio_effects/effect_aec.h>
+#include <system/audio_effects/effect_hapticgenerator.h>
#include <audio_utils/primitives.h>
@@ -398,7 +399,7 @@
return ret;
}
}
- return AudioMixer::HAPTIC_SCALE_MUTE;
+ return static_cast<int>(os::HapticScale::MUTE);
}
/* static */
@@ -1176,6 +1177,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 +1746,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;
}
@@ -3289,6 +3298,16 @@
return minThread;
}
+AudioFlinger::ThreadBase *AudioFlinger::hapticPlaybackThread_l() const {
+ for (size_t i = 0; i < mPlaybackThreads.size(); ++i) {
+ PlaybackThread *thread = mPlaybackThreads.valueAt(i).get();
+ if (thread->hapticChannelMask() != AUDIO_CHANNEL_NONE) {
+ return thread;
+ }
+ }
+ return nullptr;
+}
+
sp<AudioFlinger::SyncEvent> AudioFlinger::createSyncEvent(AudioSystem::sync_event_t type,
audio_session_t triggerSession,
audio_session_t listenerSession,
@@ -3530,6 +3549,16 @@
goto Exit;
}
+ const bool hapticPlaybackRequired = EffectModule::isHapticGenerator(&desc.type);
+ if (hapticPlaybackRequired
+ && (sessionId == AUDIO_SESSION_DEVICE
+ || sessionId == AUDIO_SESSION_OUTPUT_MIX
+ || sessionId == AUDIO_SESSION_OUTPUT_STAGE)) {
+ // haptic-generating effect is only valid when the session id is a general session id
+ lStatus = INVALID_OPERATION;
+ goto Exit;
+ }
+
// return effect descriptor
*pDesc = desc;
if (io == AUDIO_IO_HANDLE_NONE && sessionId == AUDIO_SESSION_OUTPUT_MIX) {
@@ -3604,7 +3633,17 @@
// allow only one effect chain per sessionId on mPlaybackThreads.
for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
const audio_io_handle_t checkIo = mPlaybackThreads.keyAt(i);
- if (io == checkIo) continue;
+ if (io == checkIo) {
+ if (hapticPlaybackRequired
+ && mPlaybackThreads.valueAt(i)
+ ->hapticChannelMask() == AUDIO_CHANNEL_NONE) {
+ ALOGE("%s: haptic playback thread is required while the required playback "
+ "thread(io=%d) doesn't support", __func__, (int)io);
+ lStatus = BAD_VALUE;
+ goto Exit;
+ }
+ continue;
+ }
const uint32_t sessionType =
mPlaybackThreads.valueAt(i)->hasAudioSession(sessionId);
if ((sessionType & ThreadBase::EFFECT_SESSION) != 0) {
@@ -3641,6 +3680,20 @@
// create effect on selected output thread
bool pinned = !audio_is_global_session(sessionId) && isSessionAcquired_l(sessionId);
+ ThreadBase *oriThread = nullptr;
+ if (hapticPlaybackRequired && thread->hapticChannelMask() == AUDIO_CHANNEL_NONE) {
+ ThreadBase *hapticThread = hapticPlaybackThread_l();
+ if (hapticThread == nullptr) {
+ ALOGE("%s haptic thread not found while it is required", __func__);
+ lStatus = INVALID_OPERATION;
+ goto Exit;
+ }
+ if (hapticThread != thread) {
+ // Force to use haptic thread for haptic-generating effect.
+ oriThread = thread;
+ thread = hapticThread;
+ }
+ }
handle = thread->createEffect_l(client, effectClient, priority, sessionId,
&desc, enabled, &lStatus, pinned, probe);
if (lStatus != NO_ERROR && lStatus != ALREADY_EXISTS) {
@@ -3650,6 +3703,11 @@
} else {
// handle must be valid here, but check again to be safe.
if (handle.get() != nullptr && id != nullptr) *id = handle->id();
+ // Invalidate audio session when haptic playback is created.
+ if (hapticPlaybackRequired && oriThread != nullptr) {
+ // invalidateTracksForAudioSession will trigger locking the thread.
+ oriThread->invalidateTracksForAudioSession(sessionId);
+ }
}
}
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 20f561e..d3ad908 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -91,13 +91,14 @@
#include "ThreadMetrics.h"
#include "TrackMetrics.h"
-#include <powermanager/IPowerManager.h>
+#include <android/os/IPowerManager.h>
#include <media/nblog/NBLog.h>
#include <private/media/AudioEffectShared.h>
#include <private/media/AudioTrackShared.h>
#include <vibrator/ExternalVibration.h>
+#include <vibrator/ExternalVibrationUtils.h>
#include "android/media/BnAudioRecord.h"
@@ -756,6 +757,8 @@
sp<ThreadBase> getEffectThread_l(audio_session_t sessionId, int effectId);
+ ThreadBase *hapticPlaybackThread_l() const;
+
void removeClient_l(pid_t pid);
void removeNotificationClient(pid_t pid);
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/DeviceEffectManager.h b/services/audioflinger/DeviceEffectManager.h
index 81e6065..c6d2110 100644
--- a/services/audioflinger/DeviceEffectManager.h
+++ b/services/audioflinger/DeviceEffectManager.h
@@ -165,6 +165,7 @@
uint32_t sampleRate() const override { return 0; }
audio_channel_mask_t channelMask() const override { return AUDIO_CHANNEL_NONE; }
uint32_t channelCount() const override { return 0; }
+ audio_channel_mask_t hapticChannelMask() const override { return AUDIO_CHANNEL_NONE; }
size_t frameCount() const override { return 0; }
uint32_t latency() const override { return 0; }
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index 55f2952..9ee47c9 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -25,6 +25,7 @@
#include <utils/Log.h>
#include <system/audio_effects/effect_aec.h>
#include <system/audio_effects/effect_dynamicsprocessing.h>
+#include <system/audio_effects/effect_hapticgenerator.h>
#include <system/audio_effects/effect_ns.h>
#include <system/audio_effects/effect_visualizer.h>
#include <audio_utils/channels.h>
@@ -879,6 +880,11 @@
}
#endif
}
+ if (isHapticGenerator()) {
+ audio_channel_mask_t hapticChannelMask = mCallback->hapticChannelMask();
+ mConfig.inputCfg.channels |= hapticChannelMask;
+ mConfig.outputCfg.channels |= hapticChannelMask;
+ }
mInChannelCountRequested =
audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
mOutChannelCountRequested =
@@ -1522,6 +1528,42 @@
return mOffloaded;
}
+/*static*/
+bool AudioFlinger::EffectModule::isHapticGenerator(const effect_uuid_t *type) {
+ return memcmp(type, FX_IID_HAPTICGENERATOR, sizeof(effect_uuid_t)) == 0;
+}
+
+bool AudioFlinger::EffectModule::isHapticGenerator() const {
+ return isHapticGenerator(&mDescriptor.type);
+}
+
+status_t AudioFlinger::EffectModule::setHapticIntensity(int id, int intensity)
+{
+ if (mStatus != NO_ERROR) {
+ return mStatus;
+ }
+ if (!isHapticGenerator()) {
+ ALOGW("Should not set haptic intensity for effects that are not HapticGenerator");
+ return INVALID_OPERATION;
+ }
+
+ uint32_t buf32[sizeof(effect_param_t) / sizeof(uint32_t) + 3];
+ effect_param_t *param = (effect_param_t*) buf32;
+ param->psize = sizeof(int32_t);
+ param->vsize = sizeof(int32_t) * 2;
+ *(int32_t*)param->data = HG_PARAM_HAPTIC_INTENSITY;
+ *((int32_t*)param->data + 1) = id;
+ *((int32_t*)param->data + 2) = intensity;
+ uint32_t size = sizeof(int32_t);
+ status_t status = command(
+ EFFECT_CMD_SET_PARAM, sizeof(effect_param_t) + param->psize + param->vsize,
+ param, &size, ¶m->status);
+ if (status == NO_ERROR) {
+ status = param->status;
+ }
+ return status;
+}
+
static std::string dumpInOutBuffer(bool isInput, const sp<EffectBufferHalInterface> &buffer) {
std::stringstream ss;
@@ -2385,6 +2427,25 @@
}
}
+// containsHapticGeneratingEffect_l must be called with ThreadBase::mLock or EffectChain::mLock held
+bool AudioFlinger::EffectChain::containsHapticGeneratingEffect_l()
+{
+ for (size_t i = 0; i < mEffects.size(); ++i) {
+ if (mEffects[i]->isHapticGenerator()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void AudioFlinger::EffectChain::setHapticIntensity_l(int id, int intensity)
+{
+ Mutex::Autolock _l(mLock);
+ for (size_t i = 0; i < mEffects.size(); ++i) {
+ mEffects[i]->setHapticIntensity(id, intensity);
+ }
+}
+
void AudioFlinger::EffectChain::syncHalEffectsState()
{
Mutex::Autolock _l(mLock);
@@ -2812,7 +2873,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 {
@@ -2839,6 +2900,14 @@
return t->channelCount();
}
+audio_channel_mask_t AudioFlinger::EffectChain::EffectCallback::hapticChannelMask() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return AUDIO_CHANNEL_NONE;
+ }
+ return t->hapticChannelMask();
+}
+
size_t AudioFlinger::EffectChain::EffectCallback::frameCount() const {
sp<ThreadBase> t = mThread.promote();
if (t == nullptr) {
diff --git a/services/audioflinger/Effects.h b/services/audioflinger/Effects.h
index 2826297..3cc5a44 100644
--- a/services/audioflinger/Effects.h
+++ b/services/audioflinger/Effects.h
@@ -36,6 +36,7 @@
virtual uint32_t sampleRate() const = 0;
virtual audio_channel_mask_t channelMask() const = 0;
virtual uint32_t channelCount() const = 0;
+ virtual audio_channel_mask_t hapticChannelMask() const = 0;
virtual size_t frameCount() const = 0;
// Non trivial methods usually implemented with help from ThreadBase:
@@ -257,6 +258,11 @@
sp<EffectModule> asEffectModule() override { return this; }
+ static bool isHapticGenerator(const effect_uuid_t* type);
+ bool isHapticGenerator() const;
+
+ status_t setHapticIntensity(int id, int intensity);
+
void dump(int fd, const Vector<String16>& args);
private:
@@ -503,6 +509,10 @@
// isCompatibleWithThread_l() must be called with thread->mLock held
bool isCompatibleWithThread_l(const sp<ThreadBase>& thread) const;
+ bool containsHapticGeneratingEffect_l();
+
+ void setHapticIntensity_l(int id, int intensity);
+
sp<EffectCallbackInterface> effectCallback() const { return mEffectCallback; }
wp<ThreadBase> thread() const { return mEffectCallback->thread(); }
@@ -534,6 +544,7 @@
uint32_t sampleRate() const override;
audio_channel_mask_t channelMask() const override;
uint32_t channelCount() const override;
+ audio_channel_mask_t hapticChannelMask() const override;
size_t frameCount() const override;
uint32_t latency() const override;
@@ -685,6 +696,7 @@
uint32_t sampleRate() const override;
audio_channel_mask_t channelMask() const override;
uint32_t channelCount() const override;
+ audio_channel_mask_t hapticChannelMask() const override { return AUDIO_CHANNEL_NONE; }
size_t frameCount() const override { return 0; }
uint32_t latency() const override { return 0; }
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/FastMixer.cpp b/services/audioflinger/FastMixer.cpp
index 3eacc8c..bf2e953 100644
--- a/services/audioflinger/FastMixer.cpp
+++ b/services/audioflinger/FastMixer.cpp
@@ -40,6 +40,7 @@
#include <audio_utils/channels.h>
#include <audio_utils/format.h>
#include <audio_utils/mono_blend.h>
+#include <cutils/bitops.h>
#include <media/AudioMixer.h>
#include "FastMixer.h"
#include "TypedLogger.h"
diff --git a/services/audioflinger/FastMixerState.h b/services/audioflinger/FastMixerState.h
index 396c797..857d3de 100644
--- a/services/audioflinger/FastMixerState.h
+++ b/services/audioflinger/FastMixerState.h
@@ -23,6 +23,7 @@
#include <media/ExtendedAudioBufferProvider.h>
#include <media/nbaio/NBAIO.h>
#include <media/nblog/NBLog.h>
+#include <vibrator/ExternalVibrationUtils.h>
#include "FastThreadState.h"
namespace android {
@@ -49,8 +50,7 @@
audio_format_t mFormat; // track format
int mGeneration; // increment when any field is assigned
bool mHapticPlaybackEnabled = false; // haptic playback is enabled or not
- AudioMixer::haptic_intensity_t mHapticIntensity = AudioMixer::HAPTIC_SCALE_MUTE; // intensity of
- // haptic data
+ os::HapticScale mHapticIntensity = os::HapticScale::MUTE; // intensity of haptic data
};
// Represents a single state of the fast mixer
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index d8eebf3..d05c8b8 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -159,12 +159,12 @@
mHapticPlaybackEnabled = hapticPlaybackEnabled;
}
/** Return at what intensity to play haptics, used in mixer. */
- AudioMixer::haptic_intensity_t getHapticIntensity() const { return mHapticIntensity; }
+ os::HapticScale getHapticIntensity() const { return mHapticIntensity; }
/** Set intensity of haptic playback, should be set after querying vibrator service. */
- void setHapticIntensity(AudioMixer::haptic_intensity_t hapticIntensity) {
- if (AudioMixer::isValidHapticIntensity(hapticIntensity)) {
+ void setHapticIntensity(os::HapticScale hapticIntensity) {
+ if (os::isValidHapticScale(hapticIntensity)) {
mHapticIntensity = hapticIntensity;
- setHapticPlaybackEnabled(mHapticIntensity != AudioMixer::HAPTIC_SCALE_MUTE);
+ setHapticPlaybackEnabled(mHapticIntensity != os::HapticScale::MUTE);
}
}
sp<os::ExternalVibration> getExternalVibration() const { return mExternalVibration; }
@@ -265,7 +265,7 @@
bool mHapticPlaybackEnabled = false; // indicates haptic playback enabled or not
// intensity to play haptic data
- AudioMixer::haptic_intensity_t mHapticIntensity = AudioMixer::HAPTIC_SCALE_MUTE;
+ os::HapticScale mHapticIntensity = os::HapticScale::MUTE;
class AudioVibrationController : public os::BnExternalVibrationController {
public:
explicit AudioVibrationController(Track* track) : mTrack(track) {}
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..92f18c1 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -29,6 +29,7 @@
#include <linux/futex.h>
#include <sys/stat.h>
#include <sys/syscall.h>
+#include <cutils/bitops.h>
#include <cutils/properties.h>
#include <media/AudioContainers.h>
#include <media/AudioDeviceTypeAddr.h>
@@ -484,8 +485,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 +970,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");
@@ -981,15 +986,16 @@
if (mPowerManager != 0) {
sp<IBinder> binder = new BBinder();
// Uses AID_AUDIOSERVER for wakelock. updateWakeLockUids_l() updates with client uids.
- status_t status = mPowerManager->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK,
- binder,
+ binder::Status status = mPowerManager->acquireWakeLockAsync(binder,
+ POWERMANAGER_PARTIAL_WAKE_LOCK,
getWakeLockTag(),
String16("audioserver"),
- true /* FIXME force oneway contrary to .aidl */);
- if (status == NO_ERROR) {
+ {} /* workSource */,
+ {} /* historyTag */);
+ if (status.isOk()) {
mWakeLockToken = binder;
}
- ALOGV("acquireWakeLock_l() %s status %d", mThreadName, status);
+ ALOGV("acquireWakeLock_l() %s status %d", mThreadName, status.exceptionCode());
}
gBoottime.acquire(mWakeLockToken);
@@ -1009,8 +1015,7 @@
if (mWakeLockToken != 0) {
ALOGV("releaseWakeLock_l() %s", mThreadName);
if (mPowerManager != 0) {
- mPowerManager->releaseWakeLock(mWakeLockToken, 0,
- true /* FIXME force oneway contrary to .aidl */);
+ mPowerManager->releaseWakeLockAsync(mWakeLockToken, 0);
}
mWakeLockToken.clear();
}
@@ -1024,7 +1029,7 @@
if (binder == 0) {
ALOGW("Thread %s cannot connect to the power manager service", mThreadName);
} else {
- mPowerManager = interface_cast<IPowerManager>(binder);
+ mPowerManager = interface_cast<os::IPowerManager>(binder);
binder->linkToDeath(mDeathRecipient);
}
}
@@ -1051,10 +1056,9 @@
}
if (mPowerManager != 0) {
std::vector<int> uidsAsInt(uids.begin(), uids.end()); // powermanager expects uids as ints
- status_t status = mPowerManager->updateWakeLockUids(
- mWakeLockToken, uidsAsInt.size(), uidsAsInt.data(),
- true /* FIXME force oneway contrary to .aidl */);
- ALOGV("updateWakeLockUids_l() %s status %d", mThreadName, status);
+ binder::Status status = mPowerManager->updateWakeLockUidsAsync(
+ mWakeLockToken, uidsAsInt);
+ ALOGV("updateWakeLockUids_l() %s status %d", mThreadName, status.exceptionCode());
}
}
@@ -1239,6 +1243,11 @@
return BAD_VALUE;
}
}
+
+ if (EffectModule::isHapticGenerator(&desc->type)) {
+ ALOGE("%s(): HapticGenerator is not supported in RecordThread", __func__);
+ return BAD_VALUE;
+ }
return NO_ERROR;
}
@@ -1258,6 +1267,12 @@
return NO_ERROR;
}
+ if (EffectModule::isHapticGenerator(&desc->type) && mHapticChannelCount == 0) {
+ ALOGW("%s: thread doesn't support haptic playback while the effect is HapticGenerator",
+ __func__);
+ return BAD_VALUE;
+ }
+
switch (mType) {
case MIXER: {
#ifndef MULTICHANNEL_EFFECT_CHAIN
@@ -1477,7 +1492,7 @@
}
void AudioFlinger::ThreadBase::onEffectEnable(const sp<EffectModule>& effect) {
- if (mType == OFFLOAD || mType == MMAP) {
+ if (isOffloadOrMmap()) {
Mutex::Autolock _l(mLock);
broadcast_l();
}
@@ -1493,7 +1508,7 @@
}
void AudioFlinger::ThreadBase::onEffectDisable() {
- if (mType == OFFLOAD || mType == MMAP) {
+ if (isOffloadOrMmap()) {
Mutex::Autolock _l(mLock);
broadcast_l();
}
@@ -2523,15 +2538,17 @@
track->sharedBuffer() != 0 ? Track::FS_FILLED : Track::FS_FILLING;
}
- if ((track->channelMask() & AUDIO_CHANNEL_HAPTIC_ALL) != AUDIO_CHANNEL_NONE
- && mHapticChannelMask != AUDIO_CHANNEL_NONE) {
+ sp<EffectChain> chain = getEffectChain_l(track->sessionId());
+ if (mHapticChannelMask != AUDIO_CHANNEL_NONE
+ && ((track->channelMask() & AUDIO_CHANNEL_HAPTIC_ALL) != AUDIO_CHANNEL_NONE
+ || (chain != nullptr && chain->containsHapticGeneratingEffect_l()))) {
// Unlock due to VibratorService will lock for this call and will
// call Tracks.mute/unmute which also require thread's lock.
mLock.unlock();
const int intensity = AudioFlinger::onExternalVibrationStart(
track->getExternalVibration());
mLock.lock();
- track->setHapticIntensity(static_cast<AudioMixer::haptic_intensity_t>(intensity));
+ track->setHapticIntensity(static_cast<os::HapticScale>(intensity));
// Haptic playback should be enabled by vibrator service.
if (track->getHapticPlaybackEnabled()) {
// Disable haptic playback of all active track to ensure only
@@ -2540,12 +2557,16 @@
t->setHapticPlaybackEnabled(false);
}
}
+
+ // Set haptic intensity for effect
+ if (chain != nullptr) {
+ chain->setHapticIntensity_l(track->id(), intensity);
+ }
}
track->mResetDone = false;
track->mPresentationCompleteFrames = 0;
mActiveTracks.add(track);
- sp<EffectChain> chain = getEffectChain_l(track->sessionId());
if (chain != 0) {
ALOGV("addTrack_l() starting track on chain %p for session %d", chain.get(),
track->sessionId());
@@ -3477,7 +3498,7 @@
// latency of 5 seconds).
const double minLatency = 0., maxLatency = 5000.;
if (latencyMs >= minLatency && latencyMs <= maxLatency) {
- ALOGV("new downstream latency %lf ms", latencyMs);
+ ALOGVV("new downstream latency %lf ms", latencyMs);
} else {
ALOGD("out of range downstream latency %lf ms", latencyMs);
if (latencyMs < minLatency) latencyMs = minLatency;
@@ -3526,7 +3547,7 @@
mSampleRate);
if (isTimestampCorrectionEnabled()) {
- ALOGV("TS_BEFORE: %d %lld %lld", id(),
+ ALOGVV("TS_BEFORE: %d %lld %lld", id(),
(long long)timestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL],
(long long)timestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL]);
auto correctedTimestamp = mTimestampVerifier.getLastCorrectedTimestamp();
@@ -3534,7 +3555,7 @@
= correctedTimestamp.mFrames;
timestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL]
= correctedTimestamp.mTimeNs;
- ALOGV("TS_AFTER: %d %lld %lld", id(),
+ ALOGVV("TS_AFTER: %d %lld %lld", id(),
(long long)timestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL],
(long long)timestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL]);
@@ -3728,9 +3749,15 @@
// Determine which session to pick up haptic data.
// This must be done under the same lock as prepareTracks_l().
+ // The haptic data from the effect is at a higher priority than the one from track.
// TODO: Write haptic data directly to sink buffer when mixing.
if (mHapticChannelCount > 0 && effectChains.size() > 0) {
for (const auto& track : mActiveTracks) {
+ sp<EffectChain> effectChain = getEffectChain_l(track->sessionId());
+ if (effectChain != nullptr && effectChain->containsHapticGeneratingEffect_l()) {
+ activeHapticSessionId = track->sessionId();
+ break;
+ }
if (track->getHapticPlaybackEnabled()) {
activeHapticSessionId = track->sessionId();
break;
@@ -4100,13 +4127,20 @@
// remove from our tracks vector
removeTrack_l(track);
}
- if ((track->channelMask() & AUDIO_CHANNEL_HAPTIC_ALL) != AUDIO_CHANNEL_NONE
- && mHapticChannelCount > 0) {
+ if (mHapticChannelCount > 0 &&
+ ((track->channelMask() & AUDIO_CHANNEL_HAPTIC_ALL) != AUDIO_CHANNEL_NONE
+ || (chain != nullptr && chain->containsHapticGeneratingEffect_l()))) {
mLock.unlock();
// Unlock due to VibratorService will lock for this call and will
// call Tracks.mute/unmute which also require thread's lock.
AudioFlinger::onExternalVibrationStop(track->getExternalVibration());
mLock.lock();
+
+ // When the track is stop, set the haptic intensity as MUTE
+ // for the HapticGenerator effect.
+ if (chain != nullptr) {
+ chain->setHapticIntensity_l(track->id(), static_cast<int>(os::HapticScale::MUTE));
+ }
}
}
}
@@ -4260,7 +4294,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) {
@@ -4449,7 +4483,7 @@
// audio to FastMixer
fastTrack->mFormat = mFormat; // mPipeSink format for audio to FastMixer
fastTrack->mHapticPlaybackEnabled = mHapticChannelMask != AUDIO_CHANNEL_NONE;
- fastTrack->mHapticIntensity = AudioMixer::HAPTIC_SCALE_NONE;
+ fastTrack->mHapticIntensity = os::HapticScale::NONE;
fastTrack->mGeneration++;
state->mFastTracksGen++;
state->mTrackMask = 1;
@@ -4809,19 +4843,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 +4872,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 +6086,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) {
@@ -6875,7 +6911,7 @@
snprintf(mThreadName, kThreadNameLength, "AudioIn_%X", id);
mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mThreadName);
- if (mInput != nullptr && mInput->audioHwDev != nullptr) {
+ if (mInput->audioHwDev != nullptr) {
mIsMsdDevice = strcmp(
mInput->audioHwDev->moduleName(), AUDIO_HARDWARE_MODULE_ID_MSD) == 0;
}
@@ -7073,6 +7109,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 +7198,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 +7298,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 +7386,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 */) {
@@ -7340,12 +7403,12 @@
// Correct timestamps
if (isTimestampCorrectionEnabled()) {
- ALOGV("TS_BEFORE: %d %lld %lld",
+ ALOGVV("TS_BEFORE: %d %lld %lld",
id(), (long long)time, (long long)position);
auto correctedTimestamp = mTimestampVerifier.getLastCorrectedTimestamp();
position = correctedTimestamp.mFrames;
time = correctedTimestamp.mTimeNs;
- ALOGV("TS_AFTER: %d %lld %lld",
+ ALOGVV("TS_AFTER: %d %lld %lld",
id(), (long long)time, (long long)position);
}
@@ -8393,13 +8456,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 +8492,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 +8633,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 +8747,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 +9039,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;
@@ -9346,6 +9423,11 @@
return BAD_VALUE;
}
+ if (EffectModule::isHapticGenerator(&desc->type)) {
+ ALOGE("%s(): HapticGenerator is not supported for MmapThread", __func__);
+ return BAD_VALUE;
+ }
+
return NO_ERROR;
}
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 5b8c081..2e81ae7 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()
};
@@ -271,6 +272,7 @@
// Called by AudioFlinger::frameCount(audio_io_handle_t output) and effects,
// and returns the [normal mix] buffer's frame count.
virtual size_t frameCount() const = 0;
+ virtual audio_channel_mask_t hapticChannelMask() const { return AUDIO_CHANNEL_NONE; }
virtual uint32_t latency_l() const { return 0; }
virtual void setVolumeForOutput_l(float left __unused, float right __unused) const {}
@@ -332,6 +334,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(
@@ -466,6 +479,25 @@
void onEffectEnable(const sp<EffectModule>& effect);
void onEffectDisable();
+ // invalidateTracksForAudioSession_l must be called with holding mLock.
+ virtual void invalidateTracksForAudioSession_l(audio_session_t sessionId __unused) const { }
+ // Invalidate all the tracks with the given audio session.
+ void invalidateTracksForAudioSession(audio_session_t sessionId) const {
+ Mutex::Autolock _l(mLock);
+ invalidateTracksForAudioSession_l(sessionId);
+ }
+
+ template <typename T>
+ void invalidateTracksForAudioSession_l(audio_session_t sessionId,
+ const T& tracks) const {
+ for (size_t i = 0; i < tracks.size(); ++i) {
+ const sp<TrackBase>& track = tracks[i];
+ if (sessionId == track->sessionId()) {
+ track->invalidate();
+ }
+ }
+ }
+
protected:
// entry describing an effect being suspended in mSuspendedSessions keyed vector
@@ -563,7 +595,7 @@
static const int kThreadNameLength = 16; // prctl(PR_SET_NAME) limit
char mThreadName[kThreadNameLength]; // guaranteed NUL-terminated
- sp<IPowerManager> mPowerManager;
+ sp<os::IPowerManager> mPowerManager;
sp<IBinder> mWakeLockToken;
const sp<PMDeathRecipient> mDeathRecipient;
// list of suspended effects per session and per type. The first (outer) vector is
@@ -927,6 +959,13 @@
&& outDeviceTypes().count(mTimestampCorrectedDevice) != 0;
}
+ audio_channel_mask_t hapticChannelMask() const override {
+ return mHapticChannelMask;
+ }
+ bool supportsHapticPlayback() const {
+ return (mHapticChannelMask & AUDIO_CHANNEL_HAPTIC_ALL) != AUDIO_CHANNEL_NONE;
+ }
+
protected:
// updated by readOutputParameters_l()
size_t mNormalFrameCount; // normal mixer and effects
@@ -1049,6 +1088,11 @@
uint32_t trackCountForUid_l(uid_t uid) const;
+ void invalidateTracksForAudioSession_l(
+ audio_session_t sessionId) const override {
+ ThreadBase::invalidateTracksForAudioSession_l(sessionId, mTracks);
+ }
+
private:
friend class AudioFlinger; // for numerous
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..c92bce5 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -595,14 +595,18 @@
+ "_" + std::to_string(mId) + "_T");
#endif
- if (channelMask & AUDIO_CHANNEL_HAPTIC_ALL) {
+ if (thread->supportsHapticPlayback()) {
+ // If the track is attached to haptic playback thread, it is potentially to have
+ // HapticGenerator effect, which will generate haptic data, on the track. In that case,
+ // external vibration is always created for all tracks attached to haptic playback thread.
mAudioVibrationController = new AudioVibrationController(this);
mExternalVibration = new os::ExternalVibration(
mUid, "" /* pkg */, mAttr, mAudioVibrationController);
}
// 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 +808,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 +982,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 +1519,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;
}
}
@@ -1908,6 +1921,25 @@
{
mProxy->releaseBuffer(buffer);
restartIfDisabled();
+
+ // Check if the PatchTrack has enough data to write once in releaseBuffer().
+ // If not, prevent an underrun from occurring by moving the track into FS_FILLING;
+ // this logic avoids glitches when suspending A2DP with AudioPlaybackCapture.
+ // TODO: perhaps underrun avoidance could be a track property checked in isReady() instead.
+ if (mFillingUpStatus == FS_ACTIVE
+ && audio_is_linear_pcm(mFormat)
+ && !isOffloadedOrDirect()) {
+ if (sp<ThreadBase> thread = mThread.promote();
+ thread != 0) {
+ PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
+ const size_t frameCount = playbackThread->frameCount() * sampleRate()
+ / playbackThread->sampleRate();
+ if (framesReady() < frameCount) {
+ ALOGD("%s(%d) Not enough data, wait for buffer to fill", __func__, mId);
+ mFillingUpStatus = FS_FILLING;
+ }
+ }
+ }
}
void AudioFlinger::PlaybackThread::PatchTrack::restartIfDisabled()
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/include/EffectDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/EffectDescriptor.h
index c4eab30..59eee52 100644
--- a/services/audiopolicy/common/managerdefinitions/include/EffectDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/EffectDescriptor.h
@@ -72,6 +72,9 @@
audio_io_handle_t dstOutput);
void moveEffects(const std::vector<int>& ids, audio_io_handle_t dstOutput);
+ audio_io_handle_t getIoForSession(audio_session_t sessionId,
+ const effect_uuid_t *effectType = nullptr);
+
void dump(String8 *dst, int spaces = 0, 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/common/managerdefinitions/src/EffectDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/EffectDescriptor.cpp
index 415962a..843f5da 100644
--- a/services/audiopolicy/common/managerdefinitions/src/EffectDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/EffectDescriptor.cpp
@@ -202,6 +202,19 @@
}
}
+audio_io_handle_t EffectDescriptorCollection::getIoForSession(audio_session_t sessionId,
+ const effect_uuid_t *effectType)
+{
+ for (size_t i = 0; i < size(); ++i) {
+ sp<EffectDescriptor> effect = valueAt(i);
+ if (effect->mSession == sessionId && (effectType == nullptr ||
+ memcmp(&effect->mDesc.type, effectType, sizeof(effect_uuid_t)) == 0)) {
+ return effect->mIo;
+ }
+ }
+ return AUDIO_IO_HANDLE_NONE;
+}
+
EffectDescriptorCollection EffectDescriptorCollection::getEffectsForIo(audio_io_handle_t io) const
{
EffectDescriptorCollection effects;
diff --git a/services/audiopolicy/engine/common/src/VolumeCurve.cpp b/services/audiopolicy/engine/common/src/VolumeCurve.cpp
index c352578..8aa4b08 100644
--- a/services/audiopolicy/engine/common/src/VolumeCurve.cpp
+++ b/services/audiopolicy/engine/common/src/VolumeCurve.cpp
@@ -43,10 +43,24 @@
indexInUi = volIndexMax;
}
+ // Calculate the new volume index
size_t nbCurvePoints = mCurvePoints.size();
- // the volume index in the UI is relative to the min and max volume indices for this stream
- int nbSteps = 1 + mCurvePoints[nbCurvePoints - 1].mIndex - mCurvePoints[0].mIndex;
- int volIdx = (nbSteps * (indexInUi - volIndexMin)) / (volIndexMax - volIndexMin);
+
+ int volIdx;
+ if (volIndexMin == volIndexMax) {
+ if (indexInUi == volIndexMin) {
+ volIdx = volIndexMin;
+ } else {
+ // This would result in a divide-by-zero below
+ ALOG_ASSERT(volIndexmin != volIndexMax, "Invalid volume index range & value: 0");
+ return NAN;
+ }
+ } else {
+ // interpolaate
+ // the volume index in the UI is relative to the min and max volume indices for this stream
+ int nbSteps = 1 + mCurvePoints[nbCurvePoints - 1].mIndex - mCurvePoints[0].mIndex;
+ volIdx = (nbSteps * (indexInUi - volIndexMin)) / (volIndexMax - volIndexMin);
+ }
// Where would this volume index been inserted in the curve point
size_t indexInUiPosition = mCurvePoints.orderOf(CurvePoint(volIdx, 0));
diff --git a/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_product_strategies.xml b/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_product_strategies.xml
index a7388da..bc32416 100644
--- a/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_product_strategies.xml
+++ b/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_product_strategies.xml
@@ -65,6 +65,12 @@
</ProductStrategy>
<ProductStrategy name="STRATEGY_MEDIA">
+ <AttributesGroup streamType="AUDIO_STREAM_ASSISTANT" volumeGroup="assistant">
+ <Attributes>
+ <ContentType value="AUDIO_CONTENT_TYPE_SPEECH"/>
+ <Usage value="AUDIO_USAGE_ASSISTANT"/>
+ </Attributes>
+ </AttributesGroup>
<AttributesGroup streamType="AUDIO_STREAM_MUSIC" volumeGroup="music">
<Attributes> <Usage value="AUDIO_USAGE_MEDIA"/> </Attributes>
<Attributes> <Usage value="AUDIO_USAGE_GAME"/> </Attributes>
@@ -72,12 +78,6 @@
<Attributes> <Usage value="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"/> </Attributes>
<Attributes></Attributes>
</AttributesGroup>
- <AttributesGroup streamType="AUDIO_STREAM_ASSISTANT" volumeGroup="assistant">
- <Attributes>
- <ContentType value="AUDIO_CONTENT_TYPE_SPEECH"/>
- <Usage value="AUDIO_USAGE_ASSISTANT"/>
- </Attributes>
- </AttributesGroup>
<AttributesGroup streamType="AUDIO_STREAM_SYSTEM" volumeGroup="system">
<Attributes> <Usage value="AUDIO_USAGE_ASSISTANCE_SONIFICATION"/> </Attributes>
</AttributesGroup>
diff --git a/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_product_strategies.xml b/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_product_strategies.xml
index a7388da..bc32416 100644
--- a/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_product_strategies.xml
+++ b/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_product_strategies.xml
@@ -65,6 +65,12 @@
</ProductStrategy>
<ProductStrategy name="STRATEGY_MEDIA">
+ <AttributesGroup streamType="AUDIO_STREAM_ASSISTANT" volumeGroup="assistant">
+ <Attributes>
+ <ContentType value="AUDIO_CONTENT_TYPE_SPEECH"/>
+ <Usage value="AUDIO_USAGE_ASSISTANT"/>
+ </Attributes>
+ </AttributesGroup>
<AttributesGroup streamType="AUDIO_STREAM_MUSIC" volumeGroup="music">
<Attributes> <Usage value="AUDIO_USAGE_MEDIA"/> </Attributes>
<Attributes> <Usage value="AUDIO_USAGE_GAME"/> </Attributes>
@@ -72,12 +78,6 @@
<Attributes> <Usage value="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"/> </Attributes>
<Attributes></Attributes>
</AttributesGroup>
- <AttributesGroup streamType="AUDIO_STREAM_ASSISTANT" volumeGroup="assistant">
- <Attributes>
- <ContentType value="AUDIO_CONTENT_TYPE_SPEECH"/>
- <Usage value="AUDIO_USAGE_ASSISTANT"/>
- </Attributes>
- </AttributesGroup>
<AttributesGroup streamType="AUDIO_STREAM_SYSTEM" volumeGroup="system">
<Attributes> <Usage value="AUDIO_USAGE_ASSISTANCE_SONIFICATION"/> </Attributes>
</AttributesGroup>
diff --git a/services/audiopolicy/manager/Android.bp b/services/audiopolicy/manager/Android.bp
new file mode 100644
index 0000000..5bb432f
--- /dev/null
+++ b/services/audiopolicy/manager/Android.bp
@@ -0,0 +1,32 @@
+cc_library_shared {
+ name: "libaudiopolicymanager",
+
+ srcs: [
+ "AudioPolicyFactory.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/services/audioflinger"
+ ],
+
+ shared_libs: [
+ "libaudiopolicymanagerdefault",
+ ],
+
+ static_libs: [
+ "libaudiopolicycomponents",
+ ],
+
+ header_libs: [
+ "libaudiopolicycommon",
+ "libaudiopolicyengine_interface_headers",
+ "libaudiopolicymanager_interface_headers",
+ "libaudioutils_headers",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+}
diff --git a/services/audiopolicy/manager/Android.mk b/services/audiopolicy/manager/Android.mk
deleted file mode 100644
index cae6cfa..0000000
--- a/services/audiopolicy/manager/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-ifneq ($(USE_CUSTOM_AUDIO_POLICY), 1)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- AudioPolicyFactory.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libaudiopolicymanagerdefault
-
-LOCAL_STATIC_LIBRARIES := \
- libaudiopolicycomponents
-
-LOCAL_C_INCLUDES += \
- $(call include-path-for, audio-utils)
-
-LOCAL_HEADER_LIBRARIES := \
- libaudiopolicycommon \
- libaudiopolicyengine_interface_headers \
- libaudiopolicymanager_interface_headers
-
-LOCAL_CFLAGS := -Wall -Werror
-
-LOCAL_MODULE:= libaudiopolicymanager
-
-include $(BUILD_SHARED_LIBRARY)
-
-endif #ifneq ($(USE_CUSTOM_AUDIO_POLICY), 1)
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index bcf6f38..c4d97bf 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -42,12 +42,14 @@
#include <set>
#include <unordered_set>
#include <vector>
+#include <cutils/bitops.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <media/AudioParameter.h>
#include <private/android_filesystem_config.h>
#include <system/audio.h>
#include <system/audio_config.h>
+#include <system/audio_effects/effect_hapticgenerator.h>
#include "AudioPolicyManager.h"
#include <Serializer.h>
#include "TypeConverter.h"
@@ -178,6 +180,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 +1104,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__,
@@ -1295,7 +1301,8 @@
// at this stage we should ignore the DIRECT flag as no direct output could be found earlier
*flags = (audio_output_flags_t)(*flags & ~AUDIO_OUTPUT_FLAG_DIRECT);
- output = selectOutput(outputs, *flags, config->format, channelMask, config->sample_rate);
+ output = selectOutput(
+ outputs, *flags, config->format, channelMask, config->sample_rate, session);
}
ALOGW_IF((output == 0), "getOutputForDevices() could not find output for stream %d, "
"sampling rate %d, format %#x, channels %#x, flags %#x",
@@ -1468,14 +1475,26 @@
}
audio_io_handle_t AudioPolicyManager::selectOutput(const SortedVector<audio_io_handle_t>& outputs,
- audio_output_flags_t flags,
- audio_format_t format,
- audio_channel_mask_t channelMask,
- uint32_t samplingRate)
+ audio_output_flags_t flags,
+ audio_format_t format,
+ audio_channel_mask_t channelMask,
+ uint32_t samplingRate,
+ audio_session_t sessionId)
{
LOG_ALWAYS_FATAL_IF(!(format == AUDIO_FORMAT_INVALID || audio_is_linear_pcm(format)),
"%s called with format %#x", __func__, format);
+ // Return the output that haptic-generating attached to when 1) session id is specified,
+ // 2) haptic-generating effect exists for given session id and 3) the output that
+ // haptic-generating effect attached to is in given outputs.
+ if (sessionId != AUDIO_SESSION_NONE) {
+ audio_io_handle_t hapticGeneratingOutput = mEffects.getIoForSession(
+ sessionId, FX_IID_HAPTICGENERATOR);
+ if (outputs.indexOf(hapticGeneratingOutput) >= 0) {
+ return hapticGeneratingOutput;
+ }
+ }
+
// Flags disqualifying an output: the match must happen before calling selectOutput()
static const audio_output_flags_t kExcludedFlags = (audio_output_flags_t)
(AUDIO_OUTPUT_FLAG_HW_AV_SYNC | AUDIO_OUTPUT_FLAG_MMAP_NOIRQ | AUDIO_OUTPUT_FLAG_DIRECT);
@@ -2874,7 +2893,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 +3012,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 +3030,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 +3071,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 +3848,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 +4670,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 +5254,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;
@@ -5262,7 +5304,8 @@
client->flags(),
client->config().format,
client->config().channel_mask,
- client->config().sample_rate);
+ client->config().sample_rate,
+ client->session());
if (newOutput != srcOut) {
invalidate = true;
break;
@@ -5419,6 +5462,12 @@
}
}
+ // Do not retrieve engine device for outputs through MSD
+ // TODO: support explicit routing requests by resetting MSD patch to engine device.
+ if (outputDesc->devices() == getMsdAudioOutDevices()) {
+ return outputDesc->devices();
+ }
+
// Honor explicit routing requests only if no client using default routing is active on this
// input: a specific app can not force routing for other apps by setting a preferred device.
bool active; // unused
@@ -5621,7 +5670,7 @@
sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);
setVolumeSourceMute(ttsVolumeSource, mute/*on*/, desc, 0 /*delay*/, DeviceTypeSet());
const uint32_t latency = desc->latency() * 2;
- if (latency > maxLatency) {
+ if (desc->isActive(latency * 2) && latency > maxLatency) {
maxLatency = latency;
}
}
@@ -5743,15 +5792,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 +5806,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/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index b588f89..201abc6 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.h
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.h
@@ -608,7 +608,8 @@
audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE,
audio_format_t format = AUDIO_FORMAT_INVALID,
audio_channel_mask_t channelMask = AUDIO_CHANNEL_NONE,
- uint32_t samplingRate = 0);
+ uint32_t samplingRate = 0,
+ audio_session_t sessionId = AUDIO_SESSION_NONE);
// samplingRate, format, channelMask are in/out and so may be modified
sp<IOProfile> getInputProfile(const sp<DeviceDescriptor> & device,
uint32_t& samplingRate,
diff --git a/services/audiopolicy/service/Android.bp b/services/audiopolicy/service/Android.bp
new file mode 100644
index 0000000..8a7a1b2
--- /dev/null
+++ b/services/audiopolicy/service/Android.bp
@@ -0,0 +1,55 @@
+cc_library_shared {
+ name: "libaudiopolicyservice",
+
+ srcs: [
+ "AudioPolicyClientImpl.cpp",
+ "AudioPolicyEffects.cpp",
+ "AudioPolicyInterfaceImpl.cpp",
+ "AudioPolicyService.cpp",
+ "CaptureStateNotifier.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/services/audioflinger"
+ ],
+
+ shared_libs: [
+ "libaudioclient",
+ "libaudiofoundation",
+ "libaudiopolicymanager",
+ "libaudioutils",
+ "libbinder",
+ "libcutils",
+ "libeffectsconfig",
+ "libhardware_legacy",
+ "liblog",
+ "libmedia_helper",
+ "libmediametrics",
+ "libmediautils",
+ "libsensorprivacy",
+ "libutils",
+ "capture_state_listener-aidl-cpp",
+ ],
+
+ static_libs: [
+ "libaudiopolicycomponents",
+ ],
+
+ header_libs: [
+ "libaudiopolicycommon",
+ "libaudiopolicyengine_interface_headers",
+ "libaudiopolicymanager_interface_headers",
+ "libaudioutils_headers",
+ ],
+
+ cflags: [
+ "-fvisibility=hidden",
+ "-Werror",
+ "-Wall",
+ "-Wthread-safety",
+ ],
+
+ export_shared_lib_headers: [
+ "libsensorprivacy",
+ ],
+}
diff --git a/services/audiopolicy/service/Android.mk b/services/audiopolicy/service/Android.mk
deleted file mode 100644
index 680b077..0000000
--- a/services/audiopolicy/service/Android.mk
+++ /dev/null
@@ -1,50 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- AudioPolicyService.cpp \
- AudioPolicyEffects.cpp \
- AudioPolicyInterfaceImpl.cpp \
- AudioPolicyClientImpl.cpp \
- CaptureStateNotifier.cpp
-
-LOCAL_C_INCLUDES := \
- frameworks/av/services/audioflinger \
- $(call include-path-for, audio-utils)
-
-LOCAL_HEADER_LIBRARIES := \
- libaudiopolicycommon \
- libaudiopolicyengine_interface_headers \
- libaudiopolicymanager_interface_headers
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- libutils \
- liblog \
- libbinder \
- libaudioclient \
- libaudioutils \
- libaudiofoundation \
- libhardware_legacy \
- libaudiopolicymanager \
- libmedia_helper \
- libmediametrics \
- libmediautils \
- libeffectsconfig \
- libsensorprivacy \
- capture_state_listener-aidl-cpp
-
-LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := \
- libsensorprivacy
-
-LOCAL_STATIC_LIBRARIES := \
- libaudiopolicycomponents
-
-LOCAL_MODULE:= libaudiopolicyservice
-
-LOCAL_CFLAGS += -fvisibility=hidden
-LOCAL_CFLAGS += -Wall -Werror -Wthread-safety
-
-include $(BUILD_SHARED_LIBRARY)
-
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..a6e8989 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -488,9 +488,9 @@
}
bool isAccessibility = mUidPolicy->isA11yUid(current->uid);
- // Clients capturing for Accessibility services are not considered
+ // Clients capturing for Accessibility services or virtual sources are not considered
// for top or latest active to avoid masking regular clients started before
- if (!isAccessibility) {
+ if (!isAccessibility && !isVirtualSource(current->attributes.source)) {
bool isAssistant = mUidPolicy->isAssistantUid(current->uid);
bool isPrivacySensitive =
(current->attributes.flags & AUDIO_FLAG_CAPTURE_PRIVATE) != 0;
@@ -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/audiopolicy/tests/AudioPolicyManagerTestClient.h b/services/audiopolicy/tests/AudioPolicyManagerTestClient.h
index e1721ea..bdddf06 100644
--- a/services/audiopolicy/tests/AudioPolicyManagerTestClient.h
+++ b/services/audiopolicy/tests/AudioPolicyManagerTestClient.h
@@ -75,6 +75,10 @@
status_t createAudioPatch(const struct audio_patch *patch,
audio_patch_handle_t *handle,
int /*delayMs*/) override {
+ auto iter = mActivePatches.find(*handle);
+ if (iter != mActivePatches.end()) {
+ mActivePatches.erase(*handle);
+ }
*handle = mNextPatchHandle++;
mActivePatches.insert(std::make_pair(*handle, *patch));
return NO_ERROR;
diff --git a/services/camera/libcameraservice/Android.bp b/services/camera/libcameraservice/Android.bp
index 501d922..bdbcdb0 100644
--- a/services/camera/libcameraservice/Android.bp
+++ b/services/camera/libcameraservice/Android.bp
@@ -30,7 +30,6 @@
"common/CameraProviderManager.cpp",
"common/DepthPhotoProcessor.cpp",
"common/FrameProcessorBase.cpp",
- "api1/CameraClient.cpp",
"api1/Camera2Client.cpp",
"api1/client2/Parameters.cpp",
"api1/client2/FrameProcessor.cpp",
@@ -46,7 +45,6 @@
"api2/DepthCompositeStream.cpp",
"api2/HeicEncoderInfoManager.cpp",
"api2/HeicCompositeStream.cpp",
- "device1/CameraHardwareInterface.cpp",
"device3/BufferUtils.cpp",
"device3/Camera3Device.cpp",
"device3/Camera3OfflineSession.cpp",
@@ -122,7 +120,6 @@
"android.hardware.camera.provider@2.4",
"android.hardware.camera.provider@2.5",
"android.hardware.camera.provider@2.6",
- "android.hardware.camera.device@1.0",
"android.hardware.camera.device@3.2",
"android.hardware.camera.device@3.3",
"android.hardware.camera.device@3.4",
diff --git a/services/camera/libcameraservice/CameraFlashlight.cpp b/services/camera/libcameraservice/CameraFlashlight.cpp
index e629cdd..ccdd9e5 100644
--- a/services/camera/libcameraservice/CameraFlashlight.cpp
+++ b/services/camera/libcameraservice/CameraFlashlight.cpp
@@ -59,9 +59,8 @@
if (mProviderManager->supportSetTorchMode(cameraId.string())) {
mFlashControl = new ProviderFlashControl(mProviderManager);
} else {
- // Only HAL1 devices do not support setTorchMode
- mFlashControl =
- new CameraHardwareInterfaceFlashControl(mProviderManager, mCallbacks);
+ ALOGE("Flashlight control not supported by this device!");
+ return NO_INIT;
}
return OK;
@@ -309,271 +308,4 @@
}
// ProviderFlashControl implementation ends
-/////////////////////////////////////////////////////////////////////
-// CameraHardwareInterfaceFlashControl implementation begins
-// Flash control for camera module <= v2.3 and camera HAL v1
-/////////////////////////////////////////////////////////////////////
-
-CameraHardwareInterfaceFlashControl::CameraHardwareInterfaceFlashControl(
- sp<CameraProviderManager> manager,
- CameraProviderManager::StatusListener* callbacks) :
- mProviderManager(manager),
- mCallbacks(callbacks),
- mTorchEnabled(false) {
-}
-
-CameraHardwareInterfaceFlashControl::~CameraHardwareInterfaceFlashControl() {
- disconnectCameraDevice();
-
- mSurface.clear();
- mSurfaceTexture.clear();
- mProducer.clear();
- mConsumer.clear();
-
- if (mTorchEnabled) {
- if (mCallbacks) {
- ALOGV("%s: notify the framework that torch was turned off",
- __FUNCTION__);
- mCallbacks->onTorchStatusChanged(mCameraId, TorchModeStatus::AVAILABLE_OFF);
- }
- }
-}
-
-status_t CameraHardwareInterfaceFlashControl::setTorchMode(
- const String8& cameraId, bool enabled) {
- Mutex::Autolock l(mLock);
-
- // pre-check
- status_t res;
- if (enabled) {
- bool hasFlash = false;
- // Check if it has a flash unit and leave camera device open.
- res = hasFlashUnitLocked(cameraId, &hasFlash, /*keepDeviceOpen*/true);
- // invalid camera?
- if (res) {
- // hasFlashUnitLocked() returns BAD_INDEX if mDevice is connected to
- // another camera device.
- return res == BAD_INDEX ? BAD_INDEX : -EINVAL;
- }
- // no flash unit?
- if (!hasFlash) {
- // Disconnect camera device if it has no flash.
- disconnectCameraDevice();
- return -ENOSYS;
- }
- } else if (mDevice == NULL || cameraId != mCameraId) {
- // disabling the torch mode of an un-opened or different device.
- return OK;
- } else {
- // disabling the torch mode of currently opened device
- disconnectCameraDevice();
- mTorchEnabled = false;
- mCallbacks->onTorchStatusChanged(cameraId, TorchModeStatus::AVAILABLE_OFF);
- return OK;
- }
-
- res = startPreviewAndTorch();
- if (res) {
- return res;
- }
-
- mTorchEnabled = true;
- mCallbacks->onTorchStatusChanged(cameraId, TorchModeStatus::AVAILABLE_ON);
- return OK;
-}
-
-status_t CameraHardwareInterfaceFlashControl::hasFlashUnit(
- const String8& cameraId, bool *hasFlash) {
- Mutex::Autolock l(mLock);
- // Close device after checking if it has a flash unit.
- return hasFlashUnitLocked(cameraId, hasFlash, /*keepDeviceOpen*/false);
-}
-
-status_t CameraHardwareInterfaceFlashControl::hasFlashUnitLocked(
- const String8& cameraId, bool *hasFlash, bool keepDeviceOpen) {
- bool closeCameraDevice = false;
-
- if (!hasFlash) {
- return BAD_VALUE;
- }
-
- status_t res;
- if (mDevice == NULL) {
- // Connect to camera device to query if it has a flash unit.
- res = connectCameraDevice(cameraId);
- if (res) {
- return res;
- }
- // Close camera device only when it is just opened and the caller doesn't want to keep
- // the camera device open.
- closeCameraDevice = !keepDeviceOpen;
- }
-
- if (cameraId != mCameraId) {
- return BAD_INDEX;
- }
-
- const char *flashMode =
- mParameters.get(CameraParameters::KEY_SUPPORTED_FLASH_MODES);
- if (flashMode && strstr(flashMode, CameraParameters::FLASH_MODE_TORCH)) {
- *hasFlash = true;
- } else {
- *hasFlash = false;
- }
-
- if (closeCameraDevice) {
- res = disconnectCameraDevice();
- if (res != OK) {
- ALOGE("%s: Failed to disconnect camera device. %s (%d)", __FUNCTION__,
- strerror(-res), res);
- return res;
- }
- }
-
- return OK;
-}
-
-status_t CameraHardwareInterfaceFlashControl::startPreviewAndTorch() {
- status_t res = OK;
- res = mDevice->startPreview();
- if (res) {
- ALOGE("%s: start preview failed. %s (%d)", __FUNCTION__,
- strerror(-res), res);
- return res;
- }
-
- mParameters.set(CameraParameters::KEY_FLASH_MODE,
- CameraParameters::FLASH_MODE_TORCH);
-
- return mDevice->setParameters(mParameters);
-}
-
-status_t CameraHardwareInterfaceFlashControl::getSmallestSurfaceSize(
- int32_t *width, int32_t *height) {
- if (!width || !height) {
- return BAD_VALUE;
- }
-
- int32_t w = INT32_MAX;
- int32_t h = 1;
- Vector<Size> sizes;
-
- mParameters.getSupportedPreviewSizes(sizes);
- for (size_t i = 0; i < sizes.size(); i++) {
- Size s = sizes[i];
- if (w * h > s.width * s.height) {
- w = s.width;
- h = s.height;
- }
- }
-
- if (w == INT32_MAX) {
- return NAME_NOT_FOUND;
- }
-
- *width = w;
- *height = h;
-
- return OK;
-}
-
-status_t CameraHardwareInterfaceFlashControl::initializePreviewWindow(
- const sp<CameraHardwareInterface>& device, int32_t width, int32_t height) {
- status_t res;
- BufferQueue::createBufferQueue(&mProducer, &mConsumer);
-
- mSurfaceTexture = new GLConsumer(mConsumer, 0, GLConsumer::TEXTURE_EXTERNAL,
- true, true);
- if (mSurfaceTexture == NULL) {
- return NO_MEMORY;
- }
-
- int32_t format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
- res = mSurfaceTexture->setDefaultBufferSize(width, height);
- if (res) {
- return res;
- }
- res = mSurfaceTexture->setDefaultBufferFormat(format);
- if (res) {
- return res;
- }
-
- mSurface = new Surface(mProducer, /*useAsync*/ true);
- if (mSurface == NULL) {
- return NO_MEMORY;
- }
-
- res = native_window_api_connect(mSurface.get(), NATIVE_WINDOW_API_CAMERA);
- if (res) {
- ALOGE("%s: Unable to connect to native window", __FUNCTION__);
- return res;
- }
-
- return device->setPreviewWindow(mSurface);
-}
-
-status_t CameraHardwareInterfaceFlashControl::connectCameraDevice(
- const String8& cameraId) {
- sp<CameraHardwareInterface> device =
- new CameraHardwareInterface(cameraId.string());
-
- status_t res = device->initialize(mProviderManager);
- if (res) {
- ALOGE("%s: initializing camera %s failed", __FUNCTION__,
- cameraId.string());
- return res;
- }
-
- // need to set __get_memory in set_callbacks().
- device->setCallbacks(NULL, NULL, NULL, NULL, NULL);
-
- mParameters = device->getParameters();
-
- int32_t width, height;
- res = getSmallestSurfaceSize(&width, &height);
- if (res) {
- ALOGE("%s: failed to get smallest surface size for camera %s",
- __FUNCTION__, cameraId.string());
- return res;
- }
-
- res = initializePreviewWindow(device, width, height);
- if (res) {
- ALOGE("%s: failed to initialize preview window for camera %s",
- __FUNCTION__, cameraId.string());
- return res;
- }
-
- mCameraId = cameraId;
- mDevice = device;
- return OK;
-}
-
-status_t CameraHardwareInterfaceFlashControl::disconnectCameraDevice() {
- if (mDevice == NULL) {
- return OK;
- }
-
- if (mParameters.get(CameraParameters::KEY_FLASH_MODE)) {
- // There is a flash, turn if off.
- // (If there isn't one, leave the parameter null)
- mParameters.set(CameraParameters::KEY_FLASH_MODE,
- CameraParameters::FLASH_MODE_OFF);
- mDevice->setParameters(mParameters);
- }
- mDevice->stopPreview();
- status_t res = native_window_api_disconnect(mSurface.get(),
- NATIVE_WINDOW_API_CAMERA);
- if (res) {
- ALOGW("%s: native_window_api_disconnect failed: %s (%d)",
- __FUNCTION__, strerror(-res), res);
- }
- mDevice->setPreviewWindow(NULL);
- mDevice->release();
- mDevice = NULL;
-
- return OK;
-}
-// CameraHardwareInterfaceFlashControl implementation ends
-
}
diff --git a/services/camera/libcameraservice/CameraFlashlight.h b/services/camera/libcameraservice/CameraFlashlight.h
index 1baaba2..b97fa5f 100644
--- a/services/camera/libcameraservice/CameraFlashlight.h
+++ b/services/camera/libcameraservice/CameraFlashlight.h
@@ -23,8 +23,6 @@
#include <utils/SortedVector.h>
#include "common/CameraProviderManager.h"
#include "common/CameraDeviceBase.h"
-#include "device1/CameraHardwareInterface.h"
-
namespace android {
@@ -124,59 +122,6 @@
Mutex mLock;
};
-/**
- * Flash control for camera module <= v2.3 and camera HAL v1
- */
-class CameraHardwareInterfaceFlashControl : public FlashControlBase {
- public:
- CameraHardwareInterfaceFlashControl(
- sp<CameraProviderManager> manager,
- CameraProviderManager::StatusListener* callbacks);
- virtual ~CameraHardwareInterfaceFlashControl();
-
- // FlashControlBase
- status_t setTorchMode(const String8& cameraId, bool enabled);
- status_t hasFlashUnit(const String8& cameraId, bool *hasFlash);
-
- private:
- // connect to a camera device
- status_t connectCameraDevice(const String8& cameraId);
-
- // disconnect and free mDevice
- status_t disconnectCameraDevice();
-
- // initialize the preview window
- status_t initializePreviewWindow(const sp<CameraHardwareInterface>& device,
- int32_t width, int32_t height);
-
- // start preview and enable torch
- status_t startPreviewAndTorch();
-
- // get the smallest surface
- status_t getSmallestSurfaceSize(int32_t *width, int32_t *height);
-
- // protected by mLock
- // If this function opens camera device in order to check if it has a flash unit, the
- // camera device will remain open if keepDeviceOpen is true and the camera device will be
- // closed if keepDeviceOpen is false. If camera device is already open when calling this
- // function, keepDeviceOpen is ignored.
- status_t hasFlashUnitLocked(const String8& cameraId, bool *hasFlash, bool keepDeviceOpen);
-
- sp<CameraProviderManager> mProviderManager;
- CameraProviderManager::StatusListener* mCallbacks;
- sp<CameraHardwareInterface> mDevice;
- String8 mCameraId;
- CameraParameters mParameters;
- bool mTorchEnabled;
-
- sp<IGraphicBufferProducer> mProducer;
- sp<IGraphicBufferConsumer> mConsumer;
- sp<GLConsumer> mSurfaceTexture;
- sp<Surface> mSurface;
-
- Mutex mLock;
-};
-
} // namespace android
#endif
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index c9d2c68..adafbda 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -70,7 +70,6 @@
#include <system/camera.h>
#include "CameraService.h"
-#include "api1/CameraClient.h"
#include "api1/Camera2Client.h"
#include "api2/CameraDeviceClient.h"
#include "utils/CameraTraces.h"
@@ -467,10 +466,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 +495,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();
}
}
@@ -671,9 +678,15 @@
status_t res = mCameraProviderManager->getCameraCharacteristics(
String8(cameraId).string(), cameraInfo);
if (res != OK) {
- return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION, "Unable to retrieve camera "
- "characteristics for device %s: %s (%d)", String8(cameraId).string(),
- strerror(-res), res);
+ if (res == NAME_NOT_FOUND) {
+ return STATUS_ERROR_FMT(ERROR_ILLEGAL_ARGUMENT, "Unable to retrieve camera "
+ "characteristics for unknown device %s: %s (%d)", String8(cameraId).string(),
+ strerror(-res), res);
+ } else {
+ return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION, "Unable to retrieve camera "
+ "characteristics for device %s: %s (%d)", String8(cameraId).string(),
+ strerror(-res), res);
+ }
}
SystemCameraKind deviceKind = SystemCameraKind::PUBLIC;
if (getSystemCameraKind(String8(cameraId), &deviceKind) != OK) {
@@ -794,35 +807,26 @@
Status CameraService::makeClient(const sp<CameraService>& cameraService,
const sp<IInterface>& cameraCb, const String16& packageName,
- const std::unique_ptr<String16>& featureId, const String8& cameraId, int api1CameraId,
- int facing, int clientPid, uid_t clientUid, int servicePid, int halVersion,
+ const std::optional<String16>& featureId, const String8& cameraId,
+ int api1CameraId, int facing, int clientPid, uid_t clientUid, int servicePid,
int deviceVersion, apiLevel effectiveApiLevel,
/*out*/sp<BasicClient>* client) {
- if (halVersion < 0 || halVersion == deviceVersion) {
- // Default path: HAL version is unspecified by caller, create CameraClient
- // based on device version reported by the HAL.
- switch(deviceVersion) {
- case CAMERA_DEVICE_API_VERSION_1_0:
- if (effectiveApiLevel == API_1) { // Camera1 API route
- sp<ICameraClient> tmp = static_cast<ICameraClient*>(cameraCb.get());
- *client = new CameraClient(cameraService, tmp, packageName, featureId,
- api1CameraId, facing, clientPid, clientUid,
- getpid());
- } else { // Camera2 API route
- ALOGW("Camera using old HAL version: %d", deviceVersion);
- return STATUS_ERROR_FMT(ERROR_DEPRECATED_HAL,
- "Camera device \"%s\" HAL version %d does not support camera2 API",
- cameraId.string(), deviceVersion);
- }
+ // Create CameraClient based on device version reported by the HAL.
+ switch(deviceVersion) {
+ case CAMERA_DEVICE_API_VERSION_1_0:
+ ALOGE("Camera using old HAL version: %d", deviceVersion);
+ return STATUS_ERROR_FMT(ERROR_DEPRECATED_HAL,
+ "Camera device \"%s\" HAL version %d no longer supported",
+ cameraId.string(), deviceVersion);
break;
- case CAMERA_DEVICE_API_VERSION_3_0:
- case CAMERA_DEVICE_API_VERSION_3_1:
- case CAMERA_DEVICE_API_VERSION_3_2:
- case CAMERA_DEVICE_API_VERSION_3_3:
- case CAMERA_DEVICE_API_VERSION_3_4:
- case CAMERA_DEVICE_API_VERSION_3_5:
- case CAMERA_DEVICE_API_VERSION_3_6:
+ case CAMERA_DEVICE_API_VERSION_3_0:
+ case CAMERA_DEVICE_API_VERSION_3_1:
+ case CAMERA_DEVICE_API_VERSION_3_2:
+ case CAMERA_DEVICE_API_VERSION_3_3:
+ case CAMERA_DEVICE_API_VERSION_3_4:
+ case CAMERA_DEVICE_API_VERSION_3_5:
+ case CAMERA_DEVICE_API_VERSION_3_6:
if (effectiveApiLevel == API_1) { // Camera1 API route
sp<ICameraClient> tmp = static_cast<ICameraClient*>(cameraCb.get());
*client = new Camera2Client(cameraService, tmp, packageName, featureId,
@@ -836,32 +840,12 @@
cameraId, facing, clientPid, clientUid, servicePid);
}
break;
- default:
+ default:
// Should not be reachable
ALOGE("Unknown camera device HAL version: %d", deviceVersion);
return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
"Camera device \"%s\" has unknown HAL version %d",
cameraId.string(), deviceVersion);
- }
- } else {
- // A particular HAL version is requested by caller. Create CameraClient
- // based on the requested HAL version.
- if (deviceVersion > CAMERA_DEVICE_API_VERSION_1_0 &&
- halVersion == CAMERA_DEVICE_API_VERSION_1_0) {
- // Only support higher HAL version device opened as HAL1.0 device.
- sp<ICameraClient> tmp = static_cast<ICameraClient*>(cameraCb.get());
- *client = new CameraClient(cameraService, tmp, packageName, featureId,
- api1CameraId, facing, clientPid, clientUid,
- servicePid);
- } else {
- // Other combinations (e.g. HAL3.x open as HAL2.x) are not supported yet.
- ALOGE("Invalid camera HAL version %x: HAL %x device can only be"
- " opened as HAL %x device", halVersion, deviceVersion,
- CAMERA_DEVICE_API_VERSION_1_0);
- return STATUS_ERROR_FMT(ERROR_ILLEGAL_ARGUMENT,
- "Camera device \"%s\" (HAL version %d) cannot be opened as HAL version %d",
- cameraId.string(), deviceVersion, halVersion);
- }
}
return Status::ok();
}
@@ -949,8 +933,7 @@
sp<Client> tmp = nullptr;
if (!(ret = connectHelper<ICameraClient,Client>(
sp<ICameraClient>{nullptr}, id, cameraId,
- static_cast<int>(CAMERA_HAL_API_VERSION_UNSPECIFIED),
- internalPackageName, std::unique_ptr<String16>(), uid, USE_CALLING_PID,
+ internalPackageName, {}, uid, USE_CALLING_PID,
API_1, /*shimUpdateOnly*/ true, /*out*/ tmp)
).isOk()) {
ALOGE("%s: Error initializing shim metadata: %s", __FUNCTION__, ret.toString8().string());
@@ -1366,7 +1349,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) {
@@ -1463,34 +1451,7 @@
String8 id = cameraIdIntToStr(api1CameraId);
sp<Client> client = nullptr;
ret = connectHelper<ICameraClient,Client>(cameraClient, id, api1CameraId,
- CAMERA_HAL_API_VERSION_UNSPECIFIED, clientPackageName, std::unique_ptr<String16>(),
- clientUid, clientPid, API_1, /*shimUpdateOnly*/ false, /*out*/client);
-
- if(!ret.isOk()) {
- logRejected(id, CameraThreadState::getCallingPid(), String8(clientPackageName),
- ret.toString8());
- return ret;
- }
-
- *device = client;
- return ret;
-}
-
-Status CameraService::connectLegacy(
- const sp<ICameraClient>& cameraClient,
- int api1CameraId, int halVersion,
- const String16& clientPackageName,
- int clientUid,
- /*out*/
- sp<ICamera>* device) {
-
- ATRACE_CALL();
- String8 id = cameraIdIntToStr(api1CameraId);
-
- Status ret = Status::ok();
- sp<Client> client = nullptr;
- ret = connectHelper<ICameraClient,Client>(cameraClient, id, api1CameraId, halVersion,
- clientPackageName, std::unique_ptr<String16>(), clientUid, USE_CALLING_PID, API_1,
+ clientPackageName, {}, clientUid, clientPid, API_1,
/*shimUpdateOnly*/ false, /*out*/client);
if(!ret.isOk()) {
@@ -1532,8 +1493,9 @@
int cUid = CameraThreadState::getCallingUid();
SystemCameraKind systemCameraKind = SystemCameraKind::PUBLIC;
if (getSystemCameraKind(cameraId, &systemCameraKind) != OK) {
- ALOGE("%s: Invalid camera id %s, ", __FUNCTION__, cameraId.c_str());
- return true;
+ // This isn't a known camera ID, so it's not a system camera
+ ALOGV("%s: Unknown camera id %s, ", __FUNCTION__, cameraId.c_str());
+ return false;
}
// (1) Cameraserver trying to connect, accept.
@@ -1565,7 +1527,7 @@
const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb,
const String16& cameraId,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
int clientUid,
/*out*/
sp<hardware::camera2::ICameraDeviceUser>* device) {
@@ -1582,8 +1544,7 @@
clientPackageNameAdj = String16(vendorClient.c_str());
}
ret = connectHelper<hardware::camera2::ICameraDeviceCallbacks,CameraDeviceClient>(cameraCb, id,
- /*api1CameraId*/-1,
- CAMERA_HAL_API_VERSION_UNSPECIFIED, clientPackageNameAdj, clientFeatureId,
+ /*api1CameraId*/-1, clientPackageNameAdj, clientFeatureId,
clientUid, USE_CALLING_PID, API_2, /*shimUpdateOnly*/ false, /*out*/client);
if(!ret.isOk()) {
@@ -1598,8 +1559,8 @@
template<class CALLBACK, class CLIENT>
Status CameraService::connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,
- int api1CameraId, int halVersion, const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId, int clientUid, int clientPid,
+ int api1CameraId, const String16& clientPackageName,
+ const std::optional<String16>& clientFeatureId, int clientUid, int clientPid,
apiLevel effectiveApiLevel, bool shimUpdateOnly,
/*out*/sp<CLIENT>& device) {
binder::Status ret = binder::Status::ok();
@@ -1608,9 +1569,8 @@
int originalClientPid = 0;
- ALOGI("CameraService::connect call (PID %d \"%s\", camera ID %s) for HAL version %s and "
+ ALOGI("CameraService::connect call (PID %d \"%s\", camera ID %s) and "
"Camera API version %d", clientPid, clientName8.string(), cameraId.string(),
- (halVersion == -1) ? "default" : std::to_string(halVersion).c_str(),
static_cast<int>(effectiveApiLevel));
sp<CLIENT> client = nullptr;
@@ -1627,7 +1587,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 +1618,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\"",
@@ -1686,7 +1650,7 @@
if(!(ret = makeClient(this, cameraCb, clientPackageName, clientFeatureId,
cameraId, api1CameraId, facing,
clientPid, clientUid, getpid(),
- halVersion, deviceVersion, effectiveApiLevel,
+ deviceVersion, effectiveApiLevel,
/*out*/&tmp)).isOk()) {
return ret;
}
@@ -2697,7 +2661,7 @@
CameraService::Client::Client(const sp<CameraService>& cameraService,
const sp<ICameraClient>& cameraClient,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraIdStr,
int api1CameraId, int cameraFacing,
int clientPid, uid_t clientUid,
@@ -2734,24 +2698,18 @@
CameraService::BasicClient::BasicClient(const sp<CameraService>& cameraService,
const sp<IBinder>& remoteCallback,
- const String16& clientPackageName, const std::unique_ptr<String16>& clientFeatureId,
+ const String16& clientPackageName, const std::optional<String16>& clientFeatureId,
const String8& cameraIdStr, int cameraFacing,
int clientPid, uid_t clientUid,
int servicePid):
mCameraIdStr(cameraIdStr), mCameraFacing(cameraFacing),
- mClientPackageName(clientPackageName),
+ mClientPackageName(clientPackageName), mClientFeatureId(clientFeatureId),
mClientPid(clientPid), mClientUid(clientUid),
mServicePid(servicePid),
mDisconnected(false), mUidIsTrusted(false),
mAudioRestriction(hardware::camera2::ICameraDeviceUser::AUDIO_RESTRICTION_NONE),
mRemoteBinder(remoteCallback)
{
- if (clientFeatureId) {
- mClientFeatureId = std::unique_ptr<String16>(new String16(*clientFeatureId));
- } else {
- mClientFeatureId = std::unique_ptr<String16>();
- }
-
if (sCameraService == nullptr) {
sCameraService = cameraService;
}
@@ -3757,13 +3715,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 +3833,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 +3850,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..3d3b7dd 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -51,6 +51,7 @@
#include <string>
#include <map>
#include <memory>
+#include <optional>
#include <utility>
#include <unordered_map>
#include <unordered_set>
@@ -69,7 +70,6 @@
public virtual CameraProviderManager::StatusListener
{
friend class BinderService<CameraService>;
- friend class CameraClient;
friend class CameraOfflineSessionClient;
public:
class Client;
@@ -133,15 +133,9 @@
/*out*/
sp<hardware::ICamera>* device);
- virtual binder::Status connectLegacy(const sp<hardware::ICameraClient>& cameraClient,
- int32_t cameraId, int32_t halVersion,
- const String16& clientPackageName, int32_t clientUid,
- /*out*/
- sp<hardware::ICamera>* device);
-
virtual binder::Status connectDevice(
const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb, const String16& cameraId,
- const String16& clientPackageName, const std::unique_ptr<String16>& clientFeatureId,
+ const String16& clientPackageName, const std::optional<String16>& clientFeatureId,
int32_t clientUid,
/*out*/
sp<hardware::camera2::ICameraDeviceUser>* device);
@@ -298,7 +292,7 @@
BasicClient(const sp<CameraService>& cameraService,
const sp<IBinder>& remoteCallback,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraIdStr,
int cameraFacing,
int clientPid,
@@ -318,7 +312,7 @@
const String8 mCameraIdStr;
const int mCameraFacing;
String16 mClientPackageName;
- std::unique_ptr<String16> mClientFeatureId;
+ std::optional<String16> mClientFeatureId;
pid_t mClientPid;
const uid_t mClientUid;
const pid_t mServicePid;
@@ -390,7 +384,7 @@
Client(const sp<CameraService>& cameraService,
const sp<hardware::ICameraClient>& cameraClient,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraIdStr,
int api1CameraId,
int cameraFacing,
@@ -727,8 +721,8 @@
// Single implementation shared between the various connect calls
template<class CALLBACK, class CLIENT>
binder::Status connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,
- int api1CameraId, int halVersion, const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId, int clientUid, int clientPid,
+ int api1CameraId, const String16& clientPackageName,
+ const std::optional<String16>& clientFeatureId, int clientUid, int clientPid,
apiLevel effectiveApiLevel, bool shimUpdateOnly, /*out*/sp<CLIENT>& device);
// Lock guarding camera service state
@@ -1005,7 +999,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);
@@ -1056,8 +1051,8 @@
static binder::Status makeClient(const sp<CameraService>& cameraService,
const sp<IInterface>& cameraCb, const String16& packageName,
- const std::unique_ptr<String16>& featureId, const String8& cameraId, int api1CameraId,
- int facing, int clientPid, uid_t clientUid, int servicePid, int halVersion,
+ const std::optional<String16>& featureId, const String8& cameraId, int api1CameraId,
+ int facing, int clientPid, uid_t clientUid, int servicePid,
int deviceVersion, apiLevel effectiveApiLevel,
/*out*/sp<BasicClient>* client);
diff --git a/services/camera/libcameraservice/TEST_MAPPING b/services/camera/libcameraservice/TEST_MAPPING
index 6fdac68..ca6cc58 100644
--- a/services/camera/libcameraservice/TEST_MAPPING
+++ b/services/camera/libcameraservice/TEST_MAPPING
@@ -1,7 +1,12 @@
{
"presubmit": [
{
- "name": "cameraservice_test"
+ "name": "cameraservice_test"
+ }
+ ],
+ "imports": [
+ {
+ "path": "frameworks/av/camera"
}
]
}
diff --git a/services/camera/libcameraservice/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index ebb0555..09e2c3f 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -50,7 +50,7 @@
Camera2Client::Camera2Client(const sp<CameraService>& cameraService,
const sp<hardware::ICameraClient>& cameraClient,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraDeviceId,
int api1CameraId,
int cameraFacing,
@@ -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..f8da0b6 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.h
+++ b/services/camera/libcameraservice/api1/Camera2Client.h
@@ -94,7 +94,7 @@
Camera2Client(const sp<CameraService>& cameraService,
const sp<hardware::ICameraClient>& cameraClient,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraDeviceId,
int api1CameraId,
int cameraFacing,
@@ -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/CameraClient.cpp b/services/camera/libcameraservice/api1/CameraClient.cpp
deleted file mode 100644
index 892996c..0000000
--- a/services/camera/libcameraservice/api1/CameraClient.cpp
+++ /dev/null
@@ -1,1208 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "CameraClient"
-//#define LOG_NDEBUG 0
-
-#include <cutils/atomic.h>
-#include <cutils/properties.h>
-#include <gui/Surface.h>
-#include <media/hardware/HardwareAPI.h>
-
-#include "api1/CameraClient.h"
-#include "device1/CameraHardwareInterface.h"
-#include "CameraService.h"
-#include "utils/CameraThreadState.h"
-
-namespace android {
-
-#define LOG1(...) ALOGD_IF(gLogLevel >= 1, __VA_ARGS__);
-#define LOG2(...) ALOGD_IF(gLogLevel >= 2, __VA_ARGS__);
-
-CameraClient::CameraClient(const sp<CameraService>& cameraService,
- const sp<hardware::ICameraClient>& cameraClient,
- const String16& clientPackageName, const std::unique_ptr<String16>& clientFeatureId,
- int cameraId, int cameraFacing,
- int clientPid, int clientUid,
- int servicePid):
- Client(cameraService, cameraClient, clientPackageName, clientFeatureId,
- String8::format("%d", cameraId), cameraId, cameraFacing, clientPid,
- clientUid, servicePid)
-{
- int callingPid = CameraThreadState::getCallingPid();
- LOG1("CameraClient::CameraClient E (pid %d, id %d)", callingPid, cameraId);
-
- mHardware = NULL;
- mMsgEnabled = 0;
- mSurface = 0;
- mPreviewWindow = 0;
- mDestructionStarted = false;
-
- // Callback is disabled by default
- mPreviewCallbackFlag = CAMERA_FRAME_CALLBACK_FLAG_NOOP;
- mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT);
- mPlayShutterSound = true;
- LOG1("CameraClient::CameraClient X (pid %d, id %d)", callingPid, cameraId);
-}
-
-status_t CameraClient::initialize(sp<CameraProviderManager> manager,
- const String8& /*monitorTags*/) {
- int callingPid = CameraThreadState::getCallingPid();
- status_t res;
-
- LOG1("CameraClient::initialize E (pid %d, id %d)", callingPid, mCameraId);
-
- // Verify ops permissions
- res = startCameraOps();
- if (res != OK) {
- return res;
- }
-
- char camera_device_name[10];
- snprintf(camera_device_name, sizeof(camera_device_name), "%d", mCameraId);
-
- mHardware = new CameraHardwareInterface(camera_device_name);
- res = mHardware->initialize(manager);
- if (res != OK) {
- ALOGE("%s: Camera %d: unable to initialize device: %s (%d)",
- __FUNCTION__, mCameraId, strerror(-res), res);
- mHardware.clear();
- return res;
- }
-
- mHardware->setCallbacks(notifyCallback,
- dataCallback,
- dataCallbackTimestamp,
- handleCallbackTimestampBatch,
- (void *)(uintptr_t)mCameraId);
-
- // Enable zoom, error, focus, and metadata messages by default
- enableMsgType(CAMERA_MSG_ERROR | CAMERA_MSG_ZOOM | CAMERA_MSG_FOCUS |
- CAMERA_MSG_PREVIEW_METADATA | CAMERA_MSG_FOCUS_MOVE);
-
- LOG1("CameraClient::initialize X (pid %d, id %d)", callingPid, mCameraId);
- return OK;
-}
-
-
-// tear down the client
-CameraClient::~CameraClient() {
- mDestructionStarted = true;
- int callingPid = CameraThreadState::getCallingPid();
- LOG1("CameraClient::~CameraClient E (pid %d, this %p)", callingPid, this);
-
- disconnect();
- LOG1("CameraClient::~CameraClient X (pid %d, this %p)", callingPid, this);
-}
-
-status_t CameraClient::dump(int fd, const Vector<String16>& args) {
- return BasicClient::dump(fd, args);
-}
-
-status_t CameraClient::dumpClient(int fd, const Vector<String16>& args) {
- const size_t SIZE = 256;
- char buffer[SIZE];
-
- size_t len = snprintf(buffer, SIZE, "Client[%d] (%p) with UID %d\n",
- mCameraId,
- (getRemoteCallback() != NULL ?
- IInterface::asBinder(getRemoteCallback()).get() : NULL),
- mClientUid);
- len = (len > SIZE - 1) ? SIZE - 1 : len;
- write(fd, buffer, len);
-
- len = snprintf(buffer, SIZE, "Latest set parameters:\n");
- len = (len > SIZE - 1) ? SIZE - 1 : len;
- write(fd, buffer, len);
-
- mLatestSetParameters.dump(fd, args);
-
- const char *enddump = "\n\n";
- write(fd, enddump, strlen(enddump));
-
- sp<CameraHardwareInterface> hardware = mHardware;
- if (hardware != nullptr) {
- return hardware->dump(fd, args);
- }
- ALOGI("%s: camera device closed already, skip dumping", __FUNCTION__);
- return OK;
-}
-
-// ----------------------------------------------------------------------------
-
-status_t CameraClient::checkPid() const {
- int callingPid = CameraThreadState::getCallingPid();
- if (callingPid == mClientPid) return NO_ERROR;
-
- ALOGW("attempt to use a locked camera from a different process"
- " (old pid %d, new pid %d)", mClientPid, callingPid);
- return EBUSY;
-}
-
-status_t CameraClient::checkPidAndHardware() const {
- if (mHardware == 0) {
- ALOGE("attempt to use a camera after disconnect() (pid %d)",
- CameraThreadState::getCallingPid());
- return INVALID_OPERATION;
- }
- status_t result = checkPid();
- if (result != NO_ERROR) return result;
- return NO_ERROR;
-}
-
-status_t CameraClient::lock() {
- int callingPid = CameraThreadState::getCallingPid();
- LOG1("lock (pid %d)", callingPid);
- Mutex::Autolock lock(mLock);
-
- // lock camera to this client if the the camera is unlocked
- if (mClientPid == 0) {
- mClientPid = callingPid;
- return NO_ERROR;
- }
-
- // returns NO_ERROR if the client already owns the camera, EBUSY otherwise
- return checkPid();
-}
-
-status_t CameraClient::unlock() {
- int callingPid = CameraThreadState::getCallingPid();
- LOG1("unlock (pid %d)", callingPid);
- Mutex::Autolock lock(mLock);
-
- // allow anyone to use camera (after they lock the camera)
- status_t result = checkPid();
- if (result == NO_ERROR) {
- if (mHardware->recordingEnabled()) {
- ALOGE("Not allowed to unlock camera during recording.");
- return INVALID_OPERATION;
- }
- mClientPid = 0;
- LOG1("clear mRemoteCallback (pid %d)", callingPid);
- // we need to remove the reference to ICameraClient so that when the app
- // goes away, the reference count goes to 0.
- mRemoteCallback.clear();
- }
- return result;
-}
-
-// connect a new client to the camera
-status_t CameraClient::connect(const sp<hardware::ICameraClient>& client) {
- int callingPid = CameraThreadState::getCallingPid();
- LOG1("connect E (pid %d)", callingPid);
- Mutex::Autolock lock(mLock);
-
- if (mClientPid != 0 && checkPid() != NO_ERROR) {
- ALOGW("Tried to connect to a locked camera (old pid %d, new pid %d)",
- mClientPid, callingPid);
- return EBUSY;
- }
-
- if (mRemoteCallback != 0 &&
- (IInterface::asBinder(client) == IInterface::asBinder(mRemoteCallback))) {
- LOG1("Connect to the same client");
- return NO_ERROR;
- }
-
- mPreviewCallbackFlag = CAMERA_FRAME_CALLBACK_FLAG_NOOP;
- mClientPid = callingPid;
- mRemoteCallback = client;
-
- LOG1("connect X (pid %d)", callingPid);
- return NO_ERROR;
-}
-
-static void disconnectWindow(const sp<ANativeWindow>& window) {
- if (window != 0) {
- status_t result = native_window_api_disconnect(window.get(),
- NATIVE_WINDOW_API_CAMERA);
- if (result != NO_ERROR) {
- ALOGW("native_window_api_disconnect failed: %s (%d)", strerror(-result),
- result);
- }
- }
-}
-
-binder::Status CameraClient::disconnect() {
- int callingPid = CameraThreadState::getCallingPid();
- LOG1("disconnect E (pid %d)", callingPid);
- Mutex::Autolock lock(mLock);
-
- binder::Status res = binder::Status::ok();
- // Allow both client and the cameraserver to disconnect at all times
- if (callingPid != mClientPid && callingPid != mServicePid) {
- ALOGW("different client - don't disconnect");
- return res;
- }
-
- // Make sure disconnect() is done once and once only, whether it is called
- // from the user directly, or called by the destructor.
- if (mHardware == 0) return res;
-
- LOG1("hardware teardown");
- // Before destroying mHardware, we must make sure it's in the
- // idle state.
- // Turn off all messages.
- disableMsgType(CAMERA_MSG_ALL_MSGS);
- mHardware->stopPreview();
- sCameraService->updateProxyDeviceState(
- hardware::ICameraServiceProxy::CAMERA_STATE_IDLE,
- mCameraIdStr, mCameraFacing, mClientPackageName,
- hardware::ICameraServiceProxy::CAMERA_API_LEVEL_1);
- mHardware->cancelPicture();
- // Release the hardware resources.
- mHardware->release();
-
- // Release the held ANativeWindow resources.
- if (mPreviewWindow != 0) {
- disconnectWindow(mPreviewWindow);
- mPreviewWindow = 0;
- mHardware->setPreviewWindow(mPreviewWindow);
- }
- mHardware.clear();
-
- CameraService::Client::disconnect();
-
- LOG1("disconnect X (pid %d)", callingPid);
-
- return res;
-}
-
-// ----------------------------------------------------------------------------
-
-status_t CameraClient::setPreviewWindow(const sp<IBinder>& binder,
- const sp<ANativeWindow>& window) {
- Mutex::Autolock lock(mLock);
- status_t result = checkPidAndHardware();
- if (result != NO_ERROR) return result;
-
- // return if no change in surface.
- if (binder == mSurface) {
- return NO_ERROR;
- }
-
- if (window != 0) {
- result = native_window_api_connect(window.get(), NATIVE_WINDOW_API_CAMERA);
- if (result != NO_ERROR) {
- ALOGE("native_window_api_connect failed: %s (%d)", strerror(-result),
- result);
- return result;
- }
- }
-
- // If preview has been already started, register preview buffers now.
- if (mHardware->previewEnabled()) {
- if (window != 0) {
- mHardware->setPreviewScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
- mHardware->setPreviewTransform(mOrientation);
- result = mHardware->setPreviewWindow(window);
- }
- }
-
- if (result == NO_ERROR) {
- // Everything has succeeded. Disconnect the old window and remember the
- // new window.
- disconnectWindow(mPreviewWindow);
- mSurface = binder;
- mPreviewWindow = window;
- } else {
- // Something went wrong after we connected to the new window, so
- // disconnect here.
- disconnectWindow(window);
- }
-
- return result;
-}
-
-// set the buffer consumer that the preview will use
-status_t CameraClient::setPreviewTarget(
- const sp<IGraphicBufferProducer>& bufferProducer) {
- LOG1("setPreviewTarget(%p) (pid %d)", bufferProducer.get(),
- CameraThreadState::getCallingPid());
-
- sp<IBinder> binder;
- sp<ANativeWindow> window;
- if (bufferProducer != 0) {
- binder = IInterface::asBinder(bufferProducer);
- // Using controlledByApp flag to ensure that the buffer queue remains in
- // async mode for the old camera API, where many applications depend
- // on that behavior.
- window = new Surface(bufferProducer, /*controlledByApp*/ true);
- }
- return setPreviewWindow(binder, window);
-}
-
-// set the preview callback flag to affect how the received frames from
-// preview are handled.
-void CameraClient::setPreviewCallbackFlag(int callback_flag) {
- LOG1("setPreviewCallbackFlag(%d) (pid %d)", callback_flag, CameraThreadState::getCallingPid());
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) return;
-
- mPreviewCallbackFlag = callback_flag;
- if (mPreviewCallbackFlag & CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK) {
- enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- } else {
- disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- }
-}
-
-status_t CameraClient::setPreviewCallbackTarget(
- const sp<IGraphicBufferProducer>& callbackProducer) {
- (void)callbackProducer;
- ALOGE("%s: Unimplemented!", __FUNCTION__);
- return INVALID_OPERATION;
-}
-
-// start preview mode
-status_t CameraClient::startPreview() {
- LOG1("startPreview (pid %d)", CameraThreadState::getCallingPid());
- return startCameraMode(CAMERA_PREVIEW_MODE);
-}
-
-// start recording mode
-status_t CameraClient::startRecording() {
- LOG1("startRecording (pid %d)", CameraThreadState::getCallingPid());
- return startCameraMode(CAMERA_RECORDING_MODE);
-}
-
-// start preview or recording
-status_t CameraClient::startCameraMode(camera_mode mode) {
- LOG1("startCameraMode(%d)", mode);
- Mutex::Autolock lock(mLock);
- status_t result = checkPidAndHardware();
- if (result != NO_ERROR) return result;
-
- switch(mode) {
- case CAMERA_PREVIEW_MODE:
- if (mSurface == 0 && mPreviewWindow == 0) {
- LOG1("mSurface is not set yet.");
- // still able to start preview in this case.
- }
- return startPreviewMode();
- case CAMERA_RECORDING_MODE:
- if (mSurface == 0 && mPreviewWindow == 0) {
- ALOGE("mSurface or mPreviewWindow must be set before startRecordingMode.");
- return INVALID_OPERATION;
- }
- return startRecordingMode();
- default:
- return UNKNOWN_ERROR;
- }
-}
-
-status_t CameraClient::startPreviewMode() {
- LOG1("startPreviewMode");
- status_t result = NO_ERROR;
-
- // if preview has been enabled, nothing needs to be done
- if (mHardware->previewEnabled()) {
- return NO_ERROR;
- }
-
- if (mPreviewWindow != 0) {
- mHardware->setPreviewScalingMode(
- NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
- mHardware->setPreviewTransform(mOrientation);
- }
- mHardware->setPreviewWindow(mPreviewWindow);
- result = mHardware->startPreview();
- if (result == NO_ERROR) {
- sCameraService->updateProxyDeviceState(
- hardware::ICameraServiceProxy::CAMERA_STATE_ACTIVE,
- mCameraIdStr, mCameraFacing, mClientPackageName,
- hardware::ICameraServiceProxy::CAMERA_API_LEVEL_1);
- }
- return result;
-}
-
-status_t CameraClient::startRecordingMode() {
- LOG1("startRecordingMode");
- status_t result = NO_ERROR;
-
- // if recording has been enabled, nothing needs to be done
- if (mHardware->recordingEnabled()) {
- return NO_ERROR;
- }
-
- // if preview has not been started, start preview first
- if (!mHardware->previewEnabled()) {
- result = startPreviewMode();
- if (result != NO_ERROR) {
- return result;
- }
- }
-
- // start recording mode
- enableMsgType(CAMERA_MSG_VIDEO_FRAME);
- sCameraService->playSound(CameraService::SOUND_RECORDING_START);
- result = mHardware->startRecording();
- if (result != NO_ERROR) {
- ALOGE("mHardware->startRecording() failed with status %d", result);
- }
- return result;
-}
-
-// stop preview mode
-void CameraClient::stopPreview() {
- LOG1("stopPreview (pid %d)", CameraThreadState::getCallingPid());
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) return;
-
-
- disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- mHardware->stopPreview();
- sCameraService->updateProxyDeviceState(
- hardware::ICameraServiceProxy::CAMERA_STATE_IDLE,
- mCameraIdStr, mCameraFacing, mClientPackageName,
- hardware::ICameraServiceProxy::CAMERA_API_LEVEL_1);
- mPreviewBuffer.clear();
-}
-
-// stop recording mode
-void CameraClient::stopRecording() {
- LOG1("stopRecording (pid %d)", CameraThreadState::getCallingPid());
- {
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) return;
-
- disableMsgType(CAMERA_MSG_VIDEO_FRAME);
- mHardware->stopRecording();
- sCameraService->playSound(CameraService::SOUND_RECORDING_STOP);
-
- mPreviewBuffer.clear();
- }
-
- {
- Mutex::Autolock l(mAvailableCallbackBuffersLock);
- if (!mAvailableCallbackBuffers.empty()) {
- mAvailableCallbackBuffers.clear();
- }
- }
-}
-
-// release a recording frame
-void CameraClient::releaseRecordingFrame(const sp<IMemory>& mem) {
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) return;
- if (mem == nullptr) {
- android_errorWriteWithInfoLog(CameraService::SN_EVENT_LOG_ID, "26164272",
- CameraThreadState::getCallingUid(), nullptr, 0);
- return;
- }
-
- mHardware->releaseRecordingFrame(mem);
-}
-
-void CameraClient::releaseRecordingFrameHandle(native_handle_t *handle) {
- if (handle == nullptr) return;
- Mutex::Autolock lock(mLock);
- sp<IMemory> dataPtr;
- {
- Mutex::Autolock l(mAvailableCallbackBuffersLock);
- if (!mAvailableCallbackBuffers.empty()) {
- dataPtr = mAvailableCallbackBuffers.back();
- mAvailableCallbackBuffers.pop_back();
- }
- }
-
- if (dataPtr == nullptr) {
- ALOGE("%s: %d: No callback buffer available. Dropping a native handle.", __FUNCTION__,
- __LINE__);
- native_handle_close(handle);
- native_handle_delete(handle);
- return;
- } else if (dataPtr->size() != sizeof(VideoNativeHandleMetadata)) {
- ALOGE("%s: %d: Callback buffer size doesn't match VideoNativeHandleMetadata", __FUNCTION__,
- __LINE__);
- native_handle_close(handle);
- native_handle_delete(handle);
- return;
- }
-
- if (mHardware != nullptr) {
- VideoNativeHandleMetadata *metadata = (VideoNativeHandleMetadata*)(dataPtr->unsecurePointer());
- metadata->eType = kMetadataBufferTypeNativeHandleSource;
- metadata->pHandle = handle;
- mHardware->releaseRecordingFrame(dataPtr);
- }
-}
-
-void CameraClient::releaseRecordingFrameHandleBatch(const std::vector<native_handle_t*>& handles) {
- Mutex::Autolock lock(mLock);
- bool disconnected = (mHardware == nullptr);
- size_t n = handles.size();
- std::vector<sp<IMemory>> frames;
- if (!disconnected) {
- frames.reserve(n);
- }
- bool error = false;
- for (auto& handle : handles) {
- sp<IMemory> dataPtr;
- {
- Mutex::Autolock l(mAvailableCallbackBuffersLock);
- if (!mAvailableCallbackBuffers.empty()) {
- dataPtr = mAvailableCallbackBuffers.back();
- mAvailableCallbackBuffers.pop_back();
- }
- }
-
- if (dataPtr == nullptr) {
- ALOGE("%s: %d: No callback buffer available. Dropping frames.", __FUNCTION__,
- __LINE__);
- error = true;
- break;
- } else if (dataPtr->size() != sizeof(VideoNativeHandleMetadata)) {
- ALOGE("%s: %d: Callback buffer must be VideoNativeHandleMetadata", __FUNCTION__,
- __LINE__);
- error = true;
- break;
- }
-
- if (!disconnected) {
- VideoNativeHandleMetadata *metadata = (VideoNativeHandleMetadata*)(dataPtr->unsecurePointer());
- metadata->eType = kMetadataBufferTypeNativeHandleSource;
- metadata->pHandle = handle;
- frames.push_back(dataPtr);
- }
- }
-
- if (error) {
- for (auto& handle : handles) {
- native_handle_close(handle);
- native_handle_delete(handle);
- }
- } else if (!disconnected) {
- mHardware->releaseRecordingFrameBatch(frames);
- }
- return;
-}
-
-status_t CameraClient::setVideoBufferMode(int32_t videoBufferMode) {
- LOG1("setVideoBufferMode: %d", videoBufferMode);
- bool enableMetadataInBuffers = false;
-
- if (videoBufferMode == VIDEO_BUFFER_MODE_DATA_CALLBACK_METADATA) {
- enableMetadataInBuffers = true;
- } else if (videoBufferMode != VIDEO_BUFFER_MODE_DATA_CALLBACK_YUV) {
- ALOGE("%s: %d: videoBufferMode %d is not supported.", __FUNCTION__, __LINE__,
- videoBufferMode);
- return BAD_VALUE;
- }
-
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) {
- return UNKNOWN_ERROR;
- }
-
- return mHardware->storeMetaDataInBuffers(enableMetadataInBuffers);
-}
-
-bool CameraClient::previewEnabled() {
- LOG1("previewEnabled (pid %d)", CameraThreadState::getCallingPid());
-
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) return false;
- return mHardware->previewEnabled();
-}
-
-bool CameraClient::recordingEnabled() {
- LOG1("recordingEnabled (pid %d)", CameraThreadState::getCallingPid());
-
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) return false;
- return mHardware->recordingEnabled();
-}
-
-status_t CameraClient::autoFocus() {
- LOG1("autoFocus (pid %d)", CameraThreadState::getCallingPid());
-
- Mutex::Autolock lock(mLock);
- status_t result = checkPidAndHardware();
- if (result != NO_ERROR) return result;
-
- return mHardware->autoFocus();
-}
-
-status_t CameraClient::cancelAutoFocus() {
- LOG1("cancelAutoFocus (pid %d)", CameraThreadState::getCallingPid());
-
- Mutex::Autolock lock(mLock);
- status_t result = checkPidAndHardware();
- if (result != NO_ERROR) return result;
-
- return mHardware->cancelAutoFocus();
-}
-
-// take a picture - image is returned in callback
-status_t CameraClient::takePicture(int msgType) {
- LOG1("takePicture (pid %d): 0x%x", CameraThreadState::getCallingPid(), msgType);
-
- Mutex::Autolock lock(mLock);
- status_t result = checkPidAndHardware();
- if (result != NO_ERROR) return result;
-
- if ((msgType & CAMERA_MSG_RAW_IMAGE) &&
- (msgType & CAMERA_MSG_RAW_IMAGE_NOTIFY)) {
- ALOGE("CAMERA_MSG_RAW_IMAGE and CAMERA_MSG_RAW_IMAGE_NOTIFY"
- " cannot be both enabled");
- return BAD_VALUE;
- }
-
- // We only accept picture related message types
- // and ignore other types of messages for takePicture().
- int picMsgType = msgType
- & (CAMERA_MSG_SHUTTER |
- CAMERA_MSG_POSTVIEW_FRAME |
- CAMERA_MSG_RAW_IMAGE |
- CAMERA_MSG_RAW_IMAGE_NOTIFY |
- CAMERA_MSG_COMPRESSED_IMAGE);
-
- enableMsgType(picMsgType);
-
- return mHardware->takePicture();
-}
-
-// set preview/capture parameters - key/value pairs
-status_t CameraClient::setParameters(const String8& params) {
- LOG1("setParameters (pid %d) (%s)", CameraThreadState::getCallingPid(), params.string());
-
- Mutex::Autolock lock(mLock);
- status_t result = checkPidAndHardware();
- if (result != NO_ERROR) return result;
-
- mLatestSetParameters = CameraParameters(params);
- CameraParameters p(params);
- return mHardware->setParameters(p);
-}
-
-// get preview/capture parameters - key/value pairs
-String8 CameraClient::getParameters() const {
- Mutex::Autolock lock(mLock);
- // The camera service can unconditionally get the parameters at all times
- if (CameraThreadState::getCallingPid() != mServicePid && checkPidAndHardware() != NO_ERROR) {
- return String8();
- }
-
- String8 params(mHardware->getParameters().flatten());
- LOG1("getParameters (pid %d) (%s)", CameraThreadState::getCallingPid(), params.string());
- return params;
-}
-
-// enable shutter sound
-status_t CameraClient::enableShutterSound(bool enable) {
- LOG1("enableShutterSound (pid %d)", CameraThreadState::getCallingPid());
-
- status_t result = checkPidAndHardware();
- if (result != NO_ERROR) return result;
-
- if (enable) {
- mPlayShutterSound = true;
- return OK;
- }
-
- mPlayShutterSound = false;
- return OK;
-}
-
-status_t CameraClient::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) {
- LOG1("sendCommand (pid %d)", CameraThreadState::getCallingPid());
- int orientation;
- Mutex::Autolock lock(mLock);
- status_t result = checkPidAndHardware();
- if (result != NO_ERROR) return result;
-
- if (cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION) {
- // Mirror the preview if the camera is front-facing.
- orientation = getOrientation(arg1, mCameraFacing == CAMERA_FACING_FRONT);
- if (orientation == -1) return BAD_VALUE;
-
- if (mOrientation != orientation) {
- mOrientation = orientation;
- if (mPreviewWindow != 0) {
- mHardware->setPreviewTransform(mOrientation);
- }
- }
- return OK;
- } else if (cmd == CAMERA_CMD_ENABLE_SHUTTER_SOUND) {
- switch (arg1) {
- case 0:
- return enableShutterSound(false);
- case 1:
- return enableShutterSound(true);
- default:
- return BAD_VALUE;
- }
- return OK;
- } else if (cmd == CAMERA_CMD_PLAY_RECORDING_SOUND) {
- sCameraService->playSound(CameraService::SOUND_RECORDING_START);
- } else if (cmd == CAMERA_CMD_SET_VIDEO_BUFFER_COUNT) {
- // Silently ignore this command
- return INVALID_OPERATION;
- } else if (cmd == CAMERA_CMD_PING) {
- // If mHardware is 0, checkPidAndHardware will return error.
- return OK;
- }
-
- return mHardware->sendCommand(cmd, arg1, arg2);
-}
-
-// ----------------------------------------------------------------------------
-
-void CameraClient::enableMsgType(int32_t msgType) {
- android_atomic_or(msgType, &mMsgEnabled);
- mHardware->enableMsgType(msgType);
-}
-
-void CameraClient::disableMsgType(int32_t msgType) {
- android_atomic_and(~msgType, &mMsgEnabled);
- mHardware->disableMsgType(msgType);
-}
-
-#define CHECK_MESSAGE_INTERVAL 10 // 10ms
-bool CameraClient::lockIfMessageWanted(int32_t msgType) {
- int sleepCount = 0;
- while (mMsgEnabled & msgType) {
- if (mLock.tryLock() == NO_ERROR) {
- if (sleepCount > 0) {
- LOG1("lockIfMessageWanted(%d): waited for %d ms",
- msgType, sleepCount * CHECK_MESSAGE_INTERVAL);
- }
-
- // If messages are no longer enabled after acquiring lock, release and drop message
- if ((mMsgEnabled & msgType) == 0) {
- mLock.unlock();
- break;
- }
-
- return true;
- }
- if (sleepCount++ == 0) {
- LOG1("lockIfMessageWanted(%d): enter sleep", msgType);
- }
- usleep(CHECK_MESSAGE_INTERVAL * 1000);
- }
- ALOGW("lockIfMessageWanted(%d): dropped unwanted message", msgType);
- return false;
-}
-
-sp<CameraClient> CameraClient::getClientFromCookie(void* user) {
- String8 cameraId = String8::format("%d", (int)(intptr_t) user);
- auto clientDescriptor = sCameraService->mActiveClientManager.get(cameraId);
- if (clientDescriptor != nullptr) {
- return sp<CameraClient>{
- static_cast<CameraClient*>(clientDescriptor->getValue().get())};
- }
- return sp<CameraClient>{nullptr};
-}
-
-// Callback messages can be dispatched to internal handlers or pass to our
-// client's callback functions, depending on the message type.
-//
-// notifyCallback:
-// CAMERA_MSG_SHUTTER handleShutter
-// (others) c->notifyCallback
-// dataCallback:
-// CAMERA_MSG_PREVIEW_FRAME handlePreviewData
-// CAMERA_MSG_POSTVIEW_FRAME handlePostview
-// CAMERA_MSG_RAW_IMAGE handleRawPicture
-// CAMERA_MSG_COMPRESSED_IMAGE handleCompressedPicture
-// (others) c->dataCallback
-// dataCallbackTimestamp
-// (others) c->dataCallbackTimestamp
-
-void CameraClient::notifyCallback(int32_t msgType, int32_t ext1,
- int32_t ext2, void* user) {
- LOG2("notifyCallback(%d)", msgType);
-
- sp<CameraClient> client = getClientFromCookie(user);
- if (client.get() == nullptr) return;
-
- if (!client->lockIfMessageWanted(msgType)) return;
-
- switch (msgType) {
- case CAMERA_MSG_SHUTTER:
- // ext1 is the dimension of the yuv picture.
- client->handleShutter();
- break;
- default:
- client->handleGenericNotify(msgType, ext1, ext2);
- break;
- }
-}
-
-void CameraClient::dataCallback(int32_t msgType,
- const sp<IMemory>& dataPtr, camera_frame_metadata_t *metadata, void* user) {
- LOG2("dataCallback(%d)", msgType);
-
- sp<CameraClient> client = getClientFromCookie(user);
- if (client.get() == nullptr) return;
-
- if (!client->lockIfMessageWanted(msgType)) return;
- if (dataPtr == 0 && metadata == NULL) {
- ALOGE("Null data returned in data callback");
- client->handleGenericNotify(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0);
- return;
- }
-
- switch (msgType & ~CAMERA_MSG_PREVIEW_METADATA) {
- case CAMERA_MSG_PREVIEW_FRAME:
- client->handlePreviewData(msgType, dataPtr, metadata);
- break;
- case CAMERA_MSG_POSTVIEW_FRAME:
- client->handlePostview(dataPtr);
- break;
- case CAMERA_MSG_RAW_IMAGE:
- client->handleRawPicture(dataPtr);
- break;
- case CAMERA_MSG_COMPRESSED_IMAGE:
- client->handleCompressedPicture(dataPtr);
- break;
- default:
- client->handleGenericData(msgType, dataPtr, metadata);
- break;
- }
-}
-
-void CameraClient::dataCallbackTimestamp(nsecs_t timestamp,
- int32_t msgType, const sp<IMemory>& dataPtr, void* user) {
- LOG2("dataCallbackTimestamp(%d)", msgType);
-
- sp<CameraClient> client = getClientFromCookie(user);
- if (client.get() == nullptr) return;
-
- if (!client->lockIfMessageWanted(msgType)) return;
-
- if (dataPtr == 0) {
- ALOGE("Null data returned in data with timestamp callback");
- client->handleGenericNotify(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0);
- return;
- }
-
- client->handleGenericDataTimestamp(timestamp, msgType, dataPtr);
-}
-
-void CameraClient::handleCallbackTimestampBatch(
- int32_t msgType, const std::vector<HandleTimestampMessage>& msgs, void* user) {
- LOG2("dataCallbackTimestampBatch");
- sp<CameraClient> client = getClientFromCookie(user);
- if (client.get() == nullptr) return;
- if (!client->lockIfMessageWanted(msgType)) return;
-
- sp<hardware::ICameraClient> c = client->mRemoteCallback;
- client->mLock.unlock();
- if (c != 0 && msgs.size() > 0) {
- size_t n = msgs.size();
- std::vector<nsecs_t> timestamps;
- std::vector<native_handle_t*> handles;
- timestamps.reserve(n);
- handles.reserve(n);
- for (auto& msg : msgs) {
- native_handle_t* handle = nullptr;
- if (msg.dataPtr->size() != sizeof(VideoNativeHandleMetadata)) {
- ALOGE("%s: dataPtr does not contain VideoNativeHandleMetadata!", __FUNCTION__);
- return;
- }
- // TODO: Using unsecurePointer() has some associated security pitfalls
- // (see declaration for details).
- // Either document why it is safe in this case or address the
- // issue (e.g. by copying).
- VideoNativeHandleMetadata *metadata =
- (VideoNativeHandleMetadata*)(msg.dataPtr->unsecurePointer());
- if (metadata->eType == kMetadataBufferTypeNativeHandleSource) {
- handle = metadata->pHandle;
- }
-
- if (handle == nullptr) {
- ALOGE("%s: VideoNativeHandleMetadata type mismatch or null handle passed!",
- __FUNCTION__);
- return;
- }
- {
- Mutex::Autolock l(client->mAvailableCallbackBuffersLock);
- client->mAvailableCallbackBuffers.push_back(msg.dataPtr);
- }
- timestamps.push_back(msg.timestamp);
- handles.push_back(handle);
- }
- c->recordingFrameHandleCallbackTimestampBatch(timestamps, handles);
- }
-}
-
-// snapshot taken callback
-void CameraClient::handleShutter(void) {
- if (mPlayShutterSound) {
- sCameraService->playSound(CameraService::SOUND_SHUTTER);
- }
-
- sp<hardware::ICameraClient> c = mRemoteCallback;
- if (c != 0) {
- mLock.unlock();
- c->notifyCallback(CAMERA_MSG_SHUTTER, 0, 0);
- if (!lockIfMessageWanted(CAMERA_MSG_SHUTTER)) return;
- }
- disableMsgType(CAMERA_MSG_SHUTTER);
-
- // Shutters only happen in response to takePicture, so mark device as
- // idle now, until preview is restarted
- sCameraService->updateProxyDeviceState(
- hardware::ICameraServiceProxy::CAMERA_STATE_IDLE,
- mCameraIdStr, mCameraFacing, mClientPackageName,
- hardware::ICameraServiceProxy::CAMERA_API_LEVEL_1);
-
- mLock.unlock();
-}
-
-// preview callback - frame buffer update
-void CameraClient::handlePreviewData(int32_t msgType,
- const sp<IMemory>& mem,
- camera_frame_metadata_t *metadata) {
- ssize_t offset;
- size_t size;
- sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
-
- // local copy of the callback flags
- int flags = mPreviewCallbackFlag;
-
- // is callback enabled?
- if (!(flags & CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK)) {
- // If the enable bit is off, the copy-out and one-shot bits are ignored
- LOG2("frame callback is disabled");
- mLock.unlock();
- return;
- }
-
- // hold a strong pointer to the client
- sp<hardware::ICameraClient> c = mRemoteCallback;
-
- // clear callback flags if no client or one-shot mode
- if (c == 0 || (mPreviewCallbackFlag & CAMERA_FRAME_CALLBACK_FLAG_ONE_SHOT_MASK)) {
- LOG2("Disable preview callback");
- mPreviewCallbackFlag &= ~(CAMERA_FRAME_CALLBACK_FLAG_ONE_SHOT_MASK |
- CAMERA_FRAME_CALLBACK_FLAG_COPY_OUT_MASK |
- CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK);
- disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- }
-
- if (c != 0) {
- // Is the received frame copied out or not?
- if (flags & CAMERA_FRAME_CALLBACK_FLAG_COPY_OUT_MASK) {
- LOG2("frame is copied");
- copyFrameAndPostCopiedFrame(msgType, c, heap, offset, size, metadata);
- } else {
- LOG2("frame is forwarded");
- mLock.unlock();
- c->dataCallback(msgType, mem, metadata);
- }
- } else {
- mLock.unlock();
- }
-}
-
-// picture callback - postview image ready
-void CameraClient::handlePostview(const sp<IMemory>& mem) {
- disableMsgType(CAMERA_MSG_POSTVIEW_FRAME);
-
- sp<hardware::ICameraClient> c = mRemoteCallback;
- mLock.unlock();
- if (c != 0) {
- c->dataCallback(CAMERA_MSG_POSTVIEW_FRAME, mem, NULL);
- }
-}
-
-// picture callback - raw image ready
-void CameraClient::handleRawPicture(const sp<IMemory>& mem) {
- disableMsgType(CAMERA_MSG_RAW_IMAGE);
-
- ssize_t offset;
- size_t size;
- sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
-
- sp<hardware::ICameraClient> c = mRemoteCallback;
- mLock.unlock();
- if (c != 0) {
- c->dataCallback(CAMERA_MSG_RAW_IMAGE, mem, NULL);
- }
-}
-
-// picture callback - compressed picture ready
-void CameraClient::handleCompressedPicture(const sp<IMemory>& mem) {
- disableMsgType(CAMERA_MSG_COMPRESSED_IMAGE);
-
- sp<hardware::ICameraClient> c = mRemoteCallback;
- mLock.unlock();
- if (c != 0) {
- c->dataCallback(CAMERA_MSG_COMPRESSED_IMAGE, mem, NULL);
- }
-}
-
-
-void CameraClient::handleGenericNotify(int32_t msgType,
- int32_t ext1, int32_t ext2) {
- sp<hardware::ICameraClient> c = mRemoteCallback;
- mLock.unlock();
- if (c != 0) {
- c->notifyCallback(msgType, ext1, ext2);
- }
-}
-
-void CameraClient::handleGenericData(int32_t msgType,
- const sp<IMemory>& dataPtr, camera_frame_metadata_t *metadata) {
- sp<hardware::ICameraClient> c = mRemoteCallback;
- mLock.unlock();
- if (c != 0) {
- c->dataCallback(msgType, dataPtr, metadata);
- }
-}
-
-void CameraClient::handleGenericDataTimestamp(nsecs_t timestamp,
- int32_t msgType, const sp<IMemory>& dataPtr) {
- sp<hardware::ICameraClient> c = mRemoteCallback;
- mLock.unlock();
- if (c != 0 && dataPtr != nullptr) {
- native_handle_t* handle = nullptr;
-
- // Check if dataPtr contains a VideoNativeHandleMetadata.
- if (dataPtr->size() == sizeof(VideoNativeHandleMetadata)) {
- // TODO: Using unsecurePointer() has some associated security pitfalls
- // (see declaration for details).
- // Either document why it is safe in this case or address the
- // issue (e.g. by copying).
- VideoNativeHandleMetadata *metadata =
- (VideoNativeHandleMetadata*)(dataPtr->unsecurePointer());
- if (metadata->eType == kMetadataBufferTypeNativeHandleSource) {
- handle = metadata->pHandle;
- }
- }
-
- // If dataPtr contains a native handle, send it via recordingFrameHandleCallbackTimestamp.
- if (handle != nullptr) {
- {
- Mutex::Autolock l(mAvailableCallbackBuffersLock);
- mAvailableCallbackBuffers.push_back(dataPtr);
- }
- c->recordingFrameHandleCallbackTimestamp(timestamp, handle);
- } else {
- c->dataCallbackTimestamp(timestamp, msgType, dataPtr);
- }
- }
-}
-
-void CameraClient::copyFrameAndPostCopiedFrame(
- int32_t msgType, const sp<hardware::ICameraClient>& client,
- const sp<IMemoryHeap>& heap, size_t offset, size_t size,
- camera_frame_metadata_t *metadata) {
- LOG2("copyFrameAndPostCopiedFrame");
- // It is necessary to copy out of pmem before sending this to
- // the callback. For efficiency, reuse the same MemoryHeapBase
- // provided it's big enough. Don't allocate the memory or
- // perform the copy if there's no callback.
- // hold the preview lock while we grab a reference to the preview buffer
- sp<MemoryHeapBase> previewBuffer;
-
- if (mPreviewBuffer == 0) {
- mPreviewBuffer = new MemoryHeapBase(size, 0, NULL);
- } else if (size > mPreviewBuffer->virtualSize()) {
- mPreviewBuffer.clear();
- mPreviewBuffer = new MemoryHeapBase(size, 0, NULL);
- }
- if (mPreviewBuffer == 0) {
- ALOGE("failed to allocate space for preview buffer");
- mLock.unlock();
- return;
- }
- previewBuffer = mPreviewBuffer;
-
- void* previewBufferBase = previewBuffer->base();
- void* heapBase = heap->base();
-
- if (heapBase == MAP_FAILED) {
- ALOGE("%s: Failed to mmap heap for preview frame.", __FUNCTION__);
- mLock.unlock();
- return;
- } else if (previewBufferBase == MAP_FAILED) {
- ALOGE("%s: Failed to mmap preview buffer for preview frame.", __FUNCTION__);
- mLock.unlock();
- return;
- }
-
- memcpy(previewBufferBase, (uint8_t *) heapBase + offset, size);
-
- sp<MemoryBase> frame = new MemoryBase(previewBuffer, 0, size);
- if (frame == 0) {
- ALOGE("failed to allocate space for frame callback");
- mLock.unlock();
- return;
- }
-
- mLock.unlock();
- client->dataCallback(msgType, frame, metadata);
-}
-
-int CameraClient::getOrientation(int degrees, bool mirror) {
- if (!mirror) {
- if (degrees == 0) return 0;
- else if (degrees == 90) return HAL_TRANSFORM_ROT_90;
- else if (degrees == 180) return HAL_TRANSFORM_ROT_180;
- else if (degrees == 270) return HAL_TRANSFORM_ROT_270;
- } else { // Do mirror (horizontal flip)
- if (degrees == 0) { // FLIP_H and ROT_0
- return HAL_TRANSFORM_FLIP_H;
- } else if (degrees == 90) { // FLIP_H and ROT_90
- return HAL_TRANSFORM_FLIP_H | HAL_TRANSFORM_ROT_90;
- } else if (degrees == 180) { // FLIP_H and ROT_180
- return HAL_TRANSFORM_FLIP_V;
- } else if (degrees == 270) { // FLIP_H and ROT_270
- return HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_ROT_90;
- }
- }
- ALOGE("Invalid setDisplayOrientation degrees=%d", degrees);
- return -1;
-}
-
-status_t CameraClient::setVideoTarget(const sp<IGraphicBufferProducer>& bufferProducer) {
- (void)bufferProducer;
- ALOGE("%s: %d: CameraClient doesn't support setting a video target.", __FUNCTION__, __LINE__);
- return INVALID_OPERATION;
-}
-
-status_t CameraClient::setAudioRestriction(int mode) {
- if (!isValidAudioRestriction(mode)) {
- ALOGE("%s: invalid audio restriction mode %d", __FUNCTION__, mode);
- return BAD_VALUE;
- }
-
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) {
- return INVALID_OPERATION;
- }
- return BasicClient::setAudioRestriction(mode);
-}
-
-int32_t CameraClient::getGlobalAudioRestriction() {
- Mutex::Autolock lock(mLock);
- if (checkPidAndHardware() != NO_ERROR) {
- return INVALID_OPERATION;
- }
- return BasicClient::getServiceAudioRestriction();
-}
-
-// API1->Device1 does not support this feature
-status_t CameraClient::setRotateAndCropOverride(uint8_t /*rotateAndCrop*/) {
- return OK;
-}
-
-}; // namespace android
diff --git a/services/camera/libcameraservice/api1/CameraClient.h b/services/camera/libcameraservice/api1/CameraClient.h
deleted file mode 100644
index a7eb960..0000000
--- a/services/camera/libcameraservice/api1/CameraClient.h
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_SERVERS_CAMERA_CAMERACLIENT_H
-#define ANDROID_SERVERS_CAMERA_CAMERACLIENT_H
-
-#include "CameraService.h"
-
-namespace android {
-
-class MemoryHeapBase;
-class CameraHardwareInterface;
-
-/**
- * Interface between android.hardware.Camera API and Camera HAL device for version
- * CAMERA_DEVICE_API_VERSION_1_0.
- */
-
-class CameraClient : public CameraService::Client
-{
-public:
- // ICamera interface (see ICamera for details)
- virtual binder::Status disconnect();
- virtual status_t connect(const sp<hardware::ICameraClient>& client);
- virtual status_t lock();
- virtual status_t unlock();
- virtual status_t setPreviewTarget(const sp<IGraphicBufferProducer>& bufferProducer);
- virtual void setPreviewCallbackFlag(int flag);
- virtual status_t setPreviewCallbackTarget(
- const sp<IGraphicBufferProducer>& callbackProducer);
- virtual status_t startPreview();
- virtual void stopPreview();
- virtual bool previewEnabled();
- virtual status_t setVideoBufferMode(int32_t videoBufferMode);
- virtual status_t startRecording();
- virtual void stopRecording();
- virtual bool recordingEnabled();
- virtual void releaseRecordingFrame(const sp<IMemory>& mem);
- virtual void releaseRecordingFrameHandle(native_handle_t *handle);
- virtual void releaseRecordingFrameHandleBatch(
- const std::vector<native_handle_t*>& handles);
- virtual status_t autoFocus();
- virtual status_t cancelAutoFocus();
- virtual status_t takePicture(int msgType);
- virtual status_t setParameters(const String8& params);
- virtual String8 getParameters() const;
- virtual status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2);
- virtual status_t setVideoTarget(const sp<IGraphicBufferProducer>& bufferProducer);
- virtual status_t setAudioRestriction(int mode);
- virtual int32_t getGlobalAudioRestriction();
-
- virtual status_t setRotateAndCropOverride(uint8_t override);
-
- // Interface used by CameraService
- CameraClient(const sp<CameraService>& cameraService,
- const sp<hardware::ICameraClient>& cameraClient,
- const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
- int cameraId,
- int cameraFacing,
- int clientPid,
- int clientUid,
- int servicePid);
- ~CameraClient();
-
- virtual status_t initialize(sp<CameraProviderManager> manager,
- const String8& monitorTags) override;
-
- virtual status_t dump(int fd, const Vector<String16>& args);
-
- virtual status_t dumpClient(int fd, const Vector<String16>& args);
-
-private:
-
- // check whether the calling process matches mClientPid.
- status_t checkPid() const;
- status_t checkPidAndHardware() const; // also check mHardware != 0
-
- // these are internal functions used to set up preview buffers
- status_t registerPreviewBuffers();
-
- // camera operation mode
- enum camera_mode {
- CAMERA_PREVIEW_MODE = 0, // frame automatically released
- CAMERA_RECORDING_MODE = 1, // frame has to be explicitly released by releaseRecordingFrame()
- };
- // these are internal functions used for preview/recording
- status_t startCameraMode(camera_mode mode);
- status_t startPreviewMode();
- status_t startRecordingMode();
-
- // internal function used by sendCommand to enable/disable shutter sound.
- status_t enableShutterSound(bool enable);
-
- static sp<CameraClient> getClientFromCookie(void* user);
-
- // these are static callback functions
- static void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user);
- static void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr,
- camera_frame_metadata_t *metadata, void* user);
- static void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr, void* user);
- static void handleCallbackTimestampBatch(
- int32_t msgType, const std::vector<HandleTimestampMessage>&, void* user);
- // handlers for messages
- void handleShutter(void);
- void handlePreviewData(int32_t msgType, const sp<IMemory>& mem,
- camera_frame_metadata_t *metadata);
- void handlePostview(const sp<IMemory>& mem);
- void handleRawPicture(const sp<IMemory>& mem);
- void handleCompressedPicture(const sp<IMemory>& mem);
- void handleGenericNotify(int32_t msgType, int32_t ext1, int32_t ext2);
- void handleGenericData(int32_t msgType, const sp<IMemory>& dataPtr,
- camera_frame_metadata_t *metadata);
- void handleGenericDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
-
- void copyFrameAndPostCopiedFrame(
- int32_t msgType,
- const sp<hardware::ICameraClient>& client,
- const sp<IMemoryHeap>& heap,
- size_t offset, size_t size,
- camera_frame_metadata_t *metadata);
-
- int getOrientation(int orientation, bool mirror);
-
- status_t setPreviewWindow(
- const sp<IBinder>& binder,
- const sp<ANativeWindow>& window);
-
-
- // these are initialized in the constructor.
- sp<CameraHardwareInterface> mHardware; // cleared after disconnect()
- int mPreviewCallbackFlag;
- int mOrientation; // Current display orientation
- bool mPlayShutterSound;
- bool mLegacyMode; // camera2 api legacy mode?
-
- // Ensures atomicity among the public methods
- mutable Mutex mLock;
- // This is a binder of Surface or Surface.
- sp<IBinder> mSurface;
- sp<ANativeWindow> mPreviewWindow;
-
- // If the user want us to return a copy of the preview frame (instead
- // of the original one), we allocate mPreviewBuffer and reuse it if possible.
- sp<MemoryHeapBase> mPreviewBuffer;
-
- // Debugging information
- CameraParameters mLatestSetParameters;
-
- // mAvailableCallbackBuffers stores sp<IMemory> that HAL uses to send VideoNativeHandleMetadata.
- // It will be used to send VideoNativeHandleMetadata back to HAL when camera receives the
- // native handle from releaseRecordingFrameHandle.
- Mutex mAvailableCallbackBuffersLock;
- std::vector<sp<IMemory>> mAvailableCallbackBuffers;
-
- // We need to avoid the deadlock when the incoming command thread and
- // the CameraHardwareInterface callback thread both want to grab mLock.
- // An extra flag is used to tell the callback thread that it should stop
- // trying to deliver the callback messages if the client is not
- // interested in it anymore. For example, if the client is calling
- // stopPreview(), the preview frame messages do not need to be delivered
- // anymore.
-
- // This function takes the same parameter as the enableMsgType() and
- // disableMsgType() functions in CameraHardwareInterface.
- void enableMsgType(int32_t msgType);
- void disableMsgType(int32_t msgType);
- volatile int32_t mMsgEnabled;
-
- // This function keeps trying to grab mLock, or give up if the message
- // is found to be disabled. It returns true if mLock is grabbed.
- bool lockIfMessageWanted(int32_t msgType);
-};
-
-}
-
-#endif
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.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index e35b436..022d686 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -54,7 +54,7 @@
const sp<CameraService>& cameraService,
const sp<hardware::camera2::ICameraDeviceCallbacks>& remoteCallback,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraId,
int api1CameraId,
int cameraFacing,
@@ -80,7 +80,7 @@
CameraDeviceClient::CameraDeviceClient(const sp<CameraService>& cameraService,
const sp<hardware::camera2::ICameraDeviceCallbacks>& remoteCallback,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraId,
int cameraFacing,
int clientPid,
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.h b/services/camera/libcameraservice/api2/CameraDeviceClient.h
index 964c96a..5cd16ee 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.h
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.h
@@ -50,7 +50,7 @@
CameraDeviceClientBase(const sp<CameraService>& cameraService,
const sp<hardware::camera2::ICameraDeviceCallbacks>& remoteCallback,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraId,
int api1CameraId,
int cameraFacing,
@@ -175,7 +175,7 @@
CameraDeviceClient(const sp<CameraService>& cameraService,
const sp<hardware::camera2::ICameraDeviceCallbacks>& remoteCallback,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraId,
int cameraFacing,
int clientPid,
@@ -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/CameraOfflineSessionClient.h b/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h
index b67fcb3..03621c8 100644
--- a/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h
+++ b/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h
@@ -48,7 +48,7 @@
const KeyedVector<sp<IBinder>, sp<CompositeStream>>& offlineCompositeStreamMap,
const sp<ICameraDeviceCallbacks>& remoteCallback,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraIdStr, int cameraFacing,
int clientPid, uid_t clientUid, int servicePid) :
CameraService::BasicClient(
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/Camera2ClientBase.cpp b/services/camera/libcameraservice/common/Camera2ClientBase.cpp
index 0a41776..609698c 100644
--- a/services/camera/libcameraservice/common/Camera2ClientBase.cpp
+++ b/services/camera/libcameraservice/common/Camera2ClientBase.cpp
@@ -44,7 +44,7 @@
const sp<CameraService>& cameraService,
const sp<TCamCallbacks>& remoteCallback,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraId,
int api1CameraId,
int cameraFacing,
diff --git a/services/camera/libcameraservice/common/Camera2ClientBase.h b/services/camera/libcameraservice/common/Camera2ClientBase.h
index 042f5aa..d7506af 100644
--- a/services/camera/libcameraservice/common/Camera2ClientBase.h
+++ b/services/camera/libcameraservice/common/Camera2ClientBase.h
@@ -48,7 +48,7 @@
Camera2ClientBase(const sp<CameraService>& cameraService,
const sp<TCamCallbacks>& remoteCallback,
const String16& clientPackageName,
- const std::unique_ptr<String16>& clientFeatureId,
+ const std::optional<String16>& clientFeatureId,
const String8& cameraId,
int api1CameraId,
int cameraFacing,
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/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index 32d118d..d11d470 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -417,46 +417,6 @@
return mapToStatusT(status);
}
-status_t CameraProviderManager::openSession(const std::string &id,
- const sp<device::V1_0::ICameraDeviceCallback>& callback,
- /*out*/
- sp<device::V1_0::ICameraDevice> *session) {
-
- std::lock_guard<std::mutex> lock(mInterfaceMutex);
-
- auto deviceInfo = findDeviceInfoLocked(id,
- /*minVersion*/ {1,0}, /*maxVersion*/ {2,0});
- if (deviceInfo == nullptr) return NAME_NOT_FOUND;
-
- auto *deviceInfo1 = static_cast<ProviderInfo::DeviceInfo1*>(deviceInfo);
- sp<ProviderInfo> parentProvider = deviceInfo->mParentProvider.promote();
- if (parentProvider == nullptr) {
- return DEAD_OBJECT;
- }
- const sp<provider::V2_4::ICameraProvider> provider = parentProvider->startProviderInterface();
- if (provider == nullptr) {
- return DEAD_OBJECT;
- }
- saveRef(DeviceMode::CAMERA, id, provider);
-
- auto interface = deviceInfo1->startDeviceInterface<
- CameraProviderManager::ProviderInfo::DeviceInfo1::InterfaceT>();
- if (interface == nullptr) {
- return DEAD_OBJECT;
- }
- hardware::Return<Status> status = interface->open(callback);
- if (!status.isOk()) {
- removeRef(DeviceMode::CAMERA, id);
- ALOGE("%s: Transaction error opening a session for camera device %s: %s",
- __FUNCTION__, id.c_str(), status.description().c_str());
- return DEAD_OBJECT;
- }
- if (status == Status::OK) {
- *session = interface;
- }
- return mapToStatusT(status);
-}
-
void CameraProviderManager::saveRef(DeviceMode usageType, const std::string &cameraId,
sp<provider::V2_4::ICameraProvider> provider) {
if (!kEnableLazyHal) {
@@ -1344,6 +1304,20 @@
}
}
+ // cameraDeviceStatusChange callbacks may be called (and causing new devices added)
+ // before setCallback returns
+ hardware::Return<Status> status = interface->setCallback(this);
+ if (!status.isOk()) {
+ ALOGE("%s: Transaction error setting up callbacks with camera provider '%s': %s",
+ __FUNCTION__, mProviderName.c_str(), status.description().c_str());
+ return DEAD_OBJECT;
+ }
+ if (status != Status::OK) {
+ ALOGE("%s: Unable to register callbacks with camera provider '%s'",
+ __FUNCTION__, mProviderName.c_str());
+ return mapToStatusT(status);
+ }
+
hardware::Return<bool> linked = interface->linkToDeath(this, /*cookie*/ mId);
if (!linked.isOk()) {
ALOGE("%s: Transaction error in linking to camera provider '%s' death: %s",
@@ -1372,7 +1346,6 @@
return res;
}
- Status status;
// Get initial list of camera devices, if any
std::vector<std::string> devices;
hardware::Return<void> ret = interface->getCameraIdList([&status, this, &devices](
@@ -1437,22 +1410,6 @@
}
}
- // cameraDeviceStatusChange callbacks may be called (and causing new devices added)
- // before setCallback returns. setCallback must be called after addDevice so that
- // the physical camera status callback can look up available regular
- // cameras.
- hardware::Return<Status> st = interface->setCallback(this);
- if (!st.isOk()) {
- ALOGE("%s: Transaction error setting up callbacks with camera provider '%s': %s",
- __FUNCTION__, mProviderName.c_str(), st.description().c_str());
- return DEAD_OBJECT;
- }
- if (st != Status::OK) {
- ALOGE("%s: Unable to register callbacks with camera provider '%s'",
- __FUNCTION__, mProviderName.c_str());
- return mapToStatusT(st);
- }
-
ALOGI("Camera provider %s ready with %zu camera devices",
mProviderName.c_str(), mDevices.size());
@@ -1537,9 +1494,9 @@
std::unique_ptr<DeviceInfo> deviceInfo;
switch (major) {
case 1:
- deviceInfo = initializeDeviceInfo<DeviceInfo1>(name, mProviderTagid,
- id, minor);
- break;
+ ALOGE("%s: Device %s: Unsupported HIDL device HAL major version %d:", __FUNCTION__,
+ name.c_str(), major);
+ return BAD_VALUE;
case 3:
deviceInfo = initializeDeviceInfo<DeviceInfo3>(name, mProviderTagid,
id, minor);
@@ -1783,9 +1740,10 @@
CameraDeviceStatus newStatus) {
sp<StatusListener> listener;
std::string id;
- bool initialized = false;
{
std::lock_guard<std::mutex> lock(mLock);
+ if (!mInitialized) return hardware::Void();
+
bool known = false;
for (auto& deviceInfo : mDevices) {
if (deviceInfo->mName == cameraDeviceName) {
@@ -1802,10 +1760,9 @@
__FUNCTION__, id.c_str(), physicalCameraDeviceName.c_str());
return hardware::Void();
}
- ALOGI("Camera device %s physical device %s status is now %s, was %s",
+ ALOGI("Camera device %s physical device %s status is now %s",
cameraDeviceName.c_str(), physicalCameraDeviceName.c_str(),
- deviceStatusToString(newStatus), deviceStatusToString(
- deviceInfo->mPhysicalStatus[physicalCameraDeviceName]));
+ deviceStatusToString(newStatus));
known = true;
break;
}
@@ -1818,13 +1775,12 @@
return hardware::Void();
}
listener = mManager->getStatusListener();
- initialized = mInitialized;
}
// Call without lock held to allow reentrancy into provider manager
// Don't send the callback if providerInfo hasn't been initialized.
// CameraService will initialize device status after provider is
// initialized
- if (listener != nullptr && initialized) {
+ if (listener != nullptr) {
String8 physicalId(physicalCameraDeviceName.c_str());
listener->onDeviceStatusChanged(String8(id.c_str()),
physicalId, newStatus);
@@ -2034,35 +1990,6 @@
}
template<>
-sp<device::V1_0::ICameraDevice>
-CameraProviderManager::ProviderInfo::startDeviceInterface
- <device::V1_0::ICameraDevice>(const std::string &name) {
- Status status;
- sp<device::V1_0::ICameraDevice> cameraInterface;
- hardware::Return<void> ret;
- const sp<provider::V2_4::ICameraProvider> interface = startProviderInterface();
- if (interface == nullptr) {
- return nullptr;
- }
- ret = interface->getCameraDeviceInterface_V1_x(name, [&status, &cameraInterface](
- Status s, sp<device::V1_0::ICameraDevice> interface) {
- status = s;
- cameraInterface = interface;
- });
- if (!ret.isOk()) {
- ALOGE("%s: Transaction error trying to obtain interface for camera device %s: %s",
- __FUNCTION__, name.c_str(), ret.description().c_str());
- return nullptr;
- }
- if (status != Status::OK) {
- ALOGE("%s: Unable to obtain interface for camera device %s: %s", __FUNCTION__,
- name.c_str(), statusToString(status));
- return nullptr;
- }
- return cameraInterface;
-}
-
-template<>
sp<device::V3_2::ICameraDevice>
CameraProviderManager::ProviderInfo::startDeviceInterface
<device::V3_2::ICameraDevice>(const std::string &name) {
@@ -2115,126 +2042,6 @@
return mapToStatusT(s);
}
-CameraProviderManager::ProviderInfo::DeviceInfo1::DeviceInfo1(const std::string& name,
- const metadata_vendor_id_t tagId, const std::string &id,
- uint16_t minorVersion,
- const CameraResourceCost& resourceCost,
- sp<ProviderInfo> parentProvider,
- const std::vector<std::string>& publicCameraIds,
- sp<InterfaceT> interface) :
- DeviceInfo(name, tagId, id, hardware::hidl_version{1, minorVersion},
- publicCameraIds, resourceCost, parentProvider) {
- // Get default parameters and initialize flash unit availability
- // Requires powering on the camera device
- hardware::Return<Status> status = interface->open(nullptr);
- if (!status.isOk()) {
- ALOGE("%s: Transaction error opening camera device %s to check for a flash unit: %s",
- __FUNCTION__, id.c_str(), status.description().c_str());
- return;
- }
- if (status != Status::OK) {
- ALOGE("%s: Unable to open camera device %s to check for a flash unit: %s", __FUNCTION__,
- id.c_str(), CameraProviderManager::statusToString(status));
- return;
- }
- hardware::Return<void> ret;
- ret = interface->getParameters([this](const hardware::hidl_string& parms) {
- mDefaultParameters.unflatten(String8(parms.c_str()));
- });
- if (!ret.isOk()) {
- ALOGE("%s: Transaction error reading camera device %s params to check for a flash unit: %s",
- __FUNCTION__, id.c_str(), status.description().c_str());
- return;
- }
- const char *flashMode =
- mDefaultParameters.get(CameraParameters::KEY_SUPPORTED_FLASH_MODES);
- if (flashMode && strstr(flashMode, CameraParameters::FLASH_MODE_TORCH)) {
- mHasFlashUnit = true;
- }
-
- status_t res = cacheCameraInfo(interface);
- if (res != OK) {
- ALOGE("%s: Could not cache CameraInfo", __FUNCTION__);
- return;
- }
-
- ret = interface->close();
- if (!ret.isOk()) {
- ALOGE("%s: Transaction error closing camera device %s after check for a flash unit: %s",
- __FUNCTION__, id.c_str(), status.description().c_str());
- }
-
- if (!kEnableLazyHal) {
- // Save HAL reference indefinitely
- mSavedInterface = interface;
- }
-}
-
-CameraProviderManager::ProviderInfo::DeviceInfo1::~DeviceInfo1() {}
-
-status_t CameraProviderManager::ProviderInfo::DeviceInfo1::setTorchMode(bool enabled) {
- return setTorchModeForDevice<InterfaceT>(enabled);
-}
-
-status_t CameraProviderManager::ProviderInfo::DeviceInfo1::getCameraInfo(
- hardware::CameraInfo *info) const {
- if (info == nullptr) return BAD_VALUE;
- *info = mInfo;
- return OK;
-}
-
-status_t CameraProviderManager::ProviderInfo::DeviceInfo1::cacheCameraInfo(
- sp<CameraProviderManager::ProviderInfo::DeviceInfo1::InterfaceT> interface) {
- Status status;
- device::V1_0::CameraInfo cInfo;
- hardware::Return<void> ret;
- ret = interface->getCameraInfo([&status, &cInfo](Status s, device::V1_0::CameraInfo camInfo) {
- status = s;
- cInfo = camInfo;
- });
- if (!ret.isOk()) {
- ALOGE("%s: Transaction error reading camera info from device %s: %s",
- __FUNCTION__, mId.c_str(), ret.description().c_str());
- return DEAD_OBJECT;
- }
- if (status != Status::OK) {
- return mapToStatusT(status);
- }
-
- switch(cInfo.facing) {
- case device::V1_0::CameraFacing::BACK:
- mInfo.facing = hardware::CAMERA_FACING_BACK;
- break;
- case device::V1_0::CameraFacing::EXTERNAL:
- // Map external to front for legacy API
- case device::V1_0::CameraFacing::FRONT:
- mInfo.facing = hardware::CAMERA_FACING_FRONT;
- break;
- default:
- ALOGW("%s: Device %s: Unknown camera facing: %d",
- __FUNCTION__, mId.c_str(), cInfo.facing);
- mInfo.facing = hardware::CAMERA_FACING_BACK;
- }
- mInfo.orientation = cInfo.orientation;
-
- return OK;
-}
-
-status_t CameraProviderManager::ProviderInfo::DeviceInfo1::dumpState(int fd) {
- native_handle_t* handle = native_handle_create(1,0);
- handle->data[0] = fd;
- const sp<InterfaceT> interface = startDeviceInterface<InterfaceT>();
- if (interface == nullptr) {
- return DEAD_OBJECT;
- }
- hardware::Return<Status> s = interface->dumpState(handle);
- native_handle_delete(handle);
- if (!s.isOk()) {
- return INVALID_OPERATION;
- }
- return mapToStatusT(s);
-}
-
CameraProviderManager::ProviderInfo::DeviceInfo3::DeviceInfo3(const std::string& name,
const metadata_vendor_id_t tagId, const std::string &id,
uint16_t minorVersion,
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.h b/services/camera/libcameraservice/common/CameraProviderManager.h
index 25d3639..12da5e6 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.h
+++ b/services/camera/libcameraservice/common/CameraProviderManager.h
@@ -269,11 +269,6 @@
/*out*/
sp<hardware::camera::device::V3_2::ICameraDeviceSession> *session);
- status_t openSession(const std::string &id,
- const sp<hardware::camera::device::V1_0::ICameraDeviceCallback>& callback,
- /*out*/
- sp<hardware::camera::device::V1_0::ICameraDevice> *session);
-
/**
* Save the ICameraProvider while it is being used by a camera or torch client
*/
@@ -444,8 +439,6 @@
const hardware::camera::common::V1_0::CameraResourceCost mResourceCost;
hardware::camera::common::V1_0::CameraDeviceStatus mStatus;
- std::map<std::string, hardware::camera::common::V1_0::CameraDeviceStatus>
- mPhysicalStatus;
wp<ProviderInfo> mParentProvider;
@@ -513,27 +506,6 @@
// physical camera IDs.
std::vector<std::string> mProviderPublicCameraIds;
- // HALv1-specific camera fields, including the actual device interface
- struct DeviceInfo1 : public DeviceInfo {
- typedef hardware::camera::device::V1_0::ICameraDevice InterfaceT;
-
- virtual status_t setTorchMode(bool enabled) override;
- virtual status_t getCameraInfo(hardware::CameraInfo *info) const override;
- //In case of Device1Info assume that we are always API1 compatible
- virtual bool isAPI1Compatible() const override { return true; }
- virtual status_t dumpState(int fd) override;
- DeviceInfo1(const std::string& name, const metadata_vendor_id_t tagId,
- const std::string &id, uint16_t minorVersion,
- const hardware::camera::common::V1_0::CameraResourceCost& resourceCost,
- sp<ProviderInfo> parentProvider,
- const std::vector<std::string>& publicCameraIds,
- sp<InterfaceT> interface);
- virtual ~DeviceInfo1();
- private:
- CameraParameters2 mDefaultParameters;
- status_t cacheCameraInfo(sp<InterfaceT> interface);
- };
-
// HALv3-specific camera fields, including the actual device interface
struct DeviceInfo3 : public DeviceInfo {
typedef hardware::camera::device::V3_2::ICameraDevice InterfaceT;
diff --git a/services/camera/libcameraservice/device1/CameraHardwareInterface.cpp b/services/camera/libcameraservice/device1/CameraHardwareInterface.cpp
deleted file mode 100644
index 62ef681..0000000
--- a/services/camera/libcameraservice/device1/CameraHardwareInterface.cpp
+++ /dev/null
@@ -1,818 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#define LOG_TAG "CameraHardwareInterface"
-//#define LOG_NDEBUG 0
-
-#include <inttypes.h>
-#include <media/hardware/HardwareAPI.h> // For VideoNativeHandleMetadata
-#include "CameraHardwareInterface.h"
-
-namespace android {
-
-using namespace hardware::camera::device::V1_0;
-using namespace hardware::camera::common::V1_0;
-using hardware::hidl_handle;
-
-CameraHardwareInterface::~CameraHardwareInterface()
-{
- ALOGI("Destroying camera %s", mName.string());
- if (mHidlDevice != nullptr) {
- mHidlDevice->close();
- mHidlDevice.clear();
- cleanupCirculatingBuffers();
- }
-}
-
-status_t CameraHardwareInterface::initialize(sp<CameraProviderManager> manager) {
- ALOGI("Opening camera %s", mName.string());
-
- status_t ret = manager->openSession(mName.string(), this, &mHidlDevice);
- if (ret != OK) {
- ALOGE("%s: openSession failed! %s (%d)", __FUNCTION__, strerror(-ret), ret);
- }
- return ret;
-}
-
-status_t CameraHardwareInterface::setPreviewScalingMode(int scalingMode)
-{
- int rc = OK;
- mPreviewScalingMode = scalingMode;
- if (mPreviewWindow != nullptr) {
- rc = native_window_set_scaling_mode(mPreviewWindow.get(),
- scalingMode);
- }
- return rc;
-}
-
-status_t CameraHardwareInterface::setPreviewTransform(int transform) {
- int rc = OK;
- mPreviewTransform = transform;
- if (mPreviewWindow != nullptr) {
- rc = native_window_set_buffers_transform(mPreviewWindow.get(),
- mPreviewTransform);
- }
- return rc;
-}
-
-/**
- * Implementation of android::hardware::camera::device::V1_0::ICameraDeviceCallback
- */
-hardware::Return<void> CameraHardwareInterface::notifyCallback(
- NotifyCallbackMsg msgType, int32_t ext1, int32_t ext2) {
- sNotifyCb((int32_t) msgType, ext1, ext2, (void*) this);
- return hardware::Void();
-}
-
-hardware::Return<uint32_t> CameraHardwareInterface::registerMemory(
- const hardware::hidl_handle& descriptor,
- uint32_t bufferSize, uint32_t bufferCount) {
- if (descriptor->numFds != 1) {
- ALOGE("%s: camera memory descriptor has numFds %d (expect 1)",
- __FUNCTION__, descriptor->numFds);
- return 0;
- }
- if (descriptor->data[0] < 0) {
- ALOGE("%s: camera memory descriptor has FD %d (expect >= 0)",
- __FUNCTION__, descriptor->data[0]);
- return 0;
- }
-
- camera_memory_t* mem = sGetMemory(descriptor->data[0], bufferSize, bufferCount, this);
- sp<CameraHeapMemory> camMem(static_cast<CameraHeapMemory *>(mem->handle));
- int memPoolId = camMem->mHeap->getHeapID();
- if (memPoolId < 0) {
- ALOGE("%s: CameraHeapMemory has FD %d (expect >= 0)", __FUNCTION__, memPoolId);
- return 0;
- }
- std::lock_guard<std::mutex> lock(mHidlMemPoolMapLock);
- mHidlMemPoolMap.insert(std::make_pair(memPoolId, mem));
- return memPoolId;
-}
-
-hardware::Return<void> CameraHardwareInterface::unregisterMemory(uint32_t memId) {
- camera_memory_t* mem = nullptr;
- {
- std::lock_guard<std::mutex> lock(mHidlMemPoolMapLock);
- if (mHidlMemPoolMap.count(memId) == 0) {
- ALOGE("%s: memory pool ID %d not found", __FUNCTION__, memId);
- return hardware::Void();
- }
- mem = mHidlMemPoolMap.at(memId);
- mHidlMemPoolMap.erase(memId);
- }
- sPutMemory(mem);
- return hardware::Void();
-}
-
-hardware::Return<void> CameraHardwareInterface::dataCallback(
- DataCallbackMsg msgType, uint32_t data, uint32_t bufferIndex,
- const hardware::camera::device::V1_0::CameraFrameMetadata& metadata) {
- camera_memory_t* mem = nullptr;
- {
- std::lock_guard<std::mutex> lock(mHidlMemPoolMapLock);
- if (mHidlMemPoolMap.count(data) == 0) {
- ALOGE("%s: memory pool ID %d not found", __FUNCTION__, data);
- return hardware::Void();
- }
- mem = mHidlMemPoolMap.at(data);
- }
- camera_frame_metadata_t md;
- md.number_of_faces = metadata.faces.size();
- md.faces = (camera_face_t*) metadata.faces.data();
- sDataCb((int32_t) msgType, mem, bufferIndex, &md, this);
- return hardware::Void();
-}
-
-hardware::Return<void> CameraHardwareInterface::dataCallbackTimestamp(
- DataCallbackMsg msgType, uint32_t data,
- uint32_t bufferIndex, int64_t timestamp) {
- camera_memory_t* mem = nullptr;
- {
- std::lock_guard<std::mutex> lock(mHidlMemPoolMapLock);
- if (mHidlMemPoolMap.count(data) == 0) {
- ALOGE("%s: memory pool ID %d not found", __FUNCTION__, data);
- return hardware::Void();
- }
- mem = mHidlMemPoolMap.at(data);
- }
- sDataCbTimestamp(timestamp, (int32_t) msgType, mem, bufferIndex, this);
- return hardware::Void();
-}
-
-hardware::Return<void> CameraHardwareInterface::handleCallbackTimestamp(
- DataCallbackMsg msgType, const hidl_handle& frameData, uint32_t data,
- uint32_t bufferIndex, int64_t timestamp) {
- camera_memory_t* mem = nullptr;
- {
- std::lock_guard<std::mutex> lock(mHidlMemPoolMapLock);
- if (mHidlMemPoolMap.count(data) == 0) {
- ALOGE("%s: memory pool ID %d not found", __FUNCTION__, data);
- return hardware::Void();
- }
- mem = mHidlMemPoolMap.at(data);
- }
- sp<CameraHeapMemory> heapMem(static_cast<CameraHeapMemory *>(mem->handle));
- // TODO: Using unsecurePointer() has some associated security pitfalls
- // (see declaration for details).
- // Either document why it is safe in this case or address the
- // issue (e.g. by copying).
- VideoNativeHandleMetadata* md = (VideoNativeHandleMetadata*)
- heapMem->mBuffers[bufferIndex]->unsecurePointer();
- md->pHandle = const_cast<native_handle_t*>(frameData.getNativeHandle());
- sDataCbTimestamp(timestamp, (int32_t) msgType, mem, bufferIndex, this);
- return hardware::Void();
-}
-
-hardware::Return<void> CameraHardwareInterface::handleCallbackTimestampBatch(
- DataCallbackMsg msgType,
- const hardware::hidl_vec<hardware::camera::device::V1_0::HandleTimestampMessage>& messages) {
- std::vector<android::HandleTimestampMessage> msgs;
- msgs.reserve(messages.size());
- {
- std::lock_guard<std::mutex> lock(mHidlMemPoolMapLock);
- for (const auto& hidl_msg : messages) {
- if (mHidlMemPoolMap.count(hidl_msg.data) == 0) {
- ALOGE("%s: memory pool ID %d not found", __FUNCTION__, hidl_msg.data);
- return hardware::Void();
- }
- sp<CameraHeapMemory> mem(
- static_cast<CameraHeapMemory *>(mHidlMemPoolMap.at(hidl_msg.data)->handle));
-
- if (hidl_msg.bufferIndex >= mem->mNumBufs) {
- ALOGE("%s: invalid buffer index %d, max allowed is %d", __FUNCTION__,
- hidl_msg.bufferIndex, mem->mNumBufs);
- return hardware::Void();
- }
- // TODO: Using unsecurePointer() has some associated security pitfalls
- // (see declaration for details).
- // Either document why it is safe in this case or address the
- // issue (e.g. by copying).
- VideoNativeHandleMetadata* md = (VideoNativeHandleMetadata*)
- mem->mBuffers[hidl_msg.bufferIndex]->unsecurePointer();
- md->pHandle = const_cast<native_handle_t*>(hidl_msg.frameData.getNativeHandle());
-
- msgs.push_back({hidl_msg.timestamp, mem->mBuffers[hidl_msg.bufferIndex]});
- }
- }
- mDataCbTimestampBatch((int32_t) msgType, msgs, mCbUser);
- return hardware::Void();
-}
-
-std::pair<bool, uint64_t> CameraHardwareInterface::getBufferId(
- ANativeWindowBuffer* anb) {
- std::lock_guard<std::mutex> lock(mBufferIdMapLock);
-
- buffer_handle_t& buf = anb->handle;
- auto it = mBufferIdMap.find(buf);
- if (it == mBufferIdMap.end()) {
- uint64_t bufId = mNextBufferId++;
- mBufferIdMap[buf] = bufId;
- mReversedBufMap[bufId] = anb;
- return std::make_pair(true, bufId);
- } else {
- return std::make_pair(false, it->second);
- }
-}
-
-void CameraHardwareInterface::cleanupCirculatingBuffers() {
- std::lock_guard<std::mutex> lock(mBufferIdMapLock);
- mBufferIdMap.clear();
- mReversedBufMap.clear();
-}
-
-hardware::Return<void>
-CameraHardwareInterface::dequeueBuffer(dequeueBuffer_cb _hidl_cb) {
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return hardware::Void();
- }
- ANativeWindowBuffer* anb;
- int rc = native_window_dequeue_buffer_and_wait(a, &anb);
- Status s = Status::INTERNAL_ERROR;
- uint64_t bufferId = 0;
- uint32_t stride = 0;
- hidl_handle buf = nullptr;
- if (rc == OK) {
- s = Status::OK;
- auto pair = getBufferId(anb);
- buf = (pair.first) ? anb->handle : nullptr;
- bufferId = pair.second;
- stride = anb->stride;
- }
-
- _hidl_cb(s, bufferId, buf, stride);
- return hardware::Void();
-}
-
-hardware::Return<Status>
-CameraHardwareInterface::enqueueBuffer(uint64_t bufferId) {
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return Status::INTERNAL_ERROR;
- }
- if (mReversedBufMap.count(bufferId) == 0) {
- ALOGE("%s: bufferId %" PRIu64 " not found", __FUNCTION__, bufferId);
- return Status::ILLEGAL_ARGUMENT;
- }
- int rc = a->queueBuffer(a, mReversedBufMap.at(bufferId), -1);
- if (rc == 0) {
- return Status::OK;
- }
- return Status::INTERNAL_ERROR;
-}
-
-hardware::Return<Status>
-CameraHardwareInterface::cancelBuffer(uint64_t bufferId) {
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return Status::INTERNAL_ERROR;
- }
- if (mReversedBufMap.count(bufferId) == 0) {
- ALOGE("%s: bufferId %" PRIu64 " not found", __FUNCTION__, bufferId);
- return Status::ILLEGAL_ARGUMENT;
- }
- int rc = a->cancelBuffer(a, mReversedBufMap.at(bufferId), -1);
- if (rc == 0) {
- return Status::OK;
- }
- return Status::INTERNAL_ERROR;
-}
-
-hardware::Return<Status>
-CameraHardwareInterface::setBufferCount(uint32_t count) {
- ANativeWindow *a = mPreviewWindow.get();
- if (a != nullptr) {
- // Workaround for b/27039775
- // Previously, setting the buffer count would reset the buffer
- // queue's flag that allows for all buffers to be dequeued on the
- // producer side, instead of just the producer's declared max count,
- // if no filled buffers have yet been queued by the producer. This
- // reset no longer happens, but some HALs depend on this behavior,
- // so it needs to be maintained for HAL backwards compatibility.
- // Simulate the prior behavior by disconnecting/reconnecting to the
- // window and setting the values again. This has the drawback of
- // actually causing memory reallocation, which may not have happened
- // in the past.
- native_window_api_disconnect(a, NATIVE_WINDOW_API_CAMERA);
- native_window_api_connect(a, NATIVE_WINDOW_API_CAMERA);
- if (mPreviewScalingMode != NOT_SET) {
- native_window_set_scaling_mode(a, mPreviewScalingMode);
- }
- if (mPreviewTransform != NOT_SET) {
- native_window_set_buffers_transform(a, mPreviewTransform);
- }
- if (mPreviewWidth != NOT_SET) {
- native_window_set_buffers_dimensions(a,
- mPreviewWidth, mPreviewHeight);
- native_window_set_buffers_format(a, mPreviewFormat);
- }
- if (mPreviewUsage != 0) {
- native_window_set_usage(a, mPreviewUsage);
- }
- if (mPreviewSwapInterval != NOT_SET) {
- a->setSwapInterval(a, mPreviewSwapInterval);
- }
- if (mPreviewCrop.left != NOT_SET) {
- native_window_set_crop(a, &(mPreviewCrop));
- }
- }
- int rc = native_window_set_buffer_count(a, count);
- if (rc == OK) {
- cleanupCirculatingBuffers();
- return Status::OK;
- }
- return Status::INTERNAL_ERROR;
-}
-
-hardware::Return<Status>
-CameraHardwareInterface::setBuffersGeometry(
- uint32_t w, uint32_t h, hardware::graphics::common::V1_0::PixelFormat format) {
- Status s = Status::INTERNAL_ERROR;
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return s;
- }
- mPreviewWidth = w;
- mPreviewHeight = h;
- mPreviewFormat = (int) format;
- int rc = native_window_set_buffers_dimensions(a, w, h);
- if (rc == OK) {
- rc = native_window_set_buffers_format(a, mPreviewFormat);
- }
- if (rc == OK) {
- cleanupCirculatingBuffers();
- s = Status::OK;
- }
- return s;
-}
-
-hardware::Return<Status>
-CameraHardwareInterface::setCrop(int32_t left, int32_t top, int32_t right, int32_t bottom) {
- Status s = Status::INTERNAL_ERROR;
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return s;
- }
- mPreviewCrop.left = left;
- mPreviewCrop.top = top;
- mPreviewCrop.right = right;
- mPreviewCrop.bottom = bottom;
- int rc = native_window_set_crop(a, &mPreviewCrop);
- if (rc == OK) {
- s = Status::OK;
- }
- return s;
-}
-
-hardware::Return<Status>
-CameraHardwareInterface::setUsage(hardware::graphics::common::V1_0::BufferUsage usage) {
- Status s = Status::INTERNAL_ERROR;
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return s;
- }
- mPreviewUsage = static_cast<uint64_t> (usage);
- int rc = native_window_set_usage(a, mPreviewUsage);
- if (rc == OK) {
- cleanupCirculatingBuffers();
- s = Status::OK;
- }
- return s;
-}
-
-hardware::Return<Status>
-CameraHardwareInterface::setSwapInterval(int32_t interval) {
- Status s = Status::INTERNAL_ERROR;
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return s;
- }
- mPreviewSwapInterval = interval;
- int rc = a->setSwapInterval(a, interval);
- if (rc == OK) {
- s = Status::OK;
- }
- return s;
-}
-
-hardware::Return<void>
-CameraHardwareInterface::getMinUndequeuedBufferCount(getMinUndequeuedBufferCount_cb _hidl_cb) {
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return hardware::Void();
- }
- int count = 0;
- int rc = a->query(a, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &count);
- Status s = Status::INTERNAL_ERROR;
- if (rc == OK) {
- s = Status::OK;
- }
- _hidl_cb(s, count);
- return hardware::Void();
-}
-
-hardware::Return<Status>
-CameraHardwareInterface::setTimestamp(int64_t timestamp) {
- Status s = Status::INTERNAL_ERROR;
- ANativeWindow *a = mPreviewWindow.get();
- if (a == nullptr) {
- ALOGE("%s: preview window is null", __FUNCTION__);
- return s;
- }
- int rc = native_window_set_buffers_timestamp(a, timestamp);
- if (rc == OK) {
- s = Status::OK;
- }
- return s;
-}
-
-status_t CameraHardwareInterface::setPreviewWindow(const sp<ANativeWindow>& buf)
-{
- ALOGV("%s(%s) buf %p", __FUNCTION__, mName.string(), buf.get());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- mPreviewWindow = buf;
- if (buf != nullptr) {
- if (mPreviewScalingMode != NOT_SET) {
- setPreviewScalingMode(mPreviewScalingMode);
- }
- if (mPreviewTransform != NOT_SET) {
- setPreviewTransform(mPreviewTransform);
- }
- }
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->setPreviewWindow(buf.get() ? this : nullptr));
- }
- return INVALID_OPERATION;
-}
-
-void CameraHardwareInterface::setCallbacks(notify_callback notify_cb,
- data_callback data_cb,
- data_callback_timestamp data_cb_timestamp,
- data_callback_timestamp_batch data_cb_timestamp_batch,
- void* user)
-{
- mNotifyCb = notify_cb;
- mDataCb = data_cb;
- mDataCbTimestamp = data_cb_timestamp;
- mDataCbTimestampBatch = data_cb_timestamp_batch;
- mCbUser = user;
-
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
-}
-
-void CameraHardwareInterface::enableMsgType(int32_t msgType)
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- mHidlDevice->enableMsgType(msgType);
- }
-}
-
-void CameraHardwareInterface::disableMsgType(int32_t msgType)
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- mHidlDevice->disableMsgType(msgType);
- }
-}
-
-int CameraHardwareInterface::msgTypeEnabled(int32_t msgType)
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return mHidlDevice->msgTypeEnabled(msgType);
- }
- return false;
-}
-
-status_t CameraHardwareInterface::startPreview()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->startPreview());
- }
- return INVALID_OPERATION;
-}
-
-void CameraHardwareInterface::stopPreview()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- mHidlDevice->stopPreview();
- }
-}
-
-int CameraHardwareInterface::previewEnabled()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return mHidlDevice->previewEnabled();
- }
- return false;
-}
-
-status_t CameraHardwareInterface::storeMetaDataInBuffers(int enable)
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->storeMetaDataInBuffers(enable));
- }
- return enable ? INVALID_OPERATION: OK;
-}
-
-status_t CameraHardwareInterface::startRecording()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->startRecording());
- }
- return INVALID_OPERATION;
-}
-
-/**
- * Stop a previously started recording.
- */
-void CameraHardwareInterface::stopRecording()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- mHidlDevice->stopRecording();
- }
-}
-
-/**
- * Returns true if recording is enabled.
- */
-int CameraHardwareInterface::recordingEnabled()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return mHidlDevice->recordingEnabled();
- }
- return false;
-}
-
-void CameraHardwareInterface::releaseRecordingFrame(const sp<IMemory>& mem)
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- ssize_t offset;
- size_t size;
- sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
- int heapId = heap->getHeapID();
- int bufferIndex = offset / size;
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- if (size == sizeof(VideoNativeHandleMetadata)) {
- // TODO: Using unsecurePointer() has some associated security pitfalls
- // (see declaration for details).
- // Either document why it is safe in this case or address the
- // issue (e.g. by copying).
- VideoNativeHandleMetadata* md = (VideoNativeHandleMetadata*) mem->unsecurePointer();
- // Caching the handle here because md->pHandle will be subject to HAL's edit
- native_handle_t* nh = md->pHandle;
- hidl_handle frame = nh;
- mHidlDevice->releaseRecordingFrameHandle(heapId, bufferIndex, frame);
- native_handle_close(nh);
- native_handle_delete(nh);
- } else {
- mHidlDevice->releaseRecordingFrame(heapId, bufferIndex);
- }
- }
-}
-
-void CameraHardwareInterface::releaseRecordingFrameBatch(const std::vector<sp<IMemory>>& frames)
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- size_t n = frames.size();
- std::vector<VideoFrameMessage> msgs;
- msgs.reserve(n);
- for (auto& mem : frames) {
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- ssize_t offset;
- size_t size;
- sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
- if (size == sizeof(VideoNativeHandleMetadata)) {
- uint32_t heapId = heap->getHeapID();
- uint32_t bufferIndex = offset / size;
- // TODO: Using unsecurePointer() has some associated security pitfalls
- // (see declaration for details).
- // Either document why it is safe in this case or address the
- // issue (e.g. by copying).
- VideoNativeHandleMetadata* md = (VideoNativeHandleMetadata*) mem->unsecurePointer();
- // Caching the handle here because md->pHandle will be subject to HAL's edit
- native_handle_t* nh = md->pHandle;
- VideoFrameMessage msg;
- msgs.push_back({nh, heapId, bufferIndex});
- } else {
- ALOGE("%s only supports VideoNativeHandleMetadata mode", __FUNCTION__);
- return;
- }
- }
- }
-
- mHidlDevice->releaseRecordingFrameHandleBatch(msgs);
-
- for (auto& msg : msgs) {
- native_handle_t* nh = const_cast<native_handle_t*>(msg.frameData.getNativeHandle());
- native_handle_close(nh);
- native_handle_delete(nh);
- }
-}
-
-status_t CameraHardwareInterface::autoFocus()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->autoFocus());
- }
- return INVALID_OPERATION;
-}
-
-status_t CameraHardwareInterface::cancelAutoFocus()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->cancelAutoFocus());
- }
- return INVALID_OPERATION;
-}
-
-status_t CameraHardwareInterface::takePicture()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->takePicture());
- }
- return INVALID_OPERATION;
-}
-
-status_t CameraHardwareInterface::cancelPicture()
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->cancelPicture());
- }
- return INVALID_OPERATION;
-}
-
-status_t CameraHardwareInterface::setParameters(const CameraParameters ¶ms)
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->setParameters(params.flatten().string()));
- }
- return INVALID_OPERATION;
-}
-
-CameraParameters CameraHardwareInterface::getParameters() const
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- CameraParameters parms;
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- hardware::hidl_string outParam;
- mHidlDevice->getParameters(
- [&outParam](const auto& outStr) {
- outParam = outStr;
- });
- String8 tmp(outParam.c_str());
- parms.unflatten(tmp);
- }
- return parms;
-}
-
-status_t CameraHardwareInterface::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2)
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- return CameraProviderManager::mapToStatusT(
- mHidlDevice->sendCommand((CommandType) cmd, arg1, arg2));
- }
- return INVALID_OPERATION;
-}
-
-/**
- * Release the hardware resources owned by this object. Note that this is
- * *not* done in the destructor.
- */
-void CameraHardwareInterface::release() {
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- mHidlDevice->close();
- mHidlDevice.clear();
- }
-}
-
-/**
- * Dump state of the camera hardware
- */
-status_t CameraHardwareInterface::dump(int fd, const Vector<String16>& /*args*/) const
-{
- ALOGV("%s(%s)", __FUNCTION__, mName.string());
- if (CC_LIKELY(mHidlDevice != nullptr)) {
- native_handle_t* handle = native_handle_create(1,0);
- handle->data[0] = fd;
- Status s = mHidlDevice->dumpState(handle);
- native_handle_delete(handle);
- return CameraProviderManager::mapToStatusT(s);
- }
- return OK; // It's fine if the HAL doesn't implement dump()
-}
-
-void CameraHardwareInterface::sNotifyCb(int32_t msg_type, int32_t ext1,
- int32_t ext2, void *user)
-{
- ALOGV("%s", __FUNCTION__);
- CameraHardwareInterface *object =
- static_cast<CameraHardwareInterface *>(user);
- object->mNotifyCb(msg_type, ext1, ext2, object->mCbUser);
-}
-
-void CameraHardwareInterface::sDataCb(int32_t msg_type,
- const camera_memory_t *data, unsigned int index,
- camera_frame_metadata_t *metadata,
- void *user)
-{
- ALOGV("%s", __FUNCTION__);
- CameraHardwareInterface *object =
- static_cast<CameraHardwareInterface *>(user);
- sp<CameraHeapMemory> mem(static_cast<CameraHeapMemory *>(data->handle));
- if (index >= mem->mNumBufs) {
- ALOGE("%s: invalid buffer index %d, max allowed is %d", __FUNCTION__,
- index, mem->mNumBufs);
- return;
- }
- object->mDataCb(msg_type, mem->mBuffers[index], metadata, object->mCbUser);
-}
-
-void CameraHardwareInterface::sDataCbTimestamp(nsecs_t timestamp, int32_t msg_type,
- const camera_memory_t *data, unsigned index,
- void *user)
-{
- ALOGV("%s", __FUNCTION__);
- CameraHardwareInterface *object =
- static_cast<CameraHardwareInterface *>(user);
- // Start refcounting the heap object from here on. When the clients
- // drop all references, it will be destroyed (as well as the enclosed
- // MemoryHeapBase.
- sp<CameraHeapMemory> mem(static_cast<CameraHeapMemory *>(data->handle));
- if (index >= mem->mNumBufs) {
- ALOGE("%s: invalid buffer index %d, max allowed is %d", __FUNCTION__,
- index, mem->mNumBufs);
- return;
- }
- object->mDataCbTimestamp(timestamp, msg_type, mem->mBuffers[index], object->mCbUser);
-}
-
-camera_memory_t* CameraHardwareInterface::sGetMemory(
- int fd, size_t buf_size, uint_t num_bufs,
- void *user __attribute__((unused)))
-{
- CameraHeapMemory *mem;
- if (fd < 0) {
- mem = new CameraHeapMemory(buf_size, num_bufs);
- } else {
- mem = new CameraHeapMemory(fd, buf_size, num_bufs);
- }
- mem->incStrong(mem);
- return &mem->handle;
-}
-
-void CameraHardwareInterface::sPutMemory(camera_memory_t *data)
-{
- if (!data) {
- return;
- }
-
- CameraHeapMemory *mem = static_cast<CameraHeapMemory *>(data->handle);
- mem->decStrong(mem);
-}
-
-}; // namespace android
diff --git a/services/camera/libcameraservice/device1/CameraHardwareInterface.h b/services/camera/libcameraservice/device1/CameraHardwareInterface.h
deleted file mode 100644
index e519b04..0000000
--- a/services/camera/libcameraservice/device1/CameraHardwareInterface.h
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H
-#define ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H
-
-#include <unordered_map>
-#include <binder/IMemory.h>
-#include <binder/MemoryBase.h>
-#include <binder/MemoryHeapBase.h>
-#include <utils/RefBase.h>
-#include <ui/GraphicBuffer.h>
-#include <camera/Camera.h>
-#include <camera/CameraParameters.h>
-#include <system/window.h>
-#include <hardware/camera.h>
-
-#include <common/CameraProviderManager.h>
-
-namespace android {
-
-typedef void (*notify_callback)(int32_t msgType,
- int32_t ext1,
- int32_t ext2,
- void* user);
-
-typedef void (*data_callback)(int32_t msgType,
- const sp<IMemory> &dataPtr,
- camera_frame_metadata_t *metadata,
- void* user);
-
-typedef void (*data_callback_timestamp)(nsecs_t timestamp,
- int32_t msgType,
- const sp<IMemory> &dataPtr,
- void *user);
-
-struct HandleTimestampMessage {
- nsecs_t timestamp;
- const sp<IMemory> dataPtr;
-};
-
-typedef void (*data_callback_timestamp_batch)(
- int32_t msgType,
- const std::vector<HandleTimestampMessage>&, void* user);
-
-/**
- * CameraHardwareInterface.h defines the interface to the
- * camera hardware abstraction layer, used for setting and getting
- * parameters, live previewing, and taking pictures. It is used for
- * HAL devices with version CAMERA_DEVICE_API_VERSION_1_0 only.
- *
- * It is a referenced counted interface with RefBase as its base class.
- * CameraService calls openCameraHardware() to retrieve a strong pointer to the
- * instance of this interface and may be called multiple times. The
- * following steps describe a typical sequence:
- *
- * -# After CameraService calls openCameraHardware(), getParameters() and
- * setParameters() are used to initialize the camera instance.
- * -# startPreview() is called.
- *
- * Prior to taking a picture, CameraService often calls autofocus(). When auto
- * focusing has completed, the camera instance sends a CAMERA_MSG_FOCUS notification,
- * which informs the application whether focusing was successful. The camera instance
- * only sends this message once and it is up to the application to call autoFocus()
- * again if refocusing is desired.
- *
- * CameraService calls takePicture() to request the camera instance take a
- * picture. At this point, if a shutter, postview, raw, and/or compressed
- * callback is desired, the corresponding message must be enabled. Any memory
- * provided in a data callback must be copied if it's needed after returning.
- */
-
-class CameraHardwareInterface :
- public virtual RefBase,
- public virtual hardware::camera::device::V1_0::ICameraDeviceCallback,
- public virtual hardware::camera::device::V1_0::ICameraDevicePreviewCallback {
-
-public:
- explicit CameraHardwareInterface(const char *name):
- mHidlDevice(nullptr),
- mName(name),
- mPreviewScalingMode(NOT_SET),
- mPreviewTransform(NOT_SET),
- mPreviewWidth(NOT_SET),
- mPreviewHeight(NOT_SET),
- mPreviewFormat(NOT_SET),
- mPreviewUsage(0),
- mPreviewSwapInterval(NOT_SET),
- mPreviewCrop{NOT_SET,NOT_SET,NOT_SET,NOT_SET}
- {
- }
-
- ~CameraHardwareInterface();
-
- status_t initialize(sp<CameraProviderManager> manager);
-
- /** Set the ANativeWindow to which preview frames are sent */
- status_t setPreviewWindow(const sp<ANativeWindow>& buf);
-
- status_t setPreviewScalingMode(int scalingMode);
-
- status_t setPreviewTransform(int transform);
-
- /** Set the notification and data callbacks */
- void setCallbacks(notify_callback notify_cb,
- data_callback data_cb,
- data_callback_timestamp data_cb_timestamp,
- data_callback_timestamp_batch data_cb_timestamp_batch,
- void* user);
-
- /**
- * The following three functions all take a msgtype,
- * which is a bitmask of the messages defined in
- * include/ui/Camera.h
- */
-
- /**
- * Enable a message, or set of messages.
- */
- void enableMsgType(int32_t msgType);
-
- /**
- * Disable a message, or a set of messages.
- *
- * Once received a call to disableMsgType(CAMERA_MSG_VIDEO_FRAME), camera hal
- * should not rely on its client to call releaseRecordingFrame() to release
- * video recording frames sent out by the cameral hal before and after the
- * disableMsgType(CAMERA_MSG_VIDEO_FRAME) call. Camera hal clients must not
- * modify/access any video recording frame after calling
- * disableMsgType(CAMERA_MSG_VIDEO_FRAME).
- */
- void disableMsgType(int32_t msgType);
-
- /**
- * Query whether a message, or a set of messages, is enabled.
- * Note that this is operates as an AND, if any of the messages
- * queried are off, this will return false.
- */
- int msgTypeEnabled(int32_t msgType);
-
- /**
- * Start preview mode.
- */
- status_t startPreview();
-
- /**
- * Stop a previously started preview.
- */
- void stopPreview();
-
- /**
- * Returns true if preview is enabled.
- */
- int previewEnabled();
-
- /**
- * Request the camera hal to store meta data or real YUV data in
- * the video buffers send out via CAMERA_MSG_VIDEO_FRRAME for a
- * recording session. If it is not called, the default camera
- * hal behavior is to store real YUV data in the video buffers.
- *
- * This method should be called before startRecording() in order
- * to be effective.
- *
- * If meta data is stored in the video buffers, it is up to the
- * receiver of the video buffers to interpret the contents and
- * to find the actual frame data with the help of the meta data
- * in the buffer. How this is done is outside of the scope of
- * this method.
- *
- * Some camera hal may not support storing meta data in the video
- * buffers, but all camera hal should support storing real YUV data
- * in the video buffers. If the camera hal does not support storing
- * the meta data in the video buffers when it is requested to do
- * do, INVALID_OPERATION must be returned. It is very useful for
- * the camera hal to pass meta data rather than the actual frame
- * data directly to the video encoder, since the amount of the
- * uncompressed frame data can be very large if video size is large.
- *
- * @param enable if true to instruct the camera hal to store
- * meta data in the video buffers; false to instruct
- * the camera hal to store real YUV data in the video
- * buffers.
- *
- * @return OK on success.
- */
-
- status_t storeMetaDataInBuffers(int enable);
-
- /**
- * Start record mode. When a record image is available a CAMERA_MSG_VIDEO_FRAME
- * message is sent with the corresponding frame. Every record frame must be released
- * by a cameral hal client via releaseRecordingFrame() before the client calls
- * disableMsgType(CAMERA_MSG_VIDEO_FRAME). After the client calls
- * disableMsgType(CAMERA_MSG_VIDEO_FRAME), it is camera hal's responsibility
- * to manage the life-cycle of the video recording frames, and the client must
- * not modify/access any video recording frames.
- */
- status_t startRecording();
-
- /**
- * Stop a previously started recording.
- */
- void stopRecording();
-
- /**
- * Returns true if recording is enabled.
- */
- int recordingEnabled();
-
- /**
- * Release a record frame previously returned by CAMERA_MSG_VIDEO_FRAME.
- *
- * It is camera hal client's responsibility to release video recording
- * frames sent out by the camera hal before the camera hal receives
- * a call to disableMsgType(CAMERA_MSG_VIDEO_FRAME). After it receives
- * the call to disableMsgType(CAMERA_MSG_VIDEO_FRAME), it is camera hal's
- * responsibility of managing the life-cycle of the video recording
- * frames.
- */
- void releaseRecordingFrame(const sp<IMemory>& mem);
-
- /**
- * Release a batch of recording frames previously returned by
- * CAMERA_MSG_VIDEO_FRAME. This method only supports frames that are
- * stored as VideoNativeHandleMetadata.
- *
- * It is camera hal client's responsibility to release video recording
- * frames sent out by the camera hal before the camera hal receives
- * a call to disableMsgType(CAMERA_MSG_VIDEO_FRAME). After it receives
- * the call to disableMsgType(CAMERA_MSG_VIDEO_FRAME), it is camera hal's
- * responsibility of managing the life-cycle of the video recording
- * frames.
- */
- void releaseRecordingFrameBatch(const std::vector<sp<IMemory>>& frames);
-
- /**
- * Start auto focus, the notification callback routine is called
- * with CAMERA_MSG_FOCUS once when focusing is complete. autoFocus()
- * will be called again if another auto focus is needed.
- */
- status_t autoFocus();
-
- /**
- * Cancels auto-focus function. If the auto-focus is still in progress,
- * this function will cancel it. Whether the auto-focus is in progress
- * or not, this function will return the focus position to the default.
- * If the camera does not support auto-focus, this is a no-op.
- */
- status_t cancelAutoFocus();
-
- /**
- * Take a picture.
- */
- status_t takePicture();
-
- /**
- * Cancel a picture that was started with takePicture. Calling this
- * method when no picture is being taken is a no-op.
- */
- status_t cancelPicture();
-
- /**
- * Set the camera parameters. This returns BAD_VALUE if any parameter is
- * invalid or not supported. */
- status_t setParameters(const CameraParameters ¶ms);
-
- /** Return the camera parameters. */
- CameraParameters getParameters() const;
-
- /**
- * Send command to camera driver.
- */
- status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2);
-
- /**
- * Release the hardware resources owned by this object. Note that this is
- * *not* done in the destructor.
- */
- void release();
-
- /**
- * Dump state of the camera hardware
- */
- status_t dump(int fd, const Vector<String16>& /*args*/) const;
-
-private:
- sp<hardware::camera::device::V1_0::ICameraDevice> mHidlDevice;
- String8 mName;
-
- static void sNotifyCb(int32_t msg_type, int32_t ext1,
- int32_t ext2, void *user);
-
- static void sDataCb(int32_t msg_type,
- const camera_memory_t *data, unsigned int index,
- camera_frame_metadata_t *metadata,
- void *user);
-
- static void sDataCbTimestamp(nsecs_t timestamp, int32_t msg_type,
- const camera_memory_t *data, unsigned index,
- void *user);
-
- // This is a utility class that combines a MemoryHeapBase and a MemoryBase
- // in one. Since we tend to use them in a one-to-one relationship, this is
- // handy.
- class CameraHeapMemory : public RefBase {
- public:
- CameraHeapMemory(int fd, size_t buf_size, uint_t num_buffers = 1) :
- mBufSize(buf_size),
- mNumBufs(num_buffers)
- {
- mHeap = new MemoryHeapBase(fd, buf_size * num_buffers);
- commonInitialization();
- }
-
- explicit CameraHeapMemory(size_t buf_size, uint_t num_buffers = 1) :
- mBufSize(buf_size),
- mNumBufs(num_buffers)
- {
- mHeap = new MemoryHeapBase(buf_size * num_buffers);
- commonInitialization();
- }
-
- void commonInitialization()
- {
- handle.data = mHeap->base();
- handle.size = mBufSize * mNumBufs;
- handle.handle = this;
-
- mBuffers = new sp<MemoryBase>[mNumBufs];
- for (uint_t i = 0; i < mNumBufs; i++)
- mBuffers[i] = new MemoryBase(mHeap,
- i * mBufSize,
- mBufSize);
-
- handle.release = sPutMemory;
- }
-
- virtual ~CameraHeapMemory()
- {
- delete [] mBuffers;
- }
-
- size_t mBufSize;
- uint_t mNumBufs;
- sp<MemoryHeapBase> mHeap;
- sp<MemoryBase> *mBuffers;
-
- camera_memory_t handle;
- };
-
- static camera_memory_t* sGetMemory(int fd, size_t buf_size, uint_t num_bufs,
- void *user __attribute__((unused)));
-
- static void sPutMemory(camera_memory_t *data);
-
- std::pair<bool, uint64_t> getBufferId(ANativeWindowBuffer* anb);
- void cleanupCirculatingBuffers();
-
- /**
- * Implementation of android::hardware::camera::device::V1_0::ICameraDeviceCallback
- */
- hardware::Return<void> notifyCallback(
- hardware::camera::device::V1_0::NotifyCallbackMsg msgType,
- int32_t ext1, int32_t ext2) override;
- hardware::Return<uint32_t> registerMemory(
- const hardware::hidl_handle& descriptor,
- uint32_t bufferSize, uint32_t bufferCount) override;
- hardware::Return<void> unregisterMemory(uint32_t memId) override;
- hardware::Return<void> dataCallback(
- hardware::camera::device::V1_0::DataCallbackMsg msgType,
- uint32_t data, uint32_t bufferIndex,
- const hardware::camera::device::V1_0::CameraFrameMetadata& metadata) override;
- hardware::Return<void> dataCallbackTimestamp(
- hardware::camera::device::V1_0::DataCallbackMsg msgType,
- uint32_t data, uint32_t bufferIndex, int64_t timestamp) override;
- hardware::Return<void> handleCallbackTimestamp(
- hardware::camera::device::V1_0::DataCallbackMsg msgType,
- const hardware::hidl_handle& frameData, uint32_t data,
- uint32_t bufferIndex, int64_t timestamp) override;
- hardware::Return<void> handleCallbackTimestampBatch(
- hardware::camera::device::V1_0::DataCallbackMsg msgType,
- const hardware::hidl_vec<
- hardware::camera::device::V1_0::HandleTimestampMessage>&) override;
-
- /**
- * Implementation of android::hardware::camera::device::V1_0::ICameraDevicePreviewCallback
- */
- hardware::Return<void> dequeueBuffer(dequeueBuffer_cb _hidl_cb) override;
- hardware::Return<hardware::camera::common::V1_0::Status>
- enqueueBuffer(uint64_t bufferId) override;
- hardware::Return<hardware::camera::common::V1_0::Status>
- cancelBuffer(uint64_t bufferId) override;
- hardware::Return<hardware::camera::common::V1_0::Status>
- setBufferCount(uint32_t count) override;
- hardware::Return<hardware::camera::common::V1_0::Status>
- setBuffersGeometry(uint32_t w, uint32_t h,
- hardware::graphics::common::V1_0::PixelFormat format) override;
- hardware::Return<hardware::camera::common::V1_0::Status>
- setCrop(int32_t left, int32_t top, int32_t right, int32_t bottom) override;
- hardware::Return<hardware::camera::common::V1_0::Status>
- setUsage(hardware::graphics::common::V1_0::BufferUsage usage) override;
- hardware::Return<hardware::camera::common::V1_0::Status>
- setSwapInterval(int32_t interval) override;
- hardware::Return<void> getMinUndequeuedBufferCount(
- getMinUndequeuedBufferCount_cb _hidl_cb) override;
- hardware::Return<hardware::camera::common::V1_0::Status>
- setTimestamp(int64_t timestamp) override;
-
- sp<ANativeWindow> mPreviewWindow;
-
- notify_callback mNotifyCb;
- data_callback mDataCb;
- data_callback_timestamp mDataCbTimestamp;
- data_callback_timestamp_batch mDataCbTimestampBatch;
- void *mCbUser;
-
- // Cached values for preview stream parameters
- static const int NOT_SET = -1;
- int mPreviewScalingMode;
- int mPreviewTransform;
- int mPreviewWidth;
- int mPreviewHeight;
- int mPreviewFormat;
- uint64_t mPreviewUsage;
- int mPreviewSwapInterval;
- android_native_rect_t mPreviewCrop;
-
- struct BufferHasher {
- size_t operator()(const buffer_handle_t& buf) const {
- if (buf == nullptr)
- return 0;
-
- size_t result = 1;
- result = 31 * result + buf->numFds;
- result = 31 * result + buf->numInts;
- int length = buf->numFds + buf->numInts;
- for (int i = 0; i < length; i++) {
- result = 31 * result + buf->data[i];
- }
- return result;
- }
- };
-
- struct BufferComparator {
- bool operator()(const buffer_handle_t& buf1, const buffer_handle_t& buf2) const {
- if (buf1->numFds == buf2->numFds && buf1->numInts == buf2->numInts) {
- int length = buf1->numFds + buf1->numInts;
- for (int i = 0; i < length; i++) {
- if (buf1->data[i] != buf2->data[i]) {
- return false;
- }
- }
- return true;
- }
- return false;
- }
- };
-
- std::mutex mBufferIdMapLock; // protecting mBufferIdMap and mNextBufferId
- typedef std::unordered_map<const buffer_handle_t, uint64_t,
- BufferHasher, BufferComparator> BufferIdMap;
- // stream ID -> per stream buffer ID map
- BufferIdMap mBufferIdMap;
- std::unordered_map<uint64_t, ANativeWindowBuffer*> mReversedBufMap;
- uint64_t mNextBufferId = 1;
- static const uint64_t BUFFER_ID_NO_BUFFER = 0;
-
- std::mutex mHidlMemPoolMapLock; // protecting mHidlMemPoolMap
- std::unordered_map<int, camera_memory_t*> mHidlMemPoolMap;
-};
-
-}; // namespace android
-
-#endif
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..08cde5d 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);
@@ -1164,13 +1218,13 @@
return;
}
+ bufRet.streamId = streamId;
if (outputStream->isAbandoned()) {
bufRet.val.error(StreamBufferRequestError::STREAM_DISCONNECTED);
allReqsSucceeds = false;
continue;
}
- bufRet.streamId = streamId;
size_t handOutBufferCount = outputStream->getOutstandingBuffersCount();
uint32_t numBuffersRequested = bufReq.numBuffersRequested;
size_t totalHandout = handOutBufferCount + numBuffersRequested;
@@ -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/hidl/HidlCameraService.cpp b/services/camera/libcameraservice/hidl/HidlCameraService.cpp
index a46133e..9ea9526 100644
--- a/services/camera/libcameraservice/hidl/HidlCameraService.cpp
+++ b/services/camera/libcameraservice/hidl/HidlCameraService.cpp
@@ -103,7 +103,7 @@
}
sp<hardware::camera2::ICameraDeviceCallbacks> callbacks = hybridCallbacks;
binder::Status serviceRet = mAidlICameraService->connectDevice(
- callbacks, String16(cameraId.c_str()), String16(""), std::unique_ptr<String16>(),
+ callbacks, String16(cameraId.c_str()), String16(""), {},
hardware::ICameraService::USE_CALLING_UID, /*out*/&deviceRemote);
HStatus status = HStatus::NO_ERROR;
if (!serviceRet.isOk()) {
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..05bbbc7 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",
@@ -14,26 +15,12 @@
"libmedia_codecserviceregistrant",
],
- target: {
- android: {
- product_variables: {
- malloc_not_svelte: {
- // Scudo increases memory footprint, so only enable on
- // non-svelte devices.
- shared_libs: ["libc_scudo"],
- },
- },
- },
- },
-
header_libs: [
"libmedia_headers",
],
init_rc: ["mediaswcodec.rc"],
- required: ["mediaswcodec.policy"],
-
cflags: [
"-Werror",
"-Wall",
diff --git a/services/mediaextractor/Android.bp b/services/mediaextractor/Android.bp
index 0b25d62..03e1e41 100644
--- a/services/mediaextractor/Android.bp
+++ b/services/mediaextractor/Android.bp
@@ -35,20 +35,6 @@
"liblog",
"libavservices_minijail",
],
- header_libs: [
- "bionic_libc_platform_headers",
- ],
- target: {
- android: {
- product_variables: {
- malloc_not_svelte: {
- // Scudo increases memory footprint, so only enable on
- // non-svelte devices.
- shared_libs: ["libc_scudo"],
- },
- },
- },
- },
init_rc: ["mediaextractor.rc"],
cflags: [
diff --git a/services/mediaextractor/MediaExtractorService.cpp b/services/mediaextractor/MediaExtractorService.cpp
index 9992d1c..71a5bff 100644
--- a/services/mediaextractor/MediaExtractorService.cpp
+++ b/services/mediaextractor/MediaExtractorService.cpp
@@ -39,23 +39,23 @@
::android::binder::Status MediaExtractorService::makeExtractor(
const ::android::sp<::android::IDataSource>& remoteSource,
- const ::std::unique_ptr< ::std::string> &mime,
+ const ::std::optional< ::std::string> &mime,
::android::sp<::android::IMediaExtractor>* _aidl_return) {
- ALOGV("@@@ MediaExtractorService::makeExtractor for %s", mime.get()->c_str());
+ ALOGV("@@@ MediaExtractorService::makeExtractor for %s", mime ? mime->c_str() : nullptr);
sp<DataSource> localSource = CreateDataSourceFromIDataSource(remoteSource);
MediaBuffer::useSharedMemory();
sp<IMediaExtractor> extractor = MediaExtractorFactory::CreateFromService(
localSource,
- mime.get() ? mime.get()->c_str() : nullptr);
+ mime ? mime->c_str() : nullptr);
ALOGV("extractor service created %p (%s)",
extractor.get(),
extractor == nullptr ? "" : extractor->name());
if (extractor != nullptr) {
- registerMediaExtractor(extractor, localSource, mime.get() ? mime.get()->c_str() : nullptr);
+ registerMediaExtractor(extractor, localSource, mime ? mime->c_str() : nullptr);
}
*_aidl_return = extractor;
return binder::Status::ok();
diff --git a/services/mediaextractor/MediaExtractorService.h b/services/mediaextractor/MediaExtractorService.h
index 1b40bf9..0081e7e 100644
--- a/services/mediaextractor/MediaExtractorService.h
+++ b/services/mediaextractor/MediaExtractorService.h
@@ -33,7 +33,7 @@
virtual ::android::binder::Status makeExtractor(
const ::android::sp<::android::IDataSource>& source,
- const ::std::unique_ptr< ::std::string> &mime,
+ const ::std::optional< ::std::string> &mime,
::android::sp<::android::IMediaExtractor>* _aidl_return);
virtual ::android::binder::Status makeIDataSource(
diff --git a/services/mediaextractor/main_extractorservice.cpp b/services/mediaextractor/main_extractorservice.cpp
index f21574f..b7b51a6 100644
--- a/services/mediaextractor/main_extractorservice.cpp
+++ b/services/mediaextractor/main_extractorservice.cpp
@@ -28,8 +28,6 @@
#include <android-base/properties.h>
#include <utils/misc.h>
-#include <bionic/reserved_signals.h>
-
// from LOCAL_C_INCLUDES
#include "MediaExtractorService.h"
#include "minijail.h"
@@ -50,10 +48,6 @@
signal(SIGPIPE, SIG_IGN);
- // Do not assist platform profilers (relevant only on debug builds).
- // Otherwise, the signal handler can violate the seccomp policy.
- signal(BIONIC_SIGNAL_PROFILER, SIG_IGN);
-
//b/62255959: this forces libutis.so to dlopen vendor version of libutils.so
//before minijail is on. This is dirty but required since some syscalls such
//as pread64 are used by linker but aren't allowed in the minijail. By
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..d78d1e3 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -16,20 +16,150 @@
//#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; // unused since we suppress
+
+// 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.
+
+static constexpr const char * SUPPRESSED = "SUPPRESSED";
+
+/*
+ * 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 +177,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 +207,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 +216,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 +229,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 +242,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 +337,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 +384,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 +401,185 @@
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;
+ outputDeviceNames = SUPPRESSED;
+#if 0 // TODO(b/161554630) sanitize name
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
+ }
+#endif
}
- // 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 +610,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 +699,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 = SUPPRESSED; // TODO(b/161554630) sanitize 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..33dfa8fa
--- /dev/null
+++ b/services/mediametrics/AudioPowerUsage.cpp
@@ -0,0 +1,423 @@
+/*
+ * 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;
+}
+
+bool AudioPowerUsage::saveAsItems_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
+ }
+
+ bool ret = false;
+ const int32_t input_bit = device & INPUT_DEVICE_BIT;
+ int32_t device_bits = device ^ input_bit;
+
+ while (device_bits != 0) {
+ int32_t tmp_device = device_bits & -device_bits; // get lowest bit
+ device_bits ^= tmp_device; // clear lowest bit
+ tmp_device |= input_bit; // restore input bit
+ ret = saveAsItem_l(tmp_device, duration_ns, type, average_vol);
+
+ ALOGV("%s: device %#x recorded, remaining device_bits = %#x", __func__,
+ tmp_device, device_bits);
+ }
+ return ret;
+}
+
+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);
+ saveAsItems_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;
+ saveAsItems_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;
+ saveAsItems_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..b705a6a
--- /dev/null
+++ b/services/mediametrics/AudioPowerUsage.h
@@ -0,0 +1,107 @@
+/*
+ * 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();
+ bool saveAsItems_l(int32_t device, int64_t duration, int32_t type, double average_vol)
+ REQUIRES(mLock);
+
+ 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/Android.bp b/services/mediaresourcemanager/Android.bp
index a3519d5..0d53c5e 100644
--- a/services/mediaresourcemanager/Android.bp
+++ b/services/mediaresourcemanager/Android.bp
@@ -1,6 +1,4 @@
-
-
-cc_library_shared {
+cc_library {
name: "libresourcemanagerservice",
srcs: [
@@ -25,5 +23,4 @@
],
export_include_dirs: ["."],
-
}
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/mediaresourcemanager/ResourceManagerService.h b/services/mediaresourcemanager/ResourceManagerService.h
index ee982b7..49c247e 100644
--- a/services/mediaresourcemanager/ResourceManagerService.h
+++ b/services/mediaresourcemanager/ResourceManagerService.h
@@ -43,7 +43,7 @@
using ::aidl::android::media::MediaResourcePolicyParcel;
typedef std::map<std::tuple<
- MediaResource::Type, MediaResource::SubType, std::vector<int8_t>>,
+ MediaResource::Type, MediaResource::SubType, std::vector<uint8_t>>,
MediaResourceParcel> ResourceList;
struct ResourceInfo {
diff --git a/services/mediaresourcemanager/test/Android.bp b/services/mediaresourcemanager/test/Android.bp
index b6c548c..6b2ef69 100644
--- a/services/mediaresourcemanager/test/Android.bp
+++ b/services/mediaresourcemanager/test/Android.bp
@@ -3,12 +3,12 @@
name: "ResourceManagerService_test",
srcs: ["ResourceManagerService_test.cpp"],
test_suites: ["device-tests"],
+ static_libs: ["libresourcemanagerservice"],
shared_libs: [
"libbinder",
"libbinder_ndk",
"liblog",
"libmedia",
- "libresourcemanagerservice",
"libutils",
],
include_dirs: [
@@ -19,17 +19,16 @@
"-Werror",
"-Wall",
],
- compile_multilib: "32",
}
cc_test {
name: "ServiceLog_test",
srcs: ["ServiceLog_test.cpp"],
test_suites: ["device-tests"],
+ static_libs: ["libresourcemanagerservice"],
shared_libs: [
"liblog",
"libmedia",
- "libresourcemanagerservice",
"libutils",
],
include_dirs: [
@@ -40,5 +39,4 @@
"-Werror",
"-Wall",
],
- compile_multilib: "32",
}
diff --git a/services/mediatranscoding/Android.bp b/services/mediatranscoding/Android.bp
index 17347a9..2dbcf5a 100644
--- a/services/mediatranscoding/Android.bp
+++ b/services/mediatranscoding/Android.bp
@@ -2,16 +2,25 @@
cc_library_shared {
name: "libmediatranscodingservice",
- srcs: ["MediaTranscodingService.cpp"],
+ srcs: [
+ "MediaTranscodingService.cpp",
+ "SimulatedTranscoder.cpp",
+ ],
shared_libs: [
"libbase",
+ "libbinder",
"libbinder_ndk",
+ "libcutils",
"liblog",
"libmediatranscoding",
"libutils",
],
+ export_shared_lib_headers: [
+ "libmediatranscoding",
+ ],
+
static_libs: [
"mediatranscoding_aidl_interface-ndk_platform",
],
@@ -44,18 +53,6 @@
"mediatranscoding_aidl_interface-ndk_platform",
],
- target: {
- android: {
- product_variables: {
- malloc_not_svelte: {
- // Scudo increases memory footprint, so only enable on
- // non-svelte devices.
- shared_libs: ["libc_scudo"],
- },
- },
- },
- },
-
init_rc: ["mediatranscoding.rc"],
cflags: [
diff --git a/services/mediatranscoding/MediaTranscodingService.cpp b/services/mediatranscoding/MediaTranscodingService.cpp
index 82d4161..ef7d6d2 100644
--- a/services/mediatranscoding/MediaTranscodingService.cpp
+++ b/services/mediatranscoding/MediaTranscodingService.cpp
@@ -16,13 +16,22 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaTranscodingService"
-#include <MediaTranscodingService.h>
+#include "MediaTranscodingService.h"
+
#include <android/binder_manager.h>
#include <android/binder_process.h>
+#include <binder/IServiceManager.h>
+#include <cutils/properties.h>
+#include <media/TranscoderWrapper.h>
+#include <media/TranscodingClientManager.h>
+#include <media/TranscodingJobScheduler.h>
+#include <media/TranscodingUidPolicy.h>
#include <private/android_filesystem_config.h>
#include <utils/Log.h>
#include <utils/Vector.h>
+#include "SimulatedTranscoder.h"
+
namespace android {
// Convenience methods for constructing binder::Status objects for error returns
@@ -45,9 +54,14 @@
}
}
-MediaTranscodingService::MediaTranscodingService()
- : mTranscodingClientManager(TranscodingClientManager::getInstance()) {
+MediaTranscodingService::MediaTranscodingService(
+ const std::shared_ptr<TranscoderInterface>& transcoder)
+ : mUidPolicy(new TranscodingUidPolicy()),
+ mJobScheduler(new TranscodingJobScheduler(transcoder, mUidPolicy)),
+ mClientManager(new TranscodingClientManager(mJobScheduler)) {
ALOGV("MediaTranscodingService is created");
+ transcoder->setCallback(mJobScheduler);
+ mUidPolicy->setCallback(mJobScheduler);
}
MediaTranscodingService::~MediaTranscodingService() {
@@ -56,6 +70,17 @@
binder_status_t MediaTranscodingService::dump(int fd, const char** /*args*/, uint32_t /*numArgs*/) {
String8 result;
+
+ // TODO(b/161549994): Remove libbinder dependencies for mainline.
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ result.format(
+ "Permission Denial: "
+ "can't dump MediaTranscodingService from pid=%d, uid=%d\n",
+ AIBinder_getCallingPid(), AIBinder_getCallingUid());
+ write(fd, result.string(), result.size());
+ return PERMISSION_DENIED;
+ }
+
const size_t SIZE = 256;
char buffer[SIZE];
@@ -64,14 +89,21 @@
write(fd, result.string(), result.size());
Vector<String16> args;
- mTranscodingClientManager.dumpAllClients(fd, args);
+ mClientManager->dumpAllClients(fd, args);
return OK;
}
//static
void MediaTranscodingService::instantiate() {
+ std::shared_ptr<TranscoderInterface> transcoder;
+ if (property_get_bool("debug.transcoding.simulated_transcoder", false)) {
+ transcoder = std::make_shared<SimulatedTranscoder>();
+ } else {
+ transcoder = std::make_shared<TranscoderWrapper>();
+ }
+
std::shared_ptr<MediaTranscodingService> service =
- ::ndk::SharedRefBase::make<MediaTranscodingService>();
+ ::ndk::SharedRefBase::make<MediaTranscodingService>(transcoder);
binder_status_t status =
AServiceManager_addService(service->asBinder().get(), getServiceName());
if (status != STATUS_OK) {
@@ -80,19 +112,19 @@
}
Status MediaTranscodingService::registerClient(
- const std::shared_ptr<ITranscodingServiceClient>& in_client,
- const std::string& in_opPackageName, int32_t in_clientUid, int32_t in_clientPid,
- int32_t* _aidl_return) {
- if (in_client == nullptr) {
- ALOGE("Client can not be null");
- *_aidl_return = kInvalidJobId;
- return Status::fromServiceSpecificError(ERROR_ILLEGAL_ARGUMENT);
+ const std::shared_ptr<ITranscodingClientCallback>& in_callback,
+ const std::string& in_clientName, const std::string& in_opPackageName, int32_t in_clientUid,
+ int32_t in_clientPid, std::shared_ptr<ITranscodingClient>* _aidl_return) {
+ if (in_callback == nullptr) {
+ *_aidl_return = nullptr;
+ return STATUS_ERROR_FMT(ERROR_ILLEGAL_ARGUMENT, "Client callback cannot be null!");
}
int32_t callingPid = AIBinder_getCallingPid();
int32_t callingUid = AIBinder_getCallingUid();
- // Check if we can trust clientUid. Only privilege caller could forward the uid on app client's behalf.
+ // Check if we can trust clientUid. Only privilege caller could forward the
+ // uid on app client's behalf.
if (in_clientUid == USE_CALLING_UID) {
in_clientUid = callingUid;
} else if (!isTrustedCallingUid(callingUid)) {
@@ -106,7 +138,8 @@
in_clientPid, in_clientUid);
}
- // Check if we can trust clientPid. Only privilege caller could forward the pid on app client's behalf.
+ // Check if we can trust clientPid. Only privilege caller could forward the
+ // pid on app client's behalf.
if (in_clientPid == USE_CALLING_PID) {
in_clientPid = callingPid;
} else if (!isTrustedCallingUid(callingUid)) {
@@ -120,78 +153,23 @@
in_clientPid, in_clientUid);
}
- // We know the clientId must be equal to its pid as we assigned client's pid as its clientId.
- int32_t clientId = in_clientPid;
-
- // Checks if the client already registers.
- if (mTranscodingClientManager.isClientIdRegistered(clientId)) {
- return Status::fromServiceSpecificError(ERROR_ALREADY_EXISTS);
- }
-
// Creates the client and uses its process id as client id.
- std::unique_ptr<TranscodingClientManager::ClientInfo> newClient =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- in_client, clientId, in_clientPid, in_clientUid, in_opPackageName);
- status_t err = mTranscodingClientManager.addClient(std::move(newClient));
+ std::shared_ptr<ITranscodingClient> newClient;
+
+ status_t err = mClientManager->addClient(in_callback, in_clientPid, in_clientUid, in_clientName,
+ in_opPackageName, &newClient);
if (err != OK) {
- *_aidl_return = kInvalidClientId;
+ *_aidl_return = nullptr;
return STATUS_ERROR_FMT(err, "Failed to add client to TranscodingClientManager");
}
- ALOGD("Assign client: %s pid: %d, uid: %d with id: %d", in_opPackageName.c_str(), in_clientPid,
- in_clientUid, clientId);
-
- *_aidl_return = clientId;
- return Status::ok();
-}
-
-Status MediaTranscodingService::unregisterClient(int32_t clientId, bool* _aidl_return) {
- ALOGD("unregisterClient id: %d", clientId);
- int32_t callingUid = AIBinder_getCallingUid();
- int32_t callingPid = AIBinder_getCallingPid();
-
- // Only the client with clientId or the trusted caller could unregister the client.
- if (callingPid != clientId) {
- if (!isTrustedCallingUid(callingUid)) {
- ALOGE("Untrusted caller (calling PID %d, UID %d) trying to "
- "unregister client with id: %d",
- callingUid, callingPid, clientId);
- *_aidl_return = true;
- return STATUS_ERROR_FMT(ERROR_PERMISSION_DENIED,
- "Untrusted caller (calling PID %d, UID %d) trying to "
- "unregister client with id: %d",
- callingUid, callingPid, clientId);
- }
- }
-
- *_aidl_return = (mTranscodingClientManager.removeClient(clientId) == OK);
+ *_aidl_return = newClient;
return Status::ok();
}
Status MediaTranscodingService::getNumOfClients(int32_t* _aidl_return) {
ALOGD("MediaTranscodingService::getNumOfClients");
- *_aidl_return = mTranscodingClientManager.getNumOfClients();
- return Status::ok();
-}
-
-Status MediaTranscodingService::submitRequest(int32_t /*clientId*/,
- const TranscodingRequestParcel& /*request*/,
- TranscodingJobParcel* /*job*/,
- int32_t* /*_aidl_return*/) {
- // TODO(hkuang): Add implementation.
- return Status::ok();
-}
-
-Status MediaTranscodingService::cancelJob(int32_t /*in_clientId*/, int32_t /*in_jobId*/,
- bool* /*_aidl_return*/) {
- // TODO(hkuang): Add implementation.
- return Status::ok();
-}
-
-Status MediaTranscodingService::getJobWithId(int32_t /*in_jobId*/,
- TranscodingJobParcel* /*out_job*/,
- bool* /*_aidl_return*/) {
- // TODO(hkuang): Add implementation.
+ *_aidl_return = mClientManager->getNumOfClients();
return Status::ok();
}
diff --git a/services/mediatranscoding/MediaTranscodingService.h b/services/mediatranscoding/MediaTranscodingService.h
index cc69727..505239c 100644
--- a/services/mediatranscoding/MediaTranscodingService.h
+++ b/services/mediatranscoding/MediaTranscodingService.h
@@ -19,44 +19,39 @@
#include <aidl/android/media/BnMediaTranscodingService.h>
#include <binder/IServiceManager.h>
-#include <media/TranscodingClientManager.h>
namespace android {
using Status = ::ndk::ScopedAStatus;
using ::aidl::android::media::BnMediaTranscodingService;
-using ::aidl::android::media::ITranscodingServiceClient;
+using ::aidl::android::media::ITranscodingClient;
+using ::aidl::android::media::ITranscodingClientCallback;
using ::aidl::android::media::TranscodingJobParcel;
using ::aidl::android::media::TranscodingRequestParcel;
+class TranscodingClientManager;
+class TranscodingJobScheduler;
+class TranscoderInterface;
+class UidPolicyInterface;
class MediaTranscodingService : public BnMediaTranscodingService {
public:
static constexpr int32_t kInvalidJobId = -1;
static constexpr int32_t kInvalidClientId = -1;
- MediaTranscodingService();
+ MediaTranscodingService(const std::shared_ptr<TranscoderInterface>& transcoder);
virtual ~MediaTranscodingService();
static void instantiate();
static const char* getServiceName() { return "media.transcoding"; }
- Status registerClient(const std::shared_ptr<ITranscodingServiceClient>& in_client,
- const std::string& in_opPackageName, int32_t in_clientUid,
- int32_t in_clientPid, int32_t* _aidl_return) override;
-
- Status unregisterClient(int32_t clientId, bool* _aidl_return) override;
+ Status registerClient(const std::shared_ptr<ITranscodingClientCallback>& in_callback,
+ const std::string& in_clientName, const std::string& in_opPackageName,
+ int32_t in_clientUid, int32_t in_clientPid,
+ std::shared_ptr<ITranscodingClient>* _aidl_return) override;
Status getNumOfClients(int32_t* _aidl_return) override;
- Status submitRequest(int32_t in_clientId, const TranscodingRequestParcel& in_request,
- TranscodingJobParcel* out_job, int32_t* _aidl_return) override;
-
- Status cancelJob(int32_t in_clientId, int32_t in_jobId, bool* _aidl_return) override;
-
- Status getJobWithId(int32_t in_jobId, TranscodingJobParcel* out_job,
- bool* _aidl_return) override;
-
virtual inline binder_status_t dump(int /*fd*/, const char** /*args*/, uint32_t /*numArgs*/);
private:
@@ -64,7 +59,9 @@
mutable std::mutex mServiceLock;
- TranscodingClientManager& mTranscodingClientManager;
+ std::shared_ptr<UidPolicyInterface> mUidPolicy;
+ std::shared_ptr<TranscodingJobScheduler> mJobScheduler;
+ std::shared_ptr<TranscodingClientManager> mClientManager;
};
} // namespace android
diff --git a/services/mediatranscoding/SimulatedTranscoder.cpp b/services/mediatranscoding/SimulatedTranscoder.cpp
new file mode 100644
index 0000000..97d5f5f
--- /dev/null
+++ b/services/mediatranscoding/SimulatedTranscoder.cpp
@@ -0,0 +1,170 @@
+/*
+ * 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 "SimulatedTranscoder"
+#include "SimulatedTranscoder.h"
+
+#include <utils/Log.h>
+
+#include <thread>
+
+namespace android {
+
+//static
+const char* SimulatedTranscoder::toString(Event::Type type) {
+ switch (type) {
+ case Event::Start:
+ return "Start";
+ case Event::Pause:
+ return "Pause";
+ case Event::Resume:
+ return "Resume";
+ default:
+ break;
+ }
+ return "(unknown)";
+}
+
+SimulatedTranscoder::SimulatedTranscoder() {
+ std::thread(&SimulatedTranscoder::threadLoop, this).detach();
+}
+
+void SimulatedTranscoder::setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) {
+ mCallback = cb;
+}
+
+void SimulatedTranscoder::start(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) {
+ if (request.testConfig.has_value() && request.testConfig->processingTotalTimeMs > 0) {
+ mJobProcessingTimeMs = request.testConfig->processingTotalTimeMs;
+ }
+ ALOGV("%s: job {%d}: processingTime: %lld", __FUNCTION__, jobId,
+ (long long)mJobProcessingTimeMs);
+ queueEvent(Event::Start, clientId, jobId, [=] {
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ callback->onStarted(clientId, jobId);
+ }
+ });
+}
+
+void SimulatedTranscoder::pause(ClientIdType clientId, JobIdType jobId) {
+ queueEvent(Event::Pause, clientId, jobId, [=] {
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ callback->onPaused(clientId, jobId);
+ }
+ });
+}
+
+void SimulatedTranscoder::resume(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& /*request*/,
+ const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) {
+ queueEvent(Event::Resume, clientId, jobId, [=] {
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ callback->onResumed(clientId, jobId);
+ }
+ });
+}
+
+void SimulatedTranscoder::stop(ClientIdType clientId, JobIdType jobId) {
+ queueEvent(Event::Stop, clientId, jobId, nullptr);
+}
+
+void SimulatedTranscoder::queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
+ std::function<void()> runnable) {
+ ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)clientId, jobId, toString(type));
+
+ auto lock = std::scoped_lock(mLock);
+
+ mQueue.push_back({type, clientId, jobId, runnable});
+ mCondition.notify_one();
+}
+
+void SimulatedTranscoder::threadLoop() {
+ bool running = false;
+ std::chrono::microseconds remainingUs(kJobDurationUs);
+ std::chrono::system_clock::time_point lastRunningTime;
+ Event lastRunningEvent;
+
+ std::unique_lock<std::mutex> lock(mLock);
+ // SimulatedTranscoder currently lives in the transcoding service, as long as
+ // MediaTranscodingService itself.
+ while (true) {
+ // Wait for the next event.
+ while (mQueue.empty()) {
+ if (!running) {
+ mCondition.wait(lock);
+ continue;
+ }
+ // If running, wait for the remaining life of this job. Report finish if timed out.
+ std::cv_status status = mCondition.wait_for(lock, remainingUs);
+ if (status == std::cv_status::timeout) {
+ running = false;
+
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ lock.unlock();
+ callback->onFinish(lastRunningEvent.clientId, lastRunningEvent.jobId);
+ lock.lock();
+ }
+ } else {
+ // Advance last running time and remaining time. This is needed to guard
+ // against bad events (which will be ignored) or spurious wakeups, in that
+ // case we don't want to wait for the same time again.
+ auto now = std::chrono::system_clock::now();
+ remainingUs -= (now - lastRunningTime);
+ lastRunningTime = now;
+ }
+ }
+
+ // Handle the events, adjust state and send updates to client accordingly.
+ while (!mQueue.empty()) {
+ Event event = *mQueue.begin();
+ mQueue.pop_front();
+
+ ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
+ toString(event.type));
+
+ if (!running && (event.type == Event::Start || event.type == Event::Resume)) {
+ running = true;
+ lastRunningTime = std::chrono::system_clock::now();
+ lastRunningEvent = event;
+ if (event.type == Event::Start) {
+ remainingUs = std::chrono::milliseconds(mJobProcessingTimeMs);
+ }
+ } else if (running && (event.type == Event::Pause || event.type == Event::Stop)) {
+ running = false;
+ remainingUs -= (std::chrono::system_clock::now() - lastRunningTime);
+ } else {
+ ALOGW("%s: discarding bad event: job {%lld, %d}: %s", __FUNCTION__,
+ (long long)event.clientId, event.jobId, toString(event.type));
+ continue;
+ }
+
+ if (event.runnable != nullptr) {
+ lock.unlock();
+ event.runnable();
+ lock.lock();
+ }
+ }
+ }
+}
+
+} // namespace android
diff --git a/services/mediatranscoding/SimulatedTranscoder.h b/services/mediatranscoding/SimulatedTranscoder.h
new file mode 100644
index 0000000..1c359dd
--- /dev/null
+++ b/services/mediatranscoding/SimulatedTranscoder.h
@@ -0,0 +1,79 @@
+/*
+ * 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_MEDIA_SIMULATED_TRANSCODER_H
+#define ANDROID_MEDIA_SIMULATED_TRANSCODER_H
+
+#include <android-base/thread_annotations.h>
+#include <media/TranscoderInterface.h>
+
+#include <list>
+#include <mutex>
+
+namespace android {
+
+/**
+ * SimulatedTranscoder is currently used to instantiate MediaTranscodingService
+ * on service side for testing, so that we could actually test the IPC calls of
+ * MediaTranscodingService to expose issues that's observable only over IPC.
+ * SimulatedTranscoder is used when useSimulatedTranscoder in TranscodingTestConfig
+ * is set to true.
+ *
+ * SimulatedTranscoder simulates job execution by reporting finish after kJobDurationUs.
+ * Job lifecycle events are reported via progress updates with special progress
+ * numbers (equal to the Event's type).
+ */
+class SimulatedTranscoder : public TranscoderInterface {
+public:
+ struct Event {
+ enum Type { NoEvent, Start, Pause, Resume, Stop, Finished, Failed } type;
+ ClientIdType clientId;
+ JobIdType jobId;
+ std::function<void()> runnable;
+ };
+
+ static constexpr int64_t kJobDurationUs = 1000000;
+
+ SimulatedTranscoder();
+
+ // TranscoderInterface
+ void setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) override;
+ void start(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
+ void pause(ClientIdType clientId, JobIdType jobId) override;
+ void resume(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
+ void stop(ClientIdType clientId, JobIdType jobId) override;
+ // ~TranscoderInterface
+
+private:
+ std::weak_ptr<TranscoderCallbackInterface> mCallback;
+ std::mutex mLock;
+ std::condition_variable mCondition;
+ std::list<Event> mQueue GUARDED_BY(mLock);
+
+ // Minimum time spent on transcode the video. This is used just for testing.
+ int64_t mJobProcessingTimeMs = kJobDurationUs / 1000;
+
+ static const char* toString(Event::Type type);
+ void queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
+ std::function<void()> runnable);
+ void threadLoop();
+};
+
+} // namespace android
+
+#endif // ANDROID_MEDIA_SIMULATED_TRANSCODER_H
diff --git a/services/mediatranscoding/tests/Android.bp b/services/mediatranscoding/tests/Android.bp
index e0e040c..364a198 100644
--- a/services/mediatranscoding/tests/Android.bp
+++ b/services/mediatranscoding/tests/Android.bp
@@ -19,6 +19,7 @@
"liblog",
"libutils",
"libmediatranscodingservice",
+ "libcutils",
],
static_libs: [
@@ -26,10 +27,24 @@
],
}
-// MediaTranscodingService unit test
+// MediaTranscodingService unit test using simulated transcoder
cc_test {
- name: "mediatranscodingservice_tests",
+ name: "mediatranscodingservice_simulated_tests",
defaults: ["mediatranscodingservice_test_defaults"],
- srcs: ["mediatranscodingservice_tests.cpp"],
-}
\ No newline at end of file
+ srcs: ["mediatranscodingservice_simulated_tests.cpp"],
+
+ required: [
+ "TranscodingUidPolicy_TestAppA",
+ "TranscodingUidPolicy_TestAppB",
+ "TranscodingUidPolicy_TestAppC",
+ ],
+}
+
+// MediaTranscodingService unit test using real transcoder
+cc_test {
+ name: "mediatranscodingservice_real_tests",
+ defaults: ["mediatranscodingservice_test_defaults"],
+
+ srcs: ["mediatranscodingservice_real_tests.cpp"],
+}
diff --git a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
new file mode 100644
index 0000000..53fd7ec
--- /dev/null
+++ b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Unit Test for MediaTranscodingService.
+
+#include <aidl/android/media/BnTranscodingClientCallback.h>
+#include <aidl/android/media/IMediaTranscodingService.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <aidl/android/media/TranscodingJobParcel.h>
+#include <aidl/android/media/TranscodingJobPriority.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <binder/PermissionController.h>
+#include <cutils/multiuser.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <utils/Log.h>
+
+#include <iostream>
+#include <list>
+
+#include "SimulatedTranscoder.h"
+
+namespace android {
+
+namespace media {
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::media::BnTranscodingClientCallback;
+using aidl::android::media::IMediaTranscodingService;
+using aidl::android::media::ITranscodingClient;
+using aidl::android::media::ITranscodingClientCallback;
+using aidl::android::media::TranscodingJobParcel;
+using aidl::android::media::TranscodingJobPriority;
+using aidl::android::media::TranscodingRequestParcel;
+using aidl::android::media::TranscodingVideoTrackFormat;
+
+constexpr int32_t kClientUseCallingPid = IMediaTranscodingService::USE_CALLING_PID;
+
+constexpr uid_t kClientUid = 5000;
+#define UID(n) (kClientUid + (n))
+
+constexpr int32_t kClientId = 0;
+#define CLIENT(n) (kClientId + (n))
+
+constexpr const char* kClientName = "TestClient";
+constexpr const char* kClientPackageA = "com.android.tests.transcoding.testapp.A";
+constexpr const char* kClientPackageB = "com.android.tests.transcoding.testapp.B";
+constexpr const char* kClientPackageC = "com.android.tests.transcoding.testapp.C";
+
+constexpr const char* kTestActivityName = "/com.android.tests.transcoding.MainActivity";
+
+static status_t getUidForPackage(String16 packageName, userid_t userId, /*inout*/ uid_t& uid) {
+ PermissionController pc;
+ uid = pc.getPackageUid(packageName, 0);
+ if (uid <= 0) {
+ ALOGE("Unknown package: '%s'", String8(packageName).string());
+ return BAD_VALUE;
+ }
+
+ if (userId < 0) {
+ ALOGE("Invalid user: %d", userId);
+ return BAD_VALUE;
+ }
+
+ uid = multiuser_get_uid(userId, uid);
+ return NO_ERROR;
+}
+
+struct ShellHelper {
+ static bool RunCmd(const std::string& cmdStr) {
+ int ret = system(cmdStr.c_str());
+ if (ret != 0) {
+ ALOGE("Failed to run cmd: %s, exitcode %d", cmdStr.c_str(), ret);
+ return false;
+ }
+ return true;
+ }
+
+ static bool Start(const char* packageName, const char* activityName) {
+ return RunCmd("am start -W " + std::string(packageName) + std::string(activityName) +
+ " &> /dev/null");
+ }
+
+ static bool Stop(const char* packageName) {
+ return RunCmd("am force-stop " + std::string(packageName));
+ }
+};
+
+struct EventTracker {
+ struct Event {
+ enum { NoEvent, Start, Pause, Resume, Finished, Failed } type;
+ int64_t clientId;
+ int32_t jobId;
+ };
+
+#define DECLARE_EVENT(action) \
+ static Event action(int32_t clientId, int32_t jobId) { \
+ return {Event::action, clientId, jobId}; \
+ }
+
+ DECLARE_EVENT(Start);
+ DECLARE_EVENT(Pause);
+ DECLARE_EVENT(Resume);
+ DECLARE_EVENT(Finished);
+ DECLARE_EVENT(Failed);
+
+ static constexpr Event NoEvent = {Event::NoEvent, 0, 0};
+
+ static std::string toString(const Event& event) {
+ std::string eventStr;
+ switch (event.type) {
+ case Event::Start:
+ eventStr = "Start";
+ break;
+ case Event::Pause:
+ eventStr = "Pause";
+ break;
+ case Event::Resume:
+ eventStr = "Resume";
+ break;
+ case Event::Finished:
+ eventStr = "Finished";
+ break;
+ case Event::Failed:
+ eventStr = "Failed";
+ break;
+ default:
+ return "NoEvent";
+ }
+ return "job {" + std::to_string(event.clientId) + ", " + std::to_string(event.jobId) +
+ "}: " + eventStr;
+ }
+
+ // Pop 1 event from front, wait for up to timeoutUs if empty.
+ const Event& pop(int64_t timeoutUs = 0) {
+ std::unique_lock lock(mLock);
+
+ if (mEventQueue.empty() && timeoutUs > 0) {
+ mCondition.wait_for(lock, std::chrono::microseconds(timeoutUs));
+ }
+
+ if (mEventQueue.empty()) {
+ mPoppedEvent = NoEvent;
+ } else {
+ mPoppedEvent = *mEventQueue.begin();
+ mEventQueue.pop_front();
+ }
+
+ return mPoppedEvent;
+ }
+
+ // Push 1 event to back.
+ void append(const Event& event,
+ const TranscodingErrorCode err = TranscodingErrorCode::kNoError) {
+ ALOGD("%s", toString(event).c_str());
+
+ std::unique_lock lock(mLock);
+
+ mEventQueue.push_back(event);
+ mLastErr = err;
+ mCondition.notify_one();
+ }
+
+ void updateProgress(int progress) {
+ std::unique_lock lock(mLock);
+ mLastProgress = progress;
+ mUpdateCount++;
+ }
+
+ int getUpdateCount(int *lastProgress) {
+ std::unique_lock lock(mLock);
+ *lastProgress = mLastProgress;
+ return mUpdateCount;
+ }
+
+ TranscodingErrorCode getLastError() {
+ std::unique_lock lock(mLock);
+ return mLastErr;
+ }
+
+private:
+ std::mutex mLock;
+ std::condition_variable mCondition;
+ Event mPoppedEvent;
+ std::list<Event> mEventQueue;
+ TranscodingErrorCode mLastErr;
+ int mUpdateCount = 0;
+ int mLastProgress = -1;
+};
+
+// Operators for GTest macros.
+bool operator==(const EventTracker::Event& lhs, const EventTracker::Event& rhs) {
+ return lhs.type == rhs.type && lhs.clientId == rhs.clientId && lhs.jobId == rhs.jobId;
+}
+
+std::ostream& operator<<(std::ostream& str, const EventTracker::Event& v) {
+ str << EventTracker::toString(v);
+ return str;
+}
+
+struct TestClientCallback : public BnTranscodingClientCallback, public EventTracker {
+ TestClientCallback(int32_t id) : mClientId(id) {
+ ALOGI("TestClientCallback %d Created", mClientId);
+ }
+
+ virtual ~TestClientCallback() { ALOGI("TestClientCallback %d destroyed", mClientId); }
+
+ Status openFileDescriptor(const std::string& in_fileUri, const std::string& in_mode,
+ ::ndk::ScopedFileDescriptor* _aidl_return) override {
+ ALOGD("@@@ openFileDescriptor: %s", in_fileUri.c_str());
+ int fd;
+ if (in_mode == "w" || in_mode == "rw") {
+ int kOpenFlags;
+ if (in_mode == "w") {
+ // Write-only, create file if non-existent, truncate existing file.
+ kOpenFlags = O_WRONLY | O_CREAT | O_TRUNC;
+ } else {
+ // Read-Write, create if non-existent, no truncate (service will truncate if needed)
+ kOpenFlags = O_RDWR | O_CREAT;
+ }
+ // User R+W permission.
+ constexpr int kFileMode = S_IRUSR | S_IWUSR;
+ fd = open(in_fileUri.c_str(), kOpenFlags, kFileMode);
+ } else {
+ fd = open(in_fileUri.c_str(), O_RDONLY);
+ }
+ _aidl_return->set(fd);
+ return Status::ok();
+ }
+
+ Status onTranscodingStarted(int32_t in_jobId) override {
+ append(EventTracker::Start(mClientId, in_jobId));
+ return Status::ok();
+ }
+
+ Status onTranscodingPaused(int32_t in_jobId) override {
+ append(EventTracker::Pause(mClientId, in_jobId));
+ return Status::ok();
+ }
+
+ Status onTranscodingResumed(int32_t in_jobId) override {
+ append(EventTracker::Resume(mClientId, in_jobId));
+ return Status::ok();
+ }
+
+ Status onTranscodingFinished(
+ int32_t in_jobId,
+ const ::aidl::android::media::TranscodingResultParcel& /* in_result */) override {
+ append(Finished(mClientId, in_jobId));
+ return Status::ok();
+ }
+
+ Status onTranscodingFailed(int32_t in_jobId,
+ ::aidl::android::media::TranscodingErrorCode in_errorCode) override {
+ append(Failed(mClientId, in_jobId), in_errorCode);
+ return Status::ok();
+ }
+
+ Status onAwaitNumberOfJobsChanged(int32_t /* in_jobId */, int32_t /* in_oldAwaitNumber */,
+ int32_t /* in_newAwaitNumber */) override {
+ return Status::ok();
+ }
+
+ Status onProgressUpdate(int32_t /* in_jobId */, int32_t in_progress) override {
+ updateProgress(in_progress);
+ return Status::ok();
+ }
+
+ int32_t mClientId;
+};
+
+class MediaTranscodingServiceTestBase : public ::testing::Test {
+public:
+ MediaTranscodingServiceTestBase() { ALOGI("MediaTranscodingServiceTestBase created"); }
+
+ virtual ~MediaTranscodingServiceTestBase() {
+ ALOGI("MediaTranscodingServiceTestBase destroyed");
+ }
+
+ void SetUp() override {
+ // Need thread pool to receive callbacks, otherwise oneway callbacks are
+ // silently ignored.
+ ABinderProcess_startThreadPool();
+ ::ndk::SpAIBinder binder(AServiceManager_getService("media.transcoding"));
+ mService = IMediaTranscodingService::fromBinder(binder);
+ if (mService == nullptr) {
+ ALOGE("Failed to connect to the media.trascoding service.");
+ return;
+ }
+ mClientCallback1 = ::ndk::SharedRefBase::make<TestClientCallback>(CLIENT(1));
+ mClientCallback2 = ::ndk::SharedRefBase::make<TestClientCallback>(CLIENT(2));
+ mClientCallback3 = ::ndk::SharedRefBase::make<TestClientCallback>(CLIENT(3));
+ }
+
+ std::shared_ptr<ITranscodingClient> registerOneClient(
+ const char* packageName, const std::shared_ptr<TestClientCallback>& callback,
+ uid_t defaultUid) {
+ uid_t uid;
+ if (getUidForPackage(String16(packageName), 0 /*userId*/, uid) != NO_ERROR) {
+ uid = defaultUid;
+ }
+
+ ALOGD("registering %s with uid %d", packageName, uid);
+
+ std::shared_ptr<ITranscodingClient> client;
+ Status status = mService->registerClient(callback, kClientName, packageName, uid,
+ kClientUseCallingPid, &client);
+ return status.isOk() ? client : nullptr;
+ }
+
+ void registerMultipleClients() {
+ // Register 3 clients.
+ mClient1 = registerOneClient(kClientPackageA, mClientCallback1, UID(1));
+ EXPECT_TRUE(mClient1 != nullptr);
+
+ mClient2 = registerOneClient(kClientPackageB, mClientCallback2, UID(2));
+ EXPECT_TRUE(mClient2 != nullptr);
+
+ mClient3 = registerOneClient(kClientPackageC, mClientCallback3, UID(3));
+ EXPECT_TRUE(mClient3 != nullptr);
+
+ // Check the number of clients.
+ int32_t numOfClients;
+ Status status = mService->getNumOfClients(&numOfClients);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(3, numOfClients);
+ }
+
+ void unregisterMultipleClients() {
+ Status status;
+
+ // Unregister the clients.
+ status = mClient1->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ status = mClient2->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ status = mClient3->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ // Check the number of clients.
+ int32_t numOfClients;
+ status = mService->getNumOfClients(&numOfClients);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(0, numOfClients);
+ }
+
+ static constexpr bool success = true;
+ static constexpr bool fail = false;
+
+ template <bool expectation = success>
+ bool submit(const std::shared_ptr<ITranscodingClient>& client, int32_t jobId,
+ const char* sourceFilePath, const char* destinationFilePath,
+ TranscodingJobPriority priority = TranscodingJobPriority::kNormal,
+ int bitrateBps = -1) {
+ constexpr bool shouldSucceed = (expectation == success);
+ bool result;
+ TranscodingRequestParcel request;
+ TranscodingJobParcel job;
+
+ request.sourceFilePath = sourceFilePath;
+ request.destinationFilePath = destinationFilePath;
+ request.priority = priority;
+ if (bitrateBps > 0) {
+ request.requestedVideoTrackFormat.emplace(TranscodingVideoTrackFormat());
+ request.requestedVideoTrackFormat->bitrateBps = bitrateBps;
+ }
+ Status status = client->submitRequest(request, &job, &result);
+
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(result, shouldSucceed);
+ if (shouldSucceed) {
+ EXPECT_EQ(job.jobId, jobId);
+ }
+
+ return status.isOk() && (result == shouldSucceed) && (!shouldSucceed || job.jobId == jobId);
+ }
+
+ template <bool expectation = success>
+ bool cancel(const std::shared_ptr<ITranscodingClient>& client, int32_t jobId) {
+ constexpr bool shouldSucceed = (expectation == success);
+ bool result;
+ Status status = client->cancelJob(jobId, &result);
+
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(result, shouldSucceed);
+
+ return status.isOk() && (result == shouldSucceed);
+ }
+
+ template <bool expectation = success>
+ bool getJob(const std::shared_ptr<ITranscodingClient>& client, int32_t jobId,
+ const char* sourceFilePath, const char* destinationFilePath) {
+ constexpr bool shouldSucceed = (expectation == success);
+ bool result;
+ TranscodingJobParcel job;
+ Status status = client->getJobWithId(jobId, &job, &result);
+
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(result, shouldSucceed);
+ if (shouldSucceed) {
+ EXPECT_EQ(job.jobId, jobId);
+ EXPECT_EQ(job.request.sourceFilePath, sourceFilePath);
+ }
+
+ return status.isOk() && (result == shouldSucceed) &&
+ (!shouldSucceed ||
+ (job.jobId == jobId && job.request.sourceFilePath == sourceFilePath &&
+ job.request.destinationFilePath == destinationFilePath));
+ }
+
+ void deleteFile(const char* path) { unlink(path); }
+
+ std::shared_ptr<IMediaTranscodingService> mService;
+ std::shared_ptr<TestClientCallback> mClientCallback1;
+ std::shared_ptr<TestClientCallback> mClientCallback2;
+ std::shared_ptr<TestClientCallback> mClientCallback3;
+ std::shared_ptr<ITranscodingClient> mClient1;
+ std::shared_ptr<ITranscodingClient> mClient2;
+ std::shared_ptr<ITranscodingClient> mClient3;
+ const char* mTestName;
+};
+
+} // namespace media
+} // namespace android
diff --git a/services/mediatranscoding/tests/README.txt b/services/mediatranscoding/tests/README.txt
new file mode 100644
index 0000000..cde465e
--- /dev/null
+++ b/services/mediatranscoding/tests/README.txt
@@ -0,0 +1,8 @@
+mediatranscodingservice_simulated_tests:
+ Tests media transcoding service with simulated transcoder.
+
+mediatranscodingservice_real_tests:
+ Tests media transcoding service with real transcoder. Uses the same test assets
+ as the MediaTranscoder unit tests. Before running the test, please make sure
+ to push the test assets to /sdcard:
+ adb push $TOP/frameworks/av/media/libmediatranscoding/tests/assets /data/local/tmp/TranscodingTestAssets
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/Android.bp b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/Android.bp
new file mode 100644
index 0000000..95a94fc
--- /dev/null
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/Android.bp
@@ -0,0 +1,23 @@
+android_test_helper_app {
+ name: "TranscodingUidPolicy_TestAppA",
+ manifest: "TestAppA.xml",
+ static_libs: ["androidx.test.rules"],
+ sdk_version: "test_current",
+ srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+ name: "TranscodingUidPolicy_TestAppB",
+ manifest: "TestAppB.xml",
+ static_libs: ["androidx.test.rules"],
+ sdk_version: "test_current",
+ srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+ name: "TranscodingUidPolicy_TestAppC",
+ manifest: "TestAppC.xml",
+ static_libs: ["androidx.test.rules"],
+ sdk_version: "test_current",
+ srcs: ["src/**/*.java"],
+}
\ No newline at end of file
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
new file mode 100644
index 0000000..91c14a5
--- /dev/null
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.transcoding.testapp.A"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application android:label="TestAppA">
+ <activity android:name="com.android.tests.transcoding.MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppB.xml b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppB.xml
new file mode 100644
index 0000000..4baa35a
--- /dev/null
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppB.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.transcoding.testapp.B"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application android:label="TestAppB">
+ <activity android:name="com.android.tests.transcoding.MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppC.xml b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppC.xml
new file mode 100644
index 0000000..3dde3af
--- /dev/null
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppC.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.transcoding.testapp.C"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application android:label="TestAppC">
+ <activity android:name="com.android.tests.transcoding.MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java
new file mode 100644
index 0000000..7295073
--- /dev/null
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java
@@ -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.
+ */
+package com.android.tests.transcoding;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * This is an empty activity for testing the UID policy of media transcoding service.
+ */
+public class MainActivity extends Activity {
+ private static final String TAG = "MainActivity";
+
+ // Called at the start of the full lifetime.
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Initialize Activity and inflate the UI.
+ }
+
+ // Called after onCreate has finished, use to restore UI state
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ // Restore UI state from the savedInstanceState.
+ // This bundle has also been passed to onCreate.
+ // Will only be called if the Activity has been
+ // killed by the system since it was last visible.
+ }
+
+ // Called before subsequent visible lifetimes
+ // for an activity process.
+ @Override
+ public void onRestart(){
+ super.onRestart();
+ // Load changes knowing that the Activity has already
+ // been visible within this process.
+ }
+
+ // Called at the start of the visible lifetime.
+ @Override
+ public void onStart(){
+ super.onStart();
+ // Apply any required UI change now that the Activity is visible.
+ }
+
+ // Called at the start of the active lifetime.
+ @Override
+ public void onResume(){
+ super.onResume();
+ // Resume any paused UI updates, threads, or processes required
+ // by the Activity but suspended when it was inactive.
+ }
+
+ // Called to save UI state changes at the
+ // end of the active lifecycle.
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ // Save UI state changes to the savedInstanceState.
+ // This bundle will be passed to onCreate and
+ // onRestoreInstanceState if the process is
+ // killed and restarted by the run time.
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ // Called at the end of the active lifetime.
+ @Override
+ public void onPause(){
+ // Suspend UI updates, threads, or CPU intensive processes
+ // that don't need to be updated when the Activity isn't
+ // the active foreground Activity.
+ super.onPause();
+ }
+
+ // Called at the end of the visible lifetime.
+ @Override
+ public void onStop(){
+ // Suspend remaining UI updates, threads, or processing
+ // that aren't required when the Activity isn't visible.
+ // Persist all edits or state changes
+ // as after this call the process is likely to be killed.
+ super.onStop();
+ }
+
+ // Sometimes called at the end of the full lifetime.
+ @Override
+ public void onDestroy(){
+ // Clean up any resources including ending threads,
+ // closing database connections etc.
+ super.onDestroy();
+ }
+
+}
diff --git a/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh b/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
old mode 100644
new mode 100755
index bcdc7f7..d66b340
--- a/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -13,11 +13,30 @@
mm
-echo "waiting for device"
+# Push the files onto the device.
+. $ANDROID_BUILD_TOP/frameworks/av/media/libmediatranscoding/tests/assets/push_assets.sh
-adb root && adb wait-for-device remount && adb sync
+echo "[==========] installing test apps"
+adb root
+adb install -t -r -g -d $ANDROID_TARGET_OUT_TESTCASES/TranscodingUidPolicy_TestAppA/arm64/TranscodingUidPolicy_TestAppA.apk
+adb install -t -r -g -d $ANDROID_TARGET_OUT_TESTCASES/TranscodingUidPolicy_TestAppB/arm64/TranscodingUidPolicy_TestAppB.apk
+adb install -t -r -g -d $ANDROID_TARGET_OUT_TESTCASES/TranscodingUidPolicy_TestAppC/arm64/TranscodingUidPolicy_TestAppC.apk
-echo "========================================"
+echo "[==========] waiting for device and sync"
+adb wait-for-device remount && adb sync
-echo "testing mediatranscodingservice"
-adb shell /data/nativetest64/mediatranscodingservice_tests/mediatranscodingservice_tests
+echo "[==========] running simulated tests"
+adb shell setprop debug.transcoding.simulated_transcoder true
+adb shell kill -9 `pid media.transcoding`
+#adb shell /data/nativetest64/mediatranscodingservice_simulated_tests/mediatranscodingservice_simulated_tests
+adb shell /data/nativetest/mediatranscodingservice_simulated_tests/mediatranscodingservice_simulated_tests
+
+echo "[==========] running real tests"
+adb shell setprop debug.transcoding.simulated_transcoder false
+adb shell kill -9 `pid media.transcoding`
+#adb shell /data/nativetest64/mediatranscodingservice_real_tests/mediatranscodingservice_real_tests
+adb shell /data/nativetest/mediatranscodingservice_real_tests/mediatranscodingservice_real_tests
+
+echo "[==========] removing debug properties"
+adb shell setprop debug.transcoding.simulated_transcoder \"\"
+adb shell kill -9 `pid media.transcoding`
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
new file mode 100644
index 0000000..1def4b9
--- /dev/null
+++ b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Unit Test for MediaTranscodingService.
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscodingServiceRealTest"
+
+#include "MediaTranscodingServiceTestHelper.h"
+
+/*
+ * Tests media transcoding service with real transcoder.
+ *
+ * Uses the same test assets as the MediaTranscoder unit tests. Before running the test,
+ * please make sure to push the test assets to /sdcard:
+ *
+ * adb push $TOP/frameworks/av/media/libmediatranscoding/transcoder/tests/assets /data/local/tmp/TranscodingTestAssets
+ */
+namespace android {
+
+namespace media {
+
+constexpr int64_t kPaddingUs = 400000;
+constexpr int64_t kJobWithPaddingUs = 10000000 + kPaddingUs;
+constexpr int32_t kBitRate = 8 * 1000 * 1000; // 8Mbs
+
+constexpr const char* kShortSrcPath =
+ "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+constexpr const char* kLongSrcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+
+#define OUTPATH(name) "/data/local/tmp/MediaTranscodingService_" #name ".MP4"
+
+class MediaTranscodingServiceRealTest : public MediaTranscodingServiceTestBase {
+public:
+ MediaTranscodingServiceRealTest() {}
+
+ void deleteFile(const char* path) { unlink(path); }
+};
+
+TEST_F(MediaTranscodingServiceRealTest, TestInvalidSource) {
+ registerMultipleClients();
+
+ const char* srcPath = "bad_file_uri";
+ const char* dstPath = OUTPATH(TestInvalidSource);
+ deleteFile(dstPath);
+
+ // Submit one job.
+ EXPECT_TRUE(submit(mClient1, 0, srcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Check expected error.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Failed(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->getLastError(), TranscodingErrorCode::kErrorIO);
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceRealTest, TestPassthru) {
+ registerMultipleClients();
+
+ const char* dstPath = OUTPATH(TestPassthru);
+ deleteFile(dstPath);
+
+ // Submit one job.
+ EXPECT_TRUE(submit(mClient1, 0, kShortSrcPath, dstPath));
+
+ // Wait for job to finish.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceRealTest, TestTranscodeVideo) {
+ registerMultipleClients();
+
+ const char* dstPath = OUTPATH(TestTranscodeVideo);
+ deleteFile(dstPath);
+
+ // Submit one job.
+ EXPECT_TRUE(
+ submit(mClient1, 0, kShortSrcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Wait for job to finish.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceRealTest, TestTranscodeVideoProgress) {
+ registerMultipleClients();
+
+ const char* dstPath = OUTPATH(TestTranscodeVideoProgress);
+ deleteFile(dstPath);
+
+ // Submit one job.
+ EXPECT_TRUE(
+ submit(mClient1, 0, kLongSrcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Wait for job to finish.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+
+ // Check the progress update messages are received. For this clip (around ~15 second long),
+ // expect at least 10 updates, and the last update should be 100.
+ int lastProgress;
+ EXPECT_GE(mClientCallback1->getUpdateCount(&lastProgress), 10);
+ EXPECT_EQ(lastProgress, 100);
+
+ unregisterMultipleClients();
+}
+
+/*
+ * Test cancel immediately after start.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestCancelImmediately) {
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* srcPath1 = kShortSrcPath;
+ const char* dstPath0 = OUTPATH(TestCancelImmediately_Job0);
+ const char* dstPath1 = OUTPATH(TestCancelImmediately_Job1);
+
+ deleteFile(dstPath0);
+ deleteFile(dstPath1);
+ // Submit one job, should start immediately.
+ EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+ // Test cancel job immediately, getJob should fail after cancel.
+ EXPECT_TRUE(cancel(mClient1, 0));
+ EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+
+ // Submit new job, new job should start immediately and finish.
+ EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
+
+ unregisterMultipleClients();
+}
+
+/*
+ * Test cancel in the middle of transcoding.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestCancelWhileRunning) {
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* srcPath1 = kShortSrcPath;
+ const char* dstPath0 = OUTPATH(TestCancelWhileRunning_Job0);
+ const char* dstPath1 = OUTPATH(TestCancelWhileRunning_Job1);
+
+ deleteFile(dstPath0);
+ deleteFile(dstPath1);
+ // Submit two jobs, job 0 should start immediately, job 1 should be queued.
+ EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+ EXPECT_TRUE(getJob(mClient1, 1, srcPath1, dstPath1));
+
+ // Job 0 (longtest) shouldn't finish in 1 seconds.
+ EXPECT_EQ(mClientCallback1->pop(1000000), EventTracker::NoEvent);
+
+ // Now cancel job 0. Job 1 should start immediately and finish.
+ EXPECT_TRUE(cancel(mClient1, 0));
+ EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceRealTest, TestPauseResumeSingleClient) {
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* srcPath1 = kShortSrcPath;
+ const char* dstPath0 = OUTPATH(TestPauseResumeSingleClient_Job0);
+ const char* dstPath1 = OUTPATH(TestPauseResumeSingleClient_Job1);
+ deleteFile(dstPath0);
+ deleteFile(dstPath1);
+
+ // Submit one offline job, should start immediately.
+ EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kUnspecified,
+ kBitRate));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ // Test get job after starts.
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+ // Submit one realtime job.
+ EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Offline job should pause.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Pause(CLIENT(1), 0));
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+ // Realtime job should start immediately, and run to finish.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
+
+ // Test get job after finish fails.
+ EXPECT_TRUE(getJob<fail>(mClient1, 1, "", ""));
+
+ // Then offline job should resume.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Resume(CLIENT(1), 0));
+ // Test get job after resume.
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+ // Offline job should finish.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+ // Test get job after finish fails.
+ EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+
+ unregisterMultipleClients();
+}
+
+/*
+ * Basic test for pause/resume with two clients, with one job each.
+ * Top app's job should preempt the other app's job.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestPauseResumeMultiClients) {
+ ALOGD("TestPauseResumeMultiClients starting...");
+
+ EXPECT_TRUE(ShellHelper::RunCmd("input keyevent KEYCODE_WAKEUP"));
+ EXPECT_TRUE(ShellHelper::RunCmd("wm dismiss-keyguard"));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageB));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageC));
+
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* srcPath1 = kShortSrcPath;
+ const char* dstPath0 = OUTPATH(TestPauseResumeMultiClients_Client0);
+ const char* dstPath1 = OUTPATH(TestPauseResumeMultiClients_Client1);
+ deleteFile(dstPath0);
+ deleteFile(dstPath1);
+
+ ALOGD("Moving app A to top...");
+ EXPECT_TRUE(ShellHelper::Start(kClientPackageA, kTestActivityName));
+
+ // Submit job to Client1.
+ ALOGD("Submitting job to client1 (app A) ...");
+ EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Client1's job should start immediately.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+
+ ALOGD("Moving app B to top...");
+ EXPECT_TRUE(ShellHelper::Start(kClientPackageB, kTestActivityName));
+
+ // Client1's job should continue to run, since Client2 (app B) doesn't have any job.
+ EXPECT_EQ(mClientCallback1->pop(1000000), EventTracker::NoEvent);
+
+ // Submit job to Client2.
+ ALOGD("Submitting job to client2 (app B) ...");
+ EXPECT_TRUE(submit(mClient2, 0, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Client1's job should pause, client2's job should start.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Pause(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback2->pop(kPaddingUs), EventTracker::Start(CLIENT(2), 0));
+
+ // Client2's job should finish, then Client1's job should resume.
+ EXPECT_EQ(mClientCallback2->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(2), 0));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Resume(CLIENT(1), 0));
+
+ // Client1's job should finish.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+
+ unregisterMultipleClients();
+
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageB));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageC));
+
+ ALOGD("TestPauseResumeMultiClients finished.");
+}
+
+} // namespace media
+} // namespace android
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
new file mode 100644
index 0000000..42b5877
--- /dev/null
+++ b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Unit Test for MediaTranscodingService.
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscodingServiceSimulatedTest"
+
+#include <aidl/android/media/BnTranscodingClientCallback.h>
+#include <aidl/android/media/IMediaTranscodingService.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <aidl/android/media/TranscodingJobParcel.h>
+#include <aidl/android/media/TranscodingJobPriority.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <binder/PermissionController.h>
+#include <cutils/multiuser.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <iostream>
+#include <list>
+
+#include "MediaTranscodingServiceTestHelper.h"
+#include "SimulatedTranscoder.h"
+
+namespace android {
+
+namespace media {
+
+// Note that -1 is valid and means using calling pid/uid for the service. But only privilege caller could
+// use them. This test is not a privilege caller.
+constexpr int32_t kInvalidClientPid = -5;
+constexpr const char* kInvalidClientName = "";
+constexpr const char* kInvalidClientOpPackageName = "";
+
+constexpr int32_t kClientUseCallingUid = IMediaTranscodingService::USE_CALLING_UID;
+
+constexpr int64_t kPaddingUs = 1000000;
+constexpr int64_t kJobWithPaddingUs = SimulatedTranscoder::kJobDurationUs + kPaddingUs;
+
+constexpr const char* kClientOpPackageName = "TestClientPackage";
+
+class MediaTranscodingServiceSimulatedTest : public MediaTranscodingServiceTestBase {
+public:
+ MediaTranscodingServiceSimulatedTest() {}
+};
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterNullClient) {
+ std::shared_ptr<ITranscodingClient> client;
+
+ // Register the client with null callback.
+ Status status = mService->registerClient(nullptr, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
+ EXPECT_FALSE(status.isOk());
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterClientWithInvalidClientPid) {
+ std::shared_ptr<ITranscodingClient> client;
+
+ // Register the client with the service.
+ Status status = mService->registerClient(mClientCallback1, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kInvalidClientPid, &client);
+ EXPECT_FALSE(status.isOk());
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterClientWithInvalidClientName) {
+ std::shared_ptr<ITranscodingClient> client;
+
+ // Register the client with the service.
+ Status status = mService->registerClient(mClientCallback1, kInvalidClientName,
+ kInvalidClientOpPackageName, kClientUseCallingUid,
+ kClientUseCallingPid, &client);
+ EXPECT_FALSE(status.isOk());
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterClientWithInvalidClientPackageName) {
+ std::shared_ptr<ITranscodingClient> client;
+
+ // Register the client with the service.
+ Status status =
+ mService->registerClient(mClientCallback1, kClientName, kInvalidClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
+ EXPECT_FALSE(status.isOk());
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterOneClient) {
+ std::shared_ptr<ITranscodingClient> client;
+
+ Status status = mService->registerClient(mClientCallback1, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
+ EXPECT_TRUE(status.isOk());
+
+ // Validate the client.
+ EXPECT_TRUE(client != nullptr);
+
+ // Check the number of Clients.
+ int32_t numOfClients;
+ status = mService->getNumOfClients(&numOfClients);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(1, numOfClients);
+
+ // Unregister the client.
+ status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ // Check the number of Clients.
+ status = mService->getNumOfClients(&numOfClients);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(0, numOfClients);
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterClientTwice) {
+ std::shared_ptr<ITranscodingClient> client;
+
+ Status status = mService->registerClient(mClientCallback1, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
+ EXPECT_TRUE(status.isOk());
+
+ // Validate the client.
+ EXPECT_TRUE(client != nullptr);
+
+ // Register the client again and expects failure.
+ std::shared_ptr<ITranscodingClient> client1;
+ status = mService->registerClient(mClientCallback1, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client1);
+ EXPECT_FALSE(status.isOk());
+
+ // Unregister the client.
+ status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterMultipleClients) {
+ registerMultipleClients();
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestJobIdIndependence) {
+ registerMultipleClients();
+
+ // Submit 2 requests on client1 first.
+ EXPECT_TRUE(submit(mClient1, 0, "test_source_file", "test_destination_file"));
+ EXPECT_TRUE(submit(mClient1, 1, "test_source_file", "test_destination_file"));
+
+ // Submit 2 requests on client2, jobId should be independent for each client.
+ EXPECT_TRUE(submit(mClient2, 0, "test_source_file", "test_destination_file"));
+ EXPECT_TRUE(submit(mClient2, 1, "test_source_file", "test_destination_file"));
+
+ // Cancel all jobs.
+ EXPECT_TRUE(cancel(mClient1, 0));
+ EXPECT_TRUE(cancel(mClient1, 1));
+ EXPECT_TRUE(cancel(mClient2, 0));
+ EXPECT_TRUE(cancel(mClient2, 1));
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestSubmitCancelJobs) {
+ registerMultipleClients();
+
+ // Test jobId assignment.
+ EXPECT_TRUE(submit(mClient1, 0, "test_source_file_0", "test_destination_file"));
+ EXPECT_TRUE(submit(mClient1, 1, "test_source_file_1", "test_destination_file"));
+ EXPECT_TRUE(submit(mClient1, 2, "test_source_file_2", "test_destination_file"));
+
+ // Test submit bad request (no valid sourceFilePath) fails.
+ EXPECT_TRUE(submit<fail>(mClient1, 0, "", ""));
+
+ // Test cancel non-existent job fails.
+ EXPECT_TRUE(cancel<fail>(mClient1, 100));
+
+ // Job 0 should start immediately and finish in 2 seconds, followed by Job 1 start.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+
+ // Test cancel valid jobId in random order.
+ // Test cancel finished job fails.
+ EXPECT_TRUE(cancel(mClient1, 2));
+ EXPECT_TRUE(cancel<fail>(mClient1, 0));
+ EXPECT_TRUE(cancel(mClient1, 1));
+
+ // Test cancel job again fails.
+ EXPECT_TRUE(cancel<fail>(mClient1, 1));
+
+ // Test no more events arriving after cancel.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::NoEvent);
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestGetJobs) {
+ registerMultipleClients();
+
+ // Submit 3 requests.
+ EXPECT_TRUE(submit(mClient1, 0, "test_source_file_0", "test_destination_file_0"));
+ EXPECT_TRUE(submit(mClient1, 1, "test_source_file_1", "test_destination_file_1"));
+ EXPECT_TRUE(submit(mClient1, 2, "test_source_file_2", "test_destination_file_2"));
+
+ // Test get jobs by id.
+ EXPECT_TRUE(getJob(mClient1, 2, "test_source_file_2", "test_destination_file_2"));
+ EXPECT_TRUE(getJob(mClient1, 1, "test_source_file_1", "test_destination_file_1"));
+ EXPECT_TRUE(getJob(mClient1, 0, "test_source_file_0", "test_destination_file_0"));
+
+ // Test get job by invalid id fails.
+ EXPECT_TRUE(getJob<fail>(mClient1, 100, "", ""));
+ EXPECT_TRUE(getJob<fail>(mClient1, -1, "", ""));
+
+ // Test get job after cancel fails.
+ EXPECT_TRUE(cancel(mClient1, 2));
+ EXPECT_TRUE(getJob<fail>(mClient1, 2, "", ""));
+
+ // Job 0 should start immediately and finish in 2 seconds, followed by Job 1 start.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+
+ // Test get job after finish fails.
+ EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+
+ // Test get the remaining job 1.
+ EXPECT_TRUE(getJob(mClient1, 1, "test_source_file_1", "test_destination_file_1"));
+
+ // Cancel remaining job 1.
+ EXPECT_TRUE(cancel(mClient1, 1));
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestSubmitCancelWithOfflineJobs) {
+ registerMultipleClients();
+
+ // Submit some offline jobs first.
+ EXPECT_TRUE(submit(mClient1, 0, "test_source_file_0", "test_destination_file_0",
+ TranscodingJobPriority::kUnspecified));
+ EXPECT_TRUE(submit(mClient1, 1, "test_source_file_1", "test_destination_file_1",
+ TranscodingJobPriority::kUnspecified));
+
+ // Job 0 should start immediately.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+
+ // Submit more real-time jobs.
+ EXPECT_TRUE(submit(mClient1, 2, "test_source_file_2", "test_destination_file_2"));
+ EXPECT_TRUE(submit(mClient1, 3, "test_source_file_3", "test_destination_file_3"));
+
+ // Job 0 should pause immediately and job 2 should start.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Pause(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 2));
+
+ // Job 2 should finish in 2 seconds and job 3 should start.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 2));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 3));
+
+ // Cancel job 3 now
+ EXPECT_TRUE(cancel(mClient1, 3));
+
+ // Job 0 should resume and finish in 2 seconds, followed by job 1 start.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Resume(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+
+ // Cancel remaining job 1.
+ EXPECT_TRUE(cancel(mClient1, 1));
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestClientUseAfterUnregister) {
+ std::shared_ptr<ITranscodingClient> client;
+
+ // Register a client, then unregister.
+ Status status = mService->registerClient(mClientCallback1, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
+ EXPECT_TRUE(status.isOk());
+
+ status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ // Test various operations on the client, should fail with ERROR_DISCONNECTED.
+ TranscodingJobParcel job;
+ bool result;
+ status = client->getJobWithId(0, &job, &result);
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+
+ status = client->cancelJob(0, &result);
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+
+ TranscodingRequestParcel request;
+ status = client->submitRequest(request, &job, &result);
+ EXPECT_EQ(status.getServiceSpecificError(), IMediaTranscodingService::ERROR_DISCONNECTED);
+}
+
+TEST_F(MediaTranscodingServiceSimulatedTest, TestTranscodingUidPolicy) {
+ ALOGD("TestTranscodingUidPolicy starting...");
+
+ EXPECT_TRUE(ShellHelper::RunCmd("input keyevent KEYCODE_WAKEUP"));
+ EXPECT_TRUE(ShellHelper::RunCmd("wm dismiss-keyguard"));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageB));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageC));
+
+ registerMultipleClients();
+
+ ALOGD("Moving app A to top...");
+ EXPECT_TRUE(ShellHelper::Start(kClientPackageA, kTestActivityName));
+
+ // Submit 3 requests.
+ ALOGD("Submitting job to client1 (app A) ...");
+ EXPECT_TRUE(submit(mClient1, 0, "test_source_file_0", "test_destination_file_0"));
+ EXPECT_TRUE(submit(mClient1, 1, "test_source_file_1", "test_destination_file_1"));
+ EXPECT_TRUE(submit(mClient1, 2, "test_source_file_2", "test_destination_file_2"));
+
+ // Job 0 should start immediately.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+
+ ALOGD("Moving app B to top...");
+ EXPECT_TRUE(ShellHelper::Start(kClientPackageB, kTestActivityName));
+
+ // Job 0 should continue and finish in 2 seconds, then job 1 should start.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+
+ ALOGD("Submitting job to client2 (app B) ...");
+ EXPECT_TRUE(submit(mClient2, 0, "test_source_file_0", "test_destination_file_0"));
+
+ // Client1's job should pause, client2's job should start.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Pause(CLIENT(1), 1));
+ EXPECT_EQ(mClientCallback2->pop(kPaddingUs), EventTracker::Start(CLIENT(2), 0));
+
+ ALOGD("Moving app A back to top...");
+ EXPECT_TRUE(ShellHelper::Start(kClientPackageA, kTestActivityName));
+
+ // Client2's job should pause, client1's job 1 should resume.
+ EXPECT_EQ(mClientCallback2->pop(kPaddingUs), EventTracker::Pause(CLIENT(2), 0));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Resume(CLIENT(1), 1));
+
+ // Client2's job 1 should finish in 2 seconds, then its job 2 should start.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 2));
+
+ // After client2's jobs finish, client1's job should resume.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 2));
+ EXPECT_EQ(mClientCallback2->pop(kPaddingUs), EventTracker::Resume(CLIENT(2), 0));
+
+ unregisterMultipleClients();
+
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageB));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageC));
+
+ ALOGD("TestTranscodingUidPolicy finished.");
+}
+
+} // namespace media
+} // namespace android
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp
deleted file mode 100644
index 5a791fe..0000000
--- a/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Unit Test for MediaTranscoding Service.
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaTranscodingServiceTest"
-
-#include <aidl/android/media/BnTranscodingServiceClient.h>
-#include <aidl/android/media/IMediaTranscodingService.h>
-#include <aidl/android/media/ITranscodingServiceClient.h>
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
-#include <android/binder_ibinder_jni.h>
-#include <android/binder_manager.h>
-#include <android/binder_process.h>
-#include <cutils/ashmem.h>
-#include <gtest/gtest.h>
-#include <stdlib.h>
-#include <sys/mman.h>
-#include <utils/Log.h>
-
-namespace android {
-
-namespace media {
-
-using Status = ::ndk::ScopedAStatus;
-using aidl::android::media::BnTranscodingServiceClient;
-using aidl::android::media::IMediaTranscodingService;
-using aidl::android::media::ITranscodingServiceClient;
-
-constexpr int32_t kInvalidClientId = -5;
-
-// Note that -1 is valid and means using calling pid/uid for the service. But only privilege caller could
-// use them. This test is not a privilege caller.
-constexpr int32_t kInvalidClientPid = -5;
-constexpr int32_t kInvalidClientUid = -5;
-constexpr const char* kInvalidClientOpPackageName = "";
-
-constexpr int32_t kClientUseCallingPid = -1;
-constexpr int32_t kClientUseCallingUid = -1;
-constexpr const char* kClientOpPackageName = "TestClient";
-
-class MediaTranscodingServiceTest : public ::testing::Test {
-public:
- MediaTranscodingServiceTest() { ALOGD("MediaTranscodingServiceTest created"); }
-
- void SetUp() override {
- ::ndk::SpAIBinder binder(AServiceManager_getService("media.transcoding"));
- mService = IMediaTranscodingService::fromBinder(binder);
- if (mService == nullptr) {
- ALOGE("Failed to connect to the media.trascoding service.");
- return;
- }
- }
-
- ~MediaTranscodingServiceTest() { ALOGD("MediaTranscodingingServiceTest destroyed"); }
-
- std::shared_ptr<IMediaTranscodingService> mService = nullptr;
-};
-
-struct TestClient : public BnTranscodingServiceClient {
- TestClient(const std::shared_ptr<IMediaTranscodingService>& service) : mService(service) {
- ALOGD("TestClient Created");
- }
-
- Status getName(std::string* _aidl_return) override {
- *_aidl_return = "test_client";
- return Status::ok();
- }
-
- Status onTranscodingFinished(
- int32_t /* in_jobId */,
- const ::aidl::android::media::TranscodingResultParcel& /* in_result */) override {
- return Status::ok();
- }
-
- Status onTranscodingFailed(
- int32_t /* in_jobId */,
- ::aidl::android::media::TranscodingErrorCode /*in_errorCode */) override {
- return Status::ok();
- }
-
- Status onAwaitNumberOfJobsChanged(int32_t /* in_jobId */, int32_t /* in_oldAwaitNumber */,
- int32_t /* in_newAwaitNumber */) override {
- return Status::ok();
- }
-
- Status onProgressUpdate(int32_t /* in_jobId */, int32_t /* in_progress */) override {
- return Status::ok();
- }
-
- virtual ~TestClient() { ALOGI("TestClient destroyed"); };
-
-private:
- std::shared_ptr<IMediaTranscodingService> mService;
-};
-
-TEST_F(MediaTranscodingServiceTest, TestRegisterNullClient) {
- std::shared_ptr<ITranscodingServiceClient> client = nullptr;
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kClientUseCallingPid, &clientId);
- EXPECT_FALSE(status.isOk());
-}
-
-TEST_F(MediaTranscodingServiceTest, TestRegisterClientWithInvalidClientPid) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
-
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kInvalidClientPid, &clientId);
- EXPECT_FALSE(status.isOk());
-}
-
-TEST_F(MediaTranscodingServiceTest, TestRegisterClientWithInvalidClientUid) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
-
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kInvalidClientUid,
- kClientUseCallingPid, &clientId);
- EXPECT_FALSE(status.isOk());
-}
-
-TEST_F(MediaTranscodingServiceTest, TestRegisterClientWithInvalidClientPackageName) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
-
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kInvalidClientOpPackageName,
- kClientUseCallingUid, kClientUseCallingPid, &clientId);
- EXPECT_FALSE(status.isOk());
-}
-
-TEST_F(MediaTranscodingServiceTest, TestRegisterOneClient) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
-
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingPid,
- kClientUseCallingUid, &clientId);
- ALOGD("client id is %d", clientId);
- EXPECT_TRUE(status.isOk());
-
- // Validate the clientId.
- EXPECT_TRUE(clientId > 0);
-
- // Check the number of Clients.
- int32_t numOfClients;
- status = mService->getNumOfClients(&numOfClients);
- EXPECT_TRUE(status.isOk());
- EXPECT_EQ(1, numOfClients);
-
- // Unregister the client.
- bool res;
- status = mService->unregisterClient(clientId, &res);
- EXPECT_TRUE(status.isOk());
- EXPECT_TRUE(res);
-}
-
-TEST_F(MediaTranscodingServiceTest, TestUnRegisterClientWithInvalidClientId) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
-
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kClientUseCallingPid, &clientId);
- ALOGD("client id is %d", clientId);
- EXPECT_TRUE(status.isOk());
-
- // Validate the clientId.
- EXPECT_TRUE(clientId > 0);
-
- // Check the number of Clients.
- int32_t numOfClients;
- status = mService->getNumOfClients(&numOfClients);
- EXPECT_TRUE(status.isOk());
- EXPECT_EQ(1, numOfClients);
-
- // Unregister the client with invalid ID
- bool res;
- mService->unregisterClient(kInvalidClientId, &res);
- EXPECT_FALSE(res);
-
- // Unregister the valid client.
- mService->unregisterClient(clientId, &res);
-}
-
-TEST_F(MediaTranscodingServiceTest, TestRegisterClientTwice) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
-
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kClientUseCallingPid, &clientId);
- EXPECT_TRUE(status.isOk());
-
- // Validate the clientId.
- EXPECT_TRUE(clientId > 0);
-
- // Register the client again and expects failure.
- status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kClientUseCallingPid, &clientId);
- EXPECT_FALSE(status.isOk());
-
- // Unregister the valid client.
- bool res;
- mService->unregisterClient(clientId, &res);
-}
-
-} // namespace media
-} // namespace android
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/AAudioMixer.cpp b/services/oboeservice/AAudioMixer.cpp
index 1c03b7f..c5d40b8 100644
--- a/services/oboeservice/AAudioMixer.cpp
+++ b/services/oboeservice/AAudioMixer.cpp
@@ -33,25 +33,21 @@
using android::FifoBuffer;
using android::fifo_frames_t;
-AAudioMixer::~AAudioMixer() {
- delete[] mOutputBuffer;
-}
-
void AAudioMixer::allocate(int32_t samplesPerFrame, int32_t framesPerBurst) {
mSamplesPerFrame = samplesPerFrame;
mFramesPerBurst = framesPerBurst;
int32_t samplesPerBuffer = samplesPerFrame * framesPerBurst;
- mOutputBuffer = new float[samplesPerBuffer];
+ mOutputBuffer = std::make_unique<float[]>(samplesPerBuffer);
mBufferSizeInBytes = samplesPerBuffer * sizeof(float);
}
void AAudioMixer::clear() {
- memset(mOutputBuffer, 0, mBufferSizeInBytes);
+ memset(mOutputBuffer.get(), 0, mBufferSizeInBytes);
}
int32_t AAudioMixer::mix(int streamIndex, FifoBuffer *fifo, bool allowUnderflow) {
WrappingBuffer wrappingBuffer;
- float *destination = mOutputBuffer;
+ float *destination = mOutputBuffer.get();
#if AAUDIO_MIXER_ATRACE_ENABLED
ATRACE_BEGIN("aaMix");
@@ -117,5 +113,5 @@
}
float *AAudioMixer::getOutputBuffer() {
- return mOutputBuffer;
+ return mOutputBuffer.get();
}
diff --git a/services/oboeservice/AAudioMixer.h b/services/oboeservice/AAudioMixer.h
index d5abc5b..dd466ac 100644
--- a/services/oboeservice/AAudioMixer.h
+++ b/services/oboeservice/AAudioMixer.h
@@ -25,7 +25,6 @@
class AAudioMixer {
public:
AAudioMixer() {}
- ~AAudioMixer();
void allocate(int32_t samplesPerFrame, int32_t framesPerBurst);
@@ -47,7 +46,7 @@
private:
void mixPart(float *destination, float *source, int32_t numFrames);
- float *mOutputBuffer = nullptr;
+ std::unique_ptr<float[]> mOutputBuffer;
int32_t mSamplesPerFrame = 0;
int32_t mFramesPerBurst = 0;
int32_t mBufferSizeInBytes = 0;
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/AAudioServiceEndpointCapture.cpp b/services/oboeservice/AAudioServiceEndpointCapture.cpp
index 37d105b..c603e4e 100644
--- a/services/oboeservice/AAudioServiceEndpointCapture.cpp
+++ b/services/oboeservice/AAudioServiceEndpointCapture.cpp
@@ -40,17 +40,12 @@
mStreamInternal = &mStreamInternalCapture;
}
-AAudioServiceEndpointCapture::~AAudioServiceEndpointCapture() {
- delete mDistributionBuffer;
-}
-
aaudio_result_t AAudioServiceEndpointCapture::open(const aaudio::AAudioStreamRequest &request) {
aaudio_result_t result = AAudioServiceEndpointShared::open(request);
if (result == AAUDIO_OK) {
- delete mDistributionBuffer;
int distributionBufferSizeBytes = getStreamInternal()->getFramesPerBurst()
* getStreamInternal()->getBytesPerFrame();
- mDistributionBuffer = new uint8_t[distributionBufferSizeBytes];
+ mDistributionBuffer = std::make_unique<uint8_t[]>(distributionBufferSizeBytes);
}
return result;
}
@@ -67,7 +62,8 @@
int64_t mmapFramesRead = getStreamInternal()->getFramesRead();
// Read audio data from stream using a blocking read.
- result = getStreamInternal()->read(mDistributionBuffer, getFramesPerBurst(), timeoutNanos);
+ result = getStreamInternal()->read(mDistributionBuffer.get(),
+ getFramesPerBurst(), timeoutNanos);
if (result == AAUDIO_ERROR_DISCONNECTED) {
disconnectRegisteredStreams();
break;
@@ -107,7 +103,7 @@
getFramesPerBurst()) {
streamShared->incrementXRunCount();
} else {
- fifo->write(mDistributionBuffer, getFramesPerBurst());
+ fifo->write(mDistributionBuffer.get(), getFramesPerBurst());
}
clientFramesWritten = fifo->getWriteCounter();
}
diff --git a/services/oboeservice/AAudioServiceEndpointCapture.h b/services/oboeservice/AAudioServiceEndpointCapture.h
index 971da9a..ae5a189 100644
--- a/services/oboeservice/AAudioServiceEndpointCapture.h
+++ b/services/oboeservice/AAudioServiceEndpointCapture.h
@@ -17,6 +17,8 @@
#ifndef AAUDIO_SERVICE_ENDPOINT_CAPTURE_H
#define AAUDIO_SERVICE_ENDPOINT_CAPTURE_H
+#include <memory>
+
#include "client/AudioStreamInternal.h"
#include "client/AudioStreamInternalCapture.h"
@@ -28,16 +30,15 @@
class AAudioServiceEndpointCapture : public AAudioServiceEndpointShared {
public:
explicit AAudioServiceEndpointCapture(android::AAudioService &audioService);
- virtual ~AAudioServiceEndpointCapture();
+ virtual ~AAudioServiceEndpointCapture() = default;
aaudio_result_t open(const aaudio::AAudioStreamRequest &request) override;
-
void *callbackLoop() override;
private:
AudioStreamInternalCapture mStreamInternalCapture;
- uint8_t *mDistributionBuffer = nullptr;
+ std::unique_ptr<uint8_t[]> mDistributionBuffer;
};
} /* namespace aaudio */
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/services/oboeservice/SharedMemoryProxy.cpp b/services/oboeservice/SharedMemoryProxy.cpp
index c43ed22..78d4884 100644
--- a/services/oboeservice/SharedMemoryProxy.cpp
+++ b/services/oboeservice/SharedMemoryProxy.cpp
@@ -20,6 +20,7 @@
#include <errno.h>
#include <string.h>
+#include <unistd.h>
#include <aaudio/AAudio.h>
#include "SharedMemoryProxy.h"
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
+