BnCrypto: fix use-before-init in CREATE_PLUGIN am: 4bbfb6d881 am: a9296786e3 am: 245e44d9cf Change-Id: I480db45b78b567a832d591df0834dbdae6cf88d0
diff --git a/Android.bp b/Android.bp index a3679b1..e4f12c8 100644 --- a/Android.bp +++ b/Android.bp
@@ -2,5 +2,6 @@ "camera", "drm/*", "media/*", + "services/*", "soundtrigger", ]
diff --git a/camera/Android.bp b/camera/Android.bp index c76ae50..24b3918 100644 --- a/camera/Android.bp +++ b/camera/Android.bp
@@ -29,12 +29,7 @@ // AIDL files for camera interfaces // The headers for these interfaces will be available to any modules that // include libcamera_client, at the path "aidl/package/path/BnFoo.h" - "aidl/android/hardware/ICameraService.aidl", - "aidl/android/hardware/ICameraServiceListener.aidl", - "aidl/android/hardware/ICameraServiceProxy.aidl", - "aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl", - "aidl/android/hardware/camera2/ICameraDeviceUser.aidl", - + ":libcamera_client_aidl", // Source for camera interface parcelables, and manually-written interfaces "Camera.cpp", @@ -81,3 +76,25 @@ ], } + +// AIDL interface between camera clients and the camera service. +filegroup { + name: "libcamera_client_aidl", + srcs: [ + "aidl/android/hardware/ICameraService.aidl", + "aidl/android/hardware/ICameraServiceListener.aidl", + "aidl/android/hardware/ICameraServiceProxy.aidl", + "aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl", + "aidl/android/hardware/camera2/ICameraDeviceUser.aidl", + ], +} + +// Extra AIDL files that are used by framework.jar but not libcamera_client +// because they have hand-written native implementations. +filegroup { + name: "libcamera_client_framework_aidl", + srcs: [ + "aidl/android/hardware/ICamera.aidl", + "aidl/android/hardware/ICameraClient.aidl", + ], +}
diff --git a/camera/CaptureResult.cpp b/camera/CaptureResult.cpp index e6c0d00..928a6bc 100644 --- a/camera/CaptureResult.cpp +++ b/camera/CaptureResult.cpp
@@ -60,6 +60,39 @@ return OK; } +status_t PhysicalCaptureResultInfo::readFromParcel(const android::Parcel* parcel) { + status_t res; + + mPhysicalCameraId.remove(mPhysicalCameraId.size()); + mPhysicalCameraMetadata.clear(); + + if ((res = parcel->readString16(&mPhysicalCameraId)) != OK) { + ALOGE("%s: Failed to read camera id: %d", __FUNCTION__, res); + return res; + } + + if ((res = mPhysicalCameraMetadata.readFromParcel(parcel)) != OK) { + ALOGE("%s: Failed to read metadata from parcel: %d", __FUNCTION__, res); + return res; + } + return OK; +} + +status_t PhysicalCaptureResultInfo::writeToParcel(android::Parcel* parcel) const { + status_t res; + if ((res = parcel->writeString16(mPhysicalCameraId)) != OK) { + ALOGE("%s: Failed to write physical camera ID to parcel: %d", + __FUNCTION__, res); + return res; + } + if ((res = mPhysicalCameraMetadata.writeToParcel(parcel)) != OK) { + ALOGE("%s: Failed to write physical camera metadata to parcel: %d", + __FUNCTION__, res); + return res; + } + return OK; +} + CaptureResult::CaptureResult() : mMetadata(), mResultExtras() { } @@ -67,6 +100,7 @@ CaptureResult::CaptureResult(const CaptureResult &otherResult) { mResultExtras = otherResult.mResultExtras; mMetadata = otherResult.mMetadata; + mPhysicalMetadatas = otherResult.mPhysicalMetadatas; } status_t CaptureResult::readFromParcel(android::Parcel *parcel) { @@ -79,6 +113,7 @@ } mMetadata.clear(); + mPhysicalMetadatas.clear(); status_t res = OK; res = mMetadata.readFromParcel(parcel); @@ -89,6 +124,34 @@ } ALOGV("%s: Read metadata from parcel", __FUNCTION__); + int32_t physicalMetadataCount; + if ((res = parcel->readInt32(&physicalMetadataCount)) != OK) { + ALOGE("%s: Failed to read the physical metadata count from parcel: %d", __FUNCTION__, res); + return res; + } + if (physicalMetadataCount < 0) { + ALOGE("%s: Invalid physical metadata count from parcel: %d", + __FUNCTION__, physicalMetadataCount); + return BAD_VALUE; + } + + for (int32_t i = 0; i < physicalMetadataCount; i++) { + String16 cameraId; + if ((res = parcel->readString16(&cameraId)) != OK) { + ALOGE("%s: Failed to read camera id: %d", __FUNCTION__, res); + return res; + } + + CameraMetadata physicalMetadata; + if ((res = physicalMetadata.readFromParcel(parcel)) != OK) { + ALOGE("%s: Failed to read metadata from parcel: %d", __FUNCTION__, res); + return res; + } + + mPhysicalMetadatas.emplace(mPhysicalMetadatas.end(), cameraId, physicalMetadata); + } + ALOGV("%s: Read physical metadata from parcel", __FUNCTION__); + res = mResultExtras.readFromParcel(parcel); if (res != OK) { ALOGE("%s: Failed to read result extras from parcel.", @@ -118,6 +181,27 @@ } ALOGV("%s: Wrote metadata to parcel", __FUNCTION__); + int32_t physicalMetadataCount = static_cast<int32_t>(mPhysicalMetadatas.size()); + res = parcel->writeInt32(physicalMetadataCount); + if (res != OK) { + ALOGE("%s: Failed to write physical metadata count to parcel: %d", + __FUNCTION__, res); + return BAD_VALUE; + } + for (const auto& physicalMetadata : mPhysicalMetadatas) { + if ((res = parcel->writeString16(physicalMetadata.mPhysicalCameraId)) != OK) { + ALOGE("%s: Failed to write physical camera ID to parcel: %d", + __FUNCTION__, res); + return res; + } + if ((res = physicalMetadata.mPhysicalCameraMetadata.writeToParcel(parcel)) != OK) { + ALOGE("%s: Failed to write physical camera metadata to parcel: %d", + __FUNCTION__, res); + return res; + } + } + ALOGV("%s: Wrote physical camera metadata to parcel", __FUNCTION__); + res = mResultExtras.writeToParcel(parcel); if (res != OK) { ALOGE("%s: Failed to write result extras to parcel", __FUNCTION__);
diff --git a/camera/aidl/android/hardware/ICameraServiceProxy.aidl b/camera/aidl/android/hardware/ICameraServiceProxy.aidl index 5dc23eb..7575948 100644 --- a/camera/aidl/android/hardware/ICameraServiceProxy.aidl +++ b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
@@ -46,8 +46,14 @@ const int CAMERA_FACING_EXTERNAL = 2; /** + * Values for notifyCameraState api level + */ + const int CAMERA_API_LEVEL_1 = 1; + const int CAMERA_API_LEVEL_2 = 2; + + /** * Update the status of a camera device. */ oneway void notifyCameraState(String cameraId, int facing, int newCameraState, - String clientName); + String clientName, int apiLevel); }
diff --git a/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl index 28252c0..58b19a3 100644 --- a/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl +++ b/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -18,6 +18,7 @@ import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; /** @hide */ interface ICameraDeviceCallbacks @@ -30,12 +31,14 @@ const int ERROR_CAMERA_REQUEST = 3; const int ERROR_CAMERA_RESULT = 4; const int ERROR_CAMERA_BUFFER = 5; + const int ERROR_CAMERA_DISABLED = 6; oneway void onDeviceError(int errorCode, in CaptureResultExtras resultExtras); oneway void onDeviceIdle(); oneway void onCaptureStarted(in CaptureResultExtras resultExtras, long timestamp); oneway void onResultReceived(in CameraMetadataNative result, - in CaptureResultExtras resultExtras); + in CaptureResultExtras resultExtras, + in PhysicalCaptureResultInfo[] physicalCaptureResultInfos); oneway void onPrepared(int streamId); /**
diff --git a/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl b/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl index 0771fc8..4ced08c 100644 --- a/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl +++ b/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -79,8 +79,9 @@ * <p> * @param operatingMode The kind of session to create; either NORMAL_MODE or * CONSTRAINED_HIGH_SPEED_MODE. Must be a non-negative value. + * @param sessionParams Session wide camera parameters */ - void endConfigure(int operatingMode); + void endConfigure(int operatingMode, in CameraMetadataNative sessionParams); void deleteStream(int streamId); @@ -140,5 +141,7 @@ void prepare2(int maxCount, int streamId); + void updateOutputConfiguration(int streamId, in OutputConfiguration outputConfiguration); + void finalizeOutputConfigurations(int streamId, in OutputConfiguration outputConfiguration); }
diff --git a/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl b/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl new file mode 100644 index 0000000..78d9b7b --- /dev/null +++ b/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl
@@ -0,0 +1,20 @@ +/* + * 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. + */ + +package android.hardware.camera2.impl; + +/** @hide */ +parcelable PhysicalCaptureResultInfo cpp_header "camera/CaptureResult.h";
diff --git a/camera/camera2/CaptureRequest.cpp b/camera/camera2/CaptureRequest.cpp index 0597950..1843ec4 100644 --- a/camera/camera2/CaptureRequest.cpp +++ b/camera/camera2/CaptureRequest.cpp
@@ -18,6 +18,7 @@ // #define LOG_NDEBUG 0 #define LOG_TAG "CameraRequest" #include <utils/Log.h> +#include <utils/String16.h> #include <camera/camera2/CaptureRequest.h> @@ -42,16 +43,46 @@ return BAD_VALUE; } - mMetadata.clear(); mSurfaceList.clear(); + mStreamIdxList.clear(); + mSurfaceIdxList.clear(); + mPhysicalCameraSettings.clear(); status_t err = OK; - if ((err = mMetadata.readFromParcel(parcel)) != OK) { - ALOGE("%s: Failed to read metadata from parcel", __FUNCTION__); + int32_t settingsCount; + if ((err = parcel->readInt32(&settingsCount)) != OK) { + ALOGE("%s: Failed to read the settings count from parcel: %d", __FUNCTION__, err); return err; } - ALOGV("%s: Read metadata from parcel", __FUNCTION__); + + if (settingsCount <= 0) { + ALOGE("%s: Settings count %d should always be positive!", __FUNCTION__, settingsCount); + return BAD_VALUE; + } + + for (int32_t i = 0; i < settingsCount; i++) { + String16 id; + if ((err = parcel->readString16(&id)) != OK) { + ALOGE("%s: Failed to read camera id!", __FUNCTION__); + return BAD_VALUE; + } + + CameraMetadata settings; + if ((err = settings.readFromParcel(parcel)) != OK) { + ALOGE("%s: Failed to read metadata from parcel", __FUNCTION__); + return err; + } + ALOGV("%s: Read metadata from parcel", __FUNCTION__); + mPhysicalCameraSettings.push_back({std::string(String8(id).string()), settings}); + } + + int isReprocess = 0; + if ((err = parcel->readInt32(&isReprocess)) != OK) { + ALOGE("%s: Failed to read reprocessing from parcel", __FUNCTION__); + return err; + } + mIsReprocess = (isReprocess != 0); int32_t size; if ((err = parcel->readInt32(&size)) != OK) { @@ -61,7 +92,7 @@ ALOGV("%s: Read surface list size = %d", __FUNCTION__, size); // Do not distinguish null arrays from 0-sized arrays. - for (int i = 0; i < size; ++i) { + for (int32_t i = 0; i < size; ++i) { // Parcel.writeParcelableArray size_t len; const char16_t* className = parcel->readString16Inplace(&len); @@ -88,12 +119,32 @@ mSurfaceList.push_back(surface); } - int isReprocess = 0; - if ((err = parcel->readInt32(&isReprocess)) != OK) { - ALOGE("%s: Failed to read reprocessing from parcel", __FUNCTION__); + int32_t streamSurfaceSize; + if ((err = parcel->readInt32(&streamSurfaceSize)) != OK) { + ALOGE("%s: Failed to read streamSurfaceSize from parcel", __FUNCTION__); return err; } - mIsReprocess = (isReprocess != 0); + + if (streamSurfaceSize < 0) { + ALOGE("%s: Bad streamSurfaceSize %d from parcel", __FUNCTION__, streamSurfaceSize); + return BAD_VALUE; + } + + for (int32_t i = 0; i < streamSurfaceSize; ++i) { + int streamIdx; + if ((err = parcel->readInt32(&streamIdx)) != OK) { + ALOGE("%s: Failed to read stream index from parcel", __FUNCTION__); + return err; + } + mStreamIdxList.push_back(streamIdx); + + int surfaceIdx; + if ((err = parcel->readInt32(&surfaceIdx)) != OK) { + ALOGE("%s: Failed to read surface index from parcel", __FUNCTION__); + return err; + } + mSurfaceIdxList.push_back(surfaceIdx); + } return OK; } @@ -106,32 +157,62 @@ status_t err = OK; - if ((err = mMetadata.writeToParcel(parcel)) != OK) { + int32_t settingsCount = static_cast<int32_t>(mPhysicalCameraSettings.size()); + + if ((err = parcel->writeInt32(settingsCount)) != OK) { + ALOGE("%s: Failed to write settings count!", __FUNCTION__); return err; } - int32_t size = static_cast<int32_t>(mSurfaceList.size()); + for (const auto &it : mPhysicalCameraSettings) { + if ((err = parcel->writeString16(String16(it.id.c_str()))) != OK) { + ALOGE("%s: Failed to camera id!", __FUNCTION__); + return err; + } - // Send 0-sized arrays when it's empty. Do not send null arrays. - parcel->writeInt32(size); - - for (int32_t i = 0; i < size; ++i) { - // not sure if readParcelableArray does this, hard to tell from source - parcel->writeString16(String16("android.view.Surface")); - - // Surface.writeToParcel - view::Surface surfaceShim; - surfaceShim.name = String16("unknown_name"); - surfaceShim.graphicBufferProducer = mSurfaceList[i]->getIGraphicBufferProducer(); - if ((err = surfaceShim.writeToParcel(parcel)) != OK) { - ALOGE("%s: Failed to write output target Surface %d to parcel: %s (%d)", - __FUNCTION__, i, strerror(-err), err); + if ((err = it.settings.writeToParcel(parcel)) != OK) { + ALOGE("%s: Failed to write settings!", __FUNCTION__); return err; } } parcel->writeInt32(mIsReprocess ? 1 : 0); + if (mSurfaceConverted) { + parcel->writeInt32(0); // 0-sized array + } else { + int32_t size = static_cast<int32_t>(mSurfaceList.size()); + + // Send 0-sized arrays when it's empty. Do not send null arrays. + parcel->writeInt32(size); + + for (int32_t i = 0; i < size; ++i) { + // not sure if readParcelableArray does this, hard to tell from source + parcel->writeString16(String16("android.view.Surface")); + + // Surface.writeToParcel + view::Surface surfaceShim; + surfaceShim.name = String16("unknown_name"); + surfaceShim.graphicBufferProducer = mSurfaceList[i]->getIGraphicBufferProducer(); + if ((err = surfaceShim.writeToParcel(parcel)) != OK) { + ALOGE("%s: Failed to write output target Surface %d to parcel: %s (%d)", + __FUNCTION__, i, strerror(-err), err); + return err; + } + } + } + + parcel->writeInt32(mStreamIdxList.size()); + for (size_t i = 0; i < mStreamIdxList.size(); ++i) { + if ((err = parcel->writeInt32(mStreamIdxList[i])) != OK) { + ALOGE("%s: Failed to write stream index to parcel", __FUNCTION__); + return err; + } + if ((err = parcel->writeInt32(mSurfaceIdxList[i])) != OK) { + ALOGE("%s: Failed to write surface index to parcel", __FUNCTION__); + return err; + } + } return OK; }
diff --git a/camera/camera2/OutputConfiguration.cpp b/camera/camera2/OutputConfiguration.cpp index 468a1eb..feb04c2 100644 --- a/camera/camera2/OutputConfiguration.cpp +++ b/camera/camera2/OutputConfiguration.cpp
@@ -1,6 +1,6 @@ /* ** -** Copyright 2015, The Android Open Source Project +** Copyright 2015-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. @@ -64,6 +64,10 @@ return mIsShared; } +String16 OutputConfiguration::getPhysicalCameraId() const { + return mPhysicalCameraId; +} + OutputConfiguration::OutputConfiguration() : mRotation(INVALID_ROTATION), mSurfaceSetID(INVALID_SET_ID), @@ -139,6 +143,8 @@ return err; } + parcel->readString16(&mPhysicalCameraId); + mRotation = rotation; mSurfaceSetID = setID; mSurfaceType = surfaceType; @@ -153,19 +159,20 @@ mGbps.push_back(surface.graphicBufferProducer); } - ALOGV("%s: OutputConfiguration: rotation = %d, setId = %d, surfaceType = %d", - __FUNCTION__, mRotation, mSurfaceSetID, mSurfaceType); + ALOGV("%s: OutputConfiguration: rotation = %d, setId = %d, surfaceType = %d," + " physicalCameraId = %s", __FUNCTION__, mRotation, mSurfaceSetID, + mSurfaceType, String8(mPhysicalCameraId).string()); return err; } OutputConfiguration::OutputConfiguration(sp<IGraphicBufferProducer>& gbp, int rotation, - int surfaceSetID) { + int surfaceSetID, bool isShared) { mGbps.push_back(gbp); mRotation = rotation; mSurfaceSetID = surfaceSetID; mIsDeferred = false; - mIsShared = false; + mIsShared = isShared; } status_t OutputConfiguration::writeToParcel(android::Parcel* parcel) const { @@ -204,6 +211,9 @@ err = parcel->writeParcelableVector(surfaceShims); if (err != OK) return err; + err = parcel->writeString16(mPhysicalCameraId); + if (err != OK) return err; + return OK; }
diff --git a/camera/cameraserver/cameraserver.rc b/camera/cameraserver/cameraserver.rc index fea5a1d..a9aae0b 100644 --- a/camera/cameraserver/cameraserver.rc +++ b/camera/cameraserver/cameraserver.rc
@@ -4,3 +4,4 @@ group audio camera input drmrpc ioprio rt 4 writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks + rlimit rtprio 10 10
diff --git a/camera/include/camera/CaptureResult.h b/camera/include/camera/CaptureResult.h index 917d953..56fa178 100644 --- a/camera/include/camera/CaptureResult.h +++ b/camera/include/camera/CaptureResult.h
@@ -91,14 +91,36 @@ virtual status_t readFromParcel(const android::Parcel* parcel) override; virtual status_t writeToParcel(android::Parcel* parcel) const override; }; + +struct PhysicalCaptureResultInfo : public android::Parcelable { + + PhysicalCaptureResultInfo() + : mPhysicalCameraId(), + mPhysicalCameraMetadata() { + } + PhysicalCaptureResultInfo(const String16& cameraId, + const CameraMetadata& cameraMetadata) + : mPhysicalCameraId(cameraId), + mPhysicalCameraMetadata(cameraMetadata) { + } + + String16 mPhysicalCameraId; + CameraMetadata mPhysicalCameraMetadata; + + virtual status_t readFromParcel(const android::Parcel* parcel) override; + virtual status_t writeToParcel(android::Parcel* parcel) const override; +}; + } // namespace impl } // namespace camera2 } // namespace hardware using hardware::camera2::impl::CaptureResultExtras; +using hardware::camera2::impl::PhysicalCaptureResultInfo; struct CaptureResult : public virtual LightRefBase<CaptureResult> { CameraMetadata mMetadata; + std::vector<PhysicalCaptureResultInfo> mPhysicalMetadatas; CaptureResultExtras mResultExtras; CaptureResult();
diff --git a/camera/include/camera/camera2/CaptureRequest.h b/camera/include/camera/camera2/CaptureRequest.h index 0180183..506abab 100644 --- a/camera/include/camera/camera2/CaptureRequest.h +++ b/camera/include/camera/camera2/CaptureRequest.h
@@ -40,14 +40,35 @@ CaptureRequest(CaptureRequest&& rhs) noexcept; virtual ~CaptureRequest(); - CameraMetadata mMetadata; + struct PhysicalCameraSettings { + std::string id; + CameraMetadata settings; + }; + std::vector<PhysicalCameraSettings> mPhysicalCameraSettings; + + // Used by NDK client to pass surfaces by stream/surface index. + bool mSurfaceConverted = false; + + // Starting in Android O, create a Surface from Parcel will take one extra + // IPC call. Vector<sp<Surface> > mSurfaceList; + // Optional way of passing surface list since passing Surface over binder + // is expensive. Use the stream/surface index from current output configuration + // to represent an configured output Surface. When stream/surface index is used, + // set mSurfaceList to zero length to save unparcel time. + Vector<int> mStreamIdxList; + Vector<int> mSurfaceIdxList; // per stream surface list index + bool mIsReprocess; + void* mContext; // arbitrary user context from NDK apps, null for java apps + /** * Keep impl up-to-date with CaptureRequest.java in frameworks/base */ + // used by cameraserver to receive CaptureRequest from java/NDK client status_t readFromParcel(const android::Parcel* parcel) override; + // used by NDK client to send CaptureRequest to cameraserver status_t writeToParcel(android::Parcel* parcel) const override; };
diff --git a/camera/include/camera/camera2/OutputConfiguration.h b/camera/include/camera/camera2/OutputConfiguration.h index 8e641c7..a80f44b 100644 --- a/camera/include/camera/camera2/OutputConfiguration.h +++ b/camera/include/camera/camera2/OutputConfiguration.h
@@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2015-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. @@ -46,6 +46,7 @@ int getHeight() const; bool isDeferred() const; bool isShared() const; + String16 getPhysicalCameraId() const; /** * Keep impl up-to-date with OutputConfiguration.java in frameworks/base */ @@ -64,7 +65,7 @@ OutputConfiguration(const android::Parcel& parcel); OutputConfiguration(sp<IGraphicBufferProducer>& gbp, int rotation, - int surfaceSetID = INVALID_SET_ID); + int surfaceSetID = INVALID_SET_ID, bool isShared = false); bool operator == (const OutputConfiguration& other) const { return ( mRotation == other.mRotation && @@ -74,7 +75,8 @@ mHeight == other.mHeight && mIsDeferred == other.mIsDeferred && mIsShared == other.mIsShared && - gbpsEqual(other)); + gbpsEqual(other) && + mPhysicalCameraId == other.mPhysicalCameraId ); } bool operator != (const OutputConfiguration& other) const { return !(*this == other); @@ -102,6 +104,9 @@ if (mIsShared != other.mIsShared) { return mIsShared < other.mIsShared; } + if (mPhysicalCameraId != other.mPhysicalCameraId) { + return mPhysicalCameraId < other.mPhysicalCameraId; + } return gbpsLessThan(other); } bool operator > (const OutputConfiguration& other) const { @@ -110,6 +115,7 @@ bool gbpsEqual(const OutputConfiguration& other) const; bool gbpsLessThan(const OutputConfiguration& other) const; + void addGraphicProducer(sp<IGraphicBufferProducer> gbp) {mGbps.push_back(gbp);} private: std::vector<sp<IGraphicBufferProducer>> mGbps; int mRotation; @@ -119,8 +125,7 @@ int mHeight; bool mIsDeferred; bool mIsShared; - // helper function - static String16 readMaybeEmptyString16(const android::Parcel* parcel); + String16 mPhysicalCameraId; }; } // namespace params } // namespace camera2
diff --git a/camera/ndk/NdkCameraCaptureSession.cpp b/camera/ndk/NdkCameraCaptureSession.cpp index 2a6b182..fd95296 100644 --- a/camera/ndk/NdkCameraCaptureSession.cpp +++ b/camera/ndk/NdkCameraCaptureSession.cpp
@@ -135,3 +135,19 @@ } return session->abortCaptures(); } + +EXPORT +camera_status_t ACameraCaptureSession_updateSharedOutput(ACameraCaptureSession* session, + ACaptureSessionOutput* output) { + ATRACE_CALL(); + if (session == nullptr) { + ALOGE("%s: Error: session is null", __FUNCTION__); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + if (session->isClosed()) { + ALOGE("%s: session %p is already closed", __FUNCTION__, session); + return ACAMERA_ERROR_SESSION_CLOSED; + } + return session->updateOutputConfiguration(output); +}
diff --git a/camera/ndk/NdkCameraDevice.cpp b/camera/ndk/NdkCameraDevice.cpp index 9f6d1f7..812a312 100644 --- a/camera/ndk/NdkCameraDevice.cpp +++ b/camera/ndk/NdkCameraDevice.cpp
@@ -103,11 +103,74 @@ __FUNCTION__, window, out); return ACAMERA_ERROR_INVALID_PARAMETER; } - *out = new ACaptureSessionOutput(window); + *out = new ACaptureSessionOutput(window, false); return ACAMERA_OK; } EXPORT +camera_status_t ACaptureSessionSharedOutput_create( + ANativeWindow* window, /*out*/ACaptureSessionOutput** out) { + ATRACE_CALL(); + if (window == nullptr || out == nullptr) { + ALOGE("%s: Error: bad argument. window %p, out %p", + __FUNCTION__, window, out); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + *out = new ACaptureSessionOutput(window, true); + return ACAMERA_OK; +} + +EXPORT +camera_status_t ACaptureSessionSharedOutput_add(ACaptureSessionOutput *out, + ANativeWindow* window) { + ATRACE_CALL(); + if ((window == nullptr) || (out == nullptr)) { + ALOGE("%s: Error: bad argument. window %p, out %p", + __FUNCTION__, window, out); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + if (!out->mIsShared) { + ALOGE("%s: Error trying to insert a new window in non-shared output configuration", + __FUNCTION__); + return ACAMERA_ERROR_INVALID_OPERATION; + } + if (out->mWindow == window) { + ALOGE("%s: Error trying to add the same window associated with the output configuration", + __FUNCTION__); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + auto insert = out->mSharedWindows.insert(window); + camera_status_t ret = (insert.second) ? ACAMERA_OK : ACAMERA_ERROR_INVALID_PARAMETER; + return ret; +} + +EXPORT +camera_status_t ACaptureSessionSharedOutput_remove(ACaptureSessionOutput *out, + ANativeWindow* window) { + ATRACE_CALL(); + if ((window == nullptr) || (out == nullptr)) { + ALOGE("%s: Error: bad argument. window %p, out %p", + __FUNCTION__, window, out); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + if (!out->mIsShared) { + ALOGE("%s: Error trying to remove a window in non-shared output configuration", + __FUNCTION__); + return ACAMERA_ERROR_INVALID_OPERATION; + } + if (out->mWindow == window) { + ALOGE("%s: Error trying to remove the same window associated with the output configuration", + __FUNCTION__); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + auto remove = out->mSharedWindows.erase(window); + camera_status_t ret = (remove) ? ACAMERA_OK : ACAMERA_ERROR_INVALID_PARAMETER; + return ret; +} + +EXPORT void ACaptureSessionOutput_free(ACaptureSessionOutput* output) { ATRACE_CALL(); if (output != nullptr) { @@ -157,5 +220,21 @@ __FUNCTION__, device, outputs, callbacks, session); return ACAMERA_ERROR_INVALID_PARAMETER; } - return device->createCaptureSession(outputs, callbacks, session); + return device->createCaptureSession(outputs, nullptr, callbacks, session); +} + +EXPORT +camera_status_t ACameraDevice_createCaptureSessionWithSessionParameters( + ACameraDevice* device, + const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters, + const ACameraCaptureSession_stateCallbacks* callbacks, + /*out*/ACameraCaptureSession** session) { + ATRACE_CALL(); + if (device == nullptr || outputs == nullptr || callbacks == nullptr || session == nullptr) { + ALOGE("%s: Error: invalid input: device %p, outputs %p, callbacks %p, session %p", + __FUNCTION__, device, outputs, callbacks, session); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + return device->createCaptureSession(outputs, sessionParameters, callbacks, session); }
diff --git a/camera/ndk/NdkCaptureRequest.cpp b/camera/ndk/NdkCaptureRequest.cpp index 5b4c180..ac1856b 100644 --- a/camera/ndk/NdkCaptureRequest.cpp +++ b/camera/ndk/NdkCaptureRequest.cpp
@@ -142,3 +142,40 @@ delete request; return; } + +EXPORT +camera_status_t ACaptureRequest_setUserContext( + ACaptureRequest* request, void* context) { + if (request == nullptr) { + ALOGE("%s: invalid argument! request is NULL", __FUNCTION__); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + return request->setContext(context); +} + +EXPORT +camera_status_t ACaptureRequest_getUserContext( + const ACaptureRequest* request, /*out*/void** context) { + if (request == nullptr || context == nullptr) { + ALOGE("%s: invalid argument! request %p, context %p", + __FUNCTION__, request, context); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + return request->getContext(context); +} + +EXPORT +ACaptureRequest* ACaptureRequest_copy(const ACaptureRequest* src) { + ATRACE_CALL(); + if (src == nullptr) { + ALOGE("%s: src is null!", __FUNCTION__); + return nullptr; + } + + ACaptureRequest* pRequest = new ACaptureRequest(); + pRequest->settings = new ACameraMetadata(*(src->settings)); + pRequest->targets = new ACameraOutputTargets(); + *(pRequest->targets) = *(src->targets); + pRequest->context = src->context; + return pRequest; +}
diff --git a/camera/ndk/impl/ACameraCaptureSession.cpp b/camera/ndk/impl/ACameraCaptureSession.cpp index b9c159d..f60e5fd 100644 --- a/camera/ndk/impl/ACameraCaptureSession.cpp +++ b/camera/ndk/impl/ACameraCaptureSession.cpp
@@ -148,6 +148,23 @@ return ret; } +camera_status_t ACameraCaptureSession::updateOutputConfiguration(ACaptureSessionOutput *output) { + sp<CameraDevice> dev = getDeviceSp(); + if (dev == nullptr) { + ALOGE("Error: Device associated with session %p has been closed!", this); + return ACAMERA_ERROR_SESSION_CLOSED; + } + + camera_status_t ret; + dev->lockDeviceForSessionOps(); + { + Mutex::Autolock _l(mSessionLock); + ret = dev->updateOutputConfigurationLocked(output); + } + dev->unlockDevice(); + return ret; +} + ACameraDevice* ACameraCaptureSession::getDevice() { Mutex::Autolock _l(mSessionLock);
diff --git a/camera/ndk/impl/ACameraCaptureSession.h b/camera/ndk/impl/ACameraCaptureSession.h index 339c665..a2068e7 100644 --- a/camera/ndk/impl/ACameraCaptureSession.h +++ b/camera/ndk/impl/ACameraCaptureSession.h
@@ -24,7 +24,8 @@ using namespace android; struct ACaptureSessionOutput { - explicit ACaptureSessionOutput(ANativeWindow* window) : mWindow(window) {}; + explicit ACaptureSessionOutput(ANativeWindow* window, bool isShared = false) : + mWindow(window), mIsShared(isShared) {}; bool operator == (const ACaptureSessionOutput& other) const { return mWindow == other.mWindow; @@ -40,6 +41,8 @@ } ANativeWindow* mWindow; + std::set<ANativeWindow *> mSharedWindows; + bool mIsShared; int mRotation = CAMERA3_STREAM_ROTATION_0; }; @@ -89,6 +92,8 @@ int numRequests, ACaptureRequest** requests, /*optional*/int* captureSequenceId); + camera_status_t updateOutputConfiguration(ACaptureSessionOutput *output); + ACameraDevice* getDevice(); private:
diff --git a/camera/ndk/impl/ACameraDevice.cpp b/camera/ndk/impl/ACameraDevice.cpp index af977b8..907debc 100644 --- a/camera/ndk/impl/ACameraDevice.cpp +++ b/camera/ndk/impl/ACameraDevice.cpp
@@ -59,7 +59,8 @@ mWrapper(wrapper), mInError(false), mError(ACAMERA_OK), - mIdle(true) { + mIdle(true), + mCurrentSession(nullptr) { mClosing = false; // Setup looper thread to perfrom device callbacks to app mCbLooper = new ALooper; @@ -98,18 +99,30 @@ // Device close implementaiton CameraDevice::~CameraDevice() { - Mutex::Autolock _l(mDeviceLock); - if (!isClosed()) { - disconnectLocked(); - } - if (mCbLooper != nullptr) { - mCbLooper->unregisterHandler(mHandler->id()); - mCbLooper->stop(); + sp<ACameraCaptureSession> session = mCurrentSession.promote(); + { + Mutex::Autolock _l(mDeviceLock); + if (!isClosed()) { + disconnectLocked(session); + } + mCurrentSession = nullptr; + if (mCbLooper != nullptr) { + mCbLooper->unregisterHandler(mHandler->id()); + mCbLooper->stop(); + } } mCbLooper.clear(); mHandler.clear(); } +void +CameraDevice::postSessionMsgAndCleanup(sp<AMessage>& msg) { + msg->post(); + msg.clear(); + sp<AMessage> cleanupMsg = new AMessage(kWhatCleanUpSessions, mHandler); + cleanupMsg->post(); +} + // TODO: cached created request? camera_status_t CameraDevice::createCaptureRequest( @@ -144,21 +157,23 @@ camera_status_t CameraDevice::createCaptureSession( const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters, const ACameraCaptureSession_stateCallbacks* callbacks, /*out*/ACameraCaptureSession** session) { + sp<ACameraCaptureSession> currentSession = mCurrentSession.promote(); Mutex::Autolock _l(mDeviceLock); camera_status_t ret = checkCameraClosedOrErrorLocked(); if (ret != ACAMERA_OK) { return ret; } - if (mCurrentSession != nullptr) { - mCurrentSession->closeByDevice(); + if (currentSession != nullptr) { + currentSession->closeByDevice(); stopRepeatingLocked(); } // Create new session - ret = configureStreamsLocked(outputs); + ret = configureStreamsLocked(outputs, sessionParameters); if (ret != ACAMERA_OK) { ALOGE("Fail to create new session. cannot configure streams"); return ret; @@ -264,7 +279,7 @@ msg->setPointer(kContextKey, session->mUserSessionCallback.context); msg->setObject(kSessionSpKey, session); msg->setPointer(kCallbackFpKey, (void*) session->mUserSessionCallback.onActive); - msg->post(); + postSessionMsgAndCleanup(msg); } mIdle = false; mBusySession = session; @@ -275,13 +290,93 @@ return ACAMERA_OK; } +camera_status_t CameraDevice::updateOutputConfigurationLocked(ACaptureSessionOutput *output) { + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + return ret; + } + + if (output == nullptr) { + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + if (!output->mIsShared) { + ALOGE("Error output configuration is not shared"); + return ACAMERA_ERROR_INVALID_OPERATION; + } + + int32_t streamId = -1; + for (auto& kvPair : mConfiguredOutputs) { + if (kvPair.second.first == output->mWindow) { + streamId = kvPair.first; + break; + } + } + if (streamId < 0) { + ALOGE("Error: Invalid output configuration"); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + sp<IGraphicBufferProducer> iGBP(nullptr); + ret = getIGBPfromAnw(output->mWindow, iGBP); + if (ret != ACAMERA_OK) { + ALOGE("Camera device %s failed to extract graphic producer from native window", + getId()); + return ret; + } + + OutputConfiguration outConfig(iGBP, output->mRotation, OutputConfiguration::INVALID_SET_ID, + true); + + for (auto& anw : output->mSharedWindows) { + ret = getIGBPfromAnw(anw, iGBP); + if (ret != ACAMERA_OK) { + ALOGE("Camera device %s failed to extract graphic producer from native window", + getId()); + return ret; + } + outConfig.addGraphicProducer(iGBP); + } + + auto remoteRet = mRemote->updateOutputConfiguration(streamId, outConfig); + if (!remoteRet.isOk()) { + switch (remoteRet.serviceSpecificErrorCode()) { + case hardware::ICameraService::ERROR_INVALID_OPERATION: + ALOGE("Camera device %s invalid operation: %s", getId(), + remoteRet.toString8().string()); + return ACAMERA_ERROR_INVALID_OPERATION; + break; + case hardware::ICameraService::ERROR_ALREADY_EXISTS: + ALOGE("Camera device %s output surface already exists: %s", getId(), + remoteRet.toString8().string()); + return ACAMERA_ERROR_INVALID_PARAMETER; + break; + case hardware::ICameraService::ERROR_ILLEGAL_ARGUMENT: + ALOGE("Camera device %s invalid input argument: %s", getId(), + remoteRet.toString8().string()); + return ACAMERA_ERROR_INVALID_PARAMETER; + break; + default: + ALOGE("Camera device %s failed to add shared output: %s", getId(), + remoteRet.toString8().string()); + return ACAMERA_ERROR_UNKNOWN; + } + } + mConfiguredOutputs[streamId] = std::make_pair(output->mWindow, outConfig); + + return ACAMERA_OK; +} + camera_status_t CameraDevice::allocateCaptureRequest( const ACaptureRequest* request, /*out*/sp<CaptureRequest>& outReq) { camera_status_t ret; sp<CaptureRequest> req(new CaptureRequest()); - req->mMetadata = request->settings->getInternalData(); + req->mPhysicalCameraSettings.push_back({std::string(mCameraId.string()), + request->settings->getInternalData()}); req->mIsReprocess = false; // NDK does not support reprocessing yet + req->mContext = request->context; + req->mSurfaceConverted = true; // set to true, and fill in stream/surface idx to speed up IPC for (auto outputTarget : request->targets->mOutputs) { ANativeWindow* anw = outputTarget.mWindow; @@ -292,7 +387,31 @@ return ret; } req->mSurfaceList.push_back(surface); + + bool found = false; + // lookup stream/surface ID + for (const auto& kvPair : mConfiguredOutputs) { + int streamId = kvPair.first; + const OutputConfiguration& outConfig = kvPair.second.second; + const auto& gbps = outConfig.getGraphicBufferProducers(); + for (int surfaceId = 0; surfaceId < (int) gbps.size(); surfaceId++) { + if (gbps[surfaceId] == surface->getIGraphicBufferProducer()) { + found = true; + req->mStreamIdxList.push_back(streamId); + req->mSurfaceIdxList.push_back(surfaceId); + break; + } + } + if (found) { + break; + } + } + if (!found) { + ALOGE("Unconfigured output target %p in capture request!", anw); + return ret; + } } + outReq = req; return ACAMERA_OK; } @@ -300,7 +419,7 @@ ACaptureRequest* CameraDevice::allocateACaptureRequest(sp<CaptureRequest>& req) { ACaptureRequest* pRequest = new ACaptureRequest(); - CameraMetadata clone = req->mMetadata; + CameraMetadata clone = req->mPhysicalCameraSettings.begin()->settings; pRequest->settings = new ACameraMetadata(clone.release(), ACameraMetadata::ACM_REQUEST); pRequest->targets = new ACameraOutputTargets(); for (size_t i = 0; i < req->mSurfaceList.size(); i++) { @@ -308,6 +427,7 @@ ACameraOutputTarget outputTarget(anw); pRequest->targets->mOutputs.insert(outputTarget); } + pRequest->context = req->mContext; return pRequest; } @@ -328,7 +448,7 @@ return; } - if (session != mCurrentSession) { + if (mCurrentSession != session) { // Session has been replaced by other seesion or device is closed return; } @@ -342,14 +462,14 @@ } // No new session, unconfigure now - camera_status_t ret = configureStreamsLocked(nullptr); + camera_status_t ret = configureStreamsLocked(nullptr, nullptr); if (ret != ACAMERA_OK) { ALOGE("Unconfigure stream failed. Device might still be configured! ret %d", ret); } } void -CameraDevice::disconnectLocked() { +CameraDevice::disconnectLocked(sp<ACameraCaptureSession>& session) { if (mClosing.exchange(true)) { // Already closing, just return ALOGW("Camera device %s is already closing.", getId()); @@ -361,9 +481,8 @@ } mRemote = nullptr; - if (mCurrentSession != nullptr) { - mCurrentSession->closeByDevice(); - mCurrentSession = nullptr; + if (session != nullptr) { + session->closeByDevice(); } } @@ -404,7 +523,7 @@ // This should never happen because creating a new session will close // previous one and thus reject any API call from previous session. // But still good to check here in case something unexpected happen. - if (session != mCurrentSession) { + if (mCurrentSession != session) { ALOGE("Camera %s session %p is not current active session!", getId(), session); return ACAMERA_ERROR_INVALID_OPERATION; } @@ -415,12 +534,13 @@ } mFlushing = true; + // Send onActive callback to guarantee there is always active->ready transition sp<AMessage> msg = new AMessage(kWhatSessionStateCb, mHandler); msg->setPointer(kContextKey, session->mUserSessionCallback.context); msg->setObject(kSessionSpKey, session); msg->setPointer(kCallbackFpKey, (void*) session->mUserSessionCallback.onActive); - msg->post(); + postSessionMsgAndCleanup(msg); // If device is already idling, send callback and exit early if (mIdle) { @@ -428,7 +548,7 @@ msg->setPointer(kContextKey, session->mUserSessionCallback.context); msg->setObject(kSessionSpKey, session); msg->setPointer(kCallbackFpKey, (void*) session->mUserSessionCallback.onReady); - msg->post(); + postSessionMsgAndCleanup(msg); mFlushing = false; return ACAMERA_OK; } @@ -472,17 +592,11 @@ CameraDevice::getIGBPfromAnw( ANativeWindow* anw, sp<IGraphicBufferProducer>& out) { - if (anw == nullptr) { - ALOGE("Error: output ANativeWindow is null"); - return ACAMERA_ERROR_INVALID_PARAMETER; + sp<Surface> surface; + camera_status_t ret = getSurfaceFromANativeWindow(anw, surface); + if (ret != ACAMERA_OK) { + return ret; } - int value; - int err = (*anw->query)(anw, NATIVE_WINDOW_CONCRETE_TYPE, &value); - if (err != OK || value != NATIVE_WINDOW_SURFACE) { - ALOGE("Error: ANativeWindow is not backed by Surface!"); - return ACAMERA_ERROR_INVALID_PARAMETER; - } - const sp<Surface> surface(static_cast<Surface*>(anw)); out = surface->getIGraphicBufferProducer(); return ACAMERA_OK; } @@ -506,7 +620,8 @@ } camera_status_t -CameraDevice::configureStreamsLocked(const ACaptureSessionOutputContainer* outputs) { +CameraDevice::configureStreamsLocked(const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters) { ACaptureSessionOutputContainer emptyOutput; if (outputs == nullptr) { outputs = &emptyOutput; @@ -526,7 +641,8 @@ return ret; } outputSet.insert(std::make_pair( - anw, OutputConfiguration(iGBP, outConfig.mRotation))); + anw, OutputConfiguration(iGBP, outConfig.mRotation, + OutputConfiguration::INVALID_SET_ID, outConfig.mIsShared))); } auto addSet = outputSet; std::vector<int> deleteList; @@ -568,7 +684,7 @@ msg->setObject(kSessionSpKey, mBusySession); msg->setPointer(kCallbackFpKey, (void*) mBusySession->mUserSessionCallback.onReady); mBusySession.clear(); - msg->post(); + postSessionMsgAndCleanup(msg); } mIdle = true; @@ -601,7 +717,11 @@ mConfiguredOutputs.insert(std::make_pair(streamId, outputPair)); } - remoteRet = mRemote->endConfigure(/*isConstrainedHighSpeed*/ false); + CameraMetadata params; + if ((sessionParameters != nullptr) && (sessionParameters->settings != nullptr)) { + params.append(sessionParameters->settings->getInternalData()); + } + remoteRet = mRemote->endConfigure(/*isConstrainedHighSpeed*/ false, params); if (remoteRet.serviceSpecificErrorCode() == hardware::ICameraService::ERROR_ILLEGAL_ARGUMENT) { ALOGE("Camera device %s cannnot support app output configuration: %s", getId(), remoteRet.toString8().string()); @@ -716,19 +836,26 @@ setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_SERVICE); return; } - ANativeWindow* anw = outputPairIt->second.first; - ALOGV("Camera %s Lost output buffer for ANW %p frame %" PRId64, - getId(), anw, frameNumber); + const auto& gbps = outputPairIt->second.second.getGraphicBufferProducers(); + for (const auto& outGbp : gbps) { + for (auto surface : request->mSurfaceList) { + if (surface->getIGraphicBufferProducer() == outGbp) { + ANativeWindow* anw = static_cast<ANativeWindow*>(surface.get()); + ALOGV("Camera %s Lost output buffer for ANW %p frame %" PRId64, + getId(), anw, frameNumber); - sp<AMessage> msg = new AMessage(kWhatCaptureBufferLost, mHandler); - msg->setPointer(kContextKey, cbh.mCallbacks.context); - msg->setObject(kSessionSpKey, session); - msg->setPointer(kCallbackFpKey, (void*) onBufferLost); - msg->setObject(kCaptureRequestKey, request); - msg->setPointer(kAnwKey, (void*) anw); - msg->setInt64(kFrameNumberKey, frameNumber); - msg->post(); + sp<AMessage> msg = new AMessage(kWhatCaptureBufferLost, mHandler); + msg->setPointer(kContextKey, cbh.mCallbacks.context); + msg->setObject(kSessionSpKey, session); + msg->setPointer(kCallbackFpKey, (void*) onBufferLost); + msg->setObject(kCaptureRequestKey, request); + msg->setPointer(kAnwKey, (void*) anw); + msg->setInt64(kFrameNumberKey, frameNumber); + postSessionMsgAndCleanup(msg); + } + } + } } else { // Handle other capture failures // Fire capture failure callback if there is one registered ACameraCaptureSession_captureCallback_failed onError = cbh.mCallbacks.onCaptureFailed; @@ -746,7 +873,7 @@ msg->setPointer(kCallbackFpKey, (void*) onError); msg->setObject(kCaptureRequestKey, request); msg->setObject(kCaptureFailureKey, failure); - msg->post(); + postSessionMsgAndCleanup(msg); // Update tracker mFrameNumberTracker.updateTracker(frameNumber, /*isError*/true); @@ -769,6 +896,9 @@ case kWhatCaptureBufferLost: ALOGV("%s: Received msg %d", __FUNCTION__, msg->what()); break; + case kWhatCleanUpSessions: + mCachedSessions.clear(); + return; default: ALOGE("%s:Error: unknown device callback %d", __FUNCTION__, msg->what()); return; @@ -842,6 +972,7 @@ return; } sp<ACameraCaptureSession> session(static_cast<ACameraCaptureSession*>(obj.get())); + mCachedSessions.push(session); sp<CaptureRequest> requestSp = nullptr; switch (msg->what()) { case kWhatCaptureStart: @@ -1053,7 +1184,7 @@ msg->setObject(kSessionSpKey, cbh.mSession); msg->setPointer(kCallbackFpKey, (void*) cbh.mCallbacks.onCaptureSequenceAborted); msg->setInt32(kSequenceIdKey, sequenceId); - msg->post(); + postSessionMsgAndCleanup(msg); } else { // Use mSequenceLastFrameNumberMap to track mSequenceLastFrameNumberMap.insert(std::make_pair(sequenceId, lastFrameNumber)); @@ -1110,7 +1241,7 @@ // before cbh goes out of scope and causing we call the session // destructor while holding device lock cbh.mSession.clear(); - msg->post(); + postSessionMsgAndCleanup(msg); } // No need to track sequence complete if there is no callback registered @@ -1137,6 +1268,7 @@ return ret; // device has been closed } + sp<ACameraCaptureSession> session = dev->mCurrentSession.promote(); Mutex::Autolock _l(dev->mDeviceLock); if (dev->mRemote == nullptr) { return ret; // device has been closed @@ -1145,10 +1277,10 @@ case ERROR_CAMERA_DISCONNECTED: { // Camera is disconnected, close the session and expect no more callbacks - if (dev->mCurrentSession != nullptr) { - dev->mCurrentSession->closeByDevice(); - dev->mCurrentSession = nullptr; + if (session != nullptr) { + session->closeByDevice(); } + dev->mCurrentSession = nullptr; sp<AMessage> msg = new AMessage(kWhatOnDisconnected, dev->mHandler); msg->setPointer(kContextKey, dev->mAppCallbacks.context); msg->setPointer(kDeviceKey, (void*) dev->getWrapper()); @@ -1216,6 +1348,7 @@ dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); return ret; } + sp<AMessage> msg = new AMessage(kWhatSessionStateCb, dev->mHandler); msg->setPointer(kContextKey, dev->mBusySession->mUserSessionCallback.context); msg->setObject(kSessionSpKey, dev->mBusySession); @@ -1223,7 +1356,7 @@ // Make sure we clear the sp first so the session destructor can // only happen on handler thread (where we don't hold device/session lock) dev->mBusySession.clear(); - msg->post(); + dev->postSessionMsgAndCleanup(msg); } dev->mIdle = true; dev->mFlushing = false; @@ -1265,7 +1398,7 @@ msg->setPointer(kCallbackFpKey, (void*) onStart); msg->setObject(kCaptureRequestKey, request); msg->setInt64(kTimeStampKey, timestamp); - msg->post(); + dev->postSessionMsgAndCleanup(msg); } return ret; } @@ -1273,7 +1406,9 @@ binder::Status CameraDevice::ServiceCallback::onResultReceived( const CameraMetadata& metadata, - const CaptureResultExtras& resultExtras) { + const CaptureResultExtras& resultExtras, + const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) { + (void) physicalResultInfos; binder::Status ret = binder::Status::ok(); sp<CameraDevice> dev = mDevice.promote(); @@ -1328,7 +1463,7 @@ msg->setPointer(kCallbackFpKey, (void*) onResult); msg->setObject(kCaptureRequestKey, request); msg->setObject(kCaptureResultKey, result); - msg->post(); + dev->postSessionMsgAndCleanup(msg); } if (!isPartialResult) {
diff --git a/camera/ndk/impl/ACameraDevice.h b/camera/ndk/impl/ACameraDevice.h index 855efe1..1369148 100644 --- a/camera/ndk/impl/ACameraDevice.h +++ b/camera/ndk/impl/ACameraDevice.h
@@ -36,12 +36,13 @@ #include <camera/camera2/OutputConfiguration.h> #include <camera/camera2/CaptureRequest.h> -#include <camera/NdkCameraDevice.h> +#include <camera/NdkCameraManager.h> +#include <camera/NdkCameraCaptureSession.h> #include "ACameraMetadata.h" namespace android { -// Wrap ACameraCaptureFailure so it can be ref-counter +// Wrap ACameraCaptureFailure so it can be ref-counted struct CameraCaptureFailure : public RefBase, public ACameraCaptureFailure {}; class CameraDevice final : public RefBase { @@ -59,6 +60,7 @@ camera_status_t createCaptureSession( const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters, const ACameraCaptureSession_stateCallbacks* callbacks, /*out*/ACameraCaptureSession** session); @@ -72,7 +74,8 @@ binder::Status onCaptureStarted(const CaptureResultExtras& resultExtras, int64_t timestamp) override; binder::Status onResultReceived(const CameraMetadata& metadata, - const CaptureResultExtras& resultExtras) override; + const CaptureResultExtras& resultExtras, + const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) override; binder::Status onPrepared(int streamId) override; binder::Status onRequestQueueEmpty() override; binder::Status onRepeatingRequestError(int64_t lastFrameNumber, @@ -96,7 +99,7 @@ // device goes into fatal error state after this void setCameraDeviceErrorLocked(camera_status_t error); - void disconnectLocked(); // disconnect from camera service + void disconnectLocked(sp<ACameraCaptureSession>& session); // disconnect from camera service camera_status_t stopRepeatingLocked(); @@ -122,7 +125,9 @@ /*out*/int* captureSequenceId, bool isRepeating); - static camera_status_t allocateCaptureRequest( + camera_status_t updateOutputConfigurationLocked(ACaptureSessionOutput *output); + + camera_status_t allocateCaptureRequest( const ACaptureRequest* request, sp<CaptureRequest>& outReq); static ACaptureRequest* allocateACaptureRequest(sp<CaptureRequest>& req); @@ -136,7 +141,11 @@ // For capture session to notify its end of life void notifySessionEndOfLifeLocked(ACameraCaptureSession* session); - camera_status_t configureStreamsLocked(const ACaptureSessionOutputContainer* outputs); + camera_status_t configureStreamsLocked(const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters); + + // Input message will be posted and cleared after this returns + void postSessionMsgAndCleanup(sp<AMessage>& msg); static camera_status_t getIGBPfromAnw( ANativeWindow* anw, sp<IGraphicBufferProducer>& out); @@ -185,7 +194,9 @@ kWhatCaptureFail, // onCaptureFailed kWhatCaptureSeqEnd, // onCaptureSequenceCompleted kWhatCaptureSeqAbort, // onCaptureSequenceAborted - kWhatCaptureBufferLost // onCaptureBufferLost + kWhatCaptureBufferLost,// onCaptureBufferLost + // Internal cleanup + kWhatCleanUpSessions // Cleanup cached sp<ACameraCaptureSession> }; static const char* kContextKey; static const char* kDeviceKey; @@ -199,10 +210,16 @@ static const char* kSequenceIdKey; static const char* kFrameNumberKey; static const char* kAnwKey; + class CallbackHandler : public AHandler { public: - CallbackHandler() {} void onMessageReceived(const sp<AMessage> &msg) override; + + private: + // This handler will cache all capture session sp until kWhatCleanUpSessions + // is processed. This is used to guarantee the last session reference is always + // being removed in callback thread without holding camera device lock + Vector<sp<ACameraCaptureSession>> mCachedSessions; }; sp<CallbackHandler> mHandler; @@ -210,7 +227,7 @@ * Capture session related members * ***********************************/ // The current active session - ACameraCaptureSession* mCurrentSession = nullptr; + wp<ACameraCaptureSession> mCurrentSession; bool mFlushing = false; int mNextSessionId = 0; @@ -295,9 +312,10 @@ camera_status_t createCaptureSession( const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters, const ACameraCaptureSession_stateCallbacks* callbacks, /*out*/ACameraCaptureSession** session) { - return mDevice->createCaptureSession(outputs, callbacks, session); + return mDevice->createCaptureSession(outputs, sessionParameters, callbacks, session); } /***********************
diff --git a/camera/ndk/impl/ACameraManager.cpp b/camera/ndk/impl/ACameraManager.cpp index 3f64bcc..c59d0e7 100644 --- a/camera/ndk/impl/ACameraManager.cpp +++ b/camera/ndk/impl/ACameraManager.cpp
@@ -221,7 +221,7 @@ mCallbacks.erase(cb); } -void CameraManagerGlobal::getCameraIdList(std::vector<String8> *cameraIds) { +void CameraManagerGlobal::getCameraIdList(std::vector<String8>* cameraIds) { // Ensure that we have initialized/refreshed the list of available devices auto cs = getCameraService(); Mutex::Autolock _l(mLock); @@ -340,6 +340,9 @@ msg->setString(kCameraIdKey, AString(cameraId)); msg->post(); } + if (status == hardware::ICameraServiceListener::STATUS_NOT_PRESENT) { + mDeviceStatusMap.erase(cameraId); + } } } // namespace android
diff --git a/camera/ndk/impl/ACameraManager.h b/camera/ndk/impl/ACameraManager.h index 4a172f3..cc42f77 100644 --- a/camera/ndk/impl/ACameraManager.h +++ b/camera/ndk/impl/ACameraManager.h
@@ -19,6 +19,7 @@ #include <camera/NdkCameraManager.h> +#include <android-base/parseint.h> #include <android/hardware/ICameraService.h> #include <android/hardware/BnCameraServiceListener.h> #include <camera/CameraMetadata.h> @@ -140,8 +141,29 @@ static bool validStatus(int32_t status); static bool isStatusAvailable(int32_t status); + // The sort logic must match the logic in + // libcameraservice/common/CameraProviderManager.cpp::getAPI1CompatibleCameraDeviceIds + struct CameraIdComparator { + bool operator()(const String8& a, const String8& b) const { + uint32_t aUint = 0, bUint = 0; + bool aIsUint = base::ParseUint(a.c_str(), &aUint); + bool bIsUint = base::ParseUint(b.c_str(), &bUint); + + // Uint device IDs first + if (aIsUint && bIsUint) { + return aUint < bUint; + } else if (aIsUint) { + return true; + } else if (bIsUint) { + return false; + } + // Simple string compare if both id are not uint + return a < b; + } + }; + // Map camera_id -> status - std::map<String8, int32_t> mDeviceStatusMap; + std::map<String8, int32_t, CameraIdComparator> mDeviceStatusMap; // For the singleton instance static Mutex sLock;
diff --git a/camera/ndk/impl/ACameraMetadata.cpp b/camera/ndk/impl/ACameraMetadata.cpp index 7b33c32..fc00a2d 100644 --- a/camera/ndk/impl/ACameraMetadata.cpp +++ b/camera/ndk/impl/ACameraMetadata.cpp
@@ -235,7 +235,7 @@ } const CameraMetadata& -ACameraMetadata::getInternalData() { +ACameraMetadata::getInternalData() const { return mData; } @@ -305,6 +305,7 @@ case ACAMERA_STATISTICS_FACE_DETECT_MODE: case ACAMERA_STATISTICS_HOT_PIXEL_MAP_MODE: case ACAMERA_STATISTICS_LENS_SHADING_MAP_MODE: + case ACAMERA_STATISTICS_OIS_DATA_MODE: case ACAMERA_TONEMAP_CURVE_BLUE: case ACAMERA_TONEMAP_CURVE_GREEN: case ACAMERA_TONEMAP_CURVE_RED: @@ -312,6 +313,7 @@ case ACAMERA_TONEMAP_GAMMA: case ACAMERA_TONEMAP_PRESET_CURVE: case ACAMERA_BLACK_LEVEL_LOCK: + case ACAMERA_DISTORTION_CORRECTION_MODE: return true; default: return false;
diff --git a/camera/ndk/impl/ACameraMetadata.h b/camera/ndk/impl/ACameraMetadata.h index 143efc7..0fd7efa 100644 --- a/camera/ndk/impl/ACameraMetadata.h +++ b/camera/ndk/impl/ACameraMetadata.h
@@ -64,7 +64,7 @@ void filterUnsupportedFeatures(); // Hide features not yet supported by NDK void filterStreamConfigurations(); // Hide input streams, translate hal format to NDK formats - const CameraMetadata& getInternalData(); + const CameraMetadata& getInternalData() const; template<typename INTERNAL_T, typename NDK_T> camera_status_t updateImpl(uint32_t tag, uint32_t count, const NDK_T* data) {
diff --git a/camera/ndk/impl/ACaptureRequest.h b/camera/ndk/impl/ACaptureRequest.h index e5b453e..06b2cc3 100644 --- a/camera/ndk/impl/ACaptureRequest.h +++ b/camera/ndk/impl/ACaptureRequest.h
@@ -45,8 +45,19 @@ }; struct ACaptureRequest { + camera_status_t setContext(void* ctx) { + context = ctx; + return ACAMERA_OK; + } + + camera_status_t getContext(void** ctx) const { + *ctx = context; + return ACAMERA_OK; + } + ACameraMetadata* settings; ACameraOutputTargets* targets; + void* context; }; #endif // _ACAPTURE_REQUEST_H
diff --git a/camera/ndk/include/camera/NdkCameraCaptureSession.h b/camera/ndk/include/camera/NdkCameraCaptureSession.h index d96f538..51cef8c 100644 --- a/camera/ndk/include/camera/NdkCameraCaptureSession.h +++ b/camera/ndk/include/camera/NdkCameraCaptureSession.h
@@ -33,6 +33,7 @@ * Do not #include files that aren't part of the NDK. */ #include <sys/cdefs.h> +#include <stdbool.h> #include <android/native_window.h> #include "NdkCameraError.h" @@ -591,6 +592,54 @@ #endif /* __ANDROID_API__ >= 24 */ +#if __ANDROID_API__ >= 28 + +typedef struct ACaptureSessionOutput ACaptureSessionOutput; + +/** + * Update shared ACaptureSessionOutput. + * + * <p>A shared ACaptureSessionOutput (see {@link ACaptureSessionSharedOutput_create}) that + * was modified via calls to {@link ACaptureSessionSharedOutput_add} or + * {@link ACaptureSessionSharedOutput_remove} must be updated by calling this method before its + * changes take effect. After the update call returns with {@link ACAMERA_OK}, any newly added + * native windows can be used as a target in subsequent capture requests.</p> + * + * <p>Native windows that get removed must not be part of any active repeating or single/burst + * request or have any pending results. Consider updating repeating requests via + * {@link ACaptureSessionOutput_setRepeatingRequest} and then wait for the last frame number + * when the sequence completes + * {@link ACameraCaptureSession_captureCallback#onCaptureSequenceCompleted}.</p> + * + * <p>Native windows that get added must not be part of any other registered ACaptureSessionOutput + * and must be compatible. Compatible windows must have matching format, rotation and + * consumer usage.</p> + * + * <p>A shared ACameraCaptureSession can support up to 4 additional native windows.</p> + * + * @param session the capture session of interest + * @param output the modified output configuration + * + * @return <ul><li> + * {@link ACAMERA_OK} if the method succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if session or output is NULL; or output + * contains invalid native windows; or if an attempt was made to add + * a native window to a different output configuration; or new native window is not + * compatible; or any removed native window still has pending requests;</li> + * <li>{@link ACAMERA_ERROR_INVALID_OPERATION} if output configuration is not shared (see + * {@link ACaptureSessionSharedOutput_create}; or the number of additional + * native windows goes beyond the supported limit.</li> + * <li>{@link ACAMERA_ERROR_SESSION_CLOSED} if the capture session has been closed</li> + * <li>{@link ACAMERA_ERROR_CAMERA_DISCONNECTED} if the camera device is closed</li> + * <li>{@link ACAMERA_ERROR_CAMERA_DEVICE} if the camera device encounters fatal error</li> + * <li>{@link ACAMERA_ERROR_CAMERA_SERVICE} if the camera service encounters fatal + * error</li> + * <li>{@link ACAMERA_ERROR_UNKNOWN} if the method fails for some other reasons</li></ul> + */ +camera_status_t ACameraCaptureSession_updateSharedOutput(ACameraCaptureSession* session, + ACaptureSessionOutput* output); +#endif /* __ANDROID_API__ >= 28 */ + __END_DECLS #endif /* _NDK_CAMERA_CAPTURE_SESSION_H */
diff --git a/camera/ndk/include/camera/NdkCameraDevice.h b/camera/ndk/include/camera/NdkCameraDevice.h index 9b7f6f4..92dad1c 100644 --- a/camera/ndk/include/camera/NdkCameraDevice.h +++ b/camera/ndk/include/camera/NdkCameraDevice.h
@@ -90,18 +90,18 @@ }; /** - * Camera device state callbacks to be used in {@link ACameraDevice_stateCallbacks}. + * Camera device state callbacks to be used in {@link ACameraDevice_StateCallbacks}. * - * @param context The optional context in {@link ACameraDevice_stateCallbacks} will be + * @param context The optional context in {@link ACameraDevice_StateCallbacks} will be * passed to this callback. * @param device The {@link ACameraDevice} that is being disconnected. */ typedef void (*ACameraDevice_StateCallback)(void* context, ACameraDevice* device); /** - * Camera device error state callbacks to be used in {@link ACameraDevice_stateCallbacks}. + * Camera device error state callbacks to be used in {@link ACameraDevice_StateCallbacks}. * - * @param context The optional context in {@link ACameraDevice_stateCallbacks} will be + * @param context The optional context in {@link ACameraDevice_StateCallbacks} will be * passed to this callback. * @param device The {@link ACameraDevice} that is being disconnected. * @param error The error code describes the cause of this error callback. See the folowing @@ -150,7 +150,12 @@ * */ ACameraDevice_ErrorStateCallback onError; -} ACameraDevice_stateCallbacks; +} ACameraDevice_StateCallbacks; + +/** + * For backward compatiblity. + */ +typedef ACameraDevice_StateCallbacks ACameraDevice_stateCallbacks; /** * Close the connection and free this ACameraDevice synchronously. Access to the ACameraDevice @@ -251,6 +256,7 @@ * @see ACameraDevice_createCaptureRequest */ TEMPLATE_MANUAL = 6, + } ACameraDevice_request_template; /** @@ -637,6 +643,14 @@ * target combinations with sizes outside of these guarantees, but this can only be tested for * by attempting to create a session with such targets.</p> * + * <p>Exception on 176x144 (QCIF) resolution: + * Camera devices usually have a fixed capability for downscaling from larger resolution to + * smaller, and the QCIF resolution sometimes cannot be fully supported due to this + * limitation on devices with high-resolution image sensors. Therefore, trying to configure a + * QCIF resolution stream together with any other stream larger than 1920x1080 resolution + * (either width or height) might not be supported, and capture session creation will fail if it + * is not.</p> + * * @param device the camera device of interest. * @param outputs the {@link ACaptureSessionOutputContainer} describes all output streams. * @param callbacks the {@link ACameraCaptureSession_stateCallbacks capture session state callbacks}. @@ -661,9 +675,102 @@ #endif /* __ANDROID_API__ >= 24 */ +#if __ANDROID_API__ >= 28 + +/** + * Create a shared ACaptureSessionOutput object. + * + * <p>The ACaptureSessionOutput is used in {@link ACaptureSessionOutputContainer_add} method to add + * an output {@link ANativeWindow} to ACaptureSessionOutputContainer. Use + * {@link ACaptureSessionOutput_free} to free the object and its memory after application no longer + * needs the {@link ACaptureSessionOutput}. A shared ACaptureSessionOutput can be further modified + * via {@link ACaptureSessionSharedOutput_add} or {@link ACaptureSessionSharedOutput_remove} and + * must be updated via {@link ACameraCaptureSession_updateSharedOutput}.</p> + * + * @param anw the {@link ANativeWindow} to be associated with the {@link ACaptureSessionOutput} + * @param output the output {@link ACaptureSessionOutput} will be stored here if the + * method call succeeds. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds. The created container will be + * filled in the output argument.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if anw or output is NULL.</li></ul> + * + * @see ACaptureSessionOutputContainer_add + */ +camera_status_t ACaptureSessionSharedOutput_create( + ANativeWindow* anw, /*out*/ACaptureSessionOutput** output); + +/** + * Add a native window to shared ACaptureSessionOutput. + * + * The ACaptureSessionOutput must be created via {@link ACaptureSessionSharedOutput_create}. + * + * @param output the shared ACaptureSessionOutput to be extended. + * @param anw The new native window. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if anw or output is NULL; or output is not + * shared see {@link ACaptureSessionSharedOutput_create}; or anw matches with the native + * window associated with ACaptureSessionOutput; or anw is already present inside + * ACaptureSessionOutput.</li></ul> + */ +camera_status_t ACaptureSessionSharedOutput_add(ACaptureSessionOutput *output, ANativeWindow *anw); + +/** + * Remove a native window from shared ACaptureSessionOutput. + * + * @param output the {@link ACaptureSessionOutput} to be modified. + * @param anw The native window to be removed. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if anw or output is NULL; or output is not + * shared see {@link ACaptureSessionSharedOutput_create}; or anw matches with the native + * window associated with ACaptureSessionOutput; or anw is not present inside + * ACaptureSessionOutput.</li></ul> + */ +camera_status_t ACaptureSessionSharedOutput_remove(ACaptureSessionOutput *output, + ANativeWindow* anw); + +/** + * Create a new camera capture session similar to {@link ACameraDevice_createCaptureSession}. This + * function allows clients to pass additional session parameters during session initialization. For + * further information about session parameters see {@link ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS}. + * + * @param device the camera device of interest. + * @param outputs the {@link ACaptureSessionOutputContainer} describes all output streams. + * @param sessionParameters An optional capture request that contains the initial values of session + * parameters advertised in + * {@link ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS}. + * @param callbacks the {@link ACameraCaptureSession_stateCallbacks} + * capture session state callbacks. + * @param session the created {@link ACameraCaptureSession} will be filled here if the method call + * succeeds. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds. The created capture session will be + * filled in session argument.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if any of device, outputs, callbacks or + * session is NULL.</li> + * <li>{@link ACAMERA_ERROR_CAMERA_DISCONNECTED} if the camera device is closed.</li> + * <li>{@link ACAMERA_ERROR_CAMERA_DEVICE} if the camera device encounters fatal error.</li> + * <li>{@link ACAMERA_ERROR_CAMERA_SERVICE} if the camera service encounters fatal error. + * </li> + * <li>{@link ACAMERA_ERROR_UNKNOWN} if the method fails for some other reasons.</li></ul> + */ +camera_status_t ACameraDevice_createCaptureSessionWithSessionParameters( + ACameraDevice* device, + const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters, + const ACameraCaptureSession_stateCallbacks* callbacks, + /*out*/ACameraCaptureSession** session); + +#endif /* __ANDROID_API__ >= 28 */ + __END_DECLS #endif /* _NDK_CAMERA_DEVICE_H */ /** @} */ -
diff --git a/camera/ndk/include/camera/NdkCameraManager.h b/camera/ndk/include/camera/NdkCameraManager.h index 5b5c98b..e5b3ad8 100644 --- a/camera/ndk/include/camera/NdkCameraManager.h +++ b/camera/ndk/include/camera/NdkCameraManager.h
@@ -232,18 +232,18 @@ * priority when accessing the camera, and this method will succeed even if the camera device is * in use by another camera API client. Any lower-priority application that loses control of the * camera in this way will receive an - * {@link ACameraDevice_stateCallbacks#onDisconnected} callback.</p> + * {@link ACameraDevice_StateCallbacks#onDisconnected} callback.</p> * * <p>Once the camera is successfully opened,the ACameraDevice can then be set up * for operation by calling {@link ACameraDevice_createCaptureSession} and * {@link ACameraDevice_createCaptureRequest}.</p> * * <p>If the camera becomes disconnected after this function call returns, - * {@link ACameraDevice_stateCallbacks#onDisconnected} with a + * {@link ACameraDevice_StateCallbacks#onDisconnected} with a * ACameraDevice in the disconnected state will be called.</p> * * <p>If the camera runs into error after this function call returns, - * {@link ACameraDevice_stateCallbacks#onError} with a + * {@link ACameraDevice_StateCallbacks#onError} with a * ACameraDevice in the error state will be called.</p> * * @param manager the {@link ACameraManager} of interest.
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h index 629d75a..bee1a46 100644 --- a/camera/ndk/include/camera/NdkCameraMetadataTags.h +++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -69,6 +69,8 @@ ACAMERA_SYNC, ACAMERA_REPROCESS, ACAMERA_DEPTH, + ACAMERA_LOGICAL_MULTI_CAMERA, + ACAMERA_DISTORTION_CORRECTION, ACAMERA_SECTION_COUNT, ACAMERA_VENDOR = 0x8000 @@ -104,6 +106,12 @@ ACAMERA_SYNC_START = ACAMERA_SYNC << 16, ACAMERA_REPROCESS_START = ACAMERA_REPROCESS << 16, ACAMERA_DEPTH_START = ACAMERA_DEPTH << 16, + ACAMERA_LOGICAL_MULTI_CAMERA_START + = ACAMERA_LOGICAL_MULTI_CAMERA + << 16, + ACAMERA_DISTORTION_CORRECTION_START + = ACAMERA_DISTORTION_CORRECTION + << 16, ACAMERA_VENDOR_START = ACAMERA_VENDOR << 16 } acamera_metadata_section_start_t; @@ -471,15 +479,26 @@ * Otherwise will always be present.</p> * <p>The maximum number of regions supported by the device is determined by the value * of android.control.maxRegionsAe.</p> - * <p>The data representation is int[5 * area_count]. - * Every five elements represent a metering region of (xmin, ymin, xmax, ymax, weight). - * The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and - * ymax.</p> - * <p>The coordinate system is based on the active pixel array, - * with (0,0) being the top-left pixel in the active pixel array, and + * <p>For devices not supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system always follows that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with (0,0) being + * the top-left pixel in the active pixel array, and * (ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.width - 1, - * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the - * bottom-right pixel in the active pixel array.</p> + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right pixel in the + * active pixel array.</p> + * <p>For devices supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system depends on the mode being set. + * When the distortion correction mode is OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and + * (ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE.width - 1, + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right + * pixel in the pre-correction active pixel array. + * When the distortion correction mode is not OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the active array, and + * (ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.width - 1, + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right pixel in the + * active pixel array.</p> * <p>The weight must be within <code>[0, 1000]</code>, and represents a weight * for every pixel in the area. This means that a large metering area * with the same weight as a smaller area will have more effect in @@ -495,9 +514,15 @@ * region and output only the intersection rectangle as the metering region in the result * metadata. If the region is entirely outside the crop region, it will be ignored and * not reported in the result metadata.</p> + * <p>The data representation is <code>int[5 * area_count]</code>. + * Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>. + * The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and + * ymax.</p> * + * @see ACAMERA_DISTORTION_CORRECTION_MODE * @see ACAMERA_SCALER_CROP_REGION * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ ACAMERA_CONTROL_AE_REGIONS = // int32[5*area_count] ACAMERA_CONTROL_START + 4, @@ -633,15 +658,26 @@ * Otherwise will always be present.</p> * <p>The maximum number of focus areas supported by the device is determined by the value * of android.control.maxRegionsAf.</p> - * <p>The data representation is int[5 * area_count]. - * Every five elements represent a metering region of (xmin, ymin, xmax, ymax, weight). - * The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and - * ymax.</p> - * <p>The coordinate system is based on the active pixel array, - * with (0,0) being the top-left pixel in the active pixel array, and + * <p>For devices not supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system always follows that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with (0,0) being + * the top-left pixel in the active pixel array, and * (ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.width - 1, - * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the - * bottom-right pixel in the active pixel array.</p> + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right pixel in the + * active pixel array.</p> + * <p>For devices supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system depends on the mode being set. + * When the distortion correction mode is OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and + * (ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE.width - 1, + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right + * pixel in the pre-correction active pixel array. + * When the distortion correction mode is not OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the active array, and + * (ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.width - 1, + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right pixel in the + * active pixel array.</p> * <p>The weight must be within <code>[0, 1000]</code>, and represents a weight * for every pixel in the area. This means that a large metering area * with the same weight as a smaller area will have more effect in @@ -651,15 +687,22 @@ * is used, all non-zero weights will have the same effect. A region with 0 weight is * ignored.</p> * <p>If all regions have 0 weight, then no specific metering area needs to be used by the - * camera device.</p> + * camera device. The capture result will either be a zero weight region as well, or + * the region selected by the camera device as the focus area of interest.</p> * <p>If the metering region is outside the used ACAMERA_SCALER_CROP_REGION returned in * capture result metadata, the camera device will ignore the sections outside the crop * region and output only the intersection rectangle as the metering region in the result * metadata. If the region is entirely outside the crop region, it will be ignored and * not reported in the result metadata.</p> + * <p>The data representation is <code>int[5 * area_count]</code>. + * Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>. + * The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and + * ymax.</p> * + * @see ACAMERA_DISTORTION_CORRECTION_MODE * @see ACAMERA_SCALER_CROP_REGION * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ ACAMERA_CONTROL_AF_REGIONS = // int32[5*area_count] ACAMERA_CONTROL_START + 8, @@ -791,15 +834,26 @@ * Otherwise will always be present.</p> * <p>The maximum number of regions supported by the device is determined by the value * of android.control.maxRegionsAwb.</p> - * <p>The data representation is int[5 * area_count]. - * Every five elements represent a metering region of (xmin, ymin, xmax, ymax, weight). - * The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and - * ymax.</p> - * <p>The coordinate system is based on the active pixel array, - * with (0,0) being the top-left pixel in the active pixel array, and + * <p>For devices not supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system always follows that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with (0,0) being + * the top-left pixel in the active pixel array, and * (ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.width - 1, - * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the - * bottom-right pixel in the active pixel array.</p> + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right pixel in the + * active pixel array.</p> + * <p>For devices supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system depends on the mode being set. + * When the distortion correction mode is OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and + * (ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE.width - 1, + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right + * pixel in the pre-correction active pixel array. + * When the distortion correction mode is not OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the active array, and + * (ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.width - 1, + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.height - 1) being the bottom-right pixel in the + * active pixel array.</p> * <p>The weight must range from 0 to 1000, and represents a weight * for every pixel in the area. This means that a large metering area * with the same weight as a smaller area will have more effect in @@ -815,9 +869,15 @@ * region and output only the intersection rectangle as the metering region in the result * metadata. If the region is entirely outside the crop region, it will be ignored and * not reported in the result metadata.</p> + * <p>The data representation is <code>int[5 * area_count]</code>. + * Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>. + * The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and + * ymax.</p> * + * @see ACAMERA_DISTORTION_CORRECTION_MODE * @see ACAMERA_SCALER_CROP_REGION * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ ACAMERA_CONTROL_AWB_REGIONS = // int32[5*area_count] ACAMERA_CONTROL_START + 12, @@ -837,10 +897,13 @@ * * <p>This control (except for MANUAL) is only effective if * <code>ACAMERA_CONTROL_MODE != OFF</code> and any 3A routine is active.</p> - * <p>ZERO_SHUTTER_LAG will be supported if ACAMERA_REQUEST_AVAILABLE_CAPABILITIES - * contains PRIVATE_REPROCESSING or YUV_REPROCESSING. MANUAL will be supported if - * ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains MANUAL_SENSOR. Other intent values are - * always supported.</p> + * <p>All intents are supported by all devices, except that: + * * ZERO_SHUTTER_LAG will be supported if ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains + * PRIVATE_REPROCESSING or YUV_REPROCESSING. + * * MANUAL will be supported if ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains + * MANUAL_SENSOR. + * * MOTION_TRACKING will be supported if ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains + * MOTION_TRACKING.</p> * * @see ACAMERA_CONTROL_MODE * @see ACAMERA_REQUEST_AVAILABLE_CAPABILITIES @@ -885,11 +948,10 @@ * <p>When set to AUTO, the individual algorithm controls in * ACAMERA_CONTROL_* are in effect, such as ACAMERA_CONTROL_AF_MODE.</p> * <p>When set to USE_SCENE_MODE, the individual controls in - * ACAMERA_CONTROL_* are mostly disabled, and the camera device implements - * one of the scene mode settings (such as ACTION, SUNSET, or PARTY) - * as it wishes. The camera device scene mode 3A settings are provided by - * capture results {@link ACameraMetadata} from - * {@link ACameraCaptureSession_captureCallback_result}.</p> + * ACAMERA_CONTROL_* are mostly disabled, and the camera device + * implements one of the scene mode settings (such as ACTION, + * SUNSET, or PARTY) as it wishes. The camera device scene mode + * 3A settings are provided by {@link ACameraCaptureSession_captureCallback_result capture results}.</p> * <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference * is that this frame will not be used by camera device background 3A statistics * update, as if this frame is never captured. This mode can be used in the scenario @@ -1043,20 +1105,18 @@ * <p>For constant-framerate recording, for each normal * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html">CamcorderProfile</a>, that is, a * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html">CamcorderProfile</a> that has - * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#quality">quality</a> - * in the range [ - * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#QUALITY_LOW">QUALITY_LOW</a>, - * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#QUALITY_2160P">QUALITY_2160P</a>], - * if the profile is supported by the device and has - * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#videoFrameRate">videoFrameRate</a> - * <code>x</code>, this list will always include (<code>x</code>,<code>x</code>).</p> + * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#quality">quality</a> in + * the range [<a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#QUALITY_LOW">QUALITY_LOW</a>, + * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#QUALITY_2160P">QUALITY_2160P</a>], if the profile is + * supported by the device and has + * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#videoFrameRate">videoFrameRate</a> <code>x</code>, this list will + * always include (<code>x</code>,<code>x</code>).</p> * </li> * <li> * <p>Also, a camera device must either not support any * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html">CamcorderProfile</a>, * or support at least one - * normal <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html">CamcorderProfile</a> - * that has + * normal <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html">CamcorderProfile</a> that has * <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html#videoFrameRate">videoFrameRate</a> <code>x</code> >= 24.</p> * </li> * </ul> @@ -1282,7 +1342,7 @@ * <p>State | Transition Cause | New State | Notes * :------------:|:----------------:|:---------:|:-----------------------: * INACTIVE | | INACTIVE | Camera device auto exposure algorithm is disabled</p> - * <p>When ACAMERA_CONTROL_AE_MODE is AE_MODE_ON_*:</p> + * <p>When ACAMERA_CONTROL_AE_MODE is AE_MODE_ON*:</p> * <p>State | Transition Cause | New State | Notes * :-------------:|:--------------------------------------------:|:--------------:|:-----------------: * INACTIVE | Camera device initiates AE scan | SEARCHING | Values changing @@ -1303,10 +1363,13 @@ * LOCKED | aeLock is ON and aePrecaptureTrigger is CANCEL| LOCKED | Precapture trigger is ignored when AE is already locked * Any state (excluding LOCKED) | ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER is START | PRECAPTURE | Start AE precapture metering sequence * Any state (excluding LOCKED) | ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER is CANCEL| INACTIVE | Currently active precapture metering sequence is canceled</p> + * <p>If the camera device supports AE external flash mode (ON_EXTERNAL_FLASH is included in + * ACAMERA_CONTROL_AE_AVAILABLE_MODES), ACAMERA_CONTROL_AE_STATE must be FLASH_REQUIRED after + * the camera device finishes AE scan and it's too dark without flash.</p> * <p>For the above table, the camera device may skip reporting any state changes that happen * without application intervention (i.e. mode switch, trigger, locking). Any state that * can be skipped in that manner is called a transient state.</p> - * <p>For example, for above AE modes (AE_MODE_ON_*), in addition to the state transitions + * <p>For example, for above AE modes (AE_MODE_ON*), in addition to the state transitions * listed in above table, it is also legal for the camera device to skip one or more * transient states between two results. See below table for examples:</p> * <p>State | Transition Cause | New State | Notes @@ -1319,9 +1382,11 @@ * CONVERGED | Camera device finished AE scan | FLASH_REQUIRED | Converged but too dark w/o flash after a new scan, transient states are skipped by camera device. * FLASH_REQUIRED | Camera device finished AE scan | CONVERGED | Converged after a new scan, transient states are skipped by camera device.</p> * + * @see ACAMERA_CONTROL_AE_AVAILABLE_MODES * @see ACAMERA_CONTROL_AE_LOCK * @see ACAMERA_CONTROL_AE_MODE * @see ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER + * @see ACAMERA_CONTROL_AE_STATE * @see ACAMERA_CONTROL_MODE * @see ACAMERA_CONTROL_SCENE_MODE */ @@ -1619,13 +1684,13 @@ * compared to previous regular requests. enableZsl does not affect requests with other * capture intents.</p> * <p>For example, when requests are submitted in the following order: - * Request A: enableZsl is <code>true</code>, ACAMERA_CONTROL_CAPTURE_INTENT is PREVIEW - * Request B: enableZsl is <code>true</code>, ACAMERA_CONTROL_CAPTURE_INTENT is STILL_CAPTURE</p> + * Request A: enableZsl is ON, ACAMERA_CONTROL_CAPTURE_INTENT is PREVIEW + * Request B: enableZsl is ON, ACAMERA_CONTROL_CAPTURE_INTENT is STILL_CAPTURE</p> * <p>The output images for request B may have contents captured before the output images for * request A, and the result metadata for request B may be older than the result metadata for * request A.</p> - * <p>Note that when enableZsl is <code>true</code>, it is not guaranteed to get output images captured in the - * past for requests with STILL_CAPTURE capture intent.</p> + * <p>Note that when enableZsl is <code>true</code>, it is not guaranteed to get output images captured in + * the past for requests with STILL_CAPTURE capture intent.</p> * <p>For applications targeting SDK versions O and newer, the value of enableZsl in * TEMPLATE_STILL_CAPTURE template may be <code>true</code>. The value in other templates is always * <code>false</code> if present.</p> @@ -1638,6 +1703,26 @@ */ ACAMERA_CONTROL_ENABLE_ZSL = // byte (acamera_metadata_enum_android_control_enable_zsl_t) ACAMERA_CONTROL_START + 41, + /** + * <p>Whether a significant scene change is detected within the currently-set AF + * region(s).</p> + * + * <p>Type: byte (acamera_metadata_enum_android_control_af_scene_change_t)</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> + * </ul></p> + * + * <p>When the camera focus routine detects a change in the scene it is looking at, + * such as a large shift in camera viewpoint, significant motion in the scene, or a + * significant illumination change, this value will be set to DETECTED for a single capture + * result. Otherwise the value will be NOT_DETECTED. The threshold for detection is similar + * to what would trigger a new passive focus scan to begin in CONTINUOUS autofocus modes.</p> + * <p>This key will be available if the camera device advertises this key via {@link ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS }.</p> + */ + ACAMERA_CONTROL_AF_SCENE_CHANGE = // byte (acamera_metadata_enum_android_control_af_scene_change_t) + ACAMERA_CONTROL_START + 42, ACAMERA_CONTROL_END, /** @@ -1879,8 +1964,8 @@ * the thumbnail data will also be rotated.</p> * <p>Note that this orientation is relative to the orientation of the camera sensor, given * by ACAMERA_SENSOR_ORIENTATION.</p> - * <p>To translate from the device orientation given by the Android sensor APIs, the following - * sample code may be used:</p> + * <p>To translate from the device orientation given by the Android sensor APIs for camera + * sensors which are not EXTERNAL, the following sample code may be used:</p> * <pre><code>private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) { * if (deviceOrientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) return 0; * int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION); @@ -1899,6 +1984,8 @@ * return jpegOrientation; * } * </code></pre> + * <p>For EXTERNAL cameras the sensor orientation will always be set to 0 and the facing will + * also be set to EXTERNAL. The above code is not relevant in such case.</p> * * @see ACAMERA_SENSOR_ORIENTATION */ @@ -1959,14 +2046,14 @@ * <p>When an ACAMERA_JPEG_ORIENTATION of non-zero degree is requested, * the camera device will handle thumbnail rotation in one of the following ways:</p> * <ul> - * <li>Set the - * <a href="https://developer.android.com/reference/android/media/ExifInterface.html#TAG_ORIENTATION">EXIF orientation flag</a> + * <li>Set the <a href="https://developer.android.com/reference/android/media/ExifInterface.html#TAG_ORIENTATION">EXIF orientation flag</a> * and keep jpeg and thumbnail image data unrotated.</li> * <li>Rotate the jpeg and thumbnail image data and not set - * <a href="https://developer.android.com/reference/android/media/ExifInterface.html#TAG_ORIENTATION">EXIF orientation flag</a>. - * In this case, LIMITED or FULL hardware level devices will report rotated thumnail size - * in capture result, so the width and height will be interchanged if 90 or 270 degree - * orientation is requested. LEGACY device will always report unrotated thumbnail size.</li> + * <a href="https://developer.android.com/reference/android/media/ExifInterface.html#TAG_ORIENTATION">EXIF orientation flag</a>. In this + * case, LIMITED or FULL hardware level devices will report rotated thumnail size in + * capture result, so the width and height will be interchanged if 90 or 270 degree + * orientation is requested. LEGACY device will always report unrotated thumbnail + * size.</li> * </ul> * * @see ACAMERA_JPEG_ORIENTATION @@ -2216,37 +2303,36 @@ * </ul></p> * * <p>The position of the camera device's lens optical center, - * as a three-dimensional vector <code>(x,y,z)</code>, relative to the - * optical center of the largest camera device facing in the - * same direction as this camera, in the - * <a href="https://developer.android.com/reference/android/hardware/SensorEvent.html">Android sensor coordinate axes</a>. - * Note that only the axis definitions are shared with - * the sensor coordinate system, but not the origin.</p> - * <p>If this device is the largest or only camera device with a - * given facing, then this position will be <code>(0, 0, 0)</code>; a - * camera device with a lens optical center located 3 cm from - * the main sensor along the +X axis (to the right from the - * user's perspective) will report <code>(0.03, 0, 0)</code>.</p> - * <p>To transform a pixel coordinates between two cameras - * facing the same direction, first the source camera - * ACAMERA_LENS_RADIAL_DISTORTION must be corrected for. Then - * the source camera ACAMERA_LENS_INTRINSIC_CALIBRATION needs - * to be applied, followed by the ACAMERA_LENS_POSE_ROTATION - * of the source camera, the translation of the source camera - * relative to the destination camera, the - * ACAMERA_LENS_POSE_ROTATION of the destination camera, and - * finally the inverse of ACAMERA_LENS_INTRINSIC_CALIBRATION - * of the destination camera. This obtains a - * radial-distortion-free coordinate in the destination - * camera pixel coordinates.</p> - * <p>To compare this against a real image from the destination - * camera, the destination camera image then needs to be - * corrected for radial distortion before comparison or - * sampling.</p> + * as a three-dimensional vector <code>(x,y,z)</code>.</p> + * <p>Prior to Android P, or when ACAMERA_LENS_POSE_REFERENCE is PRIMARY_CAMERA, this position + * is relative to the optical center of the largest camera device facing in the same + * direction as this camera, in the <a href="https://developer.android.com/reference/android/hardware/SensorEvent.html">Android sensor + * coordinate axes</a>. Note that only the axis definitions are shared with the sensor + * coordinate system, but not the origin.</p> + * <p>If this device is the largest or only camera device with a given facing, then this + * position will be <code>(0, 0, 0)</code>; a camera device with a lens optical center located 3 cm + * from the main sensor along the +X axis (to the right from the user's perspective) will + * report <code>(0.03, 0, 0)</code>. Note that this means that, for many computer vision + * applications, the position needs to be negated to convert it to a translation from the + * camera to the origin.</p> + * <p>To transform a pixel coordinates between two cameras facing the same direction, first + * the source camera ACAMERA_LENS_DISTORTION must be corrected for. Then the source + * camera ACAMERA_LENS_INTRINSIC_CALIBRATION needs to be applied, followed by the + * ACAMERA_LENS_POSE_ROTATION of the source camera, the translation of the source camera + * relative to the destination camera, the ACAMERA_LENS_POSE_ROTATION of the destination + * camera, and finally the inverse of ACAMERA_LENS_INTRINSIC_CALIBRATION of the destination + * camera. This obtains a radial-distortion-free coordinate in the destination camera pixel + * coordinates.</p> + * <p>To compare this against a real image from the destination camera, the destination camera + * image then needs to be corrected for radial distortion before comparison or sampling.</p> + * <p>When ACAMERA_LENS_POSE_REFERENCE is GYROSCOPE, then this position is relative to + * the center of the primary gyroscope on the device. The axis definitions are the same as + * with PRIMARY_CAMERA.</p> * + * @see ACAMERA_LENS_DISTORTION * @see ACAMERA_LENS_INTRINSIC_CALIBRATION + * @see ACAMERA_LENS_POSE_REFERENCE * @see ACAMERA_LENS_POSE_ROTATION - * @see ACAMERA_LENS_RADIAL_DISTORTION */ ACAMERA_LENS_POSE_TRANSLATION = // float[3] ACAMERA_LENS_START + 7, @@ -2335,13 +2421,15 @@ * </code></pre> * <p>which can then be combined with the camera pose rotation * <code>R</code> and translation <code>t</code> (ACAMERA_LENS_POSE_ROTATION and - * ACAMERA_LENS_POSE_TRANSLATION, respective) to calculate the + * ACAMERA_LENS_POSE_TRANSLATION, respectively) to calculate the * complete transform from world coordinates to pixel * coordinates:</p> - * <pre><code>P = [ K 0 * [ R t - * 0 1 ] 0 1 ] + * <pre><code>P = [ K 0 * [ R -Rt + * 0 1 ] 0 1 ] * </code></pre> - * <p>and with <code>p_w</code> being a point in the world coordinate system + * <p>(Note the negation of poseTranslation when mapping from camera + * to world coordinates, and multiplication by the rotation).</p> + * <p>With <code>p_w</code> being a point in the world coordinate system * and <code>p_s</code> being a point in the camera active pixel array * coordinate system, and with the mapping including the * homogeneous division by z:</p> @@ -2356,27 +2444,57 @@ * where <code>(0,0)</code> is the top-left of the * preCorrectionActiveArraySize rectangle. Once the pose and * intrinsic calibration transforms have been applied to a - * world point, then the ACAMERA_LENS_RADIAL_DISTORTION + * world point, then the ACAMERA_LENS_DISTORTION * transform needs to be applied, and the result adjusted to * be in the ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE coordinate * system (where <code>(0, 0)</code> is the top-left of the * activeArraySize rectangle), to determine the final pixel * coordinate of the world point for processed (non-RAW) * output buffers.</p> + * <p>For camera devices, the center of pixel <code>(x,y)</code> is located at + * coordinate <code>(x + 0.5, y + 0.5)</code>. So on a device with a + * precorrection active array of size <code>(10,10)</code>, the valid pixel + * indices go from <code>(0,0)-(9,9)</code>, and an perfectly-built camera would + * have an optical center at the exact center of the pixel grid, at + * coordinates <code>(5.0, 5.0)</code>, which is the top-left corner of pixel + * <code>(5,5)</code>.</p> * + * @see ACAMERA_LENS_DISTORTION * @see ACAMERA_LENS_POSE_ROTATION * @see ACAMERA_LENS_POSE_TRANSLATION - * @see ACAMERA_LENS_RADIAL_DISTORTION * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ ACAMERA_LENS_INTRINSIC_CALIBRATION = // float[5] ACAMERA_LENS_START + 10, + ACAMERA_LENS_RADIAL_DISTORTION = // Deprecated! DO NOT USE + ACAMERA_LENS_START + 11, + /** + * <p>The origin for ACAMERA_LENS_POSE_TRANSLATION.</p> + * + * @see ACAMERA_LENS_POSE_TRANSLATION + * + * <p>Type: byte (acamera_metadata_enum_android_lens_pose_reference_t)</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>Different calibration methods and use cases can produce better or worse results + * depending on the selected coordinate origin.</p> + */ + ACAMERA_LENS_POSE_REFERENCE = // byte (acamera_metadata_enum_android_lens_pose_reference_t) + ACAMERA_LENS_START + 12, /** * <p>The correction coefficients to correct for this camera device's * radial and tangential lens distortion.</p> + * <p>Replaces the deprecated ACAMERA_LENS_RADIAL_DISTORTION field, which was + * inconsistently defined.</p> * - * <p>Type: float[6]</p> + * @see ACAMERA_LENS_RADIAL_DISTORTION + * + * <p>Type: float[5]</p> * * <p>This tag may appear in: * <ul> @@ -2384,13 +2502,13 @@ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> * </ul></p> * - * <p>Four radial distortion coefficients <code>[kappa_0, kappa_1, kappa_2, + * <p>Three radial distortion coefficients <code>[kappa_1, kappa_2, * kappa_3]</code> and two tangential distortion coefficients * <code>[kappa_4, kappa_5]</code> that can be used to correct the * lens's geometric distortion with the mapping equations:</p> - * <pre><code> x_c = x_i * ( kappa_0 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + + * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * kappa_4 * (2 * x_i * y_i) + kappa_5 * ( r^2 + 2 * x_i^2 ) - * y_c = y_i * ( kappa_0 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + + * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * kappa_5 * (2 * x_i * y_i) + kappa_4 * ( r^2 + 2 * y_i^2 ) * </code></pre> * <p>Here, <code>[x_c, y_c]</code> are the coordinates to sample in the @@ -2398,23 +2516,21 @@ * corrected image at the coordinate <code>[x_i, y_i]</code>:</p> * <pre><code> correctedImage(x_i, y_i) = sample_at(x_c, y_c, inputImage) * </code></pre> - * <p>The pixel coordinates are defined in a normalized - * coordinate system related to the - * ACAMERA_LENS_INTRINSIC_CALIBRATION calibration fields. - * Both <code>[x_i, y_i]</code> and <code>[x_c, y_c]</code> have <code>(0,0)</code> at the - * lens optical center <code>[c_x, c_y]</code>. The maximum magnitudes - * of both x and y coordinates are normalized to be 1 at the - * edge further from the optical center, so the range - * for both dimensions is <code>-1 <= x <= 1</code>.</p> + * <p>The pixel coordinates are defined in a coordinate system + * related to the ACAMERA_LENS_INTRINSIC_CALIBRATION + * calibration fields; see that entry for details of the mapping stages. + * Both <code>[x_i, y_i]</code> and <code>[x_c, y_c]</code> + * have <code>(0,0)</code> at the lens optical center <code>[c_x, c_y]</code>, and + * the range of the coordinates depends on the focal length + * terms of the intrinsic calibration.</p> * <p>Finally, <code>r</code> represents the radial distance from the - * optical center, <code>r^2 = x_i^2 + y_i^2</code>, and its magnitude - * is therefore no larger than <code>|r| <= sqrt(2)</code>.</p> + * optical center, <code>r^2 = x_i^2 + y_i^2</code>.</p> * <p>The distortion model used is the Brown-Conrady model.</p> * * @see ACAMERA_LENS_INTRINSIC_CALIBRATION */ - ACAMERA_LENS_RADIAL_DISTORTION = // float[6] - ACAMERA_LENS_START + 11, + ACAMERA_LENS_DISTORTION = // float[5] + ACAMERA_LENS_START + 13, ACAMERA_LENS_END, /** @@ -2662,11 +2778,12 @@ * into the 3 stream types as below:</p> * <ul> * <li>Processed (but stalling): any non-RAW format with a stallDurations > 0. - * Typically {@link AIMAGE_FORMAT_JPEG} format.</li> - * <li>Raw formats: {@link AIMAGE_FORMAT_RAW16}, {@link AIMAGE_FORMAT_RAW10}, or - * {@link AIMAGE_FORMAT_RAW12}.</li> - * <li>Processed (but not-stalling): any non-RAW format without a stall duration. - * Typically {@link AIMAGE_FORMAT_YUV_420_888}.</li> + * Typically {@link AIMAGE_FORMAT_JPEG JPEG format}.</li> + * <li>Raw formats: {@link AIMAGE_FORMAT_RAW16 RAW_SENSOR}, {@link AIMAGE_FORMAT_RAW10 RAW10}, or + * {@link AIMAGE_FORMAT_RAW12 RAW12}.</li> + * <li>Processed (but not-stalling): any non-RAW format without a stall duration. Typically + * {@link AIMAGE_FORMAT_YUV_420_888 YUV_420_888}, + * <a href="https://developer.android.com/reference/android/graphics/ImageFormat.html#NV21">NV21</a>, or <a href="https://developer.android.com/reference/android/graphics/ImageFormat.html#YV12">YV12</a>.</li> * </ul> * * @see ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS @@ -2787,7 +2904,7 @@ ACAMERA_REQUEST_START + 12, /** * <p>A list of all keys that the camera device has available - * to use with {@link ACaptureRequest}.</p> + * to use with {@link ACaptureRequest }.</p> * * <p>Type: int32[n]</p> * @@ -2809,9 +2926,7 @@ ACAMERA_REQUEST_AVAILABLE_REQUEST_KEYS = // int32[n] ACAMERA_REQUEST_START + 13, /** - * <p>A list of all keys that the camera device has available - * to query with {@link ACameraMetadata} from - * {@link ACameraCaptureSession_captureCallback_result}.</p> + * <p>A list of all keys that the camera device has available to use with {@link ACameraCaptureSession_captureCallback_result }.</p> * * <p>Type: int32[n]</p> * @@ -2842,9 +2957,7 @@ ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS = // int32[n] ACAMERA_REQUEST_START + 14, /** - * <p>A list of all keys that the camera device has available - * to query with {@link ACameraMetadata} from - * {@link ACameraManager_getCameraCharacteristics}.</p> + * <p>A list of all keys that the camera device has available to use with {@link ACameraManager_getCameraCharacteristics }.</p> * * <p>Type: int32[n]</p> * @@ -2862,6 +2975,59 @@ */ ACAMERA_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS = // int32[n] ACAMERA_REQUEST_START + 15, + /** + * <p>A subset of the available request keys that the camera device + * can pass as part of the capture session initialization.</p> + * + * <p>Type: int32[n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>This is a subset of ACAMERA_REQUEST_AVAILABLE_REQUEST_KEYS which + * contains a list of keys that are difficult to apply per-frame and + * can result in unexpected delays when modified during the capture session + * lifetime. Typical examples include parameters that require a + * time-consuming hardware re-configuration or internal camera pipeline + * change. For performance reasons we advise clients to pass their initial + * values as part of + * {@link ACameraDevice_createCaptureSessionWithSessionParameters }. + * Once the camera capture session is enabled it is also recommended to avoid + * changing them from their initial values set in + * {@link ACameraDevice_createCaptureSessionWithSessionParameters }. + * Control over session parameters can still be exerted in capture requests + * but clients should be aware and expect delays during their application. + * An example usage scenario could look like this:</p> + * <ul> + * <li>The camera client starts by quering the session parameter key list via + * {@link ACameraManager_getCameraCharacteristics }.</li> + * <li>Before triggering the capture session create sequence, a capture request + * must be built via + * {@link ACameraDevice_createCaptureRequest } + * using an appropriate template matching the particular use case.</li> + * <li>The client should go over the list of session parameters and check + * whether some of the keys listed matches with the parameters that + * they intend to modify as part of the first capture request.</li> + * <li>If there is no such match, the capture request can be passed + * unmodified to + * {@link ACameraDevice_createCaptureSessionWithSessionParameters }.</li> + * <li>If matches do exist, the client should update the respective values + * and pass the request to + * {@link ACameraDevice_createCaptureSessionWithSessionParameters }.</li> + * <li>After the capture session initialization completes the session parameter + * key list can continue to serve as reference when posting or updating + * further requests. As mentioned above further changes to session + * parameters should ideally be avoided, if updates are necessary + * however clients could expect a delay/glitch during the + * parameter switch.</li> + * </ul> + * + * @see ACAMERA_REQUEST_AVAILABLE_REQUEST_KEYS + */ + ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS = // int32[n] + ACAMERA_REQUEST_START + 16, ACAMERA_REQUEST_END, /** @@ -2876,10 +3042,17 @@ * </ul></p> * * <p>This control can be used to implement digital zoom.</p> - * <p>The data representation is int[4], which maps to (left, top, width, height).</p> - * <p>The crop region coordinate system is based off - * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with <code>(0, 0)</code> being the - * top-left corner of the sensor active array.</p> + * <p>For devices not supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system always follows that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with <code>(0, 0)</code> being + * the top-left pixel of the active array.</p> + * <p>For devices supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system depends on the mode being set. + * When the distortion correction mode is OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array. + * When the distortion correction mode is not OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the active array.</p> * <p>Output streams use this rectangle to produce their output, * cropping to a smaller region if necessary to maintain the * stream's aspect ratio, then scaling the sensor input to @@ -2898,17 +3071,26 @@ * outputs will crop horizontally (pillarbox), and 16:9 * streams will match exactly. These additional crops will * be centered within the crop region.</p> - * <p>The width and height of the crop region cannot - * be set to be smaller than + * <p>If the coordinate system is ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, the width and height + * of the crop region cannot be set to be smaller than * <code>floor( activeArraySize.width / ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM )</code> and * <code>floor( activeArraySize.height / ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM )</code>, respectively.</p> + * <p>If the coordinate system is ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, the width + * and height of the crop region cannot be set to be smaller than + * <code>floor( preCorrectionActiveArraySize.width / ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM )</code> + * and + * <code>floor( preCorrectionActiveArraySize.height / ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM )</code>, + * respectively.</p> * <p>The camera device may adjust the crop region to account * for rounding and other hardware requirements; the final * crop region used will be included in the output capture * result.</p> + * <p>The data representation is int[4], which maps to (left, top, width, height).</p> * + * @see ACAMERA_DISTORTION_CORRECTION_MODE * @see ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ ACAMERA_SCALER_CROP_REGION = // int32[4] ACAMERA_SCALER_START, @@ -2975,6 +3157,12 @@ * IMPLEMENTATION_DEFINED | same as YUV_420_888 | Any |</p> * <p>Refer to ACAMERA_REQUEST_AVAILABLE_CAPABILITIES for additional * mandatory stream configurations on a per-capability basis.</p> + * <p>Exception on 176x144 (QCIF) resolution: camera devices usually have a fixed capability for + * downscaling from larger resolution to smaller, and the QCIF resolution sometimes is not + * fully supported due to this limitation on devices with high-resolution image sensors. + * Therefore, trying to configure a QCIF resolution stream together with any other + * stream larger than 1920x1080 resolution (either width or height) might not be supported, + * and capture session creation will fail if it is not.</p> * * @see ACAMERA_INFO_SUPPORTED_HARDWARE_LEVEL * @see ACAMERA_REQUEST_AVAILABLE_CAPABILITIES @@ -3061,13 +3249,14 @@ * ignored).</p> * <p>The following formats may always have a stall duration:</p> * <ul> - * <li>{@link AIMAGE_FORMAT_JPEG}</li> - * <li>{@link AIMAGE_FORMAT_RAW16}</li> + * <li>{@link AIMAGE_FORMAT_JPEG }</li> + * <li>{@link AIMAGE_FORMAT_RAW16 }</li> * </ul> * <p>The following formats will never have a stall duration:</p> * <ul> - * <li>{@link AIMAGE_FORMAT_YUV_420_888}</li> - * <li>{@link AIMAGE_FORMAT_RAW10}</li> + * <li>{@link AIMAGE_FORMAT_YUV_420_888 }</li> + * <li>{@link AIMAGE_FORMAT_RAW10 }</li> + * <li>{@link AIMAGE_FORMAT_RAW12 }</li> * </ul> * <p>All other formats may or may not have an allowed stall duration on * a per-capability basis; refer to ACAMERA_REQUEST_AVAILABLE_CAPABILITIES @@ -3177,39 +3366,29 @@ * can run concurrently to the rest of the camera pipeline, but * cannot process more than 1 capture at a time.</li> * </ul> - * <p>The necessary information for the application, given the model above, - * is provided via - * {@link ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS}. - * These are used to determine the maximum frame rate / minimum frame - * duration that is possible for a given stream configuration.</p> + * <p>The necessary information for the application, given the model above, is provided via + * {@link ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS }. + * These are used to determine the maximum frame rate / minimum frame duration that is + * possible for a given stream configuration.</p> * <p>Specifically, the application can use the following rules to * determine the minimum frame duration it can request from the camera * device:</p> * <ol> - * <li>Let the set of currently configured input/output streams - * be called <code>S</code>.</li> - * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking - * it up in {@link ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS} - * (with its respective size/format). Let this set of frame durations be - * called <code>F</code>.</li> - * <li>For any given request <code>R</code>, the minimum frame duration allowed - * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams - * used in <code>R</code> be called <code>S_r</code>.</li> + * <li>Let the set of currently configured input/output streams be called <code>S</code>.</li> + * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking it up in {@link ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS } + * (with its respective size/format). Let this set of frame durations be called <code>F</code>.</li> + * <li>For any given request <code>R</code>, the minimum frame duration allowed for <code>R</code> is the maximum + * out of all values in <code>F</code>. Let the streams used in <code>R</code> be called <code>S_r</code>.</li> * </ol> - * <p>If none of the streams in <code>S_r</code> have a stall time (listed in {@link - * ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS} - * using its respective size/format), then the frame duration in <code>F</code> - * determines the steady state frame rate that the application will get - * if it uses <code>R</code> as a repeating request. Let this special kind of - * request be called <code>Rsimple</code>.</p> - * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved - * by a single capture of a new request <code>Rstall</code> (which has at least - * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the - * same minimum frame duration this will not cause a frame rate loss - * if all buffers from the previous <code>Rstall</code> have already been - * delivered.</p> - * <p>For more details about stalling, see - * {@link ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS}.</p> + * <p>If none of the streams in <code>S_r</code> have a stall time (listed in {@link ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS } + * using its respective size/format), then the frame duration in <code>F</code> determines the steady + * state frame rate that the application will get if it uses <code>R</code> as a repeating request. Let + * this special kind of request be called <code>Rsimple</code>.</p> + * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved by a single capture of a + * new request <code>Rstall</code> (which has at least one in-use stream with a non-0 stall time) and if + * <code>Rstall</code> has the same minimum frame duration this will not cause a frame rate loss if all + * buffers from the previous <code>Rstall</code> have already been delivered.</p> + * <p>For more details about stalling, see {@link ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS }.</p> * <p>This control is only effective if ACAMERA_CONTROL_AE_MODE or ACAMERA_CONTROL_MODE is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> * @@ -3567,14 +3746,12 @@ * timestamps for other captures from the same camera device, but are * not guaranteed to be comparable to any other time source.</p> * <p>When ACAMERA_SENSOR_INFO_TIMESTAMP_SOURCE <code>==</code> REALTIME, the - * timestamps measure time in the same timebase as - * <a href="https://developer.android.com/reference/android/os/SystemClock.html#elapsedRealtimeNanos">elapsedRealtimeNanos</a> - * (or CLOCK_BOOTTIME), and they can + * timestamps measure time in the same timebase as <a href="https://developer.android.com/reference/android/os/SystemClock.html#elapsedRealtimeNanos">SystemClock#elapsedRealtimeNanos</a>, and they can * be compared to other timestamps from other subsystems that * are using that base.</p> * <p>For reprocessing, the timestamp will match the start of exposure of - * the input image, i.e. {@link CaptureResult#SENSOR_TIMESTAMP the - * timestamp} in the TotalCaptureResult that was used to create the + * the input image, i.e. <a href="https://developer.android.com/reference/CaptureResult.html#SENSOR_TIMESTAMP">the + * timestamp</a> in the TotalCaptureResult that was used to create the * reprocess capture request.</p> * * @see ACAMERA_SENSOR_INFO_TIMESTAMP_SOURCE @@ -3775,7 +3952,6 @@ * optically shielded pixel areas. By blocking light, these pixels * provides a reliable black reference for black level compensation * in active array region.</p> - * <p>The data representation is int[4], which maps to (left, top, width, height).</p> * <p>This key provides a list of disjoint rectangles specifying the * regions of optically shielded (with metal shield) black pixel * regions if the camera device is capable of reading out these black @@ -3785,6 +3961,7 @@ * black level of each captured raw images.</p> * <p>When this key is reported, the ACAMERA_SENSOR_DYNAMIC_BLACK_LEVEL and * ACAMERA_SENSOR_DYNAMIC_WHITE_LEVEL will also be reported.</p> + * <p>The data representation is <code>int[4]</code>, which maps to <code>(left, top, width, height)</code>.</p> * * @see ACAMERA_SENSOR_BLACK_LEVEL_PATTERN * @see ACAMERA_SENSOR_DYNAMIC_BLACK_LEVEL @@ -3825,9 +4002,8 @@ * layout key (see ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT), i.e. the * nth value given corresponds to the black level offset for the nth * color channel listed in the CFA.</p> - * <p>This key will be available if ACAMERA_SENSOR_OPTICAL_BLACK_REGIONS is - * available or the camera device advertises this key via - * {@link ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS}.</p> + * <p>This key will be available if ACAMERA_SENSOR_OPTICAL_BLACK_REGIONS is available or the + * camera device advertises this key via {@link ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS }.</p> * * @see ACAMERA_SENSOR_BLACK_LEVEL_PATTERN * @see ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT @@ -3853,7 +4029,7 @@ * estimated white level for each frame.</p> * <p>This key will be available if ACAMERA_SENSOR_OPTICAL_BLACK_REGIONS is * available or the camera device advertises this key via - * {@link ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS}.</p> + * {@link ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS }.</p> * * @see ACAMERA_SENSOR_BLACK_LEVEL_PATTERN * @see ACAMERA_SENSOR_INFO_WHITE_LEVEL @@ -3882,16 +4058,28 @@ * <p>This rectangle is defined relative to the full pixel array; (0,0) is the top-left of * the full pixel array, and the size of the full pixel array is given by * ACAMERA_SENSOR_INFO_PIXEL_ARRAY_SIZE.</p> - * <p>The data representation is int[4], which maps to (left, top, width, height).</p> * <p>The coordinate system for most other keys that list pixel coordinates, including * ACAMERA_SCALER_CROP_REGION, is defined relative to the active array rectangle given in * this field, with <code>(0, 0)</code> being the top-left of this rectangle.</p> * <p>The active array may be smaller than the full pixel array, since the full array may - * include black calibration pixels or other inactive regions, and geometric correction - * resulting in scaling or cropping may have been applied.</p> + * include black calibration pixels or other inactive regions.</p> + * <p>For devices that do not support ACAMERA_DISTORTION_CORRECTION_MODE control, the active + * array must be the same as ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE.</p> + * <p>For devices that support ACAMERA_DISTORTION_CORRECTION_MODE control, the active array must + * be enclosed by ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE. The difference between + * pre-correction active array and active array accounts for scaling or cropping caused + * by lens geometric distortion correction.</p> + * <p>In general, application should always refer to active array size for controls like + * metering regions or crop region. Two exceptions are when the application is dealing with + * RAW image buffers (RAW_SENSOR, RAW10, RAW12 etc), or when application explicitly set + * ACAMERA_DISTORTION_CORRECTION_MODE to OFF. In these cases, application should refer + * to ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE.</p> + * <p>The data representation is <code>int[4]</code>, which maps to <code>(left, top, width, height)</code>.</p> * + * @see ACAMERA_DISTORTION_CORRECTION_MODE * @see ACAMERA_SCALER_CROP_REGION * @see ACAMERA_SENSOR_INFO_PIXEL_ARRAY_SIZE + * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE */ ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE = // int32[4] ACAMERA_SENSOR_INFO_START, @@ -3960,8 +4148,7 @@ * <p>Attempting to use frame durations beyond the maximum will result in the frame * duration being clipped to the maximum. See that control for a full definition of frame * durations.</p> - * <p>Refer to {@link - * ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS} + * <p>Refer to {@link ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS } * for the minimum frame duration values.</p> */ ACAMERA_SENSOR_INFO_MAX_FRAME_DURATION = // int64 @@ -4000,9 +4187,9 @@ * the raw buffers produced by this sensor.</p> * <p>If a camera device supports raw sensor formats, either this or * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE is the maximum dimensions for the raw - * output formats listed in ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS (this depends on - * whether or not the image sensor returns buffers containing pixels that are not - * part of the active array region for blacklevel calibration or other purposes).</p> + * output formats listed in {@link ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS } + * (this depends on whether or not the image sensor returns buffers containing pixels that + * are not part of the active array region for blacklevel calibration or other purposes).</p> * <p>Some parts of the full pixel array may not receive light from the scene, * or be otherwise inactive. The ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE key * defines the rectangle of active pixels that will be included in processed image @@ -4092,7 +4279,6 @@ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> * </ul></p> * - * <p>The data representation is int[4], which maps to (left, top, width, height).</p> * <p>This is the rectangle representing the size of the active region of the sensor (i.e. * the region that actually receives light from the scene) before any geometric correction * has been applied, and should be treated as the active region rectangle for any of the @@ -4133,18 +4319,19 @@ * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.</p> * <p>The currently supported fields that correct for geometric distortion are:</p> * <ol> - * <li>ACAMERA_LENS_RADIAL_DISTORTION.</li> + * <li>ACAMERA_LENS_DISTORTION.</li> * </ol> - * <p>If all of the geometric distortion fields are no-ops, this rectangle will be the same - * as the post-distortion-corrected rectangle given in - * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.</p> + * <p>If the camera device doesn't support geometric distortion correction, or all of the + * geometric distortion fields are no-ops, this rectangle will be the same as the + * post-distortion-corrected rectangle given in ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.</p> * <p>This rectangle is defined relative to the full pixel array; (0,0) is the top-left of * the full pixel array, and the size of the full pixel array is given by * ACAMERA_SENSOR_INFO_PIXEL_ARRAY_SIZE.</p> * <p>The pre-correction active array may be smaller than the full pixel array, since the * full array may include black calibration pixels or other inactive regions.</p> + * <p>The data representation is <code>int[4]</code>, which maps to <code>(left, top, width, height)</code>.</p> * - * @see ACAMERA_LENS_RADIAL_DISTORTION + * @see ACAMERA_LENS_DISTORTION * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE * @see ACAMERA_SENSOR_INFO_PIXEL_ARRAY_SIZE * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE @@ -4282,11 +4469,22 @@ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> * </ul></p> * - * <p>The coordinate system is that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with + * <p>For devices not supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system always follows that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with <code>(0, 0)</code> being + * the top-left pixel of the active array.</p> + * <p>For devices supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system depends on the mode being set. + * When the distortion correction mode is OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array. + * When the distortion correction mode is not OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with * <code>(0, 0)</code> being the top-left pixel of the active array.</p> * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE == FULL</p> * + * @see ACAMERA_DISTORTION_CORRECTION_MODE * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE * @see ACAMERA_STATISTICS_FACE_DETECT_MODE */ ACAMERA_STATISTICS_FACE_LANDMARKS = // int32[n*6] @@ -4302,12 +4500,23 @@ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> * </ul></p> * - * <p>The data representation is int[4], which maps to (left, top, width, height).</p> - * <p>The coordinate system is that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with + * <p>For devices not supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system always follows that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with <code>(0, 0)</code> being + * the top-left pixel of the active array.</p> + * <p>For devices supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate + * system depends on the mode being set. + * When the distortion correction mode is OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, with + * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array. + * When the distortion correction mode is not OFF, the coordinate system follows + * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with * <code>(0, 0)</code> being the top-left pixel of the active array.</p> - * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE != OFF</p> + * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE != OFF + * The data representation is <code>int[4]</code>, which maps to <code>(left, top, right, bottom)</code>.</p> * + * @see ACAMERA_DISTORTION_CORRECTION_MODE * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE * @see ACAMERA_STATISTICS_FACE_DETECT_MODE */ ACAMERA_STATISTICS_FACE_RECTANGLES = // int32[n*4] @@ -4483,6 +4692,94 @@ */ ACAMERA_STATISTICS_LENS_SHADING_MAP_MODE = // byte (acamera_metadata_enum_android_statistics_lens_shading_map_mode_t) ACAMERA_STATISTICS_START + 16, + /** + * <p>A control for selecting whether optical stabilization (OIS) position + * information is included in output result metadata.</p> + * + * <p>Type: byte (acamera_metadata_enum_android_statistics_ois_data_mode_t)</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> + * <li>ACaptureRequest</li> + * </ul></p> + * + * <p>Since optical image stabilization generally involves motion much faster than the duration + * of individualq image exposure, multiple OIS samples can be included for a single capture + * result. For example, if the OIS reporting operates at 200 Hz, a typical camera operating + * at 30fps may have 6-7 OIS samples per capture result. This information can be combined + * with the rolling shutter skew to account for lens motion during image exposure in + * post-processing algorithms.</p> + */ + ACAMERA_STATISTICS_OIS_DATA_MODE = // byte (acamera_metadata_enum_android_statistics_ois_data_mode_t) + ACAMERA_STATISTICS_START + 17, + /** + * <p>An array of timestamps of OIS samples, in nanoseconds.</p> + * + * <p>Type: int64[n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> + * </ul></p> + * + * <p>The array contains the timestamps of OIS samples. The timestamps are in the same + * timebase as and comparable to ACAMERA_SENSOR_TIMESTAMP.</p> + * + * @see ACAMERA_SENSOR_TIMESTAMP + */ + ACAMERA_STATISTICS_OIS_TIMESTAMPS = // int64[n] + ACAMERA_STATISTICS_START + 18, + /** + * <p>An array of shifts of OIS samples, in x direction.</p> + * + * <p>Type: float[n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> + * </ul></p> + * + * <p>The array contains the amount of shifts in x direction, in pixels, based on OIS samples. + * A positive value is a shift from left to right in the pre-correction active array + * coordinate system. For example, if the optical center is (1000, 500) in pre-correction + * active array coordinates, a shift of (3, 0) puts the new optical center at (1003, 500).</p> + * <p>The number of shifts must match the number of timestamps in + * ACAMERA_STATISTICS_OIS_TIMESTAMPS.</p> + * <p>The OIS samples are not affected by whether lens distortion correction is enabled (on + * supporting devices). They are always reported in pre-correction active array coordinates, + * since the scaling of OIS shifts would depend on the specific spot on the sensor the shift + * is needed.</p> + * + * @see ACAMERA_STATISTICS_OIS_TIMESTAMPS + */ + ACAMERA_STATISTICS_OIS_X_SHIFTS = // float[n] + ACAMERA_STATISTICS_START + 19, + /** + * <p>An array of shifts of OIS samples, in y direction.</p> + * + * <p>Type: float[n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> + * </ul></p> + * + * <p>The array contains the amount of shifts in y direction, in pixels, based on OIS samples. + * A positive value is a shift from top to bottom in pre-correction active array coordinate + * system. For example, if the optical center is (1000, 500) in active array coordinates, a + * shift of (0, 5) puts the new optical center at (1000, 505).</p> + * <p>The number of shifts must match the number of timestamps in + * ACAMERA_STATISTICS_OIS_TIMESTAMPS.</p> + * <p>The OIS samples are not affected by whether lens distortion correction is enabled (on + * supporting devices). They are always reported in pre-correction active array coordinates, + * since the scaling of OIS shifts would depend on the specific spot on the sensor the shift + * is needed.</p> + * + * @see ACAMERA_STATISTICS_OIS_TIMESTAMPS + */ + ACAMERA_STATISTICS_OIS_Y_SHIFTS = // float[n] + ACAMERA_STATISTICS_START + 20, ACAMERA_STATISTICS_END, /** @@ -4555,6 +4852,24 @@ */ ACAMERA_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES = // byte[n] ACAMERA_STATISTICS_INFO_START + 7, + /** + * <p>List of OIS data output modes for ACAMERA_STATISTICS_OIS_DATA_MODE that + * are supported by this camera device.</p> + * + * @see ACAMERA_STATISTICS_OIS_DATA_MODE + * + * <p>Type: byte[n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>If no OIS data output is available for this camera device, this key will + * contain only OFF.</p> + */ + ACAMERA_STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES = // byte[n] + ACAMERA_STATISTICS_INFO_START + 8, ACAMERA_STATISTICS_INFO_END, /** @@ -4627,6 +4942,8 @@ * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * ACAMERA_TONEMAP_MAX_CURVE_POINTS).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> @@ -4809,12 +5126,26 @@ * the following code snippet can be used:</p> * <pre><code>// Returns true if the device supports the required hardware level, or better. * boolean isHardwareLevelSupported(CameraCharacteristics c, int requiredLevel) { + * final int[] sortedHwLevels = { + * CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, + * CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, + * CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, + * CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, + * CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 + * }; * int deviceLevel = c.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); - * if (deviceLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { - * return requiredLevel == deviceLevel; + * if (requiredLevel == deviceLevel) { + * return true; * } - * // deviceLevel is not LEGACY, can use numerical sort - * return requiredLevel <= deviceLevel; + * + * for (int sortedlevel : sortedHwLevels) { + * if (sortedlevel == requiredLevel) { + * return true; + * } else if (sortedlevel == deviceLevel) { + * return false; + * } + * } + * return false; // Should never reach here * } * </code></pre> * <p>At a high level, the levels are:</p> @@ -4828,11 +5159,13 @@ * post-processing settings, and image capture at a high rate.</li> * <li><code>LEVEL_3</code> devices additionally support YUV reprocessing and RAW image capture, along * with additional output stream configurations.</li> + * <li><code>EXTERNAL</code> devices are similar to <code>LIMITED</code> devices with exceptions like some sensor or + * lens information not reported or less stable framerates.</li> * </ul> * <p>See the individual level enums for full descriptions of the supported capabilities. The * ACAMERA_REQUEST_AVAILABLE_CAPABILITIES entry describes the device's capabilities at a * finer-grain level, if needed. In addition, many controls have their available settings or - * ranges defined in individual metadata tag entries in this document.</p> + * ranges defined in individual entries from {@link ACameraManager_getCameraCharacteristics }.</p> * <p>Some features are not part of any particular hardware level or capability and must be * queried separately. These include:</p> * <ul> @@ -4853,6 +5186,23 @@ */ ACAMERA_INFO_SUPPORTED_HARDWARE_LEVEL = // byte (acamera_metadata_enum_android_info_supported_hardware_level_t) ACAMERA_INFO_START, + /** + * <p>A short string for manufacturer version information about the camera device, such as + * ISP hardware, sensors, etc.</p> + * + * <p>Type: byte</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>This can be used in <a href="https://developer.android.com/reference/android/media/ExifInterface.html#TAG_IMAGE_DESCRIPTION">TAG_IMAGE_DESCRIPTION</a> + * in jpeg EXIF. This key may be absent if no version information is available on the + * device.</p> + */ + ACAMERA_INFO_VERSION = // byte + ACAMERA_INFO_START + 1, ACAMERA_INFO_END, /** @@ -5069,6 +5419,104 @@ ACAMERA_DEPTH_START + 4, ACAMERA_DEPTH_END, + /** + * <p>The accuracy of frame timestamp synchronization between physical cameras</p> + * + * <p>Type: byte (acamera_metadata_enum_android_logical_multi_camera_sensor_sync_type_t)</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </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> + * <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> + */ + ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE = // byte (acamera_metadata_enum_android_logical_multi_camera_sensor_sync_type_t) + ACAMERA_LOGICAL_MULTI_CAMERA_START + 1, + ACAMERA_LOGICAL_MULTI_CAMERA_END, + + /** + * <p>Mode of operation for the lens distortion correction block.</p> + * + * <p>Type: byte (acamera_metadata_enum_android_distortion_correction_mode_t)</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> + * <li>ACaptureRequest</li> + * </ul></p> + * + * <p>The lens distortion correction block attempts to improve image quality by fixing + * radial, tangential, or other geometric aberrations in the camera device's optics. If + * available, the ACAMERA_LENS_DISTORTION field documents the lens's distortion parameters.</p> + * <p>OFF means no distortion correction is done.</p> + * <p>FAST/HIGH_QUALITY both mean camera device determined distortion correction will be + * applied. HIGH_QUALITY mode indicates that the camera device will use the highest-quality + * correction algorithms, even if it slows down capture rate. FAST means the camera device + * will not slow down capture rate when applying correction. FAST may be the same as OFF if + * any correction at all would slow down capture rate. Every output stream will have a + * similar amount of enhancement applied.</p> + * <p>The correction only applies to processed outputs such as YUV, JPEG, or DEPTH16; it is not + * applied to any RAW output.</p> + * <p>This control will be on by default on devices that support this control. Applications + * disabling distortion correction need to pay extra attention with the coordinate system of + * metering regions, crop region, and face rectangles. When distortion correction is OFF, + * metadata coordinates follow the coordinate system of + * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE. When distortion is not OFF, metadata + * coordinates follow the coordinate system of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE. The + * camera device will map these metadata fields to match the corrected image produced by the + * camera device, for both capture requests and results. However, this mapping is not very + * precise, since rectangles do not generally map to rectangles when corrected. Only linear + * scaling between the active array and precorrection active array coordinates is + * performed. Applications that require precise correction of metadata need to undo that + * linear scaling, and apply a more complete correction that takes into the account the app's + * own requirements.</p> + * <p>The full list of metadata that is affected in this way by distortion correction is:</p> + * <ul> + * <li>ACAMERA_CONTROL_AF_REGIONS</li> + * <li>ACAMERA_CONTROL_AE_REGIONS</li> + * <li>ACAMERA_CONTROL_AWB_REGIONS</li> + * <li>ACAMERA_SCALER_CROP_REGION</li> + * <li>android.statistics.faces</li> + * </ul> + * + * @see ACAMERA_CONTROL_AE_REGIONS + * @see ACAMERA_CONTROL_AF_REGIONS + * @see ACAMERA_CONTROL_AWB_REGIONS + * @see ACAMERA_LENS_DISTORTION + * @see ACAMERA_SCALER_CROP_REGION + * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + */ + ACAMERA_DISTORTION_CORRECTION_MODE = // byte (acamera_metadata_enum_android_distortion_correction_mode_t) + ACAMERA_DISTORTION_CORRECTION_START, + /** + * <p>List of distortion correction modes for ACAMERA_DISTORTION_CORRECTION_MODE that are + * supported by this camera device.</p> + * + * @see ACAMERA_DISTORTION_CORRECTION_MODE + * + * <p>Type: byte[n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>No device is required to support this API; such devices will always list only 'OFF'. + * All devices that support this API will list both FAST and HIGH_QUALITY.</p> + */ + ACAMERA_DISTORTION_CORRECTION_AVAILABLE_MODES = // byte[n] + ACAMERA_DISTORTION_CORRECTION_START + 1, + ACAMERA_DISTORTION_CORRECTION_END, + } acamera_metadata_tag_t; /** @@ -5282,6 +5730,21 @@ */ ACAMERA_CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4, + /** + * <p>An external flash has been turned on.</p> + * <p>It informs the camera device that an external flash has been turned on, and that + * metering (and continuous focus if active) should be quickly recaculated to account + * for the external flash. Otherwise, this mode acts like ON.</p> + * <p>When the external flash is turned off, AE mode should be changed to one of the + * other available AE modes.</p> + * <p>If the camera device supports AE external flash mode, ACAMERA_CONTROL_AE_STATE must + * be FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without + * flash.</p> + * + * @see ACAMERA_CONTROL_AE_STATE + */ + ACAMERA_CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5, + } acamera_metadata_enum_android_control_ae_mode_t; // ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER @@ -5645,6 +6108,15 @@ */ ACAMERA_CONTROL_CAPTURE_INTENT_MANUAL = 6, + /** + * <p>This request is for a motion tracking use case, where + * the application will use camera and inertial sensor data to + * locate and track objects in the world.</p> + * <p>The camera device auto-exposure routine will limit the exposure time + * of the camera to no more than 20 milliseconds, to minimize motion blur.</p> + */ + ACAMERA_CONTROL_CAPTURE_INTENT_MOTION_TRACKING = 7, + } acamera_metadata_enum_android_control_capture_intent_t; // ACAMERA_CONTROL_EFFECT_MODE @@ -6135,6 +6607,20 @@ } acamera_metadata_enum_android_control_enable_zsl_t; +// ACAMERA_CONTROL_AF_SCENE_CHANGE +typedef enum acamera_metadata_enum_acamera_control_af_scene_change { + /** + * <p>Scene change is not detected within the AF region(s).</p> + */ + ACAMERA_CONTROL_AF_SCENE_CHANGE_NOT_DETECTED = 0, + + /** + * <p>Scene change is detected within the AF region(s).</p> + */ + ACAMERA_CONTROL_AF_SCENE_CHANGE_DETECTED = 1, + +} acamera_metadata_enum_android_control_af_scene_change_t; + // ACAMERA_EDGE_MODE @@ -6157,13 +6643,13 @@ ACAMERA_EDGE_MODE_HIGH_QUALITY = 2, /** - * <p>Edge enhancement is applied at different levels for different output streams, - * based on resolution. Streams at maximum recording resolution (see {@link - * ACameraDevice_createCaptureSession}) or below have - * edge enhancement applied, while higher-resolution streams have no edge enhancement - * applied. The level of edge enhancement for low-resolution streams is tuned so that - * frame rate is not impacted, and the quality is equal to or better than FAST (since it - * is only applied to lower-resolution outputs, quality may improve from FAST).</p> + * <p>Edge enhancement is applied at different + * levels for different output streams, based on resolution. Streams at maximum recording + * resolution (see {@link ACameraDevice_createCaptureSession }) + * or below have edge enhancement applied, while higher-resolution streams have no edge + * enhancement applied. The level of edge enhancement for low-resolution streams is tuned + * so that frame rate is not impacted, and the quality is equal to or better than FAST + * (since it is only applied to lower-resolution outputs, quality may improve from FAST).</p> * <p>This mode is intended to be used by applications operating in a zero-shutter-lag mode * with YUV or PRIVATE reprocessing, where the application continuously captures * high-resolution intermediate buffers into a circular buffer, from which a final image is @@ -6342,6 +6828,27 @@ } acamera_metadata_enum_android_lens_state_t; +// ACAMERA_LENS_POSE_REFERENCE +typedef enum acamera_metadata_enum_acamera_lens_pose_reference { + /** + * <p>The value of ACAMERA_LENS_POSE_TRANSLATION is relative to the optical center of + * the largest camera device facing the same direction as this camera.</p> + * <p>This is the default value for API levels before Android P.</p> + * + * @see ACAMERA_LENS_POSE_TRANSLATION + */ + ACAMERA_LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0, + + /** + * <p>The value of ACAMERA_LENS_POSE_TRANSLATION is relative to the position of the + * primary gyroscope of this Android device.</p> + * + * @see ACAMERA_LENS_POSE_TRANSLATION + */ + ACAMERA_LENS_POSE_REFERENCE_GYROSCOPE = 1, + +} acamera_metadata_enum_android_lens_pose_reference_t; + // ACAMERA_LENS_INFO_FOCUS_DISTANCE_CALIBRATION typedef enum acamera_metadata_enum_acamera_lens_info_focus_distance_calibration { @@ -6412,13 +6919,12 @@ /** * <p>Noise reduction is applied at different levels for different output streams, - * based on resolution. Streams at maximum recording resolution (see {@link - * ACameraDevice_createCaptureSession}) or below have noise - * reduction applied, while higher-resolution streams have MINIMAL (if supported) or no - * noise reduction applied (if MINIMAL is not supported.) The degree of noise reduction - * for low-resolution streams is tuned so that frame rate is not impacted, and the quality - * is equal to or better than FAST (since it is only applied to lower-resolution outputs, - * quality may improve from FAST).</p> + * based on resolution. Streams at maximum recording resolution (see {@link ACameraDevice_createCaptureSession }) + * or below have noise reduction applied, while higher-resolution streams have MINIMAL (if + * supported) or no noise reduction applied (if MINIMAL is not supported.) The degree of + * noise reduction for low-resolution streams is tuned so that frame rate is not impacted, + * and the quality is equal to or better than FAST (since it is only applied to + * lower-resolution outputs, quality may improve from FAST).</p> * <p>This mode is intended to be used by applications operating in a zero-shutter-lag mode * with YUV or PRIVATE reprocessing, where the application continuously captures * high-resolution intermediate buffers into a circular buffer, from which a final image is @@ -6635,18 +7141,16 @@ * to FAST. Additionally, maximum-resolution images can be captured at >= 10 frames * per second. Here, 'high resolution' means at least 8 megapixels, or the maximum * resolution of the device, whichever is smaller.</p> - * <p>More specifically, this means that at least one output {@link - * AIMAGE_FORMAT_YUV_420_888} size listed in - * {@link ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS} is larger or equal to the - * 'high resolution' defined above, and can be captured at at least 20 fps. - * For the largest {@link AIMAGE_FORMAT_YUV_420_888} size listed in - * {@link ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS}, camera device can capture this - * size for at least 10 frames per second. - * Also the ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES entry lists at least one FPS range - * where the minimum FPS is >= 1 / minimumFrameDuration for the largest YUV_420_888 size.</p> - * <p>If the device supports the {@link AIMAGE_FORMAT_RAW10}, {@link - * AIMAGE_FORMAT_RAW12}, then those can also be captured at the same rate - * as the maximum-size YUV_420_888 resolution is.</p> + * <p>More specifically, this means that at least one output {@link AIMAGE_FORMAT_YUV_420_888 } size listed in + * {@link ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS } + * is larger or equal to the 'high resolution' defined above, and can be captured at at + * least 20 fps. For the largest {@link AIMAGE_FORMAT_YUV_420_888 } size listed in + * {@link ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS }, + * camera device can capture this size for at least 10 frames per second. Also the + * ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES entry lists at least one FPS range where + * the minimum FPS is >= 1 / minimumFrameDuration for the largest YUV_420_888 size.</p> + * <p>If the device supports the {@link AIMAGE_FORMAT_RAW10 }, {@link AIMAGE_FORMAT_RAW12 }, then those can also be + * captured at the same rate as the maximum-size YUV_420_888 resolution is.</p> * <p>In addition, the ACAMERA_SYNC_MAX_LATENCY field is guaranted to have a value between 0 * and 4, inclusive. ACAMERA_CONTROL_AE_LOCK_AVAILABLE and ACAMERA_CONTROL_AWB_LOCK_AVAILABLE * are also guaranteed to be <code>true</code> so burst capture with these two locks ON yields @@ -6663,42 +7167,114 @@ * <p>The camera device can produce depth measurements from its field of view.</p> * <p>This capability requires the camera device to support the following:</p> * <ul> - * <li>{@link AIMAGE_FORMAT_DEPTH16} is supported as an output format.</li> - * <li>{@link AIMAGE_FORMAT_DEPTH_POINT_CLOUD} is optionally supported as an - * output format.</li> - * <li>This camera device, and all camera devices with the same ACAMERA_LENS_FACING, - * will list the following calibration entries in {@link ACameraMetadata} from both - * {@link ACameraManager_getCameraCharacteristics} and - * {@link ACameraCaptureSession_captureCallback_result}:<ul> + * <li>{@link AIMAGE_FORMAT_DEPTH16 } is supported as + * an output format.</li> + * <li>{@link AIMAGE_FORMAT_DEPTH_POINT_CLOUD } is + * optionally supported as an output format.</li> + * <li>This camera device, and all camera devices with the same ACAMERA_LENS_FACING, will + * list the following calibration metadata entries in both {@link ACameraManager_getCameraCharacteristics } + * and {@link ACameraCaptureSession_captureCallback_result }:<ul> * <li>ACAMERA_LENS_POSE_TRANSLATION</li> * <li>ACAMERA_LENS_POSE_ROTATION</li> * <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li> - * <li>ACAMERA_LENS_RADIAL_DISTORTION</li> + * <li>ACAMERA_LENS_DISTORTION</li> * </ul> * </li> * <li>The ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE entry is listed by this device.</li> + * <li>As of Android P, the ACAMERA_LENS_POSE_REFERENCE entry is listed by this device.</li> * <li>A LIMITED camera with only the DEPTH_OUTPUT capability does not have to support * normal YUV_420_888, JPEG, and PRIV-format outputs. It only has to support the DEPTH16 * format.</li> * </ul> * <p>Generally, depth output operates at a slower frame rate than standard color capture, * so the DEPTH16 and DEPTH_POINT_CLOUD formats will commonly have a stall duration that - * should be accounted for (see - * {@link ACAMERA_DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS}). + * should be accounted for (see {@link ACAMERA_DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS }). * On a device that supports both depth and color-based output, to enable smooth preview, * using a repeating burst is recommended, where a depth-output target is only included * once every N frames, where N is the ratio between preview output rate and depth output * rate, including depth stall time.</p> * * @see ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE + * @see ACAMERA_LENS_DISTORTION * @see ACAMERA_LENS_FACING * @see ACAMERA_LENS_INTRINSIC_CALIBRATION + * @see ACAMERA_LENS_POSE_REFERENCE * @see ACAMERA_LENS_POSE_ROTATION * @see ACAMERA_LENS_POSE_TRANSLATION - * @see ACAMERA_LENS_RADIAL_DISTORTION */ ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8, + /** + * <p>The camera device supports the MOTION_TRACKING value for + * ACAMERA_CONTROL_CAPTURE_INTENT, which limits maximum exposure time to 20 ms.</p> + * <p>This limits the motion blur of capture images, resulting in better image tracking + * results for use cases such as image stabilization or augmented reality.</p> + * + * @see ACAMERA_CONTROL_CAPTURE_INTENT + */ + ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10, + + /** + * <p>The camera device is a logical camera backed by two or more physical cameras that are + * also exposed to the application.</p> + * <p>Camera application shouldn't assume that there are at most 1 rear camera and 1 front + * camera in the system. For an application that switches between front and back cameras, + * the recommendation is to switch between the first rear camera and the first front + * camera in the list of supported camera devices.</p> + * <p>This capability requires the camera device to support the following:</p> + * <ul> + * <li>The IDs of underlying physical cameras are returned via + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#getPhysicalCameraIds">CameraCharacteristics#getPhysicalCameraIds</a>.</li> + * <li>This camera device must list static metadata + * ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE in + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html">CameraCharacteristics</a>.</li> + * <li>The underlying physical cameras' static metadata must list the following entries, + * so that the application can correlate pixels from the physical streams:<ul> + * <li>ACAMERA_LENS_POSE_REFERENCE</li> + * <li>ACAMERA_LENS_POSE_ROTATION</li> + * <li>ACAMERA_LENS_POSE_TRANSLATION</li> + * <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li> + * <li>ACAMERA_LENS_DISTORTION</li> + * </ul> + * </li> + * <li>The SENSOR_INFO_TIMESTAMP_SOURCE of the logical device and physical devices must be + * the same.</li> + * <li>The logical camera device must be LIMITED or higher device.</li> + * </ul> + * <p>Both the logical camera device and its underlying physical devices support the + * mandatory stream combinations required for their device levels.</p> + * <p>Additionally, for each guaranteed stream combination, the logical camera supports:</p> + * <ul> + * <li>For each guaranteed stream combination, the logical camera supports replacing one + * logical {@link AIMAGE_FORMAT_YUV_420_888 YUV_420_888} + * or raw stream with two physical streams of the same size and format, each from a + * separate physical camera, given that the size and format are supported by both + * physical cameras.</li> + * <li>If the logical camera doesn't advertise RAW capability, but the underlying physical + * cameras do, the logical camera will support guaranteed stream combinations for RAW + * capability, except that the RAW streams will be physical streams, each from a separate + * physical camera. This is usually the case when the physical cameras have different + * sensor sizes.</li> + * </ul> + * <p>Using physical streams in place of a logical stream of the same size and format will + * not slow down the frame rate of the capture, as long as the minimum frame duration + * of the physical and logical streams are the same.</p> + * + * @see ACAMERA_LENS_DISTORTION + * @see ACAMERA_LENS_INTRINSIC_CALIBRATION + * @see ACAMERA_LENS_POSE_REFERENCE + * @see ACAMERA_LENS_POSE_ROTATION + * @see ACAMERA_LENS_POSE_TRANSLATION + * @see ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + */ + ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11, + + /** + * <p>The camera device is a monochrome camera that doesn't contain a color filter array, + * and the pixel values on U and V planes are all 128.</p> + */ + ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME = 12, + } acamera_metadata_enum_android_request_available_capabilities_t; @@ -6918,8 +7494,8 @@ /** * <p>Timestamps from ACAMERA_SENSOR_TIMESTAMP are in the same timebase as - * <a href="https://developer.android.com/reference/android/os/SystemClock.html#elapsedRealtimeNanos">elapsedRealtimeNanos</a> - * (or CLOCK_BOOTTIME), and they can be compared to other timestamps using that base.</p> + * <a href="https://developer.android.com/reference/android/os/SystemClock.html#elapsedRealtimeNanos">SystemClock#elapsedRealtimeNanos</a>, + * and they can be compared to other timestamps using that base.</p> * * @see ACAMERA_SENSOR_TIMESTAMP */ @@ -7030,6 +7606,26 @@ } acamera_metadata_enum_android_statistics_lens_shading_map_mode_t; +// ACAMERA_STATISTICS_OIS_DATA_MODE +typedef enum acamera_metadata_enum_acamera_statistics_ois_data_mode { + /** + * <p>Do not include OIS data in the capture result.</p> + */ + ACAMERA_STATISTICS_OIS_DATA_MODE_OFF = 0, + + /** + * <p>Include OIS data in the capture result.</p> + * <p>ACAMERA_STATISTICS_OIS_TIMESTAMPS, ACAMERA_STATISTICS_OIS_X_SHIFTS, + * and ACAMERA_STATISTICS_OIS_Y_SHIFTS provide OIS data in the output result metadata.</p> + * + * @see ACAMERA_STATISTICS_OIS_TIMESTAMPS + * @see ACAMERA_STATISTICS_OIS_X_SHIFTS + * @see ACAMERA_STATISTICS_OIS_Y_SHIFTS + */ + ACAMERA_STATISTICS_OIS_DATA_MODE_ON = 1, + +} acamera_metadata_enum_android_statistics_ois_data_mode_t; + // ACAMERA_TONEMAP_MODE @@ -7104,7 +7700,7 @@ * <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or * better.</p> * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the - * {@link ACameraDevice_createCaptureSession} documentation are guaranteed to be supported.</p> + * {@link ACameraDevice_createCaptureSession createCaptureSession} documentation are guaranteed to be supported.</p> * <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic * support for color image capture. The only exception is that the device may * alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth @@ -7130,7 +7726,7 @@ /** * <p>This camera device is capable of supporting advanced imaging applications.</p> * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the - * {@link ACameraDevice_createCaptureSession} documentation are guaranteed to be supported.</p> + * {@link ACameraDevice_createCaptureSession createCaptureSession} documentation are guaranteed to be supported.</p> * <p>A <code>FULL</code> device will support below capabilities:</p> * <ul> * <li><code>BURST_CAPTURE</code> capability (ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains @@ -7157,8 +7753,7 @@ /** * <p>This camera device is running in backward compatibility mode.</p> - * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the {@link - * ACameraDevice_createCaptureSession} documentation are supported.</p> + * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the {@link ACameraDevice_createCaptureSession createCaptureSession} documentation are supported.</p> * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual * post-processing, arbitrary cropping regions, and has relaxed performance constraints. * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a @@ -7179,9 +7774,7 @@ * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to * FULL-level capabilities.</p> * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and - * <code>LIMITED</code> tables in the {@link - * ACameraDevice_createCaptureSession} - * documentation are guaranteed to be supported.</p> + * <code>LIMITED</code> tables in the {@link ACameraDevice_createCaptureSession createCaptureSession} documentation are guaranteed to be supported.</p> * <p>The following additional capabilities are guaranteed to be supported:</p> * <ul> * <li><code>YUV_REPROCESSING</code> capability (ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains @@ -7194,6 +7787,37 @@ */ ACAMERA_INFO_SUPPORTED_HARDWARE_LEVEL_3 = 3, + /** + * <p>This camera device is backed by an external camera connected to this Android device.</p> + * <p>The device has capability identical to a LIMITED level device, with the following + * exceptions:</p> + * <ul> + * <li>The device may not report lens/sensor related information such as<ul> + * <li>ACAMERA_LENS_FOCAL_LENGTH</li> + * <li>ACAMERA_LENS_INFO_HYPERFOCAL_DISTANCE</li> + * <li>ACAMERA_SENSOR_INFO_PHYSICAL_SIZE</li> + * <li>ACAMERA_SENSOR_INFO_WHITE_LEVEL</li> + * <li>ACAMERA_SENSOR_BLACK_LEVEL_PATTERN</li> + * <li>ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT</li> + * <li>ACAMERA_SENSOR_ROLLING_SHUTTER_SKEW</li> + * </ul> + * </li> + * <li>The device will report 0 for ACAMERA_SENSOR_ORIENTATION</li> + * <li>The device has less guarantee on stable framerate, as the framerate partly depends + * on the external camera being used.</li> + * </ul> + * + * @see ACAMERA_LENS_FOCAL_LENGTH + * @see ACAMERA_LENS_INFO_HYPERFOCAL_DISTANCE + * @see ACAMERA_SENSOR_BLACK_LEVEL_PATTERN + * @see ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT + * @see ACAMERA_SENSOR_INFO_PHYSICAL_SIZE + * @see ACAMERA_SENSOR_INFO_WHITE_LEVEL + * @see ACAMERA_SENSOR_ORIENTATION + * @see ACAMERA_SENSOR_ROLLING_SHUTTER_SKEW + */ + ACAMERA_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL = 4, + } acamera_metadata_enum_android_info_supported_hardware_level_t; @@ -7281,6 +7905,48 @@ } acamera_metadata_enum_android_depth_depth_is_exclusive_t; +// ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE +typedef enum acamera_metadata_enum_acamera_logical_multi_camera_sensor_sync_type { + /** + * <p>A software mechanism is used to synchronize between the physical cameras. As a result, + * the timestamp of an image from a physical stream is only an approximation of the + * image sensor start-of-exposure time.</p> + */ + ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0, + + /** + * <p>The camera device supports frame timestamp synchronization at the hardware level, + * and the timestamp of a physical stream image accurately reflects its + * start-of-exposure time.</p> + */ + ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED = 1, + +} acamera_metadata_enum_android_logical_multi_camera_sensor_sync_type_t; + + +// ACAMERA_DISTORTION_CORRECTION_MODE +typedef enum acamera_metadata_enum_acamera_distortion_correction_mode { + /** + * <p>No distortion correction is applied.</p> + */ + ACAMERA_DISTORTION_CORRECTION_MODE_OFF = 0, + + /** + * <p>Lens distortion correction is applied without reducing frame rate + * relative to sensor output. It may be the same as OFF if distortion correction would + * reduce frame rate relative to sensor.</p> + */ + ACAMERA_DISTORTION_CORRECTION_MODE_FAST = 1, + + /** + * <p>High-quality distortion correction is applied, at the cost of + * possibly reduced frame rate relative to sensor output.</p> + */ + ACAMERA_DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2, + +} acamera_metadata_enum_android_distortion_correction_mode_t; + + #endif /* __ANDROID_API__ >= 24 */ __END_DECLS
diff --git a/camera/ndk/include/camera/NdkCaptureRequest.h b/camera/ndk/include/camera/NdkCaptureRequest.h index c62ba2c..4961ce3 100644 --- a/camera/ndk/include/camera/NdkCaptureRequest.h +++ b/camera/ndk/include/camera/NdkCaptureRequest.h
@@ -305,6 +305,58 @@ #endif /* __ANDROID_API__ >= 24 */ +#if __ANDROID_API__ >= 28 + +/** + * Associate an arbitrary user context pointer to the {@link ACaptureRequest} + * + * This method is useful for user to identify the capture request in capture session callbacks. + * The context is NULL for newly created request. + * {@link ACameraOutputTarget_free} will not free the context. Also calling this method twice + * will not cause the previous context be freed. + * Also note that calling this method after the request has been sent to capture session will not + * change the context pointer in the capture callbacks. + * + * @param request the {@link ACaptureRequest} of interest. + * @param context the user context pointer to be associated with this capture request. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request is NULL.</li></ul> + */ +camera_status_t ACaptureRequest_setUserContext( + ACaptureRequest* request, void* context); + +/** + * Get the user context pointer of the {@link ACaptureRequest} + * + * This method is useful for user to identify the capture request in capture session callbacks. + * The context is NULL for newly created request. + * + * @param request the {@link ACaptureRequest} of interest. + * @param context the user context pointer of this capture request. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request is NULL.</li></ul> + */ +camera_status_t ACaptureRequest_getUserContext( + const ACaptureRequest* request, /*out*/void** context); + +/** + * Create a copy of input {@link ACaptureRequest}. + * + * <p>The returned ACaptureRequest must be freed by the application by {@link ACaptureRequest_free} + * after application is done using it.</p> + * + * @param src the input {@link ACaptureRequest} to be copied. + * + * @return a valid ACaptureRequest pointer or NULL if the input request cannot be copied. + */ +ACaptureRequest* ACaptureRequest_copy(const ACaptureRequest* src); + +#endif /* __ANDROID_API__ >= 28 */ + __END_DECLS #endif /* _NDK_CAPTURE_REQUEST_H */
diff --git a/camera/ndk/libcamera2ndk.map.txt b/camera/ndk/libcamera2ndk.map.txt index 41bb22b..d179aa0 100644 --- a/camera/ndk/libcamera2ndk.map.txt +++ b/camera/ndk/libcamera2ndk.map.txt
@@ -6,9 +6,11 @@ ACameraCaptureSession_getDevice; ACameraCaptureSession_setRepeatingRequest; ACameraCaptureSession_stopRepeating; + ACameraCaptureSession_updateSharedOutput; ACameraDevice_close; ACameraDevice_createCaptureRequest; ACameraDevice_createCaptureSession; + ACameraDevice_createCaptureSessionWithSessionParameters; ACameraDevice_getId; ACameraManager_create; ACameraManager_delete; @@ -25,9 +27,11 @@ ACameraOutputTarget_create; ACameraOutputTarget_free; ACaptureRequest_addTarget; + ACaptureRequest_copy; ACaptureRequest_free; ACaptureRequest_getAllTags; ACaptureRequest_getConstEntry; + ACaptureRequest_getUserContext; ACaptureRequest_removeTarget; ACaptureRequest_setEntry_double; ACaptureRequest_setEntry_float; @@ -35,11 +39,15 @@ ACaptureRequest_setEntry_i64; ACaptureRequest_setEntry_rational; ACaptureRequest_setEntry_u8; + ACaptureRequest_setUserContext; ACaptureSessionOutputContainer_add; ACaptureSessionOutputContainer_create; ACaptureSessionOutputContainer_free; ACaptureSessionOutputContainer_remove; ACaptureSessionOutput_create; + ACaptureSessionSharedOutput_create; + ACaptureSessionSharedOutput_add; + ACaptureSessionSharedOutput_remove; ACaptureSessionOutput_free; local: *;
diff --git a/camera/tests/CameraBinderTests.cpp b/camera/tests/CameraBinderTests.cpp index 51d9214..1de7013 100644 --- a/camera/tests/CameraBinderTests.cpp +++ b/camera/tests/CameraBinderTests.cpp
@@ -198,9 +198,11 @@ virtual binder::Status onResultReceived(const CameraMetadata& metadata, - const CaptureResultExtras& resultExtras) { + const CaptureResultExtras& resultExtras, + const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) { (void) metadata; (void) resultExtras; + (void) physicalResultInfos; Mutex::Autolock l(mLock); mLastStatus = SENT_RESULT; mStatusesHit.push_back(mLastStatus); @@ -317,6 +319,9 @@ EXPECT_TRUE(res.isOk()) << res; EXPECT_EQ(numCameras, static_cast<const int>(statuses.size())); + for (const auto &it : statuses) { + listener->onStatusChanged(it.status, String16(it.cameraId)); + } for (int32_t i = 0; i < numCameras; i++) { String16 cameraId = String16(String8::format("%d", i)); @@ -421,6 +426,9 @@ serviceListener = new TestCameraServiceListener(); std::vector<hardware::CameraStatus> statuses; service->addListener(serviceListener, &statuses); + for (const auto &it : statuses) { + serviceListener->onStatusChanged(it.status, String16(it.cameraId)); + } service->getNumberOfCameras(hardware::ICameraService::CAMERA_TYPE_BACKWARD_COMPATIBLE, &numCameras); } @@ -439,8 +447,9 @@ ASSERT_NOT_NULL(service); EXPECT_TRUE(serviceListener->waitForNumCameras(numCameras)); for (int32_t i = 0; i < numCameras; i++) { + String8 cameraId8 = String8::format("%d", i); // Make sure we're available, or skip device tests otherwise - String16 cameraId(String8::format("%d",i)); + String16 cameraId(cameraId8); int32_t s = serviceListener->getStatus(cameraId); EXPECT_EQ(hardware::ICameraServiceListener::STATUS_PRESENT, s); if (s != hardware::ICameraServiceListener::STATUS_PRESENT) { @@ -476,7 +485,8 @@ res = device->createStream(output, &streamId); EXPECT_TRUE(res.isOk()) << res; EXPECT_LE(0, streamId); - res = device->endConfigure(/*isConstrainedHighSpeed*/ false); + CameraMetadata sessionParams; + res = device->endConfigure(/*isConstrainedHighSpeed*/ false, sessionParams); EXPECT_TRUE(res.isOk()) << res; EXPECT_FALSE(callbacks->hadError()); @@ -487,7 +497,7 @@ EXPECT_TRUE(res.isOk()) << res; hardware::camera2::CaptureRequest request; - request.mMetadata = requestTemplate; + request.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate}); request.mSurfaceList.add(surface); request.mIsReprocess = false; int64_t lastFrameNumber = 0; @@ -514,7 +524,7 @@ /*out*/&requestTemplate); EXPECT_TRUE(res.isOk()) << res; hardware::camera2::CaptureRequest request2; - request2.mMetadata = requestTemplate; + request2.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate}); request2.mSurfaceList.add(surface); request2.mIsReprocess = false; callbacks->clearStatus(); @@ -547,10 +557,10 @@ EXPECT_TRUE(res.isOk()) << res; android::hardware::camera2::CaptureRequest request3; android::hardware::camera2::CaptureRequest request4; - request3.mMetadata = requestTemplate; + request3.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate}); request3.mSurfaceList.add(surface); request3.mIsReprocess = false; - request4.mMetadata = requestTemplate2; + request4.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate2}); request4.mSurfaceList.add(surface); request4.mIsReprocess = false; std::vector<hardware::camera2::CaptureRequest> requestList; @@ -574,7 +584,7 @@ EXPECT_TRUE(res.isOk()) << res; res = device->deleteStream(streamId); EXPECT_TRUE(res.isOk()) << res; - res = device->endConfigure(/*isConstrainedHighSpeed*/ false); + res = device->endConfigure(/*isConstrainedHighSpeed*/ false, sessionParams); EXPECT_TRUE(res.isOk()) << res; sleep(/*second*/1); // allow some time for errors to show up, if any @@ -584,3 +594,62 @@ } }; + +TEST_F(CameraClientBinderTest, CheckBinderCaptureRequest) { + sp<CaptureRequest> requestOriginal, requestParceled; + sp<IGraphicBufferProducer> gbProducer; + sp<IGraphicBufferConsumer> gbConsumer; + BufferQueue::createBufferQueue(&gbProducer, &gbConsumer); + sp<Surface> surface(new Surface(gbProducer, /*controlledByApp*/false)); + Vector<sp<Surface>> surfaceList; + surfaceList.push_back(surface); + std::string physicalDeviceId1 = "0"; + std::string physicalDeviceId2 = "1"; + CameraMetadata physicalDeviceSettings1, physicalDeviceSettings2; + uint8_t intent1 = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW; + uint8_t intent2 = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD; + EXPECT_EQ(OK, physicalDeviceSettings1.update(ANDROID_CONTROL_CAPTURE_INTENT, &intent1, 1)); + EXPECT_EQ(OK, physicalDeviceSettings2.update(ANDROID_CONTROL_CAPTURE_INTENT, &intent2, 1)); + + requestParceled = new CaptureRequest(); + Parcel p; + EXPECT_TRUE(requestParceled->readFromParcel(&p) != OK); + p.writeInt32(0); + p.setDataPosition(0); + EXPECT_TRUE(requestParceled->readFromParcel(&p) != OK); + p.freeData(); + p.writeInt32(-1); + p.setDataPosition(0); + EXPECT_TRUE(requestParceled->readFromParcel(&p) != OK); + p.freeData(); + p.writeInt32(1); + p.setDataPosition(0); + EXPECT_TRUE(requestParceled->readFromParcel(&p) != OK); + + requestOriginal = new CaptureRequest(); + requestOriginal->mPhysicalCameraSettings.push_back({physicalDeviceId1, + physicalDeviceSettings1}); + requestOriginal->mPhysicalCameraSettings.push_back({physicalDeviceId2, + physicalDeviceSettings2}); + requestOriginal->mSurfaceList.push_back(surface); + requestOriginal->mIsReprocess = false; + requestOriginal->mSurfaceConverted = false; + + p.freeData(); + EXPECT_TRUE(requestOriginal->writeToParcel(&p) == OK); + p.setDataPosition(0); + EXPECT_TRUE(requestParceled->readFromParcel(&p) == OK); + EXPECT_EQ(requestParceled->mIsReprocess, false); + EXPECT_FALSE(requestParceled->mSurfaceList.empty()); + EXPECT_EQ(2u, requestParceled->mPhysicalCameraSettings.size()); + auto it = requestParceled->mPhysicalCameraSettings.begin(); + EXPECT_EQ(physicalDeviceId1, it->id); + EXPECT_TRUE(it->settings.exists(ANDROID_CONTROL_CAPTURE_INTENT)); + auto entry = it->settings.find(ANDROID_CONTROL_CAPTURE_INTENT); + EXPECT_EQ(entry.data.u8[0], intent1); + it++; + EXPECT_EQ(physicalDeviceId2, it->id); + EXPECT_TRUE(it->settings.exists(ANDROID_CONTROL_CAPTURE_INTENT)); + entry = it->settings.find(ANDROID_CONTROL_CAPTURE_INTENT); + EXPECT_EQ(entry.data.u8[0], intent2); +};
diff --git a/camera/tests/CameraZSLTests.cpp b/camera/tests/CameraZSLTests.cpp index ecca354..02c6e2a 100644 --- a/camera/tests/CameraZSLTests.cpp +++ b/camera/tests/CameraZSLTests.cpp
@@ -256,10 +256,10 @@ ASSERT_TRUE(nullptr != surfaceControl.get()); ASSERT_TRUE(surfaceControl->isValid()); - SurfaceComposerClient::openGlobalTransaction(); - ASSERT_EQ(NO_ERROR, surfaceControl->setLayer(0x7fffffff)); - ASSERT_EQ(NO_ERROR, surfaceControl->show()); - SurfaceComposerClient::closeGlobalTransaction(); + SurfaceComposerClient::Transaction{} + .setLayer(surfaceControl, 0x7fffffff) + .show(surfaceControl) + .apply(); previewSurface = surfaceControl->getSurface(); ASSERT_TRUE(previewSurface != NULL);
diff --git a/camera/tests/VendorTagDescriptorTests.cpp b/camera/tests/VendorTagDescriptorTests.cpp index 75cfb73..0ee358d 100644 --- a/camera/tests/VendorTagDescriptorTests.cpp +++ b/camera/tests/VendorTagDescriptorTests.cpp
@@ -142,6 +142,7 @@ EXPECT_EQ(OK, vDescOriginal->writeToParcel(&p)); p.setDataPosition(0); + vDescParceled = new VendorTagDescriptor(); ASSERT_EQ(OK, vDescParceled->readFromParcel(&p)); // Ensure consistent tag count
diff --git a/cmds/screenrecord/Android.mk b/cmds/screenrecord/Android.mk index 7aa684a..5e83ed6 100644 --- a/cmds/screenrecord/Android.mk +++ b/cmds/screenrecord/Android.mk
@@ -25,8 +25,8 @@ Program.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libutils libbinder libstagefright_foundation \ - libjpeg libgui libcutils liblog libEGL libGLESv2 + libstagefright libmedia libmedia_omx libutils libbinder libstagefright_foundation \ + libjpeg libui libgui libcutils liblog libEGL libGLESv2 LOCAL_C_INCLUDES := \ frameworks/av/media/libstagefright \
diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp index bc32bbe..d1859d1 100644 --- a/cmds/screenrecord/screenrecord.cpp +++ b/cmds/screenrecord/screenrecord.cpp
@@ -50,6 +50,7 @@ #include <media/stagefright/MediaCodec.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaMuxer.h> +#include <media/stagefright/PersistentSurface.h> #include <media/ICrypto.h> #include <media/MediaCodecBuffer.h> @@ -70,9 +71,11 @@ static bool gVerbose = false; // chatty on stdout static bool gRotate = false; // rotate 90 degrees static bool gMonotonicTime = false; // use system monotonic time for timestamps +static bool gPersistentSurface = false; // use persistent surface static enum { FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES, FORMAT_RAW_FRAMES } gOutputFormat = FORMAT_MP4; // data format for output +static AString gCodecName = ""; // codec name override static bool gSizeSpecified = false; // was size explicitly requested? static bool gWantInfoScreen = false; // do we want initial info screen? static bool gWantFrameTime = false; // do we want times on each frame? @@ -132,18 +135,11 @@ strerror(errno)); return err; } + signal(SIGPIPE, SIG_IGN); return NO_ERROR; } /* - * Returns "true" if the device is rotated 90 degrees. - */ -static bool isDeviceRotated(int orientation) { - return orientation != DISPLAY_ORIENTATION_0 && - orientation != DISPLAY_ORIENTATION_180; -} - -/* * Configures and starts the MediaCodec encoder. Obtains an input surface * from the codec. */ @@ -154,6 +150,7 @@ if (gVerbose) { printf("Configuring recorder for %dx%d %s at %.2fMbps\n", gVideoWidth, gVideoHeight, kMimeTypeAvc, gBitRate / 1000000.0); + fflush(stdout); } sp<AMessage> format = new AMessage; @@ -169,11 +166,21 @@ looper->setName("screenrecord_looper"); looper->start(); ALOGV("Creating codec"); - sp<MediaCodec> codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true); - if (codec == NULL) { - fprintf(stderr, "ERROR: unable to create %s codec instance\n", - kMimeTypeAvc); - return UNKNOWN_ERROR; + sp<MediaCodec> codec; + if (gCodecName.empty()) { + codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true); + if (codec == NULL) { + fprintf(stderr, "ERROR: unable to create %s codec instance\n", + kMimeTypeAvc); + return UNKNOWN_ERROR; + } + } else { + codec = MediaCodec::CreateByComponentName(looper, gCodecName); + if (codec == NULL) { + fprintf(stderr, "ERROR: unable to create %s codec instance\n", + gCodecName.c_str()); + return UNKNOWN_ERROR; + } } err = codec->configure(format, NULL, NULL, @@ -187,10 +194,18 @@ ALOGV("Creating encoder input surface"); sp<IGraphicBufferProducer> bufferProducer; - err = codec->createInputSurface(&bufferProducer); + if (gPersistentSurface) { + sp<PersistentSurface> surface = MediaCodec::CreatePersistentInputSurface(); + bufferProducer = surface->getBufferProducer(); + err = codec->setInputSurface(surface); + } else { + err = codec->createInputSurface(&bufferProducer); + } if (err != NO_ERROR) { fprintf(stderr, - "ERROR: unable to create encoder input surface (err=%d)\n", err); + "ERROR: unable to %s encoder input surface (err=%d)\n", + gPersistentSurface ? "set" : "create", + err); codec->release(); return err; } @@ -213,26 +228,17 @@ * Sets the display projection, based on the display dimensions, video size, * and device orientation. */ -static status_t setDisplayProjection(const sp<IBinder>& dpy, +static status_t setDisplayProjection( + SurfaceComposerClient::Transaction& t, + const sp<IBinder>& dpy, const DisplayInfo& mainDpyInfo) { // Set the region of the layer stack we're interested in, which in our - // case is "all of it". If the app is rotated (so that the width of the - // app is based on the height of the display), reverse width/height. - bool deviceRotated = isDeviceRotated(mainDpyInfo.orientation); - uint32_t sourceWidth, sourceHeight; - if (!deviceRotated) { - sourceWidth = mainDpyInfo.w; - sourceHeight = mainDpyInfo.h; - } else { - ALOGV("using rotated width/height"); - sourceHeight = mainDpyInfo.w; - sourceWidth = mainDpyInfo.h; - } - Rect layerStackRect(sourceWidth, sourceHeight); + // case is "all of it". + Rect layerStackRect(mainDpyInfo.w, mainDpyInfo.h); // We need to preserve the aspect ratio of the display. - float displayAspect = (float) sourceHeight / (float) sourceWidth; + float displayAspect = (float) mainDpyInfo.h / (float) mainDpyInfo.w; // Set the way we map the output onto the display surface (which will @@ -273,13 +279,15 @@ if (gRotate) { printf("Rotated content area is %ux%u at offset x=%d y=%d\n", outHeight, outWidth, offY, offX); + fflush(stdout); } else { printf("Content area is %ux%u at offset x=%d y=%d\n", outWidth, outHeight, offX, offY); + fflush(stdout); } } - SurfaceComposerClient::setDisplayProjection(dpy, + t.setDisplayProjection(dpy, gRotate ? DISPLAY_ORIENTATION_90 : DISPLAY_ORIENTATION_0, layerStackRect, displayRect); return NO_ERROR; @@ -295,11 +303,11 @@ sp<IBinder> dpy = SurfaceComposerClient::createDisplay( String8("ScreenRecorder"), false /*secure*/); - SurfaceComposerClient::openGlobalTransaction(); - SurfaceComposerClient::setDisplaySurface(dpy, bufferProducer); - setDisplayProjection(dpy, mainDpyInfo); - SurfaceComposerClient::setDisplayLayerStack(dpy, 0); // default stack - SurfaceComposerClient::closeGlobalTransaction(); + SurfaceComposerClient::Transaction t; + t.setDisplaySurface(dpy, bufferProducer); + setDisplayProjection(t, dpy, mainDpyInfo); + t.setDisplayLayerStack(dpy, 0); // default stack + t.apply(); *pDisplayHandle = dpy; @@ -307,6 +315,22 @@ } /* + * Set the main display width and height to the actual width and height + */ +static status_t getActualDisplaySize(const sp<IBinder>& mainDpy, DisplayInfo* mainDpyInfo) { + Rect viewport; + status_t err = SurfaceComposerClient::getDisplayViewport(mainDpy, &viewport); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: unable to get display viewport\n"); + return err; + } + mainDpyInfo->w = viewport.width(); + mainDpyInfo->h = viewport.height(); + + return NO_ERROR; +} + +/* * Runs the MediaCodec encoder, sending the output to the MediaMuxer. The * input frames are coming from the virtual display as fast as SurfaceFlinger * wants to send them. @@ -344,6 +368,7 @@ if (systemTime(CLOCK_MONOTONIC) > endWhenNsec) { if (gVerbose) { printf("Time limit reached\n"); + fflush(stdout); } break; } @@ -375,14 +400,22 @@ // useful stuff is hard to get at without a Dalvik VM. err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo); - if (err != NO_ERROR) { + if (err == NO_ERROR) { + err = getActualDisplaySize(mainDpy, &mainDpyInfo); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: unable to set actual display size\n"); + return err; + } + + if (orientation != mainDpyInfo.orientation) { + ALOGD("orientation changed, now %d", mainDpyInfo.orientation); + SurfaceComposerClient::Transaction t; + setDisplayProjection(t, virtualDpy, mainDpyInfo); + t.apply(); + orientation = mainDpyInfo.orientation; + } + } else { ALOGW("getDisplayInfo(main) failed: %d", err); - } else if (orientation != mainDpyInfo.orientation) { - ALOGD("orientation changed, now %d", mainDpyInfo.orientation); - SurfaceComposerClient::openGlobalTransaction(); - setDisplayProjection(virtualDpy, mainDpyInfo); - SurfaceComposerClient::closeGlobalTransaction(); - orientation = mainDpyInfo.orientation; } } @@ -481,6 +514,7 @@ printf("Encoder stopping; recorded %u frames in %" PRId64 " seconds\n", debugNumFrames, nanoseconds_to_seconds( systemTime(CLOCK_MONOTONIC) - startWhenNsec)); + fflush(stdout); } return NO_ERROR; } @@ -523,6 +557,10 @@ return rawFp; } +static inline uint32_t floorToEven(uint32_t num) { + return num & ~1; +} + /* * Main "do work" start point. * @@ -550,18 +588,26 @@ fprintf(stderr, "ERROR: unable to get display characteristics\n"); return err; } + + err = getActualDisplaySize(mainDpy, &mainDpyInfo); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: unable to set actual display size\n"); + return err; + } + if (gVerbose) { printf("Main display is %dx%d @%.2ffps (orientation=%u)\n", mainDpyInfo.w, mainDpyInfo.h, mainDpyInfo.fps, mainDpyInfo.orientation); + fflush(stdout); } - bool rotated = isDeviceRotated(mainDpyInfo.orientation); + // Encoder can't take odd number as config if (gVideoWidth == 0) { - gVideoWidth = rotated ? mainDpyInfo.h : mainDpyInfo.w; + gVideoWidth = floorToEven(mainDpyInfo.w); } if (gVideoHeight == 0) { - gVideoHeight = rotated ? mainDpyInfo.w : mainDpyInfo.h; + gVideoHeight = floorToEven(mainDpyInfo.h); } // Configure and start the encoder. @@ -621,6 +667,7 @@ } if (gVerbose) { printf("Bugreport overlay created\n"); + fflush(stdout); } } else { // Use the encoder's input surface as the virtual display surface. @@ -713,6 +760,7 @@ if (gVerbose) { printf("Stopping encoder and muxer\n"); + fflush(stdout); } } @@ -759,6 +807,7 @@ printf(" %s", argv[i]); } putchar('\n'); + fflush(stdout); } pid_t pid = fork(); @@ -896,7 +945,9 @@ { "show-frame-time", no_argument, NULL, 'f' }, { "rotate", no_argument, NULL, 'r' }, { "output-format", required_argument, NULL, 'o' }, + { "codec-name", required_argument, NULL, 'N' }, { "monotonic-time", no_argument, NULL, 'm' }, + { "persistent-surface", no_argument, NULL, 'p' }, { NULL, 0, NULL, 0 } }; @@ -976,9 +1027,15 @@ return 2; } break; + case 'N': + gCodecName = optarg; + break; case 'm': gMonotonicTime = true; break; + case 'p': + gPersistentSurface = true; + break; default: if (ic != '?') { fprintf(stderr, "getopt_long returned unexpected value 0x%x\n", ic);
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index f647ffd..c7619af 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk
@@ -8,9 +8,9 @@ SineSource.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libutils libbinder libstagefright_foundation \ - libjpeg libgui libcutils liblog \ - libhidlmemory \ + libstagefright libmedia libmedia_omx libmediaextractor libutils libbinder \ + libstagefright_foundation libjpeg libui libgui libcutils liblog \ + libhidlbase \ android.hardware.media.omx@1.0 \ LOCAL_C_INCLUDES:= \ @@ -36,7 +36,8 @@ record.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia liblog libutils libbinder libstagefright_foundation + libstagefright libmedia libmediaextractor liblog libutils libbinder \ + libstagefright_foundation LOCAL_C_INCLUDES:= \ frameworks/av/media/libstagefright \ @@ -60,7 +61,8 @@ recordvideo.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia liblog libutils libbinder libstagefright_foundation + libstagefright libmedia libmediaextractor liblog libutils libbinder \ + libstagefright_foundation LOCAL_C_INCLUDES:= \ frameworks/av/media/libstagefright \ @@ -85,7 +87,8 @@ audioloop.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia liblog libutils libbinder libstagefright_foundation + libstagefright libmedia libmediaextractor liblog libutils libbinder \ + libstagefright_foundation LOCAL_C_INCLUDES:= \ frameworks/av/media/libstagefright \ @@ -107,8 +110,8 @@ stream.cpp \ LOCAL_SHARED_LIBRARIES := \ - libstagefright liblog libutils libbinder libgui \ - libstagefright_foundation libmedia libcutils + libstagefright liblog libutils libbinder libui libgui \ + libstagefright_foundation libmedia libcutils libmediaextractor LOCAL_C_INCLUDES:= \ frameworks/av/media/libstagefright \ @@ -132,7 +135,7 @@ LOCAL_SHARED_LIBRARIES := \ libstagefright liblog libutils libbinder libstagefright_foundation \ - libmedia libaudioclient libgui libcutils + libmedia libmedia_omx libaudioclient libui libgui libcutils LOCAL_C_INCLUDES:= \ frameworks/av/media/libstagefright \ @@ -163,6 +166,8 @@ libbinder \ libstagefright_foundation \ libmedia \ + libmedia_omx \ + libui \ libgui \ libcutils \ libRScpp \ @@ -199,7 +204,7 @@ LOCAL_SHARED_LIBRARIES := \ libstagefright liblog libutils libbinder libstagefright_foundation \ - libcutils libc + libcutils libc libmediaextractor LOCAL_C_INCLUDES:= \ frameworks/av/media/libstagefright \
diff --git a/cmds/stagefright/SineSource.cpp b/cmds/stagefright/SineSource.cpp index cad8caf..0ecc16c 100644 --- a/cmds/stagefright/SineSource.cpp +++ b/cmds/stagefright/SineSource.cpp
@@ -4,6 +4,7 @@ #include <media/stagefright/MediaBufferGroup.h> #include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> @@ -59,10 +60,10 @@ } status_t SineSource::read( - MediaBuffer **out, const ReadOptions * /* options */) { + MediaBufferBase **out, const ReadOptions * /* options */) { *out = NULL; - MediaBuffer *buffer; + MediaBufferBase *buffer; status_t err = mGroup->acquire_buffer(&buffer); if (err != OK) { @@ -88,7 +89,7 @@ x += k; } - buffer->meta_data()->setInt64( + buffer->meta_data().setInt64( kKeyTime, ((int64_t)mPhase * 1000000) / mSampleRate); mPhase += numFramesPerBuffer;
diff --git a/cmds/stagefright/SineSource.h b/cmds/stagefright/SineSource.h index be05661..1817291 100644 --- a/cmds/stagefright/SineSource.h +++ b/cmds/stagefright/SineSource.h
@@ -2,7 +2,7 @@ #define SINE_SOURCE_H_ -#include <media/stagefright/MediaSource.h> +#include <media/MediaSource.h> #include <utils/Compat.h> namespace android { @@ -18,7 +18,7 @@ virtual sp<MetaData> getFormat(); virtual status_t read( - MediaBuffer **out, const ReadOptions *options = NULL); + MediaBufferBase **out, const ReadOptions *options = NULL); protected: virtual ~SineSource();
diff --git a/cmds/stagefright/audioloop.cpp b/cmds/stagefright/audioloop.cpp index ed44b4d..d4f2e8d 100644 --- a/cmds/stagefright/audioloop.cpp +++ b/cmds/stagefright/audioloop.cpp
@@ -14,6 +14,10 @@ * limitations under the License. */ +#define LOG_NDEBUG 0 +#define LOG_TAG "audioloop" +#include <utils/Log.h> + #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> @@ -29,7 +33,6 @@ #include <media/stagefright/AudioSource.h> #include <media/stagefright/MediaCodecSource.h> #include <media/stagefright/MediaDefs.h> -#include <media/stagefright/MetaData.h> #include <media/stagefright/SimpleDecodingSource.h> #include "SineSource.h" @@ -37,11 +40,13 @@ static void usage(const char* name) { - fprintf(stderr, "Usage: %s [-d du.ration] [-m] [-w] [<output-file>]\n", name); + fprintf(stderr, "Usage: %s [-d du.ration] [-m] [-w] [-N name] [<output-file>]\n", name); fprintf(stderr, "Encodes either a sine wave or microphone input to AMR format\n"); fprintf(stderr, " -d duration in seconds, default 5 seconds\n"); fprintf(stderr, " -m use microphone for input, default sine source\n"); fprintf(stderr, " -w use AMR wideband (default narrowband)\n"); + fprintf(stderr, " -N name of the encoder; must be set with -M\n"); + fprintf(stderr, " -M media type of the encoder; must be set with -N\n"); fprintf(stderr, " <output-file> output file for AMR encoding," " if unspecified, decode to speaker.\n"); } @@ -54,8 +59,10 @@ bool outputWBAMR = false; bool playToSpeaker = true; const char* fileOut = NULL; + AString name; + AString mediaType; int ch; - while ((ch = getopt(argc, argv, "d:mw")) != -1) { + while ((ch = getopt(argc, argv, "d:mwN:M:")) != -1) { switch (ch) { case 'd': duration = atoi(optarg); @@ -66,6 +73,12 @@ case 'w': outputWBAMR = true; break; + case 'N': + name.setTo(optarg); + break; + case 'M': + mediaType.setTo(optarg); + break; default: usage(argv[0]); return -1; @@ -76,8 +89,18 @@ if (argc == 1) { fileOut = argv[0]; } - const int32_t kSampleRate = outputWBAMR ? 16000 : 8000; - const int32_t kBitRate = outputWBAMR ? 16000 : 8000; + if ((name.empty() && !mediaType.empty()) || (!name.empty() && mediaType.empty())) { + fprintf(stderr, "-N and -M must be set together\n"); + usage(argv[0]); + return -1; + } + if (!name.empty() && fileOut != NULL) { + fprintf(stderr, "-N and -M cannot be used with <output file>\n"); + usage(argv[0]); + return -1; + } + int32_t sampleRate = !name.empty() ? 44100 : outputWBAMR ? 16000 : 8000; + int32_t bitRate = sampleRate; android::ProcessState::self()->startThreadPool(); sp<MediaSource> source; @@ -87,22 +110,27 @@ source = new AudioSource( AUDIO_SOURCE_MIC, String16(), - kSampleRate, + sampleRate, channels); } else { // use a sine source at 500 hz. - source = new SineSource(kSampleRate, channels); + source = new SineSource(sampleRate, channels); } sp<AMessage> meta = new AMessage; - meta->setString( - "mime", - outputWBAMR ? MEDIA_MIMETYPE_AUDIO_AMR_WB - : MEDIA_MIMETYPE_AUDIO_AMR_NB); + if (name.empty()) { + meta->setString( + "mime", + outputWBAMR ? MEDIA_MIMETYPE_AUDIO_AMR_WB + : MEDIA_MIMETYPE_AUDIO_AMR_NB); + } else { + meta->setString("mime", mediaType); + meta->setString("testing-name", name); + } meta->setInt32("channel-count", channels); - meta->setInt32("sample-rate", kSampleRate); - meta->setInt32("bitrate", kBitRate); + meta->setInt32("sample-rate", sampleRate); + meta->setInt32("bitrate", bitRate); int32_t maxInputSize; if (source->getFormat()->findInt32(kKeyMaxInputSize, &maxInputSize)) { meta->setInt32("max-input-size", maxInputSize); @@ -112,7 +140,7 @@ looper->setName("audioloop"); looper->start(); - sp<IMediaSource> encoder = MediaCodecSource::Create(looper, meta, source); + sp<MediaSource> encoder = MediaCodecSource::Create(looper, meta, source); if (fileOut != NULL) { // target file specified, write encoded AMR output @@ -128,19 +156,20 @@ writer->stop(); } else { // otherwise decode to speaker - sp<IMediaSource> decoder = SimpleDecodingSource::Create(encoder); + sp<MediaSource> decoder = SimpleDecodingSource::Create(encoder); if (playToSpeaker) { - AudioPlayer *player = new AudioPlayer(NULL); - player->setSource(decoder); - player->start(); + AudioPlayer player(NULL); + player.setSource(decoder); + player.start(); sleep(duration); +ALOGI("Line: %d", __LINE__); decoder.clear(); // must clear |decoder| otherwise delete player will hang. - delete player; // there is no player->stop()... +ALOGI("Line: %d", __LINE__); } else { CHECK_EQ(decoder->start(), (status_t)OK); - MediaBuffer* buffer; + MediaBufferBase* buffer; while (decoder->read(&buffer) == OK) { // do something with buffer (save it eventually?) // need to stop after some count though... @@ -151,6 +180,7 @@ } CHECK_EQ(decoder->stop(), (status_t)OK); } +ALOGI("Line: %d", __LINE__); } return 0;
diff --git a/cmds/stagefright/codec.cpp b/cmds/stagefright/codec.cpp index 3108a67..6a58467 100644 --- a/cmds/stagefright/codec.cpp +++ b/cmds/stagefright/codec.cpp
@@ -430,10 +430,10 @@ CHECK(control != NULL); CHECK(control->isValid()); - SurfaceComposerClient::openGlobalTransaction(); - CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK); - CHECK_EQ(control->show(), (status_t)OK); - SurfaceComposerClient::closeGlobalTransaction(); + SurfaceComposerClient::Transaction{} + .setLayer(control, INT_MAX) + .show(control) + .apply(); surface = control->getSurface(); CHECK(surface != NULL);
diff --git a/cmds/stagefright/mediafilter.cpp b/cmds/stagefright/mediafilter.cpp index f219e69..f24d2dd 100644 --- a/cmds/stagefright/mediafilter.cpp +++ b/cmds/stagefright/mediafilter.cpp
@@ -764,10 +764,10 @@ CHECK(control != NULL); CHECK(control->isValid()); - SurfaceComposerClient::openGlobalTransaction(); - CHECK_EQ((status_t)OK, control->setLayer(INT_MAX)); - CHECK_EQ((status_t)OK, control->show()); - SurfaceComposerClient::closeGlobalTransaction(); + SurfaceComposerClient::Transaction{} + .setLayer(control, INT_MAX) + .show(control) + .apply(); surface = control->getSurface(); CHECK(surface != NULL);
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp index 94c2e96..44b0015 100644 --- a/cmds/stagefright/record.cpp +++ b/cmds/stagefright/record.cpp
@@ -17,6 +17,7 @@ #include "SineSource.h" #include <binder/ProcessState.h> +#include <media/MediaExtractor.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/ALooper.h> #include <media/stagefright/foundation/AMessage.h> @@ -27,7 +28,7 @@ #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaCodecSource.h> #include <media/stagefright/MetaData.h> -#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaExtractorFactory.h> #include <media/stagefright/MPEG4Writer.h> #include <media/stagefright/SimpleDecodingSource.h> #include <media/MediaPlayerInterface.h> @@ -120,7 +121,7 @@ sp<MediaSource> source; sp<MediaExtractor> extractor = - MediaExtractor::Create(new FileSource(filename)); + MediaExtractorFactory::Create(new FileSource(filename)); if (extractor == NULL) { return NULL; } @@ -320,14 +321,14 @@ looper->setName("record"); looper->start(); - sp<IMediaSource> encoder = + sp<MediaSource> encoder = MediaCodecSource::Create(looper, encMeta, audioSource); encoder->start(); int32_t n = 0; status_t err; - MediaBuffer *buffer; + MediaBufferBase *buffer; while ((err = encoder->read(&buffer)) == OK) { printf("."); fflush(stdout);
diff --git a/cmds/stagefright/recordvideo.cpp b/cmds/stagefright/recordvideo.cpp index 7a3c842..a63b9b9 100644 --- a/cmds/stagefright/recordvideo.cpp +++ b/cmds/stagefright/recordvideo.cpp
@@ -90,7 +90,7 @@ } virtual status_t read( - MediaBuffer **buffer, const MediaSource::ReadOptions *options __unused) { + MediaBufferBase **buffer, const MediaSource::ReadOptions *options __unused) { if (mNumFramesOutput % 10 == 0) { fprintf(stderr, "."); @@ -114,8 +114,8 @@ x = x >= 0xa0 ? 0x60 : x + 1; #endif (*buffer)->set_range(0, mSize); - (*buffer)->meta_data()->clear(); - (*buffer)->meta_data()->setInt64( + (*buffer)->meta_data().clear(); + (*buffer)->meta_data().setInt64( kKeyTime, (mNumFramesOutput * 1000000) / mFrameRate); ++mNumFramesOutput; @@ -303,7 +303,7 @@ looper->setName("recordvideo"); looper->start(); - sp<IMediaSource> encoder = + sp<MediaSource> encoder = MediaCodecSource::Create( looper, enc_meta, source, NULL /* consumer */, preferSoftwareCodec ? MediaCodecSource::FLAG_PREFER_SOFTWARE_CODEC : 0);
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index d70282b..61fc897 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp
@@ -31,9 +31,11 @@ #include <binder/IServiceManager.h> #include <binder/ProcessState.h> +#include <media/DataSource.h> +#include <media/MediaExtractor.h> +#include <media/MediaSource.h> #include <media/ICrypto.h> #include <media/IMediaHTTPService.h> -#include <media/IMediaCodecService.h> #include <media/IMediaPlayerService.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ALooper.h> @@ -41,14 +43,14 @@ #include <media/stagefright/foundation/AUtils.h> #include "include/NuCachedSource2.h" #include <media/stagefright/AudioPlayer.h> -#include <media/stagefright/DataSource.h> +#include <media/stagefright/DataSourceFactory.h> #include <media/stagefright/JPEGSource.h> +#include <media/stagefright/InterfaceUtils.h> #include <media/stagefright/MediaCodec.h> #include <media/stagefright/MediaCodecList.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> -#include <media/stagefright/MediaExtractor.h> -#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MediaExtractorFactory.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/SimpleDecodingSource.h> #include <media/stagefright/Utils.h> @@ -65,7 +67,6 @@ #include <gui/SurfaceComposerClient.h> #include <android/hardware/media/omx/1.0/IOmx.h> -#include <media/omx/1.0/WOmx.h> using namespace android; @@ -79,6 +80,7 @@ static bool gDisplayHistogram; static bool showProgress = true; static String8 gWriteMP4Filename; +static String8 gComponentNameOverride; static sp<ANativeWindow> gSurface; @@ -141,14 +143,14 @@ } } -static void dumpSource(const sp<IMediaSource> &source, const String8 &filename) { +static void dumpSource(const sp<MediaSource> &source, const String8 &filename) { FILE *out = fopen(filename.string(), "wb"); CHECK_EQ((status_t)OK, source->start()); status_t err; for (;;) { - MediaBuffer *mbuf; + MediaBufferBase *mbuf; err = source->read(&mbuf); if (err == INFO_FORMAT_CHANGED) { @@ -174,13 +176,13 @@ out = NULL; } -static void playSource(sp<IMediaSource> &source) { +static void playSource(sp<MediaSource> &source) { sp<MetaData> meta = source->getFormat(); const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); - sp<IMediaSource> rawSource; + sp<MediaSource> rawSource; if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mime)) { rawSource = source; } else { @@ -192,7 +194,10 @@ CHECK(!gPreferSoftwareCodec); flags |= MediaCodecList::kHardwareCodecsOnly; } - rawSource = SimpleDecodingSource::Create(source, flags, gSurface); + rawSource = SimpleDecodingSource::Create( + source, flags, gSurface, + gComponentNameOverride.isEmpty() ? nullptr : gComponentNameOverride.c_str(), + !gComponentNameOverride.isEmpty()); if (rawSource == NULL) { return; } @@ -229,7 +234,7 @@ CHECK(meta->findInt64(kKeyDuration, &durationUs)); status_t err; - MediaBuffer *buffer; + MediaBufferBase *buffer; MediaSource::ReadOptions options; int64_t seekTimeUs = -1; for (;;) { @@ -248,7 +253,7 @@ shouldSeek = true; } else { int64_t timestampUs; - CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); + CHECK(buffer->meta_data().findInt64(kKeyTime, ×tampUs)); bool failed = false; @@ -316,7 +321,7 @@ while (numIterationsLeft-- > 0) { long numFrames = 0; - MediaBuffer *buffer; + MediaBufferBase *buffer; for (;;) { int64_t startDecodeUs = getNowUs(); @@ -404,14 +409,14 @@ //////////////////////////////////////////////////////////////////////////////// struct DetectSyncSource : public MediaSource { - explicit DetectSyncSource(const sp<IMediaSource> &source); + explicit DetectSyncSource(const sp<MediaSource> &source); virtual status_t start(MetaData *params = NULL); virtual status_t stop(); virtual sp<MetaData> getFormat(); virtual status_t read( - MediaBuffer **buffer, const ReadOptions *options); + MediaBufferBase **buffer, const ReadOptions *options); private: enum StreamType { @@ -421,14 +426,14 @@ OTHER, }; - sp<IMediaSource> mSource; + sp<MediaSource> mSource; StreamType mStreamType; bool mSawFirstIDRFrame; DISALLOW_EVIL_CONSTRUCTORS(DetectSyncSource); }; -DetectSyncSource::DetectSyncSource(const sp<IMediaSource> &source) +DetectSyncSource::DetectSyncSource(const sp<MediaSource> &source) : mSource(source), mStreamType(OTHER), mSawFirstIDRFrame(false) { @@ -460,7 +465,7 @@ return mSource->getFormat(); } -static bool isIDRFrame(MediaBuffer *buffer) { +static bool isIDRFrame(MediaBufferBase *buffer) { const uint8_t *data = (const uint8_t *)buffer->data() + buffer->range_offset(); size_t size = buffer->range_length(); @@ -477,7 +482,7 @@ } status_t DetectSyncSource::read( - MediaBuffer **buffer, const ReadOptions *options) { + MediaBufferBase **buffer, const ReadOptions *options) { for (;;) { status_t err = mSource->read(buffer, options); @@ -487,12 +492,12 @@ if (mStreamType == AVC) { bool isIDR = isIDRFrame(*buffer); - (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, isIDR); + (*buffer)->meta_data().setInt32(kKeyIsSyncFrame, isIDR); if (isIDR) { mSawFirstIDRFrame = true; } } else { - (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true); + (*buffer)->meta_data().setInt32(kKeyIsSyncFrame, true); } if (mStreamType != AVC || mSawFirstIDRFrame) { @@ -510,7 +515,7 @@ //////////////////////////////////////////////////////////////////////////////// static void writeSourcesToMP4( - Vector<sp<IMediaSource> > &sources, bool syncInfoPresent) { + Vector<sp<MediaSource> > &sources, bool syncInfoPresent) { #if 0 sp<MPEG4Writer> writer = new MPEG4Writer(gWriteMP4Filename.string()); @@ -528,7 +533,7 @@ writer->setMaxFileDuration(60000000ll); for (size_t i = 0; i < sources.size(); ++i) { - sp<IMediaSource> source = sources.editItemAt(i); + sp<MediaSource> source = sources.editItemAt(i); CHECK_EQ(writer->addSource( syncInfoPresent ? source : new DetectSyncSource(source)), @@ -545,7 +550,7 @@ writer->stop(); } -static void performSeekTest(const sp<IMediaSource> &source) { +static void performSeekTest(const sp<MediaSource> &source) { CHECK_EQ((status_t)OK, source->start()); int64_t durationUs; @@ -557,7 +562,7 @@ options.setSeekTo( seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); - MediaBuffer *buffer; + MediaBufferBase *buffer; status_t err; for (;;) { err = source->read(&buffer, &options); @@ -586,7 +591,7 @@ if (err == OK) { int64_t timeUs; - CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs)); + CHECK(buffer->meta_data().findInt64(kKeyTime, &timeUs)); printf("%" PRId64 "\t%" PRId64 "\t%" PRId64 "\n", seekTimeUs, timeUs, seekTimeUs - timeUs); @@ -617,6 +622,7 @@ fprintf(stderr, " -o playback audio\n"); fprintf(stderr, " -w(rite) filename (write to .mp4 file)\n"); fprintf(stderr, " -k seek test\n"); + fprintf(stderr, " -N(ame) of the component\n"); fprintf(stderr, " -x display a histogram of decoding times/fps " "(video only)\n"); fprintf(stderr, " -q don't show progress indicator\n"); @@ -702,7 +708,7 @@ sp<ALooper> looper; int res; - while ((res = getopt(argc, argv, "haqn:lm:b:ptsrow:kxSTd:D:")) >= 0) { + while ((res = getopt(argc, argv, "haqn:lm:b:ptsrow:kN:xSTd:D:")) >= 0) { switch (res) { case 'a': { @@ -731,6 +737,12 @@ break; } + case 'N': + { + gComponentNameOverride.setTo(optarg); + break; + } + case 'l': { listComponents = true; @@ -881,7 +893,7 @@ VideoFrame *frame = (VideoFrame *)mem->pointer(); CHECK_EQ(writeJpegFile("/sdcard/out.jpg", - (uint8_t *)frame + sizeof(VideoFrame), + frame->getFlattenedData(), frame->mWidth, frame->mHeight), 0); } @@ -909,37 +921,24 @@ } if (listComponents) { - sp<IOMX> omx; - if (property_get_bool("persist.media.treble_omx", true)) { - using namespace ::android::hardware::media::omx::V1_0; - sp<IOmx> tOmx = IOmx::getService(); + using ::android::hardware::hidl_vec; + using ::android::hardware::hidl_string; + using namespace ::android::hardware::media::omx::V1_0; + sp<IOmx> omx = IOmx::getService(); + CHECK(omx.get() != nullptr); - CHECK(tOmx.get() != NULL); - - omx = new utils::LWOmx(tOmx); - } else { - sp<IServiceManager> sm = defaultServiceManager(); - sp<IBinder> binder = sm->getService(String16("media.codec")); - sp<IMediaCodecService> service = interface_cast<IMediaCodecService>(binder); - - CHECK(service.get() != NULL); - - omx = service->getOMX(); - } - CHECK(omx.get() != NULL); - - List<IOMX::ComponentInfo> list; - omx->listNodes(&list); - - for (List<IOMX::ComponentInfo>::iterator it = list.begin(); - it != list.end(); ++it) { - printf("%s\t Roles: ", (*it).mName.string()); - for (List<String8>::iterator itRoles = (*it).mRoles.begin() ; - itRoles != (*it).mRoles.end() ; ++itRoles) { - printf("%s\t", (*itRoles).string()); - } - printf("\n"); - } + hidl_vec<IOmx::ComponentInfo> nodeList; + auto transStatus = omx->listNodes([]( + const auto& status, const auto& nodeList) { + CHECK(status == Status::OK); + for (const auto& info : nodeList) { + printf("%s\t Roles: ", info.mName.c_str()); + for (const auto& role : info.mRoles) { + printf("%s\t", role.c_str()); + } + } + }); + CHECK(transStatus.isOk()); } sp<SurfaceComposerClient> composerClient; @@ -960,10 +959,10 @@ CHECK(control != NULL); CHECK(control->isValid()); - SurfaceComposerClient::openGlobalTransaction(); - CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK); - CHECK_EQ(control->show(), (status_t)OK); - SurfaceComposerClient::closeGlobalTransaction(); + SurfaceComposerClient::Transaction{} + .setLayer(control, INT_MAX) + .show(control) + .apply(); gSurface = control->getSurface(); CHECK(gSurface != NULL); @@ -988,7 +987,7 @@ const char *filename = argv[k]; sp<DataSource> dataSource = - DataSource::CreateFromURI(NULL /* httpService */, filename); + DataSourceFactory::CreateFromURI(NULL /* httpService */, filename); if (strncasecmp(filename, "sine:", 5) && dataSource == NULL) { fprintf(stderr, "Unable to create data source.\n"); @@ -1002,8 +1001,8 @@ isJPEG = true; } - Vector<sp<IMediaSource> > mediaSources; - sp<IMediaSource> mediaSource; + Vector<sp<MediaSource> > mediaSources; + sp<MediaSource> mediaSource; if (isJPEG) { mediaSource = new JPEGSource(dataSource); @@ -1022,7 +1021,7 @@ mediaSources.push(mediaSource); } } else { - sp<IMediaExtractor> extractor = MediaExtractor::Create(dataSource); + sp<IMediaExtractor> extractor = MediaExtractorFactory::Create(dataSource); if (extractor == NULL) { fprintf(stderr, "could not create extractor.\n"); @@ -1049,7 +1048,8 @@ bool haveAudio = false; bool haveVideo = false; for (size_t i = 0; i < numTracks; ++i) { - sp<IMediaSource> source = extractor->getTrack(i); + sp<MediaSource> source = CreateMediaSourceFromIMediaSource( + extractor->getTrack(i)); if (source == nullptr) { fprintf(stderr, "skip NULL track %zu, track count %zu.\n", i, numTracks); continue; @@ -1084,7 +1084,7 @@ i, MediaExtractor::kIncludeExtensiveMetaData); if (meta == NULL) { - break; + continue; } const char *mime; meta->findCString(kKeyMIMEType, &mime); @@ -1115,7 +1115,7 @@ thumbTimeUs, thumbTimeUs / 1E6); } - mediaSource = extractor->getTrack(i); + mediaSource = CreateMediaSourceFromIMediaSource(extractor->getTrack(i)); if (mediaSource == nullptr) { fprintf(stderr, "skip NULL track %zu, total tracks %zu.\n", i, numTracks); return -1; @@ -1128,7 +1128,7 @@ } else if (dumpStream) { dumpSource(mediaSource, dumpStreamFilename); } else if (dumpPCMStream) { - sp<IMediaSource> decSource = SimpleDecodingSource::Create(mediaSource); + sp<MediaSource> decSource = SimpleDecodingSource::Create(mediaSource); dumpSource(decSource, dumpStreamFilename); } else if (seekTest) { performSeekTest(mediaSource);
diff --git a/cmds/stagefright/stream.cpp b/cmds/stagefright/stream.cpp index 2e1d240..b0199d8 100644 --- a/cmds/stagefright/stream.cpp +++ b/cmds/stagefright/stream.cpp
@@ -21,15 +21,18 @@ #include <binder/ProcessState.h> #include <cutils/properties.h> // for property_get +#include <media/DataSource.h> #include <media/IMediaHTTPService.h> #include <media/IStreamSource.h> +#include <media/MediaExtractor.h> #include <media/mediaplayer.h> +#include <media/MediaSource.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> -#include <media/stagefright/DataSource.h> +#include <media/stagefright/DataSourceFactory.h> +#include <media/stagefright/InterfaceUtils.h> #include <media/stagefright/MPEG2TSWriter.h> -#include <media/stagefright/MediaExtractor.h> -#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MediaExtractorFactory.h> #include <media/stagefright/MetaData.h> #include <binder/IServiceManager.h> @@ -161,11 +164,11 @@ : mCurrentBufferIndex(-1), mCurrentBufferOffset(0) { sp<DataSource> dataSource = - DataSource::CreateFromURI(NULL /* httpService */, filename); + DataSourceFactory::CreateFromURI(NULL /* httpService */, filename); CHECK(dataSource != NULL); - sp<IMediaExtractor> extractor = MediaExtractor::Create(dataSource); + sp<IMediaExtractor> extractor = MediaExtractorFactory::Create(dataSource); CHECK(extractor != NULL); mWriter = new MPEG2TSWriter( @@ -182,7 +185,7 @@ continue; } - sp<IMediaSource> track = extractor->getTrack(i); + sp<MediaSource> track = CreateMediaSourceFromIMediaSource(extractor->getTrack(i)); if (track == nullptr) { fprintf(stderr, "skip NULL track %zu, total tracks %zu\n", i, numTracks); continue; @@ -335,10 +338,10 @@ CHECK(control != NULL); CHECK(control->isValid()); - SurfaceComposerClient::openGlobalTransaction(); - CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK); - CHECK_EQ(control->show(), (status_t)OK); - SurfaceComposerClient::closeGlobalTransaction(); + SurfaceComposerClient::Transaction{} + .setLayer(control, INT_MAX) + .show(control) + .apply(); sp<Surface> surface = control->getSurface(); CHECK(surface != NULL);
diff --git a/drm/common/Android.bp b/drm/common/Android.bp index 0098c89..1552c3f 100644 --- a/drm/common/Android.bp +++ b/drm/common/Android.bp
@@ -33,6 +33,8 @@ "ReadWriteUtils.cpp", ], + cflags: ["-Wall", "-Werror"], + static_libs: ["libbinder"], export_include_dirs: ["include"],
diff --git a/drm/libdrmframework/plugins/common/util/Android.bp b/drm/libdrmframework/plugins/common/util/Android.bp index 0c0b6f2..7372eb7 100644 --- a/drm/libdrmframework/plugins/common/util/Android.bp +++ b/drm/libdrmframework/plugins/common/util/Android.bp
@@ -19,5 +19,7 @@ srcs: ["src/MimeTypeUtil.cpp"], + cflags: ["-Wall", "-Werror"], + export_include_dirs: ["include"], }
diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.bp b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.bp index 3f0f5f7..28a78aa 100644 --- a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.bp +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.bp
@@ -21,6 +21,9 @@ "-DUSE_64BIT_DRM_API", // The flag below turns on local debug printouts //"-DDRM_OMA_FL_ENGINE_DEBUG", + "-Wall", + "-Werror", + "-Wno-unused-variable", ], srcs: ["src/FwdLockEngine.cpp"],
diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp index 830def9..73eea89 100644 --- a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp
@@ -502,8 +502,8 @@ int retVal = FwdLockFile_CheckHeaderIntegrity(fileDesc); DecodeSession* decodeSession = new DecodeSession(fileDesc); - if (retVal && NULL != decodeSession) { - decodeSessionMap.addValue(decryptHandle->decryptId, decodeSession); + if (retVal && NULL != decodeSession && + decodeSessionMap.addValue(decryptHandle->decryptId, decodeSession)) { const char *pmime= FwdLockFile_GetContentType(fileDesc); String8 contentType = String8(pmime == NULL ? "" : pmime); contentType.toLower(); @@ -513,7 +513,11 @@ decryptHandle->decryptInfo = NULL; result = DRM_NO_ERROR; } else { - LOG_VERBOSE("FwdLockEngine::onOpenDecryptSession Integrity Check failed for the fd"); + if (retVal && NULL != decodeSession) { + LOG_VERBOSE("FwdLockEngine::onOpenDecryptSession Integrity Check failed for the fd"); + } else { + LOG_VERBOSE("FwdLockEngine::onOpenDecryptSession DecodeSesssion insertion failed"); + } FwdLockFile_detach(fileDesc); delete decodeSession; } @@ -631,7 +635,7 @@ ssize_t size = -1; if (NULL != decryptHandle && - decodeSessionMap.isCreated(decryptHandle->decryptId) && + decodeSessionMap.isCreated(decryptHandle->decryptId) && NULL != buffer && numBytes > -1) { DecodeSession* session = decodeSessionMap.getValue(decryptHandle->decryptId);
diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/common/Android.bp b/drm/libdrmframework/plugins/forward-lock/internal-format/common/Android.bp index 698f278..3be327a 100644 --- a/drm/libdrmframework/plugins/forward-lock/internal-format/common/Android.bp +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/common/Android.bp
@@ -19,6 +19,8 @@ srcs: ["FwdLockGlue.c"], + cflags: ["-Wall", "-Werror"], + shared_libs: ["libcrypto"], export_include_dirs: ["."],
diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/converter/Android.bp b/drm/libdrmframework/plugins/forward-lock/internal-format/converter/Android.bp index 33f2fe0..d4e04b8 100644 --- a/drm/libdrmframework/plugins/forward-lock/internal-format/converter/Android.bp +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/converter/Android.bp
@@ -19,6 +19,8 @@ srcs: ["FwdLockConv.c"], + cflags: ["-Wall", "-Werror"], + shared_libs: ["libcrypto"], static_libs: ["libfwdlock-common"],
diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.bp b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.bp index b6d7a06..0bf2737 100644 --- a/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.bp +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.bp
@@ -19,6 +19,8 @@ srcs: ["FwdLockFile.c"], + cflags: ["-Wall", "-Werror"], + shared_libs: ["libcrypto"], static_libs: ["libfwdlock-common"],
diff --git a/drm/libdrmframework/plugins/passthru/Android.bp b/drm/libdrmframework/plugins/passthru/Android.bp index 1dcf89c..05b6440 100644 --- a/drm/libdrmframework/plugins/passthru/Android.bp +++ b/drm/libdrmframework/plugins/passthru/Android.bp
@@ -32,5 +32,7 @@ cflags: [ // Set the following flag to enable the decryption passthru flow //"-DENABLE_PASSTHRU_DECRYPTION", + "-Wall", + "-Werror", ], }
diff --git a/drm/libmediadrm/Android.bp b/drm/libmediadrm/Android.bp index f906564..4991e50 100644 --- a/drm/libmediadrm/Android.bp +++ b/drm/libmediadrm/Android.bp
@@ -2,10 +2,11 @@ // libmediadrm // -cc_library_shared { +// TODO: change it back to cc_library_shared when MediaPlayer2 switches to +// using NdkMediaDrm, instead of MediaDrm.java. +cc_library { name: "libmediadrm", - srcs: [ "DrmPluginPath.cpp", "DrmSessionManager.cpp", @@ -13,29 +14,26 @@ "IDrm.cpp", "IDrmClient.cpp", "IMediaDrmService.cpp", - "PluginMetricsReporting.cpp", "SharedLibrary.cpp", "DrmHal.cpp", "CryptoHal.cpp", - "protos/plugin_metrics.proto", ], - proto: { - type: "lite", - }, - shared_libs: [ "libbinder", "libcutils", "libdl", "liblog", + "libmediadrmmetrics_lite", "libmediametrics", "libmediautils", + "libprotobuf-cpp-lite", "libstagefright_foundation", "libutils", "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "libhidlallocatorutils", "libhidlbase", - "libhidlmemory", "libhidltransport", ], @@ -44,3 +42,70 @@ "-Wall", ], } + +// This is the version of the drm metrics configured for protobuf lite. +cc_library_shared { + name: "libmediadrmmetrics_lite", + srcs: [ + "DrmMetrics.cpp", + "PluginMetricsReporting.cpp", + "protos/metrics.proto", + ], + + proto: { + export_proto_headers: true, + type: "lite", + }, + shared_libs: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "libbase", + "libbinder", + "libhidlbase", + "liblog", + "libmediametrics", + "libprotobuf-cpp-lite", + "libstagefright_foundation", + "libutils", + ], + cflags: [ + // Suppress unused parameter and no error options. These cause problems + // with the when using the map type in a proto definition. + "-Wno-unused-parameter", + "-Wno-error", + ], +} + +// This is the version of the drm metrics library configured for full protobuf. +cc_library_shared { + name: "libmediadrmmetrics_full", + srcs: [ + "DrmMetrics.cpp", + "PluginMetricsReporting.cpp", + "protos/metrics.proto", + ], + + proto: { + export_proto_headers: true, + type: "full", + }, + shared_libs: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "libbase", + "libbinder", + "libhidlbase", + "liblog", + "libmediametrics", + "libprotobuf-cpp-full", + "libstagefright_foundation", + "libutils", + ], + cflags: [ + // Suppress unused parameter and no error options. These cause problems + // when using the map type in a proto definition. + "-Wno-unused-parameter", + "-Wno-error", + ], +} +
diff --git a/drm/libmediadrm/CryptoHal.cpp b/drm/libmediadrm/CryptoHal.cpp index 5dd2563..3035c5a 100644 --- a/drm/libmediadrm/CryptoHal.cpp +++ b/drm/libmediadrm/CryptoHal.cpp
@@ -22,13 +22,14 @@ #include <android/hidl/manager/1.0/IServiceManager.h> #include <binder/IMemory.h> -#include <cutils/native_handle.h> -#include <media/CryptoHal.h> +#include <hidlmemory/FrameworkUtils.h> #include <media/hardware/CryptoAPI.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AString.h> #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/MediaErrors.h> +#include <mediadrm/CryptoHal.h> + using ::android::hardware::drm::V1_0::BufferType; using ::android::hardware::drm::V1_0::DestinationBuffer; @@ -117,15 +118,24 @@ auto manager = ::IServiceManager::getService(); if (manager != NULL) { - manager->listByInterface(ICryptoFactory::descriptor, + manager->listByInterface(drm::V1_0::ICryptoFactory::descriptor, [&factories](const hidl_vec<hidl_string> ®istered) { for (const auto &instance : registered) { - auto factory = ICryptoFactory::getService(instance); + auto factory = drm::V1_0::ICryptoFactory::getService(instance); if (factory != NULL) { + ALOGD("found drm@1.0 ICryptoFactory %s", instance.c_str()); factories.push_back(factory); - ALOGI("makeCryptoFactories: factory instance %s is %s", - instance.c_str(), - factory->isRemote() ? "Remote" : "Not Remote"); + } + } + } + ); + manager->listByInterface(drm::V1_1::ICryptoFactory::descriptor, + [&factories](const hidl_vec<hidl_string> ®istered) { + for (const auto &instance : registered) { + auto factory = drm::V1_1::ICryptoFactory::getService(instance); + if (factory != NULL) { + ALOGD("found drm@1.1 ICryptoFactory %s", instance.c_str()); + factories.push_back(factory); } } } @@ -136,7 +146,7 @@ // must be in passthrough mode, load the default passthrough service auto passthrough = ICryptoFactory::getService(); if (passthrough != NULL) { - ALOGI("makeCryptoFactories: using default crypto instance"); + ALOGI("makeCryptoFactories: using default passthrough crypto instance"); factories.push_back(passthrough); } else { ALOGE("Failed to find any crypto factories"); @@ -213,10 +223,14 @@ Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { - return mInitCheck; + return false; } - return mPlugin->requiresSecureDecoderComponent(hidl_string(mime)); + Return<bool> hResult = mPlugin->requiresSecureDecoderComponent(hidl_string(mime)); + if (!hResult.isOk()) { + return false; + } + return hResult; } @@ -227,26 +241,20 @@ * are sent by providing an offset into the heap and a buffer size. */ int32_t CryptoHal::setHeapBase(const sp<IMemoryHeap>& heap) { + using ::android::hardware::fromHeap; + using ::android::hardware::HidlMemory; + if (heap == NULL) { ALOGE("setHeapBase(): heap is NULL"); return -1; } - native_handle_t* nativeHandle = native_handle_create(1, 0); - if (!nativeHandle) { - ALOGE("setHeapBase(), failed to create native handle"); - return -1; - } Mutex::Autolock autoLock(mLock); int32_t seqNum = mHeapSeqNum++; - - int fd = heap->getHeapID(); - nativeHandle->data[0] = fd; - auto hidlHandle = hidl_handle(nativeHandle); - auto hidlMemory = hidl_memory("ashmem", hidlHandle, heap->getSize()); + sp<HidlMemory> hidlMemory = fromHeap(heap); mHeapBases.add(seqNum, HeapBase(mNextBufferId, heap->getSize())); - Return<void> hResult = mPlugin->setSharedBufferBase(hidlMemory, mNextBufferId++); + Return<void> hResult = mPlugin->setSharedBufferBase(*hidlMemory, mNextBufferId++); ALOGE_IF(!hResult.isOk(), "setSharedBufferBase(): remote call failed"); return seqNum; } @@ -254,7 +262,22 @@ void CryptoHal::clearHeapBase(int32_t seqNum) { Mutex::Autolock autoLock(mLock); - mHeapBases.removeItem(seqNum); + /* + * Clear the remote shared memory mapping by setting the shared + * buffer base to a null hidl_memory. + * + * TODO: Add a releaseSharedBuffer method in a future DRM HAL + * API version to make this explicit. + */ + ssize_t index = mHeapBases.indexOfKey(seqNum); + if (index >= 0) { + if (mPlugin != NULL) { + uint32_t bufferId = mHeapBases[index].getBufferId(); + Return<void> hResult = mPlugin->setSharedBufferBase(hidl_memory(), bufferId); + ALOGE_IF(!hResult.isOk(), "setSharedBufferBase(): remote call failed"); + } + mHeapBases.removeItem(seqNum); + } } status_t CryptoHal::toSharedBuffer(const sp<IMemory>& memory, int32_t seqNum, ::SharedBuffer* buffer) {
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp index bc37557..cf08610 100644 --- a/drm/libmediadrm/DrmHal.cpp +++ b/drm/libmediadrm/DrmHal.cpp
@@ -16,48 +16,81 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "DrmHal" +#include <iomanip> + #include <utils/Log.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> -#include <android/hardware/drm/1.0/IDrmFactory.h> -#include <android/hardware/drm/1.0/IDrmPlugin.h> #include <android/hardware/drm/1.0/types.h> #include <android/hidl/manager/1.0/IServiceManager.h> #include <hidl/ServiceManagement.h> -#include <media/DrmHal.h> -#include <media/DrmSessionClientInterface.h> -#include <media/DrmSessionManager.h> +#include <media/EventMetric.h> #include <media/PluginMetricsReporting.h> #include <media/drm/DrmAPI.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/base64.h> #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/MediaErrors.h> +#include <mediadrm/DrmHal.h> +#include <mediadrm/DrmSessionClientInterface.h> +#include <mediadrm/DrmSessionManager.h> -using ::android::hardware::drm::V1_0::EventType; -using ::android::hardware::drm::V1_0::IDrmFactory; -using ::android::hardware::drm::V1_0::IDrmPlugin; -using ::android::hardware::drm::V1_0::KeyedVector; -using ::android::hardware::drm::V1_0::KeyRequestType; -using ::android::hardware::drm::V1_0::KeyStatus; -using ::android::hardware::drm::V1_0::KeyStatusType; -using ::android::hardware::drm::V1_0::KeyType; -using ::android::hardware::drm::V1_0::KeyValue; -using ::android::hardware::drm::V1_0::SecureStop; -using ::android::hardware::drm::V1_0::Status; +using drm::V1_0::KeyedVector; +using drm::V1_0::KeyStatusType; +using drm::V1_0::KeyType; +using drm::V1_0::KeyValue; +using drm::V1_1::HdcpLevel;; +using drm::V1_0::SecureStop; +using drm::V1_1::SecureStopRelease; +using drm::V1_0::SecureStopId; +using drm::V1_1::SecurityLevel; +using drm::V1_0::Status; +using ::android::hardware::drm::V1_1::DrmMetricGroup; using ::android::hardware::hidl_array; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; using ::android::hardware::Return; using ::android::hardware::Void; using ::android::hidl::manager::V1_0::IServiceManager; +using ::android::os::PersistableBundle; using ::android::sp; +namespace { + +// This constant corresponds to the PROPERTY_DEVICE_UNIQUE_ID constant +// in the MediaDrm API. +constexpr char kPropertyDeviceUniqueId[] = "deviceUniqueId"; +constexpr char kEqualsSign[] = "="; + +template<typename T> +std::string toBase64StringNoPad(const T* data, size_t size) { + // Note that the base 64 conversion only works with arrays of single-byte + // values. If the source is empty or is not an array of single-byte values, + // return empty string. + if (size == 0 || sizeof(data[0]) != 1) { + return ""; + } + + android::AString outputString; + encodeBase64(data, size, &outputString); + // Remove trailing equals padding if it exists. + while (outputString.size() > 0 && outputString.endsWith(kEqualsSign)) { + outputString.erase(outputString.size() - 1, 1); + } + + return std::string(outputString.c_str(), outputString.size()); +} + +} // anonymous namespace + namespace android { +#define INIT_CHECK() {if (mInitCheck != OK) return mInitCheck;} + static inline int getCallingPid() { return IPCThreadState::self()->getCallingPid(); } @@ -89,6 +122,42 @@ return hidl_string(string.string()); } +static DrmPlugin::SecurityLevel toSecurityLevel(SecurityLevel level) { + switch(level) { + case SecurityLevel::SW_SECURE_CRYPTO: + return DrmPlugin::kSecurityLevelSwSecureCrypto; + case SecurityLevel::SW_SECURE_DECODE: + return DrmPlugin::kSecurityLevelSwSecureDecode; + case SecurityLevel::HW_SECURE_CRYPTO: + return DrmPlugin::kSecurityLevelHwSecureCrypto; + case SecurityLevel::HW_SECURE_DECODE: + return DrmPlugin::kSecurityLevelHwSecureDecode; + case SecurityLevel::HW_SECURE_ALL: + return DrmPlugin::kSecurityLevelHwSecureAll; + default: + return DrmPlugin::kSecurityLevelUnknown; + } +} + +static DrmPlugin::HdcpLevel toHdcpLevel(HdcpLevel level) { + switch(level) { + case HdcpLevel::HDCP_NONE: + return DrmPlugin::kHdcpNone; + case HdcpLevel::HDCP_V1: + return DrmPlugin::kHdcpV1; + case HdcpLevel::HDCP_V2: + return DrmPlugin::kHdcpV2; + case HdcpLevel::HDCP_V2_1: + return DrmPlugin::kHdcpV2_1; + case HdcpLevel::HDCP_V2_2: + return DrmPlugin::kHdcpV2_2; + case HdcpLevel::HDCP_NO_OUTPUT: + return DrmPlugin::kHdcpNoOutput; + default: + return DrmPlugin::kHdcpLevelUnknown; + } +} + static ::KeyedVector toHidlKeyedVector(const KeyedVector<String8, String8>& keyedVector) { @@ -121,6 +190,15 @@ return secureStops; } +static List<Vector<uint8_t>> toSecureStopIds(const hidl_vec<SecureStopId>& + hSecureStopIds) { + List<Vector<uint8_t>> secureStopIds; + for (size_t i = 0; i < hSecureStopIds.size(); i++) { + secureStopIds.push_back(toVector(hSecureStopIds[i])); + } + return secureStopIds; +} + static status_t toStatusT(Status status) { switch (status) { case Status::OK: @@ -196,35 +274,63 @@ } void DrmHal::closeOpenSessions() { - if (mPlugin != NULL) { - for (size_t i = 0; i < mOpenSessions.size(); i++) { - mPlugin->closeSession(toHidlVec(mOpenSessions[i])); - DrmSessionManager::Instance()->removeSession(mOpenSessions[i]); - } + Mutex::Autolock autoLock(mLock); + auto openSessions = mOpenSessions; + for (size_t i = 0; i < openSessions.size(); i++) { + mLock.unlock(); + closeSession(openSessions[i]); + mLock.lock(); } mOpenSessions.clear(); } DrmHal::~DrmHal() { - closeOpenSessions(); DrmSessionManager::Instance()->removeDrm(mDrmSessionClient); } +void DrmHal::cleanup() { + closeOpenSessions(); + + Mutex::Autolock autoLock(mLock); + reportPluginMetrics(); + reportFrameworkMetrics(); + + setListener(NULL); + mInitCheck = NO_INIT; + + if (mPlugin != NULL) { + if (!mPlugin->setListener(NULL).isOk()) { + mInitCheck = DEAD_OBJECT; + } + } + mPlugin.clear(); + mPluginV1_1.clear(); +} + Vector<sp<IDrmFactory>> DrmHal::makeDrmFactories() { Vector<sp<IDrmFactory>> factories; auto manager = hardware::defaultServiceManager(); if (manager != NULL) { - manager->listByInterface(IDrmFactory::descriptor, + manager->listByInterface(drm::V1_0::IDrmFactory::descriptor, [&factories](const hidl_vec<hidl_string> ®istered) { for (const auto &instance : registered) { - auto factory = IDrmFactory::getService(instance); + auto factory = drm::V1_0::IDrmFactory::getService(instance); if (factory != NULL) { + ALOGD("found drm@1.0 IDrmFactory %s", instance.c_str()); factories.push_back(factory); - ALOGI("makeDrmFactories: factory instance %s is %s", - instance.c_str(), - factory->isRemote() ? "Remote" : "Not Remote"); + } + } + } + ); + manager->listByInterface(drm::V1_1::IDrmFactory::descriptor, + [&factories](const hidl_vec<hidl_string> ®istered) { + for (const auto &instance : registered) { + auto factory = drm::V1_1::IDrmFactory::getService(instance); + if (factory != NULL) { + ALOGD("found drm@1.1 IDrmFactory %s", instance.c_str()); + factories.push_back(factory); } } } @@ -235,7 +341,7 @@ // must be in passthrough mode, load the default passthrough service auto passthrough = IDrmFactory::getService(); if (passthrough != NULL) { - ALOGI("makeDrmFactories: using default drm instance"); + ALOGI("makeDrmFactories: using default passthrough drm instance"); factories.push_back(passthrough); } else { ALOGE("Failed to find any drm factories"); @@ -246,6 +352,8 @@ sp<IDrmPlugin> DrmHal::makeDrmPlugin(const sp<IDrmFactory>& factory, const uint8_t uuid[16], const String8& appPackageName) { + mAppPackageName = appPackageName; + mMetrics.SetAppPackageName(appPackageName); sp<IDrmPlugin> plugin; Return<void> hResult = factory->createPlugin(uuid, appPackageName.string(), @@ -284,6 +392,7 @@ Return<void> DrmHal::sendEvent(EventType hEventType, const hidl_vec<uint8_t>& sessionId, const hidl_vec<uint8_t>& data) { + mMetrics.mEventCounter.Increment(hEventType); mEventLock.lock(); sp<IDrmClient> listener = mListener; @@ -374,12 +483,21 @@ break; } obj.writeInt32(type); + mMetrics.mKeyStatusChangeCounter.Increment(keyStatus.type); } obj.writeInt32(hasNewUsableKey); Mutex::Autolock lock(mNotifyLock); listener->notify(DrmPlugin::kDrmPluginEventKeysChange, 0, &obj); + } else { + // There's no listener. But we still want to count the key change + // events. + size_t nKeys = keyStatusList.size(); + for (size_t i = 0; i < nKeys; i++) { + mMetrics.mKeyStatusChangeCounter.Increment(keyStatusList[i].type); + } } + return Void(); } @@ -407,6 +525,9 @@ for (size_t i = 0; i < mFactories.size(); i++) { if (mFactories[i]->isCryptoSchemeSupported(uuid)) { mPlugin = makeDrmPlugin(mFactories[i], uuid, appPackageName); + if (mPlugin != NULL) { + mPluginV1_1 = drm::V1_1::IDrmPlugin::castFrom(mPlugin); + } } } @@ -424,46 +545,66 @@ } status_t DrmHal::destroyPlugin() { - Mutex::Autolock autoLock(mLock); - if (mInitCheck != OK) { - return mInitCheck; - } - - closeOpenSessions(); - reportMetrics(); - setListener(NULL); - mInitCheck = NO_INIT; - - if (mPlugin != NULL) { - if (!mPlugin->setListener(NULL).isOk()) { - mInitCheck = DEAD_OBJECT; - } - } - mPlugin.clear(); + cleanup(); return OK; } -status_t DrmHal::openSession(Vector<uint8_t> &sessionId) { +status_t DrmHal::openSession(DrmPlugin::SecurityLevel level, + Vector<uint8_t> &sessionId) { Mutex::Autolock autoLock(mLock); + INIT_CHECK(); - if (mInitCheck != OK) { - return mInitCheck; + SecurityLevel hSecurityLevel; + bool setSecurityLevel = true; + + switch(level) { + case DrmPlugin::kSecurityLevelSwSecureCrypto: + hSecurityLevel = SecurityLevel::SW_SECURE_CRYPTO; + break; + case DrmPlugin::kSecurityLevelSwSecureDecode: + hSecurityLevel = SecurityLevel::SW_SECURE_DECODE; + break; + case DrmPlugin::kSecurityLevelHwSecureCrypto: + hSecurityLevel = SecurityLevel::HW_SECURE_CRYPTO; + break; + case DrmPlugin::kSecurityLevelHwSecureDecode: + hSecurityLevel = SecurityLevel::HW_SECURE_DECODE; + break; + case DrmPlugin::kSecurityLevelHwSecureAll: + hSecurityLevel = SecurityLevel::HW_SECURE_ALL; + break; + case DrmPlugin::kSecurityLevelMax: + setSecurityLevel = false; + break; + default: + return ERROR_DRM_CANNOT_HANDLE; } status_t err = UNKNOWN_ERROR; - bool retry = true; do { hidl_vec<uint8_t> hSessionId; - Return<void> hResult = mPlugin->openSession( - [&](Status status, const hidl_vec<uint8_t>& id) { - if (status == Status::OK) { - sessionId = toVector(id); + Return<void> hResult; + if (mPluginV1_1 == NULL || !setSecurityLevel) { + hResult = mPlugin->openSession( + [&](Status status,const hidl_vec<uint8_t>& id) { + if (status == Status::OK) { + sessionId = toVector(id); + } + err = toStatusT(status); } - err = toStatusT(status); - } - ); + ); + } else { + hResult = mPluginV1_1->openSession_1_1(hSecurityLevel, + [&](Status status, const hidl_vec<uint8_t>& id) { + if (status == Status::OK) { + sessionId = toVector(id); + } + err = toStatusT(status); + } + ); + } if (!hResult.isOk()) { err = DEAD_OBJECT; @@ -485,16 +626,16 @@ DrmSessionManager::Instance()->addSession(getCallingPid(), mDrmSessionClient, sessionId); mOpenSessions.push(sessionId); + mMetrics.SetSessionStart(sessionId); } + + mMetrics.mOpenSessionCounter.Increment(err); return err; } status_t DrmHal::closeSession(Vector<uint8_t> const &sessionId) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); Return<Status> status = mPlugin->closeSession(toHidlVec(sessionId)); if (status.isOk()) { @@ -507,9 +648,12 @@ } } } - reportMetrics(); - return toStatusT(status); + status_t response = toStatusT(status); + mMetrics.SetSessionEnd(sessionId); + mMetrics.mCloseSessionCounter.Increment(response); + return response; } + mMetrics.mCloseSessionCounter.Increment(DEAD_OBJECT); return DEAD_OBJECT; } @@ -519,10 +663,8 @@ String8> const &optionalParameters, Vector<uint8_t> &request, String8 &defaultUrl, DrmPlugin::KeyRequestType *keyRequestType) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); + EventTimer<status_t> keyRequestTimer(&mMetrics.mGetKeyRequestTimeUs); DrmSessionManager::Instance()->useSession(sessionId); @@ -534,6 +676,7 @@ } else if (keyType == DrmPlugin::kKeyType_Release) { hKeyType = KeyType::RELEASE; } else { + keyRequestTimer.SetAttribute(BAD_VALUE); return BAD_VALUE; } @@ -541,23 +684,63 @@ status_t err = UNKNOWN_ERROR; + if (mPluginV1_1 != NULL) { + Return<void> hResult = + mPluginV1_1->getKeyRequest_1_1( + toHidlVec(sessionId), toHidlVec(initData), + toHidlString(mimeType), hKeyType, hOptionalParameters, + [&](Status status, const hidl_vec<uint8_t>& hRequest, + drm::V1_1::KeyRequestType hKeyRequestType, + const hidl_string& hDefaultUrl) { + + if (status == Status::OK) { + request = toVector(hRequest); + defaultUrl = toString8(hDefaultUrl); + + switch (hKeyRequestType) { + case drm::V1_1::KeyRequestType::INITIAL: + *keyRequestType = DrmPlugin::kKeyRequestType_Initial; + break; + case drm::V1_1::KeyRequestType::RENEWAL: + *keyRequestType = DrmPlugin::kKeyRequestType_Renewal; + break; + case drm::V1_1::KeyRequestType::RELEASE: + *keyRequestType = DrmPlugin::kKeyRequestType_Release; + break; + case drm::V1_1::KeyRequestType::NONE: + *keyRequestType = DrmPlugin::kKeyRequestType_None; + break; + case drm::V1_1::KeyRequestType::UPDATE: + *keyRequestType = DrmPlugin::kKeyRequestType_Update; + break; + default: + *keyRequestType = DrmPlugin::kKeyRequestType_Unknown; + break; + } + err = toStatusT(status); + } + }); + return hResult.isOk() ? err : DEAD_OBJECT; + } + Return<void> hResult = mPlugin->getKeyRequest(toHidlVec(sessionId), toHidlVec(initData), toHidlString(mimeType), hKeyType, hOptionalParameters, [&](Status status, const hidl_vec<uint8_t>& hRequest, - KeyRequestType hKeyRequestType, const hidl_string& hDefaultUrl) { + drm::V1_0::KeyRequestType hKeyRequestType, + const hidl_string& hDefaultUrl) { if (status == Status::OK) { request = toVector(hRequest); defaultUrl = toString8(hDefaultUrl); switch (hKeyRequestType) { - case KeyRequestType::INITIAL: + case drm::V1_0::KeyRequestType::INITIAL: *keyRequestType = DrmPlugin::kKeyRequestType_Initial; break; - case KeyRequestType::RENEWAL: + case drm::V1_0::KeyRequestType::RENEWAL: *keyRequestType = DrmPlugin::kKeyRequestType_Renewal; break; - case KeyRequestType::RELEASE: + case drm::V1_0::KeyRequestType::RELEASE: *keyRequestType = DrmPlugin::kKeyRequestType_Release; break; default: @@ -568,16 +751,17 @@ } }); - return hResult.isOk() ? err : DEAD_OBJECT; + err = hResult.isOk() ? err : DEAD_OBJECT; + keyRequestTimer.SetAttribute(err); + return err; } status_t DrmHal::provideKeyResponse(Vector<uint8_t> const &sessionId, Vector<uint8_t> const &response, Vector<uint8_t> &keySetId) { Mutex::Autolock autoLock(mLock); + EventTimer<status_t> keyResponseTimer(&mMetrics.mProvideKeyResponseTimeUs); - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); @@ -592,41 +776,35 @@ err = toStatusT(status); } ); - - return hResult.isOk() ? err : DEAD_OBJECT; + err = hResult.isOk() ? err : DEAD_OBJECT; + keyResponseTimer.SetAttribute(err); + return err; } status_t DrmHal::removeKeys(Vector<uint8_t> const &keySetId) { Mutex::Autolock autoLock(mLock); + INIT_CHECK(); - if (mInitCheck != OK) { - return mInitCheck; - } - - return toStatusT(mPlugin->removeKeys(toHidlVec(keySetId))); + Return<Status> status = mPlugin->removeKeys(toHidlVec(keySetId)); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; } status_t DrmHal::restoreKeys(Vector<uint8_t> const &sessionId, Vector<uint8_t> const &keySetId) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); - return toStatusT(mPlugin->restoreKeys(toHidlVec(sessionId), - toHidlVec(keySetId))); + Return<Status> status = mPlugin->restoreKeys(toHidlVec(sessionId), + toHidlVec(keySetId)); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; } status_t DrmHal::queryKeyStatus(Vector<uint8_t> const &sessionId, KeyedVector<String8, String8> &infoMap) const { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); @@ -650,10 +828,7 @@ String8 const &certAuthority, Vector<uint8_t> &request, String8 &defaultUrl) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); status_t err = UNKNOWN_ERROR; @@ -669,16 +844,15 @@ } ); - return hResult.isOk() ? err : DEAD_OBJECT; + err = hResult.isOk() ? err : DEAD_OBJECT; + mMetrics.mGetProvisionRequestCounter.Increment(err); + return err; } status_t DrmHal::provideProvisionResponse(Vector<uint8_t> const &response, Vector<uint8_t> &certificate, Vector<uint8_t> &wrappedKey) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); status_t err = UNKNOWN_ERROR; @@ -693,15 +867,14 @@ } ); - return hResult.isOk() ? err : DEAD_OBJECT; + err = hResult.isOk() ? err : DEAD_OBJECT; + mMetrics.mProvideProvisionResponseCounter.Increment(err); + return err; } status_t DrmHal::getSecureStops(List<Vector<uint8_t>> &secureStops) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); status_t err = UNKNOWN_ERROR; @@ -718,13 +891,36 @@ } -status_t DrmHal::getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) { +status_t DrmHal::getSecureStopIds(List<Vector<uint8_t>> &secureStopIds) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } + if (mPluginV1_1 == NULL) { + return ERROR_DRM_CANNOT_HANDLE; + } + + status_t err = UNKNOWN_ERROR; + + Return<void> hResult = mPluginV1_1->getSecureStopIds( + [&](Status status, const hidl_vec<SecureStopId>& hSecureStopIds) { + if (status == Status::OK) { + secureStopIds = toSecureStopIds(hSecureStopIds); + } + err = toStatusT(status); + } + ); + + return hResult.isOk() ? err : DEAD_OBJECT; +} + + +status_t DrmHal::getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) { + Mutex::Autolock autoLock(mLock); + INIT_CHECK(); + status_t err = UNKNOWN_ERROR; Return<void> hResult = mPlugin->getSecureStop(toHidlVec(ssid), @@ -741,22 +937,132 @@ status_t DrmHal::releaseSecureStops(Vector<uint8_t> const &ssRelease) { Mutex::Autolock autoLock(mLock); + INIT_CHECK(); - if (mInitCheck != OK) { - return mInitCheck; + Return<Status> status(Status::ERROR_DRM_UNKNOWN); + if (mPluginV1_1 != NULL) { + SecureStopRelease secureStopRelease; + secureStopRelease.opaqueData = toHidlVec(ssRelease); + status = mPluginV1_1->releaseSecureStops(secureStopRelease); + } else { + status = mPlugin->releaseSecureStop(toHidlVec(ssRelease)); } - - return toStatusT(mPlugin->releaseSecureStop(toHidlVec(ssRelease))); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; } -status_t DrmHal::releaseAllSecureStops() { +status_t DrmHal::removeSecureStop(Vector<uint8_t> const &ssid) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } - return toStatusT(mPlugin->releaseAllSecureStops()); + if (mPluginV1_1 == NULL) { + return ERROR_DRM_CANNOT_HANDLE; + } + + Return<Status> status = mPluginV1_1->removeSecureStop(toHidlVec(ssid)); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; +} + +status_t DrmHal::removeAllSecureStops() { + Mutex::Autolock autoLock(mLock); + INIT_CHECK(); + + Return<Status> status(Status::ERROR_DRM_UNKNOWN); + if (mPluginV1_1 != NULL) { + status = mPluginV1_1->removeAllSecureStops(); + } else { + status = mPlugin->releaseAllSecureStops(); + } + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; +} + +status_t DrmHal::getHdcpLevels(DrmPlugin::HdcpLevel *connected, + DrmPlugin::HdcpLevel *max) const { + Mutex::Autolock autoLock(mLock); + INIT_CHECK(); + + if (connected == NULL || max == NULL) { + return BAD_VALUE; + } + status_t err = UNKNOWN_ERROR; + + if (mPluginV1_1 == NULL) { + return ERROR_DRM_CANNOT_HANDLE; + } + + *connected = DrmPlugin::kHdcpLevelUnknown; + *max = DrmPlugin::kHdcpLevelUnknown; + + Return<void> hResult = mPluginV1_1->getHdcpLevels( + [&](Status status, const HdcpLevel& hConnected, const HdcpLevel& hMax) { + if (status == Status::OK) { + *connected = toHdcpLevel(hConnected); + *max = toHdcpLevel(hMax); + } + err = toStatusT(status); + } + ); + + return hResult.isOk() ? err : DEAD_OBJECT; +} + +status_t DrmHal::getNumberOfSessions(uint32_t *open, uint32_t *max) const { + Mutex::Autolock autoLock(mLock); + INIT_CHECK(); + + if (open == NULL || max == NULL) { + return BAD_VALUE; + } + status_t err = UNKNOWN_ERROR; + + *open = 0; + *max = 0; + + if (mPluginV1_1 == NULL) { + return ERROR_DRM_CANNOT_HANDLE; + } + + Return<void> hResult = mPluginV1_1->getNumberOfSessions( + [&](Status status, uint32_t hOpen, uint32_t hMax) { + if (status == Status::OK) { + *open = hOpen; + *max = hMax; + } + err = toStatusT(status); + } + ); + + return hResult.isOk() ? err : DEAD_OBJECT; +} + +status_t DrmHal::getSecurityLevel(Vector<uint8_t> const &sessionId, + DrmPlugin::SecurityLevel *level) const { + Mutex::Autolock autoLock(mLock); + INIT_CHECK(); + + if (level == NULL) { + return BAD_VALUE; + } + status_t err = UNKNOWN_ERROR; + + if (mPluginV1_1 == NULL) { + return ERROR_DRM_CANNOT_HANDLE; + } + + *level = DrmPlugin::kSecurityLevelUnknown; + + Return<void> hResult = mPluginV1_1->getSecurityLevel(toHidlVec(sessionId), + [&](Status status, SecurityLevel hLevel) { + if (status == Status::OK) { + *level = toSecurityLevel(hLevel); + } + err = toStatusT(status); + } + ); + + return hResult.isOk() ? err : DEAD_OBJECT; } status_t DrmHal::getPropertyString(String8 const &name, String8 &value ) const { @@ -767,10 +1073,7 @@ status_t DrmHal::getPropertyStringInternal(String8 const &name, String8 &value) const { // This function is internal to the class and should only be called while // mLock is already held. - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); status_t err = UNKNOWN_ERROR; @@ -794,10 +1097,7 @@ status_t DrmHal::getPropertyByteArrayInternal(String8 const &name, Vector<uint8_t> &value ) const { // This function is internal to the class and should only be called while // mLock is already held. - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); status_t err = UNKNOWN_ERROR; @@ -810,73 +1110,106 @@ } ); - return hResult.isOk() ? err : DEAD_OBJECT; + err = hResult.isOk() ? err : DEAD_OBJECT; + if (name == kPropertyDeviceUniqueId) { + mMetrics.mGetDeviceUniqueIdCounter.Increment(err); + } + return err; } status_t DrmHal::setPropertyString(String8 const &name, String8 const &value ) const { Mutex::Autolock autoLock(mLock); + INIT_CHECK(); - if (mInitCheck != OK) { - return mInitCheck; - } - - Status status = mPlugin->setPropertyString(toHidlString(name), + Return<Status> status = mPlugin->setPropertyString(toHidlString(name), toHidlString(value)); - return toStatusT(status); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; } status_t DrmHal::setPropertyByteArray(String8 const &name, Vector<uint8_t> const &value ) const { Mutex::Autolock autoLock(mLock); + INIT_CHECK(); - if (mInitCheck != OK) { - return mInitCheck; - } - - Status status = mPlugin->setPropertyByteArray(toHidlString(name), + Return<Status> status = mPlugin->setPropertyByteArray(toHidlString(name), toHidlVec(value)); - return toStatusT(status); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; } +status_t DrmHal::getMetrics(PersistableBundle* metrics) { + if (metrics == nullptr) { + return UNEXPECTED_NULL; + } + mMetrics.Export(metrics); + + // Append vendor metrics if they are supported. + if (mPluginV1_1 != NULL) { + String8 vendor; + String8 description; + if (getPropertyStringInternal(String8("vendor"), vendor) != OK + || vendor.isEmpty()) { + ALOGE("Get vendor failed or is empty"); + vendor = "NONE"; + } + if (getPropertyStringInternal(String8("description"), description) != OK + || description.isEmpty()) { + ALOGE("Get description failed or is empty."); + description = "NONE"; + } + vendor += "."; + vendor += description; + + hidl_vec<DrmMetricGroup> pluginMetrics; + status_t err = UNKNOWN_ERROR; + + Return<void> status = mPluginV1_1->getMetrics( + [&](Status status, hidl_vec<DrmMetricGroup> pluginMetrics) { + if (status != Status::OK) { + ALOGV("Error getting plugin metrics: %d", status); + } else { + PersistableBundle pluginBundle; + if (MediaDrmMetrics::HidlMetricsToBundle( + pluginMetrics, &pluginBundle) == OK) { + metrics->putPersistableBundle(String16(vendor), pluginBundle); + } + } + err = toStatusT(status); + }); + return status.isOk() ? err : DEAD_OBJECT; + } + + return OK; +} status_t DrmHal::setCipherAlgorithm(Vector<uint8_t> const &sessionId, String8 const &algorithm) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); - Status status = mPlugin->setCipherAlgorithm(toHidlVec(sessionId), + Return<Status> status = mPlugin->setCipherAlgorithm(toHidlVec(sessionId), toHidlString(algorithm)); - return toStatusT(status); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; } status_t DrmHal::setMacAlgorithm(Vector<uint8_t> const &sessionId, String8 const &algorithm) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); - Status status = mPlugin->setMacAlgorithm(toHidlVec(sessionId), + Return<Status> status = mPlugin->setMacAlgorithm(toHidlVec(sessionId), toHidlString(algorithm)); - return toStatusT(status); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; } status_t DrmHal::encrypt(Vector<uint8_t> const &sessionId, Vector<uint8_t> const &keyId, Vector<uint8_t> const &input, Vector<uint8_t> const &iv, Vector<uint8_t> &output) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); @@ -899,10 +1232,7 @@ Vector<uint8_t> const &keyId, Vector<uint8_t> const &input, Vector<uint8_t> const &iv, Vector<uint8_t> &output) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); @@ -925,10 +1255,7 @@ Vector<uint8_t> const &keyId, Vector<uint8_t> const &message, Vector<uint8_t> &signature) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); @@ -951,10 +1278,7 @@ Vector<uint8_t> const &keyId, Vector<uint8_t> const &message, Vector<uint8_t> const &signature, bool &match) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); DrmSessionManager::Instance()->useSession(sessionId); @@ -979,10 +1303,7 @@ String8 const &algorithm, Vector<uint8_t> const &message, Vector<uint8_t> const &wrappedKey, Vector<uint8_t> &signature) { Mutex::Autolock autoLock(mLock); - - if (mInitCheck != OK) { - return mInitCheck; - } + INIT_CHECK(); if (!checkPermission("android.permission.ACCESS_DRM_CERTIFICATES")) { return -EPERM; @@ -1007,17 +1328,7 @@ void DrmHal::binderDied(const wp<IBinder> &the_late_who __unused) { - Mutex::Autolock autoLock(mLock); - closeOpenSessions(); - setListener(NULL); - mInitCheck = NO_INIT; - - if (mPlugin != NULL) { - if (!mPlugin->setListener(NULL).isOk()) { - mInitCheck = DEAD_OBJECT; - } - } - mPlugin.clear(); + cleanup(); } void DrmHal::writeByteArray(Parcel &obj, hidl_vec<uint8_t> const &vec) @@ -1030,18 +1341,55 @@ } } -void DrmHal::reportMetrics() const +void DrmHal::reportFrameworkMetrics() const { - Vector<uint8_t> metrics; + MediaAnalyticsItem item("mediadrm"); + item.generateSessionID(); + item.setPkgName(mMetrics.GetAppPackageName().c_str()); + String8 vendor; + String8 description; + status_t result = getPropertyStringInternal(String8("vendor"), vendor); + if (result != OK) { + ALOGE("Failed to get vendor from drm plugin: %d", result); + } else { + item.setCString("vendor", vendor.c_str()); + } + result = getPropertyStringInternal(String8("description"), description); + if (result != OK) { + ALOGE("Failed to get description from drm plugin: %d", result); + } else { + item.setCString("description", description.c_str()); + } + + std::string serializedMetrics; + result = mMetrics.GetSerializedMetrics(&serializedMetrics); + if (result != OK) { + ALOGE("Failed to serialize framework metrics: %d", result); + } + std::string b64EncodedMetrics = toBase64StringNoPad(serializedMetrics.data(), + serializedMetrics.size()); + if (!b64EncodedMetrics.empty()) { + item.setCString("serialized_metrics", b64EncodedMetrics.c_str()); + } + if (!item.selfrecord()) { + ALOGE("Failed to self record framework metrics"); + } +} + +void DrmHal::reportPluginMetrics() const +{ + Vector<uint8_t> metricsVector; String8 vendor; String8 description; if (getPropertyStringInternal(String8("vendor"), vendor) == OK && getPropertyStringInternal(String8("description"), description) == OK && - getPropertyByteArrayInternal(String8("metrics"), metrics) == OK) { - status_t res = android::reportDrmPluginMetrics( - metrics, vendor, description); + getPropertyByteArrayInternal(String8("metrics"), metricsVector) == OK) { + std::string metricsString = toBase64StringNoPad(metricsVector.array(), + metricsVector.size()); + status_t res = android::reportDrmPluginMetrics(metricsString, vendor, + description, mAppPackageName); if (res != OK) { - ALOGE("Metrics were retrieved but could not be reported: %i", res); + ALOGE("Metrics were retrieved but could not be reported: %d", res); } } }
diff --git a/drm/libmediadrm/DrmMetrics.cpp b/drm/libmediadrm/DrmMetrics.cpp new file mode 100644 index 0000000..4fed707 --- /dev/null +++ b/drm/libmediadrm/DrmMetrics.cpp
@@ -0,0 +1,422 @@ +/* + * 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 "DrmMetrics" +#include <iomanip> +#include <utility> + +#include <android-base/macros.h> +#include <media/stagefright/foundation/base64.h> +#include <mediadrm/DrmMetrics.h> +#include <sys/time.h> +#include <utils/Log.h> +#include <utils/Timers.h> + +#include "protos/metrics.pb.h" + +using ::android::String16; +using ::android::String8; +using ::android::drm_metrics::DrmFrameworkMetrics; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::drm::V1_0::EventType; +using ::android::hardware::drm::V1_0::KeyStatusType; +using ::android::hardware::drm::V1_1::DrmMetricGroup; +using ::android::os::PersistableBundle; + +namespace { + +template <typename T> std::string GetAttributeName(T type); + +template <> std::string GetAttributeName<KeyStatusType>(KeyStatusType type) { + static const char *type_names[] = {"USABLE", "EXPIRED", + "OUTPUT_NOT_ALLOWED", "STATUS_PENDING", + "INTERNAL_ERROR"}; + if (((size_t)type) > arraysize(type_names)) { + return "UNKNOWN_TYPE"; + } + return type_names[(size_t)type]; +} + +template <> std::string GetAttributeName<EventType>(EventType type) { + static const char *type_names[] = {"PROVISION_REQUIRED", "KEY_NEEDED", + "KEY_EXPIRED", "VENDOR_DEFINED", + "SESSION_RECLAIMED"}; + if (((size_t)type) > arraysize(type_names)) { + return "UNKNOWN_TYPE"; + } + return type_names[(size_t)type]; +} + +template <typename T> +void ExportCounterMetric(const android::CounterMetric<T> &counter, + PersistableBundle *metrics) { + if (!metrics) { + ALOGE("metrics was unexpectedly null."); + return; + } + std::string success_count_name = counter.metric_name() + ".ok.count"; + std::string error_count_name = counter.metric_name() + ".error.count"; + std::vector<int64_t> status_values; + counter.ExportValues( + [&](const android::status_t status, const int64_t value) { + if (status == android::OK) { + metrics->putLong(android::String16(success_count_name.c_str()), + value); + } else { + int64_t total_errors(0); + metrics->getLong(android::String16(error_count_name.c_str()), + &total_errors); + metrics->putLong(android::String16(error_count_name.c_str()), + total_errors + value); + status_values.push_back(status); + } + }); + if (!status_values.empty()) { + std::string error_list_name = counter.metric_name() + ".error.list"; + metrics->putLongVector(android::String16(error_list_name.c_str()), + status_values); + } +} + +template <typename T> +void ExportCounterMetricWithAttributeNames( + const android::CounterMetric<T> &counter, PersistableBundle *metrics) { + if (!metrics) { + ALOGE("metrics was unexpectedly null."); + return; + } + counter.ExportValues([&](const T &attribute, const int64_t value) { + std::string name = counter.metric_name() + "." + + GetAttributeName(attribute) + ".count"; + metrics->putLong(android::String16(name.c_str()), value); + }); +} + +template <typename T> +void ExportEventMetric(const android::EventMetric<T> &event, + PersistableBundle *metrics) { + if (!metrics) { + ALOGE("metrics was unexpectedly null."); + return; + } + std::string success_count_name = event.metric_name() + ".ok.count"; + std::string error_count_name = event.metric_name() + ".error.count"; + std::string timing_name = event.metric_name() + ".ok.average_time_micros"; + std::vector<int64_t> status_values; + event.ExportValues([&](const android::status_t &status, + const android::EventStatistics &value) { + if (status == android::OK) { + metrics->putLong(android::String16(success_count_name.c_str()), + value.count); + metrics->putLong(android::String16(timing_name.c_str()), + value.mean); + } else { + int64_t total_errors(0); + metrics->getLong(android::String16(error_count_name.c_str()), + &total_errors); + metrics->putLong(android::String16(error_count_name.c_str()), + total_errors + value.count); + status_values.push_back(status); + } + }); + if (!status_values.empty()) { + std::string error_list_name = event.metric_name() + ".error.list"; + metrics->putLongVector(android::String16(error_list_name.c_str()), + status_values); + } +} + +void ExportSessionLifespans( + const std::map<std::string, std::pair<int64_t, int64_t>> &mSessionLifespans, + PersistableBundle *metrics) { + if (!metrics) { + ALOGE("metrics was unexpectedly null."); + return; + } + + if (mSessionLifespans.empty()) { + return; + } + + PersistableBundle startTimesBundle; + PersistableBundle endTimesBundle; + for (auto it = mSessionLifespans.begin(); it != mSessionLifespans.end(); + it++) { + String16 key(it->first.c_str(), it->first.size()); + startTimesBundle.putLong(key, it->second.first); + endTimesBundle.putLong(key, it->second.second); + } + metrics->putPersistableBundle( + android::String16("drm.mediadrm.session_start_times_ms"), + startTimesBundle); + metrics->putPersistableBundle( + android::String16("drm.mediadrm.session_end_times_ms"), endTimesBundle); +} + +std::string ToHexString(const android::Vector<uint8_t> &sessionId) { + std::ostringstream out; + out << std::hex << std::setfill('0'); + for (size_t i = 0; i < sessionId.size(); i++) { + out << std::setw(2) << (int)(sessionId[i]); + } + return out.str(); +} + +template <typename CT> +void SetValue(const String16 &name, DrmMetricGroup::ValueType type, + const CT &value, PersistableBundle *bundle) { + switch (type) { + case DrmMetricGroup::ValueType::INT64_TYPE: + bundle->putLong(name, value.int64Value); + break; + case DrmMetricGroup::ValueType::DOUBLE_TYPE: + bundle->putDouble(name, value.doubleValue); + break; + case DrmMetricGroup::ValueType::STRING_TYPE: + bundle->putString(name, String16(value.stringValue.c_str())); + break; + default: + ALOGE("Unexpected value type: %hhu", type); + } +} + +inline String16 MakeIndexString(unsigned int index) { + std::string str("["); + str.append(std::to_string(index)); + str.append("]"); + return String16(str.c_str()); +} + +} // namespace + +namespace android { + +MediaDrmMetrics::MediaDrmMetrics() + : mOpenSessionCounter("drm.mediadrm.open_session", "status"), + mCloseSessionCounter("drm.mediadrm.close_session", "status"), + mGetKeyRequestTimeUs("drm.mediadrm.get_key_request", "status"), + mProvideKeyResponseTimeUs("drm.mediadrm.provide_key_response", "status"), + mGetProvisionRequestCounter("drm.mediadrm.get_provision_request", + "status"), + mProvideProvisionResponseCounter( + "drm.mediadrm.provide_provision_response", "status"), + mKeyStatusChangeCounter("drm.mediadrm.key_status_change", + "key_status_type"), + mEventCounter("drm.mediadrm.event", "event_type"), + mGetDeviceUniqueIdCounter("drm.mediadrm.get_device_unique_id", "status") { +} + +void MediaDrmMetrics::SetSessionStart( + const android::Vector<uint8_t> &sessionId) { + std::string sessionIdHex = ToHexString(sessionId); + mSessionLifespans[sessionIdHex] = + std::make_pair(GetCurrentTimeMs(), (int64_t)0); +} + +void MediaDrmMetrics::SetSessionEnd(const android::Vector<uint8_t> &sessionId) { + std::string sessionIdHex = ToHexString(sessionId); + int64_t endTimeMs = GetCurrentTimeMs(); + if (mSessionLifespans.find(sessionIdHex) != mSessionLifespans.end()) { + mSessionLifespans[sessionIdHex] = + std::make_pair(mSessionLifespans[sessionIdHex].first, endTimeMs); + } else { + mSessionLifespans[sessionIdHex] = std::make_pair((int64_t)0, endTimeMs); + } +} + +void MediaDrmMetrics::Export(PersistableBundle *metrics) { + if (!metrics) { + ALOGE("metrics was unexpectedly null."); + return; + } + ExportCounterMetric(mOpenSessionCounter, metrics); + ExportCounterMetric(mCloseSessionCounter, metrics); + ExportEventMetric(mGetKeyRequestTimeUs, metrics); + ExportEventMetric(mProvideKeyResponseTimeUs, metrics); + ExportCounterMetric(mGetProvisionRequestCounter, metrics); + ExportCounterMetric(mProvideProvisionResponseCounter, metrics); + ExportCounterMetricWithAttributeNames(mKeyStatusChangeCounter, metrics); + ExportCounterMetricWithAttributeNames(mEventCounter, metrics); + ExportCounterMetric(mGetDeviceUniqueIdCounter, metrics); + ExportSessionLifespans(mSessionLifespans, metrics); +} + +status_t MediaDrmMetrics::GetSerializedMetrics(std::string *serializedMetrics) { + + if (!serializedMetrics) { + ALOGE("serializedMetrics was unexpectedly null."); + return UNEXPECTED_NULL; + } + + DrmFrameworkMetrics metrics; + + mOpenSessionCounter.ExportValues( + [&](const android::status_t status, const int64_t value) { + DrmFrameworkMetrics::Counter *counter = + metrics.add_open_session_counter(); + counter->set_count(value); + counter->mutable_attributes()->set_error_code(status); + }); + + mCloseSessionCounter.ExportValues( + [&](const android::status_t status, const int64_t value) { + DrmFrameworkMetrics::Counter *counter = + metrics.add_close_session_counter(); + counter->set_count(value); + counter->mutable_attributes()->set_error_code(status); + }); + + mGetProvisionRequestCounter.ExportValues( + [&](const android::status_t status, const int64_t value) { + DrmFrameworkMetrics::Counter *counter = + metrics.add_get_provisioning_request_counter(); + counter->set_count(value); + counter->mutable_attributes()->set_error_code(status); + }); + + mProvideProvisionResponseCounter.ExportValues( + [&](const android::status_t status, const int64_t value) { + DrmFrameworkMetrics::Counter *counter = + metrics.add_provide_provisioning_response_counter(); + counter->set_count(value); + counter->mutable_attributes()->set_error_code(status); + }); + + mKeyStatusChangeCounter.ExportValues( + [&](const KeyStatusType key_status_type, const int64_t value) { + DrmFrameworkMetrics::Counter *counter = + metrics.add_key_status_change_counter(); + counter->set_count(value); + counter->mutable_attributes()->set_key_status_type( + (uint32_t)key_status_type); + }); + + mEventCounter.ExportValues( + [&](const EventType event_type, const int64_t value) { + DrmFrameworkMetrics::Counter *counter = + metrics.add_event_callback_counter(); + counter->set_count(value); + counter->mutable_attributes()->set_event_type((uint32_t)event_type); + }); + + mGetDeviceUniqueIdCounter.ExportValues( + [&](const status_t status, const int64_t value) { + DrmFrameworkMetrics::Counter *counter = + metrics.add_get_device_unique_id_counter(); + counter->set_count(value); + counter->mutable_attributes()->set_error_code(status); + }); + + mGetKeyRequestTimeUs.ExportValues( + [&](const status_t status, const EventStatistics &stats) { + DrmFrameworkMetrics::DistributionMetric *metric = + metrics.add_get_key_request_time_us(); + metric->set_min(stats.min); + metric->set_max(stats.max); + metric->set_mean(stats.mean); + metric->set_operation_count(stats.count); + metric->set_variance(stats.sum_squared_deviation / stats.count); + metric->mutable_attributes()->set_error_code(status); + }); + + mProvideKeyResponseTimeUs.ExportValues( + [&](const status_t status, const EventStatistics &stats) { + DrmFrameworkMetrics::DistributionMetric *metric = + metrics.add_provide_key_response_time_us(); + metric->set_min(stats.min); + metric->set_max(stats.max); + metric->set_mean(stats.mean); + metric->set_operation_count(stats.count); + metric->set_variance(stats.sum_squared_deviation / stats.count); + metric->mutable_attributes()->set_error_code(status); + }); + + for (const auto &sessionLifespan : mSessionLifespans) { + auto *map = metrics.mutable_session_lifetimes(); + + (*map)[sessionLifespan.first].set_start_time_ms( + sessionLifespan.second.first); + (*map)[sessionLifespan.first].set_end_time_ms( + sessionLifespan.second.second); + } + + if (!metrics.SerializeToString(serializedMetrics)) { + ALOGE("Failed to serialize metrics."); + return UNKNOWN_ERROR; + } + + return OK; +} + +int64_t MediaDrmMetrics::GetCurrentTimeMs() { + struct timeval tv; + gettimeofday(&tv, NULL); + return ((int64_t)tv.tv_sec * 1000) + ((int64_t)tv.tv_usec / 1000); +} + +status_t MediaDrmMetrics::HidlMetricsToBundle( + const hidl_vec<DrmMetricGroup> &hidlMetricGroups, + PersistableBundle *bundleMetricGroups) { + if (bundleMetricGroups == nullptr) { + return UNEXPECTED_NULL; + } + if (hidlMetricGroups.size() == 0) { + return OK; + } + + int groupIndex = 0; + std::map<String16, int> indexMap; + for (const auto &hidlMetricGroup : hidlMetricGroups) { + PersistableBundle bundleMetricGroup; + for (const auto &hidlMetric : hidlMetricGroup.metrics) { + String16 metricName(hidlMetric.name.c_str()); + PersistableBundle bundleMetric; + // Add metric component values. + for (const auto &value : hidlMetric.values) { + SetValue(String16(value.componentName.c_str()), value.type, + value, &bundleMetric); + } + // Set metric attributes. + PersistableBundle bundleMetricAttributes; + for (const auto &attribute : hidlMetric.attributes) { + SetValue(String16(attribute.name.c_str()), attribute.type, + attribute, &bundleMetricAttributes); + } + // Add attributes to the bundle metric. + bundleMetric.putPersistableBundle(String16("attributes"), + bundleMetricAttributes); + // Add one layer of indirection, allowing for repeated metric names. + PersistableBundle repeatedMetrics; + bundleMetricGroup.getPersistableBundle(metricName, + &repeatedMetrics); + int index = indexMap[metricName]; + repeatedMetrics.putPersistableBundle(MakeIndexString(index), + bundleMetric); + indexMap[metricName] = ++index; + + // Add the bundle metric to the group of metrics. + bundleMetricGroup.putPersistableBundle(metricName, + repeatedMetrics); + } + // Add the bundle metric group to the collection of groups. + bundleMetricGroups->putPersistableBundle(MakeIndexString(groupIndex++), + bundleMetricGroup); + } + + return OK; +} + +} // namespace android
diff --git a/drm/libmediadrm/DrmPluginPath.cpp b/drm/libmediadrm/DrmPluginPath.cpp index c760825..ac8607c 100644 --- a/drm/libmediadrm/DrmPluginPath.cpp +++ b/drm/libmediadrm/DrmPluginPath.cpp
@@ -19,7 +19,7 @@ #include <utils/Log.h> #include <cutils/properties.h> -#include <media/DrmPluginPath.h> +#include <mediadrm/DrmPluginPath.h> namespace android {
diff --git a/drm/libmediadrm/DrmSessionManager.cpp b/drm/libmediadrm/DrmSessionManager.cpp index 02270d0..375644c 100644 --- a/drm/libmediadrm/DrmSessionManager.cpp +++ b/drm/libmediadrm/DrmSessionManager.cpp
@@ -21,9 +21,9 @@ #include <binder/IPCThreadState.h> #include <binder/IProcessInfoService.h> #include <binder/IServiceManager.h> -#include <media/DrmSessionManager.h> -#include <media/DrmSessionClientInterface.h> #include <media/stagefright/ProcessInfo.h> +#include <mediadrm/DrmSessionClientInterface.h> +#include <mediadrm/DrmSessionManager.h> #include <unistd.h> #include <utils/String8.h>
diff --git a/drm/libmediadrm/ICrypto.cpp b/drm/libmediadrm/ICrypto.cpp index 7931ed7..8d8d088 100644 --- a/drm/libmediadrm/ICrypto.cpp +++ b/drm/libmediadrm/ICrypto.cpp
@@ -19,10 +19,10 @@ #include <binder/Parcel.h> #include <binder/IMemory.h> #include <cutils/log.h> -#include <media/ICrypto.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AString.h> +#include <mediadrm/ICrypto.h> #include <utils/Log.h> namespace android { @@ -354,10 +354,10 @@ return OK; } - CryptoPlugin::SubSample *subSamples = - new CryptoPlugin::SubSample[numSubSamples]; + std::unique_ptr<CryptoPlugin::SubSample[]> subSamples = + std::make_unique<CryptoPlugin::SubSample[]>(numSubSamples); - data.read(subSamples, + data.read(subSamples.get(), sizeof(CryptoPlugin::SubSample) * numSubSamples); DestinationBuffer destination; @@ -415,7 +415,7 @@ result = -EINVAL; } else { result = decrypt(key, iv, mode, pattern, source, offset, - subSamples, numSubSamples, destination, &errorDetailMsg); + subSamples.get(), numSubSamples, destination, &errorDetailMsg); } reply->writeInt32(result); @@ -434,9 +434,7 @@ } } - delete[] subSamples; - subSamples = NULL; - + subSamples.reset(); return OK; }
diff --git a/drm/libmediadrm/IDrm.cpp b/drm/libmediadrm/IDrm.cpp index 8ff6e6a..509961f 100644 --- a/drm/libmediadrm/IDrm.cpp +++ b/drm/libmediadrm/IDrm.cpp
@@ -19,10 +19,10 @@ #include <utils/Log.h> #include <binder/Parcel.h> -#include <media/IDrm.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AString.h> +#include <mediadrm/IDrm.h> namespace android { @@ -46,6 +46,7 @@ GET_PROPERTY_BYTE_ARRAY, SET_PROPERTY_STRING, SET_PROPERTY_BYTE_ARRAY, + GET_METRICS, SET_CIPHER_ALGORITHM, SET_MAC_ALGORITHM, ENCRYPT, @@ -55,7 +56,12 @@ VERIFY, SET_LISTENER, GET_SECURE_STOP, - RELEASE_ALL_SECURE_STOPS + REMOVE_ALL_SECURE_STOPS, + GET_HDCP_LEVELS, + GET_NUMBER_OF_SESSIONS, + GET_SECURITY_LEVEL, + REMOVE_SECURE_STOP, + GET_SECURE_STOP_IDS }; struct BpDrm : public BpInterface<IDrm> { @@ -114,9 +120,11 @@ return reply.readInt32(); } - virtual status_t openSession(Vector<uint8_t> &sessionId) { + virtual status_t openSession(DrmPlugin::SecurityLevel securityLevel, + Vector<uint8_t> &sessionId) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + data.writeInt32(securityLevel); status_t status = remote()->transact(OPEN_SESSION, data, &reply); if (status != OK) { @@ -297,6 +305,25 @@ return reply.readInt32(); } + virtual status_t getSecureStopIds(List<Vector<uint8_t> > &secureStopIds) { + Parcel data, reply; + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + status_t status = remote()->transact(GET_SECURE_STOP_IDS, data, &reply); + if (status != OK) { + return status; + } + + secureStopIds.clear(); + uint32_t count = reply.readInt32(); + for (size_t i = 0; i < count; i++) { + Vector<uint8_t> secureStopId; + readVector(reply, secureStopId); + secureStopIds.push_back(secureStopId); + } + return reply.readInt32(); + } + virtual status_t getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); @@ -324,11 +351,24 @@ return reply.readInt32(); } - virtual status_t releaseAllSecureStops() { + virtual status_t removeSecureStop(Vector<uint8_t> const &ssid) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); - status_t status = remote()->transact(RELEASE_ALL_SECURE_STOPS, data, &reply); + writeVector(data, ssid); + status_t status = remote()->transact(REMOVE_SECURE_STOP, data, &reply); + if (status != OK) { + return status; + } + + return reply.readInt32(); + } + + virtual status_t removeAllSecureStops() { + Parcel data, reply; + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + status_t status = remote()->transact(REMOVE_ALL_SECURE_STOPS, data, &reply); if (status != OK) { return status; } @@ -350,6 +390,65 @@ return reply.readInt32(); } + virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connected, + DrmPlugin::HdcpLevel *max) const { + Parcel data, reply; + + if (connected == NULL || max == NULL) { + return BAD_VALUE; + } + + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + status_t status = remote()->transact(GET_HDCP_LEVELS, data, &reply); + if (status != OK) { + return status; + } + + *connected = static_cast<DrmPlugin::HdcpLevel>(reply.readInt32()); + *max = static_cast<DrmPlugin::HdcpLevel>(reply.readInt32()); + return reply.readInt32(); + } + + virtual status_t getNumberOfSessions(uint32_t *open, uint32_t *max) const { + Parcel data, reply; + + if (open == NULL || max == NULL) { + return BAD_VALUE; + } + + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + status_t status = remote()->transact(GET_NUMBER_OF_SESSIONS, data, &reply); + if (status != OK) { + return status; + } + + *open = reply.readInt32(); + *max = reply.readInt32(); + return reply.readInt32(); + } + + virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId, + DrmPlugin::SecurityLevel *level) const { + Parcel data, reply; + + if (level == NULL) { + return BAD_VALUE; + } + + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + writeVector(data, sessionId); + status_t status = remote()->transact(GET_SECURITY_LEVEL, data, &reply); + if (status != OK) { + return status; + } + + *level = static_cast<DrmPlugin::SecurityLevel>(reply.readInt32()); + return reply.readInt32(); + } + virtual status_t getPropertyByteArray(String8 const &name, Vector<uint8_t> &value) const { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); @@ -393,6 +492,35 @@ return reply.readInt32(); } + virtual status_t getMetrics(os::PersistableBundle *metrics) { + if (metrics == NULL) { + return BAD_VALUE; + } + Parcel data, reply; + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + status_t status = remote()->transact(GET_METRICS, data, &reply); + if (status != OK) { + return status; + } + // The reply data is ordered as + // 1) 32 bit integer reply followed by + // 2) Serialized PersistableBundle containing metrics. + status_t reply_status; + if (reply.readInt32(&reply_status) != OK + || reply_status != OK) { + ALOGE("Failed to read getMetrics response code from parcel. %d", + reply_status); + return reply_status; + } + + status = metrics->readFromParcel(&reply); + if (status != OK) { + ALOGE("Failed to read metrics from parcel. %d", status); + return status; + } + return reply_status; + } virtual status_t setCipherAlgorithm(Vector<uint8_t> const &sessionId, String8 const &algorithm) { @@ -615,8 +743,10 @@ case OPEN_SESSION: { CHECK_INTERFACE(IDrm, data, reply); + DrmPlugin::SecurityLevel level = + static_cast<DrmPlugin::SecurityLevel>(data.readInt32()); Vector<uint8_t> sessionId; - status_t result = openSession(sessionId); + status_t result = openSession(level, sessionId); writeVector(reply, sessionId); reply->writeInt32(result); return OK; @@ -761,6 +891,24 @@ return OK; } + case GET_SECURE_STOP_IDS: + { + CHECK_INTERFACE(IDrm, data, reply); + List<Vector<uint8_t> > secureStopIds; + status_t result = getSecureStopIds(secureStopIds); + size_t count = secureStopIds.size(); + reply->writeInt32(count); + List<Vector<uint8_t> >::iterator iter = secureStopIds.begin(); + while(iter != secureStopIds.end()) { + size_t size = iter->size(); + reply->writeInt32(size); + reply->write(iter->array(), iter->size()); + iter++; + } + reply->writeInt32(result); + return OK; + } + case GET_SECURE_STOP: { CHECK_INTERFACE(IDrm, data, reply); @@ -781,10 +929,54 @@ return OK; } - case RELEASE_ALL_SECURE_STOPS: + case REMOVE_SECURE_STOP: { CHECK_INTERFACE(IDrm, data, reply); - reply->writeInt32(releaseAllSecureStops()); + Vector<uint8_t> ssid; + readVector(data, ssid); + reply->writeInt32(removeSecureStop(ssid)); + return OK; + } + + case REMOVE_ALL_SECURE_STOPS: + { + CHECK_INTERFACE(IDrm, data, reply); + reply->writeInt32(removeAllSecureStops()); + return OK; + } + + case GET_HDCP_LEVELS: + { + CHECK_INTERFACE(IDrm, data, reply); + DrmPlugin::HdcpLevel connected = DrmPlugin::kHdcpLevelUnknown; + DrmPlugin::HdcpLevel max = DrmPlugin::kHdcpLevelUnknown; + status_t result = getHdcpLevels(&connected, &max); + reply->writeInt32(connected); + reply->writeInt32(max); + reply->writeInt32(result); + return OK; + } + + case GET_NUMBER_OF_SESSIONS: + { + CHECK_INTERFACE(IDrm, data, reply); + uint32_t open = 0, max = 0; + status_t result = getNumberOfSessions(&open, &max); + reply->writeInt32(open); + reply->writeInt32(max); + reply->writeInt32(result); + return OK; + } + + case GET_SECURITY_LEVEL: + { + CHECK_INTERFACE(IDrm, data, reply); + Vector<uint8_t> sessionId; + readVector(data, sessionId); + DrmPlugin::SecurityLevel level = DrmPlugin::kSecurityLevelUnknown; + status_t result = getSecurityLevel(sessionId, &level); + reply->writeInt32(level); + reply->writeInt32(result); return OK; } @@ -829,6 +1021,24 @@ return OK; } + case GET_METRICS: + { + CHECK_INTERFACE(IDrm, data, reply); + + os::PersistableBundle metrics; + status_t result = getMetrics(&metrics); + // The reply data is ordered as + // 1) 32 bit integer reply followed by + // 2) Serialized PersistableBundle containing metrics. + // Only write the metrics if the getMetrics result was + // OK and we successfully added the status to reply. + status_t parcel_result = reply->writeInt32(result); + if (result == OK && parcel_result == OK) { + parcel_result = metrics.writeToParcel(reply); + } + return parcel_result; + } + case SET_CIPHER_ALGORITHM: { CHECK_INTERFACE(IDrm, data, reply);
diff --git a/drm/libmediadrm/IDrmClient.cpp b/drm/libmediadrm/IDrmClient.cpp index 444201f..357de9d 100644 --- a/drm/libmediadrm/IDrmClient.cpp +++ b/drm/libmediadrm/IDrmClient.cpp
@@ -24,7 +24,7 @@ #include <binder/Parcel.h> #include <media/IMediaPlayerClient.h> -#include <media/IDrmClient.h> +#include <mediadrm/IDrmClient.h> namespace android {
diff --git a/drm/libmediadrm/IMediaDrmService.cpp b/drm/libmediadrm/IMediaDrmService.cpp index 84812dc..f320d0b 100644 --- a/drm/libmediadrm/IMediaDrmService.cpp +++ b/drm/libmediadrm/IMediaDrmService.cpp
@@ -20,9 +20,9 @@ #include <binder/Parcel.h> #include <binder/IMemory.h> -#include <media/ICrypto.h> -#include <media/IDrm.h> -#include <media/IMediaDrmService.h> +#include <mediadrm/ICrypto.h> +#include <mediadrm/IDrm.h> +#include <mediadrm/IMediaDrmService.h> #include <utils/Errors.h> // for status_t #include <utils/String8.h>
diff --git a/drm/libmediadrm/PluginMetricsReporting.cpp b/drm/libmediadrm/PluginMetricsReporting.cpp index 57ff5b8..5cb48bf 100644 --- a/drm/libmediadrm/PluginMetricsReporting.cpp +++ b/drm/libmediadrm/PluginMetricsReporting.cpp
@@ -16,83 +16,35 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "PluginMetricsReporting" -#include <utils/Log.h> #include <media/PluginMetricsReporting.h> -#include <media/MediaAnalyticsItem.h> +#include <inttypes.h> -#include "protos/plugin_metrics.pb.h" +#include <media/MediaAnalyticsItem.h> +#include <utils/Log.h> + namespace android { namespace { -using android::drm_metrics::MetricsGroup; -using android::drm_metrics::MetricsGroup_Metric; -using android::drm_metrics::MetricsGroup_Metric_MetricValue; +constexpr char kSerializedMetricsField[] = "serialized_metrics"; -const char* const kParentAttribute = "/parent/external"; - -status_t reportMetricsGroup(const MetricsGroup& metricsGroup, - const String8& batchName, - const int64_t* parentId) { - MediaAnalyticsItem analyticsItem(batchName.c_str()); +status_t reportVendorMetrics(const std::string& metrics, + const String8& name, + const String8& appPackageName) { + MediaAnalyticsItem analyticsItem(name.c_str()); analyticsItem.generateSessionID(); - int64_t sessionId = analyticsItem.getSessionID(); - if (parentId != NULL) { - analyticsItem.setInt64(kParentAttribute, *parentId); + + std::string app_package_name(appPackageName.c_str(), appPackageName.size()); + analyticsItem.setPkgName(app_package_name); + if (metrics.size() > 0) { + analyticsItem.setCString(kSerializedMetricsField, metrics.c_str()); } - // Report the package name. - if (metricsGroup.has_app_package_name()) { - AString app_package_name(metricsGroup.app_package_name().c_str(), - metricsGroup.app_package_name().size()); - analyticsItem.setPkgName(app_package_name); - } - - for (int i = 0; i < metricsGroup.metric_size(); ++i) { - const MetricsGroup_Metric& metric = metricsGroup.metric(i); - if (!metric.has_name()) { - ALOGE("Metric with no name."); - return BAD_VALUE; - } - - if (!metric.has_value()) { - ALOGE("Metric with no value."); - return BAD_VALUE; - } - - const MetricsGroup_Metric_MetricValue& value = metric.value(); - if (value.has_int_value()) { - analyticsItem.setInt64(metric.name().c_str(), - value.int_value()); - } else if (value.has_double_value()) { - analyticsItem.setDouble(metric.name().c_str(), - value.double_value()); - } else if (value.has_string_value()) { - analyticsItem.setCString(metric.name().c_str(), - value.string_value().c_str()); - } else { - ALOGE("Metric Value with no actual value."); - return BAD_VALUE; - } - } - - analyticsItem.setFinalized(true); if (!analyticsItem.selfrecord()) { - // Note the cast to int is because we build on 32 and 64 bit. - // The cast prevents a peculiar printf problem where one format cannot - // satisfy both. - ALOGE("selfrecord() returned false. sessioId %d", (int) sessionId); - } - - for (int i = 0; i < metricsGroup.metric_sub_group_size(); ++i) { - const MetricsGroup& subGroup = metricsGroup.metric_sub_group(i); - status_t res = reportMetricsGroup(subGroup, batchName, &sessionId); - if (res != OK) { - return res; - } + ALOGE("selfrecord() returned false. sessioId %" PRId64, analyticsItem.getSessionID()); } return OK; @@ -114,21 +66,16 @@ } // namespace -status_t reportDrmPluginMetrics(const Vector<uint8_t>& serializedMetrics, +status_t reportDrmPluginMetrics(const std::string& b64EncodedMetrics, const String8& vendor, - const String8& description) { - MetricsGroup root_metrics_group; - if (!root_metrics_group.ParseFromArray(serializedMetrics.array(), - serializedMetrics.size())) { - ALOGE("Failure to parse."); - return BAD_VALUE; - } + const String8& description, + const String8& appPackageName) { String8 name = String8::format("drm.vendor.%s.%s", sanitize(vendor).c_str(), sanitize(description).c_str()); - return reportMetricsGroup(root_metrics_group, name, NULL); + return reportVendorMetrics(b64EncodedMetrics, name, appPackageName); } } // namespace android
diff --git a/drm/libmediadrm/SharedLibrary.cpp b/drm/libmediadrm/SharedLibrary.cpp index bebafa8..b2d635d 100644 --- a/drm/libmediadrm/SharedLibrary.cpp +++ b/drm/libmediadrm/SharedLibrary.cpp
@@ -19,7 +19,7 @@ #include <dlfcn.h> #include <media/stagefright/foundation/ADebug.h> -#include <media/SharedLibrary.h> +#include <mediadrm/SharedLibrary.h> #include <utils/Log.h> namespace android {
diff --git a/drm/libmediadrm/protos/metrics.proto b/drm/libmediadrm/protos/metrics.proto new file mode 100644 index 0000000..6160e6f --- /dev/null +++ b/drm/libmediadrm/protos/metrics.proto
@@ -0,0 +1,107 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.drm_metrics; + + +// This message contains the specific metrics captured by DrmMetrics. It is +// used for serializing and logging metrics. +// next id: 11. +message DrmFrameworkMetrics { + // TODO: Consider using extensions. + + // Attributes are associated with a recorded value. E.g. A counter may + // represent a count of an operation returning a specific error code. The + // error code will be an attribute. + message Attributes { + // Reserved for compatibility with logging proto. + reserved 2 to 13; + + // A general purpose error code where 0 means OK. + optional int32 error_code = 1; + + // Defined at ::android::hardware::drm::V1_0::KeyStatusType; + optional uint32 key_status_type = 14; + + // Defined at ::android::hardware::drm::V1_0::EventType; + optional uint32 event_type = 15; + } + + // The Counter message is used to store a count value with an associated + // Attribute. + message Counter { + optional uint64 count = 1; + // Represents the attributes associated with this counter instance. + optional Attributes attributes = 2; + } + + // The DistributionMetric is meant to capture the moments of a normally + // distributed (or approximately normal) value. + message DistributionMetric { + optional float min = 1; + optional float max = 2; + optional float mean = 3; + optional double variance = 4; + optional uint64 operation_count = 5; + + // Represents the attributes assocated with this distribution metric + // instance. + optional Attributes attributes = 6; + } + + message SessionLifetime { + // Start time of the session in milliseconds since epoch. + optional uint64 start_time_ms = 1; + // End time of the session in milliseconds since epoch. + optional uint64 end_time_ms = 2; + } + + // The count of open session operations. Each instance has a specific error + // code associated with it. + repeated Counter open_session_counter = 1; + + // The count of close session operations. Each instance has a specific error + // code associated with it. + repeated Counter close_session_counter = 2; + + // Count and execution time of getKeyRequest calls. + repeated DistributionMetric get_key_request_time_us = 3; + + // Count and execution time of provideKeyResponse calls. + repeated DistributionMetric provide_key_response_time_us = 4; + + // Count of getProvisionRequest calls. + repeated Counter get_provisioning_request_counter = 5; + + // Count of provideProvisionResponse calls. + repeated Counter provide_provisioning_response_counter = 6; + + // Count of key status events broken out by status type. + repeated Counter key_status_change_counter = 7; + + // Count of events broken out by event type + repeated Counter event_callback_counter = 8; + + // Count getPropertyByteArray calls to retrieve the device unique id. + repeated Counter get_device_unique_id_counter = 9; + + // Session ids to lifetime (start and end time) map. + // Session ids are strings of hex-encoded byte arrays. + map<string, SessionLifetime> session_lifetimes = 10; +} +
diff --git a/drm/libmediadrm/protos/plugin_metrics.proto b/drm/libmediadrm/protos/plugin_metrics.proto deleted file mode 100644 index 7e3bcf5..0000000 --- a/drm/libmediadrm/protos/plugin_metrics.proto +++ /dev/null
@@ -1,50 +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. - */ - -syntax = "proto2"; - -package android.drm_metrics; - -// need this if we are using libprotobuf-cpp-2.3.0-lite -option optimize_for = LITE_RUNTIME; - -// The MetricsGroup is a collection of metric name/value pair instances -// that can be serialized and provided to a caller. -message MetricsGroup { - message Metric { - message MetricValue { - // Exactly one of the following values must be set. - optional int64 int_value = 1; - optional double double_value = 2; - optional string string_value = 3; - } - - // The name of the metric. Must be valid UTF-8. Required. - optional string name = 1; - - // The value of the metric. Required. - optional MetricValue value = 2; - } - - // The list of name/value pairs of metrics. - repeated Metric metric = 1; - - // Allow multiple sub groups of metrics. - repeated MetricsGroup metric_sub_group = 2; - - // Name of the application package associated with the metrics. - optional string app_package_name = 3; -}
diff --git a/drm/libmediadrm/tests/Android.bp b/drm/libmediadrm/tests/Android.bp new file mode 100644 index 0000000..66c906f --- /dev/null +++ b/drm/libmediadrm/tests/Android.bp
@@ -0,0 +1,53 @@ +// Build definitions for unit tests. + +cc_test { + name: "CounterMetric_test", + srcs: ["CounterMetric_test.cpp"], + shared_libs: ["libmediadrm"], + include_dirs: ["frameworks/av/include/media"], + cflags: [ + "-Werror", + "-Wall", + ], +} + +cc_test { + name: "DrmMetrics_test", + srcs: ["DrmMetrics_test.cpp"], + shared_libs: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "libbinder", + "libhidlbase", + "liblog", + "libmediadrmmetrics_full", + "libmediametrics", + "libprotobuf-cpp-full", + "libutils", + ], + static_libs: ["libgmock"], + include_dirs: [ + "frameworks/av/include/media", + ], + cflags: [ + // Suppress unused parameter and no error options. These cause problems + // when using the map type in a proto definition. + "-Wno-unused-parameter", + "-Wno-error", + ] +} + +cc_test { + name: "EventMetric_test", + srcs: ["EventMetric_test.cpp"], + shared_libs: [ + "liblog", + "libmediadrm", + "libutils", + ], + include_dirs: ["frameworks/av/include/media"], + cflags: [ + "-Werror", + "-Wall", + ], +}
diff --git a/drm/libmediadrm/tests/CounterMetric_test.cpp b/drm/libmediadrm/tests/CounterMetric_test.cpp new file mode 100644 index 0000000..6bca0da --- /dev/null +++ b/drm/libmediadrm/tests/CounterMetric_test.cpp
@@ -0,0 +1,80 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "CounterMetric.h" + +namespace android { + +/** + * Unit tests for the CounterMetric class. + */ +class CounterMetricTest : public ::testing::Test { +}; + +TEST_F(CounterMetricTest, IntDataTypeEmpty) { + CounterMetric<int> metric("MyMetricName", "MetricAttributeName"); + + std::map<int, int64_t> values; + + metric.ExportValues( + [&] (int attribute_value, int64_t value) { + values[attribute_value] = value; + }); + + EXPECT_TRUE(values.empty()); +} + +TEST_F(CounterMetricTest, IntDataType) { + CounterMetric<int> metric("MyMetricName", "MetricAttributeName"); + + std::map<int, int64_t> values; + + metric.Increment(7); + metric.Increment(8); + metric.Increment(8); + + metric.ExportValues( + [&] (int attribute_value, int64_t value) { + values[attribute_value] = value; + }); + + ASSERT_EQ(2u, values.size()); + EXPECT_EQ(1, values[7]); + EXPECT_EQ(2, values[8]); +} + +TEST_F(CounterMetricTest, StringDataType) { + CounterMetric<std::string> metric("MyMetricName", "MetricAttributeName"); + + std::map<std::string, int64_t> values; + + metric.Increment("a"); + metric.Increment("b"); + metric.Increment("b"); + + metric.ExportValues( + [&] (std::string attribute_value, int64_t value) { + values[attribute_value] = value; + }); + + ASSERT_EQ(2u, values.size()); + EXPECT_EQ(1, values["a"]); + EXPECT_EQ(2, values["b"]); +} + +} // namespace android
diff --git a/drm/libmediadrm/tests/DrmMetrics_test.cpp b/drm/libmediadrm/tests/DrmMetrics_test.cpp new file mode 100644 index 0000000..64aa9d0 --- /dev/null +++ b/drm/libmediadrm/tests/DrmMetrics_test.cpp
@@ -0,0 +1,475 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DrmMetricsTest" +#include "mediadrm/DrmMetrics.h" + +#include <android/hardware/drm/1.0/types.h> +#include <android/hardware/drm/1.1/types.h> +#include <binder/PersistableBundle.h> +#include <google/protobuf/text_format.h> +#include <google/protobuf/util/message_differencer.h> +#include <gtest/gtest.h> +#include <utils/Log.h> + +#include "protos/metrics.pb.h" + +using ::android::drm_metrics::DrmFrameworkMetrics; +using ::android::hardware::hidl_vec; +using ::android::hardware::drm::V1_0::EventType; +using ::android::hardware::drm::V1_0::KeyStatusType; +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_1::DrmMetricGroup; +using ::android::os::PersistableBundle; +using ::google::protobuf::util::MessageDifferencer; +using ::google::protobuf::TextFormat; + +namespace android { + +/** + * Unit tests for the MediaDrmMetrics class. + */ +class MediaDrmMetricsTest : public ::testing::Test {}; + +/** + * This derived class mocks the clock for testing purposes. + */ +class FakeMediaDrmMetrics : public MediaDrmMetrics { + public: + FakeMediaDrmMetrics() : MediaDrmMetrics(), time_(0) {}; + + int64_t GetCurrentTimeMs() { return time_++; } + int64_t time_; +}; + +TEST_F(MediaDrmMetricsTest, EmptySuccess) { + MediaDrmMetrics metrics; + PersistableBundle bundle; + + metrics.Export(&bundle); + EXPECT_TRUE(bundle.empty()); +} + +TEST_F(MediaDrmMetricsTest, AllValuesSuccessCounts) { + MediaDrmMetrics metrics; + + metrics.mOpenSessionCounter.Increment(OK); + metrics.mCloseSessionCounter.Increment(OK); + + { + EventTimer<status_t> get_key_request_timer(&metrics.mGetKeyRequestTimeUs); + EventTimer<status_t> provide_key_response_timer( + &metrics.mProvideKeyResponseTimeUs); + get_key_request_timer.SetAttribute(OK); + provide_key_response_timer.SetAttribute(OK); + } + + metrics.mGetProvisionRequestCounter.Increment(OK); + metrics.mProvideProvisionResponseCounter.Increment(OK); + metrics.mGetDeviceUniqueIdCounter.Increment(OK); + + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::USABLE); + metrics.mEventCounter.Increment(EventType::PROVISION_REQUIRED); + + PersistableBundle bundle; + + metrics.Export(&bundle); + EXPECT_EQ(11U, bundle.size()); + + // Verify the list of pairs of int64 metrics. + std::vector<std::pair<std::string, int64_t>> expected_values = { + { "drm.mediadrm.open_session.ok.count", 1 }, + { "drm.mediadrm.close_session.ok.count", 1 }, + { "drm.mediadrm.get_key_request.ok.count", 1 }, + { "drm.mediadrm.provide_key_response.ok.count", 1 }, + { "drm.mediadrm.get_provision_request.ok.count", 1 }, + { "drm.mediadrm.provide_provision_response.ok.count", 1 }, + { "drm.mediadrm.key_status_change.USABLE.count", 1 }, + { "drm.mediadrm.event.PROVISION_REQUIRED.count", 1 }, + { "drm.mediadrm.get_device_unique_id.ok.count", 1 }}; + for (const auto& expected_pair : expected_values) { + String16 key(expected_pair.first.c_str()); + int64_t value = -1; + EXPECT_TRUE(bundle.getLong(key, &value)) + << "Unexpected error retrieviing key: " << key; + EXPECT_EQ(expected_pair.second, value) + << "Unexpected value for " << expected_pair.first << ". " << value; + } + + // Validate timing values exist. + String16 get_key_request_key( + "drm.mediadrm.get_key_request.ok.average_time_micros"); + String16 provide_key_response_key( + "drm.mediadrm.provide_key_response.ok.average_time_micros"); + int64_t value = -1; + EXPECT_TRUE(bundle.getLong(get_key_request_key, &value)); + EXPECT_GE(value, 0); + value = -1; + EXPECT_TRUE(bundle.getLong(provide_key_response_key, &value)); + EXPECT_GE(value, 0); +} + +TEST_F(MediaDrmMetricsTest, AllValuesFull) { + MediaDrmMetrics metrics; + + metrics.mOpenSessionCounter.Increment(OK); + metrics.mOpenSessionCounter.Increment(UNEXPECTED_NULL); + + metrics.mCloseSessionCounter.Increment(OK); + metrics.mCloseSessionCounter.Increment(UNEXPECTED_NULL); + + for (status_t s : {OK, UNEXPECTED_NULL}) { + { + EventTimer<status_t> get_key_request_timer(&metrics.mGetKeyRequestTimeUs); + EventTimer<status_t> provide_key_response_timer( + &metrics.mProvideKeyResponseTimeUs); + get_key_request_timer.SetAttribute(s); + provide_key_response_timer.SetAttribute(s); + } + } + + metrics.mGetProvisionRequestCounter.Increment(OK); + metrics.mGetProvisionRequestCounter.Increment(UNEXPECTED_NULL); + metrics.mProvideProvisionResponseCounter.Increment(OK); + metrics.mProvideProvisionResponseCounter.Increment(UNEXPECTED_NULL); + metrics.mGetDeviceUniqueIdCounter.Increment(OK); + metrics.mGetDeviceUniqueIdCounter.Increment(UNEXPECTED_NULL); + + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::USABLE); + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::EXPIRED); + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::OUTPUTNOTALLOWED); + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::STATUSPENDING); + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::INTERNALERROR); + metrics.mEventCounter.Increment(EventType::PROVISION_REQUIRED); + metrics.mEventCounter.Increment(EventType::KEY_NEEDED); + metrics.mEventCounter.Increment(EventType::KEY_EXPIRED); + metrics.mEventCounter.Increment(EventType::VENDOR_DEFINED); + metrics.mEventCounter.Increment(EventType::SESSION_RECLAIMED); + + android::Vector<uint8_t> sessionId1; + sessionId1.push_back(1); + sessionId1.push_back(2); + android::Vector<uint8_t> sessionId2; + sessionId2.push_back(3); + sessionId2.push_back(4); + String16 hexSessionId1("0102"); + String16 hexSessionId2("0304"); + + metrics.SetSessionStart(sessionId1); + metrics.SetSessionStart(sessionId2); + metrics.SetSessionEnd(sessionId2); + metrics.SetSessionEnd(sessionId1); + + PersistableBundle bundle; + metrics.Export(&bundle); + EXPECT_EQ(35U, bundle.size()); + + // Verify the list of pairs of int64 metrics. + std::vector<std::pair<std::string, int64_t>> expected_values = { + { "drm.mediadrm.open_session.ok.count", 1 }, + { "drm.mediadrm.close_session.ok.count", 1 }, + { "drm.mediadrm.get_key_request.ok.count", 1 }, + { "drm.mediadrm.provide_key_response.ok.count", 1 }, + { "drm.mediadrm.get_provision_request.ok.count", 1 }, + { "drm.mediadrm.provide_provision_response.ok.count", 1 }, + { "drm.mediadrm.get_device_unique_id.ok.count", 1 }, + { "drm.mediadrm.open_session.error.count", 1 }, + { "drm.mediadrm.close_session.error.count", 1 }, + { "drm.mediadrm.get_key_request.error.count", 1 }, + { "drm.mediadrm.provide_key_response.error.count", 1 }, + { "drm.mediadrm.get_provision_request.error.count", 1 }, + { "drm.mediadrm.provide_provision_response.error.count", 1 }, + { "drm.mediadrm.get_device_unique_id.error.count", 1 }, + { "drm.mediadrm.key_status_change.USABLE.count", 1 }, + { "drm.mediadrm.key_status_change.EXPIRED.count", 1 }, + { "drm.mediadrm.key_status_change.OUTPUT_NOT_ALLOWED.count", 1 }, + { "drm.mediadrm.key_status_change.STATUS_PENDING.count", 1 }, + { "drm.mediadrm.key_status_change.INTERNAL_ERROR.count", 1 }, + { "drm.mediadrm.event.PROVISION_REQUIRED.count", 1 }, + { "drm.mediadrm.event.KEY_NEEDED.count", 1 }, + { "drm.mediadrm.event.KEY_EXPIRED.count", 1 }, + { "drm.mediadrm.event.VENDOR_DEFINED.count", 1 }, + { "drm.mediadrm.event.SESSION_RECLAIMED.count", 1 }}; + for (const auto& expected_pair : expected_values) { + String16 key(expected_pair.first.c_str()); + int64_t value = -1; + EXPECT_TRUE(bundle.getLong(key, &value)) + << "Unexpected error retrieviing key: " << key; + EXPECT_EQ(expected_pair.second, value) + << "Unexpected value for " << expected_pair.first << ". " << value; + } + + // Verify the error lists + std::vector<std::pair<std::string, std::vector<int64_t>>> expected_vector_values = { + { "drm.mediadrm.close_session.error.list", { UNEXPECTED_NULL } }, + { "drm.mediadrm.get_device_unique_id.error.list", { UNEXPECTED_NULL } }, + { "drm.mediadrm.get_key_request.error.list", { UNEXPECTED_NULL } }, + { "drm.mediadrm.get_provision_request.error.list", { UNEXPECTED_NULL } }, + { "drm.mediadrm.open_session.error.list", { UNEXPECTED_NULL } }, + { "drm.mediadrm.provide_key_response.error.list", { UNEXPECTED_NULL } }, + { "drm.mediadrm.provide_provision_response.error.list", { UNEXPECTED_NULL } }}; + for (const auto& expected_pair : expected_vector_values) { + String16 key(expected_pair.first.c_str()); + std::vector<int64_t> values; + EXPECT_TRUE(bundle.getLongVector(key, &values)) + << "Unexpected error retrieviing key: " << key; + for (auto expected : expected_pair.second) { + EXPECT_TRUE(std::find(values.begin(), values.end(), expected) != values.end()) + << "Could not find " << expected << " for key " << expected_pair.first; + } + } + + // Verify the lifespans + PersistableBundle start_times; + PersistableBundle end_times; + String16 start_time_key("drm.mediadrm.session_start_times_ms"); + String16 end_time_key("drm.mediadrm.session_end_times_ms"); + ASSERT_TRUE(bundle.getPersistableBundle(start_time_key, &start_times)); + ASSERT_TRUE(bundle.getPersistableBundle(end_time_key, &end_times)); + EXPECT_EQ(2U, start_times.size()); + EXPECT_EQ(2U, end_times.size()); + int64_t start_time, end_time; + for (const auto& sid : { hexSessionId1, hexSessionId2 }) { + start_time = -1; + end_time = -1; + EXPECT_TRUE(start_times.getLong(sid, &start_time)); + EXPECT_TRUE(end_times.getLong(sid, &end_time)); + EXPECT_GT(start_time, 0); + EXPECT_GE(end_time, start_time); + } + + // Validate timing values exist. + String16 get_key_request_key( + "drm.mediadrm.get_key_request.ok.average_time_micros"); + String16 provide_key_response_key( + "drm.mediadrm.provide_key_response.ok.average_time_micros"); + int64_t value = -1; + EXPECT_TRUE(bundle.getLong(get_key_request_key, &value)); + EXPECT_GE(value, 0); + value = -1; + EXPECT_TRUE(bundle.getLong(provide_key_response_key, &value)); + EXPECT_GE(value, 0); +} + + +TEST_F(MediaDrmMetricsTest, CounterValuesProtoSerialization) { + MediaDrmMetrics metrics; + + metrics.mOpenSessionCounter.Increment(OK); + metrics.mOpenSessionCounter.Increment(UNEXPECTED_NULL); + metrics.mCloseSessionCounter.Increment(OK); + metrics.mCloseSessionCounter.Increment(UNEXPECTED_NULL); + + metrics.mGetProvisionRequestCounter.Increment(OK); + metrics.mGetProvisionRequestCounter.Increment(UNEXPECTED_NULL); + metrics.mProvideProvisionResponseCounter.Increment(OK); + metrics.mProvideProvisionResponseCounter.Increment(UNEXPECTED_NULL); + metrics.mGetDeviceUniqueIdCounter.Increment(OK); + metrics.mGetDeviceUniqueIdCounter.Increment(UNEXPECTED_NULL); + + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::USABLE); + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::EXPIRED); + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::OUTPUTNOTALLOWED); + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::STATUSPENDING); + metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::INTERNALERROR); + metrics.mEventCounter.Increment(EventType::PROVISION_REQUIRED); + metrics.mEventCounter.Increment(EventType::KEY_NEEDED); + metrics.mEventCounter.Increment(EventType::KEY_EXPIRED); + metrics.mEventCounter.Increment(EventType::VENDOR_DEFINED); + metrics.mEventCounter.Increment(EventType::SESSION_RECLAIMED); + + std::string serializedMetrics; + ASSERT_EQ(OK, metrics.GetSerializedMetrics(&serializedMetrics)); + + DrmFrameworkMetrics metricsProto; + ASSERT_TRUE(metricsProto.ParseFromString(serializedMetrics)); + + std::string expectedMetrics = + "open_session_counter { count: 1 attributes { error_code: -0x7FFFFFF8 } } " + "open_session_counter { count: 1 attributes { error_code: 0 } } " + "close_session_counter { count: 1 attributes { error_code: -0x7FFFFFF8 } } " + "close_session_counter { count: 1 attributes { error_code: 0 } } " + "get_provisioning_request_counter { count: 1 attributes { error_code: -0x7FFFFFF8 } } " + "get_provisioning_request_counter { count: 1 attributes { error_code: 0 } } " + "provide_provisioning_response_counter { count: 1 attributes { error_code: -0x7ffffff8 } } " + "provide_provisioning_response_counter { count: 1 attributes { error_code: 0 } } " + "get_device_unique_id_counter { count: 1 attributes { error_code: -0x7ffffff8 } } " + "get_device_unique_id_counter { count: 1 attributes { error_code: 0 } } " + "key_status_change_counter { count: 1 attributes { key_status_type: 0 } } " + "key_status_change_counter { count: 1 attributes { key_status_type: 1 } } " + "key_status_change_counter { count: 1 attributes { key_status_type: 2 } } " + "key_status_change_counter { count: 1 attributes { key_status_type: 3 } } " + "key_status_change_counter { count: 1 attributes { key_status_type: 4 } } " + "event_callback_counter { count: 1 attributes { event_type: 0 } } " + "event_callback_counter { count: 1 attributes { event_type: 1 } } " + "event_callback_counter { count: 1 attributes { event_type: 2 } } " + "event_callback_counter { count: 1 attributes { event_type: 3 } } " + "event_callback_counter { count: 1 attributes { event_type: 4 } } "; + + DrmFrameworkMetrics expectedMetricsProto; + ASSERT_TRUE(TextFormat::MergeFromString(expectedMetrics, &expectedMetricsProto)); + + std::string diffString; + MessageDifferencer differ; + differ.ReportDifferencesToString(&diffString); + ASSERT_TRUE(differ.Compare(expectedMetricsProto, metricsProto)) + << diffString; +} + +TEST_F(MediaDrmMetricsTest, TimeMetricsProtoSerialization) { + MediaDrmMetrics metrics; + + for (status_t s : {OK, UNEXPECTED_NULL}) { + double time = 0; + for (int i = 0; i < 5; i++) { + time += 1.0; + metrics.mGetKeyRequestTimeUs.Record(time, s); + metrics.mProvideKeyResponseTimeUs.Record(time, s); + } + } + + std::string serializedMetrics; + ASSERT_EQ(OK, metrics.GetSerializedMetrics(&serializedMetrics)); + + DrmFrameworkMetrics metricsProto; + ASSERT_TRUE(metricsProto.ParseFromString(serializedMetrics)); + + std::string expectedMetrics = + "get_key_request_time_us { " + " min: 1 max: 5 mean: 3.5 variance: 1 operation_count: 5 " + " attributes { error_code: -0x7FFFFFF8 } " + "} " + "get_key_request_time_us { " + " min: 1 max: 5 mean: 3.5 variance: 1 operation_count: 5 " + " attributes { error_code: 0 } " + "} " + "provide_key_response_time_us { " + " min: 1 max: 5 mean: 3.5 variance: 1 operation_count: 5 " + " attributes { error_code: -0x7FFFFFF8 } " + "} " + "provide_key_response_time_us { " + " min: 1 max: 5 mean: 3.5 variance: 1 operation_count: 5 " + " attributes { error_code: 0 } " + "} "; + + DrmFrameworkMetrics expectedMetricsProto; + ASSERT_TRUE(TextFormat::MergeFromString(expectedMetrics, &expectedMetricsProto)); + + std::string diffString; + MessageDifferencer differ; + differ.ReportDifferencesToString(&diffString); + ASSERT_TRUE(differ.Compare(expectedMetricsProto, metricsProto)) + << diffString; +} + +TEST_F(MediaDrmMetricsTest, SessionLifetimeProtoSerialization) { + // Use the fake so the clock is predictable; + FakeMediaDrmMetrics metrics; + + android::Vector<uint8_t> sessionId1; + sessionId1.push_back(1); + sessionId1.push_back(2); + android::Vector<uint8_t> sessionId2; + sessionId2.push_back(3); + sessionId2.push_back(4); + + metrics.SetSessionStart(sessionId1); + metrics.SetSessionStart(sessionId2); + metrics.SetSessionEnd(sessionId2); + metrics.SetSessionEnd(sessionId1); + + std::string serializedMetrics; + ASSERT_EQ(OK, metrics.GetSerializedMetrics(&serializedMetrics)); + + DrmFrameworkMetrics metricsProto; + ASSERT_TRUE(metricsProto.ParseFromString(serializedMetrics)); + + std::string expectedMetrics = + "session_lifetimes: { " + " key: '0102' " + " value { start_time_ms: 0 end_time_ms: 3 } " + "} " + "session_lifetimes: { " + " key: '0304' " + " value { start_time_ms: 1 end_time_ms: 2 } " + "} "; + + DrmFrameworkMetrics expectedMetricsProto; + ASSERT_TRUE(TextFormat::MergeFromString(expectedMetrics, &expectedMetricsProto)); + + std::string diffString; + MessageDifferencer differ; + differ.ReportDifferencesToString(&diffString); + ASSERT_TRUE(differ.Compare(expectedMetricsProto, metricsProto)) + << diffString; +} + +TEST_F(MediaDrmMetricsTest, HidlToBundleMetricsEmpty) { + hidl_vec<DrmMetricGroup> hidlMetricGroups; + PersistableBundle bundleMetricGroups; + + ASSERT_EQ(OK, MediaDrmMetrics::HidlMetricsToBundle(hidlMetricGroups, &bundleMetricGroups)); + ASSERT_EQ(0U, bundleMetricGroups.size()); +} + +TEST_F(MediaDrmMetricsTest, HidlToBundleMetricsMultiple) { + DrmMetricGroup hidlMetricGroup = + { { { + "open_session_ok", + { { "status", DrmMetricGroup::ValueType::INT64_TYPE, + (int64_t) Status::OK, 0.0, "" } }, + { { "count", DrmMetricGroup::ValueType::INT64_TYPE, 3, 0.0, "" } } + }, + { + "close_session_not_opened", + { { "status", DrmMetricGroup::ValueType::INT64_TYPE, + (int64_t) Status::ERROR_DRM_SESSION_NOT_OPENED, 0.0, "" } }, + { { "count", DrmMetricGroup::ValueType::INT64_TYPE, 7, 0.0, "" } } + } } }; + + PersistableBundle bundleMetricGroups; + ASSERT_EQ(OK, MediaDrmMetrics::HidlMetricsToBundle(hidl_vec<DrmMetricGroup>({hidlMetricGroup}), + &bundleMetricGroups)); + ASSERT_EQ(1U, bundleMetricGroups.size()); + PersistableBundle bundleMetricGroup; + ASSERT_TRUE(bundleMetricGroups.getPersistableBundle(String16("[0]"), &bundleMetricGroup)); + ASSERT_EQ(2U, bundleMetricGroup.size()); + + // Verify each metric. + PersistableBundle metric; + ASSERT_TRUE(bundleMetricGroup.getPersistableBundle(String16("open_session_ok"), &metric)); + PersistableBundle metricInstance; + ASSERT_TRUE(metric.getPersistableBundle(String16("[0]"), &metricInstance)); + int64_t value = 0; + ASSERT_TRUE(metricInstance.getLong(String16("count"), &value)); + ASSERT_EQ(3, value); + PersistableBundle attributeBundle; + ASSERT_TRUE(metricInstance.getPersistableBundle(String16("attributes"), &attributeBundle)); + ASSERT_TRUE(attributeBundle.getLong(String16("status"), &value)); + ASSERT_EQ((int64_t) Status::OK, value); + + ASSERT_TRUE(bundleMetricGroup.getPersistableBundle(String16("close_session_not_opened"), + &metric)); + ASSERT_TRUE(metric.getPersistableBundle(String16("[0]"), &metricInstance)); + ASSERT_TRUE(metricInstance.getLong(String16("count"), &value)); + ASSERT_EQ(7, value); + ASSERT_TRUE(metricInstance.getPersistableBundle(String16("attributes"), &attributeBundle)); + value = 0; + ASSERT_TRUE(attributeBundle.getLong(String16("status"), &value)); + ASSERT_EQ((int64_t) Status::ERROR_DRM_SESSION_NOT_OPENED, value); +} + +} // namespace android
diff --git a/drm/libmediadrm/tests/EventMetric_test.cpp b/drm/libmediadrm/tests/EventMetric_test.cpp new file mode 100644 index 0000000..eb6c4f6 --- /dev/null +++ b/drm/libmediadrm/tests/EventMetric_test.cpp
@@ -0,0 +1,142 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "EventMetric.h" + +namespace android { + +/** + * Unit tests for the EventMetric class. + */ + +TEST(EventMetricTest, IntDataTypeEmpty) { + EventMetric<int> metric("MyMetricName", "MetricAttributeName"); + + std::map<int, EventStatistics> values; + + metric.ExportValues( + [&] (int attribute_value, const EventStatistics& value) { + values[attribute_value] = value; + }); + + EXPECT_TRUE(values.empty()); +} + +TEST(EventMetricTest, IntDataType) { + EventMetric<int> metric("MyMetricName", "MetricAttributeName"); + + std::map<int, EventStatistics> values; + + metric.Record(4, 7); + metric.Record(5, 8); + metric.Record(5, 8); + metric.Record(5, 8); + metric.Record(6, 8); + metric.Record(6, 8); + metric.Record(6, 8); + + metric.ExportValues( + [&] (int attribute_value, const EventStatistics& value) { + values[attribute_value] = value; + }); + + ASSERT_EQ(2u, values.size()); + EXPECT_EQ(4, values[7].min); + EXPECT_EQ(4, values[7].max); + EXPECT_EQ(4, values[7].mean); + EXPECT_EQ(1, values[7].count); + + EXPECT_EQ(5, values[8].min); + EXPECT_EQ(6, values[8].max); + // This is an approximate value because of the technique we're using. + EXPECT_NEAR(5.5, values[8].mean, 0.2); + EXPECT_EQ(6, values[8].count); +} + +TEST(EventMetricTest, StringDataType) { + EventMetric<std::string> metric("MyMetricName", "MetricAttributeName"); + + std::map<std::string, EventStatistics> values; + + metric.Record(1, "a"); + metric.Record(2, "b"); + metric.Record(2, "b"); + metric.Record(3, "b"); + metric.Record(3, "b"); + + metric.ExportValues( + [&] (std::string attribute_value, const EventStatistics& value) { + values[attribute_value] = value; + }); + + ASSERT_EQ(2u, values.size()); + EXPECT_EQ(1, values["a"].min); + EXPECT_EQ(1, values["a"].max); + EXPECT_EQ(1, values["a"].mean); + EXPECT_EQ(1, values["a"].count); + + EXPECT_EQ(2, values["b"].min); + EXPECT_EQ(3, values["b"].max); + EXPECT_NEAR(2.5, values["b"].mean, 0.2); + EXPECT_EQ(4, values["b"].count); +} + +// Helper class that allows us to mock the clock. +template<typename AttributeType> +class MockEventTimer : public EventTimer<AttributeType> { + public: + explicit MockEventTimer(nsecs_t time_delta_ns, + EventMetric<AttributeType>* metric) + : EventTimer<AttributeType>(metric) { + // Pretend the event started earlier. + this->start_time_ = systemTime() - time_delta_ns; + } +}; + +TEST(EventTimerTest, IntDataType) { + EventMetric<int> metric("MyMetricName", "MetricAttributeName"); + + for (int i = 0; i < 5; i++) { + { + // Add a mock time delta. + MockEventTimer<int> metric_timer(i * 1000000, &metric); + metric_timer.SetAttribute(i % 2); + } + } + + std::map<int, EventStatistics> values; + metric.ExportValues( + [&] (int attribute_value, const EventStatistics& value) { + values[attribute_value] = value; + }); + + ASSERT_EQ(2u, values.size()); + EXPECT_LT(values[0].min, values[0].max); + EXPECT_GE(4000, values[0].max); + EXPECT_GT(values[0].mean, values[0].min); + EXPECT_LE(values[0].mean, values[0].max); + EXPECT_EQ(3, values[0].count); + + EXPECT_LT(values[1].min, values[1].max); + EXPECT_GE(3000, values[1].max); + EXPECT_GT(values[1].mean, values[1].min); + EXPECT_LE(values[1].mean, values[1].max); + EXPECT_EQ(2, values[1].count); +} + +} // namespace android
diff --git a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp index 50acc1d..1558e8b 100644 --- a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp +++ b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
@@ -55,7 +55,7 @@ status_t ClearKeyCasFactory::createPlugin( int32_t CA_system_id, - uint64_t appData, + void *appData, CasPluginCallback callback, CasPlugin **plugin) { if (!isSystemIdSupported(CA_system_id)) { @@ -83,7 +83,7 @@ /////////////////////////////////////////////////////////////////////////////// ClearKeyCasPlugin::ClearKeyCasPlugin( - uint64_t appData, CasPluginCallback callback) + void *appData, CasPluginCallback callback) : mCallback(callback), mAppData(appData) { ALOGV("CTOR"); } @@ -347,6 +347,9 @@ return ERROR_CAS_CANNOT_HANDLE; } + scramblingControl = (DescramblerPlugin::ScramblingControl) + (scramblingControl & DescramblerPlugin::kScrambling_Mask_Key); + AES_KEY contentKey; if (scramblingControl != DescramblerPlugin::kScrambling_Unscrambled) {
diff --git a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h index 8a9ea83..389e172 100644 --- a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h +++ b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h
@@ -44,7 +44,7 @@ std::vector<CasPluginDescriptor> *descriptors) const override; virtual status_t createPlugin( int32_t CA_system_id, - uint64_t appData, + void *appData, CasPluginCallback callback, CasPlugin **plugin) override; }; @@ -62,7 +62,7 @@ class ClearKeyCasPlugin : public CasPlugin { public: - ClearKeyCasPlugin(uint64_t appData, CasPluginCallback callback); + ClearKeyCasPlugin(void *appData, CasPluginCallback callback); virtual ~ClearKeyCasPlugin(); virtual status_t setPrivateData( @@ -94,7 +94,7 @@ Mutex mKeyFetcherLock; std::unique_ptr<KeyFetcher> mKeyFetcher; CasPluginCallback mCallback; - uint64_t mAppData; + void* mAppData; }; class ClearKeyDescramblerPlugin : public DescramblerPlugin {
diff --git a/drm/mediacas/plugins/clearkey/ClearKeyFetcher.cpp b/drm/mediacas/plugins/clearkey/ClearKeyFetcher.cpp index cb69f91..eaa3390 100644 --- a/drm/mediacas/plugins/clearkey/ClearKeyFetcher.cpp +++ b/drm/mediacas/plugins/clearkey/ClearKeyFetcher.cpp
@@ -89,7 +89,7 @@ // asset_id change. If it sends an EcmContainer with 2 Ecms with different // asset_ids (old and new) then it might be best to prefetch the Emm. if ((asset_.id() != 0) && (*asset_id != asset_.id())) { - ALOGW("Asset_id change from %" PRIu64 " to %" PRIu64, asset_.id(), *asset_id); + ALOGW("Asset_id change from %llu to %" PRIu64, asset_.id(), *asset_id); asset_.Clear(); }
diff --git a/drm/mediacas/plugins/clearkey/ClearKeySessionLibrary.cpp b/drm/mediacas/plugins/clearkey/ClearKeySessionLibrary.cpp index 9fd2d4d..3bb1176 100644 --- a/drm/mediacas/plugins/clearkey/ClearKeySessionLibrary.cpp +++ b/drm/mediacas/plugins/clearkey/ClearKeySessionLibrary.cpp
@@ -95,7 +95,7 @@ void ClearKeySessionLibrary::destroyPlugin(CasPlugin *plugin) { Mutex::Autolock lock(mSessionsLock); - for (ssize_t index = mIDToSessionMap.size() - 1; index >= 0; index--) { + for (ssize_t index = (ssize_t)mIDToSessionMap.size() - 1; index >= 0; index--) { std::shared_ptr<ClearKeyCasSession> session = mIDToSessionMap.valueAt(index); if (session->getPlugin() == plugin) { mIDToSessionMap.removeItemsAt(index);
diff --git a/drm/mediacas/plugins/clearkey/JsonAssetLoader.cpp b/drm/mediacas/plugins/clearkey/JsonAssetLoader.cpp index 6e1004c..ee8dba3 100644 --- a/drm/mediacas/plugins/clearkey/JsonAssetLoader.cpp +++ b/drm/mediacas/plugins/clearkey/JsonAssetLoader.cpp
@@ -36,8 +36,6 @@ const String8 kCasTypeTag("cas_type"); const String8 kBase64Padding("="); -const uint32_t kKeyLength = 16; - JsonAssetLoader::JsonAssetLoader() { }
diff --git a/drm/mediacas/plugins/clearkey/ecm_generator.cpp b/drm/mediacas/plugins/clearkey/ecm_generator.cpp index 7d29659..218ce35 100644 --- a/drm/mediacas/plugins/clearkey/ecm_generator.cpp +++ b/drm/mediacas/plugins/clearkey/ecm_generator.cpp
@@ -38,8 +38,6 @@ const uint16_t kTotalEcmSize = kEcmClearFieldsSize + kContentKeyByteSize; // clear fields + clear key -const uint32_t kKeyLength = 16; - #define UNALIGNED_LOAD32(_p) (*reinterpret_cast<const uint32_t *>(_p)) static uint32_t Load32(const void *p) {
diff --git a/drm/mediacas/plugins/clearkey/tests/ClearKeyFetcherTest.cpp b/drm/mediacas/plugins/clearkey/tests/ClearKeyFetcherTest.cpp index ace086a..d12cfeb 100644 --- a/drm/mediacas/plugins/clearkey/tests/ClearKeyFetcherTest.cpp +++ b/drm/mediacas/plugins/clearkey/tests/ClearKeyFetcherTest.cpp
@@ -93,7 +93,7 @@ uint64_t asset_id; std::vector<KeyFetcher::KeyInfo> keys; EXPECT_EQ(OK, fetcher.ObtainKey(ecm_, &asset_id, &keys)); - EXPECT_EQ(2, keys.size()); + EXPECT_EQ(2U, keys.size()); EXPECT_EQ(0, keys[0].key_id); EXPECT_EQ(content_key_[0]->size(), keys[0].key_bytes->size()); EXPECT_EQ(0, memcmp(content_key_[0]->data(),
diff --git a/drm/mediacas/plugins/mock/MockCasPlugin.cpp b/drm/mediacas/plugins/mock/MockCasPlugin.cpp index 18cd9a4..8404a83 100644 --- a/drm/mediacas/plugins/mock/MockCasPlugin.cpp +++ b/drm/mediacas/plugins/mock/MockCasPlugin.cpp
@@ -49,8 +49,8 @@ status_t MockCasFactory::createPlugin( int32_t CA_system_id, - uint64_t appData, - CasPluginCallback callback, + void* /*appData*/, + CasPluginCallback /*callback*/, CasPlugin **plugin) { if (!isSystemIdSupported(CA_system_id)) { return BAD_VALUE; @@ -98,7 +98,7 @@ MockSessionLibrary::get()->destroyPlugin(this); } -status_t MockCasPlugin::setPrivateData(const CasData &data) { +status_t MockCasPlugin::setPrivateData(const CasData& /*data*/) { ALOGV("setPrivateData"); return OK; } @@ -123,7 +123,7 @@ } status_t MockCasPlugin::setSessionPrivateData( - const CasSessionId &sessionId, const CasData &data) { + const CasSessionId &sessionId, const CasData& /*data*/) { ALOGV("setSessionPrivateData: sessionId=%s", arrayToString(sessionId).string()); Mutex::Autolock lock(mLock); @@ -146,7 +146,7 @@ if (session == NULL) { return BAD_VALUE; } - ALOGV("ECM: size=%d", ecm.size()); + ALOGV("ECM: size=%zu", ecm.size()); ALOGV("ECM: data=%s", arrayToString(ecm).string()); return OK; @@ -156,14 +156,14 @@ ALOGV("processEmm"); Mutex::Autolock lock(mLock); - ALOGV("EMM: size=%d", emm.size()); + ALOGV("EMM: size=%zu", emm.size()); ALOGV("EMM: data=%s", arrayToString(emm).string()); return OK; } status_t MockCasPlugin::sendEvent( - int32_t event, int arg, const CasData &eventData) { + int32_t event, int /*arg*/, const CasData& /*eventData*/) { ALOGV("sendEvent: event=%d", event); Mutex::Autolock lock(mLock); @@ -178,7 +178,7 @@ } status_t MockCasPlugin::refreshEntitlements( - int32_t refreshType, const CasData &refreshData) { + int32_t /*refreshType*/, const CasData &refreshData) { ALOGV("refreshEntitlements: refreshData=%s", arrayToString(refreshData).string()); Mutex::Autolock lock(mLock); @@ -216,7 +216,7 @@ int32_t srcOffset, void *dstPtr, int32_t dstOffset, - AString *errorDetailMsg) { + AString* /*errorDetailMsg*/) { ALOGV("MockDescramblerPlugin::descramble(secure=%d, sctrl=%d," "subSamples=%s, srcPtr=%p, dstPtr=%p, srcOffset=%d, dstOffset=%d)", (int)secure, (int)scramblingControl,
diff --git a/drm/mediacas/plugins/mock/MockCasPlugin.h b/drm/mediacas/plugins/mock/MockCasPlugin.h index 9632492..8106990 100644 --- a/drm/mediacas/plugins/mock/MockCasPlugin.h +++ b/drm/mediacas/plugins/mock/MockCasPlugin.h
@@ -39,7 +39,7 @@ std::vector<CasPluginDescriptor> *descriptors) const override; virtual status_t createPlugin( int32_t CA_system_id, - uint64_t appData, + void *appData, CasPluginCallback callback, CasPlugin **plugin) override; };
diff --git a/drm/mediadrm/plugins/clearkey/Android.bp b/drm/mediadrm/plugins/clearkey/Android.bp deleted file mode 100644 index 2973fcf..0000000 --- a/drm/mediadrm/plugins/clearkey/Android.bp +++ /dev/null
@@ -1,59 +0,0 @@ -// -// Copyright (C) 2014 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -cc_library_shared { - name: "libdrmclearkeyplugin", - - srcs: [ - "AesCtrDecryptor.cpp", - "ClearKeyUUID.cpp", - "CreatePluginFactories.cpp", - "CryptoFactory.cpp", - "CryptoPlugin.cpp", - "DrmFactory.cpp", - "DrmPlugin.cpp", - "InitDataParser.cpp", - "JsonWebKey.cpp", - "Session.cpp", - "SessionLibrary.cpp", - "Utils.cpp", - ], - - vendor: true, - relative_install_path: "mediadrm", - - shared_libs: [ - "libcrypto", - "liblog", - "libstagefright_foundation", - "libutils", - ], - - static_libs: ["libjsmn"], - - include_dirs: [ - "frameworks/native/include", - "frameworks/av/include", - ], - - export_include_dirs: ["."], - export_static_lib_headers: ["libjsmn"], -} - -//######################################################################## -// Build unit tests - -subdirs = ["tests"]
diff --git a/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp b/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp deleted file mode 100644 index eeb64c3..0000000 --- a/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp +++ /dev/null
@@ -1,60 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "ClearKeyCryptoPlugin" -#include <utils/Log.h> - -#include <utils/Errors.h> -#include <utils/StrongPointer.h> - -#include "CryptoFactory.h" - -#include "ClearKeyUUID.h" -#include "CryptoPlugin.h" -#include "Session.h" -#include "SessionLibrary.h" - -namespace clearkeydrm { - -bool CryptoFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) const { - return isClearKeyUUID(uuid); -} - -android::status_t CryptoFactory::createPlugin( - const uint8_t uuid[16], - const void* data, size_t size, - android::CryptoPlugin** plugin) { - if (!isCryptoSchemeSupported(uuid)) { - *plugin = NULL; - return android::BAD_VALUE; - } - - android::Vector<uint8_t> sessionId; - sessionId.appendArray(reinterpret_cast<const uint8_t*>(data), size); - - CryptoPlugin *clearKeyPlugin = new CryptoPlugin(sessionId); - android::status_t result = clearKeyPlugin->getInitStatus(); - if (result == android::OK) { - *plugin = clearKeyPlugin; - } else { - delete clearKeyPlugin; - *plugin = NULL; - } - return result; -} - -} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/DrmFactory.cpp b/drm/mediadrm/plugins/clearkey/DrmFactory.cpp deleted file mode 100644 index c83321b..0000000 --- a/drm/mediadrm/plugins/clearkey/DrmFactory.cpp +++ /dev/null
@@ -1,58 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "ClearKeyCryptoPlugin" -#include <utils/Log.h> - -#include <utils/Errors.h> - -#include "DrmFactory.h" - -#include "DrmPlugin.h" -#include "ClearKeyUUID.h" -#include "MimeType.h" -#include "SessionLibrary.h" - -namespace clearkeydrm { - -bool DrmFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) { - return isClearKeyUUID(uuid); -} - -bool DrmFactory::isContentTypeSupported(const android::String8 &type) { - // This should match the types handed by InitDataParser. - return type == kIsoBmffVideoMimeType || - type == kIsoBmffAudioMimeType || - type == kCencInitDataFormat || - type == kWebmVideoMimeType || - type == kWebmAudioMimeType || - type == kWebmInitDataFormat; -} - -android::status_t DrmFactory::createDrmPlugin( - const uint8_t uuid[16], - android::DrmPlugin** plugin) { - if (!isCryptoSchemeSupported(uuid)) { - *plugin = NULL; - return android::BAD_VALUE; - } - - *plugin = new DrmPlugin(SessionLibrary::get()); - return android::OK; -} - -} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp deleted file mode 100644 index ec07d87..0000000 --- a/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp +++ /dev/null
@@ -1,158 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "ClearKeyCryptoPlugin" -#include <utils/Log.h> - -#include <media/stagefright/MediaErrors.h> -#include <utils/StrongPointer.h> - -#include "DrmPlugin.h" - -#include "Session.h" - -namespace { -const android::String8 kStreaming("Streaming"); -const android::String8 kOffline("Offline"); -const android::String8 kTrue("True"); - -const android::String8 kQueryKeyLicenseType("LicenseType"); - // Value: "Streaming" or "Offline" -const android::String8 kQueryKeyPlayAllowed("PlayAllowed"); - // Value: "True" or "False" -const android::String8 kQueryKeyRenewAllowed("RenewAllowed"); - // Value: "True" or "False" -}; - -namespace clearkeydrm { - -using android::sp; - -DrmPlugin::DrmPlugin(SessionLibrary* sessionLibrary) - : mSessionLibrary(sessionLibrary) { - mPlayPolicy.clear(); -} - -status_t DrmPlugin::openSession(Vector<uint8_t>& sessionId) { - sp<Session> session = mSessionLibrary->createSession(); - sessionId = session->sessionId(); - return android::OK; -} - -status_t DrmPlugin::closeSession(const Vector<uint8_t>& sessionId) { - sp<Session> session = mSessionLibrary->findSession(sessionId); - if (sessionId.size() == 0) { - return android::BAD_VALUE; - } - if (session.get()) { - mSessionLibrary->destroySession(session); - return android::OK; - } - return android::ERROR_DRM_SESSION_NOT_OPENED; -} - -status_t DrmPlugin::getKeyRequest( - const Vector<uint8_t>& scope, - const Vector<uint8_t>& initData, - const String8& mimeType, - KeyType keyType, - const KeyedVector<String8, String8>& optionalParameters, - Vector<uint8_t>& request, - String8& defaultUrl, - DrmPlugin::KeyRequestType *keyRequestType) { - UNUSED(optionalParameters); - if (scope.size() == 0) { - return android::BAD_VALUE; - } - - if (keyType != kKeyType_Streaming) { - return android::ERROR_DRM_CANNOT_HANDLE; - } - - *keyRequestType = DrmPlugin::kKeyRequestType_Initial; - defaultUrl.clear(); - sp<Session> session = mSessionLibrary->findSession(scope); - if (!session.get()) { - return android::ERROR_DRM_SESSION_NOT_OPENED; - } - - return session->getKeyRequest(initData, mimeType, &request); -} - -void DrmPlugin::setPlayPolicy() { - mPlayPolicy.clear(); - mPlayPolicy.add(kQueryKeyLicenseType, kStreaming); - mPlayPolicy.add(kQueryKeyPlayAllowed, kTrue); - mPlayPolicy.add(kQueryKeyRenewAllowed, kTrue); -} - -status_t DrmPlugin::provideKeyResponse( - const Vector<uint8_t>& scope, - const Vector<uint8_t>& response, - Vector<uint8_t>& keySetId) { - if (scope.size() == 0 || response.size() == 0) { - return android::BAD_VALUE; - } - sp<Session> session = mSessionLibrary->findSession(scope); - if (!session.get()) { - return android::ERROR_DRM_SESSION_NOT_OPENED; - } - - setPlayPolicy(); - status_t res = session->provideKeyResponse(response); - if (res == android::OK) { - // This is for testing AMediaDrm_setOnEventListener only. - sendEvent(kDrmPluginEventVendorDefined, 0, &scope, NULL); - keySetId.clear(); - } - return res; -} - -status_t DrmPlugin::getPropertyString( - const String8& name, String8& value) const { - if (name == "vendor") { - value = "Google"; - } else if (name == "version") { - value = "1.0"; - } else if (name == "description") { - value = "ClearKey CDM"; - } else if (name == "algorithms") { - value = ""; - } else if (name == "listenerTestSupport") { - value = "true"; - } else { - ALOGE("App requested unknown string property %s", name.string()); - return android::ERROR_DRM_CANNOT_HANDLE; - } - return android::OK; -} - -status_t DrmPlugin::queryKeyStatus( - const Vector<uint8_t>& sessionId, - KeyedVector<String8, String8>& infoMap) const { - - if (sessionId.size() == 0) { - return android::BAD_VALUE; - } - - infoMap.clear(); - for (size_t i = 0; i < mPlayPolicy.size(); ++i) { - infoMap.add(mPlayPolicy.keyAt(i), mPlayPolicy.valueAt(i)); - } - return android::OK; -} -} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/DrmPlugin.h deleted file mode 100644 index f37a706..0000000 --- a/drm/mediadrm/plugins/clearkey/DrmPlugin.h +++ /dev/null
@@ -1,255 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CLEARKEY_DRM_PLUGIN_H_ -#define CLEARKEY_DRM_PLUGIN_H_ - -#include <media/drm/DrmAPI.h> -#include <media/stagefright/foundation/ABase.h> -#include <media/stagefright/MediaErrors.h> -#include <utils/Errors.h> -#include <utils/KeyedVector.h> -#include <utils/List.h> -#include <utils/String8.h> -#include <utils/Vector.h> - -#include "SessionLibrary.h" -#include "Utils.h" - -namespace clearkeydrm { - -using android::KeyedVector; -using android::List; -using android::status_t; -using android::String8; -using android::Vector; - -class DrmPlugin : public android::DrmPlugin { -public: - explicit DrmPlugin(SessionLibrary* sessionLibrary); - - virtual ~DrmPlugin() {} - - virtual status_t openSession(Vector<uint8_t>& sessionId); - - virtual status_t closeSession(const Vector<uint8_t>& sessionId); - - virtual status_t getKeyRequest( - const Vector<uint8_t>& scope, - const Vector<uint8_t>& mimeType, - const String8& initDataType, - KeyType keyType, - const KeyedVector<String8, String8>& optionalParameters, - Vector<uint8_t>& request, - String8& defaultUrl, - DrmPlugin::KeyRequestType *keyRequestType); - - virtual status_t provideKeyResponse( - const Vector<uint8_t>& scope, - const Vector<uint8_t>& response, - Vector<uint8_t>& keySetId); - - virtual status_t removeKeys(const Vector<uint8_t>& sessionId) { - if (sessionId.size() == 0) { - return android::BAD_VALUE; - } - - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t restoreKeys( - const Vector<uint8_t>& sessionId, - const Vector<uint8_t>& keySetId) { - if (sessionId.size() == 0 || keySetId.size() == 0) { - return android::BAD_VALUE; - } - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t queryKeyStatus( - const Vector<uint8_t>& sessionId, - KeyedVector<String8, String8>& infoMap) const; - - virtual status_t getProvisionRequest( - const String8& cert_type, - const String8& cert_authority, - Vector<uint8_t>& request, - String8& defaultUrl) { - UNUSED(cert_type); - UNUSED(cert_authority); - UNUSED(request); - UNUSED(defaultUrl); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t provideProvisionResponse( - const Vector<uint8_t>& response, - Vector<uint8_t>& certificate, - Vector<uint8_t>& wrappedKey) { - UNUSED(certificate); - UNUSED(wrappedKey); - if (response.size() == 0) { - // empty response - return android::BAD_VALUE; - } - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t getSecureStops(List<Vector<uint8_t> >& secureStops) { - UNUSED(secureStops); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) { - if (ssid.size() == 0) { - return android::BAD_VALUE; - } - - UNUSED(secureStop); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t releaseSecureStops(const Vector<uint8_t>& ssRelease) { - if (ssRelease.size() == 0) { - return android::BAD_VALUE; - } - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t releaseAllSecureStops() { - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t getPropertyString( - const String8& name, String8& value) const; - - virtual status_t getPropertyByteArray( - const String8& name, Vector<uint8_t>& value) const { - UNUSED(name); - UNUSED(value); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t setPropertyString( - const String8& name, const String8& value) { - UNUSED(name); - UNUSED(value); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t setPropertyByteArray( - const String8& name, const Vector<uint8_t>& value) { - UNUSED(name); - UNUSED(value); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t setCipherAlgorithm( - const Vector<uint8_t>& sessionId, const String8& algorithm) { - if (sessionId.size() == 0 || algorithm.size() == 0) { - return android::BAD_VALUE; - } - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t setMacAlgorithm( - const Vector<uint8_t>& sessionId, const String8& algorithm) { - if (sessionId.size() == 0 || algorithm.size() == 0) { - return android::BAD_VALUE; - } - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t encrypt( - const Vector<uint8_t>& sessionId, - const Vector<uint8_t>& keyId, - const Vector<uint8_t>& input, - const Vector<uint8_t>& iv, - Vector<uint8_t>& output) { - if (sessionId.size() == 0 || keyId.size() == 0 || - input.size() == 0 || iv.size() == 0) { - return android::BAD_VALUE; - } - UNUSED(output); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t decrypt( - const Vector<uint8_t>& sessionId, - const Vector<uint8_t>& keyId, - const Vector<uint8_t>& input, - const Vector<uint8_t>& iv, - Vector<uint8_t>& output) { - if (sessionId.size() == 0 || keyId.size() == 0 || - input.size() == 0 || iv.size() == 0) { - return android::BAD_VALUE; - } - UNUSED(output); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t sign( - const Vector<uint8_t>& sessionId, - const Vector<uint8_t>& keyId, - const Vector<uint8_t>& message, - Vector<uint8_t>& signature) { - if (sessionId.size() == 0 || keyId.size() == 0 || - message.size() == 0) { - return android::BAD_VALUE; - } - UNUSED(signature); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t verify( - const Vector<uint8_t>& sessionId, - const Vector<uint8_t>& keyId, - const Vector<uint8_t>& message, - const Vector<uint8_t>& signature, bool& match) { - if (sessionId.size() == 0 || keyId.size() == 0 || - message.size() == 0 || signature.size() == 0) { - return android::BAD_VALUE; - } - UNUSED(match); - return android::ERROR_DRM_CANNOT_HANDLE; - } - - virtual status_t signRSA( - const Vector<uint8_t>& sessionId, - const String8& algorithm, - const Vector<uint8_t>& message, - const Vector<uint8_t>& wrappedKey, - Vector<uint8_t>& signature) { - if (sessionId.size() == 0 || algorithm.size() == 0 || - message.size() == 0 || wrappedKey.size() == 0) { - return android::BAD_VALUE; - } - UNUSED(signature); - return android::ERROR_DRM_CANNOT_HANDLE; - } - -private: - void setPlayPolicy(); - - android::KeyedVector<android::String8, android::String8> mPlayPolicy; - SessionLibrary* mSessionLibrary; - - DISALLOW_EVIL_CONSTRUCTORS(DrmPlugin); -}; - -} // namespace clearkeydrm - -#endif // CLEARKEY_DRM_PLUGIN_H_
diff --git a/drm/mediadrm/plugins/clearkey/Session.cpp b/drm/mediadrm/plugins/clearkey/Session.cpp deleted file mode 100644 index d210f5e..0000000 --- a/drm/mediadrm/plugins/clearkey/Session.cpp +++ /dev/null
@@ -1,85 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "ClearKeyCryptoPlugin" -#include <utils/Log.h> - -#include <media/stagefright/MediaErrors.h> -#include <utils/String8.h> - -#include "Session.h" - -#include "AesCtrDecryptor.h" -#include "InitDataParser.h" -#include "JsonWebKey.h" - -namespace clearkeydrm { - -using android::Mutex; -using android::String8; -using android::Vector; -using android::status_t; - -status_t Session::getKeyRequest( - const Vector<uint8_t>& initData, - const String8& mimeType, - Vector<uint8_t>* keyRequest) const { - InitDataParser parser; - return parser.parse(initData, mimeType, keyRequest); -} - -status_t Session::provideKeyResponse(const Vector<uint8_t>& response) { - String8 responseString( - reinterpret_cast<const char*>(response.array()), response.size()); - KeyMap keys; - - Mutex::Autolock lock(mMapLock); - JsonWebKey parser; - if (parser.extractKeysFromJsonWebKeySet(responseString, &keys)) { - for (size_t i = 0; i < keys.size(); ++i) { - const KeyMap::key_type& keyId = keys.keyAt(i); - const KeyMap::value_type& key = keys.valueAt(i); - mKeyMap.add(keyId, key); - } - return android::OK; - } else { - return android::ERROR_DRM_UNKNOWN; - } -} - -status_t Session::decrypt( - const KeyId keyId, const Iv iv, const void* source, - void* destination, const SubSample* subSamples, - size_t numSubSamples, size_t* bytesDecryptedOut) { - Mutex::Autolock lock(mMapLock); - - Vector<uint8_t> keyIdVector; - keyIdVector.appendArray(keyId, kBlockSize); - if (mKeyMap.indexOfKey(keyIdVector) < 0) { - return android::ERROR_DRM_NO_LICENSE; - } - - const Vector<uint8_t>& key = mKeyMap.valueFor(keyIdVector); - AesCtrDecryptor decryptor; - return decryptor.decrypt( - key, iv, - reinterpret_cast<const uint8_t*>(source), - reinterpret_cast<uint8_t*>(destination), subSamples, - numSubSamples, bytesDecryptedOut); -} - -} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp b/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp deleted file mode 100644 index 0419f97..0000000 --- a/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp +++ /dev/null
@@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "ClearKeyCryptoPlugin" -#include <utils/Log.h> - -#include <utils/String8.h> - -#include "SessionLibrary.h" - -namespace clearkeydrm { - -using android::Mutex; -using android::sp; -using android::String8; -using android::Vector; - -Mutex SessionLibrary::sSingletonLock; -SessionLibrary* SessionLibrary::sSingleton = NULL; - -SessionLibrary* SessionLibrary::get() { - Mutex::Autolock lock(sSingletonLock); - - if (sSingleton == NULL) { - ALOGD("Instantiating Session Library Singleton."); - sSingleton = new SessionLibrary(); - } - - return sSingleton; -} - -sp<Session> SessionLibrary::createSession() { - Mutex::Autolock lock(mSessionsLock); - - String8 sessionIdString = String8::format("%u", mNextSessionId); - mNextSessionId += 1; - Vector<uint8_t> sessionId; - sessionId.appendArray( - reinterpret_cast<const uint8_t*>(sessionIdString.string()), - sessionIdString.size()); - - mSessions.add(sessionId, new Session(sessionId)); - return mSessions.valueFor(sessionId); -} - -sp<Session> SessionLibrary::findSession( - const Vector<uint8_t>& sessionId) { - Mutex::Autolock lock(mSessionsLock); - if (mSessions.indexOfKey(sessionId) < 0) { - return sp<Session>(NULL); - } - return mSessions.valueFor(sessionId); -} - -void SessionLibrary::destroySession(const sp<Session>& session) { - Mutex::Autolock lock(mSessionsLock);\ - mSessions.removeItem(session->sessionId()); -} - -} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/common/Android.bp b/drm/mediadrm/plugins/clearkey/common/Android.bp new file mode 100644 index 0000000..2c674e1 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/common/Android.bp
@@ -0,0 +1,38 @@ +// +// 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. +// + +cc_library_static { + name: "libclearkeycommon", + vendor: true, + + srcs: [ + "ClearKeyUUID.cpp", + "Utils.cpp", + ], + + cflags: ["-Wall", "-Werror"], + + include_dirs: ["frameworks/av/include"], + + shared_libs: ["libutils"], + + export_include_dirs: ["include"], + + sanitize: { + integer_overflow: true, + }, +} +
diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyUUID.cpp b/drm/mediadrm/plugins/clearkey/common/ClearKeyUUID.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/ClearKeyUUID.cpp rename to drm/mediadrm/plugins/clearkey/common/ClearKeyUUID.cpp
diff --git a/drm/mediadrm/plugins/clearkey/Utils.cpp b/drm/mediadrm/plugins/clearkey/common/Utils.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/Utils.cpp rename to drm/mediadrm/plugins/clearkey/common/Utils.cpp
diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyUUID.h b/drm/mediadrm/plugins/clearkey/common/include/ClearKeyUUID.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/ClearKeyUUID.h rename to drm/mediadrm/plugins/clearkey/common/include/ClearKeyUUID.h
diff --git a/drm/mediadrm/plugins/clearkey/MimeType.h b/drm/mediadrm/plugins/clearkey/common/include/MimeType.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/MimeType.h rename to drm/mediadrm/plugins/clearkey/common/include/MimeType.h
diff --git a/drm/mediadrm/plugins/clearkey/Utils.h b/drm/mediadrm/plugins/clearkey/common/include/Utils.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/Utils.h rename to drm/mediadrm/plugins/clearkey/common/include/Utils.h
diff --git a/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.cpp b/drm/mediadrm/plugins/clearkey/default/AesCtrDecryptor.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/AesCtrDecryptor.cpp rename to drm/mediadrm/plugins/clearkey/default/AesCtrDecryptor.cpp
diff --git a/drm/mediadrm/plugins/clearkey/default/Android.bp b/drm/mediadrm/plugins/clearkey/default/Android.bp new file mode 100644 index 0000000..7ba5708 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/Android.bp
@@ -0,0 +1,67 @@ +// +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_library_shared { + name: "libdrmclearkeyplugin", + vendor: true, + + srcs: [ + "AesCtrDecryptor.cpp", + "CreatePluginFactories.cpp", + "CryptoFactory.cpp", + "CryptoPlugin.cpp", + "DrmFactory.cpp", + "DrmPlugin.cpp", + "InitDataParser.cpp", + "JsonWebKey.cpp", + "Session.cpp", + "SessionLibrary.cpp", + ], + + relative_install_path: "mediadrm", + + cflags: ["-Wall", "-Werror"], + + shared_libs: [ + "libcrypto", + "liblog", + "libstagefright_foundation", + "libutils", + ], + + static_libs: [ + "libclearkeycommon", + "libjsmn" + ], + + local_include_dirs: ["include"], + export_include_dirs: ["include"], + export_static_lib_headers: ["libjsmn"], + + include_dirs: [ + "frameworks/native/include", + "frameworks/av/include", + ], + + sanitize: { + integer_overflow: true, + }, +} + +//######################################################################## +// Build unit tests + +subdirs = ["tests"]
diff --git a/drm/mediadrm/plugins/clearkey/CreatePluginFactories.cpp b/drm/mediadrm/plugins/clearkey/default/CreatePluginFactories.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/CreatePluginFactories.cpp rename to drm/mediadrm/plugins/clearkey/default/CreatePluginFactories.cpp
diff --git a/drm/mediadrm/plugins/clearkey/default/CryptoFactory.cpp b/drm/mediadrm/plugins/clearkey/default/CryptoFactory.cpp new file mode 100644 index 0000000..f15f92b --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/CryptoFactory.cpp
@@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ClearKeyCryptoFactory" +#include <utils/Log.h> + +#include <utils/Errors.h> +#include <utils/StrongPointer.h> + +#include "CryptoFactory.h" + +#include "ClearKeyUUID.h" +#include "CryptoPlugin.h" +#include "Session.h" +#include "SessionLibrary.h" + +namespace clearkeydrm { + +bool CryptoFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) const { + return isClearKeyUUID(uuid); +} + +android::status_t CryptoFactory::createPlugin( + const uint8_t uuid[16], + const void* data, size_t size, + android::CryptoPlugin** plugin) { + if (!isCryptoSchemeSupported(uuid)) { + *plugin = NULL; + return android::BAD_VALUE; + } + + android::Vector<uint8_t> sessionId; + sessionId.appendArray(reinterpret_cast<const uint8_t*>(data), size); + + CryptoPlugin *clearKeyPlugin = new CryptoPlugin(sessionId); + android::status_t result = clearKeyPlugin->getInitStatus(); + if (result == android::OK) { + *plugin = clearKeyPlugin; + } else { + delete clearKeyPlugin; + *plugin = NULL; + } + return result; +} + +} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp b/drm/mediadrm/plugins/clearkey/default/CryptoPlugin.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp rename to drm/mediadrm/plugins/clearkey/default/CryptoPlugin.cpp
diff --git a/drm/mediadrm/plugins/clearkey/default/DrmFactory.cpp b/drm/mediadrm/plugins/clearkey/default/DrmFactory.cpp new file mode 100644 index 0000000..8301e40 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/DrmFactory.cpp
@@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ClearKeyDrmFactory" +#include <utils/Log.h> + +#include <utils/Errors.h> + +#include "DrmFactory.h" + +#include "DrmPlugin.h" +#include "ClearKeyUUID.h" +#include "MimeType.h" +#include "SessionLibrary.h" + +namespace clearkeydrm { + +bool DrmFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) { + return isClearKeyUUID(uuid); +} + +bool DrmFactory::isContentTypeSupported(const android::String8 &type) { + // This should match the types handed by InitDataParser. + return type == kIsoBmffVideoMimeType || + type == kIsoBmffAudioMimeType || + type == kCencInitDataFormat || + type == kWebmVideoMimeType || + type == kWebmAudioMimeType || + type == kWebmInitDataFormat; +} + +android::status_t DrmFactory::createDrmPlugin( + const uint8_t uuid[16], + android::DrmPlugin** plugin) { + if (!isCryptoSchemeSupported(uuid)) { + *plugin = NULL; + return android::BAD_VALUE; + } + + *plugin = new DrmPlugin(SessionLibrary::get()); + return android::OK; +} + +} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/default/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/default/DrmPlugin.cpp new file mode 100644 index 0000000..1b8b8c1 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/DrmPlugin.cpp
@@ -0,0 +1,214 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ClearKeyDrmPlugin" +#include <utils/Log.h> + +#include <media/stagefright/MediaErrors.h> +#include <utils/StrongPointer.h> + +#include "DrmPlugin.h" +#include "ClearKeyDrmProperties.h" +#include "Session.h" + +namespace { +const android::String8 kStreaming("Streaming"); +const android::String8 kOffline("Offline"); +const android::String8 kTrue("True"); + +const android::String8 kQueryKeyLicenseType("LicenseType"); + // Value: "Streaming" or "Offline" +const android::String8 kQueryKeyPlayAllowed("PlayAllowed"); + // Value: "True" or "False" +const android::String8 kQueryKeyRenewAllowed("RenewAllowed"); + // Value: "True" or "False" +}; + +namespace clearkeydrm { + +using android::sp; + +DrmPlugin::DrmPlugin(SessionLibrary* sessionLibrary) + : mSessionLibrary(sessionLibrary) { + + mPlayPolicy.clear(); + initProperties(); +} + +void DrmPlugin::initProperties() { + mStringProperties.clear(); + mStringProperties.add(kVendorKey, kVendorValue); + mStringProperties.add(kVersionKey, kVersionValue); + mStringProperties.add(kPluginDescriptionKey, kPluginDescriptionValue); + mStringProperties.add(kAlgorithmsKey, kAlgorithmsValue); + mStringProperties.add(kListenerTestSupportKey, kListenerTestSupportValue); + + Vector<uint8_t> testDeviceId; + testDeviceId.appendArray(kTestDeviceIdData, sizeof(kTestDeviceIdData) / sizeof(uint8_t)); + mByteArrayProperties.add(kDeviceIdKey, testDeviceId); +} + +status_t DrmPlugin::openSession(Vector<uint8_t>& sessionId) { + sp<Session> session = mSessionLibrary->createSession(); + sessionId = session->sessionId(); + return android::OK; +} + +status_t DrmPlugin::closeSession(const Vector<uint8_t>& sessionId) { + sp<Session> session = mSessionLibrary->findSession(sessionId); + if (sessionId.size() == 0) { + return android::BAD_VALUE; + } + if (session.get()) { + mSessionLibrary->destroySession(session); + return android::OK; + } + return android::ERROR_DRM_SESSION_NOT_OPENED; +} + +status_t DrmPlugin::getKeyRequest( + const Vector<uint8_t>& scope, + const Vector<uint8_t>& initData, + const String8& mimeType, + KeyType keyType, + const KeyedVector<String8, String8>& optionalParameters, + Vector<uint8_t>& request, + String8& defaultUrl, + DrmPlugin::KeyRequestType *keyRequestType) { + UNUSED(optionalParameters); + if (scope.size() == 0) { + return android::BAD_VALUE; + } + + if (keyType != kKeyType_Streaming) { + return android::ERROR_DRM_CANNOT_HANDLE; + } + + *keyRequestType = DrmPlugin::kKeyRequestType_Initial; + defaultUrl.clear(); + sp<Session> session = mSessionLibrary->findSession(scope); + if (!session.get()) { + return android::ERROR_DRM_SESSION_NOT_OPENED; + } + + return session->getKeyRequest(initData, mimeType, &request); +} + +void DrmPlugin::setPlayPolicy() { + mPlayPolicy.clear(); + mPlayPolicy.add(kQueryKeyLicenseType, kStreaming); + mPlayPolicy.add(kQueryKeyPlayAllowed, kTrue); + mPlayPolicy.add(kQueryKeyRenewAllowed, kTrue); +} + +status_t DrmPlugin::provideKeyResponse( + const Vector<uint8_t>& scope, + const Vector<uint8_t>& response, + Vector<uint8_t>& keySetId) { + if (scope.size() == 0 || response.size() == 0) { + return android::BAD_VALUE; + } + sp<Session> session = mSessionLibrary->findSession(scope); + if (!session.get()) { + return android::ERROR_DRM_SESSION_NOT_OPENED; + } + + setPlayPolicy(); + status_t res = session->provideKeyResponse(response); + if (res == android::OK) { + // This is for testing AMediaDrm_setOnEventListener only. + sendEvent(kDrmPluginEventVendorDefined, 0, &scope, NULL); + keySetId.clear(); + } + return res; +} + +status_t DrmPlugin::getPropertyByteArray( + const String8& name, Vector<uint8_t>& value) const { + ssize_t index = mByteArrayProperties.indexOfKey(name); + if (index < 0) { + ALOGE("App requested unknown property: %s", name.string()); + return android::ERROR_DRM_CANNOT_HANDLE; + } + value = mByteArrayProperties.valueAt(index); + return android::OK; +} + +status_t DrmPlugin::setPropertyByteArray( + const String8& name, const Vector<uint8_t>& value) +{ + UNUSED(value); + if (0 == name.compare(kDeviceIdKey)) { + ALOGD("Cannot set immutable property: %s", name.string()); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + // Setting of undefined properties is not supported + ALOGE("Failed to set property byte array, key=%s", name.string()); + return android::ERROR_DRM_CANNOT_HANDLE; +} + +status_t DrmPlugin::getPropertyString( + const String8& name, String8& value) const { + ssize_t index = mStringProperties.indexOfKey(name); + if (index < 0) { + ALOGE("App requested unknown property: %s", name.string()); + return android::ERROR_DRM_CANNOT_HANDLE; + } + value = mStringProperties.valueAt(index); + return android::OK; +} + +status_t DrmPlugin::setPropertyString( + const String8& name, const String8& value) { + String8 immutableKeys; + immutableKeys.appendFormat("%s,%s,%s,%s", + kAlgorithmsKey.string(), kPluginDescriptionKey.string(), + kVendorKey.string(), kVersionKey.string()); + if (immutableKeys.contains(name.string())) { + ALOGD("Cannot set immutable property: %s", name.string()); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + ssize_t index = mStringProperties.indexOfKey(name); + if (index < 0) { + ALOGE("Cannot set undefined property string, key=%s", name.string()); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + if (mStringProperties.add(name, value) < 0) { + ALOGE("Failed to set property string, key=%s", name.string()); + return android::ERROR_DRM_UNKNOWN; + } + return android::OK; +} + +status_t DrmPlugin::queryKeyStatus( + const Vector<uint8_t>& sessionId, + KeyedVector<String8, String8>& infoMap) const { + + if (sessionId.size() == 0) { + return android::BAD_VALUE; + } + + infoMap.clear(); + for (size_t i = 0; i < mPlayPolicy.size(); ++i) { + infoMap.add(mPlayPolicy.keyAt(i), mPlayPolicy.valueAt(i)); + } + return android::OK; +} +} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/InitDataParser.cpp b/drm/mediadrm/plugins/clearkey/default/InitDataParser.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/InitDataParser.cpp rename to drm/mediadrm/plugins/clearkey/default/InitDataParser.cpp
diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp b/drm/mediadrm/plugins/clearkey/default/JsonWebKey.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/JsonWebKey.cpp rename to drm/mediadrm/plugins/clearkey/default/JsonWebKey.cpp
diff --git a/drm/mediadrm/plugins/clearkey/default/Session.cpp b/drm/mediadrm/plugins/clearkey/default/Session.cpp new file mode 100644 index 0000000..b3ceaec --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/Session.cpp
@@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ClearKeySession" +#include <utils/Log.h> + +#include <media/stagefright/MediaErrors.h> +#include <utils/String8.h> + +#include "Session.h" + +#include "AesCtrDecryptor.h" +#include "InitDataParser.h" +#include "JsonWebKey.h" + +namespace clearkeydrm { + +using android::Mutex; +using android::String8; +using android::Vector; +using android::status_t; + +status_t Session::getKeyRequest( + const Vector<uint8_t>& initData, + const String8& mimeType, + Vector<uint8_t>* keyRequest) const { + InitDataParser parser; + return parser.parse(initData, mimeType, keyRequest); +} + +status_t Session::provideKeyResponse(const Vector<uint8_t>& response) { + String8 responseString( + reinterpret_cast<const char*>(response.array()), response.size()); + KeyMap keys; + + Mutex::Autolock lock(mMapLock); + JsonWebKey parser; + if (parser.extractKeysFromJsonWebKeySet(responseString, &keys)) { + for (size_t i = 0; i < keys.size(); ++i) { + const KeyMap::key_type& keyId = keys.keyAt(i); + const KeyMap::value_type& key = keys.valueAt(i); + mKeyMap.add(keyId, key); + } + return android::OK; + } else { + return android::ERROR_DRM_UNKNOWN; + } +} + +status_t Session::decrypt( + const KeyId keyId, const Iv iv, const void* source, + void* destination, const SubSample* subSamples, + size_t numSubSamples, size_t* bytesDecryptedOut) { + Mutex::Autolock lock(mMapLock); + + Vector<uint8_t> keyIdVector; + keyIdVector.appendArray(keyId, kBlockSize); + if (mKeyMap.indexOfKey(keyIdVector) < 0) { + return android::ERROR_DRM_NO_LICENSE; + } + + const Vector<uint8_t>& key = mKeyMap.valueFor(keyIdVector); + AesCtrDecryptor decryptor; + return decryptor.decrypt( + key, iv, + reinterpret_cast<const uint8_t*>(source), + reinterpret_cast<uint8_t*>(destination), subSamples, + numSubSamples, bytesDecryptedOut); +} + +} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/default/SessionLibrary.cpp b/drm/mediadrm/plugins/clearkey/default/SessionLibrary.cpp new file mode 100644 index 0000000..529230e --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/SessionLibrary.cpp
@@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ClearKeySessionLibrary" +#include <utils/Log.h> + +#include <utils/String8.h> + +#include "SessionLibrary.h" + +namespace clearkeydrm { + +using android::Mutex; +using android::sp; +using android::String8; +using android::Vector; + +Mutex SessionLibrary::sSingletonLock; +SessionLibrary* SessionLibrary::sSingleton = NULL; + +SessionLibrary* SessionLibrary::get() { + Mutex::Autolock lock(sSingletonLock); + + if (sSingleton == NULL) { + ALOGD("Instantiating Session Library Singleton."); + sSingleton = new SessionLibrary(); + } + + return sSingleton; +} + +sp<Session> SessionLibrary::createSession() { + Mutex::Autolock lock(mSessionsLock); + + String8 sessionIdString = String8::format("%u", mNextSessionId); + mNextSessionId += 1; + Vector<uint8_t> sessionId; + sessionId.appendArray( + reinterpret_cast<const uint8_t*>(sessionIdString.string()), + sessionIdString.size()); + + mSessions.add(sessionId, new Session(sessionId)); + return mSessions.valueFor(sessionId); +} + +sp<Session> SessionLibrary::findSession( + const Vector<uint8_t>& sessionId) { + Mutex::Autolock lock(mSessionsLock); + if (mSessions.indexOfKey(sessionId) < 0) { + return sp<Session>(NULL); + } + return mSessions.valueFor(sessionId); +} + +void SessionLibrary::destroySession(const sp<Session>& session) { + Mutex::Autolock lock(mSessionsLock);\ + mSessions.removeItem(session->sessionId()); +} + +} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.h b/drm/mediadrm/plugins/clearkey/default/include/AesCtrDecryptor.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/AesCtrDecryptor.h rename to drm/mediadrm/plugins/clearkey/default/include/AesCtrDecryptor.h
diff --git a/drm/mediadrm/plugins/clearkey/default/include/ClearKeyDrmProperties.h b/drm/mediadrm/plugins/clearkey/default/include/ClearKeyDrmProperties.h new file mode 100644 index 0000000..a99e174 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/include/ClearKeyDrmProperties.h
@@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CLEARKEY_DRM_PROPERTIES_H_ +#define CLEARKEY_DRM_PROPERTIES_H_ + +#include <utils/String8.h> + +namespace clearkeydrm { + +static const android::String8 kVendorKey("vendor"); +static const android::String8 kVendorValue("Google"); +static const android::String8 kVersionKey("version"); +static const android::String8 kVersionValue("1.0"); +static const android::String8 kPluginDescriptionKey("description"); +static const android::String8 kPluginDescriptionValue("ClearKey CDM"); +static const android::String8 kAlgorithmsKey("algorithms"); +static const android::String8 kAlgorithmsValue(""); +static const android::String8 kListenerTestSupportKey("listenerTestSupport"); +static const android::String8 kListenerTestSupportValue("true"); + +static const android::String8 kDeviceIdKey("deviceId"); +static const uint8_t kTestDeviceIdData[] = + {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; +} // namespace clearkeydrm + +#endif // CLEARKEY_DRM_PROPERTIES_H_
diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyTypes.h b/drm/mediadrm/plugins/clearkey/default/include/ClearKeyTypes.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/ClearKeyTypes.h rename to drm/mediadrm/plugins/clearkey/default/include/ClearKeyTypes.h
diff --git a/drm/mediadrm/plugins/clearkey/CreatePluginFactories.h b/drm/mediadrm/plugins/clearkey/default/include/CreatePluginFactories.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/CreatePluginFactories.h rename to drm/mediadrm/plugins/clearkey/default/include/CreatePluginFactories.h
diff --git a/drm/mediadrm/plugins/clearkey/CryptoFactory.h b/drm/mediadrm/plugins/clearkey/default/include/CryptoFactory.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/CryptoFactory.h rename to drm/mediadrm/plugins/clearkey/default/include/CryptoFactory.h
diff --git a/drm/mediadrm/plugins/clearkey/CryptoPlugin.h b/drm/mediadrm/plugins/clearkey/default/include/CryptoPlugin.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/CryptoPlugin.h rename to drm/mediadrm/plugins/clearkey/default/include/CryptoPlugin.h
diff --git a/drm/mediadrm/plugins/clearkey/DrmFactory.h b/drm/mediadrm/plugins/clearkey/default/include/DrmFactory.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/DrmFactory.h rename to drm/mediadrm/plugins/clearkey/default/include/DrmFactory.h
diff --git a/drm/mediadrm/plugins/clearkey/default/include/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/default/include/DrmPlugin.h new file mode 100644 index 0000000..4fa42e5 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/include/DrmPlugin.h
@@ -0,0 +1,276 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CLEARKEY_DRM_PLUGIN_H_ +#define CLEARKEY_DRM_PLUGIN_H_ + +#include <media/drm/DrmAPI.h> +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/MediaErrors.h> +#include <utils/Errors.h> +#include <utils/KeyedVector.h> +#include <utils/List.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +#include "SessionLibrary.h" +#include "Utils.h" + +namespace clearkeydrm { + +using android::KeyedVector; +using android::List; +using android::status_t; +using android::String8; +using android::Vector; + +class DrmPlugin : public android::DrmPlugin { +public: + explicit DrmPlugin(SessionLibrary* sessionLibrary); + + virtual ~DrmPlugin() {} + + virtual status_t openSession(Vector<uint8_t>& sessionId); + + virtual status_t closeSession(const Vector<uint8_t>& sessionId); + + virtual status_t getKeyRequest( + const Vector<uint8_t>& scope, + const Vector<uint8_t>& mimeType, + const String8& initDataType, + KeyType keyType, + const KeyedVector<String8, String8>& optionalParameters, + Vector<uint8_t>& request, + String8& defaultUrl, + DrmPlugin::KeyRequestType *keyRequestType); + + virtual status_t provideKeyResponse( + const Vector<uint8_t>& scope, + const Vector<uint8_t>& response, + Vector<uint8_t>& keySetId); + + virtual status_t removeKeys(const Vector<uint8_t>& sessionId) { + if (sessionId.size() == 0) { + return android::BAD_VALUE; + } + + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t restoreKeys( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keySetId) { + if (sessionId.size() == 0 || keySetId.size() == 0) { + return android::BAD_VALUE; + } + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t queryKeyStatus( + const Vector<uint8_t>& sessionId, + KeyedVector<String8, String8>& infoMap) const; + + virtual status_t getProvisionRequest( + const String8& cert_type, + const String8& cert_authority, + Vector<uint8_t>& request, + String8& defaultUrl) { + UNUSED(cert_type); + UNUSED(cert_authority); + UNUSED(request); + UNUSED(defaultUrl); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t provideProvisionResponse( + const Vector<uint8_t>& response, + Vector<uint8_t>& certificate, + Vector<uint8_t>& wrappedKey) { + UNUSED(certificate); + UNUSED(wrappedKey); + if (response.size() == 0) { + // empty response + return android::BAD_VALUE; + } + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t getSecureStops(List<Vector<uint8_t> >& secureStops) { + UNUSED(secureStops); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) { + if (ssid.size() == 0) { + return android::BAD_VALUE; + } + + UNUSED(secureStop); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t releaseSecureStops(const Vector<uint8_t>& ssRelease) { + if (ssRelease.size() == 0) { + return android::BAD_VALUE; + } + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t releaseAllSecureStops() { + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t getHdcpLevels(HdcpLevel *connectedLevel, + HdcpLevel *maxLevel) const { + UNUSED(connectedLevel); + UNUSED(maxLevel); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + + virtual status_t getNumberOfSessions(uint32_t *currentSessions, + uint32_t *maxSessions) const { + UNUSED(currentSessions); + UNUSED(maxSessions); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId, + SecurityLevel *level) const { + UNUSED(sessionId); + UNUSED(level); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId, + const SecurityLevel& level) { + UNUSED(sessionId); + UNUSED(level); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t getPropertyString( + const String8& name, String8& value) const; + + virtual status_t getPropertyByteArray( + const String8& name, Vector<uint8_t>& value) const; + + virtual status_t setPropertyString( + const String8& name, const String8& value); + + virtual status_t setPropertyByteArray( + const String8& name, const Vector<uint8_t>& value); + + virtual status_t setCipherAlgorithm( + const Vector<uint8_t>& sessionId, const String8& algorithm) { + if (sessionId.size() == 0 || algorithm.size() == 0) { + return android::BAD_VALUE; + } + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t setMacAlgorithm( + const Vector<uint8_t>& sessionId, const String8& algorithm) { + if (sessionId.size() == 0 || algorithm.size() == 0) { + return android::BAD_VALUE; + } + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t encrypt( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keyId, + const Vector<uint8_t>& input, + const Vector<uint8_t>& iv, + Vector<uint8_t>& output) { + if (sessionId.size() == 0 || keyId.size() == 0 || + input.size() == 0 || iv.size() == 0) { + return android::BAD_VALUE; + } + UNUSED(output); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t decrypt( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keyId, + const Vector<uint8_t>& input, + const Vector<uint8_t>& iv, + Vector<uint8_t>& output) { + if (sessionId.size() == 0 || keyId.size() == 0 || + input.size() == 0 || iv.size() == 0) { + return android::BAD_VALUE; + } + UNUSED(output); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t sign( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keyId, + const Vector<uint8_t>& message, + Vector<uint8_t>& signature) { + if (sessionId.size() == 0 || keyId.size() == 0 || + message.size() == 0) { + return android::BAD_VALUE; + } + UNUSED(signature); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t verify( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keyId, + const Vector<uint8_t>& message, + const Vector<uint8_t>& signature, bool& match) { + if (sessionId.size() == 0 || keyId.size() == 0 || + message.size() == 0 || signature.size() == 0) { + return android::BAD_VALUE; + } + UNUSED(match); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t signRSA( + const Vector<uint8_t>& sessionId, + const String8& algorithm, + const Vector<uint8_t>& message, + const Vector<uint8_t>& wrappedKey, + Vector<uint8_t>& signature) { + if (sessionId.size() == 0 || algorithm.size() == 0 || + message.size() == 0 || wrappedKey.size() == 0) { + return android::BAD_VALUE; + } + UNUSED(signature); + return android::ERROR_DRM_CANNOT_HANDLE; + } + +private: + void initProperties(); + void setPlayPolicy(); + + android::KeyedVector<String8, String8> mPlayPolicy; + android::KeyedVector<String8, String8> mStringProperties; + android::KeyedVector<String8, Vector<uint8_t>> mByteArrayProperties; + + SessionLibrary* mSessionLibrary; + + DISALLOW_EVIL_CONSTRUCTORS(DrmPlugin); +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_DRM_PLUGIN_H_
diff --git a/drm/mediadrm/plugins/clearkey/InitDataParser.h b/drm/mediadrm/plugins/clearkey/default/include/InitDataParser.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/InitDataParser.h rename to drm/mediadrm/plugins/clearkey/default/include/InitDataParser.h
diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.h b/drm/mediadrm/plugins/clearkey/default/include/JsonWebKey.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/JsonWebKey.h rename to drm/mediadrm/plugins/clearkey/default/include/JsonWebKey.h
diff --git a/drm/mediadrm/plugins/clearkey/Session.h b/drm/mediadrm/plugins/clearkey/default/include/Session.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/Session.h rename to drm/mediadrm/plugins/clearkey/default/include/Session.h
diff --git a/drm/mediadrm/plugins/clearkey/SessionLibrary.h b/drm/mediadrm/plugins/clearkey/default/include/SessionLibrary.h similarity index 100% rename from drm/mediadrm/plugins/clearkey/SessionLibrary.h rename to drm/mediadrm/plugins/clearkey/default/include/SessionLibrary.h
diff --git a/drm/mediadrm/plugins/clearkey/tests/AesCtrDecryptorUnittest.cpp b/drm/mediadrm/plugins/clearkey/default/tests/AesCtrDecryptorUnittest.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/tests/AesCtrDecryptorUnittest.cpp rename to drm/mediadrm/plugins/clearkey/default/tests/AesCtrDecryptorUnittest.cpp
diff --git a/drm/mediadrm/plugins/clearkey/default/tests/Android.bp b/drm/mediadrm/plugins/clearkey/default/tests/Android.bp new file mode 100644 index 0000000..4419865 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/default/tests/Android.bp
@@ -0,0 +1,42 @@ +// +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ---------------------------------------------------------------- +// Builds ClearKey Drm Tests +// + +cc_test { + name: "ClearKeyDrmUnitTest", + vendor: true, + + cflags: ["-Wall", "-Werror"], + + srcs: [ + "AesCtrDecryptorUnittest.cpp", + "InitDataParserUnittest.cpp", + "JsonWebKeyUnittest.cpp", + ], + + static_libs: ["libclearkeycommon"], + + shared_libs: [ + "libcrypto", + "libdrmclearkeyplugin", + "liblog", + "libstagefright_foundation", + "libutils", + ], + header_libs: ["media_plugin_headers"], +}
diff --git a/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp b/drm/mediadrm/plugins/clearkey/default/tests/InitDataParserUnittest.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp rename to drm/mediadrm/plugins/clearkey/default/tests/InitDataParserUnittest.cpp
diff --git a/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp b/drm/mediadrm/plugins/clearkey/default/tests/JsonWebKeyUnittest.cpp similarity index 100% rename from drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp rename to drm/mediadrm/plugins/clearkey/default/tests/JsonWebKeyUnittest.cpp
diff --git a/drm/mediadrm/plugins/clearkey/hidl/AesCtrDecryptor.cpp b/drm/mediadrm/plugins/clearkey/hidl/AesCtrDecryptor.cpp new file mode 100644 index 0000000..2fce0790 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/AesCtrDecryptor.cpp
@@ -0,0 +1,86 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "hidl_ClearkeyDecryptor" +#include <utils/Log.h> + +#include <openssl/aes.h> + +#include "AesCtrDecryptor.h" +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::SubSample; +using ::android::hardware::drm::V1_0::Status; + +static const size_t kBlockBitCount = kBlockSize * 8; + +Status AesCtrDecryptor::decrypt( + const std::vector<uint8_t>& key, + const Iv iv, const uint8_t* source, + uint8_t* destination, + const std::vector<SubSample> subSamples, + size_t numSubSamples, + size_t* bytesDecryptedOut) { + uint32_t blockOffset = 0; + uint8_t previousEncryptedCounter[kBlockSize]; + memset(previousEncryptedCounter, 0, kBlockSize); + + if (key.size() != kBlockSize || (sizeof(Iv) / sizeof(uint8_t)) != kBlockSize) { + android_errorWriteLog(0x534e4554, "63982768"); + return Status::ERROR_DRM_DECRYPT; + } + + size_t offset = 0; + AES_KEY opensslKey; + AES_set_encrypt_key(key.data(), kBlockBitCount, &opensslKey); + Iv opensslIv; + memcpy(opensslIv, iv, sizeof(opensslIv)); + + for (size_t i = 0; i < numSubSamples; ++i) { + const SubSample& subSample = subSamples[i]; + + if (subSample.numBytesOfClearData > 0) { + memcpy(destination + offset, source + offset, + subSample.numBytesOfClearData); + offset += subSample.numBytesOfClearData; + } + + if (subSample.numBytesOfEncryptedData > 0) { + AES_ctr128_encrypt(source + offset, destination + offset, + subSample.numBytesOfEncryptedData, &opensslKey, + opensslIv, previousEncryptedCounter, + &blockOffset); + offset += subSample.numBytesOfEncryptedData; + } + } + + *bytesDecryptedOut = offset; + return Status::OK; +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android +
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Android.bp b/drm/mediadrm/plugins/clearkey/hidl/Android.bp new file mode 100644 index 0000000..341d4f6 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/Android.bp
@@ -0,0 +1,68 @@ +// +// 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. +// + +cc_binary { + name: "android.hardware.drm@1.1-service.clearkey", + vendor: true, + + srcs: [ + "AesCtrDecryptor.cpp", + "Base64.cpp", + "Buffer.cpp", + "CreatePluginFactories.cpp", + "CryptoFactory.cpp", + "CryptoPlugin.cpp", + "DrmFactory.cpp", + "DrmPlugin.cpp", + "InitDataParser.cpp", + "JsonWebKey.cpp", + "Session.cpp", + "SessionLibrary.cpp", + "service.cpp", + ], + + relative_install_path: "hw", + + cflags: ["-Wall", "-Werror"], + init_rc: ["android.hardware.drm@1.1-service.clearkey.rc"], + + shared_libs: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "libbase", + "libbinder", + "libcrypto", + "libhidlbase", + "libhidlmemory", + "libhidltransport", + "liblog", + "libutils", + ], + + static_libs: [ + "libclearkeycommon", + "libjsmn", + ], + + local_include_dirs: ["include"], + + export_static_lib_headers: ["libjsmn"], + + sanitize: { + integer_overflow: true, + }, +} +
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Base64.cpp b/drm/mediadrm/plugins/clearkey/hidl/Base64.cpp new file mode 100644 index 0000000..c2ed751 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/Base64.cpp
@@ -0,0 +1,175 @@ +/* + * 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. + */ + +#include "Base64.h" + +#include <string> + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +sp<Buffer> decodeBase64(const std::string &s) { + size_t n = s.size(); + + if ((n % 4) != 0) { + return nullptr; + } + + size_t padding = 0; + if (n >= 1 && s.c_str()[n - 1] == '=') { + padding = 1; + + if (n >= 2 && s.c_str()[n - 2] == '=') { + padding = 2; + + if (n >= 3 && s.c_str()[n - 3] == '=') { + padding = 3; + } + } + } + + // We divide first to avoid overflow. It's OK to do this because we + // already made sure that n % 4 == 0. + size_t outLen = (n / 4) * 3 - padding; + + sp<Buffer> buffer = new Buffer(outLen); + uint8_t *out = buffer->data(); + if (out == nullptr || buffer->size() < outLen) { + return nullptr; + } + + size_t j = 0; + uint32_t accum = 0; + for (size_t i = 0; i < n; ++i) { + char c = s.c_str()[i]; + unsigned value; + if (c >= 'A' && c <= 'Z') { + value = c - 'A'; + } else if (c >= 'a' && c <= 'z') { + value = 26 + c - 'a'; + } else if (c >= '0' && c <= '9') { + value = 52 + c - '0'; + } else if (c == '+' || c == '-') { + value = 62; + } else if (c == '/' || c == '_') { + value = 63; + } else if (c != '=') { + return nullptr; + } else { + if (i < n - padding) { + return nullptr; + } + + value = 0; + } + + accum = (accum << 6) | value; + + if (((i + 1) % 4) == 0) { + if (j < outLen) { out[j++] = (accum >> 16); } + if (j < outLen) { out[j++] = (accum >> 8) & 0xff; } + if (j < outLen) { out[j++] = accum & 0xff; } + + accum = 0; + } + } + + return buffer; +} + +static char encode6Bit(unsigned x) { + if (x <= 25) { + return 'A' + x; + } else if (x <= 51) { + return 'a' + x - 26; + } else if (x <= 61) { + return '0' + x - 52; + } else if (x == 62) { + return '+'; + } else { + return '/'; + } +} + +void encodeBase64(const void *_data, size_t size, std::string *out) { + out->clear(); + + const uint8_t *data = (const uint8_t *)_data; + + size_t i; + for (i = 0; i < (size / 3) * 3; i += 3) { + uint8_t x1 = data[i]; + uint8_t x2 = data[i + 1]; + uint8_t x3 = data[i + 2]; + + out->push_back(encode6Bit(x1 >> 2)); + out->push_back(encode6Bit((x1 << 4 | x2 >> 4) & 0x3f)); + out->push_back(encode6Bit((x2 << 2 | x3 >> 6) & 0x3f)); + out->push_back(encode6Bit(x3 & 0x3f)); + } + switch (size % 3) { + case 0: + break; + case 2: + { + uint8_t x1 = data[i]; + uint8_t x2 = data[i + 1]; + out->push_back(encode6Bit(x1 >> 2)); + out->push_back(encode6Bit((x1 << 4 | x2 >> 4) & 0x3f)); + out->push_back(encode6Bit((x2 << 2) & 0x3f)); + out->push_back('='); + break; + } + default: + { + uint8_t x1 = data[i]; + out->push_back(encode6Bit(x1 >> 2)); + out->push_back(encode6Bit((x1 << 4) & 0x3f)); + out->append("=="); + break; + } + } +} + +void encodeBase64Url(const void *_data, size_t size, std::string *out) { + encodeBase64(_data, size, out); + + if ((std::string::npos != out->find("+")) || + (std::string::npos != out->find("/"))) { + size_t outLen = out->size(); + char *base64url = new char[outLen]; + for (size_t i = 0; i < outLen; ++i) { + if (out->c_str()[i] == '+') + base64url[i] = '-'; + else if (out->c_str()[i] == '/') + base64url[i] = '_'; + else + base64url[i] = out->c_str()[i]; + } + + out->assign(base64url, outLen); + delete[] base64url; + } +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Buffer.cpp b/drm/mediadrm/plugins/clearkey/hidl/Buffer.cpp new file mode 100644 index 0000000..e58f58a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/Buffer.cpp
@@ -0,0 +1,53 @@ +/* + * 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. + */ + +#include "Buffer.h" + +#include <android/hardware/drm/1.0/types.h> + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +Buffer::Buffer(size_t capacity) + : mRangeOffset(0), + mOwnsData(true) { + mData = malloc(capacity); + if (mData == nullptr) { + mCapacity = 0; + mRangeLength = 0; + } else { + mCapacity = capacity; + mRangeLength = capacity; + } +} + +Buffer::~Buffer() { + if (mOwnsData) { + if (mData != nullptr) { + free(mData); + mData = nullptr; + } + } +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/CreatePluginFactories.cpp b/drm/mediadrm/plugins/clearkey/hidl/CreatePluginFactories.cpp new file mode 100644 index 0000000..1ba5c6a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/CreatePluginFactories.cpp
@@ -0,0 +1,44 @@ +/* + * 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. + */ + +#include "CreatePluginFactories.h" + +#include "CryptoFactory.h" +#include "DrmFactory.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +extern "C" { + +IDrmFactory* createDrmFactory() { + return new DrmFactory(); +} + +ICryptoFactory* createCryptoFactory() { + return new CryptoFactory(); +} + +} // extern "C" + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/CryptoFactory.cpp b/drm/mediadrm/plugins/clearkey/hidl/CryptoFactory.cpp new file mode 100644 index 0000000..0848cef --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/CryptoFactory.cpp
@@ -0,0 +1,67 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "hidl_ClearKeyCryptoFactory" +#include <utils/Log.h> + +#include "CryptoFactory.h" + +#include "ClearKeyUUID.h" +#include "CryptoPlugin.h" +#include "TypeConvert.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +Return<bool> CryptoFactory::isCryptoSchemeSupported( + const hidl_array<uint8_t, 16> &uuid) +{ + return clearkeydrm::isClearKeyUUID(uuid.data()); +} + +Return<void> CryptoFactory::createPlugin( + const hidl_array<uint8_t, 16> &uuid, + const hidl_vec<uint8_t> &initData, + createPlugin_cb _hidl_cb) { + + if (!isCryptoSchemeSupported(uuid.data())) { + ALOGE("Clearkey Drm HAL: failed to create clearkey plugin, " \ + "invalid crypto scheme"); + _hidl_cb(Status::BAD_VALUE, nullptr); + return Void(); + } + + CryptoPlugin *cryptoPlugin = new CryptoPlugin(initData); + Status status = cryptoPlugin->getInitStatus(); + if (status == Status::OK) { + _hidl_cb(Status::OK, cryptoPlugin); + } else { + delete cryptoPlugin; + _hidl_cb(status, nullptr); + } + return Void(); +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android +
diff --git a/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp new file mode 100644 index 0000000..cd2224d --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp
@@ -0,0 +1,197 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "hidl_ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include "CryptoPlugin.h" +#include "SessionLibrary.h" +#include "TypeConvert.h" + +#include <hidlmemory/mapping.h> + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::BufferType; + +Return<void> CryptoPlugin::setSharedBufferBase( + const hidl_memory& base, uint32_t bufferId) { + sp<IMemory> hidlMemory = mapMemory(base); + ALOGE_IF(hidlMemory == nullptr, "mapMemory returns nullptr"); + + // allow mapMemory to return nullptr + mSharedBufferMap[bufferId] = hidlMemory; + return Void(); +} + +// Returns negative values for error code and positive values for the size of +// decrypted data. In theory, the output size can be larger than the input +// size, but in practice this will never happen for AES-CTR. +Return<void> CryptoPlugin::decrypt( + bool secure, + const hidl_array<uint8_t, KEY_ID_SIZE>& keyId, + const hidl_array<uint8_t, KEY_IV_SIZE>& iv, + Mode mode, + const Pattern& pattern, + const hidl_vec<SubSample>& subSamples, + const SharedBuffer& source, + uint64_t offset, + const DestinationBuffer& destination, + decrypt_cb _hidl_cb) { + UNUSED(pattern); + + if (secure) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + "Secure decryption is not supported with ClearKey."); + return Void(); + } + + if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + "source decrypt buffer base not set"); + return Void(); + } + + if (destination.type == BufferType::SHARED_MEMORY) { + const SharedBuffer& dest = destination.nonsecureMemory; + if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + "destination decrypt buffer base not set"); + return Void(); + } + } else { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + "destination type not supported"); + return Void(); + } + + sp<IMemory> sourceBase = mSharedBufferMap[source.bufferId]; + if (sourceBase == nullptr) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "source is a nullptr"); + return Void(); + } + + if (source.offset + offset + source.size > sourceBase->getSize()) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size"); + return Void(); + } + + uint8_t *base = static_cast<uint8_t *> + (static_cast<void *>(sourceBase->getPointer())); + uint8_t* srcPtr = static_cast<uint8_t *>(base + source.offset + offset); + void* destPtr = NULL; + // destination.type == BufferType::SHARED_MEMORY + const SharedBuffer& destBuffer = destination.nonsecureMemory; + sp<IMemory> destBase = mSharedBufferMap[destBuffer.bufferId]; + if (destBase == nullptr) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "destination is a nullptr"); + return Void(); + } + + base = static_cast<uint8_t *>(static_cast<void *>(destBase->getPointer())); + + if (destBuffer.offset + destBuffer.size > destBase->getSize()) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size"); + return Void(); + } + destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset); + + // Calculate the output buffer size and determine if any subsamples are + // encrypted. + size_t destSize = 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)) { + _hidl_cb(Status::BAD_VALUE, 0, "subsample clear size overflow"); + return Void(); + } + if (__builtin_add_overflow(destSize, subSample.numBytesOfEncryptedData, &destSize)) { + _hidl_cb(Status::BAD_VALUE, 0, "subsample encrypted size overflow"); + return Void(); + } + if (subSample.numBytesOfEncryptedData > 0) { + haveEncryptedSubsamples = true; + } + } + + if (destSize > destBuffer.size) { + _hidl_cb(Status::BAD_VALUE, 0, "subsample sum too large"); + return Void(); + } + + if (mode == Mode::UNENCRYPTED) { + if (haveEncryptedSubsamples) { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + "Encrypted subsamples found in allegedly unencrypted data."); + return Void(); + } + + size_t offset = 0; + for (size_t i = 0; i < subSamples.size(); ++i) { + const SubSample& subSample = subSamples[i]; + if (subSample.numBytesOfClearData != 0) { + memcpy(reinterpret_cast<uint8_t*>(destPtr) + offset, + reinterpret_cast<const uint8_t*>(srcPtr) + offset, + subSample.numBytesOfClearData); + offset += subSample.numBytesOfClearData; + } + } + + _hidl_cb(Status::OK, static_cast<ssize_t>(offset), ""); + return Void(); + } else if (mode == Mode::AES_CTR) { + size_t bytesDecrypted; + Status res = mSession->decrypt(keyId.data(), iv.data(), srcPtr, + static_cast<uint8_t*>(destPtr), toVector(subSamples), &bytesDecrypted); + if (res == Status::OK) { + _hidl_cb(Status::OK, static_cast<ssize_t>(bytesDecrypted), ""); + return Void(); + } else { + _hidl_cb(Status::ERROR_DRM_DECRYPT, static_cast<ssize_t>(res), + "Decryption Error"); + return Void(); + } + } else { + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + "Selected encryption mode is not supported by the ClearKey DRM Plugin."); + return Void(); + } +} + +Return<Status> CryptoPlugin::setMediaDrmSession( + const hidl_vec<uint8_t>& sessionId) { + if (!sessionId.size()) { + mSession = nullptr; + } else { + mSession = SessionLibrary::get()->findSession(sessionId); + if (!mSession.get()) { + return Status::ERROR_DRM_SESSION_NOT_OPENED; + } + } + return Status::OK; +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmFactory.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmFactory.cpp new file mode 100644 index 0000000..77557f9 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/DrmFactory.cpp
@@ -0,0 +1,77 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "hidl_ClearKeyDrmFactory" +#include <utils/Log.h> + +#include <utils/Errors.h> + +#include "DrmFactory.h" + +#include "DrmPlugin.h" +#include "ClearKeyUUID.h" +#include "MimeType.h" +#include "SessionLibrary.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::Void; + +Return<bool> DrmFactory::isCryptoSchemeSupported( + const hidl_array<uint8_t, 16>& uuid) { + return clearkeydrm::isClearKeyUUID(uuid.data()); +} + +Return<bool> DrmFactory::isContentTypeSupported(const hidl_string &mimeType) { + // This should match the mimeTypes handed by InitDataParser. + return mimeType == kIsoBmffVideoMimeType || + mimeType == kIsoBmffAudioMimeType || + mimeType == kCencInitDataFormat || + mimeType == kWebmVideoMimeType || + mimeType == kWebmAudioMimeType || + mimeType == kWebmInitDataFormat; +} + +Return<void> DrmFactory::createPlugin( + const hidl_array<uint8_t, 16>& uuid, + const hidl_string& appPackageName, + createPlugin_cb _hidl_cb) { + UNUSED(appPackageName); + + DrmPlugin *plugin = NULL; + if (!isCryptoSchemeSupported(uuid.data())) { + ALOGE("Clear key Drm HAL: failed to create drm plugin, " \ + "invalid crypto scheme"); + _hidl_cb(Status::BAD_VALUE, plugin); + return Void(); + } + + plugin = new DrmPlugin(SessionLibrary::get()); + _hidl_cb(Status::OK, plugin); + return Void(); +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp new file mode 100644 index 0000000..30f7459 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
@@ -0,0 +1,578 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "hidl_ClearKeyPlugin" +#include <utils/Log.h> + +#include <stdio.h> +#include <inttypes.h> + +#include "DrmPlugin.h" +#include "ClearKeyDrmProperties.h" +#include "Session.h" +#include "TypeConvert.h" + +namespace { +const int kSecureStopIdStart = 100; +const std::string kStreaming("Streaming"); +const std::string kOffline("Offline"); +const std::string kTrue("True"); + +const std::string kQueryKeyLicenseType("LicenseType"); + // Value: "Streaming" or "Offline" +const std::string kQueryKeyPlayAllowed("PlayAllowed"); + // Value: "True" or "False" +const std::string kQueryKeyRenewAllowed("RenewAllowed"); + // Value: "True" or "False" + +const int kSecureStopIdSize = 10; + +std::vector<uint8_t> uint32ToVector(uint32_t value) { + // 10 bytes to display max value 4294967295 + one byte null terminator + char buffer[kSecureStopIdSize]; + memset(buffer, 0, kSecureStopIdSize); + snprintf(buffer, kSecureStopIdSize, "%" PRIu32, value); + return std::vector<uint8_t>(buffer, buffer + sizeof(buffer)); +} + +}; // unnamed namespace + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +DrmPlugin::DrmPlugin(SessionLibrary* sessionLibrary) + : mSessionLibrary(sessionLibrary), + mOpenSessionOkCount(0), + mCloseSessionOkCount(0), + mCloseSessionNotOpenedCount(0), + mNextSecureStopId(kSecureStopIdStart) { + mPlayPolicy.clear(); + initProperties(); + mSecureStops.clear(); +} + +void DrmPlugin::initProperties() { + mStringProperties.clear(); + mStringProperties[kVendorKey] = kVendorValue; + mStringProperties[kVersionKey] = kVersionValue; + mStringProperties[kPluginDescriptionKey] = kPluginDescriptionValue; + mStringProperties[kAlgorithmsKey] = kAlgorithmsValue; + mStringProperties[kListenerTestSupportKey] = kListenerTestSupportValue; + + std::vector<uint8_t> valueVector; + valueVector.clear(); + valueVector.insert(valueVector.end(), + kTestDeviceIdData, kTestDeviceIdData + sizeof(kTestDeviceIdData) / sizeof(uint8_t)); + mByteArrayProperties[kDeviceIdKey] = valueVector; + + valueVector.clear(); + valueVector.insert(valueVector.end(), + kMetricsData, kMetricsData + sizeof(kMetricsData) / sizeof(uint8_t)); + mByteArrayProperties[kMetricsKey] = valueVector; +} + +// The secure stop in ClearKey implementation is not installed securely. +// This function merely creates a test environment for testing secure stops APIs. +// The content in this secure stop is implementation dependent, the clearkey +// secureStop does not serve as a reference implementation. +void DrmPlugin::installSecureStop(const hidl_vec<uint8_t>& sessionId) { + ClearkeySecureStop clearkeySecureStop; + clearkeySecureStop.id = uint32ToVector(++mNextSecureStopId); + clearkeySecureStop.data.assign(sessionId.begin(), sessionId.end()); + + mSecureStops.insert(std::pair<std::vector<uint8_t>, ClearkeySecureStop>( + clearkeySecureStop.id, clearkeySecureStop)); +} + +Return<void> DrmPlugin::openSession(openSession_cb _hidl_cb) { + sp<Session> session = mSessionLibrary->createSession(); + std::vector<uint8_t> sessionId = session->sessionId(); + + Status status = setSecurityLevel(sessionId, SecurityLevel::SW_SECURE_CRYPTO); + _hidl_cb(status, toHidlVec(sessionId)); + mOpenSessionOkCount++; + return Void(); +} + +Return<void> DrmPlugin::openSession_1_1(SecurityLevel securityLevel, + openSession_1_1_cb _hidl_cb) { + sp<Session> session = mSessionLibrary->createSession(); + std::vector<uint8_t> sessionId = session->sessionId(); + + Status status = setSecurityLevel(sessionId, securityLevel); + _hidl_cb(status, toHidlVec(sessionId)); + mOpenSessionOkCount++; + return Void(); +} + +Return<Status> DrmPlugin::closeSession(const hidl_vec<uint8_t>& sessionId) { + if (sessionId.size() == 0) { + return Status::BAD_VALUE; + } + + sp<Session> session = mSessionLibrary->findSession(toVector(sessionId)); + if (session.get()) { + mCloseSessionOkCount++; + mSessionLibrary->destroySession(session); + return Status::OK; + } + mCloseSessionNotOpenedCount++; + return Status::ERROR_DRM_SESSION_NOT_OPENED; +} + +Status DrmPlugin::getKeyRequestCommon(const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& initData, + const hidl_string& mimeType, + KeyType keyType, + const hidl_vec<KeyValue>& optionalParameters, + std::vector<uint8_t> *request, + KeyRequestType *keyRequestType, + std::string *defaultUrl) { + UNUSED(optionalParameters); + + *defaultUrl = ""; + *keyRequestType = KeyRequestType::UNKNOWN; + *request = std::vector<uint8_t>(); + + if (scope.size() == 0) { + return Status::BAD_VALUE; + } + + if (keyType != KeyType::STREAMING) { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + + sp<Session> session = mSessionLibrary->findSession(toVector(scope)); + if (!session.get()) { + return Status::ERROR_DRM_SESSION_NOT_OPENED; + } + + Status status = session->getKeyRequest(initData, mimeType, request); + *keyRequestType = KeyRequestType::INITIAL; + return status; +} + +Return<void> DrmPlugin::getKeyRequest( + const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& initData, + const hidl_string& mimeType, + KeyType keyType, + const hidl_vec<KeyValue>& optionalParameters, + getKeyRequest_cb _hidl_cb) { + UNUSED(optionalParameters); + + KeyRequestType keyRequestType = KeyRequestType::UNKNOWN; + std::string defaultUrl(""); + std::vector<uint8_t> request; + Status status = getKeyRequestCommon( + scope, initData, mimeType, keyType, optionalParameters, + &request, &keyRequestType, &defaultUrl); + + _hidl_cb(status, toHidlVec(request), + static_cast<drm::V1_0::KeyRequestType>(keyRequestType), + hidl_string(defaultUrl)); + return Void(); +} + +Return<void> DrmPlugin::getKeyRequest_1_1( + const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& initData, + const hidl_string& mimeType, + KeyType keyType, + const hidl_vec<KeyValue>& optionalParameters, + getKeyRequest_1_1_cb _hidl_cb) { + UNUSED(optionalParameters); + + KeyRequestType keyRequestType = KeyRequestType::UNKNOWN; + std::string defaultUrl(""); + std::vector<uint8_t> request; + Status status = getKeyRequestCommon( + scope, initData, mimeType, keyType, optionalParameters, + &request, &keyRequestType, &defaultUrl); + + _hidl_cb(status, toHidlVec(request), keyRequestType, hidl_string(defaultUrl)); + return Void(); +} + +void DrmPlugin::setPlayPolicy() { + mPlayPolicy.clear(); + + KeyValue policy; + policy.key = kQueryKeyLicenseType; + policy.value = kStreaming; + mPlayPolicy.push_back(policy); + + policy.key = kQueryKeyPlayAllowed; + policy.value = kTrue; + mPlayPolicy.push_back(policy); + + policy.key = kQueryKeyRenewAllowed; + mPlayPolicy.push_back(policy); +} + +Return<void> DrmPlugin::provideKeyResponse( + const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& response, + provideKeyResponse_cb _hidl_cb) { + if (scope.size() == 0 || response.size() == 0) { + // Returns empty keySetId + _hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>()); + return Void(); + } + + sp<Session> session = mSessionLibrary->findSession(toVector(scope)); + if (!session.get()) { + _hidl_cb(Status::ERROR_DRM_SESSION_NOT_OPENED, hidl_vec<uint8_t>()); + return Void(); + } + + setPlayPolicy(); + std::vector<uint8_t> keySetId; + Status status = session->provideKeyResponse(response); + if (status == Status::OK) { + // This is for testing AMediaDrm_setOnEventListener only. + sendEvent(EventType::VENDOR_DEFINED, 0, scope); + keySetId.clear(); + } + + installSecureStop(scope); + + // Returns status and empty keySetId + _hidl_cb(status, toHidlVec(keySetId)); + return Void(); +} + +Return<void> DrmPlugin::getPropertyString( + const hidl_string& propertyName, getPropertyString_cb _hidl_cb) { + std::string name(propertyName.c_str()); + std::string value; + + if (name == kVendorKey) { + value = mStringProperties[kVendorKey]; + } else if (name == kVersionKey) { + value = mStringProperties[kVersionKey]; + } else if (name == kPluginDescriptionKey) { + value = mStringProperties[kPluginDescriptionKey]; + } else if (name == kAlgorithmsKey) { + value = mStringProperties[kAlgorithmsKey]; + } else if (name == kListenerTestSupportKey) { + value = mStringProperties[kListenerTestSupportKey]; + } else { + ALOGE("App requested unknown string property %s", name.c_str()); + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, ""); + return Void(); + } + _hidl_cb(Status::OK, value.c_str()); + return Void(); +} + +Return<void> DrmPlugin::getPropertyByteArray( + const hidl_string& propertyName, getPropertyByteArray_cb _hidl_cb) { + std::map<std::string, std::vector<uint8_t> >::iterator itr = + mByteArrayProperties.find(std::string(propertyName.c_str())); + if (itr == mByteArrayProperties.end()) { + ALOGE("App requested unknown property: %s", propertyName.c_str()); + _hidl_cb(Status::BAD_VALUE, std::vector<uint8_t>()); + return Void(); + } + _hidl_cb(Status::OK, itr->second); + return Void(); + +} + +Return<Status> DrmPlugin::setPropertyString( + const hidl_string& name, const hidl_string& value) { + std::string immutableKeys; + immutableKeys.append(kAlgorithmsKey + ","); + immutableKeys.append(kPluginDescriptionKey + ","); + immutableKeys.append(kVendorKey + ","); + immutableKeys.append(kVersionKey + ","); + + std::string key = std::string(name.c_str()); + if (immutableKeys.find(key) != std::string::npos) { + ALOGD("Cannot set immutable property: %s", key.c_str()); + return Status::BAD_VALUE; + } + + std::map<std::string, std::string>::iterator itr = + mStringProperties.find(key); + if (itr == mStringProperties.end()) { + ALOGE("Cannot set undefined property string, key=%s", key.c_str()); + return Status::BAD_VALUE; + } + + mStringProperties[key] = std::string(value.c_str()); + return Status::OK; +} + +Return<Status> DrmPlugin::setPropertyByteArray( + const hidl_string& name, const hidl_vec<uint8_t>& value) { + UNUSED(value); + if (name == kDeviceIdKey) { + ALOGD("Cannot set immutable property: %s", name.c_str()); + return Status::BAD_VALUE; + } + + // Setting of undefined properties is not supported + ALOGE("Failed to set property byte array, key=%s", name.c_str()); + return Status::ERROR_DRM_CANNOT_HANDLE; +} + +Return<void> DrmPlugin::queryKeyStatus( + const hidl_vec<uint8_t>& sessionId, + queryKeyStatus_cb _hidl_cb) { + + if (sessionId.size() == 0) { + // Returns empty key status KeyValue pair + _hidl_cb(Status::BAD_VALUE, hidl_vec<KeyValue>()); + return Void(); + } + + std::vector<KeyValue> infoMapVec; + infoMapVec.clear(); + + KeyValue keyValuePair; + for (size_t i = 0; i < mPlayPolicy.size(); ++i) { + keyValuePair.key = mPlayPolicy[i].key; + keyValuePair.value = mPlayPolicy[i].value; + infoMapVec.push_back(keyValuePair); + } + _hidl_cb(Status::OK, toHidlVec(infoMapVec)); + return Void(); +} + +Return<void> DrmPlugin::getNumberOfSessions(getNumberOfSessions_cb _hidl_cb) { + uint32_t currentSessions = mSessionLibrary->numOpenSessions(); + uint32_t maxSessions = 10; + _hidl_cb(Status::OK, currentSessions, maxSessions); + return Void(); +} + +Return<void> DrmPlugin::getSecurityLevel(const hidl_vec<uint8_t>& sessionId, + getSecurityLevel_cb _hidl_cb) { + if (sessionId.size() == 0) { + _hidl_cb(Status::BAD_VALUE, SecurityLevel::UNKNOWN); + return Void(); + } + + std::vector<uint8_t> sid = toVector(sessionId); + sp<Session> session = mSessionLibrary->findSession(sid); + if (!session.get()) { + _hidl_cb(Status::ERROR_DRM_SESSION_NOT_OPENED, SecurityLevel::UNKNOWN); + return Void(); + } + + std::map<std::vector<uint8_t>, SecurityLevel>::iterator itr = + mSecurityLevel.find(sid); + if (itr == mSecurityLevel.end()) { + ALOGE("Session id not found"); + _hidl_cb(Status::ERROR_DRM_INVALID_STATE, SecurityLevel::UNKNOWN); + return Void(); + } + + _hidl_cb(Status::OK, itr->second); + return Void(); +} + +Return<Status> DrmPlugin::setSecurityLevel(const hidl_vec<uint8_t>& sessionId, + SecurityLevel level) { + if (sessionId.size() == 0) { + ALOGE("Invalid empty session id"); + return Status::BAD_VALUE; + } + + if (level > SecurityLevel::SW_SECURE_CRYPTO) { + ALOGE("Cannot set security level > max"); + return Status::ERROR_DRM_CANNOT_HANDLE; + } + + std::vector<uint8_t> sid = toVector(sessionId); + sp<Session> session = mSessionLibrary->findSession(sid); + if (!session.get()) { + return Status::ERROR_DRM_SESSION_NOT_OPENED; + } + + std::map<std::vector<uint8_t>, SecurityLevel>::iterator itr = + mSecurityLevel.find(sid); + if (itr != mSecurityLevel.end()) { + mSecurityLevel[sid] = level; + } else { + if (!mSecurityLevel.insert( + std::pair<std::vector<uint8_t>, SecurityLevel>(sid, level)).second) { + ALOGE("Failed to set security level"); + return Status::ERROR_DRM_INVALID_STATE; + } + } + return Status::OK; +} + +Return<void> DrmPlugin::getMetrics(getMetrics_cb _hidl_cb) { + // Set the open session count metric. + DrmMetricGroup::Attribute openSessionOkAttribute = { + "status", DrmMetricGroup::ValueType::INT64_TYPE, (int64_t) Status::OK, 0.0, "" + }; + DrmMetricGroup::Value openSessionMetricValue = { + "count", DrmMetricGroup::ValueType::INT64_TYPE, mOpenSessionOkCount, 0.0, "" + }; + DrmMetricGroup::Metric openSessionMetric = { + "open_session", { openSessionOkAttribute }, { openSessionMetricValue } + }; + + // Set the close session count metric. + DrmMetricGroup::Attribute closeSessionOkAttribute = { + "status", DrmMetricGroup::ValueType::INT64_TYPE, (int64_t) Status::OK, 0.0, "" + }; + DrmMetricGroup::Value closeSessionMetricValue = { + "count", DrmMetricGroup::ValueType::INT64_TYPE, mCloseSessionOkCount, 0.0, "" + }; + DrmMetricGroup::Metric closeSessionMetric = { + "close_session", { closeSessionOkAttribute }, { closeSessionMetricValue } + }; + + // Set the close session, not opened metric. + DrmMetricGroup::Attribute closeSessionNotOpenedAttribute = { + "status", DrmMetricGroup::ValueType::INT64_TYPE, + (int64_t) Status::ERROR_DRM_SESSION_NOT_OPENED, 0.0, "" + }; + DrmMetricGroup::Value closeSessionNotOpenedMetricValue = { + "count", DrmMetricGroup::ValueType::INT64_TYPE, mCloseSessionNotOpenedCount, 0.0, "" + }; + DrmMetricGroup::Metric closeSessionNotOpenedMetric = { + "close_session", { closeSessionNotOpenedAttribute }, { closeSessionNotOpenedMetricValue } + }; + + DrmMetricGroup metrics = { { openSessionMetric, closeSessionMetric, + closeSessionNotOpenedMetric } }; + + _hidl_cb(Status::OK, hidl_vec<DrmMetricGroup>({metrics})); + return Void(); +} + +Return<void> DrmPlugin::getSecureStops(getSecureStops_cb _hidl_cb) { + std::vector<SecureStop> stops; + for (auto itr = mSecureStops.begin(); itr != mSecureStops.end(); ++itr) { + ClearkeySecureStop clearkeyStop = itr->second; + std::vector<uint8_t> stopVec; + stopVec.insert(stopVec.end(), clearkeyStop.id.begin(), clearkeyStop.id.end()); + stopVec.insert(stopVec.end(), clearkeyStop.data.begin(), clearkeyStop.data.end()); + + SecureStop stop; + stop.opaqueData = toHidlVec(stopVec); + stops.push_back(stop); + } + _hidl_cb(Status::OK, stops); + return Void(); +} + +Return<void> DrmPlugin::getSecureStop(const hidl_vec<uint8_t>& secureStopId, + getSecureStop_cb _hidl_cb) { + SecureStop stop; + auto itr = mSecureStops.find(toVector(secureStopId)); + if (itr != mSecureStops.end()) { + ClearkeySecureStop clearkeyStop = itr->second; + std::vector<uint8_t> stopVec; + stopVec.insert(stopVec.end(), clearkeyStop.id.begin(), clearkeyStop.id.end()); + stopVec.insert(stopVec.end(), clearkeyStop.data.begin(), clearkeyStop.data.end()); + + stop.opaqueData = toHidlVec(stopVec); + _hidl_cb(Status::OK, stop); + } else { + _hidl_cb(Status::BAD_VALUE, stop); + } + + return Void(); +} + +Return<Status> DrmPlugin::releaseSecureStop(const hidl_vec<uint8_t>& secureStopId) { + return removeSecureStop(secureStopId); +} + +Return<Status> DrmPlugin::releaseAllSecureStops() { + return removeAllSecureStops(); +} + +Return<void> DrmPlugin::getSecureStopIds(getSecureStopIds_cb _hidl_cb) { + std::vector<SecureStopId> ids; + for (auto itr = mSecureStops.begin(); itr != mSecureStops.end(); ++itr) { + ids.push_back(itr->first); + } + + _hidl_cb(Status::OK, toHidlVec(ids)); + return Void(); +} + +Return<Status> DrmPlugin::releaseSecureStops(const SecureStopRelease& ssRelease) { + if (ssRelease.opaqueData.size() == 0) { + return Status::BAD_VALUE; + } + + Status status = Status::OK; + std::vector<uint8_t> input = toVector(ssRelease.opaqueData); + + // The format of opaqueData is shared between the server + // and the drm service. The clearkey implementation consists of: + // count - number of secure stops + // list of fixed length secure stops + size_t countBufferSize = sizeof(uint32_t); + if (input.size() < countBufferSize) { + // SafetyNet logging + android_errorWriteLog(0x534e4554, "144766455"); + return Status::BAD_VALUE; + } + uint32_t count = 0; + sscanf(reinterpret_cast<char*>(input.data()), "%04" PRIu32, &count); + + // Avoid divide by 0 below. + if (count == 0) { + return Status::BAD_VALUE; + } + + size_t secureStopSize = (input.size() - countBufferSize) / count; + uint8_t buffer[secureStopSize]; + size_t offset = countBufferSize; // skip the count + for (size_t i = 0; i < count; ++i, offset += secureStopSize) { + memcpy(buffer, input.data() + offset, secureStopSize); + std::vector<uint8_t> id(buffer, buffer + kSecureStopIdSize); + + status = removeSecureStop(toHidlVec(id)); + if (Status::OK != status) break; + } + + return status; +} + +Return<Status> DrmPlugin::removeSecureStop(const hidl_vec<uint8_t>& secureStopId) { + if (1 != mSecureStops.erase(toVector(secureStopId))) { + return Status::BAD_VALUE; + } + return Status::OK; +} + +Return<Status> DrmPlugin::removeAllSecureStops() { + mSecureStops.clear(); + mNextSecureStopId = kSecureStopIdStart; + return Status::OK; +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp b/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp new file mode 100644 index 0000000..e2bb651 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp
@@ -0,0 +1,163 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "hidl_InitDataParser" + +#include <algorithm> +#include <utils/Log.h> + +#include "InitDataParser.h" + +#include "Base64.h" + +#include "ClearKeyUUID.h" +#include "MimeType.h" +#include "Utils.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +namespace { + const size_t kKeyIdSize = 16; + const size_t kSystemIdSize = 16; +} + +std::vector<uint8_t> StrToVector(const std::string& str) { + std::vector<uint8_t> vec(str.begin(), str.end()); + return vec; +} + +Status InitDataParser::parse(const std::vector<uint8_t>& initData, + const std::string& type, + std::vector<uint8_t>* licenseRequest) { + // Build a list of the key IDs + std::vector<const uint8_t*> keyIds; + + if (type == kIsoBmffVideoMimeType || + type == kIsoBmffAudioMimeType || + type == kCencInitDataFormat) { + Status res = parsePssh(initData, &keyIds); + if (res != Status::OK) { + return res; + } + } else if (type == kWebmVideoMimeType || + type == kWebmAudioMimeType || + type == kWebmInitDataFormat) { + // WebM "init data" is just a single key ID + if (initData.size() != kKeyIdSize) { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + keyIds.push_back(initData.data()); + } else { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + + // Build the request + std::string requestJson = generateRequest(keyIds); + std::vector<uint8_t> requestJsonVec = StrToVector(requestJson); + + licenseRequest->clear(); + licenseRequest->insert(licenseRequest->end(), requestJsonVec.begin(), requestJsonVec.end()); + return Status::OK; +} + +Status InitDataParser::parsePssh(const std::vector<uint8_t>& initData, + std::vector<const uint8_t*>* keyIds) { + size_t readPosition = 0; + + // Validate size field + uint32_t expectedSize = initData.size(); + expectedSize = htonl(expectedSize); + if (memcmp(&initData[readPosition], &expectedSize, + sizeof(expectedSize)) != 0) { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + readPosition += sizeof(expectedSize); + + // Validate PSSH box identifier + const char psshIdentifier[4] = {'p', 's', 's', 'h'}; + if (memcmp(&initData[readPosition], psshIdentifier, + sizeof(psshIdentifier)) != 0) { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + readPosition += sizeof(psshIdentifier); + + // Validate EME version number + const uint8_t psshVersion1[4] = {1, 0, 0, 0}; + if (memcmp(&initData[readPosition], psshVersion1, + sizeof(psshVersion1)) != 0) { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + readPosition += sizeof(psshVersion1); + + // Validate system ID + if (!clearkeydrm::isClearKeyUUID(&initData[readPosition])) { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + readPosition += kSystemIdSize; + + // Read key ID count + uint32_t keyIdCount; + memcpy(&keyIdCount, &initData[readPosition], sizeof(keyIdCount)); + keyIdCount = ntohl(keyIdCount); + readPosition += sizeof(keyIdCount); + if (readPosition + ((uint64_t)keyIdCount * kKeyIdSize) != + initData.size() - sizeof(uint32_t)) { + return Status::ERROR_DRM_CANNOT_HANDLE; + } + + // Calculate the key ID offsets + for (uint32_t i = 0; i < keyIdCount; ++i) { + size_t keyIdPosition = readPosition + (i * kKeyIdSize); + keyIds->push_back(&initData[keyIdPosition]); + } + return Status::OK; +} + +std::string InitDataParser::generateRequest(const std::vector<const uint8_t*>& keyIds) { + const std::string kRequestPrefix("{\"kids\":["); + const std::string kRequestSuffix("],\"type\":\"temporary\"}"); + + std::string request(kRequestPrefix); + std::string encodedId; + for (size_t i = 0; i < keyIds.size(); ++i) { + encodedId.clear(); + encodeBase64Url(keyIds[i], kKeyIdSize, &encodedId); + if (i != 0) { + request.append(","); + } + request.push_back('\"'); + request.append(encodedId); + request.push_back('\"'); + } + request.append(kRequestSuffix); + + // Android's Base64 encoder produces padding. EME forbids padding. + const char kBase64Padding = '='; + request.erase(std::remove(request.begin(), request.end(), kBase64Padding), request.end()); + + return request; +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp b/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp new file mode 100644 index 0000000..cccb41e --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp
@@ -0,0 +1,275 @@ +/* + * 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 "hidl_JsonWebKey" + +#include <utils/Log.h> + +#include "JsonWebKey.h" + +#include "Base64.h" + +namespace { +const std::string kKeysTag("keys"); +const std::string kKeyTypeTag("kty"); +const std::string kSymmetricKeyValue("oct"); +const std::string kKeyTag("k"); +const std::string kKeyIdTag("kid"); +const std::string kBase64Padding("="); +} + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +JsonWebKey::JsonWebKey() { +} + +JsonWebKey::~JsonWebKey() { +} + +/* + * Parses a JSON Web Key Set string, initializes a KeyMap with key id:key + * pairs from the JSON Web Key Set. Both key ids and keys are base64url + * encoded. The KeyMap contains base64url decoded key id:key pairs. + * + * @return Returns false for errors, true for success. + */ +bool JsonWebKey::extractKeysFromJsonWebKeySet(const std::string& jsonWebKeySet, + KeyMap* keys) { + + keys->clear(); + + if (!parseJsonWebKeySet(jsonWebKeySet, &mJsonObjects)) { + return false; + } + + // mJsonObjects[0] contains the entire JSON Web Key Set, including + // all the base64 encoded keys. Each key is also stored separately as + // a JSON object in mJsonObjects[1..n] where n is the total + // number of keys in the set. + if (!isJsonWebKeySet(mJsonObjects[0])) { + return false; + } + + std::string encodedKey, encodedKeyId; + std::vector<uint8_t> decodedKey, decodedKeyId; + + // mJsonObjects[1] contains the first JSON Web Key in the set + for (size_t i = 1; i < mJsonObjects.size(); ++i) { + encodedKeyId.clear(); + encodedKey.clear(); + + if (!parseJsonObject(mJsonObjects[i], &mTokens)) + return false; + + if (findKey(mJsonObjects[i], &encodedKeyId, &encodedKey)) { + if (encodedKeyId.empty() || encodedKey.empty()) { + ALOGE("Must have both key id and key in the JsonWebKey set."); + continue; + } + + if (!decodeBase64String(encodedKeyId, &decodedKeyId)) { + ALOGE("Failed to decode key id(%s)", encodedKeyId.c_str()); + continue; + } + + if (!decodeBase64String(encodedKey, &decodedKey)) { + ALOGE("Failed to decode key(%s)", encodedKey.c_str()); + continue; + } + + keys->insert(std::pair<std::vector<uint8_t>, + std::vector<uint8_t> >(decodedKeyId, decodedKey)); + } + } + return true; +} + +bool JsonWebKey::decodeBase64String(const std::string& encodedText, + std::vector<uint8_t>* decodedText) { + + decodedText->clear(); + + // encodedText should not contain padding characters as per EME spec. + if (encodedText.find(kBase64Padding) != std::string::npos) { + return false; + } + + // Since decodeBase64() requires padding characters, + // add them so length of encodedText is exactly a multiple of 4. + int remainder = encodedText.length() % 4; + std::string paddedText(encodedText); + if (remainder > 0) { + for (int i = 0; i < 4 - remainder; ++i) { + paddedText.append(kBase64Padding); + } + } + + sp<Buffer> buffer = decodeBase64(paddedText); + if (buffer == nullptr) { + ALOGE("Malformed base64 encoded content found."); + return false; + } + + decodedText->insert(decodedText->end(), buffer->base(), buffer->base() + buffer->size()); + return true; +} + +bool JsonWebKey::findKey(const std::string& jsonObject, std::string* keyId, + std::string* encodedKey) { + + std::string key, value; + + // Only allow symmetric key, i.e. "kty":"oct" pair. + if (jsonObject.find(kKeyTypeTag) != std::string::npos) { + findValue(kKeyTypeTag, &value); + if (0 != value.compare(kSymmetricKeyValue)) + return false; + } + + if (jsonObject.find(kKeyIdTag) != std::string::npos) { + findValue(kKeyIdTag, keyId); + } + + if (jsonObject.find(kKeyTag) != std::string::npos) { + findValue(kKeyTag, encodedKey); + } + return true; +} + +void JsonWebKey::findValue(const std::string &key, std::string* value) { + value->clear(); + const char* valueToken; + for (std::vector<std::string>::const_iterator nextToken = mTokens.begin(); + nextToken != mTokens.end(); ++nextToken) { + if (0 == (*nextToken).compare(key)) { + if (nextToken + 1 == mTokens.end()) + break; + valueToken = (*(nextToken + 1)).c_str(); + value->assign(valueToken); + nextToken++; + break; + } + } +} + +bool JsonWebKey::isJsonWebKeySet(const std::string& jsonObject) const { + if (jsonObject.find(kKeysTag) == std::string::npos) { + ALOGE("JSON Web Key does not contain keys."); + return false; + } + return true; +} + +/* + * Parses a JSON objects string and initializes a vector of tokens. + * + * @return Returns false for errors, true for success. + */ +bool JsonWebKey::parseJsonObject(const std::string& jsonObject, + std::vector<std::string>* tokens) { + jsmn_parser parser; + + jsmn_init(&parser); + int numTokens = jsmn_parse(&parser, + jsonObject.c_str(), jsonObject.size(), nullptr, 0); + if (numTokens < 0) { + ALOGE("Parser returns error code=%d", numTokens); + return false; + } + + unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t); + mJsmnTokens.clear(); + mJsmnTokens.resize(jsmnTokensSize); + + jsmn_init(&parser); + int status = jsmn_parse(&parser, jsonObject.c_str(), + jsonObject.size(), mJsmnTokens.data(), numTokens); + if (status < 0) { + ALOGE("Parser returns error code=%d", status); + return false; + } + + tokens->clear(); + std::string token; + const char *pjs; + for (int j = 0; j < numTokens; ++j) { + pjs = jsonObject.c_str() + mJsmnTokens[j].start; + if (mJsmnTokens[j].type == JSMN_STRING || + mJsmnTokens[j].type == JSMN_PRIMITIVE) { + token.assign(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start); + tokens->push_back(token); + } + } + return true; +} + +/* + * Parses JSON Web Key Set string and initializes a vector of JSON objects. + * + * @return Returns false for errors, true for success. + */ +bool JsonWebKey::parseJsonWebKeySet(const std::string& jsonWebKeySet, + std::vector<std::string>* jsonObjects) { + if (jsonWebKeySet.empty()) { + ALOGE("Empty JSON Web Key"); + return false; + } + + // The jsmn parser only supports unicode encoding. + jsmn_parser parser; + + // Computes number of tokens. A token marks the type, offset in + // the original string. + jsmn_init(&parser); + int numTokens = jsmn_parse(&parser, + jsonWebKeySet.c_str(), jsonWebKeySet.size(), nullptr, 0); + if (numTokens < 0) { + ALOGE("Parser returns error code=%d", numTokens); + return false; + } + + unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t); + mJsmnTokens.resize(jsmnTokensSize); + + jsmn_init(&parser); + int status = jsmn_parse(&parser, jsonWebKeySet.c_str(), + jsonWebKeySet.size(), mJsmnTokens.data(), numTokens); + if (status < 0) { + ALOGE("Parser returns error code=%d", status); + return false; + } + + std::string token; + const char *pjs; + for (int i = 0; i < numTokens; ++i) { + pjs = jsonWebKeySet.c_str() + mJsmnTokens[i].start; + if (mJsmnTokens[i].type == JSMN_OBJECT) { + token.assign(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start); + jsonObjects->push_back(token); + } + } + return true; +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android +
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Session.cpp b/drm/mediadrm/plugins/clearkey/hidl/Session.cpp new file mode 100644 index 0000000..07c9269 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/Session.cpp
@@ -0,0 +1,95 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "hidl_ClearKeySession" +#include <utils/Log.h> + +#include "Session.h" +#include "Utils.h" + +#include "AesCtrDecryptor.h" +#include "InitDataParser.h" +#include "JsonWebKey.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::KeyValue; +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_0::SubSample; +using ::android::hardware::Return; +using ::android::sp; + +using android::Mutex; + +Status Session::getKeyRequest( + const std::vector<uint8_t>& initData, + const std::string& mimeType, + std::vector<uint8_t>* keyRequest) const { + InitDataParser parser; + return parser.parse(initData, mimeType, keyRequest); +} + +Status Session::provideKeyResponse(const std::vector<uint8_t>& response) { + std::string responseString( + reinterpret_cast<const char*>(response.data()), response.size()); + KeyMap keys; + + Mutex::Autolock lock(mMapLock); + JsonWebKey parser; + if (parser.extractKeysFromJsonWebKeySet(responseString, &keys)) { + for (auto &key : keys) { + std::string first(key.first.begin(), key.first.end()); + std::string second(key.second.begin(), key.second.end()); + mKeyMap.insert(std::pair<std::vector<uint8_t>, + std::vector<uint8_t> >(key.first, key.second)); + } + return Status::OK; + } else { + return Status::ERROR_DRM_UNKNOWN; + } +} + +Status Session::decrypt( + const KeyId keyId, const Iv iv, const uint8_t* srcPtr, + uint8_t* destPtr, const std::vector<SubSample> subSamples, + size_t* bytesDecryptedOut) { + Mutex::Autolock lock(mMapLock); + + std::vector<uint8_t> keyIdVector; + keyIdVector.clear(); + keyIdVector.insert(keyIdVector.end(), keyId, keyId + kBlockSize); + std::map<std::vector<uint8_t>, std::vector<uint8_t> >::iterator itr; + itr = mKeyMap.find(keyIdVector); + if (itr == mKeyMap.end()) { + return Status::ERROR_DRM_NO_LICENSE; + } + + AesCtrDecryptor decryptor; + return decryptor.decrypt( + itr->second /*key*/, iv, srcPtr, destPtr, subSamples, + subSamples.size(), bytesDecryptedOut); +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/SessionLibrary.cpp b/drm/mediadrm/plugins/clearkey/hidl/SessionLibrary.cpp new file mode 100644 index 0000000..b4319e6 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/SessionLibrary.cpp
@@ -0,0 +1,90 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "hidl_ClearKeySessionLibrary" +#include <utils/Log.h> + +#include "SessionLibrary.h" +#include "Utils.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::sp; + +Mutex SessionLibrary::sSingletonLock; +SessionLibrary* SessionLibrary::sSingleton = NULL; + +SessionLibrary* SessionLibrary::get() { + Mutex::Autolock lock(sSingletonLock); + + if (sSingleton == NULL) { + ALOGD("Instantiating Session Library Singleton."); + sSingleton = new SessionLibrary(); + } + + return sSingleton; +} + +sp<Session> SessionLibrary::createSession() { + Mutex::Autolock lock(mSessionsLock); + + char sessionIdRaw[16]; + snprintf(sessionIdRaw, sizeof(sessionIdRaw), "%u", mNextSessionId); + + mNextSessionId += 1; + + std::vector<uint8_t> sessionId; + sessionId.insert(sessionId.end(), sessionIdRaw, + sessionIdRaw + sizeof(sessionIdRaw) / sizeof(uint8_t)); + + mSessions.insert(std::pair<std::vector<uint8_t>, + sp<Session> >(sessionId, new Session(sessionId))); + std::map<std::vector<uint8_t>, sp<Session> >::iterator itr = mSessions.find(sessionId); + if (itr != mSessions.end()) { + return itr->second; + } else { + return nullptr; + } +} + +sp<Session> SessionLibrary::findSession( + const std::vector<uint8_t>& sessionId) { + Mutex::Autolock lock(mSessionsLock); + std::map<std::vector<uint8_t>, sp<Session> >::iterator itr = mSessions.find(sessionId); + if (itr != mSessions.end()) { + return itr->second; + } else { + return nullptr; + } +} + +void SessionLibrary::destroySession(const sp<Session>& session) { + Mutex::Autolock lock(mSessionsLock); + mSessions.erase(session->sessionId()); +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.1-service.clearkey.rc b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.1-service.clearkey.rc new file mode 100644 index 0000000..ffe856a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.1-service.clearkey.rc
@@ -0,0 +1,6 @@ +service vendor.drm-clearkey-hal-1-1 /vendor/bin/hw/android.hardware.drm@1.1-service.clearkey + class hal + user media + group media mediadrm + ioprio rt 4 + writepid /dev/cpuset/foreground/tasks
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/AesCtrDecryptor.h b/drm/mediadrm/plugins/clearkey/hidl/include/AesCtrDecryptor.h new file mode 100644 index 0000000..0c7ef20 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/AesCtrDecryptor.h
@@ -0,0 +1,50 @@ +/* + * 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 CLEARKEY_AES_CTR_DECRYPTOR_H_ +#define CLEARKEY_AES_CTR_DECRYPTOR_H_ + +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_0::SubSample; + +class AesCtrDecryptor { +public: + AesCtrDecryptor() {} + + Status decrypt(const std::vector<uint8_t>& key, const Iv iv, + const uint8_t* source, uint8_t* destination, + const std::vector<SubSample> subSamples, size_t numSubSamples, + size_t* bytesDecryptedOut); + +private: + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(AesCtrDecryptor); +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_AES_CTR_DECRYPTOR_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/Base64.h b/drm/mediadrm/plugins/clearkey/hidl/include/Base64.h new file mode 100644 index 0000000..4a385bd --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/Base64.h
@@ -0,0 +1,46 @@ +/* + * 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 BASE_64_H_ + +#define BASE_64_H_ + +#include <android/hardware/drm/1.0/types.h> + +#include "Buffer.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::sp; + +struct Buffer; + +sp<Buffer> decodeBase64(const std::string &s); +void encodeBase64(const void *data, size_t size, std::string *out); + +void encodeBase64Url(const void *data, size_t size, std::string *out); + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // BASE_64_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/Buffer.h b/drm/mediadrm/plugins/clearkey/hidl/include/Buffer.h new file mode 100644 index 0000000..5bbb28a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/Buffer.h
@@ -0,0 +1,62 @@ +/* + * 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 BUFFER_H_ +#define BUFFER_H_ + +#include <android/hardware/drm/1.0/types.h> +#include <utils/RefBase.h> + +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::sp; + +struct Buffer : public RefBase { + explicit Buffer(size_t capacity); + + uint8_t *base() { return reinterpret_cast<uint8_t *>(mData); } + uint8_t *data() { return reinterpret_cast<uint8_t *>(mData) + mRangeOffset; } + size_t capacity() const { return mCapacity; } + size_t size() const { return mRangeLength; } + size_t offset() const { return mRangeOffset; } + +protected: + virtual ~Buffer(); + +private: + void *mData; + size_t mCapacity; + size_t mRangeOffset; + size_t mRangeLength; + + bool mOwnsData; + + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(Buffer); +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // BUFFER_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h new file mode 100644 index 0000000..d65b25c --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h
@@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CLEARKEY_DRM_PROPERTIES_H_ +#define CLEARKEY_DRM_PROPERTIES_H_ + +#include <string.h> + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +static const std::string kVendorKey("vendor"); +static const std::string kVendorValue("Google"); +static const std::string kVersionKey("version"); +static const std::string kVersionValue("1.1"); +static const std::string kPluginDescriptionKey("description"); +static const std::string kPluginDescriptionValue("ClearKey CDM"); +static const std::string kAlgorithmsKey("algorithms"); +static const std::string kAlgorithmsValue(""); +static const std::string kListenerTestSupportKey("listenerTestSupport"); +static const std::string kListenerTestSupportValue("true"); + +static const std::string kDeviceIdKey("deviceId"); +static const uint8_t kTestDeviceIdData[] = + {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; +// TODO stub out metrics for nw +static const std::string kMetricsKey("metrics"); +static const uint8_t kMetricsData[] = { 0 }; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_DRM_PROPERTIES_H_ +
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyTypes.h b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyTypes.h new file mode 100644 index 0000000..46cb5e4 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyTypes.h
@@ -0,0 +1,55 @@ +/* + * 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 CLEARKEY_MACROS_H_ +#define CLEARKEY_MACROS_H_ + +#include <android/hardware/drm/1.0/types.h> + +#include <map> + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::KeyValue; +using ::android::hardware::hidl_vec; + +const uint8_t kBlockSize = 16; //AES_BLOCK_SIZE; +typedef uint8_t KeyId[kBlockSize]; +typedef uint8_t Iv[kBlockSize]; + +typedef ::android::hardware::drm::V1_0::SubSample SubSample; +typedef std::map<std::vector<uint8_t>, std::vector<uint8_t> > KeyMap; + +#define CLEARKEY_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete; + +#define CLEARKEY_DISALLOW_COPY_AND_ASSIGN_AND_NEW(TypeName) \ + TypeName() = delete; \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_MACROS_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/CreatePluginFactories.h b/drm/mediadrm/plugins/clearkey/hidl/include/CreatePluginFactories.h new file mode 100644 index 0000000..9952027 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/CreatePluginFactories.h
@@ -0,0 +1,42 @@ +/* + * 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 CLEARKEY_CREATE_PLUGIN_FACTORIES_H_ +#define CLEARKEY_CREATE_PLUGIN_FACTORIES_H_ + +#include <android/hardware/drm/1.1/ICryptoFactory.h> +#include <android/hardware/drm/1.1/IDrmFactory.h> + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_1::ICryptoFactory; +using ::android::hardware::drm::V1_1::IDrmFactory; + +extern "C" { + IDrmFactory* createDrmFactory(); + ICryptoFactory* createCryptoFactory(); +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android +#endif // CLEARKEY_CREATE_PLUGIN_FACTORIES_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/CryptoFactory.h b/drm/mediadrm/plugins/clearkey/hidl/include/CryptoFactory.h new file mode 100644 index 0000000..175ab76 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/CryptoFactory.h
@@ -0,0 +1,60 @@ +/* + * 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 CLEARKEY_CRYPTO_FACTORY_H_ +#define CLEARKEY_CRYPTO_FACTORY_H_ + +#include <android/hardware/drm/1.0/ICryptoPlugin.h> +#include <android/hardware/drm/1.1/ICryptoFactory.h> + +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_1::ICryptoFactory; +using ::android::hardware::drm::V1_0::ICryptoPlugin; +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_string; +using ::android::hardware::Return; + +struct CryptoFactory : public ICryptoFactory { + CryptoFactory() {} + virtual ~CryptoFactory() {} + + Return<bool> isCryptoSchemeSupported(const hidl_array<uint8_t, 16>& uuid) + override; + + Return<void> createPlugin( + const hidl_array<uint8_t, 16>& uuid, + const hidl_vec<uint8_t>& initData, + createPlugin_cb _hidl_cb) override; + +private: + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(CryptoFactory); + +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_CRYPTO_FACTORY_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/CryptoPlugin.h b/drm/mediadrm/plugins/clearkey/hidl/include/CryptoPlugin.h new file mode 100644 index 0000000..6a73806 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/CryptoPlugin.h
@@ -0,0 +1,104 @@ +/* + * 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 CLEARKEY_CRYPTO_PLUGIN_H_ +#define CLEARKEY_CRYPTO_PLUGIN_H_ + +#include <android/hardware/drm/1.0/ICryptoPlugin.h> +#include <android/hidl/memory/1.0/IMemory.h> + +#include "ClearKeyTypes.h" +#include "Session.h" +#include "Utils.h" + +namespace { + static const size_t KEY_ID_SIZE = 16; + static const size_t KEY_IV_SIZE = 16; +} + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::DestinationBuffer; +using ::android::hardware::drm::V1_0::ICryptoPlugin; +using ::android::hardware::drm::V1_0::Mode; +using ::android::hardware::drm::V1_0::Pattern; +using ::android::hardware::drm::V1_0::SharedBuffer; +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_0::SubSample; +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_memory; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hidl::memory::V1_0::IMemory; +using ::android::sp; + +struct CryptoPlugin : public ICryptoPlugin { + explicit CryptoPlugin(const hidl_vec<uint8_t>& sessionId) { + mInitStatus = setMediaDrmSession(sessionId); + } + virtual ~CryptoPlugin() {} + + Return<bool> requiresSecureDecoderComponent(const hidl_string& mime) { + UNUSED(mime); + return false; + } + + Return<void> notifyResolution(uint32_t width, uint32_t height) { + UNUSED(width); + UNUSED(height); + return Void(); + } + + Return<void> decrypt( + bool secure, + const hidl_array<uint8_t, KEY_ID_SIZE>& keyId, + const hidl_array<uint8_t, KEY_IV_SIZE>& iv, + Mode mode, + const Pattern& pattern, + const hidl_vec<SubSample>& subSamples, + const SharedBuffer& source, + uint64_t offset, + const DestinationBuffer& destination, + decrypt_cb _hidl_cb); + + Return<void> setSharedBufferBase(const hidl_memory& base, + uint32_t bufferId); + + Return<Status> setMediaDrmSession(const hidl_vec<uint8_t>& sessionId); + + Return<Status> getInitStatus() const { return mInitStatus; } + +private: + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(CryptoPlugin); + + std::map<uint32_t, sp<IMemory> > mSharedBufferMap; + sp<Session> mSession; + Status mInitStatus; +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_CRYPTO_PLUGIN_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DrmFactory.h b/drm/mediadrm/plugins/clearkey/hidl/include/DrmFactory.h new file mode 100644 index 0000000..6f58195 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/DrmFactory.h
@@ -0,0 +1,60 @@ +/* + * 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 CLEARKEY_DRM_FACTORY_H_ +#define CLEARKEY_DRM_FACTORY_H_ + +#include <android/hardware/drm/1.1/IDrmPlugin.h> +#include <android/hardware/drm/1.1/IDrmFactory.h> + +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_string; +using ::android::hardware::Return; + +struct DrmFactory : public IDrmFactory { + DrmFactory() {} + virtual ~DrmFactory() {} + + Return<bool> isCryptoSchemeSupported(const hidl_array<uint8_t, 16>& uuid) + override; + + Return<bool> isContentTypeSupported(const hidl_string &mimeType) + override; + + Return<void> createPlugin( + const hidl_array<uint8_t, 16>& uuid, + const hidl_string& appPackageName, + createPlugin_cb _hidl_cb) override; + +private: + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(DrmFactory); +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_DRM_FACTORY_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h new file mode 100644 index 0000000..fb0695a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
@@ -0,0 +1,343 @@ +/* + * 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 CLEARKEY_DRM_PLUGIN_H_ +#define CLEARKEY_DRM_PLUGIN_H_ + +#include <android/hardware/drm/1.1/IDrmPlugin.h> + +#include <stdio.h> +#include <map> + +#include "SessionLibrary.h" +#include "Utils.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::EventType; +using ::android::hardware::drm::V1_0::IDrmPluginListener; +using ::android::hardware::drm::V1_0::KeyStatus; +using ::android::hardware::drm::V1_0::KeyType; +using ::android::hardware::drm::V1_0::KeyValue; +using ::android::hardware::drm::V1_0::SecureStop; +using ::android::hardware::drm::V1_0::SecureStopId; +using ::android::hardware::drm::V1_0::SessionId; +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_1::DrmMetricGroup; +using ::android::hardware::drm::V1_1::IDrmPlugin; +using ::android::hardware::drm::V1_1::KeyRequestType; + +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; + +struct DrmPlugin : public IDrmPlugin { + explicit DrmPlugin(SessionLibrary* sessionLibrary); + + virtual ~DrmPlugin() {} + + Return<void> openSession(openSession_cb _hidl_cb) override; + Return<void> openSession_1_1(SecurityLevel securityLevel, + openSession_cb _hidl_cb) override; + + Return<Status> closeSession(const hidl_vec<uint8_t>& sessionId) override; + + Return<void> getKeyRequest( + const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& initData, + const hidl_string& mimeType, + KeyType keyType, + const hidl_vec<KeyValue>& optionalParameters, + getKeyRequest_cb _hidl_cb) override; + + Return<void> getKeyRequest_1_1( + const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& initData, + const hidl_string& mimeType, + KeyType keyType, + const hidl_vec<KeyValue>& optionalParameters, + getKeyRequest_1_1_cb _hidl_cb) override; + + Return<void> provideKeyResponse( + const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& response, + provideKeyResponse_cb _hidl_cb) override; + + Return<Status> removeKeys(const hidl_vec<uint8_t>& sessionId) { + if (sessionId.size() == 0) { + return Status::BAD_VALUE; + } + return Status::ERROR_DRM_CANNOT_HANDLE; + } + + Return<Status> restoreKeys( + const hidl_vec<uint8_t>& sessionId, + const hidl_vec<uint8_t>& keySetId) { + + if (sessionId.size() == 0 || keySetId.size() == 0) { + return Status::BAD_VALUE; + } + return Status::ERROR_DRM_CANNOT_HANDLE; + } + + Return<void> queryKeyStatus( + const hidl_vec<uint8_t>& sessionId, + queryKeyStatus_cb _hidl_cb) override; + + Return<void> getProvisionRequest( + const hidl_string& certificateType, + const hidl_string& certificateAuthority, + getProvisionRequest_cb _hidl_cb) { + UNUSED(certificateType); + UNUSED(certificateAuthority); + + hidl_string defaultUrl; + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<uint8_t>(), defaultUrl); + return Void(); + } + + Return<void> provideProvisionResponse( + const hidl_vec<uint8_t>& response, + provideProvisionResponse_cb _hidl_cb) { + + if (response.size() == 0) { + _hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>(), hidl_vec<uint8_t>()); + return Void(); + } + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<uint8_t>(), hidl_vec<uint8_t>()); + return Void(); + } + + Return<void> getHdcpLevels(getHdcpLevels_cb _hidl_cb) { + HdcpLevel connectedLevel = HdcpLevel::HDCP_NONE; + HdcpLevel maxLevel = HdcpLevel::HDCP_NO_OUTPUT; + _hidl_cb(Status::OK, connectedLevel, maxLevel); + return Void(); + } + + Return<void> getNumberOfSessions(getNumberOfSessions_cb _hidl_cb) override; + + Return<void> getSecurityLevel(const hidl_vec<uint8_t>& sessionId, + getSecurityLevel_cb _hidl_cb) override; + + Return<void> getMetrics(getMetrics_cb _hidl_cb) override; + + Return<void> getPropertyString( + const hidl_string& name, + getPropertyString_cb _hidl_cb) override; + + Return<void> getPropertyByteArray( + const hidl_string& name, + getPropertyByteArray_cb _hidl_cb) override; + + Return<Status> setPropertyString( + const hidl_string& name, const hidl_string& value) override; + + Return<Status> setPropertyByteArray( + const hidl_string& name, const hidl_vec<uint8_t>& value) override; + + Return<Status> setCipherAlgorithm( + const hidl_vec<uint8_t>& sessionId, const hidl_string& algorithm) { + if (sessionId.size() == 0 || algorithm.size() == 0) { + return Status::BAD_VALUE; + } + return Status::ERROR_DRM_CANNOT_HANDLE; + } + + Return<Status> setMacAlgorithm( + const hidl_vec<uint8_t>& sessionId, const hidl_string& algorithm) { + if (sessionId.size() == 0 || algorithm.size() == 0) { + return Status::BAD_VALUE; + } + return Status::ERROR_DRM_CANNOT_HANDLE; + } + + Return<void> encrypt( + const hidl_vec<uint8_t>& sessionId, + const hidl_vec<uint8_t>& keyId, + const hidl_vec<uint8_t>& input, + const hidl_vec<uint8_t>& iv, + encrypt_cb _hidl_cb) { + if (sessionId.size() == 0 || keyId.size() == 0 || + input.size() == 0 || iv.size() == 0) { + _hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>()); + return Void(); + } + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<uint8_t>()); + return Void(); + } + + Return<void> decrypt( + const hidl_vec<uint8_t>& sessionId, + const hidl_vec<uint8_t>& keyId, + const hidl_vec<uint8_t>& input, + const hidl_vec<uint8_t>& iv, + decrypt_cb _hidl_cb) { + if (sessionId.size() == 0 || keyId.size() == 0 || + input.size() == 0 || iv.size() == 0) { + _hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>()); + return Void(); + } + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<uint8_t>()); + return Void(); + } + + Return<void> sign( + const hidl_vec<uint8_t>& sessionId, + const hidl_vec<uint8_t>& keyId, + const hidl_vec<uint8_t>& message, + sign_cb _hidl_cb) { + if (sessionId.size() == 0 || keyId.size() == 0 || + message.size() == 0) { + _hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>()); + return Void(); + } + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<uint8_t>()); + return Void(); + } + + Return<void> verify( + const hidl_vec<uint8_t>& sessionId, + const hidl_vec<uint8_t>& keyId, + const hidl_vec<uint8_t>& message, + const hidl_vec<uint8_t>& signature, + verify_cb _hidl_cb) { + + if (sessionId.size() == 0 || keyId.size() == 0 || + message.size() == 0 || signature.size() == 0) { + _hidl_cb(Status::BAD_VALUE, false); + return Void(); + } + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, false); + return Void(); + } + + Return<void> signRSA( + const hidl_vec<uint8_t>& sessionId, + const hidl_string& algorithm, + const hidl_vec<uint8_t>& message, + const hidl_vec<uint8_t>& wrappedKey, + signRSA_cb _hidl_cb) { + if (sessionId.size() == 0 || algorithm.size() == 0 || + message.size() == 0 || wrappedKey.size() == 0) { + _hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>()); + return Void(); + } + _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<uint8_t>()); + return Void(); + } + + Return<void> setListener(const sp<IDrmPluginListener>& listener) { + mListener = listener; + return Void(); + }; + + Return<void> sendEvent(EventType eventType, const hidl_vec<uint8_t>& sessionId, + const hidl_vec<uint8_t>& data) { + if (mListener != NULL) { + mListener->sendEvent(eventType, sessionId, data); + } else { + ALOGE("Null event listener, event not sent"); + } + return Void(); + } + + Return<void> sendExpirationUpdate(const hidl_vec<uint8_t>& sessionId, int64_t expiryTimeInMS) { + if (mListener != NULL) { + mListener->sendExpirationUpdate(sessionId, expiryTimeInMS); + } else { + ALOGE("Null event listener, event not sent"); + } + return Void(); + } + + Return<void> sendKeysChange(const hidl_vec<uint8_t>& sessionId, + const hidl_vec<KeyStatus>& keyStatusList, bool hasNewUsableKey) { + if (mListener != NULL) { + mListener->sendKeysChange(sessionId, keyStatusList, hasNewUsableKey); + } else { + ALOGE("Null event listener, event not sent"); + } + return Void(); + } + + Return<void> getSecureStops(getSecureStops_cb _hidl_cb); + + Return<void> getSecureStop(const hidl_vec<uint8_t>& secureStopId, + getSecureStop_cb _hidl_cb); + + Return<Status> releaseSecureStop(const hidl_vec<uint8_t>& ssRelease); + + Return<Status> releaseAllSecureStops(); + + Return<void> getSecureStopIds(getSecureStopIds_cb _hidl_cb); + + Return<Status> releaseSecureStops(const SecureStopRelease& ssRelease); + + Return<Status> removeSecureStop(const hidl_vec<uint8_t>& secureStopId); + + Return<Status> removeAllSecureStops(); + +private: + void initProperties(); + void installSecureStop(const hidl_vec<uint8_t>& sessionId); + void setPlayPolicy(); + + Return<Status> setSecurityLevel(const hidl_vec<uint8_t>& sessionId, + SecurityLevel level); + + Status getKeyRequestCommon(const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& initData, + const hidl_string& mimeType, + KeyType keyType, + const hidl_vec<KeyValue>& optionalParameters, + std::vector<uint8_t> *request, + KeyRequestType *getKeyRequestType, + std::string *defaultUrl); + + struct ClearkeySecureStop { + std::vector<uint8_t> id; + std::vector<uint8_t> data; + }; + + std::map<std::vector<uint8_t>, ClearkeySecureStop> mSecureStops; + std::vector<KeyValue> mPlayPolicy; + std::map<std::string, std::string> mStringProperties; + std::map<std::string, std::vector<uint8_t> > mByteArrayProperties; + std::map<std::vector<uint8_t>, SecurityLevel> mSecurityLevel; + sp<IDrmPluginListener> mListener; + SessionLibrary *mSessionLibrary; + int64_t mOpenSessionOkCount; + int64_t mCloseSessionOkCount; + int64_t mCloseSessionNotOpenedCount; + uint32_t mNextSecureStopId; + + CLEARKEY_DISALLOW_COPY_AND_ASSIGN_AND_NEW(DrmPlugin); +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_DRM_PLUGIN_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h b/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h new file mode 100644 index 0000000..3189c4a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h
@@ -0,0 +1,56 @@ +/* + * 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 CLEARKEY_INIT_DATA_PARSER_H_ +#define CLEARKEY_INIT_DATA_PARSER_H_ + +#include <android/hardware/drm/1.0/types.h> + +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::Status; + +class InitDataParser { +public: + InitDataParser() {} + + Status parse(const std::vector<uint8_t>& initData, + const std::string& type, + std::vector<uint8_t>* licenseRequest); + +private: + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(InitDataParser); + + Status parsePssh(const std::vector<uint8_t>& initData, + std::vector<const uint8_t*>* keyIds); + + std::string generateRequest( + const std::vector<const uint8_t*>& keyIds); +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_INIT_DATA_PARSER_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/JsonWebKey.h b/drm/mediadrm/plugins/clearkey/hidl/include/JsonWebKey.h new file mode 100644 index 0000000..4ab034c --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/JsonWebKey.h
@@ -0,0 +1,62 @@ +/* + * 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 CLEARKEY_JSON_WEB_KEY_H_ +#define CLEARKEY_JSON_WEB_KEY_H_ + +#include "jsmn.h" +#include "Utils.h" +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +class JsonWebKey { + public: + JsonWebKey(); + virtual ~JsonWebKey(); + + bool extractKeysFromJsonWebKeySet(const std::string& jsonWebKeySet, + KeyMap* keys); + + private: + std::vector<jsmntok_t> mJsmnTokens; + std::vector<std::string> mJsonObjects; + std::vector<std::string> mTokens; + + bool decodeBase64String(const std::string& encodedText, + std::vector<uint8_t>* decodedText); + bool findKey(const std::string& jsonObject, std::string* keyId, + std::string* encodedKey); + void findValue(const std::string &key, std::string* value); + bool isJsonWebKeySet(const std::string& jsonObject) const; + bool parseJsonObject(const std::string& jsonObject, + std::vector<std::string>* tokens); + bool parseJsonWebKeySet(const std::string& jsonWebKeySet, + std::vector<std::string>* jsonObjects); + + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(JsonWebKey); +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_JSON_WEB_KEY_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/Session.h b/drm/mediadrm/plugins/clearkey/hidl/include/Session.h new file mode 100644 index 0000000..cddfca5 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/Session.h
@@ -0,0 +1,71 @@ +/* + * 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 CLEARKEY_SESSION_H_ +#define CLEARKEY_SESSION_H_ + +#include <utils/Mutex.h> +#include <utils/RefBase.h> +#include <vector> + +#include "ClearKeyTypes.h" + + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_0::SubSample; + +class Session : public RefBase { +public: + explicit Session(const std::vector<uint8_t>& sessionId) + : mSessionId(sessionId) {} + virtual ~Session() {} + + const std::vector<uint8_t>& sessionId() const { return mSessionId; } + + Status getKeyRequest( + const std::vector<uint8_t>& mimeType, + const std::string& initDataType, + std::vector<uint8_t>* keyRequest) const; + + Status provideKeyResponse( + const std::vector<uint8_t>& response); + + Status decrypt( + const KeyId keyId, const Iv iv, const uint8_t* srcPtr, + uint8_t* dstPtr, const std::vector<SubSample> subSamples, + size_t* bytesDecryptedOut); + +private: + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(Session); + + const std::vector<uint8_t> mSessionId; + KeyMap mKeyMap; + Mutex mMapLock; +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_SESSION_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/SessionLibrary.h b/drm/mediadrm/plugins/clearkey/hidl/include/SessionLibrary.h new file mode 100644 index 0000000..326a0c1 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/SessionLibrary.h
@@ -0,0 +1,66 @@ +/* + * 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 CLEARKEY_SESSION_LIBRARY_H_ +#define CLEARKEY_SESSION_LIBRARY_H_ + +#include <utils/RefBase.h> +#include <utils/Mutex.h> + +#include "ClearKeyTypes.h" +#include "Session.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::sp; + +class SessionLibrary : public RefBase { +public: + static SessionLibrary* get(); + + sp<Session> createSession(); + + sp<Session> findSession( + const std::vector<uint8_t>& sessionId); + + void destroySession(const sp<Session>& session); + + size_t numOpenSessions() const { return mSessions.size(); } + +private: + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(SessionLibrary); + + SessionLibrary() : mNextSessionId(1) {} + + static Mutex sSingletonLock; + static SessionLibrary* sSingleton; + + Mutex mSessionsLock; + uint32_t mNextSessionId; + std::map<std::vector<uint8_t>, sp<Session> > mSessions; +}; + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_SESSION_LIBRARY_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/TypeConvert.h b/drm/mediadrm/plugins/clearkey/hidl/include/TypeConvert.h new file mode 100644 index 0000000..cc06329 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/TypeConvert.h
@@ -0,0 +1,77 @@ +/* + * 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 CLEARKEY_ANDROID_HARDWARE_DRM_V1_1_TYPECONVERT +#define CLEARKEY_ANDROID_HARDWARE_DRM_V1_1_TYPECONVERT + +#include <vector> + +#include <android/hardware/drm/1.0/types.h> + +namespace android { +namespace hardware { +namespace drm { +namespace V1_1 { +namespace clearkey { + +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_vec; + +template<typename T> const hidl_vec<T> toHidlVec(const std::vector<T> &vec) { + hidl_vec<T> hVec; + hVec.setToExternal(const_cast<T *>(vec.data()), vec.size()); + return hVec; +} + +template<typename T> hidl_vec<T> toHidlVec(std::vector<T> &vec) { + hidl_vec<T> hVec; + hVec.setToExternal(vec.data(), vec.size()); + return hVec; +} + +template<typename T> const std::vector<T> toVector(const hidl_vec<T> &hVec) { + std::vector<T> vec; + vec.assign(hVec.data(), hVec.data() + hVec.size()); + return *const_cast<const std::vector<T> *>(&vec); +} + +template<typename T> std::vector<T> toVector(hidl_vec<T> &hVec) { + std::vector<T> vec; + vec.assign(hVec.data(), hVec.data() + hVec.size()); + return vec; +} + +template<typename T, size_t SIZE> const std::vector<T> toVector( + const hidl_array<T, SIZE> &hArray) { + std::vector<T> vec; + vec.assign(hArray.data(), hArray.data() + hArray.size()); + return vec; +} + +template<typename T, size_t SIZE> std::vector<T> toVector( + hidl_array<T, SIZE> &hArray) { + std::vector<T> vec; + vec.assign(hArray.data(), hArray.data() + hArray.size()); + return vec; +} + +} // namespace clearkey +} // namespace V1_1 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_ANDROID_HARDWARE_DRM_V1_1_TYPECONVERT
diff --git a/drm/mediadrm/plugins/clearkey/hidl/service.cpp b/drm/mediadrm/plugins/clearkey/hidl/service.cpp new file mode 100644 index 0000000..6a97b72 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/service.cpp
@@ -0,0 +1,54 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define LOG_TAG "android.hardware.drm@1.1-service.clearkey" + +#include <CryptoFactory.h> +#include <DrmFactory.h> + +#include <android-base/logging.h> +#include <binder/ProcessState.h> +#include <hidl/HidlTransportSupport.h> + +using ::android::hardware::configureRpcThreadpool; +using ::android::hardware::joinRpcThreadpool; +using ::android::sp; + +using android::hardware::drm::V1_1::ICryptoFactory; +using android::hardware::drm::V1_1::IDrmFactory; +using android::hardware::drm::V1_1::clearkey::CryptoFactory; +using android::hardware::drm::V1_1::clearkey::DrmFactory; + + +int main(int /* argc */, char** /* argv */) { + ALOGD("android.hardware.drm@1.1-service.clearkey starting..."); + + // The DRM HAL may communicate to other vendor components via + // /dev/vndbinder + android::ProcessState::initWithDriver("/dev/vndbinder"); + + sp<IDrmFactory> drmFactory = new DrmFactory; + sp<ICryptoFactory> cryptoFactory = new CryptoFactory; + + configureRpcThreadpool(8, true /* callerWillJoin */); + + // Setup hwbinder service + CHECK_EQ(drmFactory->registerAsService("clearkey"), android::NO_ERROR) + << "Failed to register Clearkey Factory HAL"; + CHECK_EQ(cryptoFactory->registerAsService("clearkey"), android::NO_ERROR) + << "Failed to register Clearkey Crypto HAL"; + + joinRpcThreadpool(); +}
diff --git a/drm/mediadrm/plugins/clearkey/tests/Android.bp b/drm/mediadrm/plugins/clearkey/tests/Android.bp deleted file mode 100644 index 0fcfc64..0000000 --- a/drm/mediadrm/plugins/clearkey/tests/Android.bp +++ /dev/null
@@ -1,38 +0,0 @@ -// -// Copyright (C) 2014 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// ---------------------------------------------------------------- -// Builds ClearKey Drm Tests -// - -cc_test { - name: "ClearKeyDrmUnitTest", - vendor: true, - - srcs: [ - "AesCtrDecryptorUnittest.cpp", - "InitDataParserUnittest.cpp", - "JsonWebKeyUnittest.cpp", - ], - - shared_libs: [ - "libcrypto", - "libdrmclearkeyplugin", - "liblog", - "libstagefright_foundation", - "libutils", - ], - header_libs: ["media_plugin_headers"], -}
diff --git a/drm/mediadrm/plugins/mock/Android.bp b/drm/mediadrm/plugins/mock/Android.bp index abd1884..dd2ad7b 100644 --- a/drm/mediadrm/plugins/mock/Android.bp +++ b/drm/mediadrm/plugins/mock/Android.bp
@@ -32,5 +32,7 @@ cflags: [ // Set the following flag to enable the decryption passthru flow //"-DENABLE_PASSTHRU_DECRYPTION", + "-Wall", + "-Werror", ], }
diff --git a/include/OWNERS b/include/OWNERS index 3cb6d9c..d6bd998 100644 --- a/include/OWNERS +++ b/include/OWNERS
@@ -1,5 +1,5 @@ elaurent@google.com -gkasten@android.com +gkasten@google.com hunga@google.com jtinker@google.com lajos@google.com
diff --git a/include/common_time/OWNERS b/include/common_time/OWNERS new file mode 100644 index 0000000..f9cb567 --- /dev/null +++ b/include/common_time/OWNERS
@@ -0,0 +1 @@ +gkasten@google.com
diff --git a/include/media/AudioClient.h b/include/media/AudioClient.h deleted file mode 100644 index 9efd76d..0000000 --- a/include/media/AudioClient.h +++ /dev/null
@@ -1,38 +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. - */ - - -#ifndef ANDROID_AUDIO_CLIENT_H -#define ANDROID_AUDIO_CLIENT_H - -#include <system/audio.h> -#include <utils/String16.h> - -namespace android { - -class AudioClient { - public: - AudioClient() : - clientUid(-1), clientPid(-1), packageName("") {} - - uid_t clientUid; - pid_t clientPid; - String16 packageName; -}; - -}; // namespace android - -#endif // ANDROID_AUDIO_CLIENT_H
diff --git a/include/media/AudioClient.h b/include/media/AudioClient.h new file mode 120000 index 0000000..a0530e4 --- /dev/null +++ b/include/media/AudioClient.h
@@ -0,0 +1 @@ +../../media/libaudioclient/include/media/AudioClient.h \ No newline at end of file
diff --git a/include/media/AudioPresentationInfo.h b/include/media/AudioPresentationInfo.h new file mode 100644 index 0000000..e91a992 --- /dev/null +++ b/include/media/AudioPresentationInfo.h
@@ -0,0 +1,79 @@ +/* + * 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 AUDIO_PRESENTATION_INFO_H_ +#define AUDIO_PRESENTATION_INFO_H_ + +#include <sstream> +#include <stdint.h> + +#include <utils/KeyedVector.h> +#include <utils/RefBase.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace android { + +enum mastering_indication { + MASTERING_NOT_INDICATED, + MASTERED_FOR_STEREO, + MASTERED_FOR_SURROUND, + MASTERED_FOR_3D, + MASTERED_FOR_HEADPHONE, +}; + +struct AudioPresentation : public RefBase { + int32_t mPresentationId; + int32_t mProgramId; + KeyedVector<String8, String8> mLabels; + String8 mLanguage; + int32_t mMasteringIndication; + bool mAudioDescriptionAvailable; + bool mSpokenSubtitlesAvailable; + bool mDialogueEnhancementAvailable; + + AudioPresentation() { + mPresentationId = -1; + mProgramId = -1; + mLanguage = ""; + mMasteringIndication = MASTERING_NOT_INDICATED; + mAudioDescriptionAvailable = false; + mSpokenSubtitlesAvailable = false; + mDialogueEnhancementAvailable = false; + } +}; + +typedef Vector<sp<AudioPresentation>> AudioPresentations; + +class AudioPresentationInfo : public RefBase { + public: + AudioPresentationInfo(); + + ~AudioPresentationInfo(); + + void addPresentation(sp<AudioPresentation> presentation); + + size_t countPresentations() const; + + const sp<AudioPresentation> getPresentation(size_t index) const; + + private: + AudioPresentations mPresentations; +}; + +} // namespace android + +#endif // AUDIO_PRESENTATION_INFO_H_
diff --git a/include/media/CounterMetric.h b/include/media/CounterMetric.h new file mode 120000 index 0000000..baba043 --- /dev/null +++ b/include/media/CounterMetric.h
@@ -0,0 +1 @@ +../../media/libmedia/include/media/CounterMetric.h \ No newline at end of file
diff --git a/include/media/DataSource.h b/include/media/DataSource.h new file mode 120000 index 0000000..905bec1 --- /dev/null +++ b/include/media/DataSource.h
@@ -0,0 +1 @@ +../../media/libmediaextractor/include/media/DataSource.h \ No newline at end of file
diff --git a/include/media/DataSourceBase.h b/include/media/DataSourceBase.h new file mode 120000 index 0000000..54c8047 --- /dev/null +++ b/include/media/DataSourceBase.h
@@ -0,0 +1 @@ +../../media/libmediaextractor/include/media/DataSourceBase.h \ No newline at end of file
diff --git a/include/media/EventMetric.h b/include/media/EventMetric.h new file mode 120000 index 0000000..5707d9a --- /dev/null +++ b/include/media/EventMetric.h
@@ -0,0 +1 @@ +../../media/libmedia/include/media/EventMetric.h \ No newline at end of file
diff --git a/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h new file mode 120000 index 0000000..e2dd082 --- /dev/null +++ b/include/media/ExtractorUtils.h
@@ -0,0 +1 @@ +../../media/libmediaextractor/include/media/ExtractorUtils.h \ No newline at end of file
diff --git a/include/media/IAudioRecord.h b/include/media/IAudioRecord.h deleted file mode 120000 index 7fbf8f2..0000000 --- a/include/media/IAudioRecord.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libaudioclient/include/media/IAudioRecord.h \ No newline at end of file
diff --git a/include/media/IHDCP.h b/include/media/IHDCP.h deleted file mode 120000 index 9d4568e..0000000 --- a/include/media/IHDCP.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmedia/include/media/IHDCP.h \ No newline at end of file
diff --git a/include/media/IMediaCodecService.h b/include/media/IMediaCodecService.h deleted file mode 120000 index 37f6822..0000000 --- a/include/media/IMediaCodecService.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmedia/include/media/IMediaCodecService.h \ No newline at end of file
diff --git a/include/media/MediaDefs.h b/include/media/MediaDefs.h deleted file mode 120000 index 9850603..0000000 --- a/include/media/MediaDefs.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmedia/include/media/MediaDefs.h \ No newline at end of file
diff --git a/include/media/MediaExtractor.h b/include/media/MediaExtractor.h new file mode 120000 index 0000000..4b35fe1 --- /dev/null +++ b/include/media/MediaExtractor.h
@@ -0,0 +1 @@ +../../media/libmediaextractor/include/media/MediaExtractor.h \ No newline at end of file
diff --git a/include/media/MediaSource.h b/include/media/MediaSource.h new file mode 120000 index 0000000..2e147c4 --- /dev/null +++ b/include/media/MediaSource.h
@@ -0,0 +1 @@ +../../media/libmediaextractor/include/media/MediaSource.h \ No newline at end of file
diff --git a/include/media/MediaTrack.h b/include/media/MediaTrack.h new file mode 120000 index 0000000..5a63287a --- /dev/null +++ b/include/media/MediaTrack.h
@@ -0,0 +1 @@ +../../media/libmediaextractor/include/media/MediaTrack.h \ No newline at end of file
diff --git a/include/media/MicrophoneInfo.h b/include/media/MicrophoneInfo.h new file mode 100644 index 0000000..2287aca --- /dev/null +++ b/include/media/MicrophoneInfo.h
@@ -0,0 +1,249 @@ +/* + * 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 ANDROID_MICROPHONE_INFO_H +#define ANDROID_MICROPHONE_INFO_H + +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <system/audio.h> +#include <utils/String16.h> +#include <utils/Vector.h> + +namespace android { +namespace media { + +#define RETURN_IF_FAILED(calledOnce) \ + { \ + status_t returnStatus = calledOnce; \ + if (returnStatus) { \ + ALOGE("Failed at %s:%d (%s)", __FILE__, __LINE__, __func__); \ + return returnStatus; \ + } \ + } + +class MicrophoneInfo : public Parcelable { +public: + MicrophoneInfo() = default; + MicrophoneInfo(const MicrophoneInfo& microphoneInfo) = default; + MicrophoneInfo(audio_microphone_characteristic_t& characteristic) { + mDeviceId = String16(&characteristic.device_id[0]); + mPortId = characteristic.id; + mType = characteristic.device; + mAddress = String16(&characteristic.address[0]); + mDeviceLocation = characteristic.location; + mDeviceGroup = characteristic.group; + mIndexInTheGroup = characteristic.index_in_the_group; + mGeometricLocation.push_back(characteristic.geometric_location.x); + mGeometricLocation.push_back(characteristic.geometric_location.y); + mGeometricLocation.push_back(characteristic.geometric_location.z); + mOrientation.push_back(characteristic.orientation.x); + mOrientation.push_back(characteristic.orientation.y); + mOrientation.push_back(characteristic.orientation.z); + Vector<float> frequencies; + Vector<float> responses; + for (size_t i = 0; i < characteristic.num_frequency_responses; i++) { + frequencies.push_back(characteristic.frequency_responses[0][i]); + responses.push_back(characteristic.frequency_responses[1][i]); + } + mFrequencyResponses.push_back(frequencies); + mFrequencyResponses.push_back(responses); + for (size_t i = 0; i < AUDIO_CHANNEL_COUNT_MAX; i++) { + mChannelMapping.push_back(characteristic.channel_mapping[i]); + } + mSensitivity = characteristic.sensitivity; + mMaxSpl = characteristic.max_spl; + mMinSpl = characteristic.min_spl; + mDirectionality = characteristic.directionality; + } + + virtual ~MicrophoneInfo() = default; + + virtual status_t writeToParcel(Parcel* parcel) const { + RETURN_IF_FAILED(parcel->writeString16(mDeviceId)); + RETURN_IF_FAILED(parcel->writeInt32(mPortId)); + RETURN_IF_FAILED(parcel->writeUint32(mType)); + RETURN_IF_FAILED(parcel->writeString16(mAddress)); + RETURN_IF_FAILED(parcel->writeInt32(mDeviceLocation)); + RETURN_IF_FAILED(parcel->writeInt32(mDeviceGroup)); + RETURN_IF_FAILED(parcel->writeInt32(mIndexInTheGroup)); + RETURN_IF_FAILED(writeFloatVector(parcel, mGeometricLocation)); + RETURN_IF_FAILED(writeFloatVector(parcel, mOrientation)); + if (mFrequencyResponses.size() != 2) { + return BAD_VALUE; + } + for (size_t i = 0; i < mFrequencyResponses.size(); i++) { + RETURN_IF_FAILED(parcel->writeInt32(mFrequencyResponses[i].size())); + RETURN_IF_FAILED(writeFloatVector(parcel, mFrequencyResponses[i])); + } + std::vector<int> channelMapping; + for (size_t i = 0; i < mChannelMapping.size(); ++i) { + channelMapping.push_back(mChannelMapping[i]); + } + RETURN_IF_FAILED(parcel->writeInt32Vector(channelMapping)); + RETURN_IF_FAILED(parcel->writeFloat(mSensitivity)); + RETURN_IF_FAILED(parcel->writeFloat(mMaxSpl)); + RETURN_IF_FAILED(parcel->writeFloat(mMinSpl)); + RETURN_IF_FAILED(parcel->writeInt32(mDirectionality)); + return OK; + } + + virtual status_t readFromParcel(const Parcel* parcel) { + RETURN_IF_FAILED(parcel->readString16(&mDeviceId)); + RETURN_IF_FAILED(parcel->readInt32(&mPortId)); + RETURN_IF_FAILED(parcel->readUint32(&mType)); + RETURN_IF_FAILED(parcel->readString16(&mAddress)); + RETURN_IF_FAILED(parcel->readInt32(&mDeviceLocation)); + RETURN_IF_FAILED(parcel->readInt32(&mDeviceGroup)); + RETURN_IF_FAILED(parcel->readInt32(&mIndexInTheGroup)); + RETURN_IF_FAILED(readFloatVector(parcel, &mGeometricLocation, 3)); + RETURN_IF_FAILED(readFloatVector(parcel, &mOrientation, 3)); + int32_t frequenciesNum; + RETURN_IF_FAILED(parcel->readInt32(&frequenciesNum)); + Vector<float> frequencies; + RETURN_IF_FAILED(readFloatVector(parcel, &frequencies, frequenciesNum)); + int32_t responsesNum; + RETURN_IF_FAILED(parcel->readInt32(&responsesNum)); + Vector<float> responses; + RETURN_IF_FAILED(readFloatVector(parcel, &responses, responsesNum)); + if (frequencies.size() != responses.size()) { + return BAD_VALUE; + } + mFrequencyResponses.push_back(frequencies); + mFrequencyResponses.push_back(responses); + std::vector<int> channelMapping; + status_t result = parcel->readInt32Vector(&channelMapping); + if (result != OK) { + return result; + } + if (channelMapping.size() != AUDIO_CHANNEL_COUNT_MAX) { + return BAD_VALUE; + } + for (size_t i = 0; i < channelMapping.size(); i++) { + mChannelMapping.push_back(channelMapping[i]); + } + RETURN_IF_FAILED(parcel->readFloat(&mSensitivity)); + RETURN_IF_FAILED(parcel->readFloat(&mMaxSpl)); + RETURN_IF_FAILED(parcel->readFloat(&mMinSpl)); + RETURN_IF_FAILED(parcel->readInt32(&mDirectionality)); + return OK; + } + + String16 getDeviceId() const { + return mDeviceId; + } + + int getPortId() const { + return mPortId; + } + + unsigned int getType() const { + return mType; + } + + String16 getAddress() const { + return mAddress; + } + + int getDeviceLocation() const { + return mDeviceLocation; + } + + int getDeviceGroup() const { + return mDeviceGroup; + } + + int getIndexInTheGroup() const { + return mIndexInTheGroup; + } + + const Vector<float>& getGeometricLocation() const { + return mGeometricLocation; + } + + const Vector<float>& getOrientation() const { + return mOrientation; + } + + const Vector<Vector<float>>& getFrequencyResponses() const { + return mFrequencyResponses; + } + + const Vector<int>& getChannelMapping() const { + return mChannelMapping; + } + + float getSensitivity() const { + return mSensitivity; + } + + float getMaxSpl() const { + return mMaxSpl; + } + + float getMinSpl() const { + return mMinSpl; + } + + int getDirectionality() const { + return mDirectionality; + } + +private: + status_t readFloatVector( + const Parcel* parcel, Vector<float> *vectorPtr, size_t defaultLength) { + std::unique_ptr<std::vector<float>> v; + status_t result = parcel->readFloatVector(&v); + if (result != OK) return result; + vectorPtr->clear(); + if (v.get() != nullptr) { + for (const auto& iter : *v) { + vectorPtr->push_back(iter); + } + } else { + vectorPtr->resize(defaultLength); + } + return OK; + } + status_t writeFloatVector(Parcel* parcel, const Vector<float>& vector) const { + std::vector<float> v; + for (size_t i = 0; i < vector.size(); i++) { + v.push_back(vector[i]); + } + return parcel->writeFloatVector(v); + } + + String16 mDeviceId; + int32_t mPortId; + uint32_t mType; + String16 mAddress; + int32_t mDeviceLocation; + int32_t mDeviceGroup; + int32_t mIndexInTheGroup; + Vector<float> mGeometricLocation; + Vector<float> mOrientation; + Vector<Vector<float>> mFrequencyResponses; + Vector<int> mChannelMapping; + float mSensitivity; + float mMaxSpl; + float mMinSpl; + int32_t mDirectionality; +}; + +} // namespace media +} // namespace android + +#endif
diff --git a/include/media/MmapStreamCallback.h b/include/media/MmapStreamCallback.h index 8098e79..31b8eb5 100644 --- a/include/media/MmapStreamCallback.h +++ b/include/media/MmapStreamCallback.h
@@ -31,8 +31,9 @@ * The mmap stream should be torn down because conditions that permitted its creation with * the requested parameters have changed and do not allow it to operate with the requested * constraints any more. + * \param[in] handle handle for the client stream to tear down. */ - virtual void onTearDown() = 0; + virtual void onTearDown(audio_port_handle_t handle) = 0; /** * The volume to be applied to the use case specified when opening the stream has changed
diff --git a/include/media/MmapStreamInterface.h b/include/media/MmapStreamInterface.h index d689e25..0196a0c 100644 --- a/include/media/MmapStreamInterface.h +++ b/include/media/MmapStreamInterface.h
@@ -52,6 +52,9 @@ * \param[in,out] deviceId audio device the stream should preferably be routed to/from * Requested as input, * Actual as output + * \param[in,out] sessionId audio sessionId for the stream + * Requested as input, may be AUDIO_SESSION_ALLOCATE + * Actual as output * \param[in] callback the MmapStreamCallback interface used by AudioFlinger to notify * condition changes affecting the stream operation * \param[out] interface the MmapStreamInterface interface controlling the created stream @@ -66,6 +69,7 @@ audio_config_base_t *config, const AudioClient& client, audio_port_handle_t *deviceId, + audio_session_t *sessionId, const sp<MmapStreamCallback>& callback, sp<MmapStreamInterface>& interface, audio_port_handle_t *handle);
diff --git a/include/media/TimeCheck.h b/include/media/TimeCheck.h new file mode 120000 index 0000000..e3ef134 --- /dev/null +++ b/include/media/TimeCheck.h
@@ -0,0 +1 @@ +../../media/libmedia/include/media/TimeCheck.h \ No newline at end of file
diff --git a/include/media/VolumeShaper.h b/include/media/VolumeShaper.h index 302641f..a3aaece 100644 --- a/include/media/VolumeShaper.h +++ b/include/media/VolumeShaper.h
@@ -37,6 +37,8 @@ namespace android { +namespace media { + // The native VolumeShaper class mirrors the java VolumeShaper class; // in addition, the native class contains implementation for actual operation. // @@ -101,7 +103,7 @@ * See "frameworks/base/media/java/android/media/VolumeShaper.java" for * details on the Java implementation. */ - class Configuration : public Interpolator<S, T>, public RefBase { + class Configuration : public Interpolator<S, T>, public RefBase, public Parcelable { public: // Must match with VolumeShaper.java in frameworks/base. enum Type : int32_t { @@ -283,7 +285,7 @@ } // The parcel layout must match VolumeShaper.java - status_t writeToParcel(Parcel *parcel) const { + status_t writeToParcel(Parcel *parcel) const override { if (parcel == nullptr) return BAD_VALUE; return parcel->writeInt32((int32_t)mType) ?: parcel->writeInt32(mId) @@ -294,17 +296,17 @@ ?: Interpolator<S, T>::writeToParcel(parcel); } - status_t readFromParcel(const Parcel &parcel) { + status_t readFromParcel(const Parcel *parcel) override { int32_t type, optionFlags; - return parcel.readInt32(&type) + return parcel->readInt32(&type) ?: setType((Type)type) - ?: parcel.readInt32(&mId) + ?: parcel->readInt32(&mId) ?: mType == TYPE_ID ? NO_ERROR - : parcel.readInt32(&optionFlags) + : parcel->readInt32(&optionFlags) ?: setOptionFlags((OptionFlag)optionFlags) - ?: parcel.readDouble(&mDurationMs) - ?: Interpolator<S, T>::readFromParcel(parcel) + ?: parcel->readDouble(&mDurationMs) + ?: Interpolator<S, T>::readFromParcel(*parcel) ?: checkCurve(); } @@ -336,7 +338,7 @@ * See "frameworks/base/media/java/android/media/VolumeShaper.java" for * details on the Java implementation. */ - class Operation : public RefBase { + class Operation : public RefBase, public Parcelable { public: // Must match with VolumeShaper.java. enum Flag : int32_t { @@ -418,18 +420,18 @@ return NO_ERROR; } - status_t writeToParcel(Parcel *parcel) const { + status_t writeToParcel(Parcel *parcel) const override { if (parcel == nullptr) return BAD_VALUE; return parcel->writeInt32((int32_t)mFlags) ?: parcel->writeInt32(mReplaceId) ?: parcel->writeFloat(mXOffset); } - status_t readFromParcel(const Parcel &parcel) { + status_t readFromParcel(const Parcel *parcel) override { int32_t flags; - return parcel.readInt32(&flags) - ?: parcel.readInt32(&mReplaceId) - ?: parcel.readFloat(&mXOffset) + return parcel->readInt32(&flags) + ?: parcel->readInt32(&mReplaceId) + ?: parcel->readFloat(&mXOffset) ?: setFlags((Flag)flags); } @@ -455,7 +457,7 @@ * See "frameworks/base/media/java/android/media/VolumeShaper.java" for * details on the Java implementation. */ - class State : public RefBase { + class State : public RefBase, public Parcelable { public: State(T volume, S xOffset) : mVolume(volume) @@ -481,15 +483,15 @@ mXOffset = xOffset; } - status_t writeToParcel(Parcel *parcel) const { + status_t writeToParcel(Parcel *parcel) const override { if (parcel == nullptr) return BAD_VALUE; return parcel->writeFloat(mVolume) ?: parcel->writeFloat(mXOffset); } - status_t readFromParcel(const Parcel &parcel) { - return parcel.readFloat(&mVolume) - ?: parcel.readFloat(&mXOffset); + status_t readFromParcel(const Parcel *parcel) override { + return parcel->readFloat(&mVolume) + ?: parcel->readFloat(&mXOffset); } std::string toString() const { @@ -1020,6 +1022,8 @@ std::list<VolumeShaper> mVolumeShapers; // list provides stable iterators on erase }; // VolumeHandler +} // namespace media + } // namespace android #pragma pop_macro("LOG_TAG")
diff --git a/include/media/VorbisComment.h b/include/media/VorbisComment.h new file mode 120000 index 0000000..adaa489 --- /dev/null +++ b/include/media/VorbisComment.h
@@ -0,0 +1 @@ +../../media/libmediaextractor/include/media/VorbisComment.h \ No newline at end of file
diff --git a/include/media/nbaio/NBLog.h b/include/media/nbaio/NBLog.h deleted file mode 120000 index c35401e..0000000 --- a/include/media/nbaio/NBLog.h +++ /dev/null
@@ -1 +0,0 @@ -../../../media/libnbaio/include/media/nbaio/NBLog.h \ No newline at end of file
diff --git a/include/media/nbaio/PerformanceAnalysis.h b/include/media/nbaio/PerformanceAnalysis.h deleted file mode 120000 index 7acfc90..0000000 --- a/include/media/nbaio/PerformanceAnalysis.h +++ /dev/null
@@ -1 +0,0 @@ -../../../media/libnbaio/include/media/nbaio/PerformanceAnalysis.h \ No newline at end of file
diff --git a/include/media/nblog/NBLog.h b/include/media/nblog/NBLog.h new file mode 120000 index 0000000..3cc366c --- /dev/null +++ b/include/media/nblog/NBLog.h
@@ -0,0 +1 @@ +../../../media/libnblog/include/media/nblog/NBLog.h \ No newline at end of file
diff --git a/include/media/nblog/PerformanceAnalysis.h b/include/media/nblog/PerformanceAnalysis.h new file mode 120000 index 0000000..6ead3bc --- /dev/null +++ b/include/media/nblog/PerformanceAnalysis.h
@@ -0,0 +1 @@ +../../../media/libnblog/include/media/nblog/PerformanceAnalysis.h \ No newline at end of file
diff --git a/include/media/nblog/ReportPerformance.h b/include/media/nblog/ReportPerformance.h new file mode 120000 index 0000000..e9b8e80 --- /dev/null +++ b/include/media/nblog/ReportPerformance.h
@@ -0,0 +1 @@ +../../../media/libnblog/include/media/nblog/ReportPerformance.h \ No newline at end of file
diff --git a/include/media/Crypto.h b/include/mediadrm/Crypto.h similarity index 100% rename from include/media/Crypto.h rename to include/mediadrm/Crypto.h
diff --git a/include/media/CryptoHal.h b/include/mediadrm/CryptoHal.h similarity index 100% rename from include/media/CryptoHal.h rename to include/mediadrm/CryptoHal.h
diff --git a/include/media/Drm.h b/include/mediadrm/Drm.h similarity index 100% rename from include/media/Drm.h rename to include/mediadrm/Drm.h
diff --git a/include/media/DrmHal.h b/include/mediadrm/DrmHal.h similarity index 100% rename from include/media/DrmHal.h rename to include/mediadrm/DrmHal.h
diff --git a/include/mediadrm/DrmMetrics.h b/include/mediadrm/DrmMetrics.h new file mode 120000 index 0000000..abc966b --- /dev/null +++ b/include/mediadrm/DrmMetrics.h
@@ -0,0 +1 @@ +../../media/libmedia/include/media/DrmMetrics.h \ No newline at end of file
diff --git a/include/media/DrmPluginPath.h b/include/mediadrm/DrmPluginPath.h similarity index 100% rename from include/media/DrmPluginPath.h rename to include/mediadrm/DrmPluginPath.h
diff --git a/include/media/DrmSessionClientInterface.h b/include/mediadrm/DrmSessionClientInterface.h similarity index 100% rename from include/media/DrmSessionClientInterface.h rename to include/mediadrm/DrmSessionClientInterface.h
diff --git a/include/media/DrmSessionManager.h b/include/mediadrm/DrmSessionManager.h similarity index 100% rename from include/media/DrmSessionManager.h rename to include/mediadrm/DrmSessionManager.h
diff --git a/include/media/ICrypto.h b/include/mediadrm/ICrypto.h similarity index 100% rename from include/media/ICrypto.h rename to include/mediadrm/ICrypto.h
diff --git a/include/media/IDrm.h b/include/mediadrm/IDrm.h similarity index 100% rename from include/media/IDrm.h rename to include/mediadrm/IDrm.h
diff --git a/include/media/IDrmClient.h b/include/mediadrm/IDrmClient.h similarity index 100% rename from include/media/IDrmClient.h rename to include/mediadrm/IDrmClient.h
diff --git a/include/media/IMediaDrmService.h b/include/mediadrm/IMediaDrmService.h similarity index 100% rename from include/media/IMediaDrmService.h rename to include/mediadrm/IMediaDrmService.h
diff --git a/include/mediadrm/OWNERS b/include/mediadrm/OWNERS new file mode 100644 index 0000000..e788754 --- /dev/null +++ b/include/mediadrm/OWNERS
@@ -0,0 +1 @@ +jtinker@google.com
diff --git a/include/media/SharedLibrary.h b/include/mediadrm/SharedLibrary.h similarity index 100% rename from include/media/SharedLibrary.h rename to include/mediadrm/SharedLibrary.h
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h index ff440bc..ca119d5 100644 --- a/include/private/media/AudioTrackShared.h +++ b/include/private/media/AudioTrackShared.h
@@ -60,6 +60,8 @@ volatile int32_t mRear; // written by producer (output: client, input: server) volatile int32_t mFlush; // incremented by client to indicate a request to flush; // server notices and discards all data between mFront and mRear + volatile int32_t mStop; // set by client to indicate a stop frame position; server + // will not read beyond this position until start is called. volatile uint32_t mUnderrunFrames; // server increments for each unavailable but desired frame volatile uint32_t mUnderrunCount; // server increments for each underrun occurrence }; @@ -335,6 +337,8 @@ mTimestamp.clear(); } + virtual void stop() { }; // called by client in AudioTrack::stop() + private: // This is a copy of mCblk->mBufferSizeInFrames uint32_t mBufferSizeInFrames; // effective size of the buffer @@ -383,8 +387,14 @@ mPlaybackRateMutator.push(playbackRate); } + // Sends flush and stop position information from the client to the server, + // used by streaming AudioTrack flush() or stop(). + void sendStreamingFlushStop(bool flush); + virtual void flush(); + void stop() override; + virtual uint32_t getUnderrunFrames() const { return mCblk->u.mStreaming.mUnderrunFrames; } @@ -410,6 +420,8 @@ virtual void flush(); + void stop() override; + #define MIN_LOOP 16 // minimum length of each loop iteration in frames // setLoop(), setBufferPosition(), and setBufferPositionAndLoop() set the @@ -438,7 +450,11 @@ return 0; } - virtual uint32_t getUnderrunFrames() const { + virtual uint32_t getUnderrunFrames() const override { + return 0; + } + + virtual uint32_t getUnderrunCount() const override { return 0; } @@ -528,6 +544,10 @@ // client will be notified via Futex virtual void flushBufferIfNeeded(); + // Returns the rear position of the AudioTrack shared ring buffer, limited by + // the stop frame position level. + virtual int32_t getRear() const = 0; + // Total count of the number of flushed frames since creation (never reset). virtual int64_t framesFlushed() const { return mFlushed; } @@ -603,10 +623,18 @@ return mDrained.load(); } + int32_t getRear() const override; + + // Called on server side track start(). + virtual void start(); + private: AudioPlaybackRate mPlaybackRate; // last observed playback rate PlaybackRateQueue::Observer mPlaybackRateObserver; + // Last client stop-at position when start() was called. Used for streaming AudioTracks. + std::atomic<int32_t> mStopLast{0}; + // The server keeps a copy here where it is safe from the client. uint32_t mUnderrunCount; // echoed to mCblk bool mUnderrunning; // used to detect edge of underrun @@ -630,6 +658,10 @@ virtual void tallyUnderrunFrames(uint32_t frameCount); virtual uint32_t getUnderrunFrames() const { return 0; } + int32_t getRear() const override; + + void start() override { } // ignore for static tracks + private: status_t updateStateWithLoop(StaticAudioTrackState *localState, const StaticAudioTrackState &update) const; @@ -657,6 +689,10 @@ size_t frameSize, bool clientInServer) : ServerProxy(cblk, buffers, frameCount, frameSize, false /*isOut*/, clientInServer) { } + int32_t getRear() const override { + return mCblk->u.mStreaming.mRear; // For completeness only; mRear written by server. + } + protected: virtual ~AudioRecordServerProxy() { } };
diff --git a/include/private/media/OWNERS b/include/private/media/OWNERS new file mode 100644 index 0000000..21723ba --- /dev/null +++ b/include/private/media/OWNERS
@@ -0,0 +1,3 @@ +elaurent@google.com +gkasten@google.com +hunga@google.com
diff --git a/include/private/media/VideoFrame.h b/include/private/media/VideoFrame.h index a9d4dd1..712f118 100644 --- a/include/private/media/VideoFrame.h +++ b/include/private/media/VideoFrame.h
@@ -25,94 +25,34 @@ namespace android { -// Represents a color converted (RGB-based) video frame -// with bitmap pixels stored in FrameBuffer +// Represents a color converted (RGB-based) video frame with bitmap +// pixels stored in FrameBuffer. +// In a VideoFrame struct stored in IMemory, frame data and ICC data +// come after the VideoFrame structure. Their locations can be retrieved +// by getFlattenedData() and getFlattenedIccData(); class VideoFrame { public: // Construct a VideoFrame object with the specified parameters, - // will allocate frame buffer if |allocate| is set to true, will - // allocate buffer to hold ICC data if |iccData| and |iccSize| - // indicate its presence. + // will calculate frame buffer size if |hasData| is set to true. VideoFrame(uint32_t width, uint32_t height, uint32_t displayWidth, uint32_t displayHeight, - uint32_t angle, uint32_t bpp, bool allocate, - const void *iccData, size_t iccSize): + uint32_t tileWidth, uint32_t tileHeight, + uint32_t angle, uint32_t bpp, bool hasData, size_t iccSize): mWidth(width), mHeight(height), mDisplayWidth(displayWidth), mDisplayHeight(displayHeight), + mTileWidth(tileWidth), mTileHeight(tileHeight), mRotationAngle(angle), mBytesPerPixel(bpp), mRowBytes(bpp * width), - mSize(0), mIccSize(0), mReserved(0), mData(0), mIccData(0) { - if (allocate) { - mSize = mRowBytes * mHeight; - mData = new uint8_t[mSize]; - if (mData == NULL) { - mSize = 0; - } - } - - if (iccData != NULL && iccSize > 0) { - mIccSize = iccSize; - mIccData = new uint8_t[iccSize]; - if (mIccData != NULL) { - memcpy(mIccData, iccData, iccSize); - } else { - mIccSize = 0; - } - } + mSize(hasData ? (bpp * width * height) : 0), + mIccSize(iccSize), mReserved(0) { } - // Deep copy of both the information fields and the frame data - VideoFrame(const VideoFrame& copy) { - copyInfoOnly(copy); - - mSize = copy.mSize; - mData = NULL; // initialize it first - if (mSize > 0 && copy.mData != NULL) { - mData = new uint8_t[mSize]; - if (mData != NULL) { - memcpy(mData, copy.mData, mSize); - } else { - mSize = 0; - } - } - - mIccSize = copy.mIccSize; - mIccData = NULL; // initialize it first - if (mIccSize > 0 && copy.mIccData != NULL) { - mIccData = new uint8_t[mIccSize]; - if (mIccData != NULL) { - memcpy(mIccData, copy.mIccData, mIccSize); - } else { - mIccSize = 0; - } - } - } - - ~VideoFrame() { - if (mData != 0) { - delete[] mData; - } - if (mIccData != 0) { - delete[] mIccData; - } - } - - // Copy |copy| to a flattened VideoFrame in IMemory, 'this' must point to - // a chunk of memory back by IMemory of size at least getFlattenedSize() - // of |copy|. - void copyFlattened(const VideoFrame& copy) { - copyInfoOnly(copy); - - mSize = copy.mSize; - mData = NULL; // initialize it first - if (copy.mSize > 0 && copy.mData != NULL) { - memcpy(getFlattenedData(), copy.mData, copy.mSize); - } - - mIccSize = copy.mIccSize; - mIccData = NULL; // initialize it first - if (copy.mIccSize > 0 && copy.mIccData != NULL) { - memcpy(getFlattenedIccData(), copy.mIccData, copy.mIccSize); + void init(const VideoFrame& copy, const void* iccData, size_t iccSize) { + *this = copy; + if (mIccSize == iccSize && iccSize > 0 && iccData != NULL) { + memcpy(getFlattenedIccData(), iccData, iccSize); + } else { + mIccSize = 0; } } @@ -136,38 +76,14 @@ uint32_t mHeight; // Decoded image height before rotation uint32_t mDisplayWidth; // Display width before rotation uint32_t mDisplayHeight; // Display height before rotation + uint32_t mTileWidth; // Tile width (0 if image doesn't have grid) + uint32_t mTileHeight; // Tile height (0 if image doesn't have grid) int32_t mRotationAngle; // Rotation angle, clockwise, should be multiple of 90 uint32_t mBytesPerPixel; // Number of bytes per pixel uint32_t mRowBytes; // Number of bytes per row before rotation - uint32_t mSize; // Number of bytes in mData - uint32_t mIccSize; // Number of bytes in mIccData + uint32_t mSize; // Number of bytes of frame data + uint32_t mIccSize; // Number of bytes of ICC data uint32_t mReserved; // (padding to make mData 64-bit aligned) - - // mData should be 64-bit aligned to prevent additional padding - uint8_t* mData; // Actual binary data - // pad structure so it's the same size on 64-bit and 32-bit - char mPadding[8 - sizeof(mData)]; - - // mIccData should be 64-bit aligned to prevent additional padding - uint8_t* mIccData; // Actual binary data - // pad structure so it's the same size on 64-bit and 32-bit - char mIccPadding[8 - sizeof(mIccData)]; - -private: - // - // Utility methods used only within VideoFrame struct - // - - // Copy the information fields only - void copyInfoOnly(const VideoFrame& copy) { - mWidth = copy.mWidth; - mHeight = copy.mHeight; - mDisplayWidth = copy.mDisplayWidth; - mDisplayHeight = copy.mDisplayHeight; - mRotationAngle = copy.mRotationAngle; - mBytesPerPixel = copy.mBytesPerPixel; - mRowBytes = copy.mRowBytes; - } }; }; // namespace android
diff --git a/include/soundtrigger/OWNERS b/include/soundtrigger/OWNERS new file mode 100644 index 0000000..e83f6b9 --- /dev/null +++ b/include/soundtrigger/OWNERS
@@ -0,0 +1,2 @@ +elaurent@google.com +thorntonc@google.com
diff --git a/media/OWNERS b/media/OWNERS index 1605efd..d49eb8d 100644 --- a/media/OWNERS +++ b/media/OWNERS
@@ -2,6 +2,7 @@ dwkang@google.com elaurent@google.com essick@google.com +hkuang@google.com hunga@google.com jmtrivi@google.com krocard@google.com
diff --git a/media/audioserver/Android.mk b/media/audioserver/Android.mk index 3ee7494..70c281a 100644 --- a/media/audioserver/Android.mk +++ b/media/audioserver/Android.mk
@@ -3,7 +3,8 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ - main_audioserver.cpp + main_audioserver.cpp \ + ../libaudioclient/aidl/android/media/IAudioRecord.aidl LOCAL_SHARED_LIBRARIES := \ libaaudioservice \ @@ -12,11 +13,13 @@ libbinder \ libcutils \ liblog \ + libhidltransport \ + libhwbinder \ + libmedia \ libmedialogservice \ libnbaio \ libsoundtriggerservice \ - libutils \ - libhwbinder + libutils # TODO oboeservice is the old folder name for aaudioservice. It will be changed. LOCAL_C_INCLUDES := \ @@ -33,9 +36,13 @@ frameworks/av/media/libaaudio/include \ frameworks/av/media/libaaudio/src \ frameworks/av/media/libaaudio/src/binding \ + frameworks/av/media/libmedia \ $(call include-path-for, audio-utils) \ external/sonic \ +LOCAL_AIDL_INCLUDES := \ + frameworks/av/media/libaudioclient/aidl + # If AUDIOSERVER_MULTILIB in device.mk is non-empty then it is used to control # the LOCAL_MULTILIB for all audioserver exclusive libraries. # This is relevant for 64 bit architectures where either or both
diff --git a/media/audioserver/OWNERS b/media/audioserver/OWNERS new file mode 100644 index 0000000..f9cb567 --- /dev/null +++ b/media/audioserver/OWNERS
@@ -0,0 +1 @@ +gkasten@google.com
diff --git a/media/audioserver/audioserver.rc b/media/audioserver/audioserver.rc index 9d42bce..75675a9 100644 --- a/media/audioserver/audioserver.rc +++ b/media/audioserver/audioserver.rc
@@ -1,10 +1,13 @@ service audioserver /system/bin/audioserver - class main + class core user audioserver # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct ioprio rt 4 writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks + onrestart restart vendor.audio-hal-2-0 + # Keep the original service name for backward compatibility when upgrading + # O-MR1 devices with framework-only. onrestart restart audio-hal-2-0 on property:vts.native_server.on=1
diff --git a/media/audioserver/main_audioserver.cpp b/media/audioserver/main_audioserver.cpp index 474ef97..db57248 100644 --- a/media/audioserver/main_audioserver.cpp +++ b/media/audioserver/main_audioserver.cpp
@@ -25,12 +25,9 @@ #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <binder/IServiceManager.h> +#include <hidl/HidlTransportSupport.h> #include <utils/Log.h> -// FIXME: remove when BUG 31748996 is fixed -#include <hwbinder/IPCThreadState.h> -#include <hwbinder/ProcessState.h> - // from LOCAL_C_INCLUDES #include "aaudio/AAudioTesting.h" #include "AudioFlinger.h" @@ -38,12 +35,19 @@ #include "AAudioService.h" #include "utility/AAudioUtilities.h" #include "MediaLogService.h" +#include "MediaUtils.h" #include "SoundTriggerHwService.h" using namespace android; int main(int argc __unused, char **argv) { + // TODO: update with refined parameters + limitProcessMemory( + "audio.maxmem", /* "ro.audio.maxmem", property that defines limit */ + (size_t)512 * (1 << 20), /* SIZE_MAX, upper limit in bytes */ + 20 /* upper limit as percentage of physical RAM */); + signal(SIGPIPE, SIG_IGN); bool doLog = (bool) property_get_bool("ro.test_harness", 0); @@ -128,6 +132,7 @@ prctl(PR_SET_PDEATHSIG, SIGKILL); // if parent media.log dies before me, kill me also setpgid(0, 0); // but if I die first, don't kill my parent } + android::hardware::configureRpcThreadpool(4, false /*callerWillJoin*/); sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager(); ALOGI("ServiceManager: %p", sm.get()); @@ -145,10 +150,6 @@ SoundTriggerHwService::instantiate(); ProcessState::self()->startThreadPool(); - -// FIXME: remove when BUG 31748996 is fixed - android::hardware::ProcessState::self()->startThreadPool(); - IPCThreadState::self()->joinThreadPool(); } }
diff --git a/media/common_time/OWNERS b/media/common_time/OWNERS new file mode 100644 index 0000000..f9cb567 --- /dev/null +++ b/media/common_time/OWNERS
@@ -0,0 +1 @@ +gkasten@google.com
diff --git a/media/extractors/Android.bp b/media/extractors/Android.bp new file mode 100644 index 0000000..e8176cf --- /dev/null +++ b/media/extractors/Android.bp
@@ -0,0 +1,3 @@ +subdirs = [ + "*", +]
diff --git a/media/extractors/aac/AACExtractor.cpp b/media/extractors/aac/AACExtractor.cpp new file mode 100644 index 0000000..9fc5a76 --- /dev/null +++ b/media/extractors/aac/AACExtractor.cpp
@@ -0,0 +1,402 @@ +/* + * 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 "AACExtractor" +#include <utils/Log.h> + +#include "AACExtractor.h" +#include <media/DataSourceBase.h> +#include <media/MediaTrack.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MetaDataUtils.h> +#include <utils/String8.h> + +namespace android { + +class AACSource : public MediaTrack { +public: + AACSource( + DataSourceBase *source, + MetaDataBase &meta, + const Vector<uint64_t> &offset_vector, + int64_t frame_duration_us); + + virtual status_t start(MetaDataBase *params = NULL); + virtual status_t stop(); + + virtual status_t getFormat(MetaDataBase&); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~AACSource(); + +private: + static const size_t kMaxFrameSize; + DataSourceBase *mDataSource; + MetaDataBase mMeta; + + off64_t mOffset; + int64_t mCurrentTimeUs; + bool mStarted; + MediaBufferGroup *mGroup; + + Vector<uint64_t> mOffsetVector; + int64_t mFrameDurationUs; + + AACSource(const AACSource &); + AACSource &operator=(const AACSource &); +}; + +//////////////////////////////////////////////////////////////////////////////// + +// Returns the sample rate based on the sampling frequency index +uint32_t get_sample_rate(const uint8_t sf_index) +{ + static const uint32_t sample_rates[] = + { + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000 + }; + + if (sf_index < sizeof(sample_rates) / sizeof(sample_rates[0])) { + return sample_rates[sf_index]; + } + + return 0; +} + +// Returns the frame length in bytes as described in an ADTS header starting at the given offset, +// or 0 if the size can't be read due to an error in the header or a read failure. +// The returned value is the AAC frame size with the ADTS header length (regardless of +// the presence of the CRC). +// If headerSize is non-NULL, it will be used to return the size of the header of this ADTS frame. +static size_t getAdtsFrameLength(DataSourceBase *source, off64_t offset, size_t* headerSize) { + + const size_t kAdtsHeaderLengthNoCrc = 7; + const size_t kAdtsHeaderLengthWithCrc = 9; + + size_t frameSize = 0; + + uint8_t syncword[2]; + if (source->readAt(offset, &syncword, 2) != 2) { + return 0; + } + if ((syncword[0] != 0xff) || ((syncword[1] & 0xf6) != 0xf0)) { + return 0; + } + + uint8_t protectionAbsent; + if (source->readAt(offset + 1, &protectionAbsent, 1) < 1) { + return 0; + } + protectionAbsent &= 0x1; + + uint8_t header[3]; + if (source->readAt(offset + 3, &header, 3) < 3) { + return 0; + } + + frameSize = (header[0] & 0x3) << 11 | header[1] << 3 | header[2] >> 5; + + // protectionAbsent is 0 if there is CRC + size_t headSize = protectionAbsent ? kAdtsHeaderLengthNoCrc : kAdtsHeaderLengthWithCrc; + if (headSize > frameSize) { + return 0; + } + if (headerSize != NULL) { + *headerSize = headSize; + } + + return frameSize; +} + +AACExtractor::AACExtractor( + DataSourceBase *source, off64_t offset) + : mDataSource(source), + mInitCheck(NO_INIT), + mFrameDurationUs(0) { + + uint8_t profile, sf_index, channel, header[2]; + if (mDataSource->readAt(offset + 2, &header, 2) < 2) { + return; + } + + profile = (header[0] >> 6) & 0x3; + sf_index = (header[0] >> 2) & 0xf; + uint32_t sr = get_sample_rate(sf_index); + if (sr == 0) { + return; + } + channel = (header[0] & 0x1) << 2 | (header[1] >> 6); + + MakeAACCodecSpecificData(mMeta, profile, sf_index, channel); + + off64_t streamSize, numFrames = 0; + size_t frameSize = 0; + int64_t duration = 0; + + if (mDataSource->getSize(&streamSize) == OK) { + while (offset < streamSize) { + if ((frameSize = getAdtsFrameLength(source, offset, NULL)) == 0) { + ALOGW("prematured AAC stream (%lld vs %lld)", + (long long)offset, (long long)streamSize); + break; + } + + mOffsetVector.push(offset); + + offset += frameSize; + numFrames ++; + } + + // Round up and get the duration + mFrameDurationUs = (1024 * 1000000ll + (sr - 1)) / sr; + duration = numFrames * mFrameDurationUs; + mMeta.setInt64(kKeyDuration, duration); + } + + mInitCheck = OK; +} + +AACExtractor::~AACExtractor() { +} + +status_t AACExtractor::getMetaData(MetaDataBase &meta) { + meta.clear(); + if (mInitCheck == OK) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC_ADTS); + } + + return OK; +} + +size_t AACExtractor::countTracks() { + return mInitCheck == OK ? 1 : 0; +} + +MediaTrack *AACExtractor::getTrack(size_t index) { + if (mInitCheck != OK || index != 0) { + return NULL; + } + + return new AACSource(mDataSource, mMeta, mOffsetVector, mFrameDurationUs); +} + +status_t AACExtractor::getTrackMetaData(MetaDataBase &meta, size_t index, uint32_t /* flags */) { + if (mInitCheck != OK || index != 0) { + return UNKNOWN_ERROR; + } + + meta = mMeta; + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// 8192 = 2^13, 13bit AAC frame size (in bytes) +const size_t AACSource::kMaxFrameSize = 8192; + +AACSource::AACSource( + DataSourceBase *source, + MetaDataBase &meta, + const Vector<uint64_t> &offset_vector, + int64_t frame_duration_us) + : mDataSource(source), + mMeta(meta), + mOffset(0), + mCurrentTimeUs(0), + mStarted(false), + mGroup(NULL), + mOffsetVector(offset_vector), + mFrameDurationUs(frame_duration_us) { +} + +AACSource::~AACSource() { + if (mStarted) { + stop(); + } +} + +status_t AACSource::start(MetaDataBase * /* params */) { + CHECK(!mStarted); + + if (mOffsetVector.empty()) { + mOffset = 0; + } else { + mOffset = mOffsetVector.itemAt(0); + } + + mCurrentTimeUs = 0; + mGroup = new MediaBufferGroup; + mGroup->add_buffer(MediaBufferBase::Create(kMaxFrameSize)); + mStarted = true; + + return OK; +} + +status_t AACSource::stop() { + CHECK(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + return OK; +} + +status_t AACSource::getFormat(MetaDataBase &meta) { + meta = mMeta; + return OK; +} + +status_t AACSource::read( + MediaBufferBase **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (options && options->getSeekTo(&seekTimeUs, &mode)) { + if (mFrameDurationUs > 0) { + int64_t seekFrame = seekTimeUs / mFrameDurationUs; + if (seekFrame < 0 || seekFrame >= (int64_t)mOffsetVector.size()) { + android_errorWriteLog(0x534e4554, "70239507"); + return ERROR_MALFORMED; + } + mCurrentTimeUs = seekFrame * mFrameDurationUs; + + mOffset = mOffsetVector.itemAt(seekFrame); + } + } + + size_t frameSize, frameSizeWithoutHeader, headerSize; + if ((frameSize = getAdtsFrameLength(mDataSource, mOffset, &headerSize)) == 0) { + return ERROR_END_OF_STREAM; + } + + MediaBufferBase *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + frameSizeWithoutHeader = frameSize - headerSize; + if (mDataSource->readAt(mOffset + headerSize, buffer->data(), + frameSizeWithoutHeader) != (ssize_t)frameSizeWithoutHeader) { + buffer->release(); + buffer = NULL; + + return ERROR_IO; + } + + buffer->set_range(0, frameSizeWithoutHeader); + buffer->meta_data().setInt64(kKeyTime, mCurrentTimeUs); + buffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + + mOffset += frameSize; + mCurrentTimeUs += mFrameDurationUs; + + *out = buffer; + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +static MediaExtractor* CreateExtractor( + DataSourceBase *source, + void *meta) { + off64_t offset = *static_cast<off64_t*>(meta); + return new AACExtractor(source, offset); +} + +static MediaExtractor::CreatorFunc Sniff( + DataSourceBase *source, float *confidence, void **meta, + MediaExtractor::FreeMetaFunc *freeMeta) { + off64_t pos = 0; + + for (;;) { + uint8_t id3header[10]; + if (source->readAt(pos, id3header, sizeof(id3header)) + < (ssize_t)sizeof(id3header)) { + return NULL; + } + + if (memcmp("ID3", id3header, 3)) { + break; + } + + // Skip the ID3v2 header. + + size_t len = + ((id3header[6] & 0x7f) << 21) + | ((id3header[7] & 0x7f) << 14) + | ((id3header[8] & 0x7f) << 7) + | (id3header[9] & 0x7f); + + len += 10; + + pos += len; + + ALOGV("skipped ID3 tag, new starting offset is %lld (0x%016llx)", + (long long)pos, (long long)pos); + } + + uint8_t header[2]; + + if (source->readAt(pos, &header, 2) != 2) { + return NULL; + } + + // ADTS syncword + if ((header[0] == 0xff) && ((header[1] & 0xf6) == 0xf0)) { + *confidence = 0.2; + + off64_t *offPtr = (off64_t*) malloc(sizeof(off64_t)); + *offPtr = pos; + *meta = offPtr; + *freeMeta = ::free; + + return CreateExtractor; + } + + return NULL; +} + + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("4fd80eae-03d2-4d72-9eb9-48fa6bb54613"), + 1, // version + "AAC Extractor", + Sniff + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/aac/AACExtractor.h b/media/extractors/aac/AACExtractor.h new file mode 100644 index 0000000..9dadbed --- /dev/null +++ b/media/extractors/aac/AACExtractor.h
@@ -0,0 +1,62 @@ +/* + * 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 AAC_EXTRACTOR_H_ + +#define AAC_EXTRACTOR_H_ + +#include <media/MediaExtractor.h> +#include <media/stagefright/MetaDataBase.h> + +#include <utils/Vector.h> + +namespace android { + +struct AMessage; +class String8; + +class AACExtractor : public MediaExtractor { +public: + AACExtractor(DataSourceBase *source, off64_t offset); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + virtual const char * name() { return "AACExtractor"; } + +protected: + virtual ~AACExtractor(); + +private: + DataSourceBase *mDataSource; + MetaDataBase mMeta; + status_t mInitCheck; + + Vector<uint64_t> mOffsetVector; + int64_t mFrameDurationUs; + + AACExtractor(const AACExtractor &); + AACExtractor &operator=(const AACExtractor &); +}; + +bool SniffAAC( + DataSourceBase *source, String8 *mimeType, float *confidence, off64_t *offset); + +} // namespace android + +#endif // AAC_EXTRACTOR_H_
diff --git a/media/extractors/aac/Android.bp b/media/extractors/aac/Android.bp new file mode 100644 index 0000000..5f05b42 --- /dev/null +++ b/media/extractors/aac/Android.bp
@@ -0,0 +1,43 @@ +cc_library_shared { + + srcs: ["AACExtractor.cpp"], + + include_dirs: [ + "frameworks/av/media/libstagefright/", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + ], + + static_libs: [ + "libstagefright_foundation", + "libstagefright_metadatautils", + "libutils", + ], + + name: "libaacextractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/libstagefright/codecs/on2/h264dec/MODULE_LICENSE_APACHE2 b/media/extractors/aac/MODULE_LICENSE_APACHE2 similarity index 100% rename from media/libstagefright/codecs/on2/h264dec/MODULE_LICENSE_APACHE2 rename to media/extractors/aac/MODULE_LICENSE_APACHE2
diff --git a/media/libstagefright/codecs/on2/h264dec/NOTICE b/media/extractors/aac/NOTICE similarity index 100% rename from media/libstagefright/codecs/on2/h264dec/NOTICE rename to media/extractors/aac/NOTICE
diff --git a/media/extractors/aac/exports.lds b/media/extractors/aac/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/aac/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/amr/AMRExtractor.cpp b/media/extractors/amr/AMRExtractor.cpp new file mode 100644 index 0000000..f56d5ef --- /dev/null +++ b/media/extractors/amr/AMRExtractor.cpp
@@ -0,0 +1,392 @@ +/* + * Copyright (C) 2009 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 "AMRExtractor" +#include <utils/Log.h> + +#include "AMRExtractor.h" + +#include <media/DataSourceBase.h> +#include <media/MediaTrack.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <utils/String8.h> + +namespace android { + +class AMRSource : public MediaTrack { +public: + AMRSource( + DataSourceBase *source, + MetaDataBase &meta, + bool isWide, + const off64_t *offset_table, + size_t offset_table_length); + + virtual status_t start(MetaDataBase *params = NULL); + virtual status_t stop(); + + virtual status_t getFormat(MetaDataBase &); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~AMRSource(); + +private: + DataSourceBase *mDataSource; + MetaDataBase mMeta; + bool mIsWide; + + off64_t mOffset; + int64_t mCurrentTimeUs; + bool mStarted; + MediaBufferGroup *mGroup; + + off64_t mOffsetTable[OFFSET_TABLE_LEN]; + size_t mOffsetTableLength; + + AMRSource(const AMRSource &); + AMRSource &operator=(const AMRSource &); +}; + +//////////////////////////////////////////////////////////////////////////////// + +static size_t getFrameSize(bool isWide, unsigned FT) { + static const size_t kFrameSizeNB[16] = { + 95, 103, 118, 134, 148, 159, 204, 244, + 39, 43, 38, 37, // SID + 0, 0, 0, // future use + 0 // no data + }; + static const size_t kFrameSizeWB[16] = { + 132, 177, 253, 285, 317, 365, 397, 461, 477, + 40, // SID + 0, 0, 0, 0, // future use + 0, // speech lost + 0 // no data + }; + + if (FT > 15 || (isWide && FT > 9 && FT < 14) || (!isWide && FT > 11 && FT < 15)) { + ALOGE("illegal AMR frame type %d", FT); + return 0; + } + + size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT]; + + // Round up bits to bytes and add 1 for the header byte. + frameSize = (frameSize + 7) / 8 + 1; + + return frameSize; +} + +static status_t getFrameSizeByOffset(DataSourceBase *source, + off64_t offset, bool isWide, size_t *frameSize) { + uint8_t header; + ssize_t count = source->readAt(offset, &header, 1); + if (count == 0) { + return ERROR_END_OF_STREAM; + } else if (count < 0) { + return ERROR_IO; + } + + unsigned FT = (header >> 3) & 0x0f; + + *frameSize = getFrameSize(isWide, FT); + if (*frameSize == 0) { + return ERROR_MALFORMED; + } + return OK; +} + +static bool SniffAMR( + DataSourceBase *source, bool *isWide, float *confidence) { + char header[9]; + + if (source->readAt(0, header, sizeof(header)) != sizeof(header)) { + return false; + } + + if (!memcmp(header, "#!AMR\n", 6)) { + if (isWide != nullptr) { + *isWide = false; + } + *confidence = 0.5; + + return true; + } else if (!memcmp(header, "#!AMR-WB\n", 9)) { + if (isWide != nullptr) { + *isWide = true; + } + *confidence = 0.5; + + return true; + } + + return false; +} + +AMRExtractor::AMRExtractor(DataSourceBase *source) + : mDataSource(source), + mInitCheck(NO_INIT), + mOffsetTableLength(0) { + float confidence; + if (!SniffAMR(mDataSource, &mIsWide, &confidence)) { + return; + } + + mMeta.setCString( + kKeyMIMEType, mIsWide ? MEDIA_MIMETYPE_AUDIO_AMR_WB + : MEDIA_MIMETYPE_AUDIO_AMR_NB); + + mMeta.setInt32(kKeyChannelCount, 1); + mMeta.setInt32(kKeySampleRate, mIsWide ? 16000 : 8000); + + off64_t offset = mIsWide ? 9 : 6; + off64_t streamSize; + size_t frameSize, numFrames = 0; + int64_t duration = 0; + + if (mDataSource->getSize(&streamSize) == OK) { + while (offset < streamSize) { + status_t status = getFrameSizeByOffset(source, offset, mIsWide, &frameSize); + if (status == ERROR_END_OF_STREAM) { + break; + } else if (status != OK) { + return; + } + + if ((numFrames % 50 == 0) && (numFrames / 50 < OFFSET_TABLE_LEN)) { + CHECK_EQ(mOffsetTableLength, numFrames / 50); + mOffsetTable[mOffsetTableLength] = offset - (mIsWide ? 9: 6); + mOffsetTableLength ++; + } + + offset += frameSize; + duration += 20000; // Each frame is 20ms + numFrames ++; + } + + mMeta.setInt64(kKeyDuration, duration); + } + + mInitCheck = OK; +} + +AMRExtractor::~AMRExtractor() { +} + +status_t AMRExtractor::getMetaData(MetaDataBase &meta) { + meta.clear(); + + if (mInitCheck == OK) { + meta.setCString(kKeyMIMEType, mIsWide ? "audio/amr-wb" : "audio/amr"); + } + + return OK; +} + +size_t AMRExtractor::countTracks() { + return mInitCheck == OK ? 1 : 0; +} + +MediaTrack *AMRExtractor::getTrack(size_t index) { + if (mInitCheck != OK || index != 0) { + return NULL; + } + + return new AMRSource(mDataSource, mMeta, mIsWide, + mOffsetTable, mOffsetTableLength); +} + +status_t AMRExtractor::getTrackMetaData(MetaDataBase &meta, size_t index, uint32_t /* flags */) { + if (mInitCheck != OK || index != 0) { + return UNKNOWN_ERROR; + } + + meta = mMeta; + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +AMRSource::AMRSource( + DataSourceBase *source, MetaDataBase &meta, + bool isWide, const off64_t *offset_table, size_t offset_table_length) + : mDataSource(source), + mMeta(meta), + mIsWide(isWide), + mOffset(mIsWide ? 9 : 6), + mCurrentTimeUs(0), + mStarted(false), + mGroup(NULL), + mOffsetTableLength(offset_table_length) { + if (mOffsetTableLength > 0 && mOffsetTableLength <= OFFSET_TABLE_LEN) { + memcpy ((char*)mOffsetTable, (char*)offset_table, sizeof(off64_t) * mOffsetTableLength); + } +} + +AMRSource::~AMRSource() { + if (mStarted) { + stop(); + } +} + +status_t AMRSource::start(MetaDataBase * /* params */) { + CHECK(!mStarted); + + mOffset = mIsWide ? 9 : 6; + mCurrentTimeUs = 0; + mGroup = new MediaBufferGroup; + mGroup->add_buffer(MediaBufferBase::Create(128)); + mStarted = true; + + return OK; +} + +status_t AMRSource::stop() { + CHECK(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + return OK; +} + +status_t AMRSource::getFormat(MetaDataBase &meta) { + meta = mMeta; + return OK; +} + +status_t AMRSource::read( + MediaBufferBase **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (mOffsetTableLength > 0 && options && options->getSeekTo(&seekTimeUs, &mode)) { + size_t size; + int64_t seekFrame = seekTimeUs / 20000ll; // 20ms per frame. + mCurrentTimeUs = seekFrame * 20000ll; + + size_t index = seekFrame < 0 ? 0 : seekFrame / 50; + if (index >= mOffsetTableLength) { + index = mOffsetTableLength - 1; + } + + mOffset = mOffsetTable[index] + (mIsWide ? 9 : 6); + + for (size_t i = 0; i< seekFrame - index * 50; i++) { + status_t err; + if ((err = getFrameSizeByOffset(mDataSource, mOffset, + mIsWide, &size)) != OK) { + return err; + } + mOffset += size; + } + } + + uint8_t header; + ssize_t n = mDataSource->readAt(mOffset, &header, 1); + + if (n < 1) { + return ERROR_END_OF_STREAM; + } + + if (header & 0x83) { + // Padding bits must be 0. + + ALOGE("padding bits must be 0, header is 0x%02x", header); + + return ERROR_MALFORMED; + } + + unsigned FT = (header >> 3) & 0x0f; + + size_t frameSize = getFrameSize(mIsWide, FT); + if (frameSize == 0) { + return ERROR_MALFORMED; + } + + MediaBufferBase *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + n = mDataSource->readAt(mOffset, buffer->data(), frameSize); + + if (n != (ssize_t)frameSize) { + buffer->release(); + buffer = NULL; + + if (n < 0) { + return ERROR_IO; + } else { + // only partial frame is available, treat it as EOS. + mOffset += n; + return ERROR_END_OF_STREAM; + } + } + + buffer->set_range(0, frameSize); + buffer->meta_data().setInt64(kKeyTime, mCurrentTimeUs); + buffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + + mOffset += frameSize; + mCurrentTimeUs += 20000; // Each frame is 20ms + + *out = buffer; + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("c86639c9-2f31-40ac-a715-fa01b4493aaf"), + 1, + "AMR Extractor", + []( + DataSourceBase *source, + float *confidence, + void **, + MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc { + if (SniffAMR(source, nullptr, confidence)) { + return []( + DataSourceBase *source, + void *) -> MediaExtractor* { + return new AMRExtractor(source);}; + } + return NULL; + } + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/amr/AMRExtractor.h b/media/extractors/amr/AMRExtractor.h new file mode 100644 index 0000000..c90b325 --- /dev/null +++ b/media/extractors/amr/AMRExtractor.h
@@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 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 AMR_EXTRACTOR_H_ + +#define AMR_EXTRACTOR_H_ + +#include <utils/Errors.h> +#include <media/MediaExtractor.h> +#include <media/stagefright/MetaDataBase.h> + +namespace android { + +struct AMessage; +class String8; +#define OFFSET_TABLE_LEN 300 + +class AMRExtractor : public MediaExtractor { +public: + explicit AMRExtractor(DataSourceBase *source); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + virtual const char * name() { return "AMRExtractor"; } + +protected: + virtual ~AMRExtractor(); + +private: + DataSourceBase *mDataSource; + MetaDataBase mMeta; + status_t mInitCheck; + bool mIsWide; + + off64_t mOffsetTable[OFFSET_TABLE_LEN]; //5 min + size_t mOffsetTableLength; + + AMRExtractor(const AMRExtractor &); + AMRExtractor &operator=(const AMRExtractor &); +}; + +} // namespace android + +#endif // AMR_EXTRACTOR_H_
diff --git a/media/extractors/amr/Android.bp b/media/extractors/amr/Android.bp new file mode 100644 index 0000000..d962b93 --- /dev/null +++ b/media/extractors/amr/Android.bp
@@ -0,0 +1,41 @@ +cc_library_shared { + + srcs: ["AMRExtractor.cpp"], + + include_dirs: [ + "frameworks/av/media/libstagefright/include", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + ], + + static_libs: [ + "libstagefright_foundation", + ], + + name: "libamrextractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/libstagefright/matroska/MODULE_LICENSE_APACHE2 b/media/extractors/amr/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/libstagefright/matroska/MODULE_LICENSE_APACHE2 copy to media/extractors/amr/MODULE_LICENSE_APACHE2
diff --git a/media/libstagefright/matroska/NOTICE b/media/extractors/amr/NOTICE similarity index 100% copy from media/libstagefright/matroska/NOTICE copy to media/extractors/amr/NOTICE
diff --git a/media/extractors/amr/exports.lds b/media/extractors/amr/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/amr/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/flac/Android.bp b/media/extractors/flac/Android.bp new file mode 100644 index 0000000..6282793 --- /dev/null +++ b/media/extractors/flac/Android.bp
@@ -0,0 +1,43 @@ +cc_library_shared { + + srcs: ["FLACExtractor.cpp"], + + include_dirs: [ + "frameworks/av/media/libstagefright/include", + "external/flac/include", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + ], + + static_libs: [ + "libFLAC", + "libstagefright_foundation", + ], + + name: "libflacextractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/extractors/flac/FLACExtractor.cpp b/media/extractors/flac/FLACExtractor.cpp new file mode 100644 index 0000000..e3da259 --- /dev/null +++ b/media/extractors/flac/FLACExtractor.cpp
@@ -0,0 +1,884 @@ +/* + * 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 "FLACExtractor" +#include <utils/Log.h> + +#include "FLACExtractor.h" +// libFLAC parser +#include "FLAC/stream_decoder.h" + +#include <media/DataSourceBase.h> +#include <media/MediaTrack.h> +#include <media/VorbisComment.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/base64.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaBufferBase.h> + +namespace android { + +class FLACParser; + +class FLACSource : public MediaTrack { + +public: + FLACSource( + DataSourceBase *dataSource, + MetaDataBase &meta); + + virtual status_t start(MetaDataBase *params); + virtual status_t stop(); + virtual status_t getFormat(MetaDataBase &meta); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~FLACSource(); + +private: + DataSourceBase *mDataSource; + MetaDataBase mTrackMetadata; + FLACParser *mParser; + bool mInitCheck; + bool mStarted; + + // no copy constructor or assignment + FLACSource(const FLACSource &); + FLACSource &operator=(const FLACSource &); + +}; + +// FLACParser wraps a C libFLAC parser aka stream decoder + +class FLACParser { + +public: + enum { + kMaxChannels = 8, + }; + + explicit FLACParser( + DataSourceBase *dataSource, + // If metadata pointers aren't provided, we don't fill them + MetaDataBase *fileMetadata = 0, + MetaDataBase *trackMetadata = 0); + + virtual ~FLACParser(); + + status_t initCheck() const { + return mInitCheck; + } + + // stream properties + unsigned getMaxBlockSize() const { + return mStreamInfo.max_blocksize; + } + unsigned getSampleRate() const { + return mStreamInfo.sample_rate; + } + unsigned getChannels() const { + return mStreamInfo.channels; + } + unsigned getBitsPerSample() const { + return mStreamInfo.bits_per_sample; + } + FLAC__uint64 getTotalSamples() const { + return mStreamInfo.total_samples; + } + + // media buffers + void allocateBuffers(); + void releaseBuffers(); + MediaBufferBase *readBuffer() { + return readBuffer(false, 0LL); + } + MediaBufferBase *readBuffer(FLAC__uint64 sample) { + return readBuffer(true, sample); + } + +private: + DataSourceBase *mDataSource; + MetaDataBase *mFileMetadata; + MetaDataBase *mTrackMetadata; + bool mInitCheck; + + // media buffers + size_t mMaxBufferSize; + MediaBufferGroup *mGroup; + void (*mCopy)(short *dst, const int * src[kMaxChannels], unsigned nSamples, unsigned nChannels); + + // handle to underlying libFLAC parser + FLAC__StreamDecoder *mDecoder; + + // current position within the data source + off64_t mCurrentPos; + bool mEOF; + + // cached when the STREAMINFO metadata is parsed by libFLAC + FLAC__StreamMetadata_StreamInfo mStreamInfo; + bool mStreamInfoValid; + + // cached when a decoded PCM block is "written" by libFLAC parser + bool mWriteRequested; + bool mWriteCompleted; + FLAC__FrameHeader mWriteHeader; + FLAC__int32 const * mWriteBuffer[kMaxChannels]; + + // most recent error reported by libFLAC parser + FLAC__StreamDecoderErrorStatus mErrorStatus; + + status_t init(); + MediaBufferBase *readBuffer(bool doSeek, FLAC__uint64 sample); + + // no copy constructor or assignment + FLACParser(const FLACParser &); + FLACParser &operator=(const FLACParser &); + + // FLAC parser callbacks as C++ instance methods + FLAC__StreamDecoderReadStatus readCallback( + FLAC__byte buffer[], size_t *bytes); + FLAC__StreamDecoderSeekStatus seekCallback( + FLAC__uint64 absolute_byte_offset); + FLAC__StreamDecoderTellStatus tellCallback( + FLAC__uint64 *absolute_byte_offset); + FLAC__StreamDecoderLengthStatus lengthCallback( + FLAC__uint64 *stream_length); + FLAC__bool eofCallback(); + FLAC__StreamDecoderWriteStatus writeCallback( + const FLAC__Frame *frame, const FLAC__int32 * const buffer[]); + void metadataCallback(const FLAC__StreamMetadata *metadata); + void errorCallback(FLAC__StreamDecoderErrorStatus status); + + // FLAC parser callbacks as C-callable functions + static FLAC__StreamDecoderReadStatus read_callback( + const FLAC__StreamDecoder *decoder, + FLAC__byte buffer[], size_t *bytes, + void *client_data); + static FLAC__StreamDecoderSeekStatus seek_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 absolute_byte_offset, + void *client_data); + static FLAC__StreamDecoderTellStatus tell_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *absolute_byte_offset, + void *client_data); + static FLAC__StreamDecoderLengthStatus length_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *stream_length, + void *client_data); + static FLAC__bool eof_callback( + const FLAC__StreamDecoder *decoder, + void *client_data); + static FLAC__StreamDecoderWriteStatus write_callback( + const FLAC__StreamDecoder *decoder, + const FLAC__Frame *frame, const FLAC__int32 * const buffer[], + void *client_data); + static void metadata_callback( + const FLAC__StreamDecoder *decoder, + const FLAC__StreamMetadata *metadata, + void *client_data); + static void error_callback( + const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, + void *client_data); + +}; + +// The FLAC parser calls our C++ static callbacks using C calling conventions, +// inside FLAC__stream_decoder_process_until_end_of_metadata +// and FLAC__stream_decoder_process_single. +// We immediately then call our corresponding C++ instance methods +// with the same parameter list, but discard redundant information. + +FLAC__StreamDecoderReadStatus FLACParser::read_callback( + const FLAC__StreamDecoder * /* decoder */, FLAC__byte buffer[], + size_t *bytes, void *client_data) +{ + return ((FLACParser *) client_data)->readCallback(buffer, bytes); +} + +FLAC__StreamDecoderSeekStatus FLACParser::seek_callback( + const FLAC__StreamDecoder * /* decoder */, + FLAC__uint64 absolute_byte_offset, void *client_data) +{ + return ((FLACParser *) client_data)->seekCallback(absolute_byte_offset); +} + +FLAC__StreamDecoderTellStatus FLACParser::tell_callback( + const FLAC__StreamDecoder * /* decoder */, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + return ((FLACParser *) client_data)->tellCallback(absolute_byte_offset); +} + +FLAC__StreamDecoderLengthStatus FLACParser::length_callback( + const FLAC__StreamDecoder * /* decoder */, + FLAC__uint64 *stream_length, void *client_data) +{ + return ((FLACParser *) client_data)->lengthCallback(stream_length); +} + +FLAC__bool FLACParser::eof_callback( + const FLAC__StreamDecoder * /* decoder */, void *client_data) +{ + return ((FLACParser *) client_data)->eofCallback(); +} + +FLAC__StreamDecoderWriteStatus FLACParser::write_callback( + const FLAC__StreamDecoder * /* decoder */, const FLAC__Frame *frame, + const FLAC__int32 * const buffer[], void *client_data) +{ + return ((FLACParser *) client_data)->writeCallback(frame, buffer); +} + +void FLACParser::metadata_callback( + const FLAC__StreamDecoder * /* decoder */, + const FLAC__StreamMetadata *metadata, void *client_data) +{ + ((FLACParser *) client_data)->metadataCallback(metadata); +} + +void FLACParser::error_callback( + const FLAC__StreamDecoder * /* decoder */, + FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + ((FLACParser *) client_data)->errorCallback(status); +} + +// These are the corresponding callbacks with C++ calling conventions + +FLAC__StreamDecoderReadStatus FLACParser::readCallback( + FLAC__byte buffer[], size_t *bytes) +{ + size_t requested = *bytes; + ssize_t actual = mDataSource->readAt(mCurrentPos, buffer, requested); + if (0 > actual) { + *bytes = 0; + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } else if (0 == actual) { + *bytes = 0; + mEOF = true; + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } else { + assert(actual <= requested); + *bytes = actual; + mCurrentPos += actual; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } +} + +FLAC__StreamDecoderSeekStatus FLACParser::seekCallback( + FLAC__uint64 absolute_byte_offset) +{ + mCurrentPos = absolute_byte_offset; + mEOF = false; + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +FLAC__StreamDecoderTellStatus FLACParser::tellCallback( + FLAC__uint64 *absolute_byte_offset) +{ + *absolute_byte_offset = mCurrentPos; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +FLAC__StreamDecoderLengthStatus FLACParser::lengthCallback( + FLAC__uint64 *stream_length) +{ + off64_t size; + if (OK == mDataSource->getSize(&size)) { + *stream_length = size; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + } else { + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + } +} + +FLAC__bool FLACParser::eofCallback() +{ + return mEOF; +} + +FLAC__StreamDecoderWriteStatus FLACParser::writeCallback( + const FLAC__Frame *frame, const FLAC__int32 * const buffer[]) +{ + if (mWriteRequested) { + mWriteRequested = false; + // FLAC parser doesn't free or realloc buffer until next frame or finish + mWriteHeader = frame->header; + memmove(mWriteBuffer, buffer, sizeof(const FLAC__int32 * const) * getChannels()); + mWriteCompleted = true; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } else { + ALOGE("FLACParser::writeCallback unexpected"); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } +} + +void FLACParser::metadataCallback(const FLAC__StreamMetadata *metadata) +{ + switch (metadata->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + if (!mStreamInfoValid) { + mStreamInfo = metadata->data.stream_info; + mStreamInfoValid = true; + } else { + ALOGE("FLACParser::metadataCallback unexpected STREAMINFO"); + } + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + { + const FLAC__StreamMetadata_VorbisComment *vc; + vc = &metadata->data.vorbis_comment; + for (FLAC__uint32 i = 0; i < vc->num_comments; ++i) { + FLAC__StreamMetadata_VorbisComment_Entry *vce; + vce = &vc->comments[i]; + if (mFileMetadata != 0 && vce->entry != NULL) { + parseVorbisComment(mFileMetadata, (const char *) vce->entry, + vce->length); + } + } + } + break; + case FLAC__METADATA_TYPE_PICTURE: + if (mFileMetadata != 0) { + const FLAC__StreamMetadata_Picture *p = &metadata->data.picture; + mFileMetadata->setData(kKeyAlbumArt, + MetaData::TYPE_NONE, p->data, p->data_length); + mFileMetadata->setCString(kKeyAlbumArtMIME, p->mime_type); + } + break; + default: + ALOGW("FLACParser::metadataCallback unexpected type %u", metadata->type); + break; + } +} + +void FLACParser::errorCallback(FLAC__StreamDecoderErrorStatus status) +{ + ALOGE("FLACParser::errorCallback status=%d", status); + mErrorStatus = status; +} + +// Copy samples from FLAC native 32-bit non-interleaved to 16-bit interleaved. +// These are candidates for optimization if needed. + +static void copyMono8( + short *dst, + const int * src[FLACParser::kMaxChannels], + unsigned nSamples, + unsigned /* nChannels */) { + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i] << 8; + } +} + +static void copyStereo8( + short *dst, + const int * src[FLACParser::kMaxChannels], + unsigned nSamples, + unsigned /* nChannels */) { + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i] << 8; + *dst++ = src[1][i] << 8; + } +} + +static void copyMultiCh8(short *dst, const int * src[FLACParser::kMaxChannels], unsigned nSamples, unsigned nChannels) +{ + for (unsigned i = 0; i < nSamples; ++i) { + for (unsigned c = 0; c < nChannels; ++c) { + *dst++ = src[c][i] << 8; + } + } +} + +static void copyMono16( + short *dst, + const int * src[FLACParser::kMaxChannels], + unsigned nSamples, + unsigned /* nChannels */) { + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i]; + } +} + +static void copyStereo16( + short *dst, + const int * src[FLACParser::kMaxChannels], + unsigned nSamples, + unsigned /* nChannels */) { + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i]; + *dst++ = src[1][i]; + } +} + +static void copyMultiCh16(short *dst, const int * src[FLACParser::kMaxChannels], unsigned nSamples, unsigned nChannels) +{ + for (unsigned i = 0; i < nSamples; ++i) { + for (unsigned c = 0; c < nChannels; ++c) { + *dst++ = src[c][i]; + } + } +} + +// 24-bit versions should do dithering or noise-shaping, here or in AudioFlinger + +static void copyMono24( + short *dst, + const int * src[FLACParser::kMaxChannels], + unsigned nSamples, + unsigned /* nChannels */) { + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i] >> 8; + } +} + +static void copyStereo24( + short *dst, + const int * src[FLACParser::kMaxChannels], + unsigned nSamples, + unsigned /* nChannels */) { + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i] >> 8; + *dst++ = src[1][i] >> 8; + } +} + +static void copyMultiCh24(short *dst, const int * src[FLACParser::kMaxChannels], unsigned nSamples, unsigned nChannels) +{ + for (unsigned i = 0; i < nSamples; ++i) { + for (unsigned c = 0; c < nChannels; ++c) { + *dst++ = src[c][i] >> 8; + } + } +} + +static void copyTrespass( + short * /* dst */, + const int *[FLACParser::kMaxChannels] /* src */, + unsigned /* nSamples */, + unsigned /* nChannels */) { + TRESPASS(); +} + +// FLACParser + +FLACParser::FLACParser( + DataSourceBase *dataSource, + MetaDataBase *fileMetadata, + MetaDataBase *trackMetadata) + : mDataSource(dataSource), + mFileMetadata(fileMetadata), + mTrackMetadata(trackMetadata), + mInitCheck(false), + mMaxBufferSize(0), + mGroup(NULL), + mCopy(copyTrespass), + mDecoder(NULL), + mCurrentPos(0LL), + mEOF(false), + mStreamInfoValid(false), + mWriteRequested(false), + mWriteCompleted(false), + mErrorStatus((FLAC__StreamDecoderErrorStatus) -1) +{ + ALOGV("FLACParser::FLACParser"); + memset(&mStreamInfo, 0, sizeof(mStreamInfo)); + memset(&mWriteHeader, 0, sizeof(mWriteHeader)); + mInitCheck = init(); +} + +FLACParser::~FLACParser() +{ + ALOGV("FLACParser::~FLACParser"); + if (mDecoder != NULL) { + FLAC__stream_decoder_delete(mDecoder); + mDecoder = NULL; + } +} + +status_t FLACParser::init() +{ + // setup libFLAC parser + mDecoder = FLAC__stream_decoder_new(); + if (mDecoder == NULL) { + // The new should succeed, since probably all it does is a malloc + // that always succeeds in Android. But to avoid dependence on the + // libFLAC internals, we check and log here. + ALOGE("new failed"); + return NO_INIT; + } + FLAC__stream_decoder_set_md5_checking(mDecoder, false); + FLAC__stream_decoder_set_metadata_ignore_all(mDecoder); + FLAC__stream_decoder_set_metadata_respond( + mDecoder, FLAC__METADATA_TYPE_STREAMINFO); + FLAC__stream_decoder_set_metadata_respond( + mDecoder, FLAC__METADATA_TYPE_PICTURE); + FLAC__stream_decoder_set_metadata_respond( + mDecoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__StreamDecoderInitStatus initStatus; + initStatus = FLAC__stream_decoder_init_stream( + mDecoder, + read_callback, seek_callback, tell_callback, + length_callback, eof_callback, write_callback, + metadata_callback, error_callback, (void *) this); + if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + // A failure here probably indicates a programming error and so is + // unlikely to happen. But we check and log here similarly to above. + ALOGE("init_stream failed %d", initStatus); + return NO_INIT; + } + // parse all metadata + if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) { + ALOGE("end_of_metadata failed"); + return NO_INIT; + } + if (mStreamInfoValid) { + // check channel count + if (getChannels() == 0 || getChannels() > kMaxChannels) { + ALOGE("unsupported channel count %u", getChannels()); + return NO_INIT; + } + // check bit depth + switch (getBitsPerSample()) { + case 8: + case 16: + case 24: + break; + default: + ALOGE("unsupported bits per sample %u", getBitsPerSample()); + return NO_INIT; + } + // check sample rate + switch (getSampleRate()) { + case 8000: + case 11025: + case 12000: + case 16000: + case 22050: + case 24000: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + break; + default: + ALOGE("unsupported sample rate %u", getSampleRate()); + return NO_INIT; + } + // configure the appropriate copy function, defaulting to trespass + static const struct { + unsigned mChannels; + unsigned mBitsPerSample; + void (*mCopy)(short *dst, const int * src[kMaxChannels], unsigned nSamples, unsigned nChannels); + } table[] = { + { 1, 8, copyMono8 }, + { 2, 8, copyStereo8 }, + { 8, 8, copyMultiCh8 }, + { 1, 16, copyMono16 }, + { 2, 16, copyStereo16 }, + { 8, 16, copyMultiCh16 }, + { 1, 24, copyMono24 }, + { 2, 24, copyStereo24 }, + { 8, 24, copyMultiCh24 }, + }; + for (unsigned i = 0; i < sizeof(table)/sizeof(table[0]); ++i) { + if (table[i].mChannels >= getChannels() && + table[i].mBitsPerSample == getBitsPerSample()) { + mCopy = table[i].mCopy; + break; + } + } + // populate track metadata + if (mTrackMetadata != 0) { + mTrackMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + mTrackMetadata->setInt32(kKeyChannelCount, getChannels()); + mTrackMetadata->setInt32(kKeySampleRate, getSampleRate()); + mTrackMetadata->setInt32(kKeyPcmEncoding, kAudioEncodingPcm16bit); + // sample rate is non-zero, so division by zero not possible + mTrackMetadata->setInt64(kKeyDuration, + (getTotalSamples() * 1000000LL) / getSampleRate()); + } + } else { + ALOGE("missing STREAMINFO"); + return NO_INIT; + } + if (mFileMetadata != 0) { + mFileMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_FLAC); + } + return OK; +} + +void FLACParser::allocateBuffers() +{ + CHECK(mGroup == NULL); + mGroup = new MediaBufferGroup; + mMaxBufferSize = getMaxBlockSize() * getChannels() * sizeof(short); + mGroup->add_buffer(MediaBufferBase::Create(mMaxBufferSize)); +} + +void FLACParser::releaseBuffers() +{ + CHECK(mGroup != NULL); + delete mGroup; + mGroup = NULL; +} + +MediaBufferBase *FLACParser::readBuffer(bool doSeek, FLAC__uint64 sample) +{ + mWriteRequested = true; + mWriteCompleted = false; + if (doSeek) { + // We implement the seek callback, so this works without explicit flush + if (!FLAC__stream_decoder_seek_absolute(mDecoder, sample)) { + ALOGE("FLACParser::readBuffer seek to sample %lld failed", (long long)sample); + return NULL; + } + ALOGV("FLACParser::readBuffer seek to sample %lld succeeded", (long long)sample); + } else { + if (!FLAC__stream_decoder_process_single(mDecoder)) { + ALOGE("FLACParser::readBuffer process_single failed"); + return NULL; + } + } + if (!mWriteCompleted) { + ALOGV("FLACParser::readBuffer write did not complete"); + return NULL; + } + // verify that block header keeps the promises made by STREAMINFO + unsigned blocksize = mWriteHeader.blocksize; + if (blocksize == 0 || blocksize > getMaxBlockSize()) { + ALOGE("FLACParser::readBuffer write invalid blocksize %u", blocksize); + return NULL; + } + if (mWriteHeader.sample_rate != getSampleRate() || + mWriteHeader.channels != getChannels() || + mWriteHeader.bits_per_sample != getBitsPerSample()) { + ALOGE("FLACParser::readBuffer write changed parameters mid-stream: %d/%d/%d -> %d/%d/%d", + getSampleRate(), getChannels(), getBitsPerSample(), + mWriteHeader.sample_rate, mWriteHeader.channels, mWriteHeader.bits_per_sample); + return NULL; + } + // acquire a media buffer + CHECK(mGroup != NULL); + MediaBufferBase *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return NULL; + } + size_t bufferSize = blocksize * getChannels() * sizeof(short); + CHECK(bufferSize <= mMaxBufferSize); + short *data = (short *) buffer->data(); + buffer->set_range(0, bufferSize); + // copy PCM from FLAC write buffer to our media buffer, with interleaving + (*mCopy)(data, mWriteBuffer, blocksize, getChannels()); + // fill in buffer metadata + CHECK(mWriteHeader.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER); + FLAC__uint64 sampleNumber = mWriteHeader.number.sample_number; + int64_t timeUs = (1000000LL * sampleNumber) / getSampleRate(); + buffer->meta_data().setInt64(kKeyTime, timeUs); + buffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + return buffer; +} + +// FLACsource + +FLACSource::FLACSource( + DataSourceBase *dataSource, + MetaDataBase &trackMetadata) + : mDataSource(dataSource), + mTrackMetadata(trackMetadata), + mParser(0), + mInitCheck(false), + mStarted(false) +{ + ALOGV("FLACSource::FLACSource"); + // re-use the same track metadata passed into constructor from FLACExtractor + mParser = new FLACParser(mDataSource); + mInitCheck = mParser->initCheck(); +} + +FLACSource::~FLACSource() +{ + ALOGV("~FLACSource::FLACSource"); + if (mStarted) { + stop(); + } + delete mParser; +} + +status_t FLACSource::start(MetaDataBase * /* params */) +{ + ALOGV("FLACSource::start"); + + CHECK(!mStarted); + mParser->allocateBuffers(); + mStarted = true; + + return OK; +} + +status_t FLACSource::stop() +{ + ALOGV("FLACSource::stop"); + + CHECK(mStarted); + mParser->releaseBuffers(); + mStarted = false; + + return OK; +} + +status_t FLACSource::getFormat(MetaDataBase &meta) +{ + meta = mTrackMetadata; + return OK; +} + +status_t FLACSource::read( + MediaBufferBase **outBuffer, const ReadOptions *options) +{ + MediaBufferBase *buffer; + // process an optional seek request + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if ((NULL != options) && options->getSeekTo(&seekTimeUs, &mode)) { + FLAC__uint64 sample; + if (seekTimeUs <= 0LL) { + sample = 0LL; + } else { + // sample and total samples are both zero-based, and seek to EOF ok + sample = (seekTimeUs * mParser->getSampleRate()) / 1000000LL; + if (sample >= mParser->getTotalSamples()) { + sample = mParser->getTotalSamples(); + } + } + buffer = mParser->readBuffer(sample); + // otherwise read sequentially + } else { + buffer = mParser->readBuffer(); + } + *outBuffer = buffer; + return buffer != NULL ? (status_t) OK : (status_t) ERROR_END_OF_STREAM; +} + +// FLACExtractor + +FLACExtractor::FLACExtractor( + DataSourceBase *dataSource) + : mDataSource(dataSource), + mParser(nullptr), + mInitCheck(false) +{ + ALOGV("FLACExtractor::FLACExtractor"); + // FLACParser will fill in the metadata for us + mParser = new FLACParser(mDataSource, &mFileMetadata, &mTrackMetadata); + mInitCheck = mParser->initCheck(); +} + +FLACExtractor::~FLACExtractor() +{ + ALOGV("~FLACExtractor::FLACExtractor"); + delete mParser; +} + +size_t FLACExtractor::countTracks() +{ + return mInitCheck == OK ? 1 : 0; +} + +MediaTrack *FLACExtractor::getTrack(size_t index) +{ + if (mInitCheck != OK || index > 0) { + return NULL; + } + return new FLACSource(mDataSource, mTrackMetadata); +} + +status_t FLACExtractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t /* flags */) { + if (mInitCheck != OK || index > 0) { + return UNKNOWN_ERROR; + } + meta = mTrackMetadata; + return OK; +} + +status_t FLACExtractor::getMetaData(MetaDataBase &meta) +{ + meta = mFileMetadata; + return OK; +} + +// Sniffer + +bool SniffFLAC(DataSourceBase *source, float *confidence) +{ + // first 4 is the signature word + // second 4 is the sizeof STREAMINFO + // 042 is the mandatory STREAMINFO + // no need to read rest of the header, as a premature EOF will be caught later + uint8_t header[4+4]; + if (source->readAt(0, header, sizeof(header)) != sizeof(header) + || memcmp("fLaC\0\0\0\042", header, 4+4)) + { + return false; + } + + *confidence = 0.5; + + return true; +} + + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("1364b048-cc45-4fda-9934-327d0ebf9829"), + 1, + "FLAC Extractor", + []( + DataSourceBase *source, + float *confidence, + void **, + MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc { + if (SniffFLAC(source, confidence)) { + return []( + DataSourceBase *source, + void *) -> MediaExtractor* { + return new FLACExtractor(source);}; + } + return NULL; + } + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/flac/FLACExtractor.h b/media/extractors/flac/FLACExtractor.h new file mode 100644 index 0000000..7fb6ec6 --- /dev/null +++ b/media/extractors/flac/FLACExtractor.h
@@ -0,0 +1,62 @@ +/* + * 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 FLAC_EXTRACTOR_H_ +#define FLAC_EXTRACTOR_H_ + +#include <media/DataSourceBase.h> +#include <media/MediaExtractor.h> +#include <media/stagefright/MetaDataBase.h> +#include <utils/String8.h> + +namespace android { + +class FLACParser; + +class FLACExtractor : public MediaExtractor { + +public: + explicit FLACExtractor(DataSourceBase *source); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + virtual const char * name() { return "FLACExtractor"; } + +protected: + virtual ~FLACExtractor(); + +private: + DataSourceBase *mDataSource; + FLACParser *mParser; + status_t mInitCheck; + MetaDataBase mFileMetadata; + + // There is only one track + MetaDataBase mTrackMetadata; + + FLACExtractor(const FLACExtractor &); + FLACExtractor &operator=(const FLACExtractor &); + +}; + +bool SniffFLAC(DataSourceBase *source, float *confidence); + +} // namespace android + +#endif // FLAC_EXTRACTOR_H_
diff --git a/media/libstagefright/codecs/on2/h264dec/MODULE_LICENSE_APACHE2 b/media/extractors/flac/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/libstagefright/codecs/on2/h264dec/MODULE_LICENSE_APACHE2 copy to media/extractors/flac/MODULE_LICENSE_APACHE2
diff --git a/media/libstagefright/codecs/on2/h264dec/NOTICE b/media/extractors/flac/NOTICE similarity index 100% copy from media/libstagefright/codecs/on2/h264dec/NOTICE copy to media/extractors/flac/NOTICE
diff --git a/media/extractors/flac/exports.lds b/media/extractors/flac/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/flac/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/midi/Android.bp b/media/extractors/midi/Android.bp new file mode 100644 index 0000000..fde09df18 --- /dev/null +++ b/media/extractors/midi/Android.bp
@@ -0,0 +1,42 @@ +cc_library_shared { + + srcs: ["MidiExtractor.cpp"], + + include_dirs: [ + "frameworks/av/media/libstagefright/include", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + ], + + static_libs: [ + "libmedia_midiiowrapper", + "libsonivox", + "libstagefright_foundation" + ], + name: "libmidiextractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/libstagefright/matroska/MODULE_LICENSE_APACHE2 b/media/extractors/midi/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/libstagefright/matroska/MODULE_LICENSE_APACHE2 copy to media/extractors/midi/MODULE_LICENSE_APACHE2
diff --git a/media/extractors/midi/MidiExtractor.cpp b/media/extractors/midi/MidiExtractor.cpp new file mode 100644 index 0000000..a30b6f8 --- /dev/null +++ b/media/extractors/midi/MidiExtractor.cpp
@@ -0,0 +1,353 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MidiExtractor" +#include <utils/Log.h> + +#include "MidiExtractor.h" + +#include <media/MidiIoWrapper.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/MediaTrack.h> +#include <libsonivox/eas_reverb.h> + +namespace android { + +// how many Sonivox output buffers to aggregate into one MediaBufferBase +static const int NUM_COMBINE_BUFFERS = 4; + +class MidiSource : public MediaTrack { + +public: + MidiSource( + MidiEngine &engine, + MetaDataBase &trackMetadata); + + virtual status_t start(MetaDataBase *params); + virtual status_t stop(); + virtual status_t getFormat(MetaDataBase&); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~MidiSource(); + +private: + MidiEngine &mEngine; + MetaDataBase &mTrackMetadata; + bool mInitCheck; + bool mStarted; + + status_t init(); + + // no copy constructor or assignment + MidiSource(const MidiSource &); + MidiSource &operator=(const MidiSource &); + +}; + + +// Midisource + +MidiSource::MidiSource( + MidiEngine &engine, + MetaDataBase &trackMetadata) + : mEngine(engine), + mTrackMetadata(trackMetadata), + mInitCheck(false), + mStarted(false) +{ + ALOGV("MidiSource ctor"); + mInitCheck = init(); +} + +MidiSource::~MidiSource() +{ + ALOGV("MidiSource dtor"); + if (mStarted) { + stop(); + } +} + +status_t MidiSource::start(MetaDataBase * /* params */) +{ + ALOGV("MidiSource::start"); + + CHECK(!mStarted); + mStarted = true; + mEngine.allocateBuffers(); + return OK; +} + +status_t MidiSource::stop() +{ + ALOGV("MidiSource::stop"); + + CHECK(mStarted); + mStarted = false; + mEngine.releaseBuffers(); + + return OK; +} + +status_t MidiSource::getFormat(MetaDataBase &meta) +{ + meta = mTrackMetadata; + return OK; +} + +status_t MidiSource::read( + MediaBufferBase **outBuffer, const ReadOptions *options) +{ + ALOGV("MidiSource::read"); + MediaBufferBase *buffer; + // process an optional seek request + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if ((NULL != options) && options->getSeekTo(&seekTimeUs, &mode)) { + if (seekTimeUs <= 0LL) { + seekTimeUs = 0LL; + } + mEngine.seekTo(seekTimeUs); + } + buffer = mEngine.readBuffer(); + *outBuffer = buffer; + ALOGV("MidiSource::read %p done", this); + return buffer != NULL ? (status_t) OK : (status_t) ERROR_END_OF_STREAM; +} + +status_t MidiSource::init() +{ + ALOGV("MidiSource::init"); + return OK; +} + +// MidiEngine + +MidiEngine::MidiEngine(DataSourceBase *dataSource, + MetaDataBase *fileMetadata, + MetaDataBase *trackMetadata) : + mGroup(NULL), + mEasData(NULL), + mEasHandle(NULL), + mEasConfig(NULL), + mIsInitialized(false) { + mIoWrapper = new MidiIoWrapper(dataSource); + // spin up a new EAS engine + EAS_I32 temp; + EAS_RESULT result = EAS_Init(&mEasData); + + if (result == EAS_SUCCESS) { + result = EAS_OpenFile(mEasData, mIoWrapper->getLocator(), &mEasHandle); + } + if (result == EAS_SUCCESS) { + result = EAS_Prepare(mEasData, mEasHandle); + } + if (result == EAS_SUCCESS) { + result = EAS_ParseMetaData(mEasData, mEasHandle, &temp); + } + + if (result != EAS_SUCCESS) { + return; + } + + if (fileMetadata != NULL) { + fileMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MIDI); + } + + if (trackMetadata != NULL) { + trackMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + trackMetadata->setInt64(kKeyDuration, 1000ll * temp); // milli->micro + mEasConfig = EAS_Config(); + trackMetadata->setInt32(kKeySampleRate, mEasConfig->sampleRate); + trackMetadata->setInt32(kKeyChannelCount, mEasConfig->numChannels); + trackMetadata->setInt32(kKeyPcmEncoding, kAudioEncodingPcm16bit); + } + mIsInitialized = true; +} + +MidiEngine::~MidiEngine() { + if (mEasHandle) { + EAS_CloseFile(mEasData, mEasHandle); + } + if (mEasData) { + EAS_Shutdown(mEasData); + } + delete mGroup; + delete mIoWrapper; +} + +status_t MidiEngine::initCheck() { + return mIsInitialized ? OK : UNKNOWN_ERROR; +} + +status_t MidiEngine::allocateBuffers() { + // select reverb preset and enable + EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER); + EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE); + + mGroup = new MediaBufferGroup; + int bufsize = sizeof(EAS_PCM) + * mEasConfig->mixBufferSize * mEasConfig->numChannels * NUM_COMBINE_BUFFERS; + ALOGV("using %d byte buffer", bufsize); + mGroup->add_buffer(MediaBufferBase::Create(bufsize)); + return OK; +} + +status_t MidiEngine::releaseBuffers() { + delete mGroup; + mGroup = NULL; + return OK; +} + +status_t MidiEngine::seekTo(int64_t positionUs) { + ALOGV("seekTo %lld", (long long)positionUs); + EAS_RESULT result = EAS_Locate(mEasData, mEasHandle, positionUs / 1000, false); + return result == EAS_SUCCESS ? OK : UNKNOWN_ERROR; +} + +MediaBufferBase* MidiEngine::readBuffer() { + EAS_STATE state; + EAS_State(mEasData, mEasHandle, &state); + if ((state == EAS_STATE_STOPPED) || (state == EAS_STATE_ERROR)) { + return NULL; + } + MediaBufferBase *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + ALOGE("readBuffer: no buffer"); + return NULL; + } + EAS_I32 timeMs; + EAS_GetLocation(mEasData, mEasHandle, &timeMs); + int64_t timeUs = 1000ll * timeMs; + buffer->meta_data().setInt64(kKeyTime, timeUs); + + EAS_PCM* p = (EAS_PCM*) buffer->data(); + int numBytesOutput = 0; + for (int i = 0; i < NUM_COMBINE_BUFFERS; i++) { + EAS_I32 numRendered; + EAS_RESULT result = EAS_Render(mEasData, p, mEasConfig->mixBufferSize, &numRendered); + if (result != EAS_SUCCESS) { + ALOGE("EAS_Render() returned %ld, numBytesOutput = %d", result, numBytesOutput); + buffer->release(); + return NULL; // Stop processing to prevent infinite loops. + } + p += numRendered * mEasConfig->numChannels; + numBytesOutput += numRendered * mEasConfig->numChannels * sizeof(EAS_PCM); + } + buffer->set_range(0, numBytesOutput); + ALOGV("readBuffer: returning %zd in buffer %p", buffer->range_length(), buffer); + return buffer; +} + + +// MidiExtractor + +MidiExtractor::MidiExtractor( + DataSourceBase *dataSource) + : mDataSource(dataSource), + mInitCheck(false) +{ + ALOGV("MidiExtractor ctor"); + mEngine = new MidiEngine(mDataSource, &mFileMetadata, &mTrackMetadata); + mInitCheck = mEngine->initCheck(); +} + +MidiExtractor::~MidiExtractor() +{ + ALOGV("MidiExtractor dtor"); +} + +size_t MidiExtractor::countTracks() +{ + return mInitCheck == OK ? 1 : 0; +} + +MediaTrack *MidiExtractor::getTrack(size_t index) +{ + if (mInitCheck != OK || index > 0) { + return NULL; + } + return new MidiSource(*mEngine, mTrackMetadata); +} + +status_t MidiExtractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t /* flags */) { + ALOGV("MidiExtractor::getTrackMetaData"); + if (mInitCheck != OK || index > 0) { + return UNKNOWN_ERROR; + } + meta = mTrackMetadata; + return OK; +} + +status_t MidiExtractor::getMetaData(MetaDataBase &meta) +{ + ALOGV("MidiExtractor::getMetaData"); + meta = mFileMetadata; + return OK; +} + +// Sniffer + +bool SniffMidi(DataSourceBase *source, float *confidence) +{ + MidiEngine p(source, NULL, NULL); + if (p.initCheck() == OK) { + *confidence = 0.8; + ALOGV("SniffMidi: yes"); + return true; + } + ALOGV("SniffMidi: no"); + return false; + +} + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("ef6cca0a-f8a2-43e6-ba5f-dfcd7c9a7ef2"), + 1, + "MIDI Extractor", + []( + DataSourceBase *source, + float *confidence, + void **, + MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc { + if (SniffMidi(source, confidence)) { + return []( + DataSourceBase *source, + void *) -> MediaExtractor* { + return new MidiExtractor(source);}; + } + return NULL; + } + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/midi/MidiExtractor.h b/media/extractors/midi/MidiExtractor.h new file mode 100644 index 0000000..244dd0f --- /dev/null +++ b/media/extractors/midi/MidiExtractor.h
@@ -0,0 +1,95 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MIDI_EXTRACTOR_H_ +#define MIDI_EXTRACTOR_H_ + +#include <media/DataSourceBase.h> +#include <media/MediaExtractor.h> +#include <media/stagefright/MediaBufferBase.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MetaDataBase.h> +#include <media/MidiIoWrapper.h> +#include <utils/String8.h> +#include <libsonivox/eas.h> + +namespace android { + +class MidiEngine { +public: + explicit MidiEngine(DataSourceBase *dataSource, + MetaDataBase *fileMetadata, + MetaDataBase *trackMetadata); + ~MidiEngine(); + + status_t initCheck(); + + status_t allocateBuffers(); + status_t releaseBuffers(); + status_t seekTo(int64_t positionUs); + MediaBufferBase* readBuffer(); +private: + MidiIoWrapper *mIoWrapper; + MediaBufferGroup *mGroup; + EAS_DATA_HANDLE mEasData; + EAS_HANDLE mEasHandle; + const S_EAS_LIB_CONFIG* mEasConfig; + bool mIsInitialized; +}; + +class MidiExtractor : public MediaExtractor { + +public: + explicit MidiExtractor(DataSourceBase *source); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + virtual const char * name() { return "MidiExtractor"; } + +protected: + virtual ~MidiExtractor(); + +private: + DataSourceBase *mDataSource; + status_t mInitCheck; + MetaDataBase mFileMetadata; + + // There is only one track + MetaDataBase mTrackMetadata; + + MidiEngine *mEngine; + + EAS_DATA_HANDLE mEasData; + EAS_HANDLE mEasHandle; + EAS_PCM* mAudioBuffer; + EAS_I32 mPlayTime; + EAS_I32 mDuration; + EAS_STATE mState; + EAS_FILE mFileLocator; + + MidiExtractor(const MidiExtractor &); + MidiExtractor &operator=(const MidiExtractor &); + +}; + +bool SniffMidi(DataSourceBase *source, float *confidence); + +} // namespace android + +#endif // MIDI_EXTRACTOR_H_
diff --git a/media/libstagefright/matroska/NOTICE b/media/extractors/midi/NOTICE similarity index 100% copy from media/libstagefright/matroska/NOTICE copy to media/extractors/midi/NOTICE
diff --git a/media/extractors/midi/exports.lds b/media/extractors/midi/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/midi/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/mkv/Android.bp b/media/extractors/mkv/Android.bp new file mode 100644 index 0000000..681fd35 --- /dev/null +++ b/media/extractors/mkv/Android.bp
@@ -0,0 +1,48 @@ +cc_library_shared { + + srcs: ["MatroskaExtractor.cpp"], + + include_dirs: [ + "external/flac/include", + "external/libvpx/libwebm", + "frameworks/av/media/libstagefright/flac/dec", + "frameworks/av/media/libstagefright/include", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + ], + + static_libs: [ + "libstagefright_flacdec", + "libstagefright_foundation", + "libstagefright_metadatautils", + "libwebm", + "libutils", + ], + + name: "libmkvextractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/libstagefright/matroska/MODULE_LICENSE_APACHE2 b/media/extractors/mkv/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/libstagefright/matroska/MODULE_LICENSE_APACHE2 copy to media/extractors/mkv/MODULE_LICENSE_APACHE2
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp new file mode 100644 index 0000000..d657582 --- /dev/null +++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -0,0 +1,1665 @@ +/* + * 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 "MatroskaExtractor" +#include <utils/Log.h> + +#include "FLACDecoder.h" +#include "MatroskaExtractor.h" + +#include <media/DataSourceBase.h> +#include <media/ExtractorUtils.h> +#include <media/MediaTrack.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AUtils.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ByteUtils.h> +#include <media/stagefright/foundation/ColorUtils.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaBufferBase.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MetaDataUtils.h> +#include <utils/String8.h> + +#include <arpa/inet.h> +#include <inttypes.h> +#include <vector> + +namespace android { + +struct DataSourceBaseReader : public mkvparser::IMkvReader { + explicit DataSourceBaseReader(DataSourceBase *source) + : mSource(source) { + } + + virtual int Read(long long position, long length, unsigned char* buffer) { + CHECK(position >= 0); + CHECK(length >= 0); + + if (length == 0) { + return 0; + } + + ssize_t n = mSource->readAt(position, buffer, length); + + if (n <= 0) { + return -1; + } + + return 0; + } + + virtual int Length(long long* total, long long* available) { + off64_t size; + if (mSource->getSize(&size) != OK) { + *total = -1; + *available = (long long)((1ull << 63) - 1); + + return 0; + } + + if (total) { + *total = size; + } + + if (available) { + *available = size; + } + + return 0; + } + +private: + DataSourceBase *mSource; + + DataSourceBaseReader(const DataSourceBaseReader &); + DataSourceBaseReader &operator=(const DataSourceBaseReader &); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct BlockIterator { + BlockIterator(MatroskaExtractor *extractor, unsigned long trackNum, unsigned long index); + + bool eos() const; + + void advance(); + void reset(); + + void seek( + int64_t seekTimeUs, bool isAudio, + int64_t *actualFrameTimeUs); + + const mkvparser::Block *block() const; + int64_t blockTimeUs() const; + +private: + MatroskaExtractor *mExtractor; + long long mTrackNum; + unsigned long mIndex; + + const mkvparser::Cluster *mCluster; + const mkvparser::BlockEntry *mBlockEntry; + long mBlockEntryIndex; + + void advance_l(); + + BlockIterator(const BlockIterator &); + BlockIterator &operator=(const BlockIterator &); +}; + +struct MatroskaSource : public MediaTrack { + MatroskaSource(MatroskaExtractor *extractor, size_t index); + + virtual status_t start(MetaDataBase *params); + virtual status_t stop(); + + virtual status_t getFormat(MetaDataBase &); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options); + +protected: + virtual ~MatroskaSource(); + +private: + enum Type { + AVC, + AAC, + HEVC, + OTHER + }; + + MatroskaExtractor *mExtractor; + size_t mTrackIndex; + Type mType; + bool mIsAudio; + BlockIterator mBlockIter; + ssize_t mNALSizeLen; // for type AVC or HEVC + + List<MediaBufferBase *> mPendingFrames; + + status_t advance(); + + status_t setWebmBlockCryptoInfo(MediaBufferBase *mbuf); + status_t readBlock(); + void clearPendingFrames(); + + MatroskaSource(const MatroskaSource &); + MatroskaSource &operator=(const MatroskaSource &); +}; + +const mkvparser::Track* MatroskaExtractor::TrackInfo::getTrack() const { + return mExtractor->mSegment->GetTracks()->GetTrackByNumber(mTrackNum); +} + +// This function does exactly the same as mkvparser::Cues::Find, except that it +// searches in our own track based vectors. We should not need this once mkvparser +// adds the same functionality. +const mkvparser::CuePoint::TrackPosition *MatroskaExtractor::TrackInfo::find( + long long timeNs) const { + ALOGV("mCuePoints.size %zu", mCuePoints.size()); + if (mCuePoints.empty()) { + return NULL; + } + + const mkvparser::CuePoint* cp = mCuePoints.itemAt(0); + const mkvparser::Track* track = getTrack(); + if (timeNs <= cp->GetTime(mExtractor->mSegment)) { + return cp->Find(track); + } + + // Binary searches through relevant cues; assumes cues are ordered by timecode. + // If we do detect out-of-order cues, return NULL. + size_t lo = 0; + size_t hi = mCuePoints.size(); + while (lo < hi) { + const size_t mid = lo + (hi - lo) / 2; + const mkvparser::CuePoint* const midCp = mCuePoints.itemAt(mid); + const long long cueTimeNs = midCp->GetTime(mExtractor->mSegment); + if (cueTimeNs <= timeNs) { + lo = mid + 1; + } else { + hi = mid; + } + } + + if (lo == 0) { + return NULL; + } + + cp = mCuePoints.itemAt(lo - 1); + if (cp->GetTime(mExtractor->mSegment) > timeNs) { + return NULL; + } + + return cp->Find(track); +} + +MatroskaSource::MatroskaSource( + MatroskaExtractor *extractor, size_t index) + : mExtractor(extractor), + mTrackIndex(index), + mType(OTHER), + mIsAudio(false), + mBlockIter(mExtractor, + mExtractor->mTracks.itemAt(index).mTrackNum, + index), + mNALSizeLen(-1) { + MetaDataBase &meta = mExtractor->mTracks.editItemAt(index).mMeta; + + const char *mime; + CHECK(meta.findCString(kKeyMIMEType, &mime)); + + mIsAudio = !strncasecmp("audio/", mime, 6); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { + mType = AVC; + + uint32_t dummy; + const uint8_t *avcc; + size_t avccSize; + int32_t nalSizeLen = 0; + if (meta.findInt32(kKeyNalLengthSize, &nalSizeLen)) { + if (nalSizeLen >= 0 && nalSizeLen <= 4) { + mNALSizeLen = nalSizeLen; + } + } else if (meta.findData(kKeyAVCC, &dummy, (const void **)&avcc, &avccSize) + && avccSize >= 5u) { + mNALSizeLen = 1 + (avcc[4] & 3); + ALOGV("mNALSizeLen = %zd", mNALSizeLen); + } else { + ALOGE("No mNALSizeLen"); + } + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) { + mType = HEVC; + + uint32_t dummy; + const uint8_t *hvcc; + size_t hvccSize; + if (meta.findData(kKeyHVCC, &dummy, (const void **)&hvcc, &hvccSize) + && hvccSize >= 22u) { + mNALSizeLen = 1 + (hvcc[14+7] & 3); + ALOGV("mNALSizeLen = %zu", mNALSizeLen); + } else { + ALOGE("No mNALSizeLen"); + } + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { + mType = AAC; + } +} + +MatroskaSource::~MatroskaSource() { + clearPendingFrames(); +} + +status_t MatroskaSource::start(MetaDataBase * /* params */) { + if (mType == AVC && mNALSizeLen < 0) { + return ERROR_MALFORMED; + } + + mBlockIter.reset(); + + return OK; +} + +status_t MatroskaSource::stop() { + clearPendingFrames(); + + return OK; +} + +status_t MatroskaSource::getFormat(MetaDataBase &meta) { + meta = mExtractor->mTracks.itemAt(mTrackIndex).mMeta; + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +BlockIterator::BlockIterator( + MatroskaExtractor *extractor, unsigned long trackNum, unsigned long index) + : mExtractor(extractor), + mTrackNum(trackNum), + mIndex(index), + mCluster(NULL), + mBlockEntry(NULL), + mBlockEntryIndex(0) { + reset(); +} + +bool BlockIterator::eos() const { + return mCluster == NULL || mCluster->EOS(); +} + +void BlockIterator::advance() { + Mutex::Autolock autoLock(mExtractor->mLock); + advance_l(); +} + +void BlockIterator::advance_l() { + for (;;) { + long res = mCluster->GetEntry(mBlockEntryIndex, mBlockEntry); + ALOGV("GetEntry returned %ld", res); + + long long pos; + long len; + if (res < 0) { + // Need to parse this cluster some more + + CHECK_EQ(res, mkvparser::E_BUFFER_NOT_FULL); + + res = mCluster->Parse(pos, len); + ALOGV("Parse returned %ld", res); + + if (res < 0) { + // I/O error + + ALOGE("Cluster::Parse returned result %ld", res); + + mCluster = NULL; + break; + } + + continue; + } else if (res == 0) { + // We're done with this cluster + + const mkvparser::Cluster *nextCluster; + res = mExtractor->mSegment->ParseNext( + mCluster, nextCluster, pos, len); + ALOGV("ParseNext returned %ld", res); + + if (res != 0) { + // EOF or error + + mCluster = NULL; + break; + } + + CHECK_EQ(res, 0); + CHECK(nextCluster != NULL); + CHECK(!nextCluster->EOS()); + + mCluster = nextCluster; + + res = mCluster->Parse(pos, len); + ALOGV("Parse (2) returned %ld", res); + + if (res < 0) { + // I/O error + + ALOGE("Cluster::Parse returned result %ld", res); + + mCluster = NULL; + break; + } + + mBlockEntryIndex = 0; + continue; + } + + CHECK(mBlockEntry != NULL); + CHECK(mBlockEntry->GetBlock() != NULL); + ++mBlockEntryIndex; + + if (mBlockEntry->GetBlock()->GetTrackNumber() == mTrackNum) { + break; + } + } +} + +void BlockIterator::reset() { + Mutex::Autolock autoLock(mExtractor->mLock); + + mCluster = mExtractor->mSegment->GetFirst(); + mBlockEntry = NULL; + mBlockEntryIndex = 0; + + do { + advance_l(); + } while (!eos() && block()->GetTrackNumber() != mTrackNum); +} + +void BlockIterator::seek( + int64_t seekTimeUs, bool isAudio, + int64_t *actualFrameTimeUs) { + Mutex::Autolock autoLock(mExtractor->mLock); + + *actualFrameTimeUs = -1ll; + + if (seekTimeUs > INT64_MAX / 1000ll || + seekTimeUs < INT64_MIN / 1000ll || + (mExtractor->mSeekPreRollNs > 0 && + (seekTimeUs * 1000ll) < INT64_MIN + mExtractor->mSeekPreRollNs) || + (mExtractor->mSeekPreRollNs < 0 && + (seekTimeUs * 1000ll) > INT64_MAX + mExtractor->mSeekPreRollNs)) { + ALOGE("cannot seek to %lld", (long long) seekTimeUs); + return; + } + + const int64_t seekTimeNs = seekTimeUs * 1000ll - mExtractor->mSeekPreRollNs; + + mkvparser::Segment* const pSegment = mExtractor->mSegment; + + // Special case the 0 seek to avoid loading Cues when the application + // extraneously seeks to 0 before playing. + if (seekTimeNs <= 0) { + ALOGV("Seek to beginning: %" PRId64, seekTimeUs); + mCluster = pSegment->GetFirst(); + mBlockEntryIndex = 0; + do { + advance_l(); + } while (!eos() && block()->GetTrackNumber() != mTrackNum); + return; + } + + ALOGV("Seeking to: %" PRId64, seekTimeUs); + + // If the Cues have not been located then find them. + const mkvparser::Cues* pCues = pSegment->GetCues(); + const mkvparser::SeekHead* pSH = pSegment->GetSeekHead(); + if (!pCues && pSH) { + const size_t count = pSH->GetCount(); + const mkvparser::SeekHead::Entry* pEntry; + ALOGV("No Cues yet"); + + for (size_t index = 0; index < count; index++) { + pEntry = pSH->GetEntry(index); + + if (pEntry->id == 0x0C53BB6B) { // Cues ID + long len; long long pos; + pSegment->ParseCues(pEntry->pos, pos, len); + pCues = pSegment->GetCues(); + ALOGV("Cues found"); + break; + } + } + + if (!pCues) { + ALOGE("No Cues in file"); + return; + } + } + else if (!pSH) { + ALOGE("No SeekHead"); + return; + } + + const mkvparser::CuePoint* pCP; + mkvparser::Tracks const *pTracks = pSegment->GetTracks(); + while (!pCues->DoneParsing()) { + pCues->LoadCuePoint(); + pCP = pCues->GetLast(); + CHECK(pCP); + + size_t trackCount = mExtractor->mTracks.size(); + for (size_t index = 0; index < trackCount; ++index) { + MatroskaExtractor::TrackInfo& track = mExtractor->mTracks.editItemAt(index); + const mkvparser::Track *pTrack = pTracks->GetTrackByNumber(track.mTrackNum); + if (pTrack && pTrack->GetType() == 1 && pCP->Find(pTrack)) { // VIDEO_TRACK + track.mCuePoints.push_back(pCP); + } + } + + if (pCP->GetTime(pSegment) >= seekTimeNs) { + ALOGV("Parsed past relevant Cue"); + break; + } + } + + const mkvparser::CuePoint::TrackPosition *pTP = NULL; + const mkvparser::Track *thisTrack = pTracks->GetTrackByNumber(mTrackNum); + if (thisTrack->GetType() == 1) { // video + MatroskaExtractor::TrackInfo& track = mExtractor->mTracks.editItemAt(mIndex); + pTP = track.find(seekTimeNs); + } else { + // The Cue index is built around video keyframes + unsigned long int trackCount = pTracks->GetTracksCount(); + for (size_t index = 0; index < trackCount; ++index) { + const mkvparser::Track *pTrack = pTracks->GetTrackByIndex(index); + if (pTrack && pTrack->GetType() == 1 && pCues->Find(seekTimeNs, pTrack, pCP, pTP)) { + ALOGV("Video track located at %zu", index); + break; + } + } + } + + + // Always *search* based on the video track, but finalize based on mTrackNum + if (!pTP) { + ALOGE("Did not locate the video track for seeking"); + return; + } + + mCluster = pSegment->FindOrPreloadCluster(pTP->m_pos); + + CHECK(mCluster); + CHECK(!mCluster->EOS()); + + // mBlockEntryIndex starts at 0 but m_block starts at 1 + CHECK_GT(pTP->m_block, 0); + mBlockEntryIndex = pTP->m_block - 1; + + for (;;) { + advance_l(); + + if (eos()) break; + + if (isAudio || block()->IsKey()) { + // Accept the first key frame + int64_t frameTimeUs = (block()->GetTime(mCluster) + 500LL) / 1000LL; + if (thisTrack->GetType() == 1 || frameTimeUs >= seekTimeUs) { + *actualFrameTimeUs = frameTimeUs; + ALOGV("Requested seek point: %" PRId64 " actual: %" PRId64, + seekTimeUs, *actualFrameTimeUs); + break; + } + } + } +} + +const mkvparser::Block *BlockIterator::block() const { + CHECK(!eos()); + + return mBlockEntry->GetBlock(); +} + +int64_t BlockIterator::blockTimeUs() const { + if (mCluster == NULL || mBlockEntry == NULL) { + return -1; + } + return (mBlockEntry->GetBlock()->GetTime(mCluster) + 500ll) / 1000ll; +} + +//////////////////////////////////////////////////////////////////////////////// + +static unsigned U24_AT(const uint8_t *ptr) { + return ptr[0] << 16 | ptr[1] << 8 | ptr[2]; +} + +static AString uriDebugString(const char *uri) { + // find scheme + AString scheme; + for (size_t i = 0; i < strlen(uri); i++) { + const char c = uri[i]; + if (!isascii(c)) { + break; + } else if (isalpha(c)) { + continue; + } else if (i == 0) { + // first character must be a letter + break; + } else if (isdigit(c) || c == '+' || c == '.' || c =='-') { + continue; + } else if (c != ':') { + break; + } + scheme = AString(uri, 0, i); + scheme.append("://<suppressed>"); + return scheme; + } + return AString("<no-scheme URI suppressed>"); +} + +void MatroskaSource::clearPendingFrames() { + while (!mPendingFrames.empty()) { + MediaBufferBase *frame = *mPendingFrames.begin(); + mPendingFrames.erase(mPendingFrames.begin()); + + frame->release(); + frame = NULL; + } +} + +status_t MatroskaSource::setWebmBlockCryptoInfo(MediaBufferBase *mbuf) { + if (mbuf->range_length() < 1 || mbuf->range_length() - 1 > INT32_MAX) { + // 1-byte signal + return ERROR_MALFORMED; + } + + const uint8_t *data = (const uint8_t *)mbuf->data() + mbuf->range_offset(); + bool encrypted = data[0] & 0x1; + bool partitioned = data[0] & 0x2; + if (encrypted && mbuf->range_length() < 9) { + // 1-byte signal + 8-byte IV + return ERROR_MALFORMED; + } + + MetaDataBase &meta = mbuf->meta_data(); + if (encrypted) { + uint8_t ctrCounter[16] = { 0 }; + uint32_t type; + const uint8_t *keyId; + size_t keyIdSize; + const MetaDataBase &trackMeta = mExtractor->mTracks.itemAt(mTrackIndex).mMeta; + CHECK(trackMeta.findData(kKeyCryptoKey, &type, (const void **)&keyId, &keyIdSize)); + meta.setData(kKeyCryptoKey, 0, keyId, keyIdSize); + memcpy(ctrCounter, data + 1, 8); + meta.setData(kKeyCryptoIV, 0, ctrCounter, 16); + if (partitioned) { + /* 0 1 2 3 + * 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 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Signal Byte | | + * +-+-+-+-+-+-+-+-+ IV | + * | | + * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | num_partition | Partition 0 offset -> | + * |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| + * | -> Partition 0 offset | ... | + * |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| + * | ... | Partition n-1 offset -> | + * |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| + * | -> Partition n-1 offset | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | Clear/encrypted sample data | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + if (mbuf->range_length() < 10) { + return ERROR_MALFORMED; + } + uint8_t numPartitions = data[9]; + if (mbuf->range_length() - 10 < numPartitions * sizeof(uint32_t)) { + return ERROR_MALFORMED; + } + std::vector<uint32_t> plainSizes, encryptedSizes; + uint32_t prev = 0; + uint32_t frameOffset = 10 + numPartitions * sizeof(uint32_t); + const uint32_t *partitions = reinterpret_cast<const uint32_t*>(data + 10); + for (uint32_t i = 0; i <= numPartitions; ++i) { + uint32_t p_i = i < numPartitions + ? ntohl(partitions[i]) + : (mbuf->range_length() - frameOffset); + if (p_i < prev) { + return ERROR_MALFORMED; + } + uint32_t size = p_i - prev; + prev = p_i; + if (i % 2) { + encryptedSizes.push_back(size); + } else { + plainSizes.push_back(size); + } + } + if (plainSizes.size() > encryptedSizes.size()) { + encryptedSizes.push_back(0); + } + uint32_t sizeofPlainSizes = sizeof(uint32_t) * plainSizes.size(); + uint32_t sizeofEncryptedSizes = sizeof(uint32_t) * encryptedSizes.size(); + meta.setData(kKeyPlainSizes, 0, plainSizes.data(), sizeofPlainSizes); + meta.setData(kKeyEncryptedSizes, 0, encryptedSizes.data(), sizeofEncryptedSizes); + mbuf->set_range(frameOffset, mbuf->range_length() - frameOffset); + } else { + /* + * 0 1 2 3 + * 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 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Signal Byte | | + * +-+-+-+-+-+-+-+-+ IV | + * | | + * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | | + * |-+-+-+-+-+-+-+-+ | + * : Bytes 1..N of encrypted frame : + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + int32_t plainSizes[] = { 0 }; + int32_t encryptedSizes[] = { static_cast<int32_t>(mbuf->range_length() - 9) }; + meta.setData(kKeyPlainSizes, 0, plainSizes, sizeof(plainSizes)); + meta.setData(kKeyEncryptedSizes, 0, encryptedSizes, sizeof(encryptedSizes)); + mbuf->set_range(9, mbuf->range_length() - 9); + } + } else { + /* + * 0 1 2 3 + * 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 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Signal Byte | | + * +-+-+-+-+-+-+-+-+ | + * : Bytes 1..N of unencrypted frame : + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + int32_t plainSizes[] = { static_cast<int32_t>(mbuf->range_length() - 1) }; + int32_t encryptedSizes[] = { 0 }; + meta.setData(kKeyPlainSizes, 0, plainSizes, sizeof(plainSizes)); + meta.setData(kKeyEncryptedSizes, 0, encryptedSizes, sizeof(encryptedSizes)); + mbuf->set_range(1, mbuf->range_length() - 1); + } + + return OK; +} + +status_t MatroskaSource::readBlock() { + CHECK(mPendingFrames.empty()); + + if (mBlockIter.eos()) { + return ERROR_END_OF_STREAM; + } + + const mkvparser::Block *block = mBlockIter.block(); + + int64_t timeUs = mBlockIter.blockTimeUs(); + + for (int i = 0; i < block->GetFrameCount(); ++i) { + MatroskaExtractor::TrackInfo *trackInfo = &mExtractor->mTracks.editItemAt(mTrackIndex); + const mkvparser::Block::Frame &frame = block->GetFrame(i); + size_t len = frame.len; + if (SIZE_MAX - len < trackInfo->mHeaderLen) { + return ERROR_MALFORMED; + } + + len += trackInfo->mHeaderLen; + MediaBufferBase *mbuf = MediaBufferBase::Create(len); + uint8_t *data = static_cast<uint8_t *>(mbuf->data()); + if (trackInfo->mHeader) { + memcpy(data, trackInfo->mHeader, trackInfo->mHeaderLen); + } + + mbuf->meta_data().setInt64(kKeyTime, timeUs); + mbuf->meta_data().setInt32(kKeyIsSyncFrame, block->IsKey()); + + status_t err = frame.Read(mExtractor->mReader, data + trackInfo->mHeaderLen); + if (err == OK + && mExtractor->mIsWebm + && trackInfo->mEncrypted) { + err = setWebmBlockCryptoInfo(mbuf); + } + + if (err != OK) { + mPendingFrames.clear(); + + mBlockIter.advance(); + mbuf->release(); + return err; + } + + mPendingFrames.push_back(mbuf); + } + + mBlockIter.advance(); + + return OK; +} + +status_t MatroskaSource::read( + MediaBufferBase **out, const ReadOptions *options) { + *out = NULL; + + int64_t targetSampleTimeUs = -1ll; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (options && options->getSeekTo(&seekTimeUs, &mode)) { + if (mode == ReadOptions::SEEK_FRAME_INDEX) { + return ERROR_UNSUPPORTED; + } + + if (!mExtractor->isLiveStreaming()) { + clearPendingFrames(); + + // The audio we want is located by using the Cues to seek the video + // stream to find the target Cluster then iterating to finalize for + // audio. + int64_t actualFrameTimeUs; + mBlockIter.seek(seekTimeUs, mIsAudio, &actualFrameTimeUs); + if (mode == ReadOptions::SEEK_CLOSEST) { + targetSampleTimeUs = actualFrameTimeUs; + } + } + } + + while (mPendingFrames.empty()) { + status_t err = readBlock(); + + if (err != OK) { + clearPendingFrames(); + + return err; + } + } + + MediaBufferBase *frame = *mPendingFrames.begin(); + mPendingFrames.erase(mPendingFrames.begin()); + + if ((mType != AVC && mType != HEVC) || mNALSizeLen == 0) { + if (targetSampleTimeUs >= 0ll) { + frame->meta_data().setInt64( + kKeyTargetTime, targetSampleTimeUs); + } + + *out = frame; + + return OK; + } + + // Each input frame contains one or more NAL fragments, each fragment + // is prefixed by mNALSizeLen bytes giving the fragment length, + // followed by a corresponding number of bytes containing the fragment. + // We output all these fragments into a single large buffer separated + // by startcodes (0x00 0x00 0x00 0x01). + // + // When mNALSizeLen is 0, we assume the data is already in the format + // desired. + + const uint8_t *srcPtr = + (const uint8_t *)frame->data() + frame->range_offset(); + + size_t srcSize = frame->range_length(); + + size_t dstSize = 0; + MediaBufferBase *buffer = NULL; + uint8_t *dstPtr = NULL; + + for (int32_t pass = 0; pass < 2; ++pass) { + size_t srcOffset = 0; + size_t dstOffset = 0; + while (srcOffset + mNALSizeLen <= srcSize) { + size_t NALsize; + switch (mNALSizeLen) { + case 1: NALsize = srcPtr[srcOffset]; break; + case 2: NALsize = U16_AT(srcPtr + srcOffset); break; + case 3: NALsize = U24_AT(srcPtr + srcOffset); break; + case 4: NALsize = U32_AT(srcPtr + srcOffset); break; + default: + TRESPASS(); + } + + if (srcOffset + mNALSizeLen + NALsize <= srcOffset + mNALSizeLen) { + frame->release(); + frame = NULL; + + return ERROR_MALFORMED; + } else if (srcOffset + mNALSizeLen + NALsize > srcSize) { + break; + } + + if (pass == 1) { + memcpy(&dstPtr[dstOffset], "\x00\x00\x00\x01", 4); + + if (frame != buffer) { + memcpy(&dstPtr[dstOffset + 4], + &srcPtr[srcOffset + mNALSizeLen], + NALsize); + } + } + + dstOffset += 4; // 0x00 00 00 01 + dstOffset += NALsize; + + srcOffset += mNALSizeLen + NALsize; + } + + if (srcOffset < srcSize) { + // There were trailing bytes or not enough data to complete + // a fragment. + + frame->release(); + frame = NULL; + + return ERROR_MALFORMED; + } + + if (pass == 0) { + dstSize = dstOffset; + + if (dstSize == srcSize && mNALSizeLen == 4) { + // In this special case we can re-use the input buffer by substituting + // each 4-byte nal size with a 4-byte start code + buffer = frame; + } else { + buffer = MediaBufferBase::Create(dstSize); + } + + int64_t timeUs; + CHECK(frame->meta_data().findInt64(kKeyTime, &timeUs)); + int32_t isSync; + CHECK(frame->meta_data().findInt32(kKeyIsSyncFrame, &isSync)); + + buffer->meta_data().setInt64(kKeyTime, timeUs); + buffer->meta_data().setInt32(kKeyIsSyncFrame, isSync); + + dstPtr = (uint8_t *)buffer->data(); + } + } + + if (frame != buffer) { + frame->release(); + frame = NULL; + } + + if (targetSampleTimeUs >= 0ll) { + buffer->meta_data().setInt64( + kKeyTargetTime, targetSampleTimeUs); + } + + *out = buffer; + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +MatroskaExtractor::MatroskaExtractor(DataSourceBase *source) + : mDataSource(source), + mReader(new DataSourceBaseReader(mDataSource)), + mSegment(NULL), + mExtractedThumbnails(false), + mIsWebm(false), + mSeekPreRollNs(0) { + off64_t size; + mIsLiveStreaming = + (mDataSource->flags() + & (DataSourceBase::kWantsPrefetching + | DataSourceBase::kIsCachingDataSource)) + && mDataSource->getSize(&size) != OK; + + mkvparser::EBMLHeader ebmlHeader; + long long pos; + if (ebmlHeader.Parse(mReader, pos) < 0) { + return; + } + + if (ebmlHeader.m_docType && !strcmp("webm", ebmlHeader.m_docType)) { + mIsWebm = true; + } + + long long ret = + mkvparser::Segment::CreateInstance(mReader, pos, mSegment); + + if (ret) { + CHECK(mSegment == NULL); + return; + } + + // from mkvparser::Segment::Load(), but stop at first cluster + ret = mSegment->ParseHeaders(); + if (ret == 0) { + long len; + ret = mSegment->LoadCluster(pos, len); + if (ret >= 1) { + // no more clusters + ret = 0; + } + } else if (ret > 0) { + ret = mkvparser::E_BUFFER_NOT_FULL; + } + + if (ret < 0) { + char uri[1024]; + if(!mDataSource->getUri(uri, sizeof(uri))) { + uri[0] = '\0'; + } + ALOGW("Corrupt %s source: %s", mIsWebm ? "webm" : "matroska", + uriDebugString(uri).c_str()); + delete mSegment; + mSegment = NULL; + return; + } + +#if 0 + const mkvparser::SegmentInfo *info = mSegment->GetInfo(); + ALOGI("muxing app: %s, writing app: %s", + info->GetMuxingAppAsUTF8(), + info->GetWritingAppAsUTF8()); +#endif + + addTracks(); +} + +MatroskaExtractor::~MatroskaExtractor() { + delete mSegment; + mSegment = NULL; + + delete mReader; + mReader = NULL; +} + +size_t MatroskaExtractor::countTracks() { + return mTracks.size(); +} + +MediaTrack *MatroskaExtractor::getTrack(size_t index) { + if (index >= mTracks.size()) { + return NULL; + } + + return new MatroskaSource(this, index); +} + +status_t MatroskaExtractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t flags) { + if (index >= mTracks.size()) { + return UNKNOWN_ERROR; + } + + if ((flags & kIncludeExtensiveMetaData) && !mExtractedThumbnails + && !isLiveStreaming()) { + findThumbnails(); + mExtractedThumbnails = true; + } + + meta = mTracks.itemAt(index).mMeta; + return OK; +} + +bool MatroskaExtractor::isLiveStreaming() const { + return mIsLiveStreaming; +} + +static int bytesForSize(size_t size) { + // use at most 28 bits (4 times 7) + CHECK(size <= 0xfffffff); + + if (size > 0x1fffff) { + return 4; + } else if (size > 0x3fff) { + return 3; + } else if (size > 0x7f) { + return 2; + } + return 1; +} + +static void storeSize(uint8_t *data, size_t &idx, size_t size) { + int numBytes = bytesForSize(size); + idx += numBytes; + + data += idx; + size_t next = 0; + while (numBytes--) { + *--data = (size & 0x7f) | next; + size >>= 7; + next = 0x80; + } +} + +static void addESDSFromCodecPrivate( + MetaDataBase &meta, + bool isAudio, const void *priv, size_t privSize) { + + int privSizeBytesRequired = bytesForSize(privSize); + int esdsSize2 = 14 + privSizeBytesRequired + privSize; + int esdsSize2BytesRequired = bytesForSize(esdsSize2); + int esdsSize1 = 4 + esdsSize2BytesRequired + esdsSize2; + int esdsSize1BytesRequired = bytesForSize(esdsSize1); + size_t esdsSize = 1 + esdsSize1BytesRequired + esdsSize1; + uint8_t *esds = new uint8_t[esdsSize]; + + size_t idx = 0; + esds[idx++] = 0x03; + storeSize(esds, idx, esdsSize1); + esds[idx++] = 0x00; // ES_ID + esds[idx++] = 0x00; // ES_ID + esds[idx++] = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag + esds[idx++] = 0x04; + storeSize(esds, idx, esdsSize2); + esds[idx++] = isAudio ? 0x40 // Audio ISO/IEC 14496-3 + : 0x20; // Visual ISO/IEC 14496-2 + for (int i = 0; i < 12; i++) { + esds[idx++] = 0x00; + } + esds[idx++] = 0x05; + storeSize(esds, idx, privSize); + memcpy(esds + idx, priv, privSize); + + meta.setData(kKeyESDS, 0, esds, esdsSize); + + delete[] esds; + esds = NULL; +} + +status_t addVorbisCodecInfo( + MetaDataBase &meta, + const void *_codecPrivate, size_t codecPrivateSize) { + // hexdump(_codecPrivate, codecPrivateSize); + + if (codecPrivateSize < 1) { + return ERROR_MALFORMED; + } + + const uint8_t *codecPrivate = (const uint8_t *)_codecPrivate; + + if (codecPrivate[0] != 0x02) { + return ERROR_MALFORMED; + } + + // codecInfo starts with two lengths, len1 and len2, that are + // "Xiph-style-lacing encoded"... + + size_t offset = 1; + size_t len1 = 0; + while (offset < codecPrivateSize && codecPrivate[offset] == 0xff) { + if (len1 > (SIZE_MAX - 0xff)) { + return ERROR_MALFORMED; // would overflow + } + len1 += 0xff; + ++offset; + } + if (offset >= codecPrivateSize) { + return ERROR_MALFORMED; + } + if (len1 > (SIZE_MAX - codecPrivate[offset])) { + return ERROR_MALFORMED; // would overflow + } + len1 += codecPrivate[offset++]; + + size_t len2 = 0; + while (offset < codecPrivateSize && codecPrivate[offset] == 0xff) { + if (len2 > (SIZE_MAX - 0xff)) { + return ERROR_MALFORMED; // would overflow + } + len2 += 0xff; + ++offset; + } + if (offset >= codecPrivateSize) { + return ERROR_MALFORMED; + } + if (len2 > (SIZE_MAX - codecPrivate[offset])) { + return ERROR_MALFORMED; // would overflow + } + len2 += codecPrivate[offset++]; + + if (len1 > SIZE_MAX - len2 || offset > SIZE_MAX - (len1 + len2) || + codecPrivateSize < offset + len1 + len2) { + return ERROR_MALFORMED; + } + + if (codecPrivate[offset] != 0x01) { + return ERROR_MALFORMED; + } + meta.setData(kKeyVorbisInfo, 0, &codecPrivate[offset], len1); + + offset += len1; + if (codecPrivate[offset] != 0x03) { + return ERROR_MALFORMED; + } + + offset += len2; + if (codecPrivate[offset] != 0x05) { + return ERROR_MALFORMED; + } + + meta.setData( + kKeyVorbisBooks, 0, &codecPrivate[offset], + codecPrivateSize - offset); + + return OK; +} + +static status_t addFlacMetadata( + MetaDataBase &meta, + const void *codecPrivate, size_t codecPrivateSize) { + // hexdump(codecPrivate, codecPrivateSize); + + meta.setData(kKeyFlacMetadata, 0, codecPrivate, codecPrivateSize); + + int32_t maxInputSize = 64 << 10; + FLACDecoder *flacDecoder = FLACDecoder::Create(); + if (flacDecoder != NULL + && flacDecoder->parseMetadata((const uint8_t*)codecPrivate, codecPrivateSize) == OK) { + FLAC__StreamMetadata_StreamInfo streamInfo = flacDecoder->getStreamInfo(); + maxInputSize = streamInfo.max_framesize; + if (maxInputSize == 0) { + // In case max framesize is not available, use raw data size as max framesize, + // assuming there is no expansion. + if (streamInfo.max_blocksize != 0 + && streamInfo.channels != 0 + && ((streamInfo.bits_per_sample + 7) / 8) > + INT32_MAX / streamInfo.max_blocksize / streamInfo.channels) { + delete flacDecoder; + return ERROR_MALFORMED; + } + maxInputSize = ((streamInfo.bits_per_sample + 7) / 8) + * streamInfo.max_blocksize * streamInfo.channels; + } + } + meta.setInt32(kKeyMaxInputSize, maxInputSize); + + delete flacDecoder; + return OK; +} + +status_t MatroskaExtractor::synthesizeAVCC(TrackInfo *trackInfo, size_t index) { + BlockIterator iter(this, trackInfo->mTrackNum, index); + if (iter.eos()) { + return ERROR_MALFORMED; + } + + const mkvparser::Block *block = iter.block(); + if (block->GetFrameCount() <= 0) { + return ERROR_MALFORMED; + } + + const mkvparser::Block::Frame &frame = block->GetFrame(0); + auto tmpData = heapbuffer<unsigned char>(frame.len); + long n = frame.Read(mReader, tmpData.get()); + if (n != 0) { + return ERROR_MALFORMED; + } + + if (!MakeAVCCodecSpecificData(trackInfo->mMeta, tmpData.get(), frame.len)) { + return ERROR_MALFORMED; + } + + // Override the synthesized nal length size, which is arbitrary + trackInfo->mMeta.setInt32(kKeyNalLengthSize, 0); + return OK; +} + +static inline bool isValidInt32ColourValue(long long value) { + return value != mkvparser::Colour::kValueNotPresent + && value >= INT32_MIN + && value <= INT32_MAX; +} + +static inline bool isValidUint16ColourValue(long long value) { + return value != mkvparser::Colour::kValueNotPresent + && value >= 0 + && value <= UINT16_MAX; +} + +static inline bool isValidPrimary(const mkvparser::PrimaryChromaticity *primary) { + return primary != NULL && primary->x >= 0 && primary->x <= 1 + && primary->y >= 0 && primary->y <= 1; +} + +void MatroskaExtractor::getColorInformation( + const mkvparser::VideoTrack *vtrack, MetaDataBase &meta) { + const mkvparser::Colour *color = vtrack->GetColour(); + if (color == NULL) { + return; + } + + // Color Aspects + { + int32_t primaries = 2; // ISO unspecified + int32_t transfer = 2; // ISO unspecified + int32_t coeffs = 2; // ISO unspecified + bool fullRange = false; // default + bool rangeSpecified = false; + + if (isValidInt32ColourValue(color->primaries)) { + primaries = color->primaries; + } + if (isValidInt32ColourValue(color->transfer_characteristics)) { + transfer = color->transfer_characteristics; + } + if (isValidInt32ColourValue(color->matrix_coefficients)) { + coeffs = color->matrix_coefficients; + } + if (color->range != mkvparser::Colour::kValueNotPresent + && color->range != 0 /* MKV unspecified */) { + // We only support MKV broadcast range (== limited) and full range. + // We treat all other value as the default limited range. + fullRange = color->range == 2 /* MKV fullRange */; + rangeSpecified = true; + } + + ColorAspects aspects; + ColorUtils::convertIsoColorAspectsToCodecAspects( + primaries, transfer, coeffs, fullRange, aspects); + meta.setInt32(kKeyColorPrimaries, aspects.mPrimaries); + meta.setInt32(kKeyTransferFunction, aspects.mTransfer); + meta.setInt32(kKeyColorMatrix, aspects.mMatrixCoeffs); + meta.setInt32( + kKeyColorRange, rangeSpecified ? aspects.mRange : ColorAspects::RangeUnspecified); + } + + // HDR Static Info + { + HDRStaticInfo info, nullInfo; // nullInfo is a fully unspecified static info + memset(&info, 0, sizeof(info)); + memset(&nullInfo, 0, sizeof(nullInfo)); + if (isValidUint16ColourValue(color->max_cll)) { + info.sType1.mMaxContentLightLevel = color->max_cll; + } + if (isValidUint16ColourValue(color->max_fall)) { + info.sType1.mMaxFrameAverageLightLevel = color->max_fall; + } + const mkvparser::MasteringMetadata *mastering = color->mastering_metadata; + if (mastering != NULL) { + // Convert matroska values to HDRStaticInfo equivalent values for each fully specified + // group. See CTA-681.3 section 3.2.1 for more info. + if (mastering->luminance_max >= 0.5 && mastering->luminance_max < 65535.5) { + info.sType1.mMaxDisplayLuminance = (uint16_t)(mastering->luminance_max + 0.5); + } + if (mastering->luminance_min >= 0.00005 && mastering->luminance_min < 6.55355) { + // HDRStaticInfo Type1 stores min luminance scaled 10000:1 + info.sType1.mMinDisplayLuminance = + (uint16_t)(10000 * mastering->luminance_min + 0.5); + } + // HDRStaticInfo Type1 stores primaries scaled 50000:1 + if (isValidPrimary(mastering->white_point)) { + info.sType1.mW.x = (uint16_t)(50000 * mastering->white_point->x + 0.5); + info.sType1.mW.y = (uint16_t)(50000 * mastering->white_point->y + 0.5); + } + if (isValidPrimary(mastering->r) && isValidPrimary(mastering->g) + && isValidPrimary(mastering->b)) { + info.sType1.mR.x = (uint16_t)(50000 * mastering->r->x + 0.5); + info.sType1.mR.y = (uint16_t)(50000 * mastering->r->y + 0.5); + info.sType1.mG.x = (uint16_t)(50000 * mastering->g->x + 0.5); + info.sType1.mG.y = (uint16_t)(50000 * mastering->g->y + 0.5); + info.sType1.mB.x = (uint16_t)(50000 * mastering->b->x + 0.5); + info.sType1.mB.y = (uint16_t)(50000 * mastering->b->y + 0.5); + } + } + // Only advertise static info if at least one of the groups have been specified. + if (memcmp(&info, &nullInfo, sizeof(info)) != 0) { + info.mID = HDRStaticInfo::kType1; + meta.setData(kKeyHdrStaticInfo, 'hdrS', &info, sizeof(info)); + } + } +} + +status_t MatroskaExtractor::initTrackInfo( + const mkvparser::Track *track, MetaDataBase &meta, TrackInfo *trackInfo) { + trackInfo->mTrackNum = track->GetNumber(); + trackInfo->mMeta = meta; + trackInfo->mExtractor = this; + trackInfo->mEncrypted = false; + trackInfo->mHeader = NULL; + trackInfo->mHeaderLen = 0; + + 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++) { + const mkvparser::ContentEncoding::ContentEncryption *encryption; + encryption = encoding->GetEncryptionByIndex(j); + trackInfo->mMeta.setData(kKeyCryptoKey, 0, encryption->key_id, encryption->key_id_len); + trackInfo->mEncrypted = true; + break; + } + + for(size_t j = 0; j < encoding->GetCompressionCount(); j++) { + const mkvparser::ContentEncoding::ContentCompression *compression; + compression = encoding->GetCompressionByIndex(j); + ALOGV("compression algo %llu settings_len %lld", + compression->algo, compression->settings_len); + if (compression->algo == 3 + && compression->settings + && compression->settings_len > 0) { + trackInfo->mHeader = compression->settings; + trackInfo->mHeaderLen = compression->settings_len; + } + } + } + + return OK; +} + +void MatroskaExtractor::addTracks() { + const mkvparser::Tracks *tracks = mSegment->GetTracks(); + + for (size_t index = 0; index < tracks->GetTracksCount(); ++index) { + const mkvparser::Track *track = tracks->GetTrackByIndex(index); + + if (track == NULL) { + // Apparently this is currently valid (if unexpected) behaviour + // of the mkv parser lib. + continue; + } + + const char *const codecID = track->GetCodecId(); + ALOGV("codec id = %s", codecID); + ALOGV("codec name = %s", track->GetCodecNameAsUTF8()); + + if (codecID == NULL) { + ALOGW("unknown codecID is not supported."); + continue; + } + + size_t codecPrivateSize; + const unsigned char *codecPrivate = + track->GetCodecPrivate(codecPrivateSize); + + enum { VIDEO_TRACK = 1, AUDIO_TRACK = 2 }; + + MetaDataBase meta; + + status_t err = OK; + + switch (track->GetType()) { + case VIDEO_TRACK: + { + const mkvparser::VideoTrack *vtrack = + static_cast<const mkvparser::VideoTrack *>(track); + + if (!strcmp("V_MPEG4/ISO/AVC", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); + meta.setData(kKeyAVCC, 0, codecPrivate, codecPrivateSize); + } else if (!strcmp("V_MPEGH/ISO/HEVC", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC); + if (codecPrivateSize > 0) { + meta.setData(kKeyHVCC, kTypeHVCC, codecPrivate, codecPrivateSize); + } else { + ALOGW("HEVC is detected, but does not have configuration."); + continue; + } + } else if (!strcmp("V_MPEG4/ISO/ASP", codecID)) { + if (codecPrivateSize > 0) { + meta.setCString( + kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + addESDSFromCodecPrivate( + meta, false, codecPrivate, codecPrivateSize); + } else { + ALOGW("%s is detected, but does not have configuration.", + codecID); + continue; + } + } else if (!strcmp("V_VP8", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_VP8); + } else if (!strcmp("V_VP9", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_VP9); + if (codecPrivateSize > 0) { + // 'csd-0' for VP9 is the Blob of Codec Private data as + // specified in http://www.webmproject.org/vp9/profiles/. + meta.setData( + kKeyVp9CodecPrivate, 0, codecPrivate, + codecPrivateSize); + } + } else { + ALOGW("%s is not supported.", codecID); + continue; + } + + const long long width = vtrack->GetWidth(); + const long long height = vtrack->GetHeight(); + if (width <= 0 || width > INT32_MAX) { + ALOGW("track width exceeds int32_t, %lld", width); + continue; + } + if (height <= 0 || height > INT32_MAX) { + ALOGW("track height exceeds int32_t, %lld", height); + continue; + } + meta.setInt32(kKeyWidth, (int32_t)width); + meta.setInt32(kKeyHeight, (int32_t)height); + + // setting display width/height is optional + const long long displayUnit = vtrack->GetDisplayUnit(); + const long long displayWidth = vtrack->GetDisplayWidth(); + const long long displayHeight = vtrack->GetDisplayHeight(); + if (displayWidth > 0 && displayWidth <= INT32_MAX + && displayHeight > 0 && displayHeight <= INT32_MAX) { + switch (displayUnit) { + case 0: // pixels + meta.setInt32(kKeyDisplayWidth, (int32_t)displayWidth); + meta.setInt32(kKeyDisplayHeight, (int32_t)displayHeight); + break; + case 1: // centimeters + case 2: // inches + case 3: // aspect ratio + { + // Physical layout size is treated the same as aspect ratio. + // Note: displayWidth and displayHeight are never zero as they are + // checked in the if above. + const long long computedWidth = + std::max(width, height * displayWidth / displayHeight); + const long long computedHeight = + std::max(height, width * displayHeight / displayWidth); + if (computedWidth <= INT32_MAX && computedHeight <= INT32_MAX) { + meta.setInt32(kKeyDisplayWidth, (int32_t)computedWidth); + meta.setInt32(kKeyDisplayHeight, (int32_t)computedHeight); + } + break; + } + default: // unknown display units, perhaps future version of spec. + break; + } + } + + getColorInformation(vtrack, meta); + + break; + } + + case AUDIO_TRACK: + { + const mkvparser::AudioTrack *atrack = + static_cast<const mkvparser::AudioTrack *>(track); + + if (!strcmp("A_AAC", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC); + CHECK(codecPrivateSize >= 2); + + addESDSFromCodecPrivate( + meta, true, codecPrivate, codecPrivateSize); + } else if (!strcmp("A_VORBIS", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS); + + err = addVorbisCodecInfo( + meta, codecPrivate, codecPrivateSize); + } else if (!strcmp("A_OPUS", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_OPUS); + meta.setData(kKeyOpusHeader, 0, codecPrivate, codecPrivateSize); + meta.setInt64(kKeyOpusCodecDelay, track->GetCodecDelay()); + meta.setInt64(kKeyOpusSeekPreRoll, track->GetSeekPreRoll()); + mSeekPreRollNs = track->GetSeekPreRoll(); + } else if (!strcmp("A_MPEG/L3", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); + } else if (!strcmp("A_FLAC", codecID)) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_FLAC); + err = addFlacMetadata(meta, codecPrivate, codecPrivateSize); + } else { + ALOGW("%s is not supported.", codecID); + continue; + } + + meta.setInt32(kKeySampleRate, atrack->GetSamplingRate()); + meta.setInt32(kKeyChannelCount, atrack->GetChannels()); + break; + } + + default: + continue; + } + + const char *language = track->GetLanguage(); + if (language != NULL) { + char lang[4]; + strncpy(lang, language, 3); + lang[3] = '\0'; + meta.setCString(kKeyMediaLanguage, lang); + } + + if (err != OK) { + ALOGE("skipping track, codec specific data was malformed."); + continue; + } + + long long durationNs = mSegment->GetDuration(); + meta.setInt64(kKeyDuration, (durationNs + 500) / 1000); + + mTracks.push(); + size_t n = mTracks.size() - 1; + TrackInfo *trackInfo = &mTracks.editItemAt(n); + initTrackInfo(track, meta, trackInfo); + + if (!strcmp("V_MPEG4/ISO/AVC", codecID) && codecPrivateSize == 0) { + // Attempt to recover from AVC track without codec private data + err = synthesizeAVCC(trackInfo, n); + if (err != OK) { + mTracks.pop(); + } + } + } +} + +void MatroskaExtractor::findThumbnails() { + for (size_t i = 0; i < mTracks.size(); ++i) { + TrackInfo *info = &mTracks.editItemAt(i); + + const char *mime; + CHECK(info->mMeta.findCString(kKeyMIMEType, &mime)); + + if (strncasecmp(mime, "video/", 6)) { + continue; + } + + BlockIterator iter(this, info->mTrackNum, i); + int32_t j = 0; + int64_t thumbnailTimeUs = 0; + size_t maxBlockSize = 0; + while (!iter.eos() && j < 20) { + if (iter.block()->IsKey()) { + ++j; + + size_t blockSize = 0; + for (int k = 0; k < iter.block()->GetFrameCount(); ++k) { + blockSize += iter.block()->GetFrame(k).len; + } + + if (blockSize > maxBlockSize) { + maxBlockSize = blockSize; + thumbnailTimeUs = iter.blockTimeUs(); + } + } + iter.advance(); + } + info->mMeta.setInt64(kKeyThumbnailTime, thumbnailTimeUs); + } +} + +status_t MatroskaExtractor::getMetaData(MetaDataBase &meta) { + meta.setCString( + kKeyMIMEType, + mIsWebm ? "video/webm" : MEDIA_MIMETYPE_CONTAINER_MATROSKA); + + return OK; +} + +uint32_t MatroskaExtractor::flags() const { + uint32_t x = CAN_PAUSE; + if (!isLiveStreaming()) { + x |= CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK; + } + + return x; +} + +bool SniffMatroska( + DataSourceBase *source, float *confidence) { + DataSourceBaseReader reader(source); + mkvparser::EBMLHeader ebmlHeader; + long long pos; + if (ebmlHeader.Parse(&reader, pos) < 0) { + return false; + } + + *confidence = 0.6; + + return true; +} + + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("abbedd92-38c4-4904-a4c1-b3f45f899980"), + 1, + "Matroska Extractor", + []( + DataSourceBase *source, + float *confidence, + void **, + MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc { + if (SniffMatroska(source, confidence)) { + return []( + DataSourceBase *source, + void *) -> MediaExtractor* { + return new MatroskaExtractor(source);}; + } + return NULL; + } + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/mkv/MatroskaExtractor.h b/media/extractors/mkv/MatroskaExtractor.h new file mode 100644 index 0000000..3568ea1 --- /dev/null +++ b/media/extractors/mkv/MatroskaExtractor.h
@@ -0,0 +1,105 @@ +/* + * 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 MATROSKA_EXTRACTOR_H_ + +#define MATROSKA_EXTRACTOR_H_ + +#include "mkvparser/mkvparser.h" + +#include <media/MediaExtractor.h> +#include <media/stagefright/MetaDataBase.h> +#include <utils/Vector.h> +#include <utils/threads.h> + +namespace android { + +struct AMessage; +class String8; + +class MetaData; +struct DataSourceBaseReader; +struct MatroskaSource; + +struct MatroskaExtractor : public MediaExtractor { + explicit MatroskaExtractor(DataSourceBase *source); + + virtual size_t countTracks(); + + virtual MediaTrack *getTrack(size_t index); + + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + + virtual uint32_t flags() const; + + virtual const char * name() { return "MatroskaExtractor"; } + +protected: + virtual ~MatroskaExtractor(); + +private: + friend struct MatroskaSource; + friend struct BlockIterator; + + struct TrackInfo { + unsigned long mTrackNum; + bool mEncrypted; + MetaDataBase mMeta; + const MatroskaExtractor *mExtractor; + Vector<const mkvparser::CuePoint*> mCuePoints; + + // mHeader points to memory managed by mkvparser; + // mHeader would be deleted when mSegment is deleted + // in ~MatroskaExtractor. + unsigned char *mHeader; + size_t mHeaderLen; + + const mkvparser::Track* getTrack() const; + const mkvparser::CuePoint::TrackPosition *find(long long timeNs) const; + }; + + Mutex mLock; + Vector<TrackInfo> mTracks; + + DataSourceBase *mDataSource; + DataSourceBaseReader *mReader; + mkvparser::Segment *mSegment; + bool mExtractedThumbnails; + bool mIsLiveStreaming; + bool mIsWebm; + int64_t mSeekPreRollNs; + + status_t synthesizeAVCC(TrackInfo *trackInfo, size_t index); + status_t initTrackInfo( + const mkvparser::Track *track, + MetaDataBase &meta, + TrackInfo *trackInfo); + void addTracks(); + void findThumbnails(); + void getColorInformation( + const mkvparser::VideoTrack *vtrack, + MetaDataBase &meta); + bool isLiveStreaming() const; + + MatroskaExtractor(const MatroskaExtractor &); + MatroskaExtractor &operator=(const MatroskaExtractor &); +}; + +} // namespace android + +#endif // MATROSKA_EXTRACTOR_H_
diff --git a/media/libstagefright/matroska/NOTICE b/media/extractors/mkv/NOTICE similarity index 100% copy from media/libstagefright/matroska/NOTICE copy to media/extractors/mkv/NOTICE
diff --git a/media/extractors/mkv/exports.lds b/media/extractors/mkv/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/mkv/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/mp3/Android.bp b/media/extractors/mp3/Android.bp new file mode 100644 index 0000000..a3aeaca --- /dev/null +++ b/media/extractors/mp3/Android.bp
@@ -0,0 +1,47 @@ +cc_library_shared { + + srcs: [ + "MP3Extractor.cpp", + "VBRISeeker.cpp", + "XINGSeeker.cpp", + ], + + include_dirs: [ + "frameworks/av/media/libstagefright/include", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + "libstagefright_foundation", + ], + + static_libs: [ + "libutils", + "libstagefright_id3", + ], + + name: "libmp3extractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/extractors/mp3/MP3Extractor.cpp b/media/extractors/mp3/MP3Extractor.cpp new file mode 100644 index 0000000..33cff96 --- /dev/null +++ b/media/extractors/mp3/MP3Extractor.cpp
@@ -0,0 +1,725 @@ +/* + * Copyright (C) 2009 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 "MP3Extractor" +#include <utils/Log.h> + +#include "MP3Extractor.h" + +#include "ID3.h" +#include "VBRISeeker.h" +#include "XINGSeeker.h" + +#include <media/DataSourceBase.h> +#include <media/MediaTrack.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/avc_utils.h> +#include <media/stagefright/foundation/ByteUtils.h> +#include <media/stagefright/MediaBufferBase.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <utils/String8.h> + +namespace android { + +// Everything must match except for +// protection, bitrate, padding, private bits, mode, mode extension, +// copyright bit, original bit and emphasis. +// Yes ... there are things that must indeed match... +static const uint32_t kMask = 0xfffe0c00; + +static bool Resync( + DataSourceBase *source, uint32_t match_header, + off64_t *inout_pos, off64_t *post_id3_pos, uint32_t *out_header) { + if (post_id3_pos != NULL) { + *post_id3_pos = 0; + } + + if (*inout_pos == 0) { + // Skip an optional ID3 header if syncing at the very beginning + // of the datasource. + + for (;;) { + uint8_t id3header[10]; + if (source->readAt(*inout_pos, id3header, sizeof(id3header)) + < (ssize_t)sizeof(id3header)) { + // If we can't even read these 10 bytes, we might as well bail + // out, even if there _were_ 10 bytes of valid mp3 audio data... + return false; + } + + if (memcmp("ID3", id3header, 3)) { + break; + } + + // Skip the ID3v2 header. + + size_t len = + ((id3header[6] & 0x7f) << 21) + | ((id3header[7] & 0x7f) << 14) + | ((id3header[8] & 0x7f) << 7) + | (id3header[9] & 0x7f); + + len += 10; + + *inout_pos += len; + + ALOGV("skipped ID3 tag, new starting offset is %lld (0x%016llx)", + (long long)*inout_pos, (long long)*inout_pos); + } + + if (post_id3_pos != NULL) { + *post_id3_pos = *inout_pos; + } + } + + off64_t pos = *inout_pos; + bool valid = false; + + const size_t kMaxReadBytes = 1024; + const size_t kMaxBytesChecked = 128 * 1024; + uint8_t buf[kMaxReadBytes]; + ssize_t bytesToRead = kMaxReadBytes; + ssize_t totalBytesRead = 0; + ssize_t remainingBytes = 0; + bool reachEOS = false; + uint8_t *tmp = buf; + + do { + if (pos >= (off64_t)(*inout_pos + kMaxBytesChecked)) { + // Don't scan forever. + ALOGV("giving up at offset %lld", (long long)pos); + break; + } + + if (remainingBytes < 4) { + if (reachEOS) { + break; + } else { + memcpy(buf, tmp, remainingBytes); + bytesToRead = kMaxReadBytes - remainingBytes; + + /* + * The next read position should start from the end of + * the last buffer, and thus should include the remaining + * bytes in the buffer. + */ + totalBytesRead = source->readAt(pos + remainingBytes, + buf + remainingBytes, + bytesToRead); + if (totalBytesRead <= 0) { + break; + } + reachEOS = (totalBytesRead != bytesToRead); + totalBytesRead += remainingBytes; + remainingBytes = totalBytesRead; + tmp = buf; + continue; + } + } + + uint32_t header = U32_AT(tmp); + + if (match_header != 0 && (header & kMask) != (match_header & kMask)) { + ++pos; + ++tmp; + --remainingBytes; + continue; + } + + size_t frame_size; + int sample_rate, num_channels, bitrate; + if (!GetMPEGAudioFrameSize( + header, &frame_size, + &sample_rate, &num_channels, &bitrate)) { + ++pos; + ++tmp; + --remainingBytes; + continue; + } + + ALOGV("found possible 1st frame at %lld (header = 0x%08x)", (long long)pos, header); + + // We found what looks like a valid frame, + // now find its successors. + + off64_t test_pos = pos + frame_size; + + valid = true; + for (int j = 0; j < 3; ++j) { + uint8_t tmp[4]; + if (source->readAt(test_pos, tmp, 4) < 4) { + valid = false; + break; + } + + uint32_t test_header = U32_AT(tmp); + + ALOGV("subsequent header is %08x", test_header); + + if ((test_header & kMask) != (header & kMask)) { + valid = false; + break; + } + + size_t test_frame_size; + if (!GetMPEGAudioFrameSize( + test_header, &test_frame_size)) { + valid = false; + break; + } + + ALOGV("found subsequent frame #%d at %lld", j + 2, (long long)test_pos); + + test_pos += test_frame_size; + } + + if (valid) { + *inout_pos = pos; + + if (out_header != NULL) { + *out_header = header; + } + } else { + ALOGV("no dice, no valid sequence of frames found."); + } + + ++pos; + ++tmp; + --remainingBytes; + } while (!valid); + + return valid; +} + +class MP3Source : public MediaTrack { +public: + MP3Source( + MetaDataBase &meta, DataSourceBase *source, + off64_t first_frame_pos, uint32_t fixed_header, + MP3Seeker *seeker); + + virtual status_t start(MetaDataBase *params = NULL); + virtual status_t stop(); + + virtual status_t getFormat(MetaDataBase &meta); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~MP3Source(); + +private: + static const size_t kMaxFrameSize; + MetaDataBase &mMeta; + DataSourceBase *mDataSource; + off64_t mFirstFramePos; + uint32_t mFixedHeader; + off64_t mCurrentPos; + int64_t mCurrentTimeUs; + bool mStarted; + MP3Seeker *mSeeker; + MediaBufferGroup *mGroup; + + int64_t mBasisTimeUs; + int64_t mSamplesRead; + + MP3Source(const MP3Source &); + MP3Source &operator=(const MP3Source &); +}; + +struct Mp3Meta { + off64_t pos; + off64_t post_id3_pos; + uint32_t header; +}; + +MP3Extractor::MP3Extractor( + DataSourceBase *source, Mp3Meta *meta) + : mInitCheck(NO_INIT), + mDataSource(source), + mFirstFramePos(-1), + mFixedHeader(0), + mSeeker(NULL) { + + off64_t pos = 0; + off64_t post_id3_pos; + uint32_t header; + bool success; + + if (meta != NULL) { + // The sniffer has already done all the hard work for us, simply + // accept its judgement. + pos = meta->pos; + header = meta->header; + post_id3_pos = meta->post_id3_pos; + success = true; + } else { + success = Resync(mDataSource, 0, &pos, &post_id3_pos, &header); + } + + if (!success) { + // mInitCheck will remain NO_INIT + return; + } + + mFirstFramePos = pos; + mFixedHeader = header; + XINGSeeker *seeker = XINGSeeker::CreateFromSource(mDataSource, mFirstFramePos); + + if (seeker == NULL) { + mSeeker = VBRISeeker::CreateFromSource(mDataSource, post_id3_pos); + } else { + mSeeker = seeker; + int encd = seeker->getEncoderDelay(); + int encp = seeker->getEncoderPadding(); + if (encd != 0 || encp != 0) { + mMeta.setInt32(kKeyEncoderDelay, encd); + mMeta.setInt32(kKeyEncoderPadding, encp); + } + } + + if (mSeeker != NULL) { + // While it is safe to send the XING/VBRI frame to the decoder, this will + // result in an extra 1152 samples being output. In addition, the bitrate + // of the Xing header might not match the rest of the file, which could + // lead to problems when seeking. The real first frame to decode is after + // the XING/VBRI frame, so skip there. + size_t frame_size; + int sample_rate; + int num_channels; + int bitrate; + GetMPEGAudioFrameSize( + header, &frame_size, &sample_rate, &num_channels, &bitrate); + pos += frame_size; + if (!Resync(mDataSource, 0, &pos, &post_id3_pos, &header)) { + // mInitCheck will remain NO_INIT + return; + } + mFirstFramePos = pos; + mFixedHeader = header; + } + + size_t frame_size; + int sample_rate; + int num_channels; + int bitrate; + GetMPEGAudioFrameSize( + header, &frame_size, &sample_rate, &num_channels, &bitrate); + + unsigned layer = 4 - ((header >> 17) & 3); + + switch (layer) { + case 1: + mMeta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_I); + break; + case 2: + mMeta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_II); + break; + case 3: + mMeta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); + break; + default: + TRESPASS(); + } + + mMeta.setInt32(kKeySampleRate, sample_rate); + mMeta.setInt32(kKeyBitRate, bitrate * 1000); + mMeta.setInt32(kKeyChannelCount, num_channels); + + int64_t durationUs; + + if (mSeeker == NULL || !mSeeker->getDuration(&durationUs)) { + off64_t fileSize; + if (mDataSource->getSize(&fileSize) == OK) { + off64_t dataLength = fileSize - mFirstFramePos; + if (dataLength > INT64_MAX / 8000LL) { + // duration would overflow + durationUs = INT64_MAX; + } else { + durationUs = 8000LL * dataLength / bitrate; + } + } else { + durationUs = -1; + } + } + + if (durationUs >= 0) { + mMeta.setInt64(kKeyDuration, durationUs); + } + + mInitCheck = OK; + + // Get iTunes-style gapless info if present. + // When getting the id3 tag, skip the V1 tags to prevent the source cache + // from being iterated to the end of the file. + ID3 id3(mDataSource, true); + if (id3.isValid()) { + ID3::Iterator *com = new ID3::Iterator(id3, "COM"); + if (com->done()) { + delete com; + com = new ID3::Iterator(id3, "COMM"); + } + while(!com->done()) { + String8 commentdesc; + String8 commentvalue; + com->getString(&commentdesc, &commentvalue); + const char * desc = commentdesc.string(); + const char * value = commentvalue.string(); + + // first 3 characters are the language, which we don't care about + if(strlen(desc) > 3 && strcmp(desc + 3, "iTunSMPB") == 0) { + + int32_t delay, padding; + if (sscanf(value, " %*x %x %x %*x", &delay, &padding) == 2) { + mMeta.setInt32(kKeyEncoderDelay, delay); + mMeta.setInt32(kKeyEncoderPadding, padding); + } + break; + } + com->next(); + } + delete com; + com = NULL; + } +} + +MP3Extractor::~MP3Extractor() { + delete mSeeker; +} + +size_t MP3Extractor::countTracks() { + return mInitCheck != OK ? 0 : 1; +} + +MediaTrack *MP3Extractor::getTrack(size_t index) { + if (mInitCheck != OK || index != 0) { + return NULL; + } + + return new MP3Source( + mMeta, mDataSource, mFirstFramePos, mFixedHeader, + mSeeker); +} + +status_t MP3Extractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t /* flags */) { + if (mInitCheck != OK || index != 0) { + return UNKNOWN_ERROR; + } + meta = mMeta; + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// The theoretical maximum frame size for an MPEG audio stream should occur +// while playing a Layer 2, MPEGv2.5 audio stream at 160kbps (with padding). +// The size of this frame should be... +// ((1152 samples/frame * 160000 bits/sec) / +// (8000 samples/sec * 8 bits/byte)) + 1 padding byte/frame = 2881 bytes/frame. +// Set our max frame size to the nearest power of 2 above this size (aka, 4kB) +const size_t MP3Source::kMaxFrameSize = (1 << 12); /* 4096 bytes */ +MP3Source::MP3Source( + MetaDataBase &meta, DataSourceBase *source, + off64_t first_frame_pos, uint32_t fixed_header, + MP3Seeker *seeker) + : mMeta(meta), + mDataSource(source), + mFirstFramePos(first_frame_pos), + mFixedHeader(fixed_header), + mCurrentPos(0), + mCurrentTimeUs(0), + mStarted(false), + mSeeker(seeker), + mGroup(NULL), + mBasisTimeUs(0), + mSamplesRead(0) { +} + +MP3Source::~MP3Source() { + if (mStarted) { + stop(); + } +} + +status_t MP3Source::start(MetaDataBase *) { + CHECK(!mStarted); + + mGroup = new MediaBufferGroup; + + mGroup->add_buffer(MediaBufferBase::Create(kMaxFrameSize)); + + mCurrentPos = mFirstFramePos; + mCurrentTimeUs = 0; + + mBasisTimeUs = mCurrentTimeUs; + mSamplesRead = 0; + + mStarted = true; + + return OK; +} + +status_t MP3Source::stop() { + CHECK(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + + return OK; +} + +status_t MP3Source::getFormat(MetaDataBase &meta) { + meta = mMeta; + return OK; +} + +status_t MP3Source::read( + MediaBufferBase **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + bool seekCBR = false; + + if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) { + int64_t actualSeekTimeUs = seekTimeUs; + if (mSeeker == NULL + || !mSeeker->getOffsetForTime(&actualSeekTimeUs, &mCurrentPos)) { + int32_t bitrate; + if (!mMeta.findInt32(kKeyBitRate, &bitrate)) { + // bitrate is in bits/sec. + ALOGI("no bitrate"); + + return ERROR_UNSUPPORTED; + } + + mCurrentTimeUs = seekTimeUs; + mCurrentPos = mFirstFramePos + seekTimeUs * bitrate / 8000000; + seekCBR = true; + } else { + mCurrentTimeUs = actualSeekTimeUs; + } + + mBasisTimeUs = mCurrentTimeUs; + mSamplesRead = 0; + } + + MediaBufferBase *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + size_t frame_size; + int bitrate; + int num_samples; + int sample_rate; + for (;;) { + ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), 4); + if (n < 4) { + buffer->release(); + buffer = NULL; + + return (n < 0 ? n : ERROR_END_OF_STREAM); + } + + uint32_t header = U32_AT((const uint8_t *)buffer->data()); + + if ((header & kMask) == (mFixedHeader & kMask) + && GetMPEGAudioFrameSize( + header, &frame_size, &sample_rate, NULL, + &bitrate, &num_samples)) { + + // re-calculate mCurrentTimeUs because we might have called Resync() + if (seekCBR) { + mCurrentTimeUs = (mCurrentPos - mFirstFramePos) * 8000 / bitrate; + mBasisTimeUs = mCurrentTimeUs; + } + + break; + } + + // Lost sync. + ALOGV("lost sync! header = 0x%08x, old header = 0x%08x\n", header, mFixedHeader); + + off64_t pos = mCurrentPos; + if (!Resync(mDataSource, mFixedHeader, &pos, NULL, NULL)) { + ALOGE("Unable to resync. Signalling end of stream."); + + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + mCurrentPos = pos; + + // Try again with the new position. + } + + CHECK(frame_size <= buffer->size()); + + ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), frame_size); + if (n < (ssize_t)frame_size) { + buffer->release(); + buffer = NULL; + + return (n < 0 ? n : ERROR_END_OF_STREAM); + } + + buffer->set_range(0, frame_size); + + buffer->meta_data().setInt64(kKeyTime, mCurrentTimeUs); + buffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + + mCurrentPos += frame_size; + + mSamplesRead += num_samples; + mCurrentTimeUs = mBasisTimeUs + ((mSamplesRead * 1000000) / sample_rate); + + *out = buffer; + + return OK; +} + +status_t MP3Extractor::getMetaData(MetaDataBase &meta) { + meta.clear(); + if (mInitCheck != OK) { + return UNKNOWN_ERROR; + } + meta.setCString(kKeyMIMEType, "audio/mpeg"); + + ID3 id3(mDataSource); + + if (!id3.isValid()) { + return OK; + } + + struct Map { + int key; + const char *tag1; + const char *tag2; + }; + static const Map kMap[] = { + { kKeyAlbum, "TALB", "TAL" }, + { kKeyArtist, "TPE1", "TP1" }, + { kKeyAlbumArtist, "TPE2", "TP2" }, + { kKeyComposer, "TCOM", "TCM" }, + { kKeyGenre, "TCON", "TCO" }, + { kKeyTitle, "TIT2", "TT2" }, + { kKeyYear, "TYE", "TYER" }, + { kKeyAuthor, "TXT", "TEXT" }, + { kKeyCDTrackNumber, "TRK", "TRCK" }, + { kKeyDiscNumber, "TPA", "TPOS" }, + { kKeyCompilation, "TCP", "TCMP" }, + }; + static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]); + + for (size_t i = 0; i < kNumMapEntries; ++i) { + ID3::Iterator *it = new ID3::Iterator(id3, kMap[i].tag1); + if (it->done()) { + delete it; + it = new ID3::Iterator(id3, kMap[i].tag2); + } + + if (it->done()) { + delete it; + continue; + } + + String8 s; + it->getString(&s); + delete it; + + meta.setCString(kMap[i].key, s); + } + + size_t dataSize; + String8 mime; + const void *data = id3.getAlbumArt(&dataSize, &mime); + + if (data) { + meta.setData(kKeyAlbumArt, MetaData::TYPE_NONE, data, dataSize); + meta.setCString(kKeyAlbumArtMIME, mime.string()); + } + + return OK; +} + +static MediaExtractor* CreateExtractor( + DataSourceBase *source, + void *meta) { + Mp3Meta *metaData = static_cast<Mp3Meta *>(meta); + return new MP3Extractor(source, metaData); +} + +static MediaExtractor::CreatorFunc Sniff( + DataSourceBase *source, float *confidence, void **meta, + MediaExtractor::FreeMetaFunc *freeMeta) { + off64_t pos = 0; + off64_t post_id3_pos; + uint32_t header; + uint8_t mpeg_header[5]; + if (source->readAt(0, mpeg_header, sizeof(mpeg_header)) < (ssize_t)sizeof(mpeg_header)) { + return NULL; + } + + if (!memcmp("\x00\x00\x01\xba", mpeg_header, 4) && (mpeg_header[4] >> 4) == 2) { + ALOGV("MPEG1PS container is not supported!"); + return NULL; + } + if (!Resync(source, 0, &pos, &post_id3_pos, &header)) { + return NULL; + } + + Mp3Meta *mp3Meta = new Mp3Meta; + mp3Meta->pos = pos; + mp3Meta->header = header; + mp3Meta->post_id3_pos = post_id3_pos; + *meta = mp3Meta; + *freeMeta = ::free; + + *confidence = 0.2f; + + return CreateExtractor; +} + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("812a3f6c-c8cf-46de-b529-3774b14103d4"), + 1, // version + "MP3 Extractor", + Sniff + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/mp3/MP3Extractor.h b/media/extractors/mp3/MP3Extractor.h new file mode 100644 index 0000000..485b0ca --- /dev/null +++ b/media/extractors/mp3/MP3Extractor.h
@@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 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 MP3_EXTRACTOR_H_ + +#define MP3_EXTRACTOR_H_ + +#include <utils/Errors.h> +#include <media/MediaExtractor.h> +#include <media/stagefright/MetaDataBase.h> + +namespace android { + +struct AMessage; +class DataSourceBase; +struct MP3Seeker; +class String8; +struct Mp3Meta; + +class MP3Extractor : public MediaExtractor { +public: + MP3Extractor(DataSourceBase *source, Mp3Meta *meta); + ~MP3Extractor(); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + virtual const char * name() { return "MP3Extractor"; } + +private: + status_t mInitCheck; + + DataSourceBase *mDataSource; + off64_t mFirstFramePos; + MetaDataBase mMeta; + uint32_t mFixedHeader; + MP3Seeker *mSeeker; + + MP3Extractor(const MP3Extractor &); + MP3Extractor &operator=(const MP3Extractor &); +}; + +} // namespace android + +#endif // MP3_EXTRACTOR_H_
diff --git a/media/extractors/mp3/MP3Seeker.h b/media/extractors/mp3/MP3Seeker.h new file mode 100644 index 0000000..0e3af25 --- /dev/null +++ b/media/extractors/mp3/MP3Seeker.h
@@ -0,0 +1,45 @@ +/* + * 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 MP3_SEEKER_H_ + +#define MP3_SEEKER_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/RefBase.h> + +namespace android { + +struct MP3Seeker { + MP3Seeker() {} + + virtual bool getDuration(int64_t *durationUs) = 0; + + // Given a request seek time in "*timeUs", find the byte offset closest + // to that position and return it in "*pos". Update "*timeUs" to reflect + // the actual time that seekpoint represents. + virtual bool getOffsetForTime(int64_t *timeUs, off64_t *pos) = 0; + + virtual ~MP3Seeker() {} + +private: + DISALLOW_EVIL_CONSTRUCTORS(MP3Seeker); +}; + +} // namespace android + +#endif // MP3_SEEKER_H_ +
diff --git a/media/extractors/mp3/VBRISeeker.cpp b/media/extractors/mp3/VBRISeeker.cpp new file mode 100644 index 0000000..523f14c --- /dev/null +++ b/media/extractors/mp3/VBRISeeker.cpp
@@ -0,0 +1,188 @@ +/* + * 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 "VBRISeeker" + +#include <inttypes.h> + +#include <utils/Log.h> + +#include "VBRISeeker.h" + +#include <media/stagefright/foundation/avc_utils.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ByteUtils.h> +#include <media/DataSourceBase.h> + +namespace android { + +static uint32_t U24_AT(const uint8_t *ptr) { + return ptr[0] << 16 | ptr[1] << 8 | ptr[2]; +} + +// static +VBRISeeker *VBRISeeker::CreateFromSource( + DataSourceBase *source, off64_t post_id3_pos) { + off64_t pos = post_id3_pos; + + uint8_t header[4]; + ssize_t n = source->readAt(pos, header, sizeof(header)); + if (n < (ssize_t)sizeof(header)) { + return NULL; + } + + uint32_t tmp = U32_AT(&header[0]); + size_t frameSize; + int sampleRate; + if (!GetMPEGAudioFrameSize(tmp, &frameSize, &sampleRate)) { + return NULL; + } + + // VBRI header follows 32 bytes after the header _ends_. + pos += sizeof(header) + 32; + + uint8_t vbriHeader[26]; + n = source->readAt(pos, vbriHeader, sizeof(vbriHeader)); + if (n < (ssize_t)sizeof(vbriHeader)) { + return NULL; + } + + if (memcmp(vbriHeader, "VBRI", 4)) { + return NULL; + } + + size_t numFrames = U32_AT(&vbriHeader[14]); + + int64_t durationUs = + numFrames * 1000000ll * (sampleRate >= 32000 ? 1152 : 576) / sampleRate; + + ALOGV("duration = %.2f secs", durationUs / 1E6); + + size_t numEntries = U16_AT(&vbriHeader[18]); + size_t entrySize = U16_AT(&vbriHeader[22]); + size_t scale = U16_AT(&vbriHeader[20]); + + ALOGV("%zu entries, scale=%zu, size_per_entry=%zu", + numEntries, + scale, + entrySize); + + if (entrySize > 4) { + ALOGE("invalid VBRI entry size: %zu", entrySize); + return NULL; + } + + VBRISeeker *seeker = new (std::nothrow) VBRISeeker; + if (seeker == NULL) { + ALOGW("Couldn't allocate VBRISeeker"); + return NULL; + } + + size_t totalEntrySize = numEntries * entrySize; + uint8_t *buffer = new (std::nothrow) uint8_t[totalEntrySize]; + if (!buffer) { + ALOGW("Couldn't allocate %zu bytes", totalEntrySize); + delete seeker; + return NULL; + } + + n = source->readAt(pos + sizeof(vbriHeader), buffer, totalEntrySize); + if (n < (ssize_t)totalEntrySize) { + delete[] buffer; + buffer = NULL; + delete seeker; + return NULL; + } + + seeker->mBasePos = post_id3_pos + frameSize; + // only update mDurationUs if the calculated duration is valid (non zero) + // otherwise, leave duration at -1 so that getDuration() and getOffsetForTime() + // return false when called, to indicate that this vbri tag does not have the + // requested information + if (durationUs) { + seeker->mDurationUs = durationUs; + } + + off64_t offset = post_id3_pos; + for (size_t i = 0; i < numEntries; ++i) { + uint32_t numBytes; + switch (entrySize) { + case 1: numBytes = buffer[i]; break; + case 2: numBytes = U16_AT(buffer + 2 * i); break; + case 3: numBytes = U24_AT(buffer + 3 * i); break; + default: + { + CHECK_EQ(entrySize, 4u); + numBytes = U32_AT(buffer + 4 * i); break; + } + } + + numBytes *= scale; + + seeker->mSegments.push(numBytes); + + ALOGV("entry #%zu: %u offset %#016llx", i, numBytes, (long long)offset); + offset += numBytes; + } + + delete[] buffer; + buffer = NULL; + + ALOGI("Found VBRI header."); + + return seeker; +} + +VBRISeeker::VBRISeeker() + : mDurationUs(-1) { +} + +bool VBRISeeker::getDuration(int64_t *durationUs) { + if (mDurationUs < 0) { + return false; + } + + *durationUs = mDurationUs; + + return true; +} + +bool VBRISeeker::getOffsetForTime(int64_t *timeUs, off64_t *pos) { + if (mDurationUs < 0 || mSegments.size() == 0) { + return false; + } + + int64_t segmentDurationUs = mDurationUs / mSegments.size(); + + int64_t nowUs = 0; + *pos = mBasePos; + size_t segmentIndex = 0; + while (segmentIndex < mSegments.size() && nowUs < *timeUs) { + nowUs += segmentDurationUs; + *pos += mSegments.itemAt(segmentIndex++); + } + + ALOGV("getOffsetForTime %lld us => 0x%016llx", (long long)*timeUs, (long long)*pos); + + *timeUs = nowUs; + + return true; +} + +} // namespace android +
diff --git a/media/extractors/mp3/VBRISeeker.h b/media/extractors/mp3/VBRISeeker.h new file mode 100644 index 0000000..9213f6e --- /dev/null +++ b/media/extractors/mp3/VBRISeeker.h
@@ -0,0 +1,50 @@ +/* + * 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 VBRI_SEEKER_H_ + +#define VBRI_SEEKER_H_ + +#include "MP3Seeker.h" + +#include <utils/Vector.h> + +namespace android { + +class DataSourceBase; + +struct VBRISeeker : public MP3Seeker { + static VBRISeeker *CreateFromSource( + DataSourceBase *source, off64_t post_id3_pos); + + virtual bool getDuration(int64_t *durationUs); + virtual bool getOffsetForTime(int64_t *timeUs, off64_t *pos); + +private: + off64_t mBasePos; + int64_t mDurationUs; + Vector<uint32_t> mSegments; + + VBRISeeker(); + + DISALLOW_EVIL_CONSTRUCTORS(VBRISeeker); +}; + +} // namespace android + +#endif // VBRI_SEEKER_H_ + +
diff --git a/media/extractors/mp3/XINGSeeker.cpp b/media/extractors/mp3/XINGSeeker.cpp new file mode 100644 index 0000000..95ca556 --- /dev/null +++ b/media/extractors/mp3/XINGSeeker.cpp
@@ -0,0 +1,202 @@ +/* + * 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_TAG "XINGSEEKER" +#include <utils/Log.h> + +#include "XINGSeeker.h" +#include <media/stagefright/foundation/avc_utils.h> + +#include <media/stagefright/foundation/ByteUtils.h> +#include <media/DataSourceBase.h> + +namespace android { + +XINGSeeker::XINGSeeker() + : mDurationUs(-1), + mSizeBytes(0), + mEncoderDelay(0), + mEncoderPadding(0), + mTOCValid(false) { +} + +bool XINGSeeker::getDuration(int64_t *durationUs) { + if (mDurationUs < 0) { + return false; + } + + *durationUs = mDurationUs; + + return true; +} + +bool XINGSeeker::getOffsetForTime(int64_t *timeUs, off64_t *pos) { + if (mSizeBytes == 0 || !mTOCValid || mDurationUs < 0) { + return false; + } + + float percent = (float)(*timeUs) * 100 / mDurationUs; + float fx; + if( percent <= 0.0f ) { + fx = 0.0f; + } else if( percent >= 100.0f ) { + fx = 256.0f; + } else { + int a = (int)percent; + float fa, fb; + if ( a == 0 ) { + fa = 0.0f; + } else { + fa = (float)mTOC[a-1]; + } + if ( a < 99 ) { + fb = (float)mTOC[a]; + } else { + fb = 256.0f; + } + fx = fa + (fb-fa)*(percent-a); + } + + *pos = (int)((1.0f/256.0f)*fx*mSizeBytes) + mFirstFramePos; + + return true; +} + +// static +XINGSeeker *XINGSeeker::CreateFromSource( + DataSourceBase *source, off64_t first_frame_pos) { + + uint8_t buffer[4]; + int offset = first_frame_pos; + if (source->readAt(offset, &buffer, 4) < 4) { // get header + return NULL; + } + offset += 4; + + int header = U32_AT(buffer);; + size_t xingframesize = 0; + int sampling_rate = 0; + int num_channels; + int samples_per_frame = 0; + if (!GetMPEGAudioFrameSize(header, &xingframesize, &sampling_rate, &num_channels, + NULL, &samples_per_frame)) { + return NULL; + } + uint8_t version = (buffer[1] >> 3) & 3; + + // determine offset of XING header + if(version & 1) { // mpeg1 + if (num_channels != 1) offset += 32; + else offset += 17; + } else { // mpeg 2 or 2.5 + if (num_channels != 1) offset += 17; + else offset += 9; + } + + int xingbase = offset; + + if (source->readAt(offset, &buffer, 4) < 4) { // XING header ID + return NULL; + } + offset += 4; + // Check XING ID + if ((buffer[0] != 'X') || (buffer[1] != 'i') + || (buffer[2] != 'n') || (buffer[3] != 'g')) { + if ((buffer[0] != 'I') || (buffer[1] != 'n') + || (buffer[2] != 'f') || (buffer[3] != 'o')) { + return NULL; + } + } + + if (source->readAt(offset, &buffer, 4) < 4) { // flags + return NULL; + } + offset += 4; + uint32_t flags = U32_AT(buffer); + + XINGSeeker *seeker = new XINGSeeker; + seeker->mFirstFramePos = first_frame_pos + xingframesize; + + if (flags & 0x0001) { // Frames field is present + if (source->readAt(offset, buffer, 4) < 4) { + delete seeker; + return NULL; + } + int32_t frames = U32_AT(buffer); + // only update mDurationUs if the calculated duration is valid (non zero) + // otherwise, leave duration at -1 so that getDuration() and getOffsetForTime() + // return false when called, to indicate that this xing tag does not have the + // requested information + if (frames) { + seeker->mDurationUs = (int64_t)frames * samples_per_frame * 1000000LL / sampling_rate; + } + offset += 4; + } + if (flags & 0x0002) { // Bytes field is present + if (source->readAt(offset, buffer, 4) < 4) { + delete seeker; + return NULL; + } + seeker->mSizeBytes = U32_AT(buffer); + offset += 4; + } + if (flags & 0x0004) { // TOC field is present + if (source->readAt(offset + 1, seeker->mTOC, 99) < 99) { + delete seeker; + return NULL; + } + seeker->mTOCValid = true; + offset += 100; + } + +#if 0 + if (flags & 0x0008) { // Quality indicator field is present + if (source->readAt(offset, buffer, 4) < 4) { + delete seeker; + return NULL; + } + // do something with the quality indicator + offset += 4; + } + + if (source->readAt(xingbase + 0xaf - 0x24, &buffer, 1) < 1) { // encoding flags + delete seeker; + return false; + } + + ALOGV("nogap preceding: %s, nogap continued in next: %s", + (buffer[0] & 0x80) ? "true" : "false", + (buffer[0] & 0x40) ? "true" : "false"); +#endif + + if (source->readAt(xingbase + 0xb1 - 0x24, &buffer, 3) == 3) { + seeker->mEncoderDelay = (buffer[0] << 4) + (buffer[1] >> 4); + seeker->mEncoderPadding = ((buffer[1] & 0xf) << 8) + buffer[2]; + } + + return seeker; +} + +int32_t XINGSeeker::getEncoderDelay() { + return mEncoderDelay; +} + +int32_t XINGSeeker::getEncoderPadding() { + return mEncoderPadding; +} + +} // namespace android +
diff --git a/media/extractors/mp3/XINGSeeker.h b/media/extractors/mp3/XINGSeeker.h new file mode 100644 index 0000000..5867eae --- /dev/null +++ b/media/extractors/mp3/XINGSeeker.h
@@ -0,0 +1,56 @@ +/* + * 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 XING_SEEKER_H_ + +#define XING_SEEKER_H_ + +#include "MP3Seeker.h" + +namespace android { + +class DataSourceBase; + +struct XINGSeeker : public MP3Seeker { + static XINGSeeker *CreateFromSource( + DataSourceBase *source, off64_t first_frame_pos); + + virtual bool getDuration(int64_t *durationUs); + virtual bool getOffsetForTime(int64_t *timeUs, off64_t *pos); + + virtual int32_t getEncoderDelay(); + virtual int32_t getEncoderPadding(); + +private: + int64_t mFirstFramePos; + int64_t mDurationUs; + int32_t mSizeBytes; + int32_t mEncoderDelay; + int32_t mEncoderPadding; + + // TOC entries in XING header. Skip the first one since it's always 0. + unsigned char mTOC[99]; + bool mTOCValid; + + XINGSeeker(); + + DISALLOW_EVIL_CONSTRUCTORS(XINGSeeker); +}; + +} // namespace android + +#endif // XING_SEEKER_H_ +
diff --git a/media/extractors/mp3/exports.lds b/media/extractors/mp3/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/mp3/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/mp4/Android.bp b/media/extractors/mp4/Android.bp new file mode 100644 index 0000000..fa739e8 --- /dev/null +++ b/media/extractors/mp4/Android.bp
@@ -0,0 +1,60 @@ +cc_defaults { + name: "libmp4extractor_defaults", + + srcs: [ + "ItemTable.cpp", + "MPEG4Extractor.cpp", + "SampleIterator.cpp", + "SampleTable.cpp", + ], + + include_dirs: [ + "frameworks/av/media/libstagefright/", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + ], + + static_libs: [ + "libstagefright_esds", + "libstagefright_foundation", + "libstagefright_id3", + "libutils", + ], + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + relative_install_path: "extractors", + compile_multilib: "first", +} + +cc_library_shared { + + + name: "libmp4extractor", + defaults: ["libmp4extractor_defaults"], + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +} + +cc_library_static { + name: "libmp4extractor_fuzzing", + + defaults: ["libmp4extractor_defaults"], +}
diff --git a/media/extractors/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp new file mode 100644 index 0000000..ca9deab --- /dev/null +++ b/media/extractors/mp4/ItemTable.cpp
@@ -0,0 +1,1681 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "ItemTable" + +#include <ItemTable.h> +#include <media/DataSourceBase.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ByteUtils.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/MediaDefs.h> +#include <utils/Log.h> + +namespace android { + +namespace heif { + +///////////////////////////////////////////////////////////////////// +// +// struct to keep track of one image item +// + +struct ImageItem { + friend struct ItemReference; + friend struct ItemProperty; + + ImageItem() : ImageItem(0, 0, false) {} + ImageItem(uint32_t _type, uint32_t _id, bool _hidden) : + type(_type), itemId(_id), hidden(_hidden), + rows(0), columns(0), width(0), height(0), rotation(0), + offset(0), size(0), nextTileIndex(0) {} + + bool isGrid() const { + return type == FOURCC('g', 'r', 'i', 'd'); + } + + status_t getNextTileItemId(uint32_t *nextTileItemId, bool reset) { + if (reset) { + nextTileIndex = 0; + } + if (nextTileIndex >= dimgRefs.size()) { + return ERROR_END_OF_STREAM; + } + *nextTileItemId = dimgRefs[nextTileIndex++]; + return OK; + } + + uint32_t type; + uint32_t itemId; + bool hidden; + int32_t rows; + int32_t columns; + int32_t width; + int32_t height; + int32_t rotation; + off64_t offset; + size_t size; + sp<ABuffer> hvcc; + sp<ABuffer> icc; + + Vector<uint32_t> thumbnails; + Vector<uint32_t> dimgRefs; + Vector<uint32_t> cdscRefs; + size_t nextTileIndex; +}; + +struct ExifItem { + off64_t offset; + size_t size; +}; + +///////////////////////////////////////////////////////////////////// +// +// ISO boxes +// + +struct Box { +protected: + Box(DataSourceBase *source, uint32_t type) : + mDataSource(source), mType(type) {} + + virtual ~Box() {} + + virtual status_t onChunkData( + uint32_t /*type*/, off64_t /*offset*/, size_t /*size*/) { + return OK; + } + + inline uint32_t type() const { return mType; } + + inline DataSourceBase *source() const { return mDataSource; } + + status_t parseChunk(off64_t *offset); + + status_t parseChunks(off64_t offset, size_t size); + +private: + DataSourceBase *mDataSource; + uint32_t mType; +}; + +status_t Box::parseChunk(off64_t *offset) { + if (*offset < 0) { + ALOGE("b/23540914"); + return ERROR_MALFORMED; + } + uint32_t hdr[2]; + if (mDataSource->readAt(*offset, hdr, 8) < 8) { + return ERROR_IO; + } + uint64_t chunk_size = ntohl(hdr[0]); + int32_t chunk_type = ntohl(hdr[1]); + off64_t data_offset = *offset + 8; + + if (chunk_size == 1) { + if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) { + return ERROR_IO; + } + chunk_size = ntoh64(chunk_size); + data_offset += 8; + + if (chunk_size < 16) { + // The smallest valid chunk is 16 bytes long in this case. + return ERROR_MALFORMED; + } + } else if (chunk_size == 0) { + // This shouldn't happen since we should never be top level + ALOGE("invalid chunk size 0 for non-top level box"); + return ERROR_MALFORMED; + } else if (chunk_size < 8) { + // The smallest valid chunk is 8 bytes long. + ALOGE("invalid chunk size: %lld", (long long)chunk_size); + return ERROR_MALFORMED; + } + + char chunk[5]; + MakeFourCCString(chunk_type, chunk); + ALOGV("chunk: %s @ %lld", chunk, (long long)*offset); + + off64_t chunk_data_size = chunk_size - (data_offset - *offset); + if (chunk_data_size < 0) { + ALOGE("b/23540914"); + return ERROR_MALFORMED; + } + + status_t err = onChunkData(chunk_type, data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + *offset += chunk_size; + return OK; +} + +status_t Box::parseChunks(off64_t offset, size_t size) { + off64_t stopOffset = offset + size; + while (offset < stopOffset) { + status_t err = parseChunk(&offset); + if (err != OK) { + return err; + } + } + if (offset != stopOffset) { + return ERROR_MALFORMED; + } + return OK; +} + +/////////////////////////////////////////////////////////////////////// + +struct FullBox : public Box { +protected: + FullBox(DataSourceBase *source, uint32_t type) : + Box(source, type), mVersion(0), mFlags(0) {} + + inline uint8_t version() const { return mVersion; } + + inline uint32_t flags() const { return mFlags; } + + status_t parseFullBoxHeader(off64_t *offset, size_t *size); + +private: + uint8_t mVersion; + uint32_t mFlags; +}; + +status_t FullBox::parseFullBoxHeader(off64_t *offset, size_t *size) { + if (*size < 4) { + return ERROR_MALFORMED; + } + if (!source()->readAt(*offset, &mVersion, 1)) { + return ERROR_IO; + } + if (!source()->getUInt24(*offset + 1, &mFlags)) { + return ERROR_IO; + } + *offset += 4; + *size -= 4; + return OK; +} + +///////////////////////////////////////////////////////////////////// +// +// PrimaryImage box +// + +struct PitmBox : public FullBox { + PitmBox(DataSourceBase *source) : + FullBox(source, FOURCC('p', 'i', 't', 'm')) {} + + status_t parse(off64_t offset, size_t size, uint32_t *primaryItemId); +}; + +status_t PitmBox::parse(off64_t offset, size_t size, uint32_t *primaryItemId) { + status_t err = parseFullBoxHeader(&offset, &size); + if (err != OK) { + return err; + } + + size_t itemIdSize = (version() == 0) ? 2 : 4; + if (size < itemIdSize) { + return ERROR_MALFORMED; + } + uint32_t itemId; + if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) { + return ERROR_IO; + } + + ALOGV("primary id %d", itemId); + *primaryItemId = itemId; + + return OK; +} + +///////////////////////////////////////////////////////////////////// +// +// ItemLocation related boxes +// + +struct ExtentEntry { + uint64_t extentIndex; + uint64_t extentOffset; + uint64_t extentLength; +}; + +struct ItemLoc { + ItemLoc() : ItemLoc(0, 0, 0, 0) {} + ItemLoc(uint32_t item_id, uint16_t construction_method, + uint16_t data_reference_index, uint64_t base_offset) : + itemId(item_id), + constructionMethod(construction_method), + dataReferenceIndex(data_reference_index), + baseOffset(base_offset) {} + + void addExtent(const ExtentEntry& extent) { + extents.push_back(extent); + } + + status_t getLoc(off64_t *offset, size_t *size, + off64_t idatOffset, size_t idatSize) const { + // TODO: fix extent handling, fix constructionMethod = 2 + CHECK(extents.size() == 1); + if (constructionMethod == 0) { + *offset = baseOffset + extents[0].extentOffset; + *size = extents[0].extentLength; + return OK; + } else if (constructionMethod == 1) { + if (baseOffset + extents[0].extentOffset + extents[0].extentLength + > idatSize) { + return ERROR_MALFORMED; + } + *offset = baseOffset + extents[0].extentOffset + idatOffset; + *size = extents[0].extentLength; + return OK; + } + return ERROR_UNSUPPORTED; + } + + // parsed info + uint32_t itemId; + uint16_t constructionMethod; + uint16_t dataReferenceIndex; + off64_t baseOffset; + Vector<ExtentEntry> extents; +}; + +struct IlocBox : public FullBox { + IlocBox(DataSourceBase *source, KeyedVector<uint32_t, ItemLoc> *itemLocs) : + FullBox(source, FOURCC('i', 'l', 'o', 'c')), + mItemLocs(itemLocs), mHasConstructMethod1(false) {} + + status_t parse(off64_t offset, size_t size); + + bool hasConstructMethod1() { return mHasConstructMethod1; } + +private: + static bool isSizeFieldValid(uint32_t offset_size) { + return offset_size == 0 || offset_size == 4 || offset_size == 8; + } + KeyedVector<uint32_t, ItemLoc> *mItemLocs; + bool mHasConstructMethod1; +}; + +status_t IlocBox::parse(off64_t offset, size_t size) { + status_t err = parseFullBoxHeader(&offset, &size); + if (err != OK) { + return err; + } + if (version() > 2) { + ALOGE("%s: invalid version %d", __FUNCTION__, version()); + return ERROR_MALFORMED; + } + + if (size < 2) { + return ERROR_MALFORMED; + } + uint8_t offset_size; + if (!source()->readAt(offset++, &offset_size, 1)) { + return ERROR_IO; + } + uint8_t length_size = (offset_size & 0xF); + offset_size >>= 4; + + uint8_t base_offset_size; + if (!source()->readAt(offset++, &base_offset_size, 1)) { + return ERROR_IO; + } + uint8_t index_size = 0; + if (version() == 1 || version() == 2) { + index_size = (base_offset_size & 0xF); + } + base_offset_size >>= 4; + size -= 2; + + if (!isSizeFieldValid(offset_size) + || !isSizeFieldValid(length_size) + || !isSizeFieldValid(base_offset_size) + || !isSizeFieldValid((index_size))) { + ALOGE("%s: offset size not valid: %d, %d, %d, %d", __FUNCTION__, + offset_size, length_size, base_offset_size, index_size); + return ERROR_MALFORMED; + } + + uint32_t item_count; + size_t itemFieldSize = version() < 2 ? 2 : 4; + if (size < itemFieldSize) { + return ERROR_MALFORMED; + } + if (!source()->getUInt32Var(offset, &item_count, itemFieldSize)) { + return ERROR_IO; + } + + ALOGV("item_count %lld", (long long) item_count); + offset += itemFieldSize; + size -= itemFieldSize; + + for (size_t i = 0; i < item_count; i++) { + uint32_t item_id; + if (!source()->getUInt32Var(offset, &item_id, itemFieldSize)) { + return ERROR_IO; + } + ALOGV("item[%zu]: id %lld", i, (long long)item_id); + offset += itemFieldSize; + + uint8_t construction_method = 0; + if (version() == 1 || version() == 2) { + uint8_t buf[2]; + if (!source()->readAt(offset, buf, 2)) { + return ERROR_IO; + } + construction_method = (buf[1] & 0xF); + ALOGV("construction_method %d", construction_method); + if (construction_method == 1) { + mHasConstructMethod1 = true; + } + + offset += 2; + } + + uint16_t data_reference_index; + if (!source()->getUInt16(offset, &data_reference_index)) { + return ERROR_IO; + } + ALOGV("data_reference_index %d", data_reference_index); + if (data_reference_index != 0) { + // we don't support reference to other files + return ERROR_UNSUPPORTED; + } + offset += 2; + + uint64_t base_offset = 0; + if (base_offset_size != 0) { + if (!source()->getUInt64Var(offset, &base_offset, base_offset_size)) { + return ERROR_IO; + } + offset += base_offset_size; + } + ALOGV("base_offset %lld", (long long) base_offset); + + ssize_t index = mItemLocs->add(item_id, ItemLoc( + item_id, construction_method, data_reference_index, base_offset)); + ItemLoc &item = mItemLocs->editValueAt(index); + + uint16_t extent_count; + if (!source()->getUInt16(offset, &extent_count)) { + return ERROR_IO; + } + ALOGV("extent_count %d", extent_count); + + if (extent_count > 1 && (offset_size == 0 || length_size == 0)) { + // if the item is dividec into more than one extents, offset and + // length must be present. + return ERROR_MALFORMED; + } + offset += 2; + + for (size_t j = 0; j < extent_count; j++) { + uint64_t extent_index = 1; // default=1 + if ((version() == 1 || version() == 2) && (index_size > 0)) { + if (!source()->getUInt64Var(offset, &extent_index, index_size)) { + return ERROR_IO; + } + // TODO: add support for this mode + offset += index_size; + ALOGV("extent_index %lld", (long long)extent_index); + } + + uint64_t extent_offset = 0; // default=0 + if (offset_size > 0) { + if (!source()->getUInt64Var(offset, &extent_offset, offset_size)) { + return ERROR_IO; + } + offset += offset_size; + } + ALOGV("extent_offset %lld", (long long)extent_offset); + + uint64_t extent_length = 0; // this indicates full length of file + if (length_size > 0) { + if (!source()->getUInt64Var(offset, &extent_length, length_size)) { + return ERROR_IO; + } + offset += length_size; + } + ALOGV("extent_length %lld", (long long)extent_length); + + item.addExtent({ extent_index, extent_offset, extent_length }); + } + } + return OK; +} + +///////////////////////////////////////////////////////////////////// +// +// ItemReference related boxes +// + +struct ItemReference : public Box, public RefBase { + ItemReference(DataSourceBase *source, uint32_t type, uint32_t itemIdSize) : + Box(source, type), mItemId(0), mRefIdSize(itemIdSize) {} + + status_t parse(off64_t offset, size_t size); + + uint32_t itemId() { return mItemId; } + + void apply( + KeyedVector<uint32_t, ImageItem> &itemIdToItemMap, + KeyedVector<uint32_t, ExifItem> &itemIdToExifMap) const; + +private: + uint32_t mItemId; + uint32_t mRefIdSize; + Vector<uint32_t> mRefs; + + DISALLOW_EVIL_CONSTRUCTORS(ItemReference); +}; + +void ItemReference::apply( + KeyedVector<uint32_t, ImageItem> &itemIdToItemMap, + KeyedVector<uint32_t, ExifItem> &itemIdToExifMap) const { + ALOGV("attach reference type 0x%x to item id %d)", type(), mItemId); + + switch(type()) { + case FOURCC('d', 'i', 'm', 'g'): { + ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId); + + // ignore non-image items + if (itemIndex < 0) { + return; + } + + ImageItem &derivedImage = itemIdToItemMap.editValueAt(itemIndex); + if (!derivedImage.dimgRefs.empty()) { + ALOGW("dimgRefs not clean!"); + } + derivedImage.dimgRefs.appendVector(mRefs); + + for (size_t i = 0; i < mRefs.size(); i++) { + itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]); + + // ignore non-image items + if (itemIndex < 0) { + continue; + } + ImageItem &sourceImage = itemIdToItemMap.editValueAt(itemIndex); + + // mark the source image of the derivation as hidden + sourceImage.hidden = true; + } + break; + } + case FOURCC('t', 'h', 'm', 'b'): { + ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId); + + // ignore non-image items + if (itemIndex < 0) { + return; + } + + // mark thumbnail image as hidden, these can be retrieved if the client + // request thumbnail explicitly, but won't be exposed as displayables. + ImageItem &thumbImage = itemIdToItemMap.editValueAt(itemIndex); + thumbImage.hidden = true; + + for (size_t i = 0; i < mRefs.size(); i++) { + itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]); + + // ignore non-image items + if (itemIndex < 0) { + continue; + } + ALOGV("Image item id %d uses thumbnail item id %d", mRefs[i], mItemId); + ImageItem &masterImage = itemIdToItemMap.editValueAt(itemIndex); + if (!masterImage.thumbnails.empty()) { + ALOGW("already has thumbnails!"); + } + masterImage.thumbnails.push_back(mItemId); + } + break; + } + case FOURCC('c', 'd', 's', 'c'): { + ssize_t itemIndex = itemIdToExifMap.indexOfKey(mItemId); + + // ignore non-exif block items + if (itemIndex < 0) { + return; + } + + for (size_t i = 0; i < mRefs.size(); i++) { + itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]); + + // ignore non-image items + if (itemIndex < 0) { + continue; + } + ALOGV("Image item id %d uses metadata item id %d", mRefs[i], mItemId); + ImageItem &image = itemIdToItemMap.editValueAt(itemIndex); + image.cdscRefs.push_back(mItemId); + } + break; + } + case FOURCC('a', 'u', 'x', 'l'): { + ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId); + + // ignore non-image items + if (itemIndex < 0) { + return; + } + + // mark auxiliary image as hidden + ImageItem &auxImage = itemIdToItemMap.editValueAt(itemIndex); + auxImage.hidden = true; + break; + } + default: + ALOGW("ignoring unsupported ref type 0x%x", type()); + } +} + +status_t ItemReference::parse(off64_t offset, size_t size) { + if (size < mRefIdSize + 2) { + return ERROR_MALFORMED; + } + if (!source()->getUInt32Var(offset, &mItemId, mRefIdSize)) { + return ERROR_IO; + } + offset += mRefIdSize; + + uint16_t count; + if (!source()->getUInt16(offset, &count)) { + return ERROR_IO; + } + offset += 2; + size -= (mRefIdSize + 2); + + if (size < count * mRefIdSize) { + return ERROR_MALFORMED; + } + + for (size_t i = 0; i < count; i++) { + uint32_t refItemId; + if (!source()->getUInt32Var(offset, &refItemId, mRefIdSize)) { + return ERROR_IO; + } + offset += mRefIdSize; + mRefs.push_back(refItemId); + ALOGV("item id %d: referencing item id %d", mItemId, refItemId); + } + + return OK; +} + +struct IrefBox : public FullBox { + IrefBox(DataSourceBase *source, Vector<sp<ItemReference> > *itemRefs) : + FullBox(source, FOURCC('i', 'r', 'e', 'f')), mRefIdSize(0), mItemRefs(itemRefs) {} + + status_t parse(off64_t offset, size_t size); + +protected: + status_t onChunkData(uint32_t type, off64_t offset, size_t size) override; + +private: + uint32_t mRefIdSize; + Vector<sp<ItemReference> > *mItemRefs; +}; + +status_t IrefBox::parse(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + status_t err = parseFullBoxHeader(&offset, &size); + if (err != OK) { + return err; + } + + mRefIdSize = (version() == 0) ? 2 : 4; + return parseChunks(offset, size); +} + +status_t IrefBox::onChunkData(uint32_t type, off64_t offset, size_t size) { + sp<ItemReference> itemRef = new ItemReference(source(), type, mRefIdSize); + + status_t err = itemRef->parse(offset, size); + if (err != OK) { + return err; + } + mItemRefs->push_back(itemRef); + return OK; +} + +///////////////////////////////////////////////////////////////////// +// +// ItemProperty related boxes +// + +struct AssociationEntry { + uint32_t itemId; + bool essential; + uint16_t index; +}; + +struct ItemProperty : public RefBase { + ItemProperty() {} + + virtual void attachTo(ImageItem &/*image*/) const { + ALOGW("Unrecognized property"); + } + virtual status_t parse(off64_t /*offset*/, size_t /*size*/) { + ALOGW("Unrecognized property"); + return OK; + } + +private: + DISALLOW_EVIL_CONSTRUCTORS(ItemProperty); +}; + +struct IspeBox : public FullBox, public ItemProperty { + IspeBox(DataSourceBase *source) : + FullBox(source, FOURCC('i', 's', 'p', 'e')), mWidth(0), mHeight(0) {} + + status_t parse(off64_t offset, size_t size) override; + + void attachTo(ImageItem &image) const override { + image.width = mWidth; + image.height = mHeight; + } + +private: + uint32_t mWidth; + uint32_t mHeight; +}; + +status_t IspeBox::parse(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + status_t err = parseFullBoxHeader(&offset, &size); + if (err != OK) { + return err; + } + + if (size < 8) { + return ERROR_MALFORMED; + } + if (!source()->getUInt32(offset, &mWidth) + || !source()->getUInt32(offset + 4, &mHeight)) { + return ERROR_IO; + } + ALOGV("property ispe: %dx%d", mWidth, mHeight); + + return OK; +} + +struct HvccBox : public Box, public ItemProperty { + HvccBox(DataSourceBase *source) : + Box(source, FOURCC('h', 'v', 'c', 'C')) {} + + status_t parse(off64_t offset, size_t size) override; + + void attachTo(ImageItem &image) const override { + image.hvcc = mHVCC; + } + +private: + sp<ABuffer> mHVCC; +}; + +status_t HvccBox::parse(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + mHVCC = new ABuffer(size); + + if (mHVCC->data() == NULL) { + ALOGE("b/28471206"); + return NO_MEMORY; + } + + if (source()->readAt(offset, mHVCC->data(), size) < (ssize_t)size) { + return ERROR_IO; + } + + ALOGV("property hvcC"); + + return OK; +} + +struct IrotBox : public Box, public ItemProperty { + IrotBox(DataSourceBase *source) : + Box(source, FOURCC('i', 'r', 'o', 't')), mAngle(0) {} + + status_t parse(off64_t offset, size_t size) override; + + void attachTo(ImageItem &image) const override { + image.rotation = mAngle * 90; + } + +private: + uint8_t mAngle; +}; + +status_t IrotBox::parse(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + if (size < 1) { + return ERROR_MALFORMED; + } + if (source()->readAt(offset, &mAngle, 1) != 1) { + return ERROR_IO; + } + mAngle &= 0x3; + ALOGV("property irot: %d", mAngle); + + return OK; +} + +struct ColrBox : public Box, public ItemProperty { + ColrBox(DataSourceBase *source) : + Box(source, FOURCC('c', 'o', 'l', 'r')) {} + + status_t parse(off64_t offset, size_t size) override; + + void attachTo(ImageItem &image) const override { + image.icc = mICCData; + } + +private: + sp<ABuffer> mICCData; +}; + +status_t ColrBox::parse(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + if (size < 4) { + return ERROR_MALFORMED; + } + uint32_t colour_type; + if (!source()->getUInt32(offset, &colour_type)) { + return ERROR_IO; + } + offset += 4; + size -= 4; + if (colour_type == FOURCC('n', 'c', 'l', 'x')) { + return OK; + } + if ((colour_type != FOURCC('r', 'I', 'C', 'C')) && + (colour_type != FOURCC('p', 'r', 'o', 'f'))) { + return ERROR_MALFORMED; + } + + mICCData = new ABuffer(size); + if (mICCData->data() == NULL) { + ALOGE("b/28471206"); + return NO_MEMORY; + } + + if (source()->readAt(offset, mICCData->data(), size) != (ssize_t)size) { + return ERROR_IO; + } + + ALOGV("property Colr: size %zd", size); + return OK; +} + +struct IpmaBox : public FullBox { + IpmaBox(DataSourceBase *source, Vector<AssociationEntry> *associations) : + FullBox(source, FOURCC('i', 'p', 'm', 'a')), mAssociations(associations) {} + + status_t parse(off64_t offset, size_t size); +private: + Vector<AssociationEntry> *mAssociations; +}; + +status_t IpmaBox::parse(off64_t offset, size_t size) { + status_t err = parseFullBoxHeader(&offset, &size); + if (err != OK) { + return err; + } + + if (size < 4) { + return ERROR_MALFORMED; + } + uint32_t entryCount; + if (!source()->getUInt32(offset, &entryCount)) { + return ERROR_IO; + } + offset += 4; + size -= 4; + + for (size_t k = 0; k < entryCount; ++k) { + uint32_t itemId = 0; + size_t itemIdSize = (version() < 1) ? 2 : 4; + + if (size < itemIdSize + 1) { + return ERROR_MALFORMED; + } + + if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) { + return ERROR_IO; + } + offset += itemIdSize; + size -= itemIdSize; + + uint8_t associationCount; + if (!source()->readAt(offset, &associationCount, 1)) { + return ERROR_IO; + } + offset++; + size--; + + for (size_t i = 0; i < associationCount; ++i) { + size_t propIndexSize = (flags() & 1) ? 2 : 1; + if (size < propIndexSize) { + return ERROR_MALFORMED; + } + uint16_t propIndex; + if (!source()->getUInt16Var(offset, &propIndex, propIndexSize)) { + return ERROR_IO; + } + offset += propIndexSize; + size -= propIndexSize; + uint16_t bitmask = (1 << (8 * propIndexSize - 1)); + AssociationEntry entry = { + .itemId = itemId, + .essential = !!(propIndex & bitmask), + .index = (uint16_t) (propIndex & ~bitmask) + }; + + ALOGV("item id %d associated to property %d (essential %d)", + itemId, entry.index, entry.essential); + + mAssociations->push_back(entry); + } + } + + return OK; +} + +struct IpcoBox : public Box { + IpcoBox(DataSourceBase *source, Vector<sp<ItemProperty> > *properties) : + Box(source, FOURCC('i', 'p', 'c', 'o')), mItemProperties(properties) {} + + status_t parse(off64_t offset, size_t size); +protected: + status_t onChunkData(uint32_t type, off64_t offset, size_t size) override; + +private: + Vector<sp<ItemProperty> > *mItemProperties; +}; + +status_t IpcoBox::parse(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + // push dummy as the index is 1-based + mItemProperties->push_back(new ItemProperty()); + return parseChunks(offset, size); +} + +status_t IpcoBox::onChunkData(uint32_t type, off64_t offset, size_t size) { + sp<ItemProperty> itemProperty; + switch(type) { + case FOURCC('h', 'v', 'c', 'C'): + { + itemProperty = new HvccBox(source()); + break; + } + case FOURCC('i', 's', 'p', 'e'): + { + itemProperty = new IspeBox(source()); + break; + } + case FOURCC('i', 'r', 'o', 't'): + { + itemProperty = new IrotBox(source()); + break; + } + case FOURCC('c', 'o', 'l', 'r'): + { + itemProperty = new ColrBox(source()); + break; + } + default: + { + // push dummy to maintain correct item property index + itemProperty = new ItemProperty(); + break; + } + } + status_t err = itemProperty->parse(offset, size); + if (err != OK) { + return err; + } + mItemProperties->push_back(itemProperty); + return OK; +} + +struct IprpBox : public Box { + IprpBox(DataSourceBase *source, + Vector<sp<ItemProperty> > *properties, + Vector<AssociationEntry> *associations) : + Box(source, FOURCC('i', 'p', 'r', 'p')), + mProperties(properties), mAssociations(associations) {} + + status_t parse(off64_t offset, size_t size); +protected: + status_t onChunkData(uint32_t type, off64_t offset, size_t size) override; + +private: + Vector<sp<ItemProperty> > *mProperties; + Vector<AssociationEntry> *mAssociations; +}; + +status_t IprpBox::parse(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + status_t err = parseChunks(offset, size); + if (err != OK) { + return err; + } + return OK; +} + +status_t IprpBox::onChunkData(uint32_t type, off64_t offset, size_t size) { + switch(type) { + case FOURCC('i', 'p', 'c', 'o'): + { + IpcoBox ipcoBox(source(), mProperties); + return ipcoBox.parse(offset, size); + } + case FOURCC('i', 'p', 'm', 'a'): + { + IpmaBox ipmaBox(source(), mAssociations); + return ipmaBox.parse(offset, size); + } + default: + { + ALOGW("Unrecognized box."); + break; + } + } + return OK; +} + +///////////////////////////////////////////////////////////////////// +// +// ItemInfo related boxes +// +struct ItemInfo { + uint32_t itemId; + uint32_t itemType; + bool hidden; +}; + +struct InfeBox : public FullBox { + InfeBox(DataSourceBase *source) : + FullBox(source, FOURCC('i', 'n', 'f', 'e')) {} + + status_t parse(off64_t offset, size_t size, ItemInfo *itemInfo); + +private: + bool parseNullTerminatedString(off64_t *offset, size_t *size, String8 *out); +}; + +bool InfeBox::parseNullTerminatedString( + off64_t *offset, size_t *size, String8 *out) { + char tmp; + Vector<char> buf; + buf.setCapacity(256); + off64_t newOffset = *offset; + off64_t stopOffset = *offset + *size; + while (newOffset < stopOffset) { + if (!source()->readAt(newOffset++, &tmp, 1)) { + return false; + } + buf.push_back(tmp); + if (tmp == 0) { + out->setTo(buf.array()); + + *offset = newOffset; + *size = stopOffset - newOffset; + + return true; + } + } + return false; +} + +status_t InfeBox::parse(off64_t offset, size_t size, ItemInfo *itemInfo) { + status_t err = parseFullBoxHeader(&offset, &size); + if (err != OK) { + return err; + } + + if (version() == 0 || version() == 1) { + return ERROR_UNSUPPORTED; + } else { // version >= 2 + uint32_t item_id; + size_t itemIdSize = (version() == 2) ? 2 : 4; + if (size < itemIdSize + 6) { + return ERROR_MALFORMED; + } + if (!source()->getUInt32Var(offset, &item_id, itemIdSize)) { + return ERROR_IO; + } + ALOGV("item_id %d", item_id); + offset += itemIdSize; + uint16_t item_protection_index; + if (!source()->getUInt16(offset, &item_protection_index)) { + return ERROR_IO; + } + ALOGV("item_protection_index %d", item_protection_index); + offset += 2; + uint32_t item_type; + if (!source()->getUInt32(offset, &item_type)) { + return ERROR_IO; + } + + itemInfo->itemId = item_id; + itemInfo->itemType = item_type; + // According to HEIF spec, (flags & 1) indicates the image is hidden + // and not supposed to be displayed. + itemInfo->hidden = (flags() & 1); + + char itemTypeString[5]; + MakeFourCCString(item_type, itemTypeString); + ALOGV("item_type %s", itemTypeString); + offset += 4; + size -= itemIdSize + 6; + + String8 item_name; + if (!parseNullTerminatedString(&offset, &size, &item_name)) { + return ERROR_MALFORMED; + } + ALOGV("item_name %s", item_name.c_str()); + + if (item_type == FOURCC('m', 'i', 'm', 'e')) { + String8 content_type; + if (!parseNullTerminatedString(&offset, &size, &content_type)) { + return ERROR_MALFORMED; + } + + // content_encoding is optional; can be omitted if would be empty + if (size > 0) { + String8 content_encoding; + if (!parseNullTerminatedString(&offset, &size, &content_encoding)) { + return ERROR_MALFORMED; + } + } + } else if (item_type == FOURCC('u', 'r', 'i', ' ')) { + String8 item_uri_type; + if (!parseNullTerminatedString(&offset, &size, &item_uri_type)) { + return ERROR_MALFORMED; + } + } + } + return OK; +} + +struct IinfBox : public FullBox { + IinfBox(DataSourceBase *source, Vector<ItemInfo> *itemInfos) : + FullBox(source, FOURCC('i', 'i', 'n', 'f')), + mItemInfos(itemInfos), mHasGrids(false) {} + + status_t parse(off64_t offset, size_t size); + + bool hasGrids() { return mHasGrids; } + +protected: + status_t onChunkData(uint32_t type, off64_t offset, size_t size) override; + +private: + Vector<ItemInfo> *mItemInfos; + bool mHasGrids; +}; + +status_t IinfBox::parse(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + status_t err = parseFullBoxHeader(&offset, &size); + if (err != OK) { + return err; + } + + size_t entryCountSize = version() == 0 ? 2 : 4; + if (size < entryCountSize) { + return ERROR_MALFORMED; + } + uint32_t entry_count; + if (!source()->getUInt32Var(offset, &entry_count, entryCountSize)) { + return ERROR_IO; + } + ALOGV("entry_count %d", entry_count); + + off64_t stopOffset = offset + size; + offset += entryCountSize; + for (size_t i = 0; i < entry_count && offset < stopOffset; i++) { + ALOGV("entry %zu", i); + status_t err = parseChunk(&offset); + if (err != OK) { + return err; + } + } + if (offset != stopOffset) { + return ERROR_MALFORMED; + } + + return OK; +} + +status_t IinfBox::onChunkData(uint32_t type, off64_t offset, size_t size) { + if (type != FOURCC('i', 'n', 'f', 'e')) { + return OK; + } + + InfeBox infeBox(source()); + ItemInfo itemInfo; + status_t err = infeBox.parse(offset, size, &itemInfo); + if (err == OK) { + mItemInfos->push_back(itemInfo); + mHasGrids |= (itemInfo.itemType == FOURCC('g', 'r', 'i', 'd')); + } + // InfeBox parse returns ERROR_UNSUPPORTED if the box if an unsupported + // version. Ignore this error as it's not fatal. + return (err == ERROR_UNSUPPORTED) ? OK : err; +} + +////////////////////////////////////////////////////////////////// + +ItemTable::ItemTable(DataSourceBase *source) + : mDataSource(source), + mPrimaryItemId(0), + mIdatOffset(0), + mIdatSize(0), + mImageItemsValid(false), + mCurrentItemIndex(0) { + mRequiredBoxes.insert('iprp'); + mRequiredBoxes.insert('iloc'); + mRequiredBoxes.insert('pitm'); + mRequiredBoxes.insert('iinf'); +} + +ItemTable::~ItemTable() {} + +status_t ItemTable::parse(uint32_t type, off64_t data_offset, size_t chunk_data_size) { + switch(type) { + case FOURCC('i', 'l', 'o', 'c'): + { + return parseIlocBox(data_offset, chunk_data_size); + } + case FOURCC('i', 'i', 'n', 'f'): + { + return parseIinfBox(data_offset, chunk_data_size); + } + case FOURCC('i', 'p', 'r', 'p'): + { + return parseIprpBox(data_offset, chunk_data_size); + } + case FOURCC('p', 'i', 't', 'm'): + { + return parsePitmBox(data_offset, chunk_data_size); + } + case FOURCC('i', 'd', 'a', 't'): + { + return parseIdatBox(data_offset, chunk_data_size); + } + case FOURCC('i', 'r', 'e', 'f'): + { + return parseIrefBox(data_offset, chunk_data_size); + } + case FOURCC('i', 'p', 'r', 'o'): + { + ALOGW("ipro box not supported!"); + break; + } + default: + { + ALOGW("unrecognized box type: 0x%x", type); + break; + } + } + return ERROR_UNSUPPORTED; +} + +status_t ItemTable::parseIlocBox(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + IlocBox ilocBox(mDataSource, &mItemLocs); + status_t err = ilocBox.parse(offset, size); + if (err != OK) { + return err; + } + + if (ilocBox.hasConstructMethod1()) { + mRequiredBoxes.insert('idat'); + } + + return buildImageItemsIfPossible('iloc'); +} + +status_t ItemTable::parseIinfBox(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + IinfBox iinfBox(mDataSource, &mItemInfos); + status_t err = iinfBox.parse(offset, size); + if (err != OK) { + return err; + } + + if (iinfBox.hasGrids()) { + mRequiredBoxes.insert('iref'); + } + + return buildImageItemsIfPossible('iinf'); +} + +status_t ItemTable::parsePitmBox(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + PitmBox pitmBox(mDataSource); + status_t err = pitmBox.parse(offset, size, &mPrimaryItemId); + if (err != OK) { + return err; + } + + return buildImageItemsIfPossible('pitm'); +} + +status_t ItemTable::parseIprpBox(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + IprpBox iprpBox(mDataSource, &mItemProperties, &mAssociations); + status_t err = iprpBox.parse(offset, size); + if (err != OK) { + return err; + } + + return buildImageItemsIfPossible('iprp'); +} + +status_t ItemTable::parseIdatBox(off64_t offset, size_t size) { + ALOGV("%s: idat offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + // only remember the offset and size of idat box for later use + mIdatOffset = offset; + mIdatSize = size; + + return buildImageItemsIfPossible('idat'); +} + +status_t ItemTable::parseIrefBox(off64_t offset, size_t size) { + ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size); + + IrefBox irefBox(mDataSource, &mItemReferences); + status_t err = irefBox.parse(offset, size); + if (err != OK) { + return err; + } + + return buildImageItemsIfPossible('iref'); +} + +status_t ItemTable::buildImageItemsIfPossible(uint32_t type) { + if (mImageItemsValid) { + return OK; + } + + mBoxesSeen.insert(type); + + // need at least 'iprp', 'iloc', 'pitm', 'iinf'; + // need 'idat' if any items used construction_method of 2; + // need 'iref' if there are grids. + if (!std::includes( + mBoxesSeen.begin(), mBoxesSeen.end(), + mRequiredBoxes.begin(), mRequiredBoxes.end())) { + return OK; + } + + ALOGV("building image table..."); + + for (size_t i = 0; i < mItemInfos.size(); i++) { + const ItemInfo &info = mItemInfos[i]; + + // Only handle 3 types of items, all others are ignored: + // 'grid': derived image from tiles + // 'hvc1': coded image (or tile) + // 'Exif': EXIF metadata + if (info.itemType != FOURCC('g', 'r', 'i', 'd') && + info.itemType != FOURCC('h', 'v', 'c', '1') && + info.itemType != FOURCC('E', 'x', 'i', 'f')) { + continue; + } + + ssize_t itemIndex = mItemIdToItemMap.indexOfKey(info.itemId); + if (itemIndex >= 0) { + ALOGW("ignoring duplicate image item id %d", info.itemId); + continue; + } + + ssize_t ilocIndex = mItemLocs.indexOfKey(info.itemId); + if (ilocIndex < 0) { + ALOGE("iloc missing for image item id %d", info.itemId); + continue; + } + const ItemLoc &iloc = mItemLocs[ilocIndex]; + + off64_t offset; + size_t size; + if (iloc.getLoc(&offset, &size, mIdatOffset, mIdatSize) != OK) { + return ERROR_MALFORMED; + } + + if (info.itemType == FOURCC('E', 'x', 'i', 'f')) { + // Only add if the Exif data is non-empty. The first 4 bytes contain + // the offset to TIFF header, which the Exif parser doesn't use. + if (size > 4) { + ExifItem exifItem = { + .offset = offset, + .size = size, + }; + mItemIdToExifMap.add(info.itemId, exifItem); + } + continue; + } + + ImageItem image(info.itemType, info.itemId, info.hidden); + + ALOGV("adding %s: itemId %d", image.isGrid() ? "grid" : "image", info.itemId); + + if (image.isGrid()) { + // ImageGrid struct is at least 8-byte, at most 12-byte (if flags&1) + if (size < 8 || size > 12) { + return ERROR_MALFORMED; + } + uint8_t buf[12]; + if (!mDataSource->readAt(offset, buf, size)) { + return ERROR_IO; + } + + image.rows = buf[2] + 1; + image.columns = buf[3] + 1; + + ALOGV("rows %d, columans %d", image.rows, image.columns); + } else { + image.offset = offset; + image.size = size; + } + mItemIdToItemMap.add(info.itemId, image); + } + + for (size_t i = 0; i < mAssociations.size(); i++) { + attachProperty(mAssociations[i]); + } + + for (size_t i = 0; i < mItemReferences.size(); i++) { + mItemReferences[i]->apply(mItemIdToItemMap, mItemIdToExifMap); + } + + bool foundPrimary = false; + for (size_t i = 0; i < mItemIdToItemMap.size(); i++) { + // add all non-hidden images, also add the primary even if it's marked + // hidden, in case the primary is set to a thumbnail + bool isPrimary = (mItemIdToItemMap[i].itemId == mPrimaryItemId); + if (!mItemIdToItemMap[i].hidden || isPrimary) { + mDisplayables.push_back(i); + } + foundPrimary |= isPrimary; + } + + ALOGV("found %zu displayables", mDisplayables.size()); + + // fail if no displayables are found + if (mDisplayables.empty()) { + return ERROR_MALFORMED; + } + + // if the primary item id is invalid, set primary to the first displayable + if (!foundPrimary) { + mPrimaryItemId = mItemIdToItemMap[mDisplayables[0]].itemId; + } + + mImageItemsValid = true; + return OK; +} + +void ItemTable::attachProperty(const AssociationEntry &association) { + ssize_t itemIndex = mItemIdToItemMap.indexOfKey(association.itemId); + + // ignore non-image items + if (itemIndex < 0) { + return; + } + + uint16_t propertyIndex = association.index; + if (propertyIndex >= mItemProperties.size()) { + ALOGW("Ignoring invalid property index %d", propertyIndex); + return; + } + + ALOGV("attach property %d to item id %d)", + propertyIndex, association.itemId); + + mItemProperties[propertyIndex]->attachTo(mItemIdToItemMap.editValueAt(itemIndex)); +} + +uint32_t ItemTable::countImages() const { + return mImageItemsValid ? mDisplayables.size() : 0; +} + +sp<MetaData> ItemTable::getImageMeta(const uint32_t imageIndex) { + if (!mImageItemsValid) { + return NULL; + } + + if (imageIndex >= mDisplayables.size()) { + ALOGE("%s: invalid image index %u", __FUNCTION__, imageIndex); + return NULL; + } + const uint32_t itemIndex = mDisplayables[imageIndex]; + ALOGV("image[%u]: item index %u", imageIndex, itemIndex); + + const ImageItem *image = &mItemIdToItemMap[itemIndex]; + + ssize_t tileItemIndex = -1; + if (image->isGrid()) { + if (image->dimgRefs.empty()) { + return NULL; + } + tileItemIndex = mItemIdToItemMap.indexOfKey(image->dimgRefs[0]); + if (tileItemIndex < 0) { + return NULL; + } + } + + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC); + + if (image->itemId == mPrimaryItemId) { + meta->setInt32(kKeyTrackIsDefault, 1); + } + + ALOGV("image[%u]: size %dx%d", imageIndex, image->width, image->height); + + meta->setInt32(kKeyWidth, image->width); + meta->setInt32(kKeyHeight, image->height); + if (image->rotation != 0) { + // Rotation angle in HEIF is CCW, convert to CW here to be + // consistent with the other media formats. + switch(image->rotation) { + case 90: meta->setInt32(kKeyRotation, 270); break; + case 180: meta->setInt32(kKeyRotation, 180); break; + case 270: meta->setInt32(kKeyRotation, 90); break; + default: break; // don't set if invalid + } + } + meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5); + + if (!image->thumbnails.empty()) { + ssize_t thumbItemIndex = mItemIdToItemMap.indexOfKey(image->thumbnails[0]); + if (thumbItemIndex >= 0) { + const ImageItem &thumbnail = mItemIdToItemMap[thumbItemIndex]; + + meta->setInt32(kKeyThumbnailWidth, thumbnail.width); + meta->setInt32(kKeyThumbnailHeight, thumbnail.height); + meta->setData(kKeyThumbnailHVCC, kTypeHVCC, + thumbnail.hvcc->data(), thumbnail.hvcc->size()); + ALOGV("image[%u]: thumbnail: size %dx%d, item index %zd", + imageIndex, thumbnail.width, thumbnail.height, thumbItemIndex); + } else { + ALOGW("%s: Referenced thumbnail does not exist!", __FUNCTION__); + } + } + + if (image->isGrid()) { + meta->setInt32(kKeyGridRows, image->rows); + meta->setInt32(kKeyGridCols, image->columns); + + // point image to the first tile for grid size and HVCC + image = &mItemIdToItemMap.editValueAt(tileItemIndex); + meta->setInt32(kKeyTileWidth, image->width); + meta->setInt32(kKeyTileHeight, image->height); + meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5); + } + + if (image->hvcc == NULL) { + ALOGE("%s: hvcc is missing for image[%u]!", __FUNCTION__, imageIndex); + return NULL; + } + meta->setData(kKeyHVCC, kTypeHVCC, image->hvcc->data(), image->hvcc->size()); + + if (image->icc != NULL) { + meta->setData(kKeyIccProfile, 0, image->icc->data(), image->icc->size()); + } + return meta; +} + +status_t ItemTable::findImageItem(const uint32_t imageIndex, uint32_t *itemIndex) { + if (!mImageItemsValid) { + return INVALID_OPERATION; + } + + if (imageIndex >= mDisplayables.size()) { + ALOGE("%s: invalid image index %d", __FUNCTION__, imageIndex); + return BAD_VALUE; + } + + *itemIndex = mDisplayables[imageIndex]; + + ALOGV("image[%u]: item index %u", imageIndex, *itemIndex); + return OK; +} + +status_t ItemTable::findThumbnailItem(const uint32_t imageIndex, uint32_t *itemIndex) { + if (!mImageItemsValid) { + return INVALID_OPERATION; + } + + if (imageIndex >= mDisplayables.size()) { + ALOGE("%s: invalid image index %d", __FUNCTION__, imageIndex); + return BAD_VALUE; + } + + uint32_t masterItemIndex = mDisplayables[imageIndex]; + + const ImageItem &masterImage = mItemIdToItemMap[masterItemIndex]; + if (masterImage.thumbnails.empty()) { + *itemIndex = masterItemIndex; + return OK; + } + + ssize_t thumbItemIndex = mItemIdToItemMap.indexOfKey(masterImage.thumbnails[0]); + if (thumbItemIndex < 0) { + // Do not return the master image in this case, fail it so that the + // thumbnail extraction code knows we really don't have it. + return INVALID_OPERATION; + } + + *itemIndex = thumbItemIndex; + return OK; +} + +status_t ItemTable::getImageOffsetAndSize( + uint32_t *itemIndex, off64_t *offset, size_t *size) { + if (!mImageItemsValid) { + return INVALID_OPERATION; + } + + if (itemIndex != NULL) { + if (*itemIndex >= mItemIdToItemMap.size()) { + ALOGE("%s: Bad item index!", __FUNCTION__); + return BAD_VALUE; + } + mCurrentItemIndex = *itemIndex; + } + + ImageItem &image = mItemIdToItemMap.editValueAt(mCurrentItemIndex); + if (image.isGrid()) { + uint32_t tileItemId; + status_t err = image.getNextTileItemId(&tileItemId, itemIndex != NULL); + if (err != OK) { + return err; + } + ssize_t tileItemIndex = mItemIdToItemMap.indexOfKey(tileItemId); + if (tileItemIndex < 0) { + return ERROR_END_OF_STREAM; + } + *offset = mItemIdToItemMap[tileItemIndex].offset; + *size = mItemIdToItemMap[tileItemIndex].size; + } else { + if (itemIndex == NULL) { + // For single images, we only allow it to be read once, after that + // it's EOS. New item index must be requested each time. + return ERROR_END_OF_STREAM; + } + *offset = mItemIdToItemMap[mCurrentItemIndex].offset; + *size = mItemIdToItemMap[mCurrentItemIndex].size; + } + + return OK; +} + +status_t ItemTable::getExifOffsetAndSize(off64_t *offset, size_t *size) { + if (!mImageItemsValid) { + return INVALID_OPERATION; + } + + ssize_t itemIndex = mItemIdToItemMap.indexOfKey(mPrimaryItemId); + + // this should not happen, something's seriously wrong. + if (itemIndex < 0) { + return INVALID_OPERATION; + } + + const ImageItem &image = mItemIdToItemMap[itemIndex]; + if (image.cdscRefs.size() == 0) { + return NAME_NOT_FOUND; + } + + ssize_t exifIndex = mItemIdToExifMap.indexOfKey(image.cdscRefs[0]); + if (exifIndex < 0) { + return NAME_NOT_FOUND; + } + + // skip the first 4-byte of the offset to TIFF header + *offset = mItemIdToExifMap[exifIndex].offset + 4; + *size = mItemIdToExifMap[exifIndex].size - 4; + return OK; +} + +} // namespace heif + +} // namespace android
diff --git a/media/extractors/mp4/ItemTable.h b/media/extractors/mp4/ItemTable.h new file mode 100644 index 0000000..536dcb0 --- /dev/null +++ b/media/extractors/mp4/ItemTable.h
@@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ITEM_TABLE_H_ +#define ITEM_TABLE_H_ + +#include <set> + +#include <media/stagefright/foundation/ADebug.h> +#include <utils/KeyedVector.h> +#include <utils/RefBase.h> + +namespace android { + +class DataSourceBase; +class MetaData; + +namespace heif { + +struct AssociationEntry; +struct ImageItem; +struct ExifItem; +struct ItemLoc; +struct ItemInfo; +struct ItemProperty; +struct ItemReference; + +/* + * ItemTable keeps track of all image items (including coded images, grids and + * tiles) inside a HEIF still image (ISO/IEC FDIS 23008-12.2:2017(E)). + */ + +class ItemTable : public RefBase { +public: + explicit ItemTable(DataSourceBase *source); + + status_t parse(uint32_t type, off64_t offset, size_t size); + + bool isValid() { return mImageItemsValid; } + uint32_t countImages() const; + sp<MetaData> getImageMeta(const uint32_t imageIndex); + status_t findImageItem(const uint32_t imageIndex, uint32_t *itemIndex); + status_t findThumbnailItem(const uint32_t imageIndex, uint32_t *itemIndex); + status_t getImageOffsetAndSize( + uint32_t *itemIndex, off64_t *offset, size_t *size); + status_t getExifOffsetAndSize(off64_t *offset, size_t *size); + +protected: + ~ItemTable(); + +private: + DataSourceBase *mDataSource; + + KeyedVector<uint32_t, ItemLoc> mItemLocs; + Vector<ItemInfo> mItemInfos; + Vector<AssociationEntry> mAssociations; + Vector<sp<ItemProperty> > mItemProperties; + Vector<sp<ItemReference> > mItemReferences; + + uint32_t mPrimaryItemId; + off64_t mIdatOffset; + size_t mIdatSize; + + std::set<uint32_t> mRequiredBoxes; + std::set<uint32_t> mBoxesSeen; + + bool mImageItemsValid; + uint32_t mCurrentItemIndex; + KeyedVector<uint32_t, ImageItem> mItemIdToItemMap; + KeyedVector<uint32_t, ExifItem> mItemIdToExifMap; + Vector<uint32_t> mDisplayables; + + status_t parseIlocBox(off64_t offset, size_t size); + status_t parseIinfBox(off64_t offset, size_t size); + status_t parsePitmBox(off64_t offset, size_t size); + status_t parseIprpBox(off64_t offset, size_t size); + status_t parseIdatBox(off64_t offset, size_t size); + status_t parseIrefBox(off64_t offset, size_t size); + + void attachProperty(const AssociationEntry &association); + status_t buildImageItemsIfPossible(uint32_t type); + + DISALLOW_EVIL_CONSTRUCTORS(ItemTable); +}; + +} // namespace heif +} // namespace android + +#endif // ITEM_TABLE_H_
diff --git a/media/libstagefright/matroska/MODULE_LICENSE_APACHE2 b/media/extractors/mp4/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/libstagefright/matroska/MODULE_LICENSE_APACHE2 copy to media/extractors/mp4/MODULE_LICENSE_APACHE2
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp new file mode 100644 index 0000000..7b3b81d --- /dev/null +++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -0,0 +1,5585 @@ +/* + * Copyright (C) 2009 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 "MPEG4Extractor" + +#include <ctype.h> +#include <inttypes.h> +#include <memory> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <utils/Log.h> + +#include "MPEG4Extractor.h" +#include "SampleTable.h" +#include "ItemTable.h" +#include "include/ESDS.h" + +#include <media/ExtractorUtils.h> +#include <media/MediaTrack.h> +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/AUtils.h> +#include <media/stagefright/foundation/ByteUtils.h> +#include <media/stagefright/foundation/ColorUtils.h> +#include <media/stagefright/foundation/avc_utils.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaBufferBase.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <utils/String8.h> + +#include <byteswap.h> +#include "include/ID3.h" + +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +namespace android { + +enum { + // max track header chunk to return + kMaxTrackHeaderSize = 32, + + // maximum size of an atom. Some atoms can be bigger according to the spec, + // but we only allow up to this size. + kMaxAtomSize = 64 * 1024 * 1024, +}; + +class MPEG4Source : public MediaTrack { +public: + // Caller retains ownership of both "dataSource" and "sampleTable". + MPEG4Source(MetaDataBase &format, + DataSourceBase *dataSource, + int32_t timeScale, + const sp<SampleTable> &sampleTable, + Vector<SidxEntry> &sidx, + const Trex *trex, + off64_t firstMoofOffset, + const sp<ItemTable> &itemTable); + virtual status_t init(); + + virtual status_t start(MetaDataBase *params = NULL); + virtual status_t stop(); + + virtual status_t getFormat(MetaDataBase &); + + virtual status_t read(MediaBufferBase **buffer, const ReadOptions *options = NULL); + virtual bool supportNonblockingRead() { return true; } + virtual status_t fragmentedRead(MediaBufferBase **buffer, const ReadOptions *options = NULL); + + virtual ~MPEG4Source(); + +private: + Mutex mLock; + + MetaDataBase &mFormat; + DataSourceBase *mDataSource; + int32_t mTimescale; + sp<SampleTable> mSampleTable; + uint32_t mCurrentSampleIndex; + uint32_t mCurrentFragmentIndex; + Vector<SidxEntry> &mSegments; + const Trex *mTrex; + off64_t mFirstMoofOffset; + off64_t mCurrentMoofOffset; + off64_t mNextMoofOffset; + uint32_t mCurrentTime; + int32_t mLastParsedTrackId; + int32_t mTrackId; + + int32_t mCryptoMode; // passed in from extractor + int32_t mDefaultIVSize; // passed in from extractor + uint8_t mCryptoKey[16]; // passed in from extractor + int32_t mDefaultEncryptedByteBlock; + int32_t mDefaultSkipByteBlock; + uint32_t mCurrentAuxInfoType; + uint32_t mCurrentAuxInfoTypeParameter; + int32_t mCurrentDefaultSampleInfoSize; + uint32_t mCurrentSampleInfoCount; + uint32_t mCurrentSampleInfoAllocSize; + uint8_t* mCurrentSampleInfoSizes; + uint32_t mCurrentSampleInfoOffsetCount; + uint32_t mCurrentSampleInfoOffsetsAllocSize; + uint64_t* mCurrentSampleInfoOffsets; + + bool mIsAVC; + bool mIsHEVC; + size_t mNALLengthSize; + + bool mStarted; + + MediaBufferGroup *mGroup; + + MediaBufferBase *mBuffer; + + bool mWantsNALFragments; + + uint8_t *mSrcBuffer; + + bool mIsHeif; + sp<ItemTable> mItemTable; + + size_t parseNALSize(const uint8_t *data) const; + status_t parseChunk(off64_t *offset); + status_t parseTrackFragmentHeader(off64_t offset, off64_t size); + status_t parseTrackFragmentRun(off64_t offset, off64_t size); + status_t parseSampleAuxiliaryInformationSizes(off64_t offset, off64_t size); + status_t parseSampleAuxiliaryInformationOffsets(off64_t offset, off64_t size); + status_t parseClearEncryptedSizes(off64_t offset, bool isSubsampleEncryption, uint32_t flags); + status_t parseSampleEncryption(off64_t offset); + + struct TrackFragmentHeaderInfo { + enum Flags { + kBaseDataOffsetPresent = 0x01, + kSampleDescriptionIndexPresent = 0x02, + kDefaultSampleDurationPresent = 0x08, + kDefaultSampleSizePresent = 0x10, + kDefaultSampleFlagsPresent = 0x20, + kDurationIsEmpty = 0x10000, + }; + + uint32_t mTrackID; + uint32_t mFlags; + uint64_t mBaseDataOffset; + uint32_t mSampleDescriptionIndex; + uint32_t mDefaultSampleDuration; + uint32_t mDefaultSampleSize; + uint32_t mDefaultSampleFlags; + + uint64_t mDataOffset; + }; + TrackFragmentHeaderInfo mTrackFragmentHeaderInfo; + + struct Sample { + off64_t offset; + size_t size; + uint32_t duration; + int32_t compositionOffset; + uint8_t iv[16]; + Vector<size_t> clearsizes; + Vector<size_t> encryptedsizes; + }; + Vector<Sample> mCurrentSamples; + + MPEG4Source(const MPEG4Source &); + MPEG4Source &operator=(const MPEG4Source &); +}; + +// This custom data source wraps an existing one and satisfies requests +// falling entirely within a cached range from the cache while forwarding +// all remaining requests to the wrapped datasource. +// This is used to cache the full sampletable metadata for a single track, +// possibly wrapping multiple times to cover all tracks, i.e. +// Each CachedRangedDataSource caches the sampletable metadata for a single track. + +struct CachedRangedDataSource : public DataSourceBase { + explicit CachedRangedDataSource(DataSourceBase *source); + virtual ~CachedRangedDataSource(); + + virtual status_t initCheck() const; + virtual ssize_t readAt(off64_t offset, void *data, size_t size); + virtual status_t getSize(off64_t *size); + virtual uint32_t flags(); + + status_t setCachedRange(off64_t offset, size_t size, bool assumeSourceOwnershipOnSuccess); + + +private: + Mutex mLock; + + DataSourceBase *mSource; + bool mOwnsDataSource; + off64_t mCachedOffset; + size_t mCachedSize; + uint8_t *mCache; + + void clearCache(); + + CachedRangedDataSource(const CachedRangedDataSource &); + CachedRangedDataSource &operator=(const CachedRangedDataSource &); +}; + +CachedRangedDataSource::CachedRangedDataSource(DataSourceBase *source) + : mSource(source), + mOwnsDataSource(false), + mCachedOffset(0), + mCachedSize(0), + mCache(NULL) { +} + +CachedRangedDataSource::~CachedRangedDataSource() { + clearCache(); + if (mOwnsDataSource) { + delete (CachedRangedDataSource*)mSource; + } +} + +void CachedRangedDataSource::clearCache() { + if (mCache) { + free(mCache); + mCache = NULL; + } + + mCachedOffset = 0; + mCachedSize = 0; +} + +status_t CachedRangedDataSource::initCheck() const { + return mSource->initCheck(); +} + +ssize_t CachedRangedDataSource::readAt(off64_t offset, void *data, size_t size) { + Mutex::Autolock autoLock(mLock); + + if (isInRange(mCachedOffset, mCachedSize, offset, size)) { + memcpy(data, &mCache[offset - mCachedOffset], size); + return size; + } + + return mSource->readAt(offset, data, size); +} + +status_t CachedRangedDataSource::getSize(off64_t *size) { + return mSource->getSize(size); +} + +uint32_t CachedRangedDataSource::flags() { + return mSource->flags(); +} + +status_t CachedRangedDataSource::setCachedRange(off64_t offset, + size_t size, + bool assumeSourceOwnershipOnSuccess) { + Mutex::Autolock autoLock(mLock); + + clearCache(); + + mCache = (uint8_t *)malloc(size); + + if (mCache == NULL) { + return -ENOMEM; + } + + mCachedOffset = offset; + mCachedSize = size; + + ssize_t err = mSource->readAt(mCachedOffset, mCache, mCachedSize); + + if (err < (ssize_t)size) { + clearCache(); + + return ERROR_IO; + } + mOwnsDataSource = assumeSourceOwnershipOnSuccess; + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +static const bool kUseHexDump = false; + +static const char *FourCC2MIME(uint32_t fourcc) { + switch (fourcc) { + case FOURCC('m', 'p', '4', 'a'): + return MEDIA_MIMETYPE_AUDIO_AAC; + + case FOURCC('s', 'a', 'm', 'r'): + return MEDIA_MIMETYPE_AUDIO_AMR_NB; + + case FOURCC('s', 'a', 'w', 'b'): + return MEDIA_MIMETYPE_AUDIO_AMR_WB; + + case FOURCC('m', 'p', '4', 'v'): + return MEDIA_MIMETYPE_VIDEO_MPEG4; + + case FOURCC('s', '2', '6', '3'): + case FOURCC('h', '2', '6', '3'): + case FOURCC('H', '2', '6', '3'): + return MEDIA_MIMETYPE_VIDEO_H263; + + case FOURCC('a', 'v', 'c', '1'): + return MEDIA_MIMETYPE_VIDEO_AVC; + + case FOURCC('h', 'v', 'c', '1'): + case FOURCC('h', 'e', 'v', '1'): + return MEDIA_MIMETYPE_VIDEO_HEVC; + default: + ALOGW("Unknown fourcc: %c%c%c%c", + (fourcc >> 24) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 8) & 0xff, + fourcc & 0xff + ); + return "application/octet-stream"; + } +} + +static bool AdjustChannelsAndRate(uint32_t fourcc, uint32_t *channels, uint32_t *rate) { + if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, FourCC2MIME(fourcc))) { + // AMR NB audio is always mono, 8kHz + *channels = 1; + *rate = 8000; + return true; + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_WB, FourCC2MIME(fourcc))) { + // AMR WB audio is always mono, 16kHz + *channels = 1; + *rate = 16000; + return true; + } + return false; +} + +MPEG4Extractor::MPEG4Extractor(DataSourceBase *source, const char *mime) + : mMoofOffset(0), + mMoofFound(false), + mMdatFound(false), + mDataSource(source), + mCachedSource(NULL), + mInitCheck(NO_INIT), + mHeaderTimescale(0), + mIsQT(false), + mIsHeif(false), + mHasMoovBox(false), + mPreferHeif(mime != NULL && !strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_HEIF)), + mFirstTrack(NULL), + mLastTrack(NULL) { + ALOGV("mime=%s, mPreferHeif=%d", mime, mPreferHeif); +} + +MPEG4Extractor::~MPEG4Extractor() { + Track *track = mFirstTrack; + while (track) { + Track *next = track->next; + + delete track; + track = next; + } + mFirstTrack = mLastTrack = NULL; + + for (size_t i = 0; i < mPssh.size(); i++) { + delete [] mPssh[i].data; + } + mPssh.clear(); + + delete mCachedSource; +} + +uint32_t MPEG4Extractor::flags() const { + return CAN_PAUSE | + ((mMoofOffset == 0 || mSidxEntries.size() != 0) ? + (CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK) : 0); +} + +status_t MPEG4Extractor::getMetaData(MetaDataBase &meta) { + status_t err; + if ((err = readMetaData()) != OK) { + return UNKNOWN_ERROR; + } + meta = mFileMetaData; + return OK; +} + +size_t MPEG4Extractor::countTracks() { + status_t err; + if ((err = readMetaData()) != OK) { + ALOGV("MPEG4Extractor::countTracks: no tracks"); + return 0; + } + + size_t n = 0; + Track *track = mFirstTrack; + while (track) { + ++n; + track = track->next; + } + + ALOGV("MPEG4Extractor::countTracks: %zu tracks", n); + return n; +} + +status_t MPEG4Extractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t flags) { + status_t err; + if ((err = readMetaData()) != OK) { + return UNKNOWN_ERROR; + } + + Track *track = mFirstTrack; + while (index > 0) { + if (track == NULL) { + return UNKNOWN_ERROR; + } + + track = track->next; + --index; + } + + if (track == NULL) { + return UNKNOWN_ERROR; + } + + [=] { + int64_t duration; + int32_t samplerate; + if (track->has_elst && mHeaderTimescale != 0 && + track->meta.findInt64(kKeyDuration, &duration) && + track->meta.findInt32(kKeySampleRate, &samplerate)) { + + track->has_elst = false; + + if (track->elst_segment_duration > INT64_MAX) { + return; + } + int64_t segment_duration = track->elst_segment_duration; + int64_t media_time = track->elst_media_time; + int64_t halfscale = mHeaderTimescale / 2; + ALOGV("segment_duration = %" PRId64 ", media_time = %" PRId64 + ", halfscale = %" PRId64 ", timescale = %d", + segment_duration, + media_time, + halfscale, + mHeaderTimescale); + + int64_t delay; + // delay = ((media_time * samplerate) + halfscale) / mHeaderTimescale; + if (__builtin_mul_overflow(media_time, samplerate, &delay) || + __builtin_add_overflow(delay, halfscale, &delay) || + (delay /= mHeaderTimescale, false) || + delay > INT32_MAX || + delay < INT32_MIN) { + return; + } + ALOGV("delay = %" PRId64, delay); + track->meta.setInt32(kKeyEncoderDelay, delay); + + int64_t scaled_duration; + // scaled_duration = duration * mHeaderTimescale; + if (__builtin_mul_overflow(duration, mHeaderTimescale, &scaled_duration)) { + return; + } + ALOGV("scaled_duration = %" PRId64, scaled_duration); + + int64_t segment_end; + int64_t padding; + // padding = scaled_duration - ((segment_duration + media_time) * 1000000); + if (__builtin_add_overflow(segment_duration, media_time, &segment_end) || + __builtin_mul_overflow(segment_end, 1000000, &segment_end) || + __builtin_sub_overflow(scaled_duration, segment_end, &padding)) { + return; + } + ALOGV("segment_end = %" PRId64 ", padding = %" PRId64, segment_end, padding); + + if (padding < 0) { + // track duration from media header (which is what kKeyDuration is) might + // be slightly shorter than the segment duration, which would make the + // padding negative. Clamp to zero. + padding = 0; + } + + int64_t paddingsamples; + int64_t halfscale_e6; + int64_t timescale_e6; + // paddingsamples = ((padding * samplerate) + (halfscale * 1000000)) + // / (mHeaderTimescale * 1000000); + if (__builtin_mul_overflow(padding, samplerate, &paddingsamples) || + __builtin_mul_overflow(halfscale, 1000000, &halfscale_e6) || + __builtin_mul_overflow(mHeaderTimescale, 1000000, ×cale_e6) || + __builtin_add_overflow(paddingsamples, halfscale_e6, &paddingsamples) || + (paddingsamples /= timescale_e6, false) || + paddingsamples > INT32_MAX) { + return; + } + ALOGV("paddingsamples = %" PRId64, paddingsamples); + track->meta.setInt32(kKeyEncoderPadding, paddingsamples); + } + }(); + + if ((flags & kIncludeExtensiveMetaData) + && !track->includes_expensive_metadata) { + track->includes_expensive_metadata = true; + + const char *mime; + CHECK(track->meta.findCString(kKeyMIMEType, &mime)); + if (!strncasecmp("video/", mime, 6)) { + // MPEG2 tracks do not provide CSD, so read the stream header + if (!strcmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG2)) { + off64_t offset; + size_t size; + if (track->sampleTable->getMetaDataForSample( + 0 /* sampleIndex */, &offset, &size, NULL /* sampleTime */) == OK) { + if (size > kMaxTrackHeaderSize) { + size = kMaxTrackHeaderSize; + } + uint8_t header[kMaxTrackHeaderSize]; + if (mDataSource->readAt(offset, &header, size) == (ssize_t)size) { + track->meta.setData(kKeyStreamHeader, 'mdat', header, size); + } + } + } + + if (mMoofOffset > 0) { + int64_t duration; + if (track->meta.findInt64(kKeyDuration, &duration)) { + // nothing fancy, just pick a frame near 1/4th of the duration + track->meta.setInt64( + kKeyThumbnailTime, duration / 4); + } + } else { + uint32_t sampleIndex; + uint32_t sampleTime; + if (track->timescale != 0 && + track->sampleTable->findThumbnailSample(&sampleIndex) == OK + && track->sampleTable->getMetaDataForSample( + sampleIndex, NULL /* offset */, NULL /* size */, + &sampleTime) == OK) { + track->meta.setInt64( + kKeyThumbnailTime, + ((int64_t)sampleTime * 1000000) / track->timescale); + } + } + } + } + + meta = track->meta; + return OK; +} + +status_t MPEG4Extractor::readMetaData() { + if (mInitCheck != NO_INIT) { + return mInitCheck; + } + + off64_t offset = 0; + status_t err; + bool sawMoovOrSidx = false; + + while (!((mHasMoovBox && sawMoovOrSidx && (mMdatFound || mMoofFound)) || + (mIsHeif && (mPreferHeif || !mHasMoovBox) && + (mItemTable != NULL) && mItemTable->isValid()))) { + off64_t orig_offset = offset; + err = parseChunk(&offset, 0); + + if (err != OK && err != UNKNOWN_ERROR) { + break; + } else if (offset <= orig_offset) { + // only continue parsing if the offset was advanced, + // otherwise we might end up in an infinite loop + ALOGE("did not advance: %lld->%lld", (long long)orig_offset, (long long)offset); + err = ERROR_MALFORMED; + break; + } else if (err == UNKNOWN_ERROR) { + sawMoovOrSidx = true; + } + } + + if (mIsHeif && (mItemTable != NULL) && (mItemTable->countImages() > 0)) { + off64_t exifOffset; + size_t exifSize; + if (mItemTable->getExifOffsetAndSize(&exifOffset, &exifSize) == OK) { + mFileMetaData.setInt64(kKeyExifOffset, (int64_t)exifOffset); + mFileMetaData.setInt64(kKeyExifSize, (int64_t)exifSize); + } + for (uint32_t imageIndex = 0; + imageIndex < mItemTable->countImages(); imageIndex++) { + sp<MetaData> meta = mItemTable->getImageMeta(imageIndex); + if (meta == NULL) { + ALOGE("heif image %u has no meta!", imageIndex); + continue; + } + // Some heif files advertise image sequence brands (eg. 'hevc') in + // ftyp box, but don't have any valid tracks in them. Instead of + // reporting the entire file as malformed, we override the error + // to allow still images to be extracted. + if (err != OK) { + ALOGW("Extracting still images only"); + err = OK; + } + mInitCheck = OK; + + ALOGV("adding HEIF image track %u", imageIndex); + Track *track = new Track; + track->next = NULL; + if (mLastTrack != NULL) { + mLastTrack->next = track; + } else { + mFirstTrack = track; + } + mLastTrack = track; + + track->meta = *(meta.get()); + track->meta.setInt32(kKeyTrackID, imageIndex); + track->includes_expensive_metadata = false; + track->skipTrack = false; + track->timescale = 1000000; + } + } + + if (mInitCheck == OK) { + if (findTrackByMimePrefix("video/") != NULL) { + mFileMetaData.setCString( + kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG4); + } else if (findTrackByMimePrefix("audio/") != NULL) { + mFileMetaData.setCString(kKeyMIMEType, "audio/mp4"); + } else if (findTrackByMimePrefix( + MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC) != NULL) { + mFileMetaData.setCString( + kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_HEIF); + } else { + mFileMetaData.setCString(kKeyMIMEType, "application/octet-stream"); + } + } else { + mInitCheck = err; + } + + CHECK_NE(err, (status_t)NO_INIT); + + // copy pssh data into file metadata + uint64_t psshsize = 0; + for (size_t i = 0; i < mPssh.size(); i++) { + psshsize += 20 + mPssh[i].datalen; + } + if (psshsize > 0 && psshsize <= UINT32_MAX) { + char *buf = (char*)malloc(psshsize); + if (!buf) { + ALOGE("b/28471206"); + return NO_MEMORY; + } + char *ptr = buf; + for (size_t i = 0; i < mPssh.size(); i++) { + memcpy(ptr, mPssh[i].uuid, 20); // uuid + length + memcpy(ptr + 20, mPssh[i].data, mPssh[i].datalen); + ptr += (20 + mPssh[i].datalen); + } + mFileMetaData.setData(kKeyPssh, 'pssh', buf, psshsize); + free(buf); + } + + return mInitCheck; +} + +struct PathAdder { + PathAdder(Vector<uint32_t> *path, uint32_t chunkType) + : mPath(path) { + mPath->push(chunkType); + } + + ~PathAdder() { + mPath->pop(); + } + +private: + Vector<uint32_t> *mPath; + + PathAdder(const PathAdder &); + PathAdder &operator=(const PathAdder &); +}; + +static bool underMetaDataPath(const Vector<uint32_t> &path) { + return path.size() >= 5 + && path[0] == FOURCC('m', 'o', 'o', 'v') + && path[1] == FOURCC('u', 'd', 't', 'a') + && path[2] == FOURCC('m', 'e', 't', 'a') + && path[3] == FOURCC('i', 'l', 's', 't'); +} + +static bool underQTMetaPath(const Vector<uint32_t> &path, int32_t depth) { + return path.size() >= 2 + && path[0] == FOURCC('m', 'o', 'o', 'v') + && path[1] == FOURCC('m', 'e', 't', 'a') + && (depth == 2 + || (depth == 3 + && (path[2] == FOURCC('h', 'd', 'l', 'r') + || path[2] == FOURCC('i', 'l', 's', 't') + || path[2] == FOURCC('k', 'e', 'y', 's')))); +} + +// Given a time in seconds since Jan 1 1904, produce a human-readable string. +static bool convertTimeToDate(int64_t time_1904, String8 *s) { + // delta between mpeg4 time and unix epoch time + static const int64_t delta = (((66 * 365 + 17) * 24) * 3600); + if (time_1904 < INT64_MIN + delta) { + return false; + } + time_t time_1970 = time_1904 - delta; + + char tmp[32]; + struct tm* tm = gmtime(&time_1970); + if (tm != NULL && + strftime(tmp, sizeof(tmp), "%Y%m%dT%H%M%S.000Z", tm) > 0) { + s->setTo(tmp); + return true; + } + return false; +} + +status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { + ALOGV("entering parseChunk %lld/%d", (long long)*offset, depth); + + if (*offset < 0) { + ALOGE("b/23540914"); + return ERROR_MALFORMED; + } + if (depth > 100) { + ALOGE("b/27456299"); + return ERROR_MALFORMED; + } + uint32_t hdr[2]; + if (mDataSource->readAt(*offset, hdr, 8) < 8) { + return ERROR_IO; + } + uint64_t chunk_size = ntohl(hdr[0]); + int32_t chunk_type = ntohl(hdr[1]); + off64_t data_offset = *offset + 8; + + if (chunk_size == 1) { + if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) { + return ERROR_IO; + } + chunk_size = ntoh64(chunk_size); + data_offset += 8; + + if (chunk_size < 16) { + // The smallest valid chunk is 16 bytes long in this case. + return ERROR_MALFORMED; + } + } else if (chunk_size == 0) { + if (depth == 0) { + // atom extends to end of file + off64_t sourceSize; + if (mDataSource->getSize(&sourceSize) == OK) { + chunk_size = (sourceSize - *offset); + } else { + // XXX could we just pick a "sufficiently large" value here? + ALOGE("atom size is 0, and data source has no size"); + return ERROR_MALFORMED; + } + } else { + // not allowed for non-toplevel atoms, skip it + *offset += 4; + return OK; + } + } else if (chunk_size < 8) { + // The smallest valid chunk is 8 bytes long. + ALOGE("invalid chunk size: %" PRIu64, chunk_size); + return ERROR_MALFORMED; + } + + char chunk[5]; + MakeFourCCString(chunk_type, chunk); + ALOGV("chunk: %s @ %lld, %d", chunk, (long long)*offset, depth); + + if (kUseHexDump) { + static const char kWhitespace[] = " "; + const char *indent = &kWhitespace[sizeof(kWhitespace) - 1 - 2 * depth]; + printf("%sfound chunk '%s' of size %" PRIu64 "\n", indent, chunk, chunk_size); + + char buffer[256]; + size_t n = chunk_size; + if (n > sizeof(buffer)) { + n = sizeof(buffer); + } + if (mDataSource->readAt(*offset, buffer, n) + < (ssize_t)n) { + return ERROR_IO; + } + + hexdump(buffer, n); + } + + PathAdder autoAdder(&mPath, chunk_type); + + // (data_offset - *offset) is either 8 or 16 + off64_t chunk_data_size = chunk_size - (data_offset - *offset); + if (chunk_data_size < 0) { + ALOGE("b/23540914"); + return ERROR_MALFORMED; + } + if (chunk_type != FOURCC('m', 'd', 'a', 't') && chunk_data_size > kMaxAtomSize) { + char errMsg[100]; + sprintf(errMsg, "%s atom has size %" PRId64, chunk, chunk_data_size); + ALOGE("%s (b/28615448)", errMsg); + android_errorWriteWithInfoLog(0x534e4554, "28615448", -1, errMsg, strlen(errMsg)); + return ERROR_MALFORMED; + } + + if (chunk_type != FOURCC('c', 'p', 'r', 't') + && chunk_type != FOURCC('c', 'o', 'v', 'r') + && mPath.size() == 5 && underMetaDataPath(mPath)) { + off64_t stop_offset = *offset + chunk_size; + *offset = data_offset; + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + + if (*offset != stop_offset) { + return ERROR_MALFORMED; + } + + return OK; + } + + switch(chunk_type) { + case FOURCC('m', 'o', 'o', 'v'): + case FOURCC('t', 'r', 'a', 'k'): + case FOURCC('m', 'd', 'i', 'a'): + case FOURCC('m', 'i', 'n', 'f'): + case FOURCC('d', 'i', 'n', 'f'): + case FOURCC('s', 't', 'b', 'l'): + case FOURCC('m', 'v', 'e', 'x'): + case FOURCC('m', 'o', 'o', 'f'): + case FOURCC('t', 'r', 'a', 'f'): + case FOURCC('m', 'f', 'r', 'a'): + case FOURCC('u', 'd', 't', 'a'): + case FOURCC('i', 'l', 's', 't'): + case FOURCC('s', 'i', 'n', 'f'): + case FOURCC('s', 'c', 'h', 'i'): + case FOURCC('e', 'd', 't', 's'): + case FOURCC('w', 'a', 'v', 'e'): + { + if (chunk_type == FOURCC('m', 'o', 'o', 'v') && depth != 0) { + ALOGE("moov: depth %d", depth); + return ERROR_MALFORMED; + } + + if (chunk_type == FOURCC('m', 'o', 'o', 'v') && mInitCheck == OK) { + ALOGE("duplicate moov"); + return ERROR_MALFORMED; + } + + if (chunk_type == FOURCC('m', 'o', 'o', 'f') && !mMoofFound) { + // store the offset of the first segment + mMoofFound = true; + mMoofOffset = *offset; + } + + if (chunk_type == FOURCC('s', 't', 'b', 'l')) { + ALOGV("sampleTable chunk is %" PRIu64 " bytes long.", chunk_size); + + if (mDataSource->flags() + & (DataSourceBase::kWantsPrefetching + | DataSourceBase::kIsCachingDataSource)) { + CachedRangedDataSource *cachedSource = + new CachedRangedDataSource(mDataSource); + + if (cachedSource->setCachedRange( + *offset, chunk_size, + mCachedSource != NULL /* assume ownership on success */) == OK) { + mDataSource = mCachedSource = cachedSource; + } else { + delete cachedSource; + } + } + + if (mLastTrack == NULL) { + return ERROR_MALFORMED; + } + + mLastTrack->sampleTable = new SampleTable(mDataSource); + } + + bool isTrack = false; + if (chunk_type == FOURCC('t', 'r', 'a', 'k')) { + if (depth != 1) { + ALOGE("trak: depth %d", depth); + return ERROR_MALFORMED; + } + isTrack = true; + + ALOGV("adding new track"); + Track *track = new Track; + track->next = NULL; + if (mLastTrack) { + mLastTrack->next = track; + } else { + mFirstTrack = track; + } + mLastTrack = track; + + track->includes_expensive_metadata = false; + track->skipTrack = false; + track->timescale = 0; + track->meta.setCString(kKeyMIMEType, "application/octet-stream"); + track->has_elst = false; + track->subsample_encryption = false; + } + + off64_t stop_offset = *offset + chunk_size; + *offset = data_offset; + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + if (isTrack) { + mLastTrack->skipTrack = true; + break; + } + return err; + } + } + + if (*offset != stop_offset) { + return ERROR_MALFORMED; + } + + if (isTrack) { + int32_t trackId; + // There must be exact one track header per track. + if (!mLastTrack->meta.findInt32(kKeyTrackID, &trackId)) { + mLastTrack->skipTrack = true; + } + + status_t err = verifyTrack(mLastTrack); + if (err != OK) { + mLastTrack->skipTrack = true; + } + + if (mLastTrack->skipTrack) { + ALOGV("skipping this track..."); + Track *cur = mFirstTrack; + + if (cur == mLastTrack) { + delete cur; + mFirstTrack = mLastTrack = NULL; + } else { + while (cur && cur->next != mLastTrack) { + cur = cur->next; + } + if (cur) { + cur->next = NULL; + } + delete mLastTrack; + mLastTrack = cur; + } + + return OK; + } + } else if (chunk_type == FOURCC('m', 'o', 'o', 'v')) { + mInitCheck = OK; + + return UNKNOWN_ERROR; // Return a dummy error. + } + break; + } + + case FOURCC('s', 'c', 'h', 'm'): + { + + *offset += chunk_size; + if (!mLastTrack) { + return ERROR_MALFORMED; + } + + uint32_t scheme_type; + if (mDataSource->readAt(data_offset + 4, &scheme_type, 4) < 4) { + return ERROR_IO; + } + scheme_type = ntohl(scheme_type); + int32_t mode = kCryptoModeUnencrypted; + switch(scheme_type) { + case FOURCC('c', 'b', 'c', '1'): + { + mode = kCryptoModeAesCbc; + break; + } + case FOURCC('c', 'b', 'c', 's'): + { + mode = kCryptoModeAesCbc; + mLastTrack->subsample_encryption = true; + break; + } + case FOURCC('c', 'e', 'n', 'c'): + { + mode = kCryptoModeAesCtr; + break; + } + case FOURCC('c', 'e', 'n', 's'): + { + mode = kCryptoModeAesCtr; + mLastTrack->subsample_encryption = true; + break; + } + } + if (mode != kCryptoModeUnencrypted) { + mLastTrack->meta.setInt32(kKeyCryptoMode, mode); + } + break; + } + + + case FOURCC('e', 'l', 's', 't'): + { + *offset += chunk_size; + + if (!mLastTrack) { + return ERROR_MALFORMED; + } + + // See 14496-12 8.6.6 + uint8_t version; + if (mDataSource->readAt(data_offset, &version, 1) < 1) { + return ERROR_IO; + } + + uint32_t entry_count; + if (!mDataSource->getUInt32(data_offset + 4, &entry_count)) { + return ERROR_IO; + } + + if (entry_count != 1) { + // we only support a single entry at the moment, for gapless playback + ALOGW("ignoring edit list with %d entries", entry_count); + } else { + off64_t entriesoffset = data_offset + 8; + uint64_t segment_duration; + int64_t media_time; + + if (version == 1) { + if (!mDataSource->getUInt64(entriesoffset, &segment_duration) || + !mDataSource->getUInt64(entriesoffset + 8, (uint64_t*)&media_time)) { + return ERROR_IO; + } + } else if (version == 0) { + uint32_t sd; + int32_t mt; + if (!mDataSource->getUInt32(entriesoffset, &sd) || + !mDataSource->getUInt32(entriesoffset + 4, (uint32_t*)&mt)) { + return ERROR_IO; + } + segment_duration = sd; + media_time = mt; + } else { + return ERROR_IO; + } + + // save these for later, because the elst atom might precede + // the atoms that actually gives us the duration and sample rate + // needed to calculate the padding and delay values + mLastTrack->has_elst = true; + mLastTrack->elst_media_time = media_time; + mLastTrack->elst_segment_duration = segment_duration; + } + break; + } + + case FOURCC('f', 'r', 'm', 'a'): + { + *offset += chunk_size; + + uint32_t original_fourcc; + if (mDataSource->readAt(data_offset, &original_fourcc, 4) < 4) { + return ERROR_IO; + } + original_fourcc = ntohl(original_fourcc); + ALOGV("read original format: %d", original_fourcc); + + if (mLastTrack == NULL) { + return ERROR_MALFORMED; + } + + mLastTrack->meta.setCString(kKeyMIMEType, FourCC2MIME(original_fourcc)); + uint32_t num_channels = 0; + uint32_t sample_rate = 0; + if (AdjustChannelsAndRate(original_fourcc, &num_channels, &sample_rate)) { + mLastTrack->meta.setInt32(kKeyChannelCount, num_channels); + mLastTrack->meta.setInt32(kKeySampleRate, sample_rate); + } + break; + } + + case FOURCC('t', 'e', 'n', 'c'): + { + *offset += chunk_size; + + if (chunk_size < 32) { + return ERROR_MALFORMED; + } + + // tenc box contains 1 byte version, 3 byte flags, 3 byte default algorithm id, one byte + // default IV size, 16 bytes default KeyID + // (ISO 23001-7) + + uint8_t version; + if (mDataSource->readAt(data_offset, &version, sizeof(version)) + < (ssize_t)sizeof(version)) { + return ERROR_IO; + } + + uint8_t buf[4]; + memset(buf, 0, 4); + if (mDataSource->readAt(data_offset + 4, buf + 1, 3) < 3) { + return ERROR_IO; + } + + if (mLastTrack == NULL) { + return ERROR_MALFORMED; + } + + uint8_t defaultEncryptedByteBlock = 0; + uint8_t defaultSkipByteBlock = 0; + uint32_t defaultAlgorithmId = ntohl(*((int32_t*)buf)); + if (version == 1) { + uint32_t pattern = buf[2]; + defaultEncryptedByteBlock = pattern >> 4; + defaultSkipByteBlock = pattern & 0xf; + if (defaultEncryptedByteBlock == 0 && defaultSkipByteBlock == 0) { + // use (1,0) to mean "encrypt everything" + defaultEncryptedByteBlock = 1; + } + } else if (mLastTrack->subsample_encryption) { + ALOGW("subsample_encryption should be version 1"); + } else if (defaultAlgorithmId > 1) { + // only 0 (clear) and 1 (AES-128) are valid + ALOGW("defaultAlgorithmId: %u is a reserved value", defaultAlgorithmId); + defaultAlgorithmId = 1; + } + + memset(buf, 0, 4); + if (mDataSource->readAt(data_offset + 7, buf + 3, 1) < 1) { + return ERROR_IO; + } + uint32_t defaultIVSize = ntohl(*((int32_t*)buf)); + + if (defaultAlgorithmId == 0 && defaultIVSize != 0) { + // only unencrypted data must have 0 IV size + return ERROR_MALFORMED; + } else if (defaultIVSize != 0 && + defaultIVSize != 8 && + defaultIVSize != 16) { + return ERROR_MALFORMED; + } + + uint8_t defaultKeyId[16]; + + if (mDataSource->readAt(data_offset + 8, &defaultKeyId, 16) < 16) { + return ERROR_IO; + } + + sp<ABuffer> defaultConstantIv; + if (defaultAlgorithmId != 0 && defaultIVSize == 0) { + + uint8_t ivlength; + if (mDataSource->readAt(data_offset + 24, &ivlength, sizeof(ivlength)) + < (ssize_t)sizeof(ivlength)) { + return ERROR_IO; + } + + if (ivlength != 8 && ivlength != 16) { + ALOGW("unsupported IV length: %u", ivlength); + return ERROR_MALFORMED; + } + + defaultConstantIv = new ABuffer(ivlength); + if (mDataSource->readAt(data_offset + 25, defaultConstantIv->data(), ivlength) + < (ssize_t)ivlength) { + return ERROR_IO; + } + + defaultConstantIv->setRange(0, ivlength); + } + + int32_t tmpAlgorithmId; + if (!mLastTrack->meta.findInt32(kKeyCryptoMode, &tmpAlgorithmId)) { + mLastTrack->meta.setInt32(kKeyCryptoMode, defaultAlgorithmId); + } + + mLastTrack->meta.setInt32(kKeyCryptoDefaultIVSize, defaultIVSize); + mLastTrack->meta.setData(kKeyCryptoKey, 'tenc', defaultKeyId, 16); + mLastTrack->meta.setInt32(kKeyEncryptedByteBlock, defaultEncryptedByteBlock); + mLastTrack->meta.setInt32(kKeySkipByteBlock, defaultSkipByteBlock); + if (defaultConstantIv != NULL) { + mLastTrack->meta.setData(kKeyCryptoIV, 'dciv', defaultConstantIv->data(), defaultConstantIv->size()); + } + break; + } + + case FOURCC('t', 'k', 'h', 'd'): + { + *offset += chunk_size; + + status_t err; + if ((err = parseTrackHeader(data_offset, chunk_data_size)) != OK) { + return err; + } + + break; + } + + case FOURCC('t', 'r', 'e', 'f'): + { + off64_t stop_offset = *offset + chunk_size; + *offset = data_offset; + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + if (*offset != stop_offset) { + return ERROR_MALFORMED; + } + break; + } + + case FOURCC('t', 'h', 'm', 'b'): + { + *offset += chunk_size; + + if (mLastTrack != NULL) { + // Skip thumbnail track for now since we don't have an + // API to retrieve it yet. + // The thumbnail track can't be accessed by negative index or time, + // because each timed sample has its own corresponding thumbnail + // in the thumbnail track. We'll need a dedicated API to retrieve + // thumbnail at time instead. + mLastTrack->skipTrack = true; + } + + break; + } + + case FOURCC('p', 's', 's', 'h'): + { + *offset += chunk_size; + + PsshInfo pssh; + + if (mDataSource->readAt(data_offset + 4, &pssh.uuid, 16) < 16) { + return ERROR_IO; + } + + uint32_t psshdatalen = 0; + if (mDataSource->readAt(data_offset + 20, &psshdatalen, 4) < 4) { + return ERROR_IO; + } + pssh.datalen = ntohl(psshdatalen); + ALOGV("pssh data size: %d", pssh.datalen); + if (chunk_size < 20 || pssh.datalen > chunk_size - 20) { + // pssh data length exceeds size of containing box + return ERROR_MALFORMED; + } + + pssh.data = new (std::nothrow) uint8_t[pssh.datalen]; + if (pssh.data == NULL) { + return ERROR_MALFORMED; + } + ALOGV("allocated pssh @ %p", pssh.data); + ssize_t requested = (ssize_t) pssh.datalen; + if (mDataSource->readAt(data_offset + 24, pssh.data, requested) < requested) { + delete[] pssh.data; + return ERROR_IO; + } + mPssh.push_back(pssh); + + break; + } + + case FOURCC('m', 'd', 'h', 'd'): + { + *offset += chunk_size; + + if (chunk_data_size < 4 || mLastTrack == NULL) { + return ERROR_MALFORMED; + } + + uint8_t version; + if (mDataSource->readAt( + data_offset, &version, sizeof(version)) + < (ssize_t)sizeof(version)) { + return ERROR_IO; + } + + off64_t timescale_offset; + + if (version == 1) { + timescale_offset = data_offset + 4 + 16; + } else if (version == 0) { + timescale_offset = data_offset + 4 + 8; + } else { + return ERROR_IO; + } + + uint32_t timescale; + if (mDataSource->readAt( + timescale_offset, ×cale, sizeof(timescale)) + < (ssize_t)sizeof(timescale)) { + return ERROR_IO; + } + + if (!timescale) { + ALOGE("timescale should not be ZERO."); + return ERROR_MALFORMED; + } + + mLastTrack->timescale = ntohl(timescale); + + // 14496-12 says all ones means indeterminate, but some files seem to use + // 0 instead. We treat both the same. + int64_t duration = 0; + if (version == 1) { + if (mDataSource->readAt( + timescale_offset + 4, &duration, sizeof(duration)) + < (ssize_t)sizeof(duration)) { + return ERROR_IO; + } + if (duration != -1) { + duration = ntoh64(duration); + } + } else { + uint32_t duration32; + if (mDataSource->readAt( + timescale_offset + 4, &duration32, sizeof(duration32)) + < (ssize_t)sizeof(duration32)) { + return ERROR_IO; + } + if (duration32 != 0xffffffff) { + duration = ntohl(duration32); + } + } + if (duration != 0 && mLastTrack->timescale != 0) { + mLastTrack->meta.setInt64( + kKeyDuration, (duration * 1000000) / mLastTrack->timescale); + } + + uint8_t lang[2]; + off64_t lang_offset; + if (version == 1) { + lang_offset = timescale_offset + 4 + 8; + } else if (version == 0) { + lang_offset = timescale_offset + 4 + 4; + } else { + return ERROR_IO; + } + + if (mDataSource->readAt(lang_offset, &lang, sizeof(lang)) + < (ssize_t)sizeof(lang)) { + return ERROR_IO; + } + + // To get the ISO-639-2/T three character language code + // 1 bit pad followed by 3 5-bits characters. Each character + // is packed as the difference between its ASCII value and 0x60. + char lang_code[4]; + lang_code[0] = ((lang[0] >> 2) & 0x1f) + 0x60; + lang_code[1] = ((lang[0] & 0x3) << 3 | (lang[1] >> 5)) + 0x60; + lang_code[2] = (lang[1] & 0x1f) + 0x60; + lang_code[3] = '\0'; + + mLastTrack->meta.setCString( + kKeyMediaLanguage, lang_code); + + break; + } + + case FOURCC('s', 't', 's', 'd'): + { + uint8_t buffer[8]; + if (chunk_data_size < (off64_t)sizeof(buffer)) { + return ERROR_MALFORMED; + } + + if (mDataSource->readAt( + data_offset, buffer, 8) < 8) { + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + return ERROR_MALFORMED; + } + + uint32_t entry_count = U32_AT(&buffer[4]); + + if (entry_count > 1) { + // For 3GPP timed text, there could be multiple tx3g boxes contain + // multiple text display formats. These formats will be used to + // display the timed text. + // For encrypted files, there may also be more than one entry. + const char *mime; + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + CHECK(mLastTrack->meta.findCString(kKeyMIMEType, &mime)); + if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) && + strcasecmp(mime, "application/octet-stream")) { + // For now we only support a single type of media per track. + mLastTrack->skipTrack = true; + *offset += chunk_size; + break; + } + } + off64_t stop_offset = *offset + chunk_size; + *offset = data_offset + 8; + for (uint32_t i = 0; i < entry_count; ++i) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + + if (*offset != stop_offset) { + return ERROR_MALFORMED; + } + break; + } + case FOURCC('m', 'e', 't', 't'): + { + *offset += chunk_size; + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + auto buffer = heapbuffer<uint8_t>(chunk_data_size); + if (buffer.get() == NULL) { + return NO_MEMORY; + } + + if (mDataSource->readAt( + data_offset, buffer.get(), chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + String8 mimeFormat((const char *)(buffer.get()), chunk_data_size); + mLastTrack->meta.setCString(kKeyMIMEType, mimeFormat.string()); + + break; + } + + case FOURCC('m', 'p', '4', 'a'): + case FOURCC('e', 'n', 'c', 'a'): + case FOURCC('s', 'a', 'm', 'r'): + case FOURCC('s', 'a', 'w', 'b'): + { + if (mIsQT && chunk_type == FOURCC('m', 'p', '4', 'a') + && depth >= 1 && mPath[depth - 1] == FOURCC('w', 'a', 'v', 'e')) { + // Ignore mp4a embedded in QT wave atom + *offset += chunk_size; + break; + } + + uint8_t buffer[8 + 20]; + if (chunk_data_size < (ssize_t)sizeof(buffer)) { + // Basic AudioSampleEntry size. + return ERROR_MALFORMED; + } + + if (mDataSource->readAt( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + uint16_t data_ref_index __unused = U16_AT(&buffer[6]); + uint16_t version = U16_AT(&buffer[8]); + uint32_t num_channels = U16_AT(&buffer[16]); + + uint16_t sample_size = U16_AT(&buffer[18]); + uint32_t sample_rate = U32_AT(&buffer[24]) >> 16; + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + off64_t stop_offset = *offset + chunk_size; + *offset = data_offset + sizeof(buffer); + + if (mIsQT && chunk_type == FOURCC('m', 'p', '4', 'a')) { + if (version == 1) { + if (mDataSource->readAt(*offset, buffer, 16) < 16) { + return ERROR_IO; + } + +#if 0 + U32_AT(buffer); // samples per packet + U32_AT(&buffer[4]); // bytes per packet + U32_AT(&buffer[8]); // bytes per frame + U32_AT(&buffer[12]); // bytes per sample +#endif + *offset += 16; + } else if (version == 2) { + uint8_t v2buffer[36]; + if (mDataSource->readAt(*offset, v2buffer, 36) < 36) { + return ERROR_IO; + } + +#if 0 + U32_AT(v2buffer); // size of struct only + sample_rate = (uint32_t)U64_AT(&v2buffer[4]); // audio sample rate + num_channels = U32_AT(&v2buffer[12]); // num audio channels + U32_AT(&v2buffer[16]); // always 0x7f000000 + sample_size = (uint16_t)U32_AT(&v2buffer[20]); // const bits per channel + U32_AT(&v2buffer[24]); // format specifc flags + U32_AT(&v2buffer[28]); // const bytes per audio packet + U32_AT(&v2buffer[32]); // const LPCM frames per audio packet +#endif + *offset += 36; + } + } + + if (chunk_type != FOURCC('e', 'n', 'c', 'a')) { + // if the chunk type is enca, we'll get the type from the frma box later + mLastTrack->meta.setCString(kKeyMIMEType, FourCC2MIME(chunk_type)); + AdjustChannelsAndRate(chunk_type, &num_channels, &sample_rate); + } + ALOGV("*** coding='%s' %d channels, size %d, rate %d\n", + chunk, num_channels, sample_size, sample_rate); + mLastTrack->meta.setInt32(kKeyChannelCount, num_channels); + mLastTrack->meta.setInt32(kKeySampleRate, sample_rate); + + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + + if (*offset != stop_offset) { + return ERROR_MALFORMED; + } + break; + } + + case FOURCC('m', 'p', '4', 'v'): + case FOURCC('e', 'n', 'c', 'v'): + case FOURCC('s', '2', '6', '3'): + case FOURCC('H', '2', '6', '3'): + case FOURCC('h', '2', '6', '3'): + case FOURCC('a', 'v', 'c', '1'): + case FOURCC('h', 'v', 'c', '1'): + case FOURCC('h', 'e', 'v', '1'): + { + uint8_t buffer[78]; + if (chunk_data_size < (ssize_t)sizeof(buffer)) { + // Basic VideoSampleEntry size. + return ERROR_MALFORMED; + } + + if (mDataSource->readAt( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + uint16_t data_ref_index __unused = U16_AT(&buffer[6]); + uint16_t width = U16_AT(&buffer[6 + 18]); + uint16_t height = U16_AT(&buffer[6 + 20]); + + // The video sample is not standard-compliant if it has invalid dimension. + // Use some default width and height value, and + // let the decoder figure out the actual width and height (and thus + // be prepared for INFO_FOMRAT_CHANGED event). + if (width == 0) width = 352; + if (height == 0) height = 288; + + // printf("*** coding='%s' width=%d height=%d\n", + // chunk, width, height); + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + if (chunk_type != FOURCC('e', 'n', 'c', 'v')) { + // if the chunk type is encv, we'll get the type from the frma box later + mLastTrack->meta.setCString(kKeyMIMEType, FourCC2MIME(chunk_type)); + } + mLastTrack->meta.setInt32(kKeyWidth, width); + mLastTrack->meta.setInt32(kKeyHeight, height); + + off64_t stop_offset = *offset + chunk_size; + *offset = data_offset + sizeof(buffer); + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + + if (*offset != stop_offset) { + return ERROR_MALFORMED; + } + break; + } + + case FOURCC('s', 't', 'c', 'o'): + case FOURCC('c', 'o', '6', '4'): + { + if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL)) { + return ERROR_MALFORMED; + } + + status_t err = + mLastTrack->sampleTable->setChunkOffsetParams( + chunk_type, data_offset, chunk_data_size); + + *offset += chunk_size; + + if (err != OK) { + return err; + } + + break; + } + + case FOURCC('s', 't', 's', 'c'): + { + if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL)) + return ERROR_MALFORMED; + + status_t err = + mLastTrack->sampleTable->setSampleToChunkParams( + data_offset, chunk_data_size); + + *offset += chunk_size; + + if (err != OK) { + return err; + } + + break; + } + + case FOURCC('s', 't', 's', 'z'): + case FOURCC('s', 't', 'z', '2'): + { + if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL)) { + return ERROR_MALFORMED; + } + + status_t err = + mLastTrack->sampleTable->setSampleSizeParams( + chunk_type, data_offset, chunk_data_size); + + *offset += chunk_size; + + if (err != OK) { + return err; + } + + size_t max_size; + err = mLastTrack->sampleTable->getMaxSampleSize(&max_size); + + if (err != OK) { + return err; + } + + if (max_size != 0) { + // Assume that a given buffer only contains at most 10 chunks, + // each chunk originally prefixed with a 2 byte length will + // have a 4 byte header (0x00 0x00 0x00 0x01) after conversion, + // and thus will grow by 2 bytes per chunk. + if (max_size > SIZE_MAX - 10 * 2) { + ALOGE("max sample size too big: %zu", max_size); + return ERROR_MALFORMED; + } + mLastTrack->meta.setInt32(kKeyMaxInputSize, max_size + 10 * 2); + } else { + // No size was specified. Pick a conservatively large size. + uint32_t width, height; + if (!mLastTrack->meta.findInt32(kKeyWidth, (int32_t*)&width) || + !mLastTrack->meta.findInt32(kKeyHeight,(int32_t*) &height)) { + ALOGE("No width or height, assuming worst case 1080p"); + width = 1920; + height = 1080; + } else { + // A resolution was specified, check that it's not too big. The values below + // were chosen so that the calculations below don't cause overflows, they're + // not indicating that resolutions up to 32kx32k are actually supported. + if (width > 32768 || height > 32768) { + ALOGE("can't support %u x %u video", width, height); + return ERROR_MALFORMED; + } + } + + const char *mime; + CHECK(mLastTrack->meta.findCString(kKeyMIMEType, &mime)); + if (!strncmp(mime, "audio/", 6)) { + // for audio, use 128KB + max_size = 1024 * 128; + } else if (!strcmp(mime, MEDIA_MIMETYPE_VIDEO_AVC) + || !strcmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) { + // AVC & HEVC requires compression ratio of at least 2, and uses + // macroblocks + max_size = ((width + 15) / 16) * ((height + 15) / 16) * 192; + } else { + // For all other formats there is no minimum compression + // ratio. Use compression ratio of 1. + max_size = width * height * 3 / 2; + } + // HACK: allow 10% overhead + // TODO: read sample size from traf atom for fragmented MPEG4. + max_size += max_size / 10; + mLastTrack->meta.setInt32(kKeyMaxInputSize, max_size); + } + + // NOTE: setting another piece of metadata invalidates any pointers (such as the + // mimetype) previously obtained, so don't cache them. + const char *mime; + CHECK(mLastTrack->meta.findCString(kKeyMIMEType, &mime)); + // Calculate average frame rate. + if (!strncasecmp("video/", mime, 6)) { + size_t nSamples = mLastTrack->sampleTable->countSamples(); + if (nSamples == 0) { + int32_t trackId; + if (mLastTrack->meta.findInt32(kKeyTrackID, &trackId)) { + for (size_t i = 0; i < mTrex.size(); i++) { + Trex *t = &mTrex.editItemAt(i); + if (t->track_ID == (uint32_t) trackId) { + if (t->default_sample_duration > 0) { + int32_t frameRate = + mLastTrack->timescale / t->default_sample_duration; + mLastTrack->meta.setInt32(kKeyFrameRate, frameRate); + } + break; + } + } + } + } else { + int64_t durationUs; + if (mLastTrack->meta.findInt64(kKeyDuration, &durationUs)) { + if (durationUs > 0) { + int32_t frameRate = (nSamples * 1000000LL + + (durationUs >> 1)) / durationUs; + mLastTrack->meta.setInt32(kKeyFrameRate, frameRate); + } + } + ALOGV("setting frame count %zu", nSamples); + mLastTrack->meta.setInt32(kKeyFrameCount, nSamples); + } + } + + break; + } + + case FOURCC('s', 't', 't', 's'): + { + if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL)) + return ERROR_MALFORMED; + + *offset += chunk_size; + + status_t err = + mLastTrack->sampleTable->setTimeToSampleParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + break; + } + + case FOURCC('c', 't', 't', 's'): + { + if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL)) + return ERROR_MALFORMED; + + *offset += chunk_size; + + status_t err = + mLastTrack->sampleTable->setCompositionTimeToSampleParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + break; + } + + case FOURCC('s', 't', 's', 's'): + { + if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL)) + return ERROR_MALFORMED; + + *offset += chunk_size; + + status_t err = + mLastTrack->sampleTable->setSyncSampleParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + break; + } + + // \xA9xyz + case FOURCC(0xA9, 'x', 'y', 'z'): + { + *offset += chunk_size; + + // Best case the total data length inside "\xA9xyz" box would + // be 9, for instance "\xA9xyz" + "\x00\x05\x15\xc7" + "+0+0/", + // where "\x00\x05" is the text string length with value = 5, + // "\0x15\xc7" is the language code = en, and "+0+0/" is a + // location (string) value with longitude = 0 and latitude = 0. + // Since some devices encountered in the wild omit the trailing + // slash, we'll allow that. + if (chunk_data_size < 8) { // 8 instead of 9 to allow for missing / + return ERROR_MALFORMED; + } + + uint16_t len; + if (!mDataSource->getUInt16(data_offset, &len)) { + return ERROR_IO; + } + + // allow "+0+0" without trailing slash + if (len < 4 || len > chunk_data_size - 4) { + return ERROR_MALFORMED; + } + // The location string following the language code is formatted + // according to ISO 6709:2008 (https://en.wikipedia.org/wiki/ISO_6709). + // Allocate 2 extra bytes, in case we need to add a trailing slash, + // and to add a terminating 0. + std::unique_ptr<char[]> buffer(new (std::nothrow) char[len+2]()); + if (!buffer) { + return NO_MEMORY; + } + + if (mDataSource->readAt( + data_offset + 4, &buffer[0], len) < len) { + return ERROR_IO; + } + + len = strlen(&buffer[0]); + if (len < 4) { + return ERROR_MALFORMED; + } + // Add a trailing slash if there wasn't one. + if (buffer[len - 1] != '/') { + buffer[len] = '/'; + } + mFileMetaData.setCString(kKeyLocation, &buffer[0]); + break; + } + + case FOURCC('e', 's', 'd', 's'): + { + *offset += chunk_size; + + if (chunk_data_size < 4) { + return ERROR_MALFORMED; + } + + uint8_t buffer[256]; + if (chunk_data_size > (off64_t)sizeof(buffer)) { + return ERROR_BUFFER_TOO_SMALL; + } + + if (mDataSource->readAt( + data_offset, buffer, chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + return ERROR_MALFORMED; + } + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + mLastTrack->meta.setData( + kKeyESDS, kTypeESDS, &buffer[4], chunk_data_size - 4); + + if (mPath.size() >= 2 + && mPath[mPath.size() - 2] == FOURCC('m', 'p', '4', 'a')) { + // Information from the ESDS must be relied on for proper + // setup of sample rate and channel count for MPEG4 Audio. + // The generic header appears to only contain generic + // information... + + status_t err = updateAudioTrackInfoFromESDS_MPEG4Audio( + &buffer[4], chunk_data_size - 4); + + if (err != OK) { + return err; + } + } + if (mPath.size() >= 2 + && mPath[mPath.size() - 2] == FOURCC('m', 'p', '4', 'v')) { + // Check if the video is MPEG2 + ESDS esds(&buffer[4], chunk_data_size - 4); + + uint8_t objectTypeIndication; + if (esds.getObjectTypeIndication(&objectTypeIndication) == OK) { + if (objectTypeIndication >= 0x60 && objectTypeIndication <= 0x65) { + mLastTrack->meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG2); + } + } + } + break; + } + + case FOURCC('b', 't', 'r', 't'): + { + *offset += chunk_size; + if (mLastTrack == NULL) { + return ERROR_MALFORMED; + } + + uint8_t buffer[12]; + if (chunk_data_size != sizeof(buffer)) { + return ERROR_MALFORMED; + } + + if (mDataSource->readAt( + data_offset, buffer, chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + uint32_t maxBitrate = U32_AT(&buffer[4]); + uint32_t avgBitrate = U32_AT(&buffer[8]); + if (maxBitrate > 0 && maxBitrate < INT32_MAX) { + mLastTrack->meta.setInt32(kKeyMaxBitRate, (int32_t)maxBitrate); + } + if (avgBitrate > 0 && avgBitrate < INT32_MAX) { + mLastTrack->meta.setInt32(kKeyBitRate, (int32_t)avgBitrate); + } + break; + } + + case FOURCC('a', 'v', 'c', 'C'): + { + *offset += chunk_size; + + auto buffer = heapbuffer<uint8_t>(chunk_data_size); + + if (buffer.get() == NULL) { + ALOGE("b/28471206"); + return NO_MEMORY; + } + + if (mDataSource->readAt( + data_offset, buffer.get(), chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + mLastTrack->meta.setData( + kKeyAVCC, kTypeAVCC, buffer.get(), chunk_data_size); + + break; + } + case FOURCC('h', 'v', 'c', 'C'): + { + auto buffer = heapbuffer<uint8_t>(chunk_data_size); + + if (buffer.get() == NULL) { + ALOGE("b/28471206"); + return NO_MEMORY; + } + + if (mDataSource->readAt( + data_offset, buffer.get(), chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + mLastTrack->meta.setData( + kKeyHVCC, kTypeHVCC, buffer.get(), chunk_data_size); + + *offset += chunk_size; + break; + } + + case FOURCC('d', '2', '6', '3'): + { + *offset += chunk_size; + /* + * d263 contains a fixed 7 bytes part: + * vendor - 4 bytes + * version - 1 byte + * level - 1 byte + * profile - 1 byte + * optionally, "d263" box itself may contain a 16-byte + * bit rate box (bitr) + * average bit rate - 4 bytes + * max bit rate - 4 bytes + */ + char buffer[23]; + if (chunk_data_size != 7 && + chunk_data_size != 23) { + ALOGE("Incorrect D263 box size %lld", (long long)chunk_data_size); + return ERROR_MALFORMED; + } + + if (mDataSource->readAt( + data_offset, buffer, chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + mLastTrack->meta.setData(kKeyD263, kTypeD263, buffer, chunk_data_size); + + break; + } + + case FOURCC('m', 'e', 't', 'a'): + { + off64_t stop_offset = *offset + chunk_size; + *offset = data_offset; + bool isParsingMetaKeys = underQTMetaPath(mPath, 2); + if (!isParsingMetaKeys) { + uint8_t buffer[4]; + if (chunk_data_size < (off64_t)sizeof(buffer)) { + *offset = stop_offset; + return ERROR_MALFORMED; + } + + if (mDataSource->readAt( + data_offset, buffer, 4) < 4) { + *offset = stop_offset; + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + + // If it's not, let's assume this is one of those + // apparently malformed chunks that don't have flags + // and completely different semantics than what's + // in the MPEG4 specs and skip it. + *offset = stop_offset; + return OK; + } + *offset += sizeof(buffer); + } + + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + + if (*offset != stop_offset) { + return ERROR_MALFORMED; + } + break; + } + + case FOURCC('i', 'l', 'o', 'c'): + case FOURCC('i', 'i', 'n', 'f'): + case FOURCC('i', 'p', 'r', 'p'): + case FOURCC('p', 'i', 't', 'm'): + case FOURCC('i', 'd', 'a', 't'): + case FOURCC('i', 'r', 'e', 'f'): + case FOURCC('i', 'p', 'r', 'o'): + { + if (mIsHeif) { + if (mItemTable == NULL) { + mItemTable = new ItemTable(mDataSource); + } + status_t err = mItemTable->parse( + chunk_type, data_offset, chunk_data_size); + if (err != OK) { + return err; + } + } + *offset += chunk_size; + break; + } + + case FOURCC('m', 'e', 'a', 'n'): + case FOURCC('n', 'a', 'm', 'e'): + case FOURCC('d', 'a', 't', 'a'): + { + *offset += chunk_size; + + if (mPath.size() == 6 && underMetaDataPath(mPath)) { + status_t err = parseITunesMetaData(data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + } + + break; + } + + case FOURCC('m', 'v', 'h', 'd'): + { + *offset += chunk_size; + + if (depth != 1) { + ALOGE("mvhd: depth %d", depth); + return ERROR_MALFORMED; + } + if (chunk_data_size < 32) { + return ERROR_MALFORMED; + } + + uint8_t header[32]; + if (mDataSource->readAt( + data_offset, header, sizeof(header)) + < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + uint64_t creationTime; + uint64_t duration = 0; + if (header[0] == 1) { + creationTime = U64_AT(&header[4]); + mHeaderTimescale = U32_AT(&header[20]); + duration = U64_AT(&header[24]); + if (duration == 0xffffffffffffffff) { + duration = 0; + } + } else if (header[0] != 0) { + return ERROR_MALFORMED; + } else { + creationTime = U32_AT(&header[4]); + mHeaderTimescale = U32_AT(&header[12]); + uint32_t d32 = U32_AT(&header[16]); + if (d32 == 0xffffffff) { + d32 = 0; + } + duration = d32; + } + if (duration != 0 && mHeaderTimescale != 0 && duration < UINT64_MAX / 1000000) { + mFileMetaData.setInt64(kKeyDuration, duration * 1000000 / mHeaderTimescale); + } + + String8 s; + if (convertTimeToDate(creationTime, &s)) { + mFileMetaData.setCString(kKeyDate, s.string()); + } + + + break; + } + + case FOURCC('m', 'e', 'h', 'd'): + { + *offset += chunk_size; + + if (chunk_data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t flags[4]; + if (mDataSource->readAt( + data_offset, flags, sizeof(flags)) + < (ssize_t)sizeof(flags)) { + return ERROR_IO; + } + + uint64_t duration = 0; + if (flags[0] == 1) { + // 64 bit + if (chunk_data_size < 12) { + return ERROR_MALFORMED; + } + mDataSource->getUInt64(data_offset + 4, &duration); + if (duration == 0xffffffffffffffff) { + duration = 0; + } + } else if (flags[0] == 0) { + // 32 bit + uint32_t d32; + mDataSource->getUInt32(data_offset + 4, &d32); + if (d32 == 0xffffffff) { + d32 = 0; + } + duration = d32; + } else { + return ERROR_MALFORMED; + } + + if (duration != 0 && mHeaderTimescale != 0) { + mFileMetaData.setInt64(kKeyDuration, duration * 1000000 / mHeaderTimescale); + } + + break; + } + + case FOURCC('m', 'd', 'a', 't'): + { + mMdatFound = true; + + *offset += chunk_size; + break; + } + + case FOURCC('h', 'd', 'l', 'r'): + { + *offset += chunk_size; + + if (underQTMetaPath(mPath, 3)) { + break; + } + + uint32_t buffer; + if (mDataSource->readAt( + data_offset + 8, &buffer, 4) < 4) { + return ERROR_IO; + } + + uint32_t type = ntohl(buffer); + // For the 3GPP file format, the handler-type within the 'hdlr' box + // shall be 'text'. We also want to support 'sbtl' handler type + // for a practical reason as various MPEG4 containers use it. + if (type == FOURCC('t', 'e', 'x', 't') || type == FOURCC('s', 'b', 't', 'l')) { + if (mLastTrack != NULL) { + mLastTrack->meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP); + } + } + + break; + } + + case FOURCC('k', 'e', 'y', 's'): + { + *offset += chunk_size; + + if (underQTMetaPath(mPath, 3)) { + status_t err = parseQTMetaKey(data_offset, chunk_data_size); + if (err != OK) { + return err; + } + } + break; + } + + case FOURCC('t', 'r', 'e', 'x'): + { + *offset += chunk_size; + + if (chunk_data_size < 24) { + return ERROR_IO; + } + Trex trex; + if (!mDataSource->getUInt32(data_offset + 4, &trex.track_ID) || + !mDataSource->getUInt32(data_offset + 8, &trex.default_sample_description_index) || + !mDataSource->getUInt32(data_offset + 12, &trex.default_sample_duration) || + !mDataSource->getUInt32(data_offset + 16, &trex.default_sample_size) || + !mDataSource->getUInt32(data_offset + 20, &trex.default_sample_flags)) { + return ERROR_IO; + } + mTrex.add(trex); + break; + } + + case FOURCC('t', 'x', '3', 'g'): + { + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + uint32_t type; + const void *data; + size_t size = 0; + if (!mLastTrack->meta.findData( + kKeyTextFormatData, &type, &data, &size)) { + size = 0; + } + + if ((chunk_size > SIZE_MAX) || (SIZE_MAX - chunk_size <= size)) { + return ERROR_MALFORMED; + } + + uint8_t *buffer = new (std::nothrow) uint8_t[size + chunk_size]; + if (buffer == NULL) { + return ERROR_MALFORMED; + } + + if (size > 0) { + memcpy(buffer, data, size); + } + + if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size)) + < chunk_size) { + delete[] buffer; + buffer = NULL; + + // advance read pointer so we don't end up reading this again + *offset += chunk_size; + return ERROR_IO; + } + + mLastTrack->meta.setData( + kKeyTextFormatData, 0, buffer, size + chunk_size); + + delete[] buffer; + + *offset += chunk_size; + break; + } + + case FOURCC('c', 'o', 'v', 'r'): + { + *offset += chunk_size; + + ALOGV("chunk_data_size = %" PRId64 " and data_offset = %" PRId64, + chunk_data_size, data_offset); + + if (chunk_data_size < 0 || static_cast<uint64_t>(chunk_data_size) >= SIZE_MAX - 1) { + return ERROR_MALFORMED; + } + auto buffer = heapbuffer<uint8_t>(chunk_data_size); + if (buffer.get() == NULL) { + ALOGE("b/28471206"); + return NO_MEMORY; + } + if (mDataSource->readAt( + data_offset, buffer.get(), chunk_data_size) != (ssize_t)chunk_data_size) { + return ERROR_IO; + } + const int kSkipBytesOfDataBox = 16; + if (chunk_data_size <= kSkipBytesOfDataBox) { + return ERROR_MALFORMED; + } + + mFileMetaData.setData( + kKeyAlbumArt, MetaData::TYPE_NONE, + buffer.get() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox); + + break; + } + + case FOURCC('c', 'o', 'l', 'r'): + { + *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('s', 't', 's', 'd')) { + status_t err = parseColorInfo(data_offset, chunk_data_size); + if (err != OK) { + return err; + } + } + + break; + } + + case FOURCC('t', 'i', 't', 'l'): + case FOURCC('p', 'e', 'r', 'f'): + case FOURCC('a', 'u', 't', 'h'): + case FOURCC('g', 'n', 'r', 'e'): + case FOURCC('a', 'l', 'b', 'm'): + case FOURCC('y', 'r', 'r', 'c'): + { + *offset += chunk_size; + + status_t err = parse3GPPMetaData(data_offset, chunk_data_size, depth); + + if (err != OK) { + return err; + } + + break; + } + + case FOURCC('I', 'D', '3', '2'): + { + *offset += chunk_size; + + if (chunk_data_size < 6) { + return ERROR_MALFORMED; + } + + parseID3v2MetaData(data_offset + 6); + + break; + } + + case FOURCC('-', '-', '-', '-'): + { + mLastCommentMean.clear(); + mLastCommentName.clear(); + mLastCommentData.clear(); + *offset += chunk_size; + break; + } + + case FOURCC('s', 'i', 'd', 'x'): + { + status_t err = parseSegmentIndex(data_offset, chunk_data_size); + if (err != OK) { + return err; + } + *offset += chunk_size; + return UNKNOWN_ERROR; // stop parsing after sidx + } + + case FOURCC('a', 'c', '-', '3'): + { + *offset += chunk_size; + return parseAC3SampleEntry(data_offset); + } + + case FOURCC('f', 't', 'y', 'p'): + { + if (chunk_data_size < 8 || depth != 0) { + return ERROR_MALFORMED; + } + + off64_t stop_offset = *offset + chunk_size; + uint32_t numCompatibleBrands = (chunk_data_size - 8) / 4; + std::set<uint32_t> brandSet; + for (size_t i = 0; i < numCompatibleBrands + 2; ++i) { + if (i == 1) { + // Skip this index, it refers to the minorVersion, + // not a brand. + continue; + } + + uint32_t brand; + if (mDataSource->readAt(data_offset + 4 * i, &brand, 4) < 4) { + return ERROR_MALFORMED; + } + + brand = ntohl(brand); + brandSet.insert(brand); + } + + if (brandSet.count(FOURCC('q', 't', ' ', ' ')) > 0) { + mIsQT = true; + } else { + if (brandSet.count(FOURCC('m', 'i', 'f', '1')) > 0 + && brandSet.count(FOURCC('h', 'e', 'i', 'c')) > 0) { + ALOGV("identified HEIF image"); + + mIsHeif = true; + brandSet.erase(FOURCC('m', 'i', 'f', '1')); + brandSet.erase(FOURCC('h', 'e', 'i', 'c')); + } + + if (!brandSet.empty()) { + // This means that the file should have moov box. + // It could be any iso files (mp4, heifs, etc.) + mHasMoovBox = true; + if (mIsHeif) { + ALOGV("identified HEIF image with other tracks"); + } + } + } + + *offset = stop_offset; + + break; + } + + default: + { + // check if we're parsing 'ilst' for meta keys + // if so, treat type as a number (key-id). + if (underQTMetaPath(mPath, 3)) { + status_t err = parseQTMetaVal(chunk_type, data_offset, chunk_data_size); + if (err != OK) { + return err; + } + } + + *offset += chunk_size; + break; + } + } + + return OK; +} + +status_t MPEG4Extractor::parseAC3SampleEntry(off64_t offset) { + // skip 16 bytes: + // + 6-byte reserved, + // + 2-byte data reference index, + // + 8-byte reserved + offset += 16; + uint16_t channelCount; + if (!mDataSource->getUInt16(offset, &channelCount)) { + return ERROR_MALFORMED; + } + // skip 8 bytes: + // + 2-byte channelCount, + // + 2-byte sample size, + // + 4-byte reserved + offset += 8; + uint16_t sampleRate; + if (!mDataSource->getUInt16(offset, &sampleRate)) { + ALOGE("MPEG4Extractor: error while reading ac-3 block: cannot read sample rate"); + return ERROR_MALFORMED; + } + + // skip 4 bytes: + // + 2-byte sampleRate, + // + 2-byte reserved + offset += 4; + return parseAC3SpecificBox(offset, sampleRate); +} + +status_t MPEG4Extractor::parseAC3SpecificBox( + off64_t offset, uint16_t sampleRate) { + uint32_t size; + // + 4-byte size + // + 4-byte type + // + 3-byte payload + const uint32_t kAC3SpecificBoxSize = 11; + if (!mDataSource->getUInt32(offset, &size) || size < kAC3SpecificBoxSize) { + ALOGE("MPEG4Extractor: error while reading ac-3 block: cannot read specific box size"); + return ERROR_MALFORMED; + } + + offset += 4; + uint32_t type; + if (!mDataSource->getUInt32(offset, &type) || type != FOURCC('d', 'a', 'c', '3')) { + ALOGE("MPEG4Extractor: error while reading ac-3 specific block: header not dac3"); + return ERROR_MALFORMED; + } + + offset += 4; + const uint32_t kAC3SpecificBoxPayloadSize = 3; + uint8_t chunk[kAC3SpecificBoxPayloadSize]; + if (mDataSource->readAt(offset, chunk, sizeof(chunk)) != sizeof(chunk)) { + ALOGE("MPEG4Extractor: error while reading ac-3 specific block: bitstream fields"); + return ERROR_MALFORMED; + } + + ABitReader br(chunk, sizeof(chunk)); + static const unsigned channelCountTable[] = {2, 1, 2, 3, 3, 4, 4, 5}; + static const unsigned sampleRateTable[] = {48000, 44100, 32000}; + + unsigned fscod = br.getBits(2); + if (fscod == 3) { + ALOGE("Incorrect fscod (3) in AC3 header"); + return ERROR_MALFORMED; + } + unsigned boxSampleRate = sampleRateTable[fscod]; + if (boxSampleRate != sampleRate) { + ALOGE("sample rate mismatch: boxSampleRate = %d, sampleRate = %d", + boxSampleRate, sampleRate); + return ERROR_MALFORMED; + } + + unsigned bsid = br.getBits(5); + if (bsid > 8) { + ALOGW("Incorrect bsid in AC3 header. Possibly E-AC-3?"); + return ERROR_MALFORMED; + } + + // skip + unsigned bsmod __unused = br.getBits(3); + + unsigned acmod = br.getBits(3); + unsigned lfeon = br.getBits(1); + unsigned channelCount = channelCountTable[acmod] + lfeon; + + if (mLastTrack == NULL) { + return ERROR_MALFORMED; + } + mLastTrack->meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AC3); + mLastTrack->meta.setInt32(kKeyChannelCount, channelCount); + mLastTrack->meta.setInt32(kKeySampleRate, sampleRate); + return OK; +} + +status_t MPEG4Extractor::parseSegmentIndex(off64_t offset, size_t size) { + ALOGV("MPEG4Extractor::parseSegmentIndex"); + + if (size < 12) { + return -EINVAL; + } + + uint32_t flags; + if (!mDataSource->getUInt32(offset, &flags)) { + return ERROR_MALFORMED; + } + + uint32_t version = flags >> 24; + flags &= 0xffffff; + + ALOGV("sidx version %d", version); + + uint32_t referenceId; + if (!mDataSource->getUInt32(offset + 4, &referenceId)) { + return ERROR_MALFORMED; + } + + uint32_t timeScale; + if (!mDataSource->getUInt32(offset + 8, &timeScale)) { + return ERROR_MALFORMED; + } + ALOGV("sidx refid/timescale: %d/%d", referenceId, timeScale); + if (timeScale == 0) + return ERROR_MALFORMED; + + uint64_t earliestPresentationTime; + uint64_t firstOffset; + + offset += 12; + size -= 12; + + if (version == 0) { + if (size < 8) { + return -EINVAL; + } + uint32_t tmp; + if (!mDataSource->getUInt32(offset, &tmp)) { + return ERROR_MALFORMED; + } + earliestPresentationTime = tmp; + if (!mDataSource->getUInt32(offset + 4, &tmp)) { + return ERROR_MALFORMED; + } + firstOffset = tmp; + offset += 8; + size -= 8; + } else { + if (size < 16) { + return -EINVAL; + } + if (!mDataSource->getUInt64(offset, &earliestPresentationTime)) { + return ERROR_MALFORMED; + } + if (!mDataSource->getUInt64(offset + 8, &firstOffset)) { + return ERROR_MALFORMED; + } + offset += 16; + size -= 16; + } + ALOGV("sidx pres/off: %" PRIu64 "/%" PRIu64, earliestPresentationTime, firstOffset); + + if (size < 4) { + return -EINVAL; + } + + uint16_t referenceCount; + if (!mDataSource->getUInt16(offset + 2, &referenceCount)) { + return ERROR_MALFORMED; + } + offset += 4; + size -= 4; + ALOGV("refcount: %d", referenceCount); + + if (size < referenceCount * 12) { + return -EINVAL; + } + + uint64_t total_duration = 0; + for (unsigned int i = 0; i < referenceCount; i++) { + uint32_t d1, d2, d3; + + if (!mDataSource->getUInt32(offset, &d1) || // size + !mDataSource->getUInt32(offset + 4, &d2) || // duration + !mDataSource->getUInt32(offset + 8, &d3)) { // flags + return ERROR_MALFORMED; + } + + if (d1 & 0x80000000) { + ALOGW("sub-sidx boxes not supported yet"); + } + bool sap = d3 & 0x80000000; + uint32_t saptype = (d3 >> 28) & 7; + if (!sap || (saptype != 1 && saptype != 2)) { + // type 1 and 2 are sync samples + ALOGW("not a stream access point, or unsupported type: %08x", d3); + } + total_duration += d2; + offset += 12; + ALOGV(" item %d, %08x %08x %08x", i, d1, d2, d3); + SidxEntry se; + se.mSize = d1 & 0x7fffffff; + se.mDurationUs = 1000000LL * d2 / timeScale; + mSidxEntries.add(se); + } + + uint64_t sidxDuration = total_duration * 1000000 / timeScale; + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + int64_t metaDuration; + if (!mLastTrack->meta.findInt64(kKeyDuration, &metaDuration) || metaDuration == 0) { + mLastTrack->meta.setInt64(kKeyDuration, sidxDuration); + } + return OK; +} + +status_t MPEG4Extractor::parseQTMetaKey(off64_t offset, size_t size) { + if (size < 8) { + return ERROR_MALFORMED; + } + + uint32_t count; + if (!mDataSource->getUInt32(offset + 4, &count)) { + return ERROR_MALFORMED; + } + + if (mMetaKeyMap.size() > 0) { + ALOGW("'keys' atom seen again, discarding existing entries"); + mMetaKeyMap.clear(); + } + + off64_t keyOffset = offset + 8; + off64_t stopOffset = offset + size; + for (size_t i = 1; i <= count; i++) { + if (keyOffset + 8 > stopOffset) { + return ERROR_MALFORMED; + } + + uint32_t keySize; + if (!mDataSource->getUInt32(keyOffset, &keySize) + || keySize < 8 + || keyOffset + keySize > stopOffset) { + return ERROR_MALFORMED; + } + + uint32_t type; + if (!mDataSource->getUInt32(keyOffset + 4, &type) + || type != FOURCC('m', 'd', 't', 'a')) { + return ERROR_MALFORMED; + } + + keySize -= 8; + keyOffset += 8; + + auto keyData = heapbuffer<uint8_t>(keySize); + if (keyData.get() == NULL) { + return ERROR_MALFORMED; + } + if (mDataSource->readAt( + keyOffset, keyData.get(), keySize) < (ssize_t) keySize) { + return ERROR_MALFORMED; + } + + AString key((const char *)keyData.get(), keySize); + mMetaKeyMap.add(i, key); + + keyOffset += keySize; + } + return OK; +} + +status_t MPEG4Extractor::parseQTMetaVal( + int32_t keyId, off64_t offset, size_t size) { + ssize_t index = mMetaKeyMap.indexOfKey(keyId); + if (index < 0) { + // corresponding key is not present, ignore + return ERROR_MALFORMED; + } + + if (size <= 16) { + return ERROR_MALFORMED; + } + uint32_t dataSize; + if (!mDataSource->getUInt32(offset, &dataSize) + || dataSize > size || dataSize <= 16) { + return ERROR_MALFORMED; + } + uint32_t atomFourCC; + if (!mDataSource->getUInt32(offset + 4, &atomFourCC) + || atomFourCC != FOURCC('d', 'a', 't', 'a')) { + return ERROR_MALFORMED; + } + uint32_t dataType; + if (!mDataSource->getUInt32(offset + 8, &dataType) + || ((dataType & 0xff000000) != 0)) { + // not well-known type + return ERROR_MALFORMED; + } + + dataSize -= 16; + offset += 16; + + if (dataType == 23 && dataSize >= 4) { + // BE Float32 + uint32_t val; + if (!mDataSource->getUInt32(offset, &val)) { + return ERROR_MALFORMED; + } + if (!strcasecmp(mMetaKeyMap[index].c_str(), "com.android.capture.fps")) { + mFileMetaData.setFloat(kKeyCaptureFramerate, *(float *)&val); + } + } else if (dataType == 67 && dataSize >= 4) { + // BE signed int32 + uint32_t val; + if (!mDataSource->getUInt32(offset, &val)) { + return ERROR_MALFORMED; + } + if (!strcasecmp(mMetaKeyMap[index].c_str(), "com.android.video.temporal_layers_count")) { + mFileMetaData.setInt32(kKeyTemporalLayerCount, val); + } + } else { + // add more keys if needed + ALOGV("ignoring key: type %d, size %d", dataType, dataSize); + } + + return OK; +} + +status_t MPEG4Extractor::parseTrackHeader( + off64_t data_offset, off64_t data_size) { + if (data_size < 4) { + return ERROR_MALFORMED; + } + + uint8_t version; + if (mDataSource->readAt(data_offset, &version, 1) < 1) { + return ERROR_IO; + } + + size_t dynSize = (version == 1) ? 36 : 24; + + uint8_t buffer[36 + 60]; + + if (data_size != (off64_t)dynSize + 60) { + return ERROR_MALFORMED; + } + + if (mDataSource->readAt( + data_offset, buffer, data_size) < (ssize_t)data_size) { + 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]); + id = U32_AT(&buffer[20]); + duration = U64_AT(&buffer[28]); + } else if (version == 0) { + ctime = U32_AT(&buffer[4]); + mtime = U32_AT(&buffer[8]); + id = U32_AT(&buffer[12]); + duration = U32_AT(&buffer[20]); + } else { + return ERROR_UNSUPPORTED; + } + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + mLastTrack->meta.setInt32(kKeyTrackID, id); + + size_t matrixOffset = dynSize + 16; + int32_t a00 = U32_AT(&buffer[matrixOffset]); + int32_t a01 = U32_AT(&buffer[matrixOffset + 4]); + int32_t a10 = U32_AT(&buffer[matrixOffset + 12]); + int32_t a11 = U32_AT(&buffer[matrixOffset + 16]); + +#if 0 + int32_t dx = U32_AT(&buffer[matrixOffset + 8]); + int32_t dy = U32_AT(&buffer[matrixOffset + 20]); + + ALOGI("x' = %.2f * x + %.2f * y + %.2f", + a00 / 65536.0f, a01 / 65536.0f, dx / 65536.0f); + ALOGI("y' = %.2f * x + %.2f * y + %.2f", + a10 / 65536.0f, a11 / 65536.0f, dy / 65536.0f); +#endif + + uint32_t rotationDegrees; + + static const int32_t kFixedOne = 0x10000; + if (a00 == kFixedOne && a01 == 0 && a10 == 0 && a11 == kFixedOne) { + // Identity, no rotation + rotationDegrees = 0; + } else if (a00 == 0 && a01 == kFixedOne && a10 == -kFixedOne && a11 == 0) { + rotationDegrees = 90; + } else if (a00 == 0 && a01 == -kFixedOne && a10 == kFixedOne && a11 == 0) { + rotationDegrees = 270; + } else if (a00 == -kFixedOne && a01 == 0 && a10 == 0 && a11 == -kFixedOne) { + rotationDegrees = 180; + } else { + ALOGW("We only support 0,90,180,270 degree rotation matrices"); + rotationDegrees = 0; + } + + if (rotationDegrees != 0) { + mLastTrack->meta.setInt32(kKeyRotation, rotationDegrees); + } + + // Handle presentation display size, which could be different + // from the image size indicated by kKeyWidth and kKeyHeight. + uint32_t width = U32_AT(&buffer[dynSize + 52]); + uint32_t height = U32_AT(&buffer[dynSize + 56]); + mLastTrack->meta.setInt32(kKeyDisplayWidth, width >> 16); + mLastTrack->meta.setInt32(kKeyDisplayHeight, height >> 16); + + return OK; +} + +status_t MPEG4Extractor::parseITunesMetaData(off64_t offset, size_t size) { + if (size == 0) { + return OK; + } + + if (size < 4 || size == SIZE_MAX) { + return ERROR_MALFORMED; + } + + uint8_t *buffer = new (std::nothrow) uint8_t[size + 1]; + if (buffer == NULL) { + return ERROR_MALFORMED; + } + if (mDataSource->readAt( + offset, buffer, size) != (ssize_t)size) { + delete[] buffer; + buffer = NULL; + + return ERROR_IO; + } + + uint32_t flags = U32_AT(buffer); + + uint32_t metadataKey = 0; + char chunk[5]; + MakeFourCCString(mPath[4], chunk); + ALOGV("meta: %s @ %lld", chunk, (long long)offset); + switch ((int32_t)mPath[4]) { + case FOURCC(0xa9, 'a', 'l', 'b'): + { + metadataKey = kKeyAlbum; + break; + } + case FOURCC(0xa9, 'A', 'R', 'T'): + { + metadataKey = kKeyArtist; + break; + } + case FOURCC('a', 'A', 'R', 'T'): + { + metadataKey = kKeyAlbumArtist; + break; + } + case FOURCC(0xa9, 'd', 'a', 'y'): + { + metadataKey = kKeyYear; + break; + } + case FOURCC(0xa9, 'n', 'a', 'm'): + { + metadataKey = kKeyTitle; + break; + } + case FOURCC(0xa9, 'w', 'r', 't'): + { + metadataKey = kKeyWriter; + break; + } + case FOURCC('c', 'o', 'v', 'r'): + { + metadataKey = kKeyAlbumArt; + break; + } + case FOURCC('g', 'n', 'r', 'e'): + { + metadataKey = kKeyGenre; + break; + } + case FOURCC(0xa9, 'g', 'e', 'n'): + { + metadataKey = kKeyGenre; + break; + } + case FOURCC('c', 'p', 'i', 'l'): + { + if (size == 9 && flags == 21) { + char tmp[16]; + sprintf(tmp, "%d", + (int)buffer[size - 1]); + + mFileMetaData.setCString(kKeyCompilation, tmp); + } + break; + } + case FOURCC('t', 'r', 'k', 'n'): + { + if (size == 16 && flags == 0) { + char tmp[16]; + uint16_t* pTrack = (uint16_t*)&buffer[10]; + uint16_t* pTotalTracks = (uint16_t*)&buffer[12]; + sprintf(tmp, "%d/%d", ntohs(*pTrack), ntohs(*pTotalTracks)); + + mFileMetaData.setCString(kKeyCDTrackNumber, tmp); + } + break; + } + case FOURCC('d', 'i', 's', 'k'): + { + if ((size == 14 || size == 16) && flags == 0) { + char tmp[16]; + uint16_t* pDisc = (uint16_t*)&buffer[10]; + uint16_t* pTotalDiscs = (uint16_t*)&buffer[12]; + sprintf(tmp, "%d/%d", ntohs(*pDisc), ntohs(*pTotalDiscs)); + + mFileMetaData.setCString(kKeyDiscNumber, tmp); + } + break; + } + case FOURCC('-', '-', '-', '-'): + { + buffer[size] = '\0'; + switch (mPath[5]) { + case FOURCC('m', 'e', 'a', 'n'): + mLastCommentMean.setTo((const char *)buffer + 4); + break; + case FOURCC('n', 'a', 'm', 'e'): + mLastCommentName.setTo((const char *)buffer + 4); + break; + case FOURCC('d', 'a', 't', 'a'): + if (size < 8) { + delete[] buffer; + buffer = NULL; + ALOGE("b/24346430"); + return ERROR_MALFORMED; + } + mLastCommentData.setTo((const char *)buffer + 8); + break; + } + + // Once we have a set of mean/name/data info, go ahead and process + // it to see if its something we are interested in. Whether or not + // were are interested in the specific tag, make sure to clear out + // the set so we can be ready to process another tuple should one + // show up later in the file. + if ((mLastCommentMean.length() != 0) && + (mLastCommentName.length() != 0) && + (mLastCommentData.length() != 0)) { + + if (mLastCommentMean == "com.apple.iTunes" + && mLastCommentName == "iTunSMPB") { + int32_t delay, padding; + if (sscanf(mLastCommentData, + " %*x %x %x %*x", &delay, &padding) == 2) { + if (mLastTrack == NULL) { + delete[] buffer; + return ERROR_MALFORMED; + } + + mLastTrack->meta.setInt32(kKeyEncoderDelay, delay); + mLastTrack->meta.setInt32(kKeyEncoderPadding, padding); + } + } + + mLastCommentMean.clear(); + mLastCommentName.clear(); + mLastCommentData.clear(); + } + break; + } + + default: + break; + } + + if (size >= 8 && metadataKey && !mFileMetaData.hasData(metadataKey)) { + if (metadataKey == kKeyAlbumArt) { + mFileMetaData.setData( + kKeyAlbumArt, MetaData::TYPE_NONE, + buffer + 8, size - 8); + } else if (metadataKey == kKeyGenre) { + if (flags == 0) { + // uint8_t genre code, iTunes genre codes are + // the standard id3 codes, except they start + // at 1 instead of 0 (e.g. Pop is 14, not 13) + // We use standard id3 numbering, so subtract 1. + int genrecode = (int)buffer[size - 1]; + genrecode--; + if (genrecode < 0) { + genrecode = 255; // reserved for 'unknown genre' + } + char genre[10]; + sprintf(genre, "%d", genrecode); + + mFileMetaData.setCString(metadataKey, genre); + } else if (flags == 1) { + // custom genre string + buffer[size] = '\0'; + + mFileMetaData.setCString( + metadataKey, (const char *)buffer + 8); + } + } else { + buffer[size] = '\0'; + + mFileMetaData.setCString( + metadataKey, (const char *)buffer + 8); + } + } + + delete[] buffer; + buffer = NULL; + + return OK; +} + +status_t MPEG4Extractor::parseColorInfo(off64_t offset, size_t size) { + if (size < 4 || size == SIZE_MAX || mLastTrack == NULL) { + return ERROR_MALFORMED; + } + + uint8_t *buffer = new (std::nothrow) uint8_t[size + 1]; + if (buffer == NULL) { + return ERROR_MALFORMED; + } + if (mDataSource->readAt(offset, buffer, size) != (ssize_t)size) { + delete[] buffer; + buffer = NULL; + + return ERROR_IO; + } + + int32_t type = U32_AT(&buffer[0]); + if ((type == FOURCC('n', 'c', 'l', 'x') && size >= 11) + || (type == FOURCC('n', 'c', 'l', 'c') && size >= 10)) { + int32_t primaries = U16_AT(&buffer[4]); + int32_t transfer = U16_AT(&buffer[6]); + int32_t coeffs = U16_AT(&buffer[8]); + bool fullRange = (type == FOURCC('n', 'c', 'l', 'x')) && (buffer[10] & 128); + + ColorAspects aspects; + ColorUtils::convertIsoColorAspectsToCodecAspects( + primaries, transfer, coeffs, fullRange, aspects); + + // only store the first color specification + if (!mLastTrack->meta.hasData(kKeyColorPrimaries)) { + mLastTrack->meta.setInt32(kKeyColorPrimaries, aspects.mPrimaries); + mLastTrack->meta.setInt32(kKeyTransferFunction, aspects.mTransfer); + mLastTrack->meta.setInt32(kKeyColorMatrix, aspects.mMatrixCoeffs); + mLastTrack->meta.setInt32(kKeyColorRange, aspects.mRange); + } + } + + delete[] buffer; + buffer = NULL; + + return OK; +} + +status_t MPEG4Extractor::parse3GPPMetaData(off64_t offset, size_t size, int depth) { + if (size < 4 || size == SIZE_MAX) { + return ERROR_MALFORMED; + } + + uint8_t *buffer = new (std::nothrow) uint8_t[size + 1]; + if (buffer == NULL) { + return ERROR_MALFORMED; + } + if (mDataSource->readAt( + offset, buffer, size) != (ssize_t)size) { + delete[] buffer; + buffer = NULL; + + return ERROR_IO; + } + + uint32_t metadataKey = 0; + switch (mPath[depth]) { + case FOURCC('t', 'i', 't', 'l'): + { + metadataKey = kKeyTitle; + break; + } + case FOURCC('p', 'e', 'r', 'f'): + { + metadataKey = kKeyArtist; + break; + } + case FOURCC('a', 'u', 't', 'h'): + { + metadataKey = kKeyWriter; + break; + } + case FOURCC('g', 'n', 'r', 'e'): + { + metadataKey = kKeyGenre; + break; + } + case FOURCC('a', 'l', 'b', 'm'): + { + if (buffer[size - 1] != '\0') { + char tmp[4]; + sprintf(tmp, "%u", buffer[size - 1]); + + mFileMetaData.setCString(kKeyCDTrackNumber, tmp); + } + + metadataKey = kKeyAlbum; + break; + } + case FOURCC('y', 'r', 'r', 'c'): + { + if (size < 6) { + delete[] buffer; + buffer = NULL; + ALOGE("b/62133227"); + android_errorWriteLog(0x534e4554, "62133227"); + return ERROR_MALFORMED; + } + char tmp[5]; + uint16_t year = U16_AT(&buffer[4]); + + if (year < 10000) { + sprintf(tmp, "%u", year); + + mFileMetaData.setCString(kKeyYear, tmp); + } + break; + } + + default: + break; + } + + if (metadataKey > 0) { + bool isUTF8 = true; // Common case + char16_t *framedata = NULL; + int len16 = 0; // Number of UTF-16 characters + + // smallest possible valid UTF-16 string w BOM: 0xfe 0xff 0x00 0x00 + if (size < 6) { + delete[] buffer; + buffer = NULL; + return ERROR_MALFORMED; + } + + if (size - 6 >= 4) { + len16 = ((size - 6) / 2) - 1; // don't include 0x0000 terminator + framedata = (char16_t *)(buffer + 6); + if (0xfffe == *framedata) { + // endianness marker (BOM) doesn't match host endianness + for (int i = 0; i < len16; i++) { + framedata[i] = bswap_16(framedata[i]); + } + // BOM is now swapped to 0xfeff, we will execute next block too + } + + if (0xfeff == *framedata) { + // Remove the BOM + framedata++; + len16--; + isUTF8 = false; + } + // else normal non-zero-length UTF-8 string + // we can't handle UTF-16 without BOM as there is no other + // indication of encoding. + } + + if (isUTF8) { + buffer[size] = 0; + mFileMetaData.setCString(metadataKey, (const char *)buffer + 6); + } else { + // Convert from UTF-16 string to UTF-8 string. + String8 tmpUTF8str(framedata, len16); + mFileMetaData.setCString(metadataKey, tmpUTF8str.string()); + } + } + + delete[] buffer; + buffer = NULL; + + return OK; +} + +void MPEG4Extractor::parseID3v2MetaData(off64_t offset) { + ID3 id3(mDataSource, true /* ignorev1 */, offset); + + if (id3.isValid()) { + struct Map { + int key; + const char *tag1; + const char *tag2; + }; + static const Map kMap[] = { + { kKeyAlbum, "TALB", "TAL" }, + { kKeyArtist, "TPE1", "TP1" }, + { kKeyAlbumArtist, "TPE2", "TP2" }, + { kKeyComposer, "TCOM", "TCM" }, + { kKeyGenre, "TCON", "TCO" }, + { kKeyTitle, "TIT2", "TT2" }, + { kKeyYear, "TYE", "TYER" }, + { kKeyAuthor, "TXT", "TEXT" }, + { kKeyCDTrackNumber, "TRK", "TRCK" }, + { kKeyDiscNumber, "TPA", "TPOS" }, + { kKeyCompilation, "TCP", "TCMP" }, + }; + static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]); + + for (size_t i = 0; i < kNumMapEntries; ++i) { + if (!mFileMetaData.hasData(kMap[i].key)) { + ID3::Iterator *it = new ID3::Iterator(id3, kMap[i].tag1); + if (it->done()) { + delete it; + it = new ID3::Iterator(id3, kMap[i].tag2); + } + + if (it->done()) { + delete it; + continue; + } + + String8 s; + it->getString(&s); + delete it; + + mFileMetaData.setCString(kMap[i].key, s); + } + } + + size_t dataSize; + String8 mime; + const void *data = id3.getAlbumArt(&dataSize, &mime); + + if (data) { + mFileMetaData.setData(kKeyAlbumArt, MetaData::TYPE_NONE, data, dataSize); + mFileMetaData.setCString(kKeyAlbumArtMIME, mime.string()); + } + } +} + +MediaTrack *MPEG4Extractor::getTrack(size_t index) { + status_t err; + if ((err = readMetaData()) != OK) { + return NULL; + } + + Track *track = mFirstTrack; + while (index > 0) { + if (track == NULL) { + return NULL; + } + + track = track->next; + --index; + } + + if (track == NULL) { + return NULL; + } + + + Trex *trex = NULL; + int32_t trackId; + if (track->meta.findInt32(kKeyTrackID, &trackId)) { + for (size_t i = 0; i < mTrex.size(); i++) { + Trex *t = &mTrex.editItemAt(i); + if (t->track_ID == (uint32_t) trackId) { + trex = t; + break; + } + } + } else { + ALOGE("b/21657957"); + return NULL; + } + + ALOGV("getTrack called, pssh: %zu", mPssh.size()); + + const char *mime; + if (!track->meta.findCString(kKeyMIMEType, &mime)) { + return NULL; + } + + sp<ItemTable> itemTable; + if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { + uint32_t type; + const void *data; + size_t size; + if (!track->meta.findData(kKeyAVCC, &type, &data, &size)) { + return NULL; + } + + const uint8_t *ptr = (const uint8_t *)data; + + if (size < 7 || ptr[0] != 1) { // configurationVersion == 1 + return NULL; + } + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC) + || !strcasecmp(mime, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC)) { + uint32_t type; + const void *data; + size_t size; + if (!track->meta.findData(kKeyHVCC, &type, &data, &size)) { + return NULL; + } + + const uint8_t *ptr = (const uint8_t *)data; + + if (size < 22 || ptr[0] != 1) { // configurationVersion == 1 + return NULL; + } + if (!strcasecmp(mime, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC)) { + itemTable = mItemTable; + } + } + + MPEG4Source *source = new MPEG4Source( + track->meta, mDataSource, track->timescale, track->sampleTable, + mSidxEntries, trex, mMoofOffset, itemTable); + if (source->init() != OK) { + delete source; + return NULL; + } + return source; +} + +// static +status_t MPEG4Extractor::verifyTrack(Track *track) { + const char *mime; + CHECK(track->meta.findCString(kKeyMIMEType, &mime)); + + uint32_t type; + const void *data; + size_t size; + if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { + if (!track->meta.findData(kKeyAVCC, &type, &data, &size) + || type != kTypeAVCC) { + return ERROR_MALFORMED; + } + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) { + if (!track->meta.findData(kKeyHVCC, &type, &data, &size) + || type != kTypeHVCC) { + return ERROR_MALFORMED; + } + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) + || !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG2) + || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { + if (!track->meta.findData(kKeyESDS, &type, &data, &size) + || type != kTypeESDS) { + return ERROR_MALFORMED; + } + } + + if (track->sampleTable == NULL || !track->sampleTable->isValid()) { + // Make sure we have all the metadata we need. + ALOGE("stbl atom missing/invalid."); + return ERROR_MALFORMED; + } + + if (track->timescale == 0) { + ALOGE("timescale invalid."); + return ERROR_MALFORMED; + } + + return OK; +} + +typedef enum { + //AOT_NONE = -1, + //AOT_NULL_OBJECT = 0, + //AOT_AAC_MAIN = 1, /**< Main profile */ + AOT_AAC_LC = 2, /**< Low Complexity object */ + //AOT_AAC_SSR = 3, + //AOT_AAC_LTP = 4, + AOT_SBR = 5, + //AOT_AAC_SCAL = 6, + //AOT_TWIN_VQ = 7, + //AOT_CELP = 8, + //AOT_HVXC = 9, + //AOT_RSVD_10 = 10, /**< (reserved) */ + //AOT_RSVD_11 = 11, /**< (reserved) */ + //AOT_TTSI = 12, /**< TTSI Object */ + //AOT_MAIN_SYNTH = 13, /**< Main Synthetic object */ + //AOT_WAV_TAB_SYNTH = 14, /**< Wavetable Synthesis object */ + //AOT_GEN_MIDI = 15, /**< General MIDI object */ + //AOT_ALG_SYNTH_AUD_FX = 16, /**< Algorithmic Synthesis and Audio FX object */ + AOT_ER_AAC_LC = 17, /**< Error Resilient(ER) AAC Low Complexity */ + //AOT_RSVD_18 = 18, /**< (reserved) */ + //AOT_ER_AAC_LTP = 19, /**< Error Resilient(ER) AAC LTP object */ + AOT_ER_AAC_SCAL = 20, /**< Error Resilient(ER) AAC Scalable object */ + //AOT_ER_TWIN_VQ = 21, /**< Error Resilient(ER) TwinVQ object */ + AOT_ER_BSAC = 22, /**< Error Resilient(ER) BSAC object */ + AOT_ER_AAC_LD = 23, /**< Error Resilient(ER) AAC LowDelay object */ + //AOT_ER_CELP = 24, /**< Error Resilient(ER) CELP object */ + //AOT_ER_HVXC = 25, /**< Error Resilient(ER) HVXC object */ + //AOT_ER_HILN = 26, /**< Error Resilient(ER) HILN object */ + //AOT_ER_PARA = 27, /**< Error Resilient(ER) Parametric object */ + //AOT_RSVD_28 = 28, /**< might become SSC */ + AOT_PS = 29, /**< PS, Parametric Stereo (includes SBR) */ + //AOT_MPEGS = 30, /**< MPEG Surround */ + + AOT_ESCAPE = 31, /**< Signal AOT uses more than 5 bits */ + + //AOT_MP3ONMP4_L1 = 32, /**< MPEG-Layer1 in mp4 */ + //AOT_MP3ONMP4_L2 = 33, /**< MPEG-Layer2 in mp4 */ + //AOT_MP3ONMP4_L3 = 34, /**< MPEG-Layer3 in mp4 */ + //AOT_RSVD_35 = 35, /**< might become DST */ + //AOT_RSVD_36 = 36, /**< might become ALS */ + //AOT_AAC_SLS = 37, /**< AAC + SLS */ + //AOT_SLS = 38, /**< SLS */ + //AOT_ER_AAC_ELD = 39, /**< AAC Enhanced Low Delay */ + + //AOT_USAC = 42, /**< USAC */ + //AOT_SAOC = 43, /**< SAOC */ + //AOT_LD_MPEGS = 44, /**< Low Delay MPEG Surround */ + + //AOT_RSVD50 = 50, /**< Interim AOT for Rsvd50 */ +} AUDIO_OBJECT_TYPE; + +status_t MPEG4Extractor::updateAudioTrackInfoFromESDS_MPEG4Audio( + const void *esds_data, size_t esds_size) { + ESDS esds(esds_data, esds_size); + + uint8_t objectTypeIndication; + if (esds.getObjectTypeIndication(&objectTypeIndication) != OK) { + return ERROR_MALFORMED; + } + + if (objectTypeIndication == 0xe1) { + // This isn't MPEG4 audio at all, it's QCELP 14k... + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + mLastTrack->meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_QCELP); + return OK; + } + + if (objectTypeIndication == 0x6b) { + // The media subtype is MP3 audio + // Our software MP3 audio decoder may not be able to handle + // packetized MP3 audio; for now, lets just return ERROR_UNSUPPORTED + ALOGE("MP3 track in MP4/3GPP file is not supported"); + return ERROR_UNSUPPORTED; + } + + if (mLastTrack != NULL) { + uint32_t maxBitrate = 0; + uint32_t avgBitrate = 0; + esds.getBitRate(&maxBitrate, &avgBitrate); + if (maxBitrate > 0 && maxBitrate < INT32_MAX) { + mLastTrack->meta.setInt32(kKeyMaxBitRate, (int32_t)maxBitrate); + } + if (avgBitrate > 0 && avgBitrate < INT32_MAX) { + mLastTrack->meta.setInt32(kKeyBitRate, (int32_t)avgBitrate); + } + } + + const uint8_t *csd; + size_t csd_size; + if (esds.getCodecSpecificInfo( + (const void **)&csd, &csd_size) != OK) { + return ERROR_MALFORMED; + } + + if (kUseHexDump) { + printf("ESD of size %zu\n", csd_size); + hexdump(csd, csd_size); + } + + if (csd_size == 0) { + // There's no further information, i.e. no codec specific data + // Let's assume that the information provided in the mpeg4 headers + // is accurate and hope for the best. + + return OK; + } + + if (csd_size < 2) { + return ERROR_MALFORMED; + } + + static uint32_t kSamplingRate[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350 + }; + + ABitReader br(csd, csd_size); + uint32_t objectType = br.getBits(5); + + if (objectType == 31) { // AAC-ELD => additional 6 bits + objectType = 32 + br.getBits(6); + } + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + //keep AOT type + mLastTrack->meta.setInt32(kKeyAACAOT, objectType); + + uint32_t freqIndex = br.getBits(4); + + int32_t sampleRate = 0; + int32_t numChannels = 0; + if (freqIndex == 15) { + if (br.numBitsLeft() < 28) return ERROR_MALFORMED; + sampleRate = br.getBits(24); + numChannels = br.getBits(4); + } else { + if (br.numBitsLeft() < 4) return ERROR_MALFORMED; + numChannels = br.getBits(4); + + if (freqIndex == 13 || freqIndex == 14) { + return ERROR_MALFORMED; + } + + sampleRate = kSamplingRate[freqIndex]; + } + + if (objectType == AOT_SBR || objectType == AOT_PS) {//SBR specific config per 14496-3 table 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); + } else { + if (extFreqIndex == 13 || extFreqIndex == 14) { + return ERROR_MALFORMED; + } + extSampleRate = kSamplingRate[extFreqIndex]; + } + //TODO: save the extension sampling rate value in meta data => + // mLastTrack->meta.setInt32(kKeyExtSampleRate, extSampleRate); + } + + switch (numChannels) { + // values defined in 14496-3_2009 amendment-4 Table 1.19 - Channel Configuration + case 0: + case 1:// FC + case 2:// FL FR + case 3:// FC, FL FR + case 4:// FC, FL FR, RC + case 5:// FC, FL FR, SL SR + case 6:// FC, FL FR, SL SR, LFE + //numChannels already contains the right value + break; + case 11:// FC, FL FR, SL SR, RC, LFE + numChannels = 7; + break; + case 7: // FC, FCL FCR, FL FR, SL SR, LFE + case 12:// FC, FL FR, SL SR, RL RR, LFE + case 14:// FC, FL FR, SL SR, LFE, FHL FHR + numChannels = 8; + break; + default: + return ERROR_UNSUPPORTED; + } + + { + if (objectType == AOT_SBR || objectType == AOT_PS) { + if (br.numBitsLeft() < 5) return ERROR_MALFORMED; + objectType = br.getBits(5); + + if (objectType == AOT_ESCAPE) { + if (br.numBitsLeft() < 6) return ERROR_MALFORMED; + objectType = 32 + br.getBits(6); + } + } + if (objectType == AOT_AAC_LC || objectType == AOT_ER_AAC_LC || + 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); + + const int32_t dependsOnCoreCoder = br.getBits(1); + + if (dependsOnCoreCoder ) { + if (br.numBitsLeft() < 14) return ERROR_MALFORMED; + const int32_t coreCoderDelay __unused = br.getBits(14); + } + + int32_t extensionFlag = -1; + if (br.numBitsLeft() > 0) { + extensionFlag = br.getBits(1); + } else { + switch (objectType) { + // 14496-3 4.5.1.1 extensionFlag + case AOT_AAC_LC: + extensionFlag = 0; + break; + case AOT_ER_AAC_LC: + case AOT_ER_AAC_SCAL: + case AOT_ER_BSAC: + case AOT_ER_AAC_LD: + extensionFlag = 1; + break; + default: + return ERROR_MALFORMED; + break; + } + ALOGW("csd missing extension flag; assuming %d for object type %u.", + extensionFlag, objectType); + } + + if (numChannels == 0) { + int32_t channelsEffectiveNum = 0; + int32_t channelsNum = 0; + 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); + 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); + + 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); + } + + 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); + } + + 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); + } + + 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); + 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); + 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); + 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); + channelsNum += 1; + } + ALOGV("mpeg4 audio channelsNum = %d", channelsNum); + ALOGV("mpeg4 audio channelsEffectiveNum = %d", channelsEffectiveNum); + numChannels = channelsNum; + } + } + } + + if (numChannels == 0) { + return ERROR_UNSUPPORTED; + } + + if (mLastTrack == NULL) + return ERROR_MALFORMED; + + int32_t prevSampleRate; + CHECK(mLastTrack->meta.findInt32(kKeySampleRate, &prevSampleRate)); + + if (prevSampleRate != sampleRate) { + ALOGV("mpeg4 audio sample rate different from previous setting. " + "was: %d, now: %d", prevSampleRate, sampleRate); + } + + mLastTrack->meta.setInt32(kKeySampleRate, sampleRate); + + int32_t prevChannelCount; + CHECK(mLastTrack->meta.findInt32(kKeyChannelCount, &prevChannelCount)); + + if (prevChannelCount != numChannels) { + ALOGV("mpeg4 audio channel count different from previous setting. " + "was: %d, now: %d", prevChannelCount, numChannels); + } + + mLastTrack->meta.setInt32(kKeyChannelCount, numChannels); + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG4Source::MPEG4Source( + MetaDataBase &format, + DataSourceBase *dataSource, + int32_t timeScale, + const sp<SampleTable> &sampleTable, + Vector<SidxEntry> &sidx, + const Trex *trex, + off64_t firstMoofOffset, + const sp<ItemTable> &itemTable) + : mFormat(format), + mDataSource(dataSource), + mTimescale(timeScale), + mSampleTable(sampleTable), + mCurrentSampleIndex(0), + mCurrentFragmentIndex(0), + mSegments(sidx), + mTrex(trex), + mFirstMoofOffset(firstMoofOffset), + mCurrentMoofOffset(firstMoofOffset), + mNextMoofOffset(-1), + mCurrentTime(0), + mDefaultEncryptedByteBlock(0), + mDefaultSkipByteBlock(0), + mCurrentSampleInfoAllocSize(0), + mCurrentSampleInfoSizes(NULL), + mCurrentSampleInfoOffsetsAllocSize(0), + mCurrentSampleInfoOffsets(NULL), + mIsAVC(false), + mIsHEVC(false), + mNALLengthSize(0), + mStarted(false), + mGroup(NULL), + mBuffer(NULL), + mWantsNALFragments(false), + mSrcBuffer(NULL), + mIsHeif(itemTable != NULL), + mItemTable(itemTable) { + + memset(&mTrackFragmentHeaderInfo, 0, sizeof(mTrackFragmentHeaderInfo)); + + mFormat.findInt32(kKeyCryptoMode, &mCryptoMode); + mDefaultIVSize = 0; + mFormat.findInt32(kKeyCryptoDefaultIVSize, &mDefaultIVSize); + uint32_t keytype; + const void *key; + size_t keysize; + if (mFormat.findData(kKeyCryptoKey, &keytype, &key, &keysize)) { + CHECK(keysize <= 16); + memset(mCryptoKey, 0, 16); + memcpy(mCryptoKey, key, keysize); + } + + mFormat.findInt32(kKeyEncryptedByteBlock, &mDefaultEncryptedByteBlock); + mFormat.findInt32(kKeySkipByteBlock, &mDefaultSkipByteBlock); + + const char *mime; + bool success = mFormat.findCString(kKeyMIMEType, &mime); + CHECK(success); + + mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC); + mIsHEVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC) || + !strcasecmp(mime, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC); + + if (mIsAVC) { + uint32_t type; + const void *data; + size_t size; + CHECK(format.findData(kKeyAVCC, &type, &data, &size)); + + const uint8_t *ptr = (const uint8_t *)data; + + CHECK(size >= 7); + CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1 + + // The number of bytes used to encode the length of a NAL unit. + mNALLengthSize = 1 + (ptr[4] & 3); + } else if (mIsHEVC) { + uint32_t type; + const void *data; + size_t size; + CHECK(format.findData(kKeyHVCC, &type, &data, &size)); + + const uint8_t *ptr = (const uint8_t *)data; + + CHECK(size >= 22); + CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1 + + mNALLengthSize = 1 + (ptr[14 + 7] & 3); + } + + CHECK(format.findInt32(kKeyTrackID, &mTrackId)); + +} + +status_t MPEG4Source::init() { + if (mFirstMoofOffset != 0) { + off64_t offset = mFirstMoofOffset; + return parseChunk(&offset); + } + return OK; +} + +MPEG4Source::~MPEG4Source() { + if (mStarted) { + stop(); + } + free(mCurrentSampleInfoSizes); + free(mCurrentSampleInfoOffsets); +} + +status_t MPEG4Source::start(MetaDataBase *params) { + Mutex::Autolock autoLock(mLock); + + CHECK(!mStarted); + + int32_t val; + if (params && params->findInt32(kKeyWantsNALFragments, &val) + && val != 0) { + mWantsNALFragments = true; + } else { + mWantsNALFragments = false; + } + + int32_t tmp; + CHECK(mFormat.findInt32(kKeyMaxInputSize, &tmp)); + size_t max_size = tmp; + + // A somewhat arbitrary limit that should be sufficient for 8k video frames + // If you see the message below for a valid input stream: increase the limit + const size_t kMaxBufferSize = 64 * 1024 * 1024; + if (max_size > kMaxBufferSize) { + ALOGE("bogus max input size: %zu > %zu", max_size, kMaxBufferSize); + return ERROR_MALFORMED; + } + if (max_size == 0) { + ALOGE("zero max input size"); + return ERROR_MALFORMED; + } + + // Allow up to kMaxBuffers, but not if the total exceeds kMaxBufferSize. + const size_t kInitialBuffers = 2; + const size_t kMaxBuffers = 8; + const size_t realMaxBuffers = min(kMaxBufferSize / max_size, kMaxBuffers); + mGroup = new MediaBufferGroup(kInitialBuffers, max_size, realMaxBuffers); + mSrcBuffer = new (std::nothrow) uint8_t[max_size]; + if (mSrcBuffer == NULL) { + // file probably specified a bad max size + delete mGroup; + mGroup = NULL; + return ERROR_MALFORMED; + } + + mStarted = true; + + return OK; +} + +status_t MPEG4Source::stop() { + Mutex::Autolock autoLock(mLock); + + CHECK(mStarted); + + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + + delete[] mSrcBuffer; + mSrcBuffer = NULL; + + delete mGroup; + mGroup = NULL; + + mStarted = false; + mCurrentSampleIndex = 0; + + return OK; +} + +status_t MPEG4Source::parseChunk(off64_t *offset) { + uint32_t hdr[2]; + if (mDataSource->readAt(*offset, hdr, 8) < 8) { + return ERROR_IO; + } + uint64_t chunk_size = ntohl(hdr[0]); + uint32_t chunk_type = ntohl(hdr[1]); + off64_t data_offset = *offset + 8; + + if (chunk_size == 1) { + if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) { + return ERROR_IO; + } + chunk_size = ntoh64(chunk_size); + data_offset += 8; + + if (chunk_size < 16) { + // The smallest valid chunk is 16 bytes long in this case. + return ERROR_MALFORMED; + } + } else if (chunk_size < 8) { + // The smallest valid chunk is 8 bytes long. + return ERROR_MALFORMED; + } + + char chunk[5]; + MakeFourCCString(chunk_type, chunk); + ALOGV("MPEG4Source chunk %s @ %#llx", chunk, (long long)*offset); + + off64_t chunk_data_size = *offset + chunk_size - data_offset; + + switch(chunk_type) { + + case FOURCC('t', 'r', 'a', 'f'): + case FOURCC('m', 'o', 'o', 'f'): { + off64_t stop_offset = *offset + chunk_size; + *offset = data_offset; + while (*offset < stop_offset) { + status_t err = parseChunk(offset); + if (err != OK) { + return err; + } + } + if (chunk_type == FOURCC('m', 'o', 'o', 'f')) { + // *offset points to the box following this moof. Find the next moof from there. + + while (true) { + if (mDataSource->readAt(*offset, hdr, 8) < 8) { + // no more box to the end of file. + break; + } + chunk_size = ntohl(hdr[0]); + chunk_type = ntohl(hdr[1]); + if (chunk_size == 1) { + // ISO/IEC 14496-12:2012, 8.8.4 Movie Fragment Box, moof is a Box + // which is defined in 4.2 Object Structure. + // When chunk_size==1, 8 bytes follows as "largesize". + if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) { + return ERROR_IO; + } + chunk_size = ntoh64(chunk_size); + if (chunk_size < 16) { + // The smallest valid chunk is 16 bytes long in this case. + return ERROR_MALFORMED; + } + } else if (chunk_size == 0) { + // next box extends to end of file. + } else if (chunk_size < 8) { + // The smallest valid chunk is 8 bytes long in this case. + return ERROR_MALFORMED; + } + + if (chunk_type == FOURCC('m', 'o', 'o', 'f')) { + mNextMoofOffset = *offset; + break; + } else if (chunk_size == 0) { + break; + } + *offset += chunk_size; + } + } + break; + } + + case FOURCC('t', 'f', 'h', 'd'): { + status_t err; + if ((err = parseTrackFragmentHeader(data_offset, chunk_data_size)) != OK) { + return err; + } + *offset += chunk_size; + break; + } + + case FOURCC('t', 'r', 'u', 'n'): { + status_t err; + if (mLastParsedTrackId == mTrackId) { + if ((err = parseTrackFragmentRun(data_offset, chunk_data_size)) != OK) { + return err; + } + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 'a', 'i', 'z'): { + status_t err; + if ((err = parseSampleAuxiliaryInformationSizes(data_offset, chunk_data_size)) != OK) { + return err; + } + *offset += chunk_size; + break; + } + case FOURCC('s', 'a', 'i', 'o'): { + status_t err; + if ((err = parseSampleAuxiliaryInformationOffsets(data_offset, chunk_data_size)) != OK) { + return err; + } + *offset += chunk_size; + break; + } + + case FOURCC('s', 'e', 'n', 'c'): { + status_t err; + if ((err = parseSampleEncryption(data_offset)) != OK) { + return err; + } + *offset += chunk_size; + break; + } + + case FOURCC('m', 'd', 'a', 't'): { + // parse DRM info if present + ALOGV("MPEG4Source::parseChunk mdat"); + // if saiz/saoi was previously observed, do something with the sampleinfos + *offset += chunk_size; + break; + } + + default: { + *offset += chunk_size; + break; + } + } + return OK; +} + +status_t MPEG4Source::parseSampleAuxiliaryInformationSizes( + off64_t offset, off64_t /* size */) { + ALOGV("parseSampleAuxiliaryInformationSizes"); + // 14496-12 8.7.12 + uint8_t version; + if (mDataSource->readAt( + offset, &version, sizeof(version)) + < (ssize_t)sizeof(version)) { + return ERROR_IO; + } + + if (version != 0) { + return ERROR_UNSUPPORTED; + } + offset++; + + uint32_t flags; + if (!mDataSource->getUInt24(offset, &flags)) { + return ERROR_IO; + } + offset += 3; + + if (flags & 1) { + uint32_t tmp; + if (!mDataSource->getUInt32(offset, &tmp)) { + return ERROR_MALFORMED; + } + mCurrentAuxInfoType = tmp; + offset += 4; + if (!mDataSource->getUInt32(offset, &tmp)) { + return ERROR_MALFORMED; + } + mCurrentAuxInfoTypeParameter = tmp; + offset += 4; + } + + uint8_t defsize; + if (mDataSource->readAt(offset, &defsize, 1) != 1) { + return ERROR_MALFORMED; + } + mCurrentDefaultSampleInfoSize = defsize; + offset++; + + uint32_t smplcnt; + if (!mDataSource->getUInt32(offset, &smplcnt)) { + return ERROR_MALFORMED; + } + mCurrentSampleInfoCount = smplcnt; + offset += 4; + + if (mCurrentDefaultSampleInfoSize != 0) { + ALOGV("@@@@ using default sample info size of %d", mCurrentDefaultSampleInfoSize); + return OK; + } + if (smplcnt > mCurrentSampleInfoAllocSize) { + uint8_t * newPtr = (uint8_t*) realloc(mCurrentSampleInfoSizes, smplcnt); + if (newPtr == NULL) { + ALOGE("failed to realloc %u -> %u", mCurrentSampleInfoAllocSize, smplcnt); + return NO_MEMORY; + } + mCurrentSampleInfoSizes = newPtr; + mCurrentSampleInfoAllocSize = smplcnt; + } + + mDataSource->readAt(offset, mCurrentSampleInfoSizes, smplcnt); + return OK; +} + +status_t MPEG4Source::parseSampleAuxiliaryInformationOffsets( + off64_t offset, off64_t /* size */) { + ALOGV("parseSampleAuxiliaryInformationOffsets"); + // 14496-12 8.7.13 + uint8_t version; + if (mDataSource->readAt(offset, &version, sizeof(version)) != 1) { + return ERROR_IO; + } + offset++; + + uint32_t flags; + if (!mDataSource->getUInt24(offset, &flags)) { + return ERROR_IO; + } + offset += 3; + + uint32_t entrycount; + if (!mDataSource->getUInt32(offset, &entrycount)) { + return ERROR_IO; + } + offset += 4; + if (entrycount == 0) { + return OK; + } + if (entrycount > UINT32_MAX / 8) { + return ERROR_MALFORMED; + } + + if (entrycount > mCurrentSampleInfoOffsetsAllocSize) { + uint64_t *newPtr = (uint64_t *)realloc(mCurrentSampleInfoOffsets, entrycount * 8); + if (newPtr == NULL) { + ALOGE("failed to realloc %u -> %u", mCurrentSampleInfoOffsetsAllocSize, entrycount * 8); + return NO_MEMORY; + } + mCurrentSampleInfoOffsets = newPtr; + mCurrentSampleInfoOffsetsAllocSize = entrycount; + } + mCurrentSampleInfoOffsetCount = entrycount; + + if (mCurrentSampleInfoOffsets == NULL) { + return OK; + } + + for (size_t i = 0; i < entrycount; i++) { + if (version == 0) { + uint32_t tmp; + if (!mDataSource->getUInt32(offset, &tmp)) { + return ERROR_IO; + } + mCurrentSampleInfoOffsets[i] = tmp; + offset += 4; + } else { + uint64_t tmp; + if (!mDataSource->getUInt64(offset, &tmp)) { + return ERROR_IO; + } + mCurrentSampleInfoOffsets[i] = tmp; + offset += 8; + } + } + + // parse clear/encrypted data + + off64_t drmoffset = mCurrentSampleInfoOffsets[0]; // from moof + + drmoffset += mCurrentMoofOffset; + + return parseClearEncryptedSizes(drmoffset, false, 0); +} + +status_t MPEG4Source::parseClearEncryptedSizes(off64_t offset, bool isSubsampleEncryption, uint32_t flags) { + + int ivlength; + CHECK(mFormat.findInt32(kKeyCryptoDefaultIVSize, &ivlength)); + + // only 0, 8 and 16 byte initialization vectors are supported + if (ivlength != 0 && ivlength != 8 && ivlength != 16) { + ALOGW("unsupported IV length: %d", ivlength); + return ERROR_MALFORMED; + } + + uint32_t sampleCount = mCurrentSampleInfoCount; + if (isSubsampleEncryption) { + if (!mDataSource->getUInt32(offset, &sampleCount)) { + return ERROR_IO; + } + offset += 4; + } + + // read CencSampleAuxiliaryDataFormats + for (size_t i = 0; i < sampleCount; i++) { + if (i >= mCurrentSamples.size()) { + ALOGW("too few samples"); + break; + } + Sample *smpl = &mCurrentSamples.editItemAt(i); + if (!smpl->clearsizes.isEmpty()) { + continue; + } + + memset(smpl->iv, 0, 16); + if (mDataSource->readAt(offset, smpl->iv, ivlength) != ivlength) { + return ERROR_IO; + } + + offset += ivlength; + + bool readSubsamples; + if (isSubsampleEncryption) { + readSubsamples = flags & 2; + } else { + int32_t smplinfosize = mCurrentDefaultSampleInfoSize; + if (smplinfosize == 0) { + smplinfosize = mCurrentSampleInfoSizes[i]; + } + readSubsamples = smplinfosize > ivlength; + } + + if (readSubsamples) { + uint16_t numsubsamples; + if (!mDataSource->getUInt16(offset, &numsubsamples)) { + return ERROR_IO; + } + offset += 2; + for (size_t j = 0; j < numsubsamples; j++) { + uint16_t numclear; + uint32_t numencrypted; + if (!mDataSource->getUInt16(offset, &numclear)) { + return ERROR_IO; + } + offset += 2; + if (!mDataSource->getUInt32(offset, &numencrypted)) { + return ERROR_IO; + } + offset += 4; + smpl->clearsizes.add(numclear); + smpl->encryptedsizes.add(numencrypted); + } + } else { + smpl->clearsizes.add(0); + smpl->encryptedsizes.add(smpl->size); + } + } + + return OK; +} + +status_t MPEG4Source::parseSampleEncryption(off64_t offset) { + uint32_t flags; + if (!mDataSource->getUInt32(offset, &flags)) { // actually version + flags + return ERROR_MALFORMED; + } + return parseClearEncryptedSizes(offset + 4, true, flags); +} + +status_t MPEG4Source::parseTrackFragmentHeader(off64_t offset, off64_t size) { + + if (size < 8) { + return -EINVAL; + } + + uint32_t flags; + if (!mDataSource->getUInt32(offset, &flags)) { // actually version + flags + return ERROR_MALFORMED; + } + + if (flags & 0xff000000) { + return -EINVAL; + } + + if (!mDataSource->getUInt32(offset + 4, (uint32_t*)&mLastParsedTrackId)) { + return ERROR_MALFORMED; + } + + if (mLastParsedTrackId != mTrackId) { + // this is not the right track, skip it + return OK; + } + + mTrackFragmentHeaderInfo.mFlags = flags; + mTrackFragmentHeaderInfo.mTrackID = mLastParsedTrackId; + offset += 8; + size -= 8; + + ALOGV("fragment header: %08x %08x", flags, mTrackFragmentHeaderInfo.mTrackID); + + if (flags & TrackFragmentHeaderInfo::kBaseDataOffsetPresent) { + if (size < 8) { + return -EINVAL; + } + + if (!mDataSource->getUInt64(offset, &mTrackFragmentHeaderInfo.mBaseDataOffset)) { + return ERROR_MALFORMED; + } + offset += 8; + size -= 8; + } + + if (flags & TrackFragmentHeaderInfo::kSampleDescriptionIndexPresent) { + if (size < 4) { + return -EINVAL; + } + + if (!mDataSource->getUInt32(offset, &mTrackFragmentHeaderInfo.mSampleDescriptionIndex)) { + return ERROR_MALFORMED; + } + offset += 4; + size -= 4; + } + + if (flags & TrackFragmentHeaderInfo::kDefaultSampleDurationPresent) { + if (size < 4) { + return -EINVAL; + } + + if (!mDataSource->getUInt32(offset, &mTrackFragmentHeaderInfo.mDefaultSampleDuration)) { + return ERROR_MALFORMED; + } + offset += 4; + size -= 4; + } + + if (flags & TrackFragmentHeaderInfo::kDefaultSampleSizePresent) { + if (size < 4) { + return -EINVAL; + } + + if (!mDataSource->getUInt32(offset, &mTrackFragmentHeaderInfo.mDefaultSampleSize)) { + return ERROR_MALFORMED; + } + offset += 4; + size -= 4; + } + + if (flags & TrackFragmentHeaderInfo::kDefaultSampleFlagsPresent) { + if (size < 4) { + return -EINVAL; + } + + if (!mDataSource->getUInt32(offset, &mTrackFragmentHeaderInfo.mDefaultSampleFlags)) { + return ERROR_MALFORMED; + } + offset += 4; + size -= 4; + } + + if (!(flags & TrackFragmentHeaderInfo::kBaseDataOffsetPresent)) { + mTrackFragmentHeaderInfo.mBaseDataOffset = mCurrentMoofOffset; + } + + mTrackFragmentHeaderInfo.mDataOffset = 0; + return OK; +} + +status_t MPEG4Source::parseTrackFragmentRun(off64_t offset, off64_t size) { + + ALOGV("MPEG4Extractor::parseTrackFragmentRun"); + if (size < 8) { + return -EINVAL; + } + + enum { + kDataOffsetPresent = 0x01, + kFirstSampleFlagsPresent = 0x04, + kSampleDurationPresent = 0x100, + kSampleSizePresent = 0x200, + kSampleFlagsPresent = 0x400, + kSampleCompositionTimeOffsetPresent = 0x800, + }; + + uint32_t flags; + if (!mDataSource->getUInt32(offset, &flags)) { + return ERROR_MALFORMED; + } + // |version| only affects SampleCompositionTimeOffset field. + // If version == 0, SampleCompositionTimeOffset is uint32_t; + // Otherwise, SampleCompositionTimeOffset is int32_t. + // Sample.compositionOffset is defined as int32_t. + uint8_t version = flags >> 24; + flags &= 0xffffff; + ALOGV("fragment run version: 0x%02x, flags: 0x%06x", version, flags); + + if ((flags & kFirstSampleFlagsPresent) && (flags & kSampleFlagsPresent)) { + // These two shall not be used together. + return -EINVAL; + } + + uint32_t sampleCount; + if (!mDataSource->getUInt32(offset + 4, &sampleCount)) { + return ERROR_MALFORMED; + } + offset += 8; + size -= 8; + + uint64_t dataOffset = mTrackFragmentHeaderInfo.mDataOffset; + + uint32_t firstSampleFlags = 0; + + if (flags & kDataOffsetPresent) { + if (size < 4) { + return -EINVAL; + } + + int32_t dataOffsetDelta; + if (!mDataSource->getUInt32(offset, (uint32_t*)&dataOffsetDelta)) { + return ERROR_MALFORMED; + } + + dataOffset = mTrackFragmentHeaderInfo.mBaseDataOffset + dataOffsetDelta; + + offset += 4; + size -= 4; + } + + if (flags & kFirstSampleFlagsPresent) { + if (size < 4) { + return -EINVAL; + } + + if (!mDataSource->getUInt32(offset, &firstSampleFlags)) { + return ERROR_MALFORMED; + } + offset += 4; + size -= 4; + } + + uint32_t sampleDuration = 0, sampleSize = 0, sampleFlags = 0, + sampleCtsOffset = 0; + + size_t bytesPerSample = 0; + if (flags & kSampleDurationPresent) { + bytesPerSample += 4; + } else if (mTrackFragmentHeaderInfo.mFlags + & TrackFragmentHeaderInfo::kDefaultSampleDurationPresent) { + sampleDuration = mTrackFragmentHeaderInfo.mDefaultSampleDuration; + } else if (mTrex) { + sampleDuration = mTrex->default_sample_duration; + } + + if (flags & kSampleSizePresent) { + bytesPerSample += 4; + } else if (mTrackFragmentHeaderInfo.mFlags + & TrackFragmentHeaderInfo::kDefaultSampleSizePresent) { + sampleSize = mTrackFragmentHeaderInfo.mDefaultSampleSize; + } else { + sampleSize = mTrackFragmentHeaderInfo.mDefaultSampleSize; + } + + if (flags & kSampleFlagsPresent) { + bytesPerSample += 4; + } else if (mTrackFragmentHeaderInfo.mFlags + & TrackFragmentHeaderInfo::kDefaultSampleFlagsPresent) { + sampleFlags = mTrackFragmentHeaderInfo.mDefaultSampleFlags; + } else { + sampleFlags = mTrackFragmentHeaderInfo.mDefaultSampleFlags; + } + + if (flags & kSampleCompositionTimeOffsetPresent) { + bytesPerSample += 4; + } else { + sampleCtsOffset = 0; + } + + if (size < (off64_t)(sampleCount * bytesPerSample)) { + return -EINVAL; + } + + Sample tmp; + for (uint32_t i = 0; i < sampleCount; ++i) { + if (flags & kSampleDurationPresent) { + if (!mDataSource->getUInt32(offset, &sampleDuration)) { + return ERROR_MALFORMED; + } + offset += 4; + } + + if (flags & kSampleSizePresent) { + if (!mDataSource->getUInt32(offset, &sampleSize)) { + return ERROR_MALFORMED; + } + offset += 4; + } + + if (flags & kSampleFlagsPresent) { + if (!mDataSource->getUInt32(offset, &sampleFlags)) { + return ERROR_MALFORMED; + } + offset += 4; + } + + if (flags & kSampleCompositionTimeOffsetPresent) { + if (!mDataSource->getUInt32(offset, &sampleCtsOffset)) { + return ERROR_MALFORMED; + } + offset += 4; + } + + ALOGV("adding sample %d at offset 0x%08" PRIx64 ", size %u, duration %u, " + " flags 0x%08x", i + 1, + dataOffset, sampleSize, sampleDuration, + (flags & kFirstSampleFlagsPresent) && i == 0 + ? firstSampleFlags : sampleFlags); + tmp.offset = dataOffset; + tmp.size = sampleSize; + tmp.duration = sampleDuration; + tmp.compositionOffset = sampleCtsOffset; + memset(tmp.iv, 0, sizeof(tmp.iv)); + mCurrentSamples.add(tmp); + + dataOffset += sampleSize; + } + + mTrackFragmentHeaderInfo.mDataOffset = dataOffset; + + return OK; +} + +status_t MPEG4Source::getFormat(MetaDataBase &meta) { + Mutex::Autolock autoLock(mLock); + meta = mFormat; + return OK; +} + +size_t MPEG4Source::parseNALSize(const uint8_t *data) const { + switch (mNALLengthSize) { + case 1: + return *data; + case 2: + return U16_AT(data); + case 3: + return ((size_t)data[0] << 16) | U16_AT(&data[1]); + case 4: + return U32_AT(data); + } + + // This cannot happen, mNALLengthSize springs to life by adding 1 to + // a 2-bit integer. + CHECK(!"Should not be here."); + + return 0; +} + +status_t MPEG4Source::read( + MediaBufferBase **out, const ReadOptions *options) { + Mutex::Autolock autoLock(mLock); + + CHECK(mStarted); + + if (options != nullptr && options->getNonBlocking() && !mGroup->has_buffers()) { + *out = nullptr; + return WOULD_BLOCK; + } + + if (mFirstMoofOffset > 0) { + return fragmentedRead(out, options); + } + + *out = NULL; + + int64_t targetSampleTimeUs = -1; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (options && options->getSeekTo(&seekTimeUs, &mode)) { + if (mIsHeif) { + CHECK(mSampleTable == NULL); + CHECK(mItemTable != NULL); + int32_t imageIndex; + if (!mFormat.findInt32(kKeyTrackID, &imageIndex)) { + return ERROR_MALFORMED; + } + + status_t err; + if (seekTimeUs >= 0) { + err = mItemTable->findImageItem(imageIndex, &mCurrentSampleIndex); + } else { + err = mItemTable->findThumbnailItem(imageIndex, &mCurrentSampleIndex); + } + if (err != OK) { + return err; + } + } else { + uint32_t findFlags = 0; + switch (mode) { + case ReadOptions::SEEK_PREVIOUS_SYNC: + findFlags = SampleTable::kFlagBefore; + break; + case ReadOptions::SEEK_NEXT_SYNC: + findFlags = SampleTable::kFlagAfter; + break; + case ReadOptions::SEEK_CLOSEST_SYNC: + case ReadOptions::SEEK_CLOSEST: + findFlags = SampleTable::kFlagClosest; + break; + case ReadOptions::SEEK_FRAME_INDEX: + findFlags = SampleTable::kFlagFrameIndex; + break; + default: + CHECK(!"Should not be here."); + break; + } + + uint32_t sampleIndex; + status_t err = mSampleTable->findSampleAtTime( + seekTimeUs, 1000000, mTimescale, + &sampleIndex, findFlags); + + if (mode == ReadOptions::SEEK_CLOSEST + || mode == ReadOptions::SEEK_FRAME_INDEX) { + // We found the closest sample already, now we want the sync + // sample preceding it (or the sample itself of course), even + // if the subsequent sync sample is closer. + findFlags = SampleTable::kFlagBefore; + } + + uint32_t syncSampleIndex; + if (err == OK) { + err = mSampleTable->findSyncSampleNear( + sampleIndex, &syncSampleIndex, findFlags); + } + + uint32_t sampleTime; + if (err == OK) { + err = mSampleTable->getMetaDataForSample( + sampleIndex, NULL, NULL, &sampleTime); + } + + if (err != OK) { + if (err == ERROR_OUT_OF_RANGE) { + // An attempt to seek past the end of the stream would + // normally cause this ERROR_OUT_OF_RANGE error. Propagating + // this all the way to the MediaPlayer would cause abnormal + // termination. Legacy behaviour appears to be to behave as if + // we had seeked to the end of stream, ending normally. + err = ERROR_END_OF_STREAM; + } + ALOGV("end of stream"); + return err; + } + + if (mode == ReadOptions::SEEK_CLOSEST + || mode == ReadOptions::SEEK_FRAME_INDEX) { + targetSampleTimeUs = (sampleTime * 1000000ll) / mTimescale; + } + +#if 0 + uint32_t syncSampleTime; + CHECK_EQ(OK, mSampleTable->getMetaDataForSample( + syncSampleIndex, NULL, NULL, &syncSampleTime)); + + ALOGI("seek to time %lld us => sample at time %lld us, " + "sync sample at time %lld us", + seekTimeUs, + sampleTime * 1000000ll / mTimescale, + syncSampleTime * 1000000ll / mTimescale); +#endif + + mCurrentSampleIndex = syncSampleIndex; + } + + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + + // fall through + } + + off64_t offset = 0; + size_t size = 0; + uint32_t cts, stts; + bool isSyncSample; + bool newBuffer = false; + if (mBuffer == NULL) { + newBuffer = true; + + status_t err; + if (!mIsHeif) { + err = mSampleTable->getMetaDataForSample( + mCurrentSampleIndex, &offset, &size, &cts, &isSyncSample, &stts); + } else { + err = mItemTable->getImageOffsetAndSize( + options && options->getSeekTo(&seekTimeUs, &mode) ? + &mCurrentSampleIndex : NULL, &offset, &size); + + cts = stts = 0; + isSyncSample = 0; + ALOGV("image offset %lld, size %zu", (long long)offset, size); + } + + if (err != OK) { + return err; + } + + err = mGroup->acquire_buffer(&mBuffer); + + if (err != OK) { + CHECK(mBuffer == NULL); + return err; + } + if (size > mBuffer->size()) { + ALOGE("buffer too small: %zu > %zu", size, mBuffer->size()); + mBuffer->release(); + mBuffer = NULL; + return ERROR_BUFFER_TOO_SMALL; + } + } + + if ((!mIsAVC && !mIsHEVC) || mWantsNALFragments) { + if (newBuffer) { + ssize_t num_bytes_read = + mDataSource->readAt(offset, (uint8_t *)mBuffer->data(), size); + + if (num_bytes_read < (ssize_t)size) { + mBuffer->release(); + mBuffer = NULL; + + return ERROR_IO; + } + + CHECK(mBuffer != NULL); + mBuffer->set_range(0, size); + mBuffer->meta_data().clear(); + mBuffer->meta_data().setInt64( + kKeyTime, ((int64_t)cts * 1000000) / mTimescale); + mBuffer->meta_data().setInt64( + kKeyDuration, ((int64_t)stts * 1000000) / mTimescale); + + if (targetSampleTimeUs >= 0) { + mBuffer->meta_data().setInt64( + kKeyTargetTime, targetSampleTimeUs); + } + + if (isSyncSample) { + mBuffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + } + + ++mCurrentSampleIndex; + } + + if (!mIsAVC && !mIsHEVC) { + *out = mBuffer; + mBuffer = NULL; + + return OK; + } + + // Each NAL unit is split up into its constituent fragments and + // each one of them returned in its own buffer. + + CHECK(mBuffer->range_length() >= mNALLengthSize); + + const uint8_t *src = + (const uint8_t *)mBuffer->data() + mBuffer->range_offset(); + + size_t nal_size = parseNALSize(src); + if (mNALLengthSize > SIZE_MAX - nal_size) { + ALOGE("b/24441553, b/24445122"); + } + if (mBuffer->range_length() - mNALLengthSize < nal_size) { + ALOGE("incomplete NAL unit."); + + mBuffer->release(); + mBuffer = NULL; + + return ERROR_MALFORMED; + } + + MediaBufferBase *clone = mBuffer->clone(); + CHECK(clone != NULL); + clone->set_range(mBuffer->range_offset() + mNALLengthSize, nal_size); + + CHECK(mBuffer != NULL); + mBuffer->set_range( + mBuffer->range_offset() + mNALLengthSize + nal_size, + mBuffer->range_length() - mNALLengthSize - nal_size); + + if (mBuffer->range_length() == 0) { + mBuffer->release(); + mBuffer = NULL; + } + + *out = clone; + + return OK; + } else { + // Whole NAL units are returned but each fragment is prefixed by + // the start code (0x00 00 00 01). + ssize_t num_bytes_read = 0; + int32_t drm = 0; + bool usesDRM = (mFormat.findInt32(kKeyIsDRM, &drm) && drm != 0); + if (usesDRM) { + num_bytes_read = + mDataSource->readAt(offset, (uint8_t*)mBuffer->data(), size); + } else { + num_bytes_read = mDataSource->readAt(offset, mSrcBuffer, size); + } + + if (num_bytes_read < (ssize_t)size) { + mBuffer->release(); + mBuffer = NULL; + + return ERROR_IO; + } + + if (usesDRM) { + CHECK(mBuffer != NULL); + mBuffer->set_range(0, size); + + } else { + uint8_t *dstData = (uint8_t *)mBuffer->data(); + size_t srcOffset = 0; + size_t dstOffset = 0; + + while (srcOffset < size) { + bool isMalFormed = !isInRange((size_t)0u, size, srcOffset, mNALLengthSize); + size_t nalLength = 0; + if (!isMalFormed) { + nalLength = parseNALSize(&mSrcBuffer[srcOffset]); + srcOffset += mNALLengthSize; + isMalFormed = !isInRange((size_t)0u, size, srcOffset, nalLength); + } + + if (isMalFormed) { + ALOGE("Video is malformed"); + mBuffer->release(); + mBuffer = NULL; + return ERROR_MALFORMED; + } + + if (nalLength == 0) { + continue; + } + + if (dstOffset > SIZE_MAX - 4 || + dstOffset + 4 > SIZE_MAX - nalLength || + dstOffset + 4 + nalLength > mBuffer->size()) { + ALOGE("b/27208621 : %zu %zu", dstOffset, mBuffer->size()); + android_errorWriteLog(0x534e4554, "27208621"); + mBuffer->release(); + mBuffer = NULL; + return ERROR_MALFORMED; + } + + dstData[dstOffset++] = 0; + dstData[dstOffset++] = 0; + dstData[dstOffset++] = 0; + dstData[dstOffset++] = 1; + memcpy(&dstData[dstOffset], &mSrcBuffer[srcOffset], nalLength); + srcOffset += nalLength; + dstOffset += nalLength; + } + CHECK_EQ(srcOffset, size); + CHECK(mBuffer != NULL); + mBuffer->set_range(0, dstOffset); + } + + mBuffer->meta_data().clear(); + mBuffer->meta_data().setInt64( + kKeyTime, ((int64_t)cts * 1000000) / mTimescale); + mBuffer->meta_data().setInt64( + kKeyDuration, ((int64_t)stts * 1000000) / mTimescale); + + if (targetSampleTimeUs >= 0) { + mBuffer->meta_data().setInt64( + kKeyTargetTime, targetSampleTimeUs); + } + + if (mIsAVC) { + uint32_t layerId = FindAVCLayerId( + (const uint8_t *)mBuffer->data(), mBuffer->range_length()); + mBuffer->meta_data().setInt32(kKeyTemporalLayerId, layerId); + } + + if (isSyncSample) { + mBuffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + } + + ++mCurrentSampleIndex; + + *out = mBuffer; + mBuffer = NULL; + + return OK; + } +} + +status_t MPEG4Source::fragmentedRead( + MediaBufferBase **out, const ReadOptions *options) { + + ALOGV("MPEG4Source::fragmentedRead"); + + CHECK(mStarted); + + *out = NULL; + + int64_t targetSampleTimeUs = -1; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (options && options->getSeekTo(&seekTimeUs, &mode)) { + + int numSidxEntries = mSegments.size(); + if (numSidxEntries != 0) { + int64_t totalTime = 0; + off64_t totalOffset = mFirstMoofOffset; + for (int i = 0; i < numSidxEntries; i++) { + const SidxEntry *se = &mSegments[i]; + if (totalTime + se->mDurationUs > seekTimeUs) { + // The requested time is somewhere in this segment + if ((mode == ReadOptions::SEEK_NEXT_SYNC && seekTimeUs > totalTime) || + (mode == ReadOptions::SEEK_CLOSEST_SYNC && + (seekTimeUs - totalTime) > (totalTime + se->mDurationUs - seekTimeUs))) { + // requested next sync, or closest sync and it was closer to the end of + // this segment + totalTime += se->mDurationUs; + totalOffset += se->mSize; + } + break; + } + totalTime += se->mDurationUs; + totalOffset += se->mSize; + } + mCurrentMoofOffset = totalOffset; + mNextMoofOffset = -1; + mCurrentSamples.clear(); + mCurrentSampleIndex = 0; + status_t err = parseChunk(&totalOffset); + if (err != OK) { + return err; + } + mCurrentTime = totalTime * mTimescale / 1000000ll; + } else { + // without sidx boxes, we can only seek to 0 + mCurrentMoofOffset = mFirstMoofOffset; + mNextMoofOffset = -1; + mCurrentSamples.clear(); + mCurrentSampleIndex = 0; + off64_t tmp = mCurrentMoofOffset; + status_t err = parseChunk(&tmp); + if (err != OK) { + return err; + } + mCurrentTime = 0; + } + + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + + // fall through + } + + off64_t offset = 0; + size_t size = 0; + uint32_t cts = 0; + bool isSyncSample = false; + bool newBuffer = false; + if (mBuffer == NULL) { + newBuffer = true; + + if (mCurrentSampleIndex >= mCurrentSamples.size()) { + // move to next fragment if there is one + if (mNextMoofOffset <= mCurrentMoofOffset) { + return ERROR_END_OF_STREAM; + } + off64_t nextMoof = mNextMoofOffset; + mCurrentMoofOffset = nextMoof; + mCurrentSamples.clear(); + mCurrentSampleIndex = 0; + status_t err = parseChunk(&nextMoof); + if (err != OK) { + return err; + } + if (mCurrentSampleIndex >= mCurrentSamples.size()) { + return ERROR_END_OF_STREAM; + } + } + + const Sample *smpl = &mCurrentSamples[mCurrentSampleIndex]; + offset = smpl->offset; + size = smpl->size; + cts = mCurrentTime + smpl->compositionOffset; + mCurrentTime += smpl->duration; + isSyncSample = (mCurrentSampleIndex == 0); // XXX + + status_t err = mGroup->acquire_buffer(&mBuffer); + + if (err != OK) { + CHECK(mBuffer == NULL); + ALOGV("acquire_buffer returned %d", err); + return err; + } + if (size > mBuffer->size()) { + ALOGE("buffer too small: %zu > %zu", size, mBuffer->size()); + mBuffer->release(); + mBuffer = NULL; + return ERROR_BUFFER_TOO_SMALL; + } + } + + const Sample *smpl = &mCurrentSamples[mCurrentSampleIndex]; + MetaDataBase &bufmeta = mBuffer->meta_data(); + bufmeta.clear(); + if (smpl->encryptedsizes.size()) { + // store clear/encrypted lengths in metadata + bufmeta.setData(kKeyPlainSizes, 0, + smpl->clearsizes.array(), smpl->clearsizes.size() * 4); + bufmeta.setData(kKeyEncryptedSizes, 0, + smpl->encryptedsizes.array(), smpl->encryptedsizes.size() * 4); + bufmeta.setInt32(kKeyCryptoDefaultIVSize, mDefaultIVSize); + bufmeta.setInt32(kKeyCryptoMode, mCryptoMode); + bufmeta.setData(kKeyCryptoKey, 0, mCryptoKey, 16); + bufmeta.setInt32(kKeyEncryptedByteBlock, mDefaultEncryptedByteBlock); + bufmeta.setInt32(kKeySkipByteBlock, mDefaultSkipByteBlock); + + uint32_t type = 0; + const void *iv = NULL; + size_t ivlength = 0; + if (!mFormat.findData( + kKeyCryptoIV, &type, &iv, &ivlength)) { + iv = smpl->iv; + ivlength = 16; // use 16 or the actual size? + } + bufmeta.setData(kKeyCryptoIV, 0, iv, ivlength); + + } + + if ((!mIsAVC && !mIsHEVC)|| mWantsNALFragments) { + if (newBuffer) { + if (!isInRange((size_t)0u, mBuffer->size(), size)) { + mBuffer->release(); + mBuffer = NULL; + + ALOGE("fragmentedRead ERROR_MALFORMED size %zu", size); + return ERROR_MALFORMED; + } + + ssize_t num_bytes_read = + mDataSource->readAt(offset, (uint8_t *)mBuffer->data(), size); + + if (num_bytes_read < (ssize_t)size) { + mBuffer->release(); + mBuffer = NULL; + + ALOGE("i/o error"); + return ERROR_IO; + } + + CHECK(mBuffer != NULL); + mBuffer->set_range(0, size); + mBuffer->meta_data().setInt64( + kKeyTime, ((int64_t)cts * 1000000) / mTimescale); + mBuffer->meta_data().setInt64( + kKeyDuration, ((int64_t)smpl->duration * 1000000) / mTimescale); + + if (targetSampleTimeUs >= 0) { + mBuffer->meta_data().setInt64( + kKeyTargetTime, targetSampleTimeUs); + } + + if (mIsAVC) { + uint32_t layerId = FindAVCLayerId( + (const uint8_t *)mBuffer->data(), mBuffer->range_length()); + mBuffer->meta_data().setInt32(kKeyTemporalLayerId, layerId); + } + + if (isSyncSample) { + mBuffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + } + + ++mCurrentSampleIndex; + } + + if (!mIsAVC && !mIsHEVC) { + *out = mBuffer; + mBuffer = NULL; + + return OK; + } + + // Each NAL unit is split up into its constituent fragments and + // each one of them returned in its own buffer. + + CHECK(mBuffer->range_length() >= mNALLengthSize); + + const uint8_t *src = + (const uint8_t *)mBuffer->data() + mBuffer->range_offset(); + + size_t nal_size = parseNALSize(src); + if (mNALLengthSize > SIZE_MAX - nal_size) { + ALOGE("b/24441553, b/24445122"); + } + + if (mBuffer->range_length() - mNALLengthSize < nal_size) { + ALOGE("incomplete NAL unit."); + + mBuffer->release(); + mBuffer = NULL; + + return ERROR_MALFORMED; + } + + MediaBufferBase *clone = mBuffer->clone(); + CHECK(clone != NULL); + clone->set_range(mBuffer->range_offset() + mNALLengthSize, nal_size); + + CHECK(mBuffer != NULL); + mBuffer->set_range( + mBuffer->range_offset() + mNALLengthSize + nal_size, + mBuffer->range_length() - mNALLengthSize - nal_size); + + if (mBuffer->range_length() == 0) { + mBuffer->release(); + mBuffer = NULL; + } + + *out = clone; + + return OK; + } else { + ALOGV("whole NAL"); + // Whole NAL units are returned but each fragment is prefixed by + // the start code (0x00 00 00 01). + ssize_t num_bytes_read = 0; + int32_t drm = 0; + bool usesDRM = (mFormat.findInt32(kKeyIsDRM, &drm) && drm != 0); + void *data = NULL; + bool isMalFormed = false; + if (usesDRM) { + if (mBuffer == NULL || !isInRange((size_t)0u, mBuffer->size(), size)) { + isMalFormed = true; + } else { + data = mBuffer->data(); + } + } else { + int32_t max_size; + if (!mFormat.findInt32(kKeyMaxInputSize, &max_size) + || !isInRange((size_t)0u, (size_t)max_size, size)) { + isMalFormed = true; + } else { + data = mSrcBuffer; + } + } + + if (isMalFormed || data == NULL) { + ALOGE("isMalFormed size %zu", size); + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + return ERROR_MALFORMED; + } + num_bytes_read = mDataSource->readAt(offset, data, size); + + if (num_bytes_read < (ssize_t)size) { + mBuffer->release(); + mBuffer = NULL; + + ALOGE("i/o error"); + return ERROR_IO; + } + + if (usesDRM) { + CHECK(mBuffer != NULL); + mBuffer->set_range(0, size); + + } else { + uint8_t *dstData = (uint8_t *)mBuffer->data(); + size_t srcOffset = 0; + size_t dstOffset = 0; + + while (srcOffset < size) { + isMalFormed = !isInRange((size_t)0u, size, srcOffset, mNALLengthSize); + size_t nalLength = 0; + if (!isMalFormed) { + nalLength = parseNALSize(&mSrcBuffer[srcOffset]); + srcOffset += mNALLengthSize; + isMalFormed = !isInRange((size_t)0u, size, srcOffset, nalLength) + || !isInRange((size_t)0u, mBuffer->size(), dstOffset, (size_t)4u) + || !isInRange((size_t)0u, mBuffer->size(), dstOffset + 4, nalLength); + } + + if (isMalFormed) { + ALOGE("Video is malformed; nalLength %zu", nalLength); + mBuffer->release(); + mBuffer = NULL; + return ERROR_MALFORMED; + } + + if (nalLength == 0) { + continue; + } + + if (dstOffset > SIZE_MAX - 4 || + dstOffset + 4 > SIZE_MAX - nalLength || + dstOffset + 4 + nalLength > mBuffer->size()) { + ALOGE("b/26365349 : %zu %zu", dstOffset, mBuffer->size()); + android_errorWriteLog(0x534e4554, "26365349"); + mBuffer->release(); + mBuffer = NULL; + return ERROR_MALFORMED; + } + + dstData[dstOffset++] = 0; + dstData[dstOffset++] = 0; + dstData[dstOffset++] = 0; + dstData[dstOffset++] = 1; + memcpy(&dstData[dstOffset], &mSrcBuffer[srcOffset], nalLength); + srcOffset += nalLength; + dstOffset += nalLength; + } + CHECK_EQ(srcOffset, size); + CHECK(mBuffer != NULL); + mBuffer->set_range(0, dstOffset); + } + + mBuffer->meta_data().setInt64( + kKeyTime, ((int64_t)cts * 1000000) / mTimescale); + mBuffer->meta_data().setInt64( + kKeyDuration, ((int64_t)smpl->duration * 1000000) / mTimescale); + + if (targetSampleTimeUs >= 0) { + mBuffer->meta_data().setInt64( + kKeyTargetTime, targetSampleTimeUs); + } + + if (isSyncSample) { + mBuffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + } + + ++mCurrentSampleIndex; + + *out = mBuffer; + mBuffer = NULL; + + return OK; + } +} + +MPEG4Extractor::Track *MPEG4Extractor::findTrackByMimePrefix( + const char *mimePrefix) { + for (Track *track = mFirstTrack; track != NULL; track = track->next) { + const char *mime; + if (track->meta.findCString(kKeyMIMEType, &mime) + && !strncasecmp(mime, mimePrefix, strlen(mimePrefix))) { + return track; + } + } + + return NULL; +} + +static bool LegacySniffMPEG4(DataSourceBase *source, float *confidence) { + uint8_t header[8]; + + ssize_t n = source->readAt(4, header, sizeof(header)); + if (n < (ssize_t)sizeof(header)) { + return false; + } + + if (!memcmp(header, "ftyp3gp", 7) || !memcmp(header, "ftypmp42", 8) + || !memcmp(header, "ftyp3gr6", 8) || !memcmp(header, "ftyp3gs6", 8) + || !memcmp(header, "ftyp3ge6", 8) || !memcmp(header, "ftyp3gg6", 8) + || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8) + || !memcmp(header, "ftypM4A ", 8) || !memcmp(header, "ftypf4v ", 8) + || !memcmp(header, "ftypkddi", 8) || !memcmp(header, "ftypM4VP", 8) + || !memcmp(header, "ftypmif1", 8) || !memcmp(header, "ftypheic", 8) + || !memcmp(header, "ftypmsf1", 8) || !memcmp(header, "ftyphevc", 8)) { + *confidence = 0.4; + + return true; + } + + return false; +} + +static bool isCompatibleBrand(uint32_t fourcc) { + static const uint32_t kCompatibleBrands[] = { + FOURCC('i', 's', 'o', 'm'), + FOURCC('i', 's', 'o', '2'), + FOURCC('a', 'v', 'c', '1'), + FOURCC('h', 'v', 'c', '1'), + FOURCC('h', 'e', 'v', '1'), + FOURCC('3', 'g', 'p', '4'), + FOURCC('m', 'p', '4', '1'), + FOURCC('m', 'p', '4', '2'), + FOURCC('d', 'a', 's', 'h'), + + // Won't promise that the following file types can be played. + // Just give these file types a chance. + FOURCC('q', 't', ' ', ' '), // Apple's QuickTime + FOURCC('M', 'S', 'N', 'V'), // Sony's PSP + + FOURCC('3', 'g', '2', 'a'), // 3GPP2 + FOURCC('3', 'g', '2', 'b'), + FOURCC('m', 'i', 'f', '1'), // HEIF image + FOURCC('h', 'e', 'i', 'c'), // HEIF image + FOURCC('m', 's', 'f', '1'), // HEIF image sequence + FOURCC('h', 'e', 'v', 'c'), // HEIF image sequence + }; + + for (size_t i = 0; + i < sizeof(kCompatibleBrands) / sizeof(kCompatibleBrands[0]); + ++i) { + if (kCompatibleBrands[i] == fourcc) { + return true; + } + } + + return false; +} + +// Attempt to actually parse the 'ftyp' atom and determine if a suitable +// compatible brand is present. +// Also try to identify where this file's metadata ends +// (end of the 'moov' atom) and report it to the caller as part of +// the metadata. +static bool BetterSniffMPEG4(DataSourceBase *source, float *confidence) { + // We scan up to 128 bytes to identify this file as an MP4. + static const off64_t kMaxScanOffset = 128ll; + + off64_t offset = 0ll; + bool foundGoodFileType = false; + off64_t moovAtomEndOffset = -1ll; + bool done = false; + + while (!done && offset < kMaxScanOffset) { + uint32_t hdr[2]; + if (source->readAt(offset, hdr, 8) < 8) { + return false; + } + + uint64_t chunkSize = ntohl(hdr[0]); + uint32_t chunkType = ntohl(hdr[1]); + off64_t chunkDataOffset = offset + 8; + + if (chunkSize == 1) { + if (source->readAt(offset + 8, &chunkSize, 8) < 8) { + return false; + } + + chunkSize = ntoh64(chunkSize); + chunkDataOffset += 8; + + if (chunkSize < 16) { + // The smallest valid chunk is 16 bytes long in this case. + return false; + } + + } else if (chunkSize < 8) { + // The smallest valid chunk is 8 bytes long. + return false; + } + + // (data_offset - offset) is either 8 or 16 + off64_t chunkDataSize = chunkSize - (chunkDataOffset - offset); + if (chunkDataSize < 0) { + ALOGE("b/23540914"); + return false; + } + + char chunkstring[5]; + MakeFourCCString(chunkType, chunkstring); + ALOGV("saw chunk type %s, size %" PRIu64 " @ %lld", chunkstring, chunkSize, (long long)offset); + switch (chunkType) { + case FOURCC('f', 't', 'y', 'p'): + { + if (chunkDataSize < 8) { + return false; + } + + uint32_t numCompatibleBrands = (chunkDataSize - 8) / 4; + for (size_t i = 0; i < numCompatibleBrands + 2; ++i) { + if (i == 1) { + // Skip this index, it refers to the minorVersion, + // not a brand. + continue; + } + + uint32_t brand; + if (source->readAt( + chunkDataOffset + 4 * i, &brand, 4) < 4) { + return false; + } + + brand = ntohl(brand); + + if (isCompatibleBrand(brand)) { + foundGoodFileType = true; + break; + } + } + + if (!foundGoodFileType) { + return false; + } + + break; + } + + case FOURCC('m', 'o', 'o', 'v'): + { + moovAtomEndOffset = offset + chunkSize; + + done = true; + break; + } + + default: + break; + } + + offset += chunkSize; + } + + if (!foundGoodFileType) { + return false; + } + + *confidence = 0.4f; + + return true; +} + +static MediaExtractor* CreateExtractor(DataSourceBase *source, void *) { + return new MPEG4Extractor(source); +} + +static MediaExtractor::CreatorFunc Sniff( + DataSourceBase *source, float *confidence, void **, + MediaExtractor::FreeMetaFunc *) { + if (BetterSniffMPEG4(source, confidence)) { + return CreateExtractor; + } + + if (LegacySniffMPEG4(source, confidence)) { + ALOGW("Identified supported mpeg4 through LegacySniffMPEG4."); + return CreateExtractor; + } + + return NULL; +} + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("27575c67-4417-4c54-8d3d-8e626985a164"), + 1, // version + "MP4 Extractor", + Sniff + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/mp4/MPEG4Extractor.h b/media/extractors/mp4/MPEG4Extractor.h new file mode 100644 index 0000000..3ea0963 --- /dev/null +++ b/media/extractors/mp4/MPEG4Extractor.h
@@ -0,0 +1,155 @@ +/* + * Copyright (C) 2009 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 MPEG4_EXTRACTOR_H_ + +#define MPEG4_EXTRACTOR_H_ + +#include <arpa/inet.h> + +#include <media/DataSourceBase.h> +#include <media/MediaExtractor.h> +#include <media/stagefright/MetaDataBase.h> +#include <media/stagefright/foundation/AString.h> +#include <utils/KeyedVector.h> +#include <utils/List.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace android { +struct AMessage; +class DataSourceBase; +struct CachedRangedDataSource; +class SampleTable; +class String8; +namespace heif { +class ItemTable; +} +using heif::ItemTable; + +struct SidxEntry { + size_t mSize; + uint32_t mDurationUs; +}; + +struct Trex { + uint32_t track_ID; + uint32_t default_sample_description_index; + uint32_t default_sample_duration; + uint32_t default_sample_size; + uint32_t default_sample_flags; +}; + +class MPEG4Extractor : public MediaExtractor { +public: + explicit MPEG4Extractor(DataSourceBase *source, const char *mime = NULL); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + virtual uint32_t flags() const; + virtual const char * name() { return "MPEG4Extractor"; } + +protected: + virtual ~MPEG4Extractor(); + +private: + + struct PsshInfo { + uint8_t uuid[16]; + uint32_t datalen; + uint8_t *data; + }; + struct Track { + Track *next; + MetaDataBase meta; + uint32_t timescale; + sp<SampleTable> sampleTable; + bool includes_expensive_metadata; + bool skipTrack; + bool has_elst; + int64_t elst_media_time; + uint64_t elst_segment_duration; + bool subsample_encryption; + }; + + Vector<SidxEntry> mSidxEntries; + off64_t mMoofOffset; + bool mMoofFound; + bool mMdatFound; + + Vector<PsshInfo> mPssh; + + Vector<Trex> mTrex; + + DataSourceBase *mDataSource; + CachedRangedDataSource *mCachedSource; + status_t mInitCheck; + uint32_t mHeaderTimescale; + bool mIsQT; + bool mIsHeif; + bool mHasMoovBox; + bool mPreferHeif; + + Track *mFirstTrack, *mLastTrack; + + MetaDataBase mFileMetaData; + + Vector<uint32_t> mPath; + String8 mLastCommentMean; + String8 mLastCommentName; + String8 mLastCommentData; + + KeyedVector<uint32_t, AString> mMetaKeyMap; + + status_t readMetaData(); + 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 parse3GPPMetaData(off64_t offset, size_t size, int depth); + void parseID3v2MetaData(off64_t offset); + status_t parseQTMetaKey(off64_t data_offset, size_t data_size); + status_t parseQTMetaVal(int32_t keyId, off64_t data_offset, size_t data_size); + + status_t updateAudioTrackInfoFromESDS_MPEG4Audio( + const void *esds_data, size_t esds_size); + + static status_t verifyTrack(Track *track); + + sp<ItemTable> mItemTable; + + status_t parseTrackHeader(off64_t data_offset, off64_t data_size); + + status_t parseSegmentIndex(off64_t data_offset, size_t data_size); + + Track *findTrackByMimePrefix(const char *mimePrefix); + + status_t parseAC3SampleEntry(off64_t offset); + status_t parseAC3SpecificBox(off64_t offset, uint16_t sampleRate); + + MPEG4Extractor(const MPEG4Extractor &); + MPEG4Extractor &operator=(const MPEG4Extractor &); +}; + +bool SniffMPEG4( + DataSourceBase *source, String8 *mimeType, float *confidence, + sp<AMessage> *); + +} // namespace android + +#endif // MPEG4_EXTRACTOR_H_
diff --git a/media/libstagefright/matroska/NOTICE b/media/extractors/mp4/NOTICE similarity index 100% copy from media/libstagefright/matroska/NOTICE copy to media/extractors/mp4/NOTICE
diff --git a/media/extractors/mp4/SampleIterator.cpp b/media/extractors/mp4/SampleIterator.cpp new file mode 100644 index 0000000..93ee7c6 --- /dev/null +++ b/media/extractors/mp4/SampleIterator.cpp
@@ -0,0 +1,352 @@ +/* + * 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_TAG "SampleIterator" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include "SampleIterator.h" + +#include <arpa/inet.h> + +#include <media/DataSourceBase.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ByteUtils.h> + +#include "SampleTable.h" + +namespace android { + +SampleIterator::SampleIterator(SampleTable *table) + : mTable(table), + mInitialized(false), + mTimeToSampleIndex(0), + mTTSSampleIndex(0), + mTTSSampleTime(0), + mTTSCount(0), + mTTSDuration(0) { + reset(); +} + +void SampleIterator::reset() { + mSampleToChunkIndex = 0; + mFirstChunk = 0; + mFirstChunkSampleIndex = 0; + mStopChunk = 0; + mStopChunkSampleIndex = 0; + mSamplesPerChunk = 0; + mChunkDesc = 0; +} + +status_t SampleIterator::seekTo(uint32_t sampleIndex) { + ALOGV("seekTo(%d)", sampleIndex); + + if (sampleIndex >= mTable->mNumSampleSizes) { + return ERROR_END_OF_STREAM; + } + + if (mTable->mSampleToChunkOffset < 0 + || mTable->mChunkOffsetOffset < 0 + || mTable->mSampleSizeOffset < 0 + || mTable->mTimeToSampleCount == 0) { + + return ERROR_MALFORMED; + } + + if (mInitialized && mCurrentSampleIndex == sampleIndex) { + return OK; + } + + if (!mInitialized || sampleIndex < mFirstChunkSampleIndex) { + reset(); + } + + if (sampleIndex >= mStopChunkSampleIndex) { + status_t err; + if ((err = findChunkRange(sampleIndex)) != OK) { + ALOGE("findChunkRange failed"); + return err; + } + } + + CHECK(sampleIndex < mStopChunkSampleIndex); + + if (mSamplesPerChunk == 0) { + ALOGE("b/22802344"); + return ERROR_MALFORMED; + } + + uint32_t chunk = + (sampleIndex - mFirstChunkSampleIndex) / mSamplesPerChunk + + mFirstChunk; + + if (!mInitialized || chunk != mCurrentChunkIndex) { + status_t err; + if ((err = getChunkOffset(chunk, &mCurrentChunkOffset)) != OK) { + ALOGE("getChunkOffset return error"); + return err; + } + + mCurrentChunkSampleSizes.clear(); + + uint32_t firstChunkSampleIndex = + mFirstChunkSampleIndex + + mSamplesPerChunk * (chunk - mFirstChunk); + + for (uint32_t i = 0; i < mSamplesPerChunk; ++i) { + size_t sampleSize; + if ((err = getSampleSizeDirect( + firstChunkSampleIndex + i, &sampleSize)) != OK) { + ALOGE("getSampleSizeDirect return error"); + mCurrentChunkSampleSizes.clear(); + return err; + } + + mCurrentChunkSampleSizes.push(sampleSize); + } + + mCurrentChunkIndex = chunk; + } + + uint32_t chunkRelativeSampleIndex = + (sampleIndex - mFirstChunkSampleIndex) % mSamplesPerChunk; + + mCurrentSampleOffset = mCurrentChunkOffset; + for (uint32_t i = 0; i < chunkRelativeSampleIndex; ++i) { + mCurrentSampleOffset += mCurrentChunkSampleSizes[i]; + } + + mCurrentSampleSize = mCurrentChunkSampleSizes[chunkRelativeSampleIndex]; + if (sampleIndex < mTTSSampleIndex) { + mTimeToSampleIndex = 0; + mTTSSampleIndex = 0; + mTTSSampleTime = 0; + mTTSCount = 0; + mTTSDuration = 0; + } + + status_t err; + if ((err = findSampleTimeAndDuration( + sampleIndex, &mCurrentSampleTime, &mCurrentSampleDuration)) != OK) { + ALOGE("findSampleTime return error"); + return err; + } + + mCurrentSampleIndex = sampleIndex; + + mInitialized = true; + + return OK; +} + +status_t SampleIterator::findChunkRange(uint32_t sampleIndex) { + CHECK(sampleIndex >= mFirstChunkSampleIndex); + + while (sampleIndex >= mStopChunkSampleIndex) { + if (mSampleToChunkIndex == mTable->mNumSampleToChunkOffsets) { + return ERROR_OUT_OF_RANGE; + } + + mFirstChunkSampleIndex = mStopChunkSampleIndex; + + const SampleTable::SampleToChunkEntry *entry = + &mTable->mSampleToChunkEntries[mSampleToChunkIndex]; + + mFirstChunk = entry->startChunk; + mSamplesPerChunk = entry->samplesPerChunk; + mChunkDesc = entry->chunkDesc; + + if (mSampleToChunkIndex + 1 < mTable->mNumSampleToChunkOffsets) { + mStopChunk = entry[1].startChunk; + + if (mSamplesPerChunk == 0 || mStopChunk < mFirstChunk || + (mStopChunk - mFirstChunk) > UINT32_MAX / mSamplesPerChunk || + ((mStopChunk - mFirstChunk) * mSamplesPerChunk > + UINT32_MAX - mFirstChunkSampleIndex)) { + + return ERROR_OUT_OF_RANGE; + } + mStopChunkSampleIndex = + mFirstChunkSampleIndex + + (mStopChunk - mFirstChunk) * mSamplesPerChunk; + } else { + mStopChunk = 0xffffffff; + mStopChunkSampleIndex = 0xffffffff; + } + + ++mSampleToChunkIndex; + } + + return OK; +} + +status_t SampleIterator::getChunkOffset(uint32_t chunk, off64_t *offset) { + *offset = 0; + + if (chunk >= mTable->mNumChunkOffsets) { + return ERROR_OUT_OF_RANGE; + } + + if (mTable->mChunkOffsetType == SampleTable::kChunkOffsetType32) { + uint32_t offset32; + + if (mTable->mDataSource->readAt( + mTable->mChunkOffsetOffset + 8 + 4 * chunk, + &offset32, + sizeof(offset32)) < (ssize_t)sizeof(offset32)) { + return ERROR_IO; + } + + *offset = ntohl(offset32); + } else { + CHECK_EQ(mTable->mChunkOffsetType, SampleTable::kChunkOffsetType64); + + uint64_t offset64; + if (mTable->mDataSource->readAt( + mTable->mChunkOffsetOffset + 8 + 8 * chunk, + &offset64, + sizeof(offset64)) < (ssize_t)sizeof(offset64)) { + return ERROR_IO; + } + + *offset = ntoh64(offset64); + } + + return OK; +} + +status_t SampleIterator::getSampleSizeDirect( + uint32_t sampleIndex, size_t *size) { + *size = 0; + + if (sampleIndex >= mTable->mNumSampleSizes) { + return ERROR_OUT_OF_RANGE; + } + + if (mTable->mDefaultSampleSize > 0) { + *size = mTable->mDefaultSampleSize; + return OK; + } + + switch (mTable->mSampleSizeFieldSize) { + case 32: + { + uint32_t x; + if (mTable->mDataSource->readAt( + mTable->mSampleSizeOffset + 12 + 4 * sampleIndex, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *size = ntohl(x); + break; + } + + case 16: + { + uint16_t x; + if (mTable->mDataSource->readAt( + mTable->mSampleSizeOffset + 12 + 2 * sampleIndex, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *size = ntohs(x); + break; + } + + case 8: + { + uint8_t x; + if (mTable->mDataSource->readAt( + mTable->mSampleSizeOffset + 12 + sampleIndex, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *size = x; + break; + } + + default: + { + CHECK_EQ(mTable->mSampleSizeFieldSize, 4u); + + uint8_t x; + if (mTable->mDataSource->readAt( + mTable->mSampleSizeOffset + 12 + sampleIndex / 2, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *size = (sampleIndex & 1) ? x & 0x0f : x >> 4; + break; + } + } + + return OK; +} + +status_t SampleIterator::findSampleTimeAndDuration( + uint32_t sampleIndex, uint32_t *time, uint32_t *duration) { + if (sampleIndex >= mTable->mNumSampleSizes) { + return ERROR_OUT_OF_RANGE; + } + + while (true) { + if (mTTSSampleIndex > UINT32_MAX - mTTSCount) { + return ERROR_OUT_OF_RANGE; + } + if(sampleIndex < mTTSSampleIndex + mTTSCount) { + break; + } + if (mTimeToSampleIndex == mTable->mTimeToSampleCount || + (mTTSDuration != 0 && mTTSCount > UINT32_MAX / mTTSDuration) || + mTTSSampleTime > UINT32_MAX - (mTTSCount * mTTSDuration)) { + return ERROR_OUT_OF_RANGE; + } + + mTTSSampleIndex += mTTSCount; + mTTSSampleTime += mTTSCount * mTTSDuration; + + mTTSCount = mTable->mTimeToSample[2 * mTimeToSampleIndex]; + mTTSDuration = mTable->mTimeToSample[2 * mTimeToSampleIndex + 1]; + + ++mTimeToSampleIndex; + } + + *time = mTTSSampleTime + mTTSDuration * (sampleIndex - mTTSSampleIndex); + + int32_t offset = mTable->getCompositionTimeOffset(sampleIndex); + if ((offset < 0 && *time < (offset == INT32_MIN ? + INT32_MAX : uint32_t(-offset))) || + (offset > 0 && *time > UINT32_MAX - offset)) { + ALOGE("%u + %d would overflow", *time, offset); + return ERROR_OUT_OF_RANGE; + } + if (offset > 0) { + *time += offset; + } else { + *time -= (offset == INT32_MIN ? INT32_MAX : (-offset)); + } + + *duration = mTTSDuration; + + return OK; +} + +} // namespace android +
diff --git a/media/libstagefright/include/SampleIterator.h b/media/extractors/mp4/SampleIterator.h similarity index 100% rename from media/libstagefright/include/SampleIterator.h rename to media/extractors/mp4/SampleIterator.h
diff --git a/media/extractors/mp4/SampleTable.cpp b/media/extractors/mp4/SampleTable.cpp new file mode 100644 index 0000000..81c353e --- /dev/null +++ b/media/extractors/mp4/SampleTable.cpp
@@ -0,0 +1,1008 @@ +/* + * Copyright (C) 2009 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 "SampleTable" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include <limits> + +#include "SampleTable.h" +#include "SampleIterator.h" + +#include <arpa/inet.h> + +#include <media/DataSourceBase.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ByteUtils.h> + +/* TODO: remove after being merged into other branches */ +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +namespace android { + +// static +const uint32_t SampleTable::kChunkOffsetType32 = FOURCC('s', 't', 'c', 'o'); +// static +const uint32_t SampleTable::kChunkOffsetType64 = FOURCC('c', 'o', '6', '4'); +// static +const uint32_t SampleTable::kSampleSizeType32 = FOURCC('s', 't', 's', 'z'); +// static +const uint32_t SampleTable::kSampleSizeTypeCompact = FOURCC('s', 't', 'z', '2'); + +//////////////////////////////////////////////////////////////////////////////// + +const off64_t kMaxOffset = std::numeric_limits<off64_t>::max(); + +struct SampleTable::CompositionDeltaLookup { + CompositionDeltaLookup(); + + void setEntries( + const int32_t *deltaEntries, size_t numDeltaEntries); + + int32_t getCompositionTimeOffset(uint32_t sampleIndex); + +private: + Mutex mLock; + + const int32_t *mDeltaEntries; + size_t mNumDeltaEntries; + + size_t mCurrentDeltaEntry; + size_t mCurrentEntrySampleIndex; + + DISALLOW_EVIL_CONSTRUCTORS(CompositionDeltaLookup); +}; + +SampleTable::CompositionDeltaLookup::CompositionDeltaLookup() + : mDeltaEntries(NULL), + mNumDeltaEntries(0), + mCurrentDeltaEntry(0), + mCurrentEntrySampleIndex(0) { +} + +void SampleTable::CompositionDeltaLookup::setEntries( + const int32_t *deltaEntries, size_t numDeltaEntries) { + Mutex::Autolock autolock(mLock); + + mDeltaEntries = deltaEntries; + mNumDeltaEntries = numDeltaEntries; + mCurrentDeltaEntry = 0; + mCurrentEntrySampleIndex = 0; +} + +int32_t SampleTable::CompositionDeltaLookup::getCompositionTimeOffset( + uint32_t sampleIndex) { + Mutex::Autolock autolock(mLock); + + if (mDeltaEntries == NULL) { + return 0; + } + + if (sampleIndex < mCurrentEntrySampleIndex) { + mCurrentDeltaEntry = 0; + mCurrentEntrySampleIndex = 0; + } + + while (mCurrentDeltaEntry < mNumDeltaEntries) { + uint32_t sampleCount = mDeltaEntries[2 * mCurrentDeltaEntry]; + if (sampleIndex < mCurrentEntrySampleIndex + sampleCount) { + return mDeltaEntries[2 * mCurrentDeltaEntry + 1]; + } + + mCurrentEntrySampleIndex += sampleCount; + ++mCurrentDeltaEntry; + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +SampleTable::SampleTable(DataSourceBase *source) + : mDataSource(source), + mChunkOffsetOffset(-1), + mChunkOffsetType(0), + mNumChunkOffsets(0), + mSampleToChunkOffset(-1), + mNumSampleToChunkOffsets(0), + mSampleSizeOffset(-1), + mSampleSizeFieldSize(0), + mDefaultSampleSize(0), + mNumSampleSizes(0), + mHasTimeToSample(false), + mTimeToSampleCount(0), + mTimeToSample(NULL), + mSampleTimeEntries(NULL), + mCompositionTimeDeltaEntries(NULL), + mNumCompositionTimeDeltaEntries(0), + mCompositionDeltaLookup(new CompositionDeltaLookup), + mSyncSampleOffset(-1), + mNumSyncSamples(0), + mSyncSamples(NULL), + mLastSyncSampleIndex(0), + mSampleToChunkEntries(NULL), + mTotalSize(0) { + mSampleIterator = new SampleIterator(this); +} + +SampleTable::~SampleTable() { + delete[] mSampleToChunkEntries; + mSampleToChunkEntries = NULL; + + delete[] mSyncSamples; + mSyncSamples = NULL; + + delete[] mTimeToSample; + mTimeToSample = NULL; + + delete mCompositionDeltaLookup; + mCompositionDeltaLookup = NULL; + + delete[] mCompositionTimeDeltaEntries; + mCompositionTimeDeltaEntries = NULL; + + delete[] mSampleTimeEntries; + mSampleTimeEntries = NULL; + + delete mSampleIterator; + mSampleIterator = NULL; +} + +bool SampleTable::isValid() const { + return mChunkOffsetOffset >= 0 + && mSampleToChunkOffset >= 0 + && mSampleSizeOffset >= 0 + && mHasTimeToSample; +} + +status_t SampleTable::setChunkOffsetParams( + uint32_t type, off64_t data_offset, size_t data_size) { + if (mChunkOffsetOffset >= 0) { + return ERROR_MALFORMED; + } + + CHECK(type == kChunkOffsetType32 || type == kChunkOffsetType64); + + mChunkOffsetOffset = data_offset; + mChunkOffsetType = type; + + if (data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->readAt( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mNumChunkOffsets = U32_AT(&header[4]); + + if (mChunkOffsetType == kChunkOffsetType32) { + if ((data_size - 8) / 4 < mNumChunkOffsets) { + return ERROR_MALFORMED; + } + } else { + if ((data_size - 8) / 8 < mNumChunkOffsets) { + return ERROR_MALFORMED; + } + } + + return OK; +} + +status_t SampleTable::setSampleToChunkParams( + off64_t data_offset, size_t data_size) { + if (mSampleToChunkOffset >= 0) { + // already set + return ERROR_MALFORMED; + } + + if (data_offset < 0) { + return ERROR_MALFORMED; + } + + mSampleToChunkOffset = data_offset; + + if (data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->readAt( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mNumSampleToChunkOffsets = U32_AT(&header[4]); + + if ((data_size - 8) / sizeof(SampleToChunkEntry) < mNumSampleToChunkOffsets) { + return ERROR_MALFORMED; + } + + if ((uint64_t)kMaxTotalSize / sizeof(SampleToChunkEntry) <= + (uint64_t)mNumSampleToChunkOffsets) { + ALOGE("Sample-to-chunk table size too large."); + return ERROR_OUT_OF_RANGE; + } + + mTotalSize += (uint64_t)mNumSampleToChunkOffsets * + sizeof(SampleToChunkEntry); + if (mTotalSize > kMaxTotalSize) { + ALOGE("Sample-to-chunk table size would make sample table too large.\n" + " Requested sample-to-chunk table size = %llu\n" + " Eventual sample table size >= %llu\n" + " Allowed sample table size = %llu\n", + (unsigned long long)mNumSampleToChunkOffsets * + sizeof(SampleToChunkEntry), + (unsigned long long)mTotalSize, + (unsigned long long)kMaxTotalSize); + return ERROR_OUT_OF_RANGE; + } + + mSampleToChunkEntries = + new (std::nothrow) SampleToChunkEntry[mNumSampleToChunkOffsets]; + if (!mSampleToChunkEntries) { + ALOGE("Cannot allocate sample-to-chunk table with %llu entries.", + (unsigned long long)mNumSampleToChunkOffsets); + return ERROR_OUT_OF_RANGE; + } + + if (mNumSampleToChunkOffsets == 0) { + return OK; + } + + if ((off64_t)(kMaxOffset - 8 - + ((mNumSampleToChunkOffsets - 1) * sizeof(SampleToChunkEntry))) + < mSampleToChunkOffset) { + return ERROR_MALFORMED; + } + + for (uint32_t i = 0; i < mNumSampleToChunkOffsets; ++i) { + uint8_t buffer[sizeof(SampleToChunkEntry)]; + + if (mDataSource->readAt( + mSampleToChunkOffset + 8 + i * sizeof(SampleToChunkEntry), + buffer, + sizeof(buffer)) + != (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + // chunk index is 1 based in the spec. + if (U32_AT(buffer) < 1) { + ALOGE("b/23534160"); + return ERROR_OUT_OF_RANGE; + } + + // We want the chunk index to be 0-based. + mSampleToChunkEntries[i].startChunk = U32_AT(buffer) - 1; + mSampleToChunkEntries[i].samplesPerChunk = U32_AT(&buffer[4]); + mSampleToChunkEntries[i].chunkDesc = U32_AT(&buffer[8]); + } + + return OK; +} + +status_t SampleTable::setSampleSizeParams( + uint32_t type, off64_t data_offset, size_t data_size) { + if (mSampleSizeOffset >= 0) { + return ERROR_MALFORMED; + } + + CHECK(type == kSampleSizeType32 || type == kSampleSizeTypeCompact); + + mSampleSizeOffset = data_offset; + + if (data_size < 12) { + return ERROR_MALFORMED; + } + + uint8_t header[12]; + if (mDataSource->readAt( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mDefaultSampleSize = U32_AT(&header[4]); + mNumSampleSizes = U32_AT(&header[8]); + if (mNumSampleSizes > (UINT32_MAX - 12) / 16) { + ALOGE("b/23247055, mNumSampleSizes(%u)", mNumSampleSizes); + return ERROR_MALFORMED; + } + + if (type == kSampleSizeType32) { + mSampleSizeFieldSize = 32; + + if (mDefaultSampleSize != 0) { + return OK; + } + + if (data_size < 12 + mNumSampleSizes * 4) { + return ERROR_MALFORMED; + } + } else { + if ((mDefaultSampleSize & 0xffffff00) != 0) { + // The high 24 bits are reserved and must be 0. + return ERROR_MALFORMED; + } + + mSampleSizeFieldSize = mDefaultSampleSize & 0xff; + mDefaultSampleSize = 0; + + if (mSampleSizeFieldSize != 4 && mSampleSizeFieldSize != 8 + && mSampleSizeFieldSize != 16) { + return ERROR_MALFORMED; + } + + if (data_size < 12 + (mNumSampleSizes * mSampleSizeFieldSize + 4) / 8) { + return ERROR_MALFORMED; + } + } + + return OK; +} + +status_t SampleTable::setTimeToSampleParams( + off64_t data_offset, size_t data_size) { + if (mHasTimeToSample || data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->readAt( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mTimeToSampleCount = U32_AT(&header[4]); + if (mTimeToSampleCount > UINT32_MAX / (2 * sizeof(uint32_t))) { + // Choose this bound because + // 1) 2 * sizeof(uint32_t) is the amount of memory needed for one + // time-to-sample entry in the time-to-sample table. + // 2) mTimeToSampleCount is the number of entries of the time-to-sample + // table. + // 3) We hope that the table size does not exceed UINT32_MAX. + ALOGE("Time-to-sample table size too large."); + return ERROR_OUT_OF_RANGE; + } + + // Note: At this point, we know that mTimeToSampleCount * 2 will not + // overflow because of the above condition. + + uint64_t allocSize = (uint64_t)mTimeToSampleCount * 2 * sizeof(uint32_t); + mTotalSize += allocSize; + if (mTotalSize > kMaxTotalSize) { + ALOGE("Time-to-sample table size would make sample table too large.\n" + " Requested time-to-sample table size = %llu\n" + " Eventual sample table size >= %llu\n" + " Allowed sample table size = %llu\n", + (unsigned long long)allocSize, + (unsigned long long)mTotalSize, + (unsigned long long)kMaxTotalSize); + return ERROR_OUT_OF_RANGE; + } + + mTimeToSample = new (std::nothrow) uint32_t[mTimeToSampleCount * 2]; + if (!mTimeToSample) { + ALOGE("Cannot allocate time-to-sample table with %llu entries.", + (unsigned long long)mTimeToSampleCount); + return ERROR_OUT_OF_RANGE; + } + + if (mDataSource->readAt(data_offset + 8, mTimeToSample, + (size_t)allocSize) < (ssize_t)allocSize) { + ALOGE("Incomplete data read for time-to-sample table."); + return ERROR_IO; + } + + for (size_t i = 0; i < mTimeToSampleCount * 2; ++i) { + mTimeToSample[i] = ntohl(mTimeToSample[i]); + } + + mHasTimeToSample = true; + return OK; +} + +// NOTE: per 14996-12, version 0 ctts contains unsigned values, while version 1 +// contains signed values, however some software creates version 0 files that +// contain signed values, so we're always treating the values as signed, +// regardless of version. +status_t SampleTable::setCompositionTimeToSampleParams( + off64_t data_offset, size_t data_size) { + ALOGI("There are reordered frames present."); + + if (mCompositionTimeDeltaEntries != NULL || data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->readAt( + data_offset, header, sizeof(header)) + < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + uint32_t flags = U32_AT(header); + uint32_t version = flags >> 24; + flags &= 0xffffff; + + if ((version != 0 && version != 1) || flags != 0) { + // Expected version = 0 or 1, flags = 0. + return ERROR_MALFORMED; + } + + size_t numEntries = U32_AT(&header[4]); + + if (((SIZE_MAX / 8) - 1 < numEntries) || (data_size != (numEntries + 1) * 8)) { + return ERROR_MALFORMED; + } + + mNumCompositionTimeDeltaEntries = numEntries; + uint64_t allocSize = (uint64_t)numEntries * 2 * sizeof(int32_t); + if (allocSize > kMaxTotalSize) { + ALOGE("Composition-time-to-sample table size too large."); + return ERROR_OUT_OF_RANGE; + } + + mTotalSize += allocSize; + if (mTotalSize > kMaxTotalSize) { + ALOGE("Composition-time-to-sample table would make sample table too large.\n" + " Requested composition-time-to-sample table size = %llu\n" + " Eventual sample table size >= %llu\n" + " Allowed sample table size = %llu\n", + (unsigned long long)allocSize, + (unsigned long long)mTotalSize, + (unsigned long long)kMaxTotalSize); + return ERROR_OUT_OF_RANGE; + } + + mCompositionTimeDeltaEntries = new (std::nothrow) int32_t[2 * numEntries]; + if (!mCompositionTimeDeltaEntries) { + ALOGE("Cannot allocate composition-time-to-sample table with %llu " + "entries.", (unsigned long long)numEntries); + return ERROR_OUT_OF_RANGE; + } + + if (mDataSource->readAt(data_offset + 8, mCompositionTimeDeltaEntries, + (size_t)allocSize) < (ssize_t)allocSize) { + delete[] mCompositionTimeDeltaEntries; + mCompositionTimeDeltaEntries = NULL; + + return ERROR_IO; + } + + for (size_t i = 0; i < 2 * numEntries; ++i) { + mCompositionTimeDeltaEntries[i] = ntohl(mCompositionTimeDeltaEntries[i]); + } + + mCompositionDeltaLookup->setEntries( + mCompositionTimeDeltaEntries, mNumCompositionTimeDeltaEntries); + + return OK; +} + +status_t SampleTable::setSyncSampleParams(off64_t data_offset, size_t data_size) { + if (mSyncSampleOffset >= 0 || data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->readAt( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + uint32_t numSyncSamples = U32_AT(&header[4]); + + if (numSyncSamples < 2) { + ALOGV("Table of sync samples is empty or has only a single entry!"); + } + + uint64_t allocSize = (uint64_t)numSyncSamples * sizeof(uint32_t); + if (allocSize > kMaxTotalSize) { + ALOGE("Sync sample table size too large."); + return ERROR_OUT_OF_RANGE; + } + + mTotalSize += allocSize; + if (mTotalSize > kMaxTotalSize) { + ALOGE("Sync sample table size would make sample table too large.\n" + " Requested sync sample table size = %llu\n" + " Eventual sample table size >= %llu\n" + " Allowed sample table size = %llu\n", + (unsigned long long)allocSize, + (unsigned long long)mTotalSize, + (unsigned long long)kMaxTotalSize); + return ERROR_OUT_OF_RANGE; + } + + mSyncSamples = new (std::nothrow) uint32_t[numSyncSamples]; + if (!mSyncSamples) { + ALOGE("Cannot allocate sync sample table with %llu entries.", + (unsigned long long)numSyncSamples); + return ERROR_OUT_OF_RANGE; + } + + if (mDataSource->readAt(data_offset + 8, mSyncSamples, + (size_t)allocSize) != (ssize_t)allocSize) { + delete[] mSyncSamples; + mSyncSamples = NULL; + return ERROR_IO; + } + + for (size_t i = 0; i < numSyncSamples; ++i) { + if (mSyncSamples[i] == 0) { + ALOGE("b/32423862, unexpected zero value in stss"); + continue; + } + mSyncSamples[i] = ntohl(mSyncSamples[i]) - 1; + } + + mSyncSampleOffset = data_offset; + mNumSyncSamples = numSyncSamples; + + return OK; +} + +uint32_t SampleTable::countChunkOffsets() const { + return mNumChunkOffsets; +} + +uint32_t SampleTable::countSamples() const { + return mNumSampleSizes; +} + +status_t SampleTable::getMaxSampleSize(size_t *max_size) { + Mutex::Autolock autoLock(mLock); + + *max_size = 0; + + for (uint32_t i = 0; i < mNumSampleSizes; ++i) { + size_t sample_size; + status_t err = getSampleSize_l(i, &sample_size); + + if (err != OK) { + return err; + } + + if (sample_size > *max_size) { + *max_size = sample_size; + } + } + + return OK; +} + +uint32_t abs_difference(uint32_t time1, uint32_t time2) { + return time1 > time2 ? time1 - time2 : time2 - time1; +} + +// static +int SampleTable::CompareIncreasingTime(const void *_a, const void *_b) { + const SampleTimeEntry *a = (const SampleTimeEntry *)_a; + const SampleTimeEntry *b = (const SampleTimeEntry *)_b; + + if (a->mCompositionTime < b->mCompositionTime) { + return -1; + } else if (a->mCompositionTime > b->mCompositionTime) { + return 1; + } + + return 0; +} + +void SampleTable::buildSampleEntriesTable() { + Mutex::Autolock autoLock(mLock); + + if (mSampleTimeEntries != NULL || mNumSampleSizes == 0) { + if (mNumSampleSizes == 0) { + ALOGE("b/23247055, mNumSampleSizes(%u)", mNumSampleSizes); + } + return; + } + + mTotalSize += (uint64_t)mNumSampleSizes * sizeof(SampleTimeEntry); + if (mTotalSize > kMaxTotalSize) { + ALOGE("Sample entry table size would make sample table too large.\n" + " Requested sample entry table size = %llu\n" + " Eventual sample table size >= %llu\n" + " Allowed sample table size = %llu\n", + (unsigned long long)mNumSampleSizes * sizeof(SampleTimeEntry), + (unsigned long long)mTotalSize, + (unsigned long long)kMaxTotalSize); + return; + } + + mSampleTimeEntries = new (std::nothrow) SampleTimeEntry[mNumSampleSizes]; + if (!mSampleTimeEntries) { + ALOGE("Cannot allocate sample entry table with %llu entries.", + (unsigned long long)mNumSampleSizes); + return; + } + + uint32_t sampleIndex = 0; + uint32_t sampleTime = 0; + + for (uint32_t i = 0; i < mTimeToSampleCount; ++i) { + uint32_t n = mTimeToSample[2 * i]; + uint32_t delta = mTimeToSample[2 * i + 1]; + + for (uint32_t j = 0; j < n; ++j) { + if (sampleIndex < mNumSampleSizes) { + // Technically this should always be the case if the file + // is well-formed, but you know... there's (gasp) malformed + // content out there. + + mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex; + + int32_t compTimeDelta = + mCompositionDeltaLookup->getCompositionTimeOffset( + sampleIndex); + + if ((compTimeDelta < 0 && sampleTime < + (compTimeDelta == INT32_MIN ? + INT32_MAX : uint32_t(-compTimeDelta))) + || (compTimeDelta > 0 && + sampleTime > UINT32_MAX - compTimeDelta)) { + ALOGE("%u + %d would overflow, clamping", + sampleTime, compTimeDelta); + if (compTimeDelta < 0) { + sampleTime = 0; + } else { + sampleTime = UINT32_MAX; + } + compTimeDelta = 0; + } + + mSampleTimeEntries[sampleIndex].mCompositionTime = + compTimeDelta > 0 ? sampleTime + compTimeDelta: + sampleTime - (-compTimeDelta); + } + + ++sampleIndex; + if (sampleTime > UINT32_MAX - delta) { + ALOGE("%u + %u would overflow, clamping", + sampleTime, delta); + sampleTime = UINT32_MAX; + } else { + sampleTime += delta; + } + } + } + + qsort(mSampleTimeEntries, mNumSampleSizes, sizeof(SampleTimeEntry), + CompareIncreasingTime); +} + +status_t SampleTable::findSampleAtTime( + uint64_t req_time, uint64_t scale_num, uint64_t scale_den, + uint32_t *sample_index, uint32_t flags) { + buildSampleEntriesTable(); + + if (mSampleTimeEntries == NULL) { + return ERROR_OUT_OF_RANGE; + } + + if (flags == kFlagFrameIndex) { + if (req_time >= mNumSampleSizes) { + return ERROR_OUT_OF_RANGE; + } + *sample_index = mSampleTimeEntries[req_time].mSampleIndex; + return OK; + } + + uint32_t left = 0; + uint32_t right_plus_one = mNumSampleSizes; + while (left < right_plus_one) { + uint32_t center = left + (right_plus_one - left) / 2; + uint64_t centerTime = + getSampleTime(center, scale_num, scale_den); + + if (req_time < centerTime) { + right_plus_one = center; + } else if (req_time > centerTime) { + left = center + 1; + } else { + *sample_index = mSampleTimeEntries[center].mSampleIndex; + return OK; + } + } + + uint32_t closestIndex = left; + + if (closestIndex == mNumSampleSizes) { + if (flags == kFlagAfter) { + return ERROR_OUT_OF_RANGE; + } + flags = kFlagBefore; + } else if (closestIndex == 0) { + if (flags == kFlagBefore) { + // normally we should return out of range, but that is + // treated as end-of-stream. instead return first sample + // + // return ERROR_OUT_OF_RANGE; + } + flags = kFlagAfter; + } + + switch (flags) { + case kFlagBefore: + { + --closestIndex; + break; + } + + case kFlagAfter: + { + // nothing to do + break; + } + + default: + { + CHECK(flags == kFlagClosest); + // pick closest based on timestamp. use abs_difference for safety + if (abs_difference( + getSampleTime(closestIndex, scale_num, scale_den), req_time) > + abs_difference( + req_time, getSampleTime(closestIndex - 1, scale_num, scale_den))) { + --closestIndex; + } + break; + } + } + + *sample_index = mSampleTimeEntries[closestIndex].mSampleIndex; + return OK; +} + +status_t SampleTable::findSyncSampleNear( + uint32_t start_sample_index, uint32_t *sample_index, uint32_t flags) { + Mutex::Autolock autoLock(mLock); + + *sample_index = 0; + + if (mSyncSampleOffset < 0) { + // All samples are sync-samples. + *sample_index = start_sample_index; + return OK; + } + + if (mNumSyncSamples == 0) { + *sample_index = 0; + return OK; + } + + uint32_t left = 0; + uint32_t right_plus_one = mNumSyncSamples; + while (left < right_plus_one) { + uint32_t center = left + (right_plus_one - left) / 2; + uint32_t x = mSyncSamples[center]; + + if (start_sample_index < x) { + right_plus_one = center; + } else if (start_sample_index > x) { + left = center + 1; + } else { + *sample_index = x; + return OK; + } + } + + if (left == mNumSyncSamples) { + if (flags == kFlagAfter) { + ALOGE("tried to find a sync frame after the last one: %d", left); + return ERROR_OUT_OF_RANGE; + } + flags = kFlagBefore; + } + else if (left == 0) { + if (flags == kFlagBefore) { + ALOGE("tried to find a sync frame before the first one: %d", left); + + // normally we should return out of range, but that is + // treated as end-of-stream. instead seek to first sync + // + // return ERROR_OUT_OF_RANGE; + } + flags = kFlagAfter; + } + + // Now ssi[left - 1] <(=) start_sample_index <= ssi[left] + switch (flags) { + case kFlagBefore: + { + --left; + break; + } + case kFlagAfter: + { + // nothing to do + break; + } + default: + { + // this route is not used, but implement it nonetheless + CHECK(flags == kFlagClosest); + + status_t err = mSampleIterator->seekTo(start_sample_index); + if (err != OK) { + return err; + } + uint32_t sample_time = mSampleIterator->getSampleTime(); + + err = mSampleIterator->seekTo(mSyncSamples[left]); + if (err != OK) { + return err; + } + uint32_t upper_time = mSampleIterator->getSampleTime(); + + err = mSampleIterator->seekTo(mSyncSamples[left - 1]); + if (err != OK) { + return err; + } + uint32_t lower_time = mSampleIterator->getSampleTime(); + + // use abs_difference for safety + if (abs_difference(upper_time, sample_time) > + abs_difference(sample_time, lower_time)) { + --left; + } + break; + } + } + + *sample_index = mSyncSamples[left]; + return OK; +} + +status_t SampleTable::findThumbnailSample(uint32_t *sample_index) { + Mutex::Autolock autoLock(mLock); + + if (mSyncSampleOffset < 0) { + // All samples are sync-samples. + *sample_index = 0; + return OK; + } + + uint32_t bestSampleIndex = 0; + size_t maxSampleSize = 0; + + static const size_t kMaxNumSyncSamplesToScan = 20; + + // Consider the first kMaxNumSyncSamplesToScan sync samples and + // pick the one with the largest (compressed) size as the thumbnail. + + size_t numSamplesToScan = mNumSyncSamples; + if (numSamplesToScan > kMaxNumSyncSamplesToScan) { + numSamplesToScan = kMaxNumSyncSamplesToScan; + } + + for (size_t i = 0; i < numSamplesToScan; ++i) { + uint32_t x = mSyncSamples[i]; + + // Now x is a sample index. + size_t sampleSize; + status_t err = getSampleSize_l(x, &sampleSize); + if (err != OK) { + return err; + } + + if (i == 0 || sampleSize > maxSampleSize) { + bestSampleIndex = x; + maxSampleSize = sampleSize; + } + } + + *sample_index = bestSampleIndex; + + return OK; +} + +status_t SampleTable::getSampleSize_l( + uint32_t sampleIndex, size_t *sampleSize) { + return mSampleIterator->getSampleSizeDirect( + sampleIndex, sampleSize); +} + +status_t SampleTable::getMetaDataForSample( + uint32_t sampleIndex, + off64_t *offset, + size_t *size, + uint32_t *compositionTime, + bool *isSyncSample, + uint32_t *sampleDuration) { + Mutex::Autolock autoLock(mLock); + + status_t err; + if ((err = mSampleIterator->seekTo(sampleIndex)) != OK) { + return err; + } + + if (offset) { + *offset = mSampleIterator->getSampleOffset(); + } + + if (size) { + *size = mSampleIterator->getSampleSize(); + } + + if (compositionTime) { + *compositionTime = mSampleIterator->getSampleTime(); + } + + if (isSyncSample) { + *isSyncSample = false; + if (mSyncSampleOffset < 0) { + // Every sample is a sync sample. + *isSyncSample = true; + } else { + size_t i = (mLastSyncSampleIndex < mNumSyncSamples) + && (mSyncSamples[mLastSyncSampleIndex] <= sampleIndex) + ? mLastSyncSampleIndex : 0; + + while (i < mNumSyncSamples && mSyncSamples[i] < sampleIndex) { + ++i; + } + + if (i < mNumSyncSamples && mSyncSamples[i] == sampleIndex) { + *isSyncSample = true; + } + + mLastSyncSampleIndex = i; + } + } + + if (sampleDuration) { + *sampleDuration = mSampleIterator->getSampleDuration(); + } + + return OK; +} + +int32_t SampleTable::getCompositionTimeOffset(uint32_t sampleIndex) { + return mCompositionDeltaLookup->getCompositionTimeOffset(sampleIndex); +} + +} // namespace android
diff --git a/media/extractors/mp4/SampleTable.h b/media/extractors/mp4/SampleTable.h new file mode 100644 index 0000000..e4e974b --- /dev/null +++ b/media/extractors/mp4/SampleTable.h
@@ -0,0 +1,171 @@ +/* + * Copyright (C) 2009 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 SAMPLE_TABLE_H_ + +#define SAMPLE_TABLE_H_ + +#include <sys/types.h> +#include <stdint.h> + +#include <media/stagefright/MediaErrors.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class DataSourceBase; +struct SampleIterator; + +class SampleTable : public RefBase { +public: + explicit SampleTable(DataSourceBase *source); + + bool isValid() const; + + // type can be 'stco' or 'co64'. + status_t setChunkOffsetParams( + uint32_t type, off64_t data_offset, size_t data_size); + + status_t setSampleToChunkParams(off64_t data_offset, size_t data_size); + + // type can be 'stsz' or 'stz2'. + status_t setSampleSizeParams( + uint32_t type, off64_t data_offset, size_t data_size); + + status_t setTimeToSampleParams(off64_t data_offset, size_t data_size); + + status_t setCompositionTimeToSampleParams( + off64_t data_offset, size_t data_size); + + status_t setSyncSampleParams(off64_t data_offset, size_t data_size); + + //////////////////////////////////////////////////////////////////////////// + + uint32_t countChunkOffsets() const; + + uint32_t countSamples() const; + + status_t getMaxSampleSize(size_t *size); + + status_t getMetaDataForSample( + uint32_t sampleIndex, + off64_t *offset, + size_t *size, + uint32_t *compositionTime, + bool *isSyncSample = NULL, + uint32_t *sampleDuration = NULL); + + enum { + kFlagBefore, + kFlagAfter, + kFlagClosest, + kFlagFrameIndex, + }; + status_t findSampleAtTime( + uint64_t req_time, uint64_t scale_num, uint64_t scale_den, + uint32_t *sample_index, uint32_t flags); + + status_t findSyncSampleNear( + uint32_t start_sample_index, uint32_t *sample_index, + uint32_t flags); + + status_t findThumbnailSample(uint32_t *sample_index); + +protected: + ~SampleTable(); + +private: + struct CompositionDeltaLookup; + + static const uint32_t kChunkOffsetType32; + static const uint32_t kChunkOffsetType64; + static const uint32_t kSampleSizeType32; + static const uint32_t kSampleSizeTypeCompact; + + // Limit the total size of all internal tables to 200MiB. + static const size_t kMaxTotalSize = 200 * (1 << 20); + + DataSourceBase *mDataSource; + Mutex mLock; + + off64_t mChunkOffsetOffset; + uint32_t mChunkOffsetType; + uint32_t mNumChunkOffsets; + + off64_t mSampleToChunkOffset; + uint32_t mNumSampleToChunkOffsets; + + off64_t mSampleSizeOffset; + uint32_t mSampleSizeFieldSize; + uint32_t mDefaultSampleSize; + uint32_t mNumSampleSizes; + + bool mHasTimeToSample; + uint32_t mTimeToSampleCount; + uint32_t* mTimeToSample; + + struct SampleTimeEntry { + uint32_t mSampleIndex; + uint32_t mCompositionTime; + }; + SampleTimeEntry *mSampleTimeEntries; + + int32_t *mCompositionTimeDeltaEntries; + size_t mNumCompositionTimeDeltaEntries; + CompositionDeltaLookup *mCompositionDeltaLookup; + + off64_t mSyncSampleOffset; + uint32_t mNumSyncSamples; + uint32_t *mSyncSamples; + size_t mLastSyncSampleIndex; + + SampleIterator *mSampleIterator; + + struct SampleToChunkEntry { + uint32_t startChunk; + uint32_t samplesPerChunk; + uint32_t chunkDesc; + }; + SampleToChunkEntry *mSampleToChunkEntries; + + // Approximate size of all tables combined. + uint64_t mTotalSize; + + friend struct SampleIterator; + + // normally we don't round + inline uint64_t getSampleTime( + size_t sample_index, uint64_t scale_num, uint64_t scale_den) const { + return (sample_index < (size_t)mNumSampleSizes && mSampleTimeEntries != NULL + && scale_den != 0) + ? (mSampleTimeEntries[sample_index].mCompositionTime * scale_num) / scale_den : 0; + } + + status_t getSampleSize_l(uint32_t sample_index, size_t *sample_size); + int32_t getCompositionTimeOffset(uint32_t sampleIndex); + + static int CompareIncreasingTime(const void *, const void *); + + void buildSampleEntriesTable(); + + SampleTable(const SampleTable &); + SampleTable &operator=(const SampleTable &); +}; + +} // namespace android + +#endif // SAMPLE_TABLE_H_
diff --git a/media/extractors/mp4/exports.lds b/media/extractors/mp4/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/mp4/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/mpeg2/Android.bp b/media/extractors/mpeg2/Android.bp new file mode 100644 index 0000000..5e4a592 --- /dev/null +++ b/media/extractors/mpeg2/Android.bp
@@ -0,0 +1,56 @@ +cc_library_shared { + + srcs: [ + "ExtractorBundle.cpp", + "MPEG2PSExtractor.cpp", + "MPEG2TSExtractor.cpp", + ], + + include_dirs: [ + "frameworks/av/media/libstagefright", + "frameworks/av/media/libstagefright/include", + ], + + shared_libs: [ + "android.hardware.cas@1.0", + "android.hardware.cas.native@1.0", + "android.hidl.token@1.0-utils", + "libbinder", + "libcrypto", + "libcutils", + "libhidlallocatorutils", + "libhidlbase", + "liblog", + "libmediaextractor", + "libstagefright_foundation", + ], + + static_libs: [ + "libstagefright_mpeg2support", + "libutils", + ], + + name: "libmpeg2extractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/extractors/mpeg2/ExtractorBundle.cpp b/media/extractors/mpeg2/ExtractorBundle.cpp new file mode 100644 index 0000000..8a0fa03 --- /dev/null +++ b/media/extractors/mpeg2/ExtractorBundle.cpp
@@ -0,0 +1,59 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "MPEG2ExtractorBundle" +#include <utils/Log.h> + +#include <media/MediaExtractor.h> +#include "MPEG2PSExtractor.h" +#include "MPEG2TSExtractor.h" + +namespace android { + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("3d1dcfeb-e40a-436d-a574-c2438a555e5f"), + 1, + "MPEG2-PS/TS Extractor", + []( + DataSourceBase *source, + float *confidence, + void **, + MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc { + if (SniffMPEG2TS(source, confidence)) { + return []( + DataSourceBase *source, + void *) -> MediaExtractor* { + return new MPEG2TSExtractor(source);}; + } else if (SniffMPEG2PS(source, confidence)) { + return []( + DataSourceBase *source, + void *) -> MediaExtractor* { + return new MPEG2PSExtractor(source);}; + } + return NULL; + } + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/libstagefright/matroska/MODULE_LICENSE_APACHE2 b/media/extractors/mpeg2/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/libstagefright/matroska/MODULE_LICENSE_APACHE2 copy to media/extractors/mpeg2/MODULE_LICENSE_APACHE2
diff --git a/media/extractors/mpeg2/MPEG2PSExtractor.cpp b/media/extractors/mpeg2/MPEG2PSExtractor.cpp new file mode 100644 index 0000000..6980b82 --- /dev/null +++ b/media/extractors/mpeg2/MPEG2PSExtractor.cpp
@@ -0,0 +1,772 @@ +/* + * 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 "MPEG2PSExtractor" +#include <utils/Log.h> + +#include "MPEG2PSExtractor.h" + +#include "mpeg2ts/AnotherPacketSource.h" +#include "mpeg2ts/ESQueue.h" + +#include <media/DataSourceBase.h> +#include <media/MediaTrack.h> +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/ByteUtils.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <utils/String8.h> + +#include <inttypes.h> + +namespace android { + +struct MPEG2PSExtractor::Track : public MediaTrack, public RefBase { + Track(MPEG2PSExtractor *extractor, + unsigned stream_id, unsigned stream_type); + + virtual status_t start(MetaDataBase *params); + virtual status_t stop(); + virtual status_t getFormat(MetaDataBase &); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options); + +protected: + virtual ~Track(); + +private: + friend struct MPEG2PSExtractor; + + MPEG2PSExtractor *mExtractor; + + unsigned mStreamID; + unsigned mStreamType; + ElementaryStreamQueue *mQueue; + sp<AnotherPacketSource> mSource; + + status_t appendPESData( + unsigned PTS_DTS_flags, + uint64_t PTS, uint64_t DTS, + const uint8_t *data, size_t size); + + DISALLOW_EVIL_CONSTRUCTORS(Track); +}; + +struct MPEG2PSExtractor::WrappedTrack : public MediaTrack { + WrappedTrack(MPEG2PSExtractor *extractor, const sp<Track> &track); + + virtual status_t start(MetaDataBase *params); + virtual status_t stop(); + virtual status_t getFormat(MetaDataBase &); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options); + +protected: + virtual ~WrappedTrack(); + +private: + MPEG2PSExtractor *mExtractor; + sp<MPEG2PSExtractor::Track> mTrack; + + DISALLOW_EVIL_CONSTRUCTORS(WrappedTrack); +}; + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2PSExtractor::MPEG2PSExtractor(DataSourceBase *source) + : mDataSource(source), + mOffset(0), + mFinalResult(OK), + mBuffer(new ABuffer(0)), + mScanning(true), + mProgramStreamMapValid(false) { + for (size_t i = 0; i < 500; ++i) { + if (feedMore() != OK) { + break; + } + } + + // Remove all tracks that were unable to determine their format. + MetaDataBase meta; + for (size_t i = mTracks.size(); i > 0;) { + i--; + if (mTracks.valueAt(i)->getFormat(meta) != OK) { + mTracks.removeItemsAt(i); + } + } + + mScanning = false; +} + +MPEG2PSExtractor::~MPEG2PSExtractor() { +} + +size_t MPEG2PSExtractor::countTracks() { + return mTracks.size(); +} + +MediaTrack *MPEG2PSExtractor::getTrack(size_t index) { + if (index >= mTracks.size()) { + return NULL; + } + + return new WrappedTrack(this, mTracks.valueAt(index)); +} + +status_t MPEG2PSExtractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t /* flags */) { + if (index >= mTracks.size()) { + return UNKNOWN_ERROR; + } + + return mTracks.valueAt(index)->getFormat(meta); +} + +status_t MPEG2PSExtractor::getMetaData(MetaDataBase &meta) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG2PS); + + return OK; +} + +uint32_t MPEG2PSExtractor::flags() const { + return CAN_PAUSE; +} + +status_t MPEG2PSExtractor::feedMore() { + Mutex::Autolock autoLock(mLock); + + // How much data we're reading at a time + static const size_t kChunkSize = 8192; + + for (;;) { + status_t err = dequeueChunk(); + + if (err == -EAGAIN && mFinalResult == OK) { + memmove(mBuffer->base(), mBuffer->data(), mBuffer->size()); + mBuffer->setRange(0, mBuffer->size()); + + if (mBuffer->size() + kChunkSize > mBuffer->capacity()) { + size_t newCapacity = mBuffer->capacity() + kChunkSize; + sp<ABuffer> newBuffer = new ABuffer(newCapacity); + memcpy(newBuffer->data(), mBuffer->data(), mBuffer->size()); + newBuffer->setRange(0, mBuffer->size()); + mBuffer = newBuffer; + } + + ssize_t n = mDataSource->readAt( + mOffset, mBuffer->data() + mBuffer->size(), kChunkSize); + + if (n < (ssize_t)kChunkSize) { + mFinalResult = (n < 0) ? (status_t)n : ERROR_END_OF_STREAM; + return mFinalResult; + } + + mBuffer->setRange(mBuffer->offset(), mBuffer->size() + n); + mOffset += n; + } else if (err != OK) { + mFinalResult = err; + return err; + } else { + return OK; + } + } +} + +status_t MPEG2PSExtractor::dequeueChunk() { + if (mBuffer->size() < 4) { + return -EAGAIN; + } + + if (memcmp("\x00\x00\x01", mBuffer->data(), 3)) { + return ERROR_MALFORMED; + } + + unsigned chunkType = mBuffer->data()[3]; + + ssize_t res; + + switch (chunkType) { + case 0xba: + { + res = dequeuePack(); + break; + } + + case 0xbb: + { + res = dequeueSystemHeader(); + break; + } + + default: + { + res = dequeuePES(); + break; + } + } + + if (res > 0) { + if (mBuffer->size() < (size_t)res) { + return -EAGAIN; + } + + mBuffer->setRange(mBuffer->offset() + res, mBuffer->size() - res); + res = OK; + } + + return res; +} + +ssize_t MPEG2PSExtractor::dequeuePack() { + // 32 + 2 + 3 + 1 + 15 + 1 + 15+ 1 + 9 + 1 + 22 + 1 + 1 | +5 + + if (mBuffer->size() < 14) { + return -EAGAIN; + } + + unsigned pack_stuffing_length = mBuffer->data()[13] & 7; + + return pack_stuffing_length + 14; +} + +ssize_t MPEG2PSExtractor::dequeueSystemHeader() { + if (mBuffer->size() < 6) { + return -EAGAIN; + } + + unsigned header_length = U16_AT(mBuffer->data() + 4); + + return header_length + 6; +} + +ssize_t MPEG2PSExtractor::dequeuePES() { + if (mBuffer->size() < 6) { + return -EAGAIN; + } + + unsigned PES_packet_length = U16_AT(mBuffer->data() + 4); + if (PES_packet_length == 0u) { + ALOGE("PES_packet_length is 0"); + return -EAGAIN; + } + + size_t n = PES_packet_length + 6; + + if (mBuffer->size() < n) { + return -EAGAIN; + } + + ABitReader br(mBuffer->data(), n); + + unsigned packet_startcode_prefix = br.getBits(24); + + ALOGV("packet_startcode_prefix = 0x%08x", packet_startcode_prefix); + + if (packet_startcode_prefix != 1) { + ALOGV("Supposedly payload_unit_start=1 unit does not start " + "with startcode."); + + return ERROR_MALFORMED; + } + + if (packet_startcode_prefix != 0x000001u) { + ALOGE("Wrong PES prefix"); + return ERROR_MALFORMED; + } + + unsigned stream_id = br.getBits(8); + ALOGV("stream_id = 0x%02x", stream_id); + + /* unsigned PES_packet_length = */br.getBits(16); + + if (stream_id == 0xbc) { + // program_stream_map + + if (!mScanning) { + return n; + } + + mStreamTypeByESID.clear(); + + /* unsigned current_next_indicator = */br.getBits(1); + /* unsigned reserved = */br.getBits(2); + /* unsigned program_stream_map_version = */br.getBits(5); + /* unsigned reserved = */br.getBits(7); + /* unsigned marker_bit = */br.getBits(1); + unsigned program_stream_info_length = br.getBits(16); + + size_t offset = 0; + while (offset < program_stream_info_length) { + if (offset + 2 > program_stream_info_length) { + return ERROR_MALFORMED; + } + + unsigned descriptor_tag = br.getBits(8); + unsigned descriptor_length = br.getBits(8); + + ALOGI("found descriptor tag 0x%02x of length %u", + descriptor_tag, descriptor_length); + + if (offset + 2 + descriptor_length > program_stream_info_length) { + return ERROR_MALFORMED; + } + + br.skipBits(8 * descriptor_length); + + offset += 2 + descriptor_length; + } + + unsigned elementary_stream_map_length = br.getBits(16); + + offset = 0; + while (offset < elementary_stream_map_length) { + if (offset + 4 > elementary_stream_map_length) { + return ERROR_MALFORMED; + } + + unsigned stream_type = br.getBits(8); + unsigned elementary_stream_id = br.getBits(8); + + ALOGI("elementary stream id 0x%02x has stream type 0x%02x", + elementary_stream_id, stream_type); + + mStreamTypeByESID.add(elementary_stream_id, stream_type); + + unsigned elementary_stream_info_length = br.getBits(16); + + if (offset + 4 + elementary_stream_info_length + > elementary_stream_map_length) { + return ERROR_MALFORMED; + } + + offset += 4 + elementary_stream_info_length; + } + + /* unsigned CRC32 = */br.getBits(32); + + mProgramStreamMapValid = true; + } else if (stream_id != 0xbe // padding_stream + && stream_id != 0xbf // private_stream_2 + && stream_id != 0xf0 // ECM + && stream_id != 0xf1 // EMM + && stream_id != 0xff // program_stream_directory + && stream_id != 0xf2 // DSMCC + && stream_id != 0xf8) { // H.222.1 type E + /* unsigned PES_marker_bits = */br.getBits(2); // should be 0x2(hex) + /* unsigned PES_scrambling_control = */br.getBits(2); + /* unsigned PES_priority = */br.getBits(1); + /* unsigned data_alignment_indicator = */br.getBits(1); + /* unsigned copyright = */br.getBits(1); + /* unsigned original_or_copy = */br.getBits(1); + + unsigned PTS_DTS_flags = br.getBits(2); + ALOGV("PTS_DTS_flags = %u", PTS_DTS_flags); + + unsigned ESCR_flag = br.getBits(1); + ALOGV("ESCR_flag = %u", ESCR_flag); + + unsigned ES_rate_flag = br.getBits(1); + ALOGV("ES_rate_flag = %u", ES_rate_flag); + + unsigned DSM_trick_mode_flag = br.getBits(1); + ALOGV("DSM_trick_mode_flag = %u", DSM_trick_mode_flag); + + unsigned additional_copy_info_flag = br.getBits(1); + ALOGV("additional_copy_info_flag = %u", additional_copy_info_flag); + + /* unsigned PES_CRC_flag = */br.getBits(1); + /* PES_extension_flag = */br.getBits(1); + + unsigned PES_header_data_length = br.getBits(8); + ALOGV("PES_header_data_length = %u", PES_header_data_length); + + unsigned optional_bytes_remaining = PES_header_data_length; + + uint64_t PTS = 0, DTS = 0; + + if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) { + if (optional_bytes_remaining < 5u) { + return ERROR_MALFORMED; + } + + if (br.getBits(4) != PTS_DTS_flags) { + return ERROR_MALFORMED; + } + + PTS = ((uint64_t)br.getBits(3)) << 30; + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + PTS |= ((uint64_t)br.getBits(15)) << 15; + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + PTS |= br.getBits(15); + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + + ALOGV("PTS = %" PRIu64, PTS); + // ALOGI("PTS = %.2f secs", PTS / 90000.0f); + + optional_bytes_remaining -= 5; + + if (PTS_DTS_flags == 3) { + if (optional_bytes_remaining < 5u) { + return ERROR_MALFORMED; + } + + if (br.getBits(4) != 1u) { + return ERROR_MALFORMED; + } + + DTS = ((uint64_t)br.getBits(3)) << 30; + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + DTS |= ((uint64_t)br.getBits(15)) << 15; + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + DTS |= br.getBits(15); + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + + ALOGV("DTS = %" PRIu64, DTS); + + optional_bytes_remaining -= 5; + } + } + + if (ESCR_flag) { + if (optional_bytes_remaining < 6u) { + return ERROR_MALFORMED; + } + + br.getBits(2); + + uint64_t ESCR = ((uint64_t)br.getBits(3)) << 30; + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + ESCR |= ((uint64_t)br.getBits(15)) << 15; + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + ESCR |= br.getBits(15); + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + + ALOGV("ESCR = %" PRIu64, ESCR); + /* unsigned ESCR_extension = */br.getBits(9); + + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + + optional_bytes_remaining -= 6; + } + + if (ES_rate_flag) { + if (optional_bytes_remaining < 3u) { + return ERROR_MALFORMED; + } + + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + /* unsigned ES_rate = */br.getBits(22); + if (br.getBits(1) != 1u) { + return ERROR_MALFORMED; + } + + optional_bytes_remaining -= 3; + } + + if (br.numBitsLeft() < optional_bytes_remaining * 8) { + return ERROR_MALFORMED; + } + + br.skipBits(optional_bytes_remaining * 8); + + // ES data follows. + + if (PES_packet_length < PES_header_data_length + 3) { + return ERROR_MALFORMED; + } + + unsigned dataLength = + PES_packet_length - 3 - PES_header_data_length; + + if (br.numBitsLeft() < dataLength * 8) { + ALOGE("PES packet does not carry enough data to contain " + "payload. (numBitsLeft = %zu, required = %u)", + br.numBitsLeft(), dataLength * 8); + + return ERROR_MALFORMED; + } + + if (br.numBitsLeft() < dataLength * 8) { + return ERROR_MALFORMED; + } + + ssize_t index = mTracks.indexOfKey(stream_id); + if (index < 0 && mScanning) { + unsigned streamType; + + ssize_t streamTypeIndex; + if (mProgramStreamMapValid + && (streamTypeIndex = + mStreamTypeByESID.indexOfKey(stream_id)) >= 0) { + streamType = mStreamTypeByESID.valueAt(streamTypeIndex); + } else if ((stream_id & ~0x1f) == 0xc0) { + // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 + // or ISO/IEC 14496-3 audio + streamType = ATSParser::STREAMTYPE_MPEG2_AUDIO; + } else if ((stream_id & ~0x0f) == 0xe0) { + // ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC 14496-2 video + streamType = ATSParser::STREAMTYPE_MPEG2_VIDEO; + } else { + streamType = ATSParser::STREAMTYPE_RESERVED; + } + + index = mTracks.add( + stream_id, new Track(this, stream_id, streamType)); + } + + status_t err = OK; + + if (index >= 0) { + err = + mTracks.editValueAt(index)->appendPESData( + PTS_DTS_flags, PTS, DTS, br.data(), dataLength); + } + + br.skipBits(dataLength * 8); + + if (err != OK) { + return err; + } + } else if (stream_id == 0xbe) { // padding_stream + if (PES_packet_length == 0u) { + return ERROR_MALFORMED; + } + br.skipBits(PES_packet_length * 8); + } else { + if (PES_packet_length == 0u) { + return ERROR_MALFORMED; + } + br.skipBits(PES_packet_length * 8); + } + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2PSExtractor::Track::Track( + MPEG2PSExtractor *extractor, unsigned stream_id, unsigned stream_type) + : mExtractor(extractor), + mStreamID(stream_id), + mStreamType(stream_type), + mQueue(NULL) { + bool supported = true; + ElementaryStreamQueue::Mode mode; + + switch (mStreamType) { + case ATSParser::STREAMTYPE_H264: + mode = ElementaryStreamQueue::H264; + break; + case ATSParser::STREAMTYPE_MPEG2_AUDIO_ADTS: + mode = ElementaryStreamQueue::AAC; + break; + case ATSParser::STREAMTYPE_MPEG1_AUDIO: + case ATSParser::STREAMTYPE_MPEG2_AUDIO: + mode = ElementaryStreamQueue::MPEG_AUDIO; + break; + + case ATSParser::STREAMTYPE_MPEG1_VIDEO: + case ATSParser::STREAMTYPE_MPEG2_VIDEO: + mode = ElementaryStreamQueue::MPEG_VIDEO; + break; + + case ATSParser::STREAMTYPE_MPEG4_VIDEO: + mode = ElementaryStreamQueue::MPEG4_VIDEO; + break; + + default: + supported = false; + break; + } + + if (supported) { + mQueue = new ElementaryStreamQueue(mode); + } else { + ALOGI("unsupported stream ID 0x%02x", stream_id); + } +} + +MPEG2PSExtractor::Track::~Track() { + delete mQueue; + mQueue = NULL; +} + +status_t MPEG2PSExtractor::Track::start(MetaDataBase *) { + if (mSource == NULL) { + return NO_INIT; + } + + return mSource->start(NULL); // AnotherPacketSource::start doesn't use its argument +} + +status_t MPEG2PSExtractor::Track::stop() { + if (mSource == NULL) { + return NO_INIT; + } + + return mSource->stop(); +} + +status_t MPEG2PSExtractor::Track::getFormat(MetaDataBase &meta) { + if (mSource == NULL) { + return NO_INIT; + } + + sp<MetaData> sourceMeta = mSource->getFormat(); + meta = *sourceMeta; + return OK; +} + +status_t MPEG2PSExtractor::Track::read( + MediaBufferBase **buffer, const ReadOptions *options) { + if (mSource == NULL) { + return NO_INIT; + } + + status_t finalResult; + while (!mSource->hasBufferAvailable(&finalResult)) { + if (finalResult != OK) { + return ERROR_END_OF_STREAM; + } + + status_t err = mExtractor->feedMore(); + + if (err != OK) { + mSource->signalEOS(err); + } + } + + return mSource->read(buffer, options); +} + +status_t MPEG2PSExtractor::Track::appendPESData( + unsigned PTS_DTS_flags, + uint64_t PTS, uint64_t /* DTS */, + const uint8_t *data, size_t size) { + if (mQueue == NULL) { + return OK; + } + + int64_t timeUs; + if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) { + timeUs = (PTS * 100) / 9; + } else { + timeUs = 0; + } + + status_t err = mQueue->appendData(data, size, timeUs); + + if (err != OK) { + return err; + } + + sp<ABuffer> accessUnit; + while ((accessUnit = mQueue->dequeueAccessUnit()) != NULL) { + if (mSource == NULL) { + sp<MetaData> meta = mQueue->getFormat(); + + if (meta != NULL) { + ALOGV("Stream ID 0x%02x now has data.", mStreamID); + + mSource = new AnotherPacketSource(meta); + mSource->queueAccessUnit(accessUnit); + } + } else if (mQueue->getFormat() != NULL) { + mSource->queueAccessUnit(accessUnit); + } + } + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2PSExtractor::WrappedTrack::WrappedTrack( + MPEG2PSExtractor *extractor, const sp<Track> &track) + : mExtractor(extractor), + mTrack(track) { +} + +MPEG2PSExtractor::WrappedTrack::~WrappedTrack() { +} + +status_t MPEG2PSExtractor::WrappedTrack::start(MetaDataBase *params) { + return mTrack->start(params); +} + +status_t MPEG2PSExtractor::WrappedTrack::stop() { + return mTrack->stop(); +} + +status_t MPEG2PSExtractor::WrappedTrack::getFormat(MetaDataBase &meta) { + return mTrack->getFormat(meta); +} + +status_t MPEG2PSExtractor::WrappedTrack::read( + MediaBufferBase **buffer, const ReadOptions *options) { + return mTrack->read(buffer, options); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool SniffMPEG2PS( + DataSourceBase *source, float *confidence) { + uint8_t header[5]; + if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return false; + } + + if (memcmp("\x00\x00\x01\xba", header, 4) || (header[4] >> 6) != 1) { + return false; + } + + *confidence = 0.25f; // Slightly larger than .mp3 extractor's confidence + + return true; +} + +} // namespace android
diff --git a/media/extractors/mpeg2/MPEG2PSExtractor.h b/media/extractors/mpeg2/MPEG2PSExtractor.h new file mode 100644 index 0000000..8b9dad9 --- /dev/null +++ b/media/extractors/mpeg2/MPEG2PSExtractor.h
@@ -0,0 +1,80 @@ +/* + * 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 MPEG2_PS_EXTRACTOR_H_ + +#define MPEG2_PS_EXTRACTOR_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <media/MediaExtractor.h> +#include <media/stagefright/MetaDataBase.h> +#include <utils/threads.h> +#include <utils/KeyedVector.h> + +namespace android { + +struct ABuffer; +struct AMessage; +struct Track; +class String8; + +struct MPEG2PSExtractor : public MediaExtractor { + explicit MPEG2PSExtractor(DataSourceBase *source); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + + virtual uint32_t flags() const; + virtual const char * name() { return "MPEG2PSExtractor"; } + +protected: + virtual ~MPEG2PSExtractor(); + +private: + struct Track; + struct WrappedTrack; + + mutable Mutex mLock; + DataSourceBase *mDataSource; + + off64_t mOffset; + status_t mFinalResult; + sp<ABuffer> mBuffer; + KeyedVector<unsigned, sp<Track> > mTracks; + bool mScanning; + + bool mProgramStreamMapValid; + KeyedVector<unsigned, unsigned> mStreamTypeByESID; + + status_t feedMore(); + + status_t dequeueChunk(); + ssize_t dequeuePack(); + ssize_t dequeueSystemHeader(); + ssize_t dequeuePES(); + + DISALLOW_EVIL_CONSTRUCTORS(MPEG2PSExtractor); +}; + +bool SniffMPEG2PS(DataSourceBase *source, float *confidence); + +} // namespace android + +#endif // MPEG2_PS_EXTRACTOR_H_ +
diff --git a/media/extractors/mpeg2/MPEG2TSExtractor.cpp b/media/extractors/mpeg2/MPEG2TSExtractor.cpp new file mode 100644 index 0000000..c83f7ce --- /dev/null +++ b/media/extractors/mpeg2/MPEG2TSExtractor.cpp
@@ -0,0 +1,669 @@ +/* + * 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 "MPEG2TSExtractor" + +#include <inttypes.h> +#include <utils/Log.h> + +#include "MPEG2TSExtractor.h" + +#include <media/DataSourceBase.h> +#include <media/IStreamSource.h> +#include <media/MediaTrack.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AUtils.h> +#include <media/stagefright/foundation/MediaKeys.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <utils/String8.h> + +#include "mpeg2ts/AnotherPacketSource.h" +#include "mpeg2ts/ATSParser.h" + +#include <hidl/HybridInterface.h> +#include <android/hardware/cas/1.0/ICas.h> + +namespace android { + +using hardware::cas::V1_0::ICas; + +static const size_t kTSPacketSize = 188; +static const int kMaxDurationReadSize = 250000LL; +static const int kMaxDurationRetry = 6; + +struct MPEG2TSSource : public MediaTrack { + MPEG2TSSource( + MPEG2TSExtractor *extractor, + const sp<AnotherPacketSource> &impl, + bool doesSeek); + virtual ~MPEG2TSSource(); + + virtual status_t start(MetaDataBase *params = NULL); + virtual status_t stop(); + virtual status_t getFormat(MetaDataBase &); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL); + +private: + MPEG2TSExtractor *mExtractor; + sp<AnotherPacketSource> mImpl; + + // If there are both audio and video streams, only the video stream + // will signal seek on the extractor; otherwise the single stream will seek. + bool mDoesSeek; + + DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSSource); +}; + +MPEG2TSSource::MPEG2TSSource( + MPEG2TSExtractor *extractor, + const sp<AnotherPacketSource> &impl, + bool doesSeek) + : mExtractor(extractor), + mImpl(impl), + mDoesSeek(doesSeek) { +} + +MPEG2TSSource::~MPEG2TSSource() { +} + +status_t MPEG2TSSource::start(MetaDataBase *) { + return mImpl->start(NULL); // AnotherPacketSource::start() doesn't use its argument +} + +status_t MPEG2TSSource::stop() { + return mImpl->stop(); +} + +status_t MPEG2TSSource::getFormat(MetaDataBase &meta) { + sp<MetaData> implMeta = mImpl->getFormat(); + meta = *implMeta; + return OK; +} + +status_t MPEG2TSSource::read( + MediaBufferBase **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode seekMode; + if (mDoesSeek && options && options->getSeekTo(&seekTimeUs, &seekMode)) { + // seek is needed + status_t err = mExtractor->seek(seekTimeUs, seekMode); + if (err != OK) { + return err; + } + } + + if (mExtractor->feedUntilBufferAvailable(mImpl) != OK) { + return ERROR_END_OF_STREAM; + } + + return mImpl->read(out, options); +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2TSExtractor::MPEG2TSExtractor(DataSourceBase *source) + : mDataSource(source), + mParser(new ATSParser), + mLastSyncEvent(0), + mOffset(0) { + init(); +} + +size_t MPEG2TSExtractor::countTracks() { + return mSourceImpls.size(); +} + +MediaTrack *MPEG2TSExtractor::getTrack(size_t index) { + if (index >= mSourceImpls.size()) { + return NULL; + } + + // The seek reference track (video if present; audio otherwise) performs + // seek requests, while other tracks ignore requests. + return new MPEG2TSSource(this, mSourceImpls.editItemAt(index), + (mSeekSyncPoints == &mSyncPoints.editItemAt(index))); +} + +status_t MPEG2TSExtractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t /* flags */) { + sp<MetaData> implMeta = index < mSourceImpls.size() + ? mSourceImpls.editItemAt(index)->getFormat() : NULL; + if (implMeta == NULL) { + return UNKNOWN_ERROR; + } + meta = *implMeta; + return OK; +} + +status_t MPEG2TSExtractor::getMetaData(MetaDataBase &meta) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG2TS); + + return OK; +} + +//static +bool MPEG2TSExtractor::isScrambledFormat(MetaDataBase &format) { + const char *mime; + return format.findCString(kKeyMIMEType, &mime) + && (!strcasecmp(MEDIA_MIMETYPE_VIDEO_SCRAMBLED, mime) + || !strcasecmp(MEDIA_MIMETYPE_AUDIO_SCRAMBLED, mime)); +} + +status_t MPEG2TSExtractor::setMediaCas(const uint8_t* casToken, size_t size) { + HalToken halToken; + halToken.setToExternal((uint8_t*)casToken, size); + sp<ICas> cas = ICas::castFrom(retrieveHalInterface(halToken)); + ALOGD("setMediaCas: %p", cas.get()); + + status_t err = mParser->setMediaCas(cas); + if (err == OK) { + ALOGI("All tracks now have descramblers"); + init(); + } + return err; +} + +void MPEG2TSExtractor::addSource(const sp<AnotherPacketSource> &impl) { + bool found = false; + for (size_t i = 0; i < mSourceImpls.size(); i++) { + if (mSourceImpls[i] == impl) { + found = true; + break; + } + } + if (!found) { + mSourceImpls.push(impl); + } +} + +void MPEG2TSExtractor::init() { + bool haveAudio = false; + bool haveVideo = false; + int64_t startTime = ALooper::GetNowUs(); + + status_t err; + while ((err = feedMore(true /* isInit */)) == OK + || err == ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED) { + if (haveAudio && haveVideo) { + addSyncPoint_l(mLastSyncEvent); + mLastSyncEvent.reset(); + break; + } + if (!haveVideo) { + sp<AnotherPacketSource> impl = mParser->getSource(ATSParser::VIDEO); + + if (impl != NULL) { + sp<MetaData> format = impl->getFormat(); + if (format != NULL) { + haveVideo = true; + addSource(impl); + if (!isScrambledFormat(*(format.get()))) { + mSyncPoints.push(); + mSeekSyncPoints = &mSyncPoints.editTop(); + } + } + } + } + + if (!haveAudio) { + sp<AnotherPacketSource> impl = mParser->getSource(ATSParser::AUDIO); + + if (impl != NULL) { + sp<MetaData> format = impl->getFormat(); + if (format != NULL) { + haveAudio = true; + addSource(impl); + if (!isScrambledFormat(*(format.get()))) { + mSyncPoints.push(); + if (!haveVideo) { + mSeekSyncPoints = &mSyncPoints.editTop(); + } + } + } + } + } + + addSyncPoint_l(mLastSyncEvent); + mLastSyncEvent.reset(); + + // ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED is returned when the mpeg2ts + // is scrambled but we don't have a MediaCas object set. The extraction + // will only continue when setMediaCas() is called successfully. + if (err == ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED) { + ALOGI("stopped parsing scrambled content, " + "haveAudio=%d, haveVideo=%d, elaspedTime=%" PRId64, + haveAudio, haveVideo, ALooper::GetNowUs() - startTime); + return; + } + + // Wait only for 2 seconds to detect audio/video streams. + if (ALooper::GetNowUs() - startTime > 2000000ll) { + break; + } + } + + off64_t size; + if (mDataSource->getSize(&size) == OK && (haveAudio || haveVideo)) { + sp<AnotherPacketSource> impl = haveVideo + ? mParser->getSource(ATSParser::VIDEO) + : mParser->getSource(ATSParser::AUDIO); + size_t prevSyncSize = 1; + int64_t durationUs = -1; + List<int64_t> durations; + // Estimate duration --- stabilize until you get <500ms deviation. + while (feedMore() == OK + && ALooper::GetNowUs() - startTime <= 2000000ll) { + if (mSeekSyncPoints->size() > prevSyncSize) { + prevSyncSize = mSeekSyncPoints->size(); + int64_t diffUs = mSeekSyncPoints->keyAt(prevSyncSize - 1) + - mSeekSyncPoints->keyAt(0); + off64_t diffOffset = mSeekSyncPoints->valueAt(prevSyncSize - 1) + - mSeekSyncPoints->valueAt(0); + int64_t currentDurationUs = size * diffUs / diffOffset; + durations.push_back(currentDurationUs); + if (durations.size() > 5) { + durations.erase(durations.begin()); + int64_t min = *durations.begin(); + int64_t max = *durations.begin(); + for (auto duration : durations) { + if (min > duration) { + min = duration; + } + if (max < duration) { + max = duration; + } + } + if (max - min < 500 * 1000) { + durationUs = currentDurationUs; + break; + } + } + } + } + status_t err; + int64_t bufferedDurationUs; + bufferedDurationUs = impl->getBufferedDurationUs(&err); + if (err == ERROR_END_OF_STREAM) { + durationUs = bufferedDurationUs; + } + if (durationUs > 0) { + const sp<MetaData> meta = impl->getFormat(); + meta->setInt64(kKeyDuration, durationUs); + impl->setFormat(meta); + } else { + estimateDurationsFromTimesUsAtEnd(); + } + } + + ALOGI("haveAudio=%d, haveVideo=%d, elaspedTime=%" PRId64, + haveAudio, haveVideo, ALooper::GetNowUs() - startTime); +} + +status_t MPEG2TSExtractor::feedMore(bool isInit) { + Mutex::Autolock autoLock(mLock); + + uint8_t packet[kTSPacketSize]; + ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize); + + if (n < (ssize_t)kTSPacketSize) { + if (n >= 0) { + mParser->signalEOS(ERROR_END_OF_STREAM); + } + return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM; + } + + ATSParser::SyncEvent event(mOffset); + mOffset += n; + status_t err = mParser->feedTSPacket(packet, kTSPacketSize, &event); + if (event.hasReturnedData()) { + if (isInit) { + mLastSyncEvent = event; + } else { + addSyncPoint_l(event); + } + } + return err; +} + +void MPEG2TSExtractor::addSyncPoint_l(const ATSParser::SyncEvent &event) { + if (!event.hasReturnedData()) { + return; + } + + for (size_t i = 0; i < mSourceImpls.size(); ++i) { + if (mSourceImpls[i].get() == event.getMediaSource().get()) { + KeyedVector<int64_t, off64_t> *syncPoints = &mSyncPoints.editItemAt(i); + syncPoints->add(event.getTimeUs(), event.getOffset()); + // We're keeping the size of the sync points at most 5mb per a track. + size_t size = syncPoints->size(); + if (size >= 327680) { + int64_t firstTimeUs = syncPoints->keyAt(0); + int64_t lastTimeUs = syncPoints->keyAt(size - 1); + if (event.getTimeUs() - firstTimeUs > lastTimeUs - event.getTimeUs()) { + syncPoints->removeItemsAt(0, 4096); + } else { + syncPoints->removeItemsAt(size - 4096, 4096); + } + } + break; + } + } +} + +status_t MPEG2TSExtractor::estimateDurationsFromTimesUsAtEnd() { + if (!(mDataSource->flags() & DataSourceBase::kIsLocalFileSource)) { + return ERROR_UNSUPPORTED; + } + + off64_t size = 0; + status_t err = mDataSource->getSize(&size); + if (err != OK) { + return err; + } + + uint8_t packet[kTSPacketSize]; + const off64_t zero = 0; + off64_t offset = max(zero, size - kMaxDurationReadSize); + if (mDataSource->readAt(offset, &packet, 0) < 0) { + return ERROR_IO; + } + + int retry = 0; + bool allDurationsFound = false; + int64_t timeAnchorUs = mParser->getFirstPTSTimeUs(); + do { + int bytesRead = 0; + sp<ATSParser> parser = new ATSParser(ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE); + ATSParser::SyncEvent ev(0); + offset = max(zero, size - (kMaxDurationReadSize << retry)); + offset = (offset / kTSPacketSize) * kTSPacketSize; + for (;;) { + if (bytesRead >= kMaxDurationReadSize << max(0, retry - 1)) { + break; + } + + ssize_t n = mDataSource->readAt(offset, packet, kTSPacketSize); + if (n < 0) { + return n; + } else if (n < (ssize_t)kTSPacketSize) { + break; + } + + offset += kTSPacketSize; + bytesRead += kTSPacketSize; + err = parser->feedTSPacket(packet, kTSPacketSize, &ev); + if (err != OK) { + return err; + } + + if (ev.hasReturnedData()) { + int64_t durationUs = ev.getTimeUs(); + ATSParser::SourceType type = ev.getType(); + ev.reset(); + + int64_t firstTimeUs; + sp<AnotherPacketSource> src = mParser->getSource(type); + if (src == NULL || src->nextBufferTime(&firstTimeUs) != OK) { + continue; + } + durationUs += src->getEstimatedBufferDurationUs(); + durationUs -= timeAnchorUs; + durationUs -= firstTimeUs; + if (durationUs > 0) { + int64_t origDurationUs, lastDurationUs; + const sp<MetaData> meta = src->getFormat(); + const uint32_t kKeyLastDuration = 'ldur'; + // Require two consecutive duration calculations to be within 1 sec before + // updating; use MetaData to store previous duration estimate in per-stream + // context. + if (!meta->findInt64(kKeyDuration, &origDurationUs) + || !meta->findInt64(kKeyLastDuration, &lastDurationUs) + || (origDurationUs < durationUs + && abs(durationUs - lastDurationUs) < 60000000)) { + meta->setInt64(kKeyDuration, durationUs); + } + meta->setInt64(kKeyLastDuration, durationUs); + } + } + } + + if (!allDurationsFound) { + allDurationsFound = true; + for (auto t: {ATSParser::VIDEO, ATSParser::AUDIO}) { + sp<AnotherPacketSource> src = mParser->getSource(t); + if (src == NULL) { + continue; + } + int64_t durationUs; + const sp<MetaData> meta = src->getFormat(); + if (!meta->findInt64(kKeyDuration, &durationUs)) { + allDurationsFound = false; + break; + } + } + } + + ++retry; + } while(!allDurationsFound && offset > 0 && retry <= kMaxDurationRetry); + + return allDurationsFound? OK : ERROR_UNSUPPORTED; +} + +uint32_t MPEG2TSExtractor::flags() const { + return CAN_PAUSE | CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD; +} + +status_t MPEG2TSExtractor::seek(int64_t seekTimeUs, + const MediaTrack::ReadOptions::SeekMode &seekMode) { + if (mSeekSyncPoints == NULL || mSeekSyncPoints->isEmpty()) { + ALOGW("No sync point to seek to."); + // ... and therefore we have nothing useful to do here. + return OK; + } + + // Determine whether we're seeking beyond the known area. + bool shouldSeekBeyond = + (seekTimeUs > mSeekSyncPoints->keyAt(mSeekSyncPoints->size() - 1)); + + // Determine the sync point to seek. + size_t index = 0; + for (; index < mSeekSyncPoints->size(); ++index) { + int64_t timeUs = mSeekSyncPoints->keyAt(index); + if (timeUs > seekTimeUs) { + break; + } + } + + switch (seekMode) { + case MediaTrack::ReadOptions::SEEK_NEXT_SYNC: + if (index == mSeekSyncPoints->size()) { + ALOGW("Next sync not found; starting from the latest sync."); + --index; + } + break; + case MediaTrack::ReadOptions::SEEK_CLOSEST_SYNC: + case MediaTrack::ReadOptions::SEEK_CLOSEST: + ALOGW("seekMode not supported: %d; falling back to PREVIOUS_SYNC", + seekMode); + // fall-through + case MediaTrack::ReadOptions::SEEK_PREVIOUS_SYNC: + if (index == 0) { + ALOGW("Previous sync not found; starting from the earliest " + "sync."); + } else { + --index; + } + break; + default: + return ERROR_UNSUPPORTED; + } + if (!shouldSeekBeyond || mOffset <= mSeekSyncPoints->valueAt(index)) { + int64_t actualSeekTimeUs = mSeekSyncPoints->keyAt(index); + mOffset = mSeekSyncPoints->valueAt(index); + status_t err = queueDiscontinuityForSeek(actualSeekTimeUs); + if (err != OK) { + return err; + } + } + + if (shouldSeekBeyond) { + status_t err = seekBeyond(seekTimeUs); + if (err != OK) { + return err; + } + } + + // Fast-forward to sync frame. + for (size_t i = 0; i < mSourceImpls.size(); ++i) { + const sp<AnotherPacketSource> &impl = mSourceImpls[i]; + status_t err; + feedUntilBufferAvailable(impl); + while (impl->hasBufferAvailable(&err)) { + sp<AMessage> meta = impl->getMetaAfterLastDequeued(0); + sp<ABuffer> buffer; + if (meta == NULL) { + return UNKNOWN_ERROR; + } + int32_t sync; + if (meta->findInt32("isSync", &sync) && sync) { + break; + } + err = impl->dequeueAccessUnit(&buffer); + if (err != OK) { + return err; + } + feedUntilBufferAvailable(impl); + } + } + + return OK; +} + +status_t MPEG2TSExtractor::queueDiscontinuityForSeek(int64_t actualSeekTimeUs) { + // Signal discontinuity + sp<AMessage> extra(new AMessage); + extra->setInt64(kATSParserKeyMediaTimeUs, actualSeekTimeUs); + mParser->signalDiscontinuity(ATSParser::DISCONTINUITY_TIME, extra); + + // After discontinuity, impl should only have discontinuities + // with the last being what we queued. Dequeue them all here. + for (size_t i = 0; i < mSourceImpls.size(); ++i) { + const sp<AnotherPacketSource> &impl = mSourceImpls.itemAt(i); + sp<ABuffer> buffer; + status_t err; + while (impl->hasBufferAvailable(&err)) { + if (err != OK) { + return err; + } + err = impl->dequeueAccessUnit(&buffer); + // If the source contains anything but discontinuity, that's + // a programming mistake. + CHECK(err == INFO_DISCONTINUITY); + } + } + + // Feed until we have a buffer for each source. + for (size_t i = 0; i < mSourceImpls.size(); ++i) { + const sp<AnotherPacketSource> &impl = mSourceImpls.itemAt(i); + sp<ABuffer> buffer; + status_t err = feedUntilBufferAvailable(impl); + if (err != OK) { + return err; + } + } + + return OK; +} + +status_t MPEG2TSExtractor::seekBeyond(int64_t seekTimeUs) { + // If we're seeking beyond where we know --- read until we reach there. + size_t syncPointsSize = mSeekSyncPoints->size(); + + while (seekTimeUs > mSeekSyncPoints->keyAt( + mSeekSyncPoints->size() - 1)) { + status_t err; + if (syncPointsSize < mSeekSyncPoints->size()) { + syncPointsSize = mSeekSyncPoints->size(); + int64_t syncTimeUs = mSeekSyncPoints->keyAt(syncPointsSize - 1); + // Dequeue buffers before sync point in order to avoid too much + // cache building up. + sp<ABuffer> buffer; + for (size_t i = 0; i < mSourceImpls.size(); ++i) { + const sp<AnotherPacketSource> &impl = mSourceImpls[i]; + int64_t timeUs; + while ((err = impl->nextBufferTime(&timeUs)) == OK) { + if (timeUs < syncTimeUs) { + impl->dequeueAccessUnit(&buffer); + } else { + break; + } + } + if (err != OK && err != -EWOULDBLOCK) { + return err; + } + } + } + if (feedMore() != OK) { + return ERROR_END_OF_STREAM; + } + } + + return OK; +} + +status_t MPEG2TSExtractor::feedUntilBufferAvailable( + const sp<AnotherPacketSource> &impl) { + status_t finalResult; + while (!impl->hasBufferAvailable(&finalResult)) { + if (finalResult != OK) { + return finalResult; + } + + status_t err = feedMore(); + if (err != OK) { + impl->signalEOS(err); + } + } + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool SniffMPEG2TS(DataSourceBase *source, float *confidence) { + for (int i = 0; i < 5; ++i) { + char header; + if (source->readAt(kTSPacketSize * i, &header, 1) != 1 + || header != 0x47) { + return false; + } + } + + *confidence = 0.1f; + + return true; +} + +} // namespace android
diff --git a/media/extractors/mpeg2/MPEG2TSExtractor.h b/media/extractors/mpeg2/MPEG2TSExtractor.h new file mode 100644 index 0000000..cbdd3cb --- /dev/null +++ b/media/extractors/mpeg2/MPEG2TSExtractor.h
@@ -0,0 +1,108 @@ +/* + + * 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 MPEG2_TS_EXTRACTOR_H_ + +#define MPEG2_TS_EXTRACTOR_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <media/MediaExtractor.h> +#include <media/MediaTrack.h> +#include <media/stagefright/MetaDataBase.h> +#include <utils/threads.h> +#include <utils/KeyedVector.h> +#include <utils/Vector.h> + +#include "mpeg2ts/ATSParser.h" + +namespace android { + +struct AMessage; +struct AnotherPacketSource; +struct ATSParser; +class DataSourceBase; +struct MPEG2TSSource; +class String8; + +struct MPEG2TSExtractor : public MediaExtractor { + explicit MPEG2TSExtractor(DataSourceBase *source); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase &meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + + virtual status_t setMediaCas(const uint8_t* /*casToken*/, size_t /*size*/) override; + + virtual uint32_t flags() const; + virtual const char * name() { return "MPEG2TSExtractor"; } + +private: + friend struct MPEG2TSSource; + + mutable Mutex mLock; + + DataSourceBase *mDataSource; + + sp<ATSParser> mParser; + + // Used to remember SyncEvent occurred in feedMore() when called from init(), + // because init() needs to update |mSourceImpls| before adding SyncPoint. + ATSParser::SyncEvent mLastSyncEvent; + + Vector<sp<AnotherPacketSource> > mSourceImpls; + + Vector<KeyedVector<int64_t, off64_t> > mSyncPoints; + // Sync points used for seeking --- normally one for video track is used. + // If no video track is present, audio track will be used instead. + KeyedVector<int64_t, off64_t> *mSeekSyncPoints; + + off64_t mOffset; + + static bool isScrambledFormat(MetaDataBase &format); + + void init(); + void addSource(const sp<AnotherPacketSource> &impl); + // Try to feed more data from source to parser. + // |isInit| means this function is called inside init(). This is a signal to + // save SyncEvent so that init() can add SyncPoint after it updates |mSourceImpls|. + // This function returns OK if expected amount of data is fed from DataSourceBase to + // parser and is successfully parsed. Otherwise, various error codes could be + // returned, e.g., ERROR_END_OF_STREAM, or no data availalbe from DataSourceBase, or + // the data has syntax error during parsing, etc. + status_t feedMore(bool isInit = false); + status_t seek(int64_t seekTimeUs, + const MediaSource::ReadOptions::SeekMode& seekMode); + status_t queueDiscontinuityForSeek(int64_t actualSeekTimeUs); + status_t seekBeyond(int64_t seekTimeUs); + + status_t feedUntilBufferAvailable(const sp<AnotherPacketSource> &impl); + + // Add a SynPoint derived from |event|. + void addSyncPoint_l(const ATSParser::SyncEvent &event); + + status_t estimateDurationsFromTimesUsAtEnd(); + + DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSExtractor); +}; + +bool SniffMPEG2TS(DataSourceBase *source, float *confidence); + +} // namespace android + +#endif // MPEG2_TS_EXTRACTOR_H_
diff --git a/media/libstagefright/matroska/NOTICE b/media/extractors/mpeg2/NOTICE similarity index 100% copy from media/libstagefright/matroska/NOTICE copy to media/extractors/mpeg2/NOTICE
diff --git a/media/extractors/mpeg2/exports.lds b/media/extractors/mpeg2/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/mpeg2/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/ogg/Android.bp b/media/extractors/ogg/Android.bp new file mode 100644 index 0000000..7c6fc75 --- /dev/null +++ b/media/extractors/ogg/Android.bp
@@ -0,0 +1,44 @@ +cc_library_shared { + + srcs: ["OggExtractor.cpp"], + + include_dirs: [ + "frameworks/av/media/libstagefright/include", + "external/tremolo", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + ], + + static_libs: [ + "libstagefright_foundation", + "libutils", + "libvorbisidec", + ], + + name: "liboggextractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/libstagefright/matroska/MODULE_LICENSE_APACHE2 b/media/extractors/ogg/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/libstagefright/matroska/MODULE_LICENSE_APACHE2 copy to media/extractors/ogg/MODULE_LICENSE_APACHE2
diff --git a/media/libstagefright/matroska/NOTICE b/media/extractors/ogg/NOTICE similarity index 100% copy from media/libstagefright/matroska/NOTICE copy to media/extractors/ogg/NOTICE
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp new file mode 100644 index 0000000..b2fe69c --- /dev/null +++ b/media/extractors/ogg/OggExtractor.cpp
@@ -0,0 +1,1287 @@ +/* + * 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 "OggExtractor" +#include <utils/Log.h> + +#include "OggExtractor.h" + +#include <cutils/properties.h> +#include <media/DataSourceBase.h> +#include <media/ExtractorUtils.h> +#include <media/MediaTrack.h> +#include <media/VorbisComment.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/base64.h> +#include <media/stagefright/foundation/ByteUtils.h> +#include <media/stagefright/MediaBufferBase.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaDataBase.h> +#include <utils/String8.h> + +extern "C" { + #include <Tremolo/codec_internal.h> + + int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb); + int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb); + int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb); + long vorbis_packet_blocksize(vorbis_info *vi,ogg_packet *op); +} + +namespace android { + +struct OggSource : public MediaTrack { + explicit OggSource(OggExtractor *extractor); + + virtual status_t getFormat(MetaDataBase &); + + virtual status_t start(MetaDataBase *params = NULL); + virtual status_t stop(); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~OggSource(); + +private: + OggExtractor *mExtractor; + bool mStarted; + + OggSource(const OggSource &); + OggSource &operator=(const OggSource &); +}; + +struct MyOggExtractor { + MyOggExtractor( + DataSourceBase *source, + const char *mimeType, + size_t numHeaders, + int64_t seekPreRollUs); + virtual ~MyOggExtractor(); + + status_t getFormat(MetaDataBase &) const; + + // Returns an approximate bitrate in bits per second. + virtual uint64_t approxBitrate() const = 0; + + status_t seekToTime(int64_t timeUs); + status_t seekToOffset(off64_t offset); + virtual status_t readNextPacket(MediaBufferBase **buffer) = 0; + + status_t init(); + + status_t getFileMetaData(MetaDataBase &meta) { + meta = mFileMeta; + return OK; + } + +protected: + struct Page { + uint64_t mGranulePosition; + int32_t mPrevPacketSize; + uint64_t mPrevPacketPos; + uint32_t mSerialNo; + uint32_t mPageNo; + uint8_t mFlags; + uint8_t mNumSegments; + uint8_t mLace[255]; + }; + + struct TOCEntry { + off64_t mPageOffset; + int64_t mTimeUs; + }; + + DataSourceBase *mSource; + off64_t mOffset; + Page mCurrentPage; + uint64_t mCurGranulePosition; + uint64_t mPrevGranulePosition; + size_t mCurrentPageSize; + bool mFirstPacketInPage; + uint64_t mCurrentPageSamples; + size_t mNextLaceIndex; + + const char *mMimeType; + size_t mNumHeaders; + int64_t mSeekPreRollUs; + + off64_t mFirstDataOffset; + + vorbis_info mVi; + vorbis_comment mVc; + + MetaDataBase mMeta; + MetaDataBase mFileMeta; + + Vector<TOCEntry> mTableOfContents; + + ssize_t readPage(off64_t offset, Page *page); + status_t findNextPage(off64_t startOffset, off64_t *pageOffset); + + virtual int64_t getTimeUsOfGranule(uint64_t granulePos) const = 0; + + // Extract codec format, metadata tags, and various codec specific data; + // the format and CSD's are required to setup the decoders for the enclosed media content. + // + // Valid values for `type` are: + // 1 - bitstream identification header + // 3 - comment header + // 5 - codec setup header (Vorbis only) + virtual status_t verifyHeader(MediaBufferBase *buffer, uint8_t type) = 0; + + // Read the next ogg packet from the underlying data source; optionally + // calculate the timestamp for the output packet whilst pretending + // that we are parsing an Ogg Vorbis stream. + // + // *buffer is NULL'ed out immediately upon entry, and if successful a new buffer is allocated; + // clients are responsible for releasing the original buffer. + status_t _readNextPacket(MediaBufferBase **buffer, bool calcVorbisTimestamp); + + int32_t getPacketBlockSize(MediaBufferBase *buffer); + + void parseFileMetaData(); + + status_t findPrevGranulePosition(off64_t pageOffset, uint64_t *granulePos); + + void buildTableOfContents(); + + MyOggExtractor(const MyOggExtractor &); + MyOggExtractor &operator=(const MyOggExtractor &); +}; + +struct MyVorbisExtractor : public MyOggExtractor { + explicit MyVorbisExtractor(DataSourceBase *source) + : MyOggExtractor(source, + MEDIA_MIMETYPE_AUDIO_VORBIS, + /* numHeaders */ 3, + /* seekPreRollUs */ 0) { + } + + virtual uint64_t approxBitrate() const; + + virtual status_t readNextPacket(MediaBufferBase **buffer) { + return _readNextPacket(buffer, /* calcVorbisTimestamp = */ true); + } + +protected: + virtual int64_t getTimeUsOfGranule(uint64_t granulePos) const { + if (granulePos > INT64_MAX / 1000000ll) { + return INT64_MAX; + } + return granulePos * 1000000ll / mVi.rate; + } + + virtual status_t verifyHeader(MediaBufferBase *buffer, uint8_t type); +}; + +struct MyOpusExtractor : public MyOggExtractor { + static const int32_t kOpusSampleRate = 48000; + static const int64_t kOpusSeekPreRollUs = 80000; // 80 ms + + explicit MyOpusExtractor(DataSourceBase *source) + : MyOggExtractor(source, MEDIA_MIMETYPE_AUDIO_OPUS, /*numHeaders*/ 2, kOpusSeekPreRollUs), + mChannelCount(0), + mCodecDelay(0), + mStartGranulePosition(-1) { + } + + virtual uint64_t approxBitrate() const { + return 0; + } + + virtual status_t readNextPacket(MediaBufferBase **buffer); + +protected: + virtual int64_t getTimeUsOfGranule(uint64_t granulePos) const; + virtual status_t verifyHeader(MediaBufferBase *buffer, uint8_t type); + +private: + status_t verifyOpusHeader(MediaBufferBase *buffer); + status_t verifyOpusComments(MediaBufferBase *buffer); + uint32_t getNumSamplesInPacket(MediaBufferBase *buffer) const; + + uint8_t mChannelCount; + uint16_t mCodecDelay; + int64_t mStartGranulePosition; +}; + +//////////////////////////////////////////////////////////////////////////////// + +OggSource::OggSource(OggExtractor *extractor) + : mExtractor(extractor), + mStarted(false) { +} + +OggSource::~OggSource() { + if (mStarted) { + stop(); + } +} + +status_t OggSource::getFormat(MetaDataBase &meta) { + return mExtractor->mImpl->getFormat(meta); +} + +status_t OggSource::start(MetaDataBase * /* params */) { + if (mStarted) { + return INVALID_OPERATION; + } + + mStarted = true; + + return OK; +} + +status_t OggSource::stop() { + mStarted = false; + + return OK; +} + +status_t OggSource::read( + MediaBufferBase **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (options && options->getSeekTo(&seekTimeUs, &mode)) { + status_t err = mExtractor->mImpl->seekToTime(seekTimeUs); + if (err != OK) { + return err; + } + } + + MediaBufferBase *packet; + status_t err = mExtractor->mImpl->readNextPacket(&packet); + + if (err != OK) { + return err; + } + +#if 0 + int64_t timeUs; + if (packet->meta_data().findInt64(kKeyTime, &timeUs)) { + ALOGI("found time = %lld us", timeUs); + } else { + ALOGI("NO time"); + } +#endif + + packet->meta_data().setInt32(kKeyIsSyncFrame, 1); + + *out = packet; + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +MyOggExtractor::MyOggExtractor( + DataSourceBase *source, + const char *mimeType, + size_t numHeaders, + int64_t seekPreRollUs) + : mSource(source), + mOffset(0), + mCurGranulePosition(0), + mPrevGranulePosition(0), + mCurrentPageSize(0), + mFirstPacketInPage(true), + mCurrentPageSamples(0), + mNextLaceIndex(0), + mMimeType(mimeType), + mNumHeaders(numHeaders), + mSeekPreRollUs(seekPreRollUs), + mFirstDataOffset(-1) { + mCurrentPage.mNumSegments = 0; + + vorbis_info_init(&mVi); + vorbis_comment_init(&mVc); +} + +MyOggExtractor::~MyOggExtractor() { + vorbis_comment_clear(&mVc); + vorbis_info_clear(&mVi); +} + +status_t MyOggExtractor::getFormat(MetaDataBase &meta) const { + meta = mMeta; + return OK; +} + +status_t MyOggExtractor::findNextPage( + off64_t startOffset, off64_t *pageOffset) { + *pageOffset = startOffset; + + for (;;) { + char signature[4]; + ssize_t n = mSource->readAt(*pageOffset, &signature, 4); + + if (n < 4) { + *pageOffset = 0; + + return (n < 0) ? n : (status_t)ERROR_END_OF_STREAM; + } + + if (!memcmp(signature, "OggS", 4)) { + if (*pageOffset > startOffset) { + ALOGV("skipped %lld bytes of junk to reach next frame", + (long long)(*pageOffset - startOffset)); + } + + return OK; + } + + ++*pageOffset; + } +} + +// Given the offset of the "current" page, find the page immediately preceding +// it (if any) and return its granule position. +// To do this we back up from the "current" page's offset until we find any +// page preceding it and then scan forward to just before the current page. +status_t MyOggExtractor::findPrevGranulePosition( + off64_t pageOffset, uint64_t *granulePos) { + *granulePos = 0; + + off64_t prevPageOffset = 0; + off64_t prevGuess = pageOffset; + for (;;) { + if (prevGuess >= 5000) { + prevGuess -= 5000; + } else { + prevGuess = 0; + } + + ALOGV("backing up %lld bytes", (long long)(pageOffset - prevGuess)); + + status_t err = findNextPage(prevGuess, &prevPageOffset); + if (err == ERROR_END_OF_STREAM) { + // We are at the last page and didn't back off enough; + // back off 5000 bytes more and try again. + continue; + } else if (err != OK) { + return err; + } + + if (prevPageOffset < pageOffset || prevGuess == 0) { + break; + } + } + + if (prevPageOffset == pageOffset) { + // We did not find a page preceding this one. + return UNKNOWN_ERROR; + } + + ALOGV("prevPageOffset at %lld, pageOffset at %lld", + (long long)prevPageOffset, (long long)pageOffset); + + for (;;) { + Page prevPage; + ssize_t n = readPage(prevPageOffset, &prevPage); + + if (n <= 0) { + return (status_t)n; + } + + prevPageOffset += n; + + if (prevPageOffset == pageOffset) { + *granulePos = prevPage.mGranulePosition; + return OK; + } + } +} + +status_t MyOggExtractor::seekToTime(int64_t timeUs) { + timeUs -= mSeekPreRollUs; + if (timeUs < 0) { + timeUs = 0; + } + + if (mTableOfContents.isEmpty()) { + // Perform approximate seeking based on avg. bitrate. + uint64_t bps = approxBitrate(); + if (bps <= 0) { + return INVALID_OPERATION; + } + + off64_t pos = timeUs * bps / 8000000ll; + + ALOGV("seeking to offset %lld", (long long)pos); + return seekToOffset(pos); + } + + size_t left = 0; + size_t right_plus_one = mTableOfContents.size(); + while (left < right_plus_one) { + size_t center = left + (right_plus_one - left) / 2; + + const TOCEntry &entry = mTableOfContents.itemAt(center); + + if (timeUs < entry.mTimeUs) { + right_plus_one = center; + } else if (timeUs > entry.mTimeUs) { + left = center + 1; + } else { + left = center; + break; + } + } + + if (left == mTableOfContents.size()) { + --left; + } + + const TOCEntry &entry = mTableOfContents.itemAt(left); + + ALOGV("seeking to entry %zu / %zu at offset %lld", + left, mTableOfContents.size(), (long long)entry.mPageOffset); + + return seekToOffset(entry.mPageOffset); +} + +status_t MyOggExtractor::seekToOffset(off64_t offset) { + if (mFirstDataOffset >= 0 && offset < mFirstDataOffset) { + // Once we know where the actual audio data starts (past the headers) + // don't ever seek to anywhere before that. + offset = mFirstDataOffset; + } + + off64_t pageOffset; + status_t err = findNextPage(offset, &pageOffset); + + if (err != OK) { + return err; + } + + // We found the page we wanted to seek to, but we'll also need + // the page preceding it to determine how many valid samples are on + // this page. + findPrevGranulePosition(pageOffset, &mPrevGranulePosition); + + mOffset = pageOffset; + + mCurrentPageSize = 0; + mFirstPacketInPage = true; + mCurrentPageSamples = 0; + mCurrentPage.mNumSegments = 0; + mCurrentPage.mPrevPacketSize = -1; + mNextLaceIndex = 0; + + // XXX what if new page continues packet from last??? + + return OK; +} + +ssize_t MyOggExtractor::readPage(off64_t offset, Page *page) { + uint8_t header[27]; + ssize_t n; + if ((n = mSource->readAt(offset, header, sizeof(header))) + < (ssize_t)sizeof(header)) { + ALOGV("failed to read %zu bytes at offset %#016llx, got %zd bytes", + sizeof(header), (long long)offset, n); + + if (n < 0) { + return n; + } else if (n == 0) { + return ERROR_END_OF_STREAM; + } else { + return ERROR_IO; + } + } + + if (memcmp(header, "OggS", 4)) { + return ERROR_MALFORMED; + } + + if (header[4] != 0) { + // Wrong version. + + return ERROR_UNSUPPORTED; + } + + page->mFlags = header[5]; + + if (page->mFlags & ~7) { + // Only bits 0-2 are defined in version 0. + return ERROR_MALFORMED; + } + + page->mGranulePosition = U64LE_AT(&header[6]); + +#if 0 + printf("granulePosition = %llu (0x%llx)\n", + page->mGranulePosition, page->mGranulePosition); +#endif + + page->mSerialNo = U32LE_AT(&header[14]); + page->mPageNo = U32LE_AT(&header[18]); + + page->mNumSegments = header[26]; + if (mSource->readAt( + offset + sizeof(header), page->mLace, page->mNumSegments) + < (ssize_t)page->mNumSegments) { + return ERROR_IO; + } + + size_t totalSize = 0;; + for (size_t i = 0; i < page->mNumSegments; ++i) { + totalSize += page->mLace[i]; + } + +#if 0 + String8 tmp; + for (size_t i = 0; i < page->mNumSegments; ++i) { + char x[32]; + sprintf(x, "%s%u", i > 0 ? ", " : "", (unsigned)page->mLace[i]); + + tmp.append(x); + } + + ALOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string()); +#endif + + return sizeof(header) + page->mNumSegments + totalSize; +} + +status_t MyOpusExtractor::readNextPacket(MediaBufferBase **out) { + if (mOffset <= mFirstDataOffset && mStartGranulePosition < 0) { + // The first sample might not start at time 0; find out where by subtracting + // the number of samples on the first page from the granule position + // (position of last complete sample) of the first page. This happens + // the first time before we attempt to read a packet from the first page. + MediaBufferBase *mBuf; + uint32_t numSamples = 0; + uint64_t curGranulePosition = 0; + while (true) { + status_t err = _readNextPacket(&mBuf, /* calcVorbisTimestamp = */false); + if (err != OK && err != ERROR_END_OF_STREAM) { + return err; + } + // First two pages are header pages. + if (err == ERROR_END_OF_STREAM || mCurrentPage.mPageNo > 2) { + if (mBuf != NULL) { + mBuf->release(); + mBuf = NULL; + } + break; + } + curGranulePosition = mCurrentPage.mGranulePosition; + numSamples += getNumSamplesInPacket(mBuf); + mBuf->release(); + mBuf = NULL; + } + + if (curGranulePosition > numSamples) { + mStartGranulePosition = curGranulePosition - numSamples; + } else { + mStartGranulePosition = 0; + } + seekToOffset(0); + } + + status_t err = _readNextPacket(out, /* calcVorbisTimestamp = */false); + if (err != OK) { + return err; + } + + int32_t currentPageSamples; + // Calculate timestamps by accumulating durations starting from the first sample of a page; + // We assume that we only seek to page boundaries. + if ((*out)->meta_data().findInt32(kKeyValidSamples, ¤tPageSamples)) { + // first packet in page + if (mOffset == mFirstDataOffset) { + currentPageSamples -= mStartGranulePosition; + (*out)->meta_data().setInt32(kKeyValidSamples, currentPageSamples); + } + mCurGranulePosition = mCurrentPage.mGranulePosition - currentPageSamples; + } + + int64_t timeUs = getTimeUsOfGranule(mCurGranulePosition); + (*out)->meta_data().setInt64(kKeyTime, timeUs); + + uint32_t frames = getNumSamplesInPacket(*out); + mCurGranulePosition += frames; + return OK; +} + +uint32_t MyOpusExtractor::getNumSamplesInPacket(MediaBufferBase *buffer) const { + if (buffer == NULL || buffer->range_length() < 1) { + return 0; + } + + uint8_t *data = (uint8_t *)buffer->data() + buffer->range_offset(); + uint8_t toc = data[0]; + uint8_t config = (toc >> 3) & 0x1f; + uint32_t frameSizesUs[] = { + 10000, 20000, 40000, 60000, // 0...3 + 10000, 20000, 40000, 60000, // 4...7 + 10000, 20000, 40000, 60000, // 8...11 + 10000, 20000, // 12...13 + 10000, 20000, // 14...15 + 2500, 5000, 10000, 20000, // 16...19 + 2500, 5000, 10000, 20000, // 20...23 + 2500, 5000, 10000, 20000, // 24...27 + 2500, 5000, 10000, 20000 // 28...31 + }; + uint32_t frameSizeUs = frameSizesUs[config]; + + uint32_t numFrames; + uint8_t c = toc & 3; + switch (c) { + case 0: + numFrames = 1; + break; + case 1: + case 2: + numFrames = 2; + break; + case 3: + if (buffer->range_length() < 3) { + numFrames = 0; + } else { + numFrames = data[2] & 0x3f; + } + break; + default: + TRESPASS(); + } + + uint32_t numSamples = frameSizeUs * numFrames * kOpusSampleRate / 1000000; + return numSamples; +} + +status_t MyOggExtractor::_readNextPacket(MediaBufferBase **out, bool calcVorbisTimestamp) { + *out = NULL; + + MediaBufferBase *buffer = NULL; + int64_t timeUs = -1; + + for (;;) { + size_t i; + size_t packetSize = 0; + bool gotFullPacket = false; + for (i = mNextLaceIndex; i < mCurrentPage.mNumSegments; ++i) { + uint8_t lace = mCurrentPage.mLace[i]; + + packetSize += lace; + + if (lace < 255) { + gotFullPacket = true; + ++i; + break; + } + } + + if (mNextLaceIndex < mCurrentPage.mNumSegments) { + off64_t dataOffset = mOffset + 27 + mCurrentPage.mNumSegments; + for (size_t j = 0; j < mNextLaceIndex; ++j) { + dataOffset += mCurrentPage.mLace[j]; + } + + size_t fullSize = packetSize; + if (buffer != NULL) { + fullSize += buffer->range_length(); + } + if (fullSize > 16 * 1024 * 1024) { // arbitrary limit of 16 MB packet size + if (buffer != NULL) { + buffer->release(); + } + ALOGE("b/36592202"); + return ERROR_MALFORMED; + } + MediaBufferBase *tmp = MediaBufferBase::Create(fullSize); + if (tmp == NULL) { + if (buffer != NULL) { + buffer->release(); + } + ALOGE("b/36592202"); + return ERROR_MALFORMED; + } + if (buffer != NULL) { + memcpy(tmp->data(), buffer->data(), buffer->range_length()); + tmp->set_range(0, buffer->range_length()); + buffer->release(); + } else { + tmp->set_range(0, 0); + } + buffer = tmp; + + ssize_t n = mSource->readAt( + dataOffset, + (uint8_t *)buffer->data() + buffer->range_length(), + packetSize); + + if (n < (ssize_t)packetSize) { + buffer->release(); + ALOGV("failed to read %zu bytes at %#016llx, got %zd bytes", + packetSize, (long long)dataOffset, n); + return ERROR_IO; + } + + buffer->set_range(0, fullSize); + + mNextLaceIndex = i; + + if (gotFullPacket) { + // We've just read the entire packet. + + if (mFirstPacketInPage) { + buffer->meta_data().setInt32( + kKeyValidSamples, mCurrentPageSamples); + mFirstPacketInPage = false; + } + + if (calcVorbisTimestamp) { + int32_t curBlockSize = getPacketBlockSize(buffer); + if (mCurrentPage.mPrevPacketSize < 0) { + mCurrentPage.mPrevPacketSize = curBlockSize; + mCurrentPage.mPrevPacketPos = + mCurrentPage.mGranulePosition - mCurrentPageSamples; + timeUs = mCurrentPage.mPrevPacketPos * 1000000ll / mVi.rate; + } else { + // The effective block size is the average of the two overlapped blocks + int32_t actualBlockSize = + (curBlockSize + mCurrentPage.mPrevPacketSize) / 2; + timeUs = mCurrentPage.mPrevPacketPos * 1000000ll / mVi.rate; + // The actual size output by the decoder will be half the effective + // size, due to the overlap + mCurrentPage.mPrevPacketPos += actualBlockSize / 2; + mCurrentPage.mPrevPacketSize = curBlockSize; + } + buffer->meta_data().setInt64(kKeyTime, timeUs); + } + *out = buffer; + + return OK; + } + + // fall through, the buffer now contains the start of the packet. + } + + CHECK_EQ(mNextLaceIndex, mCurrentPage.mNumSegments); + + mOffset += mCurrentPageSize; + ssize_t n = readPage(mOffset, &mCurrentPage); + + if (n <= 0) { + if (buffer) { + buffer->release(); + buffer = NULL; + } + + ALOGV("readPage returned %zd", n); + + return n < 0 ? n : (status_t)ERROR_END_OF_STREAM; + } + + // Prevent a harmless unsigned integer overflow by clamping to 0 + if (mCurrentPage.mGranulePosition >= mPrevGranulePosition) { + mCurrentPageSamples = + mCurrentPage.mGranulePosition - mPrevGranulePosition; + } else { + mCurrentPageSamples = 0; + } + mFirstPacketInPage = true; + + mPrevGranulePosition = mCurrentPage.mGranulePosition; + + mCurrentPageSize = n; + mNextLaceIndex = 0; + + if (buffer != NULL) { + if ((mCurrentPage.mFlags & 1) == 0) { + // This page does not continue the packet, i.e. the packet + // is already complete. + + if (timeUs >= 0) { + buffer->meta_data().setInt64(kKeyTime, timeUs); + } + + buffer->meta_data().setInt32( + kKeyValidSamples, mCurrentPageSamples); + mFirstPacketInPage = false; + + *out = buffer; + + return OK; + } + } + } +} + +status_t MyOggExtractor::init() { + mMeta.setCString(kKeyMIMEType, mMimeType); + + status_t err; + MediaBufferBase *packet; + for (size_t i = 0; i < mNumHeaders; ++i) { + // ignore timestamp for configuration packets + if ((err = _readNextPacket(&packet, /* calcVorbisTimestamp = */ false)) != OK) { + return err; + } + ALOGV("read packet of size %zu\n", packet->range_length()); + err = verifyHeader(packet, /* type = */ i * 2 + 1); + packet->release(); + packet = NULL; + if (err != OK) { + return err; + } + } + + mFirstDataOffset = mOffset + mCurrentPageSize; + + off64_t size; + uint64_t lastGranulePosition; + if (!(mSource->flags() & DataSourceBase::kIsCachingDataSource) + && mSource->getSize(&size) == OK + && findPrevGranulePosition(size, &lastGranulePosition) == OK) { + // Let's assume it's cheap to seek to the end. + // The granule position of the final page in the stream will + // give us the exact duration of the content, something that + // we can only approximate using avg. bitrate if seeking to + // the end is too expensive or impossible (live streaming). + + int64_t durationUs = getTimeUsOfGranule(lastGranulePosition); + + mMeta.setInt64(kKeyDuration, durationUs); + + buildTableOfContents(); + } + + return OK; +} + +void MyOggExtractor::buildTableOfContents() { + off64_t offset = mFirstDataOffset; + Page page; + ssize_t pageSize; + while ((pageSize = readPage(offset, &page)) > 0) { + mTableOfContents.push(); + + TOCEntry &entry = + mTableOfContents.editItemAt(mTableOfContents.size() - 1); + + entry.mPageOffset = offset; + entry.mTimeUs = getTimeUsOfGranule(page.mGranulePosition); + + offset += (size_t)pageSize; + } + + // Limit the maximum amount of RAM we spend on the table of contents, + // if necessary thin out the table evenly to trim it down to maximum + // size. + + static const size_t kMaxTOCSize = 8192; + static const size_t kMaxNumTOCEntries = kMaxTOCSize / sizeof(TOCEntry); + + size_t numerator = mTableOfContents.size(); + + if (numerator > kMaxNumTOCEntries) { + size_t denom = numerator - kMaxNumTOCEntries; + + size_t accum = 0; + for (ssize_t i = mTableOfContents.size() - 1; i >= 0; --i) { + accum += denom; + if (accum >= numerator) { + mTableOfContents.removeAt(i); + accum -= numerator; + } + } + } +} + +int32_t MyOggExtractor::getPacketBlockSize(MediaBufferBase *buffer) { + const uint8_t *data = + (const uint8_t *)buffer->data() + buffer->range_offset(); + + size_t size = buffer->range_length(); + + ogg_buffer buf; + buf.data = (uint8_t *)data; + buf.size = size; + buf.refcount = 1; + buf.ptr.owner = NULL; + + ogg_reference ref; + ref.buffer = &buf; + ref.begin = 0; + ref.length = size; + ref.next = NULL; + + ogg_packet pack; + pack.packet = &ref; + pack.bytes = ref.length; + pack.b_o_s = 0; + pack.e_o_s = 0; + pack.granulepos = 0; + pack.packetno = 0; + + return vorbis_packet_blocksize(&mVi, &pack); +} + +int64_t MyOpusExtractor::getTimeUsOfGranule(uint64_t granulePos) const { + uint64_t pcmSamplePosition = 0; + if (granulePos > mCodecDelay) { + pcmSamplePosition = granulePos - mCodecDelay; + } + if (pcmSamplePosition > INT64_MAX / 1000000ll) { + return INT64_MAX; + } + return pcmSamplePosition * 1000000ll / kOpusSampleRate; +} + +status_t MyOpusExtractor::verifyHeader(MediaBufferBase *buffer, uint8_t type) { + switch (type) { + // there are actually no header types defined in the Opus spec; we choose 1 and 3 to mean + // header and comments such that we can share code with MyVorbisExtractor. + case 1: + return verifyOpusHeader(buffer); + case 3: + return verifyOpusComments(buffer); + default: + return INVALID_OPERATION; + } +} + +status_t MyOpusExtractor::verifyOpusHeader(MediaBufferBase *buffer) { + const size_t kOpusHeaderSize = 19; + const uint8_t *data = + (const uint8_t *)buffer->data() + buffer->range_offset(); + + size_t size = buffer->range_length(); + + if (size < kOpusHeaderSize + || memcmp(data, "OpusHead", 8) + || /* version = */ data[8] != 1) { + return ERROR_MALFORMED; + } + + mChannelCount = data[9]; + mCodecDelay = U16LE_AT(&data[10]); + + mMeta.setData(kKeyOpusHeader, 0, data, size); + mMeta.setInt32(kKeySampleRate, kOpusSampleRate); + mMeta.setInt32(kKeyChannelCount, mChannelCount); + mMeta.setInt64(kKeyOpusSeekPreRoll /* ns */, kOpusSeekPreRollUs * 1000 /* = 80 ms*/); + mMeta.setInt64(kKeyOpusCodecDelay /* ns */, + mCodecDelay /* sample/s */ * 1000000000ll / kOpusSampleRate); + + return OK; +} + +status_t MyOpusExtractor::verifyOpusComments(MediaBufferBase *buffer) { + // add artificial framing bit so we can reuse _vorbis_unpack_comment + int32_t commentSize = buffer->range_length() + 1; + auto tmp = heapbuffer<uint8_t>(commentSize); + uint8_t *commentData = tmp.get(); + if (commentData == nullptr) { + return ERROR_MALFORMED; + } + + memcpy(commentData, + (uint8_t *)buffer->data() + buffer->range_offset(), + buffer->range_length()); + + ogg_buffer buf; + buf.data = commentData; + buf.size = commentSize; + buf.refcount = 1; + buf.ptr.owner = NULL; + + ogg_reference ref; + ref.buffer = &buf; + ref.begin = 0; + ref.length = commentSize; + ref.next = NULL; + + oggpack_buffer bits; + oggpack_readinit(&bits, &ref); + + // skip 'OpusTags' + const char *OpusTags = "OpusTags"; + const int32_t headerLen = strlen(OpusTags); + int32_t framingBitOffset = headerLen; + for (int i = 0; i < headerLen; ++i) { + char chr = oggpack_read(&bits, 8); + if (chr != OpusTags[i]) { + return ERROR_MALFORMED; + } + } + + int32_t vendorLen = oggpack_read(&bits, 32); + framingBitOffset += 4; + if (vendorLen < 0 || vendorLen > commentSize - 8) { + return ERROR_MALFORMED; + } + // skip vendor string + framingBitOffset += vendorLen; + for (int i = 0; i < vendorLen; ++i) { + oggpack_read(&bits, 8); + } + + int32_t n = oggpack_read(&bits, 32); + framingBitOffset += 4; + if (n < 0 || n > ((commentSize - oggpack_bytes(&bits)) >> 2)) { + return ERROR_MALFORMED; + } + for (int i = 0; i < n; ++i) { + int32_t len = oggpack_read(&bits, 32); + framingBitOffset += 4; + if (len < 0 || len > (commentSize - oggpack_bytes(&bits))) { + return ERROR_MALFORMED; + } + framingBitOffset += len; + for (int j = 0; j < len; ++j) { + oggpack_read(&bits, 8); + } + } + if (framingBitOffset < 0 || framingBitOffset >= commentSize) { + return ERROR_MALFORMED; + } + commentData[framingBitOffset] = 1; + + buf.data = commentData + headerLen; + buf.size = commentSize - headerLen; + buf.refcount = 1; + buf.ptr.owner = NULL; + + ref.buffer = &buf; + ref.begin = 0; + ref.length = commentSize - headerLen; + ref.next = NULL; + + oggpack_readinit(&bits, &ref); + int err = _vorbis_unpack_comment(&mVc, &bits); + if (0 != err) { + return ERROR_MALFORMED; + } + + parseFileMetaData(); + return OK; +} + +status_t MyVorbisExtractor::verifyHeader( + MediaBufferBase *buffer, uint8_t type) { + const uint8_t *data = + (const uint8_t *)buffer->data() + buffer->range_offset(); + + size_t size = buffer->range_length(); + + if (size < 7 || data[0] != type || memcmp(&data[1], "vorbis", 6)) { + return ERROR_MALFORMED; + } + + ogg_buffer buf; + buf.data = (uint8_t *)data; + buf.size = size; + buf.refcount = 1; + buf.ptr.owner = NULL; + + ogg_reference ref; + ref.buffer = &buf; + ref.begin = 0; + ref.length = size; + ref.next = NULL; + + oggpack_buffer bits; + oggpack_readinit(&bits, &ref); + + if (oggpack_read(&bits, 8) != type) { + return ERROR_MALFORMED; + } + for (size_t i = 0; i < 6; ++i) { + oggpack_read(&bits, 8); // skip 'vorbis' + } + + switch (type) { + case 1: + { + if (0 != _vorbis_unpack_info(&mVi, &bits)) { + return ERROR_MALFORMED; + } + + mMeta.setData(kKeyVorbisInfo, 0, data, size); + mMeta.setInt32(kKeySampleRate, mVi.rate); + mMeta.setInt32(kKeyChannelCount, mVi.channels); + mMeta.setInt32(kKeyBitRate, mVi.bitrate_nominal); + + ALOGV("lower-bitrate = %ld", mVi.bitrate_lower); + ALOGV("upper-bitrate = %ld", mVi.bitrate_upper); + ALOGV("nominal-bitrate = %ld", mVi.bitrate_nominal); + ALOGV("window-bitrate = %ld", mVi.bitrate_window); + ALOGV("blocksizes: %d/%d", + vorbis_info_blocksize(&mVi, 0), + vorbis_info_blocksize(&mVi, 1) + ); + + off64_t size; + if (mSource->getSize(&size) == OK) { + uint64_t bps = approxBitrate(); + if (bps != 0) { + mMeta.setInt64(kKeyDuration, size * 8000000ll / bps); + } + } + break; + } + + case 3: + { + if (0 != _vorbis_unpack_comment(&mVc, &bits)) { + return ERROR_MALFORMED; + } + + parseFileMetaData(); + break; + } + + case 5: + { + if (0 != _vorbis_unpack_books(&mVi, &bits)) { + return ERROR_MALFORMED; + } + + mMeta.setData(kKeyVorbisBooks, 0, data, size); + break; + } + } + + return OK; +} + +uint64_t MyVorbisExtractor::approxBitrate() const { + if (mVi.bitrate_nominal != 0) { + return mVi.bitrate_nominal; + } + + return (mVi.bitrate_lower + mVi.bitrate_upper) / 2; +} + + +void MyOggExtractor::parseFileMetaData() { + mFileMeta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG); + + for (int i = 0; i < mVc.comments; ++i) { + const char *comment = mVc.user_comments[i]; + size_t commentLength = mVc.comment_lengths[i]; + parseVorbisComment(&mFileMeta, comment, commentLength); + //ALOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]); + } +} + + +//////////////////////////////////////////////////////////////////////////////// + +OggExtractor::OggExtractor(DataSourceBase *source) + : mDataSource(source), + mInitCheck(NO_INIT), + mImpl(NULL) { + for (int i = 0; i < 2; ++i) { + if (mImpl != NULL) { + delete mImpl; + } + if (i == 0) { + mImpl = new MyVorbisExtractor(mDataSource); + } else { + mImpl = new MyOpusExtractor(mDataSource); + } + mInitCheck = mImpl->seekToOffset(0); + + if (mInitCheck == OK) { + mInitCheck = mImpl->init(); + if (mInitCheck == OK) { + break; + } + } + } +} + +OggExtractor::~OggExtractor() { + delete mImpl; + mImpl = NULL; +} + +size_t OggExtractor::countTracks() { + return mInitCheck != OK ? 0 : 1; +} + +MediaTrack *OggExtractor::getTrack(size_t index) { + if (index >= 1) { + return NULL; + } + + return new OggSource(this); +} + +status_t OggExtractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t /* flags */) { + if (index >= 1) { + return UNKNOWN_ERROR; + } + + return mImpl->getFormat(meta); +} + +status_t OggExtractor::getMetaData(MetaDataBase &meta) { + return mImpl->getFileMetaData(meta); +} + +static MediaExtractor* CreateExtractor( + DataSourceBase *source, + void *) { + return new OggExtractor(source); +} + +static MediaExtractor::CreatorFunc Sniff( + DataSourceBase *source, + float *confidence, + void **, + MediaExtractor::FreeMetaFunc *) { + char tmp[4]; + if (source->readAt(0, tmp, 4) < 4 || memcmp(tmp, "OggS", 4)) { + return NULL; + } + + *confidence = 0.2f; + + return CreateExtractor; +} + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("8cc5cd06-f772-495e-8a62-cba9649374e9"), + 1, // version + "Ogg Extractor", + Sniff + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/ogg/OggExtractor.h b/media/extractors/ogg/OggExtractor.h new file mode 100644 index 0000000..9fe2944 --- /dev/null +++ b/media/extractors/ogg/OggExtractor.h
@@ -0,0 +1,60 @@ +/* + * 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 OGG_EXTRACTOR_H_ + +#define OGG_EXTRACTOR_H_ + +#include <utils/Errors.h> +#include <media/MediaExtractor.h> + +namespace android { + +struct AMessage; +class DataSourceBase; +class String8; + +struct MyOggExtractor; +struct OggSource; + +struct OggExtractor : public MediaExtractor { + explicit OggExtractor(DataSourceBase *source); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + virtual const char * name() { return "OggExtractor"; } + +protected: + virtual ~OggExtractor(); + +private: + friend struct OggSource; + + DataSourceBase *mDataSource; + status_t mInitCheck; + + MyOggExtractor *mImpl; + + OggExtractor(const OggExtractor &); + OggExtractor &operator=(const OggExtractor &); +}; + +} // namespace android + +#endif // OGG_EXTRACTOR_H_
diff --git a/media/extractors/ogg/exports.lds b/media/extractors/ogg/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/ogg/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/extractors/wav/Android.bp b/media/extractors/wav/Android.bp new file mode 100644 index 0000000..067933e --- /dev/null +++ b/media/extractors/wav/Android.bp
@@ -0,0 +1,42 @@ +cc_library_shared { + + srcs: ["WAVExtractor.cpp"], + + include_dirs: [ + "frameworks/av/media/libstagefright/include", + ], + + shared_libs: [ + "liblog", + "libmediaextractor", + ], + + static_libs: [ + "libfifo", + "libstagefright_foundation", + ], + + name: "libwavextractor", + relative_install_path: "extractors", + + compile_multilib: "first", + + cflags: [ + "-Werror", + "-Wall", + "-fvisibility=hidden", + ], + version_script: "exports.lds", + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + diag: { + cfi: true, + }, + }, + +}
diff --git a/media/libstagefright/matroska/MODULE_LICENSE_APACHE2 b/media/extractors/wav/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/libstagefright/matroska/MODULE_LICENSE_APACHE2 copy to media/extractors/wav/MODULE_LICENSE_APACHE2
diff --git a/media/libstagefright/matroska/NOTICE b/media/extractors/wav/NOTICE similarity index 100% copy from media/libstagefright/matroska/NOTICE copy to media/extractors/wav/NOTICE
diff --git a/media/extractors/wav/WAVExtractor.cpp b/media/extractors/wav/WAVExtractor.cpp new file mode 100644 index 0000000..f5a1b01 --- /dev/null +++ b/media/extractors/wav/WAVExtractor.cpp
@@ -0,0 +1,594 @@ +/* + * Copyright (C) 2009 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 "WAVExtractor" +#include <utils/Log.h> + +#include "WAVExtractor.h" + +#include <audio_utils/primitives.h> +#include <media/DataSourceBase.h> +#include <media/MediaTrack.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <utils/String8.h> +#include <cutils/bitops.h> + +#define CHANNEL_MASK_USE_CHANNEL_ORDER 0 + +namespace android { + +enum { + WAVE_FORMAT_PCM = 0x0001, + WAVE_FORMAT_IEEE_FLOAT = 0x0003, + WAVE_FORMAT_ALAW = 0x0006, + WAVE_FORMAT_MULAW = 0x0007, + WAVE_FORMAT_MSGSM = 0x0031, + WAVE_FORMAT_EXTENSIBLE = 0xFFFE +}; + +static const char* WAVEEXT_SUBFORMAT = "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71"; +static const char* AMBISONIC_SUBFORMAT = "\x00\x00\x21\x07\xD3\x11\x86\x44\xC8\xC1\xCA\x00\x00\x00"; + +static uint32_t U32_LE_AT(const uint8_t *ptr) { + return ptr[3] << 24 | ptr[2] << 16 | ptr[1] << 8 | ptr[0]; +} + +static uint16_t U16_LE_AT(const uint8_t *ptr) { + return ptr[1] << 8 | ptr[0]; +} + +struct WAVSource : public MediaTrack { + WAVSource( + DataSourceBase *dataSource, + MetaDataBase &meta, + uint16_t waveFormat, + int32_t bitsPerSample, + off64_t offset, size_t size); + + virtual status_t start(MetaDataBase *params = NULL); + virtual status_t stop(); + virtual status_t getFormat(MetaDataBase &meta); + + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL); + + virtual bool supportNonblockingRead() { return true; } + +protected: + virtual ~WAVSource(); + +private: + static const size_t kMaxFrameSize; + + DataSourceBase *mDataSource; + MetaDataBase &mMeta; + uint16_t mWaveFormat; + int32_t mSampleRate; + int32_t mNumChannels; + int32_t mBitsPerSample; + off64_t mOffset; + size_t mSize; + bool mStarted; + MediaBufferGroup *mGroup; + off64_t mCurrentPos; + + WAVSource(const WAVSource &); + WAVSource &operator=(const WAVSource &); +}; + +WAVExtractor::WAVExtractor(DataSourceBase *source) + : mDataSource(source), + mValidFormat(false), + mChannelMask(CHANNEL_MASK_USE_CHANNEL_ORDER) { + mInitCheck = init(); +} + +WAVExtractor::~WAVExtractor() { +} + +status_t WAVExtractor::getMetaData(MetaDataBase &meta) { + meta.clear(); + if (mInitCheck == OK) { + meta.setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_WAV); + } + + return OK; +} + +size_t WAVExtractor::countTracks() { + return mInitCheck == OK ? 1 : 0; +} + +MediaTrack *WAVExtractor::getTrack(size_t index) { + if (mInitCheck != OK || index > 0) { + return NULL; + } + + return new WAVSource( + mDataSource, mTrackMeta, + mWaveFormat, mBitsPerSample, mDataOffset, mDataSize); +} + +status_t WAVExtractor::getTrackMetaData( + MetaDataBase &meta, + size_t index, uint32_t /* flags */) { + if (mInitCheck != OK || index > 0) { + return UNKNOWN_ERROR; + } + + meta = mTrackMeta; + return OK; +} + +status_t WAVExtractor::init() { + uint8_t header[12]; + if (mDataSource->readAt( + 0, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return NO_INIT; + } + + if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) { + return NO_INIT; + } + + size_t totalSize = U32_LE_AT(&header[4]); + + off64_t offset = 12; + size_t remainingSize = totalSize; + while (remainingSize >= 8) { + uint8_t chunkHeader[8]; + if (mDataSource->readAt(offset, chunkHeader, 8) < 8) { + return NO_INIT; + } + + remainingSize -= 8; + offset += 8; + + uint32_t chunkSize = U32_LE_AT(&chunkHeader[4]); + + if (chunkSize > remainingSize) { + return NO_INIT; + } + + if (!memcmp(chunkHeader, "fmt ", 4)) { + if (chunkSize < 16) { + return NO_INIT; + } + + uint8_t formatSpec[40]; + if (mDataSource->readAt(offset, formatSpec, 2) < 2) { + return NO_INIT; + } + + mWaveFormat = U16_LE_AT(formatSpec); + if (mWaveFormat != WAVE_FORMAT_PCM + && mWaveFormat != WAVE_FORMAT_IEEE_FLOAT + && mWaveFormat != WAVE_FORMAT_ALAW + && mWaveFormat != WAVE_FORMAT_MULAW + && mWaveFormat != WAVE_FORMAT_MSGSM + && mWaveFormat != WAVE_FORMAT_EXTENSIBLE) { + return ERROR_UNSUPPORTED; + } + + uint8_t fmtSize = 16; + if (mWaveFormat == WAVE_FORMAT_EXTENSIBLE) { + fmtSize = 40; + } + if (mDataSource->readAt(offset, formatSpec, fmtSize) < fmtSize) { + return NO_INIT; + } + + mNumChannels = U16_LE_AT(&formatSpec[2]); + + if (mNumChannels < 1 || mNumChannels > 8) { + ALOGE("Unsupported number of channels (%d)", mNumChannels); + return ERROR_UNSUPPORTED; + } + + if (mWaveFormat != WAVE_FORMAT_EXTENSIBLE) { + if (mNumChannels != 1 && mNumChannels != 2) { + ALOGW("More than 2 channels (%d) in non-WAVE_EXT, unknown channel mask", + mNumChannels); + } + } + + mSampleRate = U32_LE_AT(&formatSpec[4]); + + if (mSampleRate == 0) { + return ERROR_MALFORMED; + } + + mBitsPerSample = U16_LE_AT(&formatSpec[14]); + + if (mWaveFormat == WAVE_FORMAT_EXTENSIBLE) { + uint16_t validBitsPerSample = U16_LE_AT(&formatSpec[18]); + if (validBitsPerSample != mBitsPerSample) { + if (validBitsPerSample != 0) { + ALOGE("validBits(%d) != bitsPerSample(%d) are not supported", + validBitsPerSample, mBitsPerSample); + return ERROR_UNSUPPORTED; + } else { + // we only support valitBitsPerSample == bitsPerSample but some WAV_EXT + // writers don't correctly set the valid bits value, and leave it at 0. + ALOGW("WAVE_EXT has 0 valid bits per sample, ignoring"); + } + } + + mChannelMask = U32_LE_AT(&formatSpec[20]); + ALOGV("numChannels=%d channelMask=0x%x", mNumChannels, mChannelMask); + if ((mChannelMask >> 18) != 0) { + ALOGE("invalid channel mask 0x%x", mChannelMask); + return ERROR_MALFORMED; + } + + if ((mChannelMask != CHANNEL_MASK_USE_CHANNEL_ORDER) + && (popcount(mChannelMask) != mNumChannels)) { + ALOGE("invalid number of channels (%d) in channel mask (0x%x)", + popcount(mChannelMask), mChannelMask); + return ERROR_MALFORMED; + } + + // In a WAVE_EXT header, the first two bytes of the GUID stored at byte 24 contain + // the sample format, using the same definitions as a regular WAV header + mWaveFormat = U16_LE_AT(&formatSpec[24]); + if (memcmp(&formatSpec[26], WAVEEXT_SUBFORMAT, 14) && + memcmp(&formatSpec[26], AMBISONIC_SUBFORMAT, 14)) { + ALOGE("unsupported GUID"); + return ERROR_UNSUPPORTED; + } + } + + if (mWaveFormat == WAVE_FORMAT_PCM) { + if (mBitsPerSample != 8 && mBitsPerSample != 16 + && mBitsPerSample != 24 && mBitsPerSample != 32) { + return ERROR_UNSUPPORTED; + } + } else if (mWaveFormat == WAVE_FORMAT_IEEE_FLOAT) { + if (mBitsPerSample != 32) { // TODO we don't support double + return ERROR_UNSUPPORTED; + } + } + else if (mWaveFormat == WAVE_FORMAT_MSGSM) { + if (mBitsPerSample != 0) { + return ERROR_UNSUPPORTED; + } + } else if (mWaveFormat == WAVE_FORMAT_MULAW || mWaveFormat == WAVE_FORMAT_ALAW) { + if (mBitsPerSample != 8) { + return ERROR_UNSUPPORTED; + } + } else { + return ERROR_UNSUPPORTED; + } + + mValidFormat = true; + } else if (!memcmp(chunkHeader, "data", 4)) { + if (mValidFormat) { + mDataOffset = offset; + mDataSize = chunkSize; + + mTrackMeta.clear(); + + switch (mWaveFormat) { + case WAVE_FORMAT_PCM: + case WAVE_FORMAT_IEEE_FLOAT: + mTrackMeta.setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + break; + case WAVE_FORMAT_ALAW: + mTrackMeta.setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_G711_ALAW); + break; + case WAVE_FORMAT_MSGSM: + mTrackMeta.setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MSGSM); + break; + default: + CHECK_EQ(mWaveFormat, (uint16_t)WAVE_FORMAT_MULAW); + mTrackMeta.setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_G711_MLAW); + break; + } + + mTrackMeta.setInt32(kKeyChannelCount, mNumChannels); + mTrackMeta.setInt32(kKeyChannelMask, mChannelMask); + mTrackMeta.setInt32(kKeySampleRate, mSampleRate); + mTrackMeta.setInt32(kKeyPcmEncoding, kAudioEncodingPcm16bit); + + int64_t durationUs = 0; + if (mWaveFormat == WAVE_FORMAT_MSGSM) { + // 65 bytes decode to 320 8kHz samples + durationUs = + 1000000LL * (mDataSize / 65 * 320) / 8000; + } else { + size_t bytesPerSample = mBitsPerSample >> 3; + + if (!bytesPerSample || !mNumChannels) + return ERROR_MALFORMED; + + size_t num_samples = mDataSize / (mNumChannels * bytesPerSample); + + if (!mSampleRate) + return ERROR_MALFORMED; + + durationUs = + 1000000LL * num_samples / mSampleRate; + } + + mTrackMeta.setInt64(kKeyDuration, durationUs); + + return OK; + } + } + + offset += chunkSize; + } + + return NO_INIT; +} + +const size_t WAVSource::kMaxFrameSize = 32768; + +WAVSource::WAVSource( + DataSourceBase *dataSource, + MetaDataBase &meta, + uint16_t waveFormat, + int32_t bitsPerSample, + off64_t offset, size_t size) + : mDataSource(dataSource), + mMeta(meta), + mWaveFormat(waveFormat), + mSampleRate(0), + mNumChannels(0), + mBitsPerSample(bitsPerSample), + mOffset(offset), + mSize(size), + mStarted(false), + mGroup(NULL) { + CHECK(mMeta.findInt32(kKeySampleRate, &mSampleRate)); + CHECK(mMeta.findInt32(kKeyChannelCount, &mNumChannels)); + + mMeta.setInt32(kKeyMaxInputSize, kMaxFrameSize); +} + +WAVSource::~WAVSource() { + if (mStarted) { + stop(); + } +} + +status_t WAVSource::start(MetaDataBase * /* params */) { + ALOGV("WAVSource::start"); + + CHECK(!mStarted); + + // some WAV files may have large audio buffers that use shared memory transfer. + mGroup = new MediaBufferGroup(4 /* buffers */, kMaxFrameSize); + + if (mBitsPerSample == 8) { + // As a temporary buffer for 8->16 bit conversion. + mGroup->add_buffer(MediaBufferBase::Create(kMaxFrameSize)); + } + + mCurrentPos = mOffset; + + mStarted = true; + + return OK; +} + +status_t WAVSource::stop() { + ALOGV("WAVSource::stop"); + + CHECK(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + + return OK; +} + +status_t WAVSource::getFormat(MetaDataBase &meta) { + ALOGV("WAVSource::getFormat"); + + meta = mMeta; + return OK; +} + +status_t WAVSource::read( + MediaBufferBase **out, const ReadOptions *options) { + *out = NULL; + + if (options != nullptr && options->getNonBlocking() && !mGroup->has_buffers()) { + return WOULD_BLOCK; + } + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) { + int64_t pos = 0; + + if (mWaveFormat == WAVE_FORMAT_MSGSM) { + // 65 bytes decode to 320 8kHz samples + int64_t samplenumber = (seekTimeUs * mSampleRate) / 1000000; + int64_t framenumber = samplenumber / 320; + pos = framenumber * 65; + } else { + pos = (seekTimeUs * mSampleRate) / 1000000 * mNumChannels * (mBitsPerSample >> 3); + } + if (pos > (off64_t)mSize) { + pos = mSize; + } + mCurrentPos = pos + mOffset; + } + + MediaBufferBase *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + // make sure that maxBytesToRead is multiple of 3, in 24-bit case + size_t maxBytesToRead = + mBitsPerSample == 8 ? kMaxFrameSize / 2 : + (mBitsPerSample == 24 ? 3*(kMaxFrameSize/3): kMaxFrameSize); + + size_t maxBytesAvailable = + (mCurrentPos - mOffset >= (off64_t)mSize) + ? 0 : mSize - (mCurrentPos - mOffset); + + if (maxBytesToRead > maxBytesAvailable) { + maxBytesToRead = maxBytesAvailable; + } + + if (mWaveFormat == WAVE_FORMAT_MSGSM) { + // Microsoft packs 2 frames into 65 bytes, rather than using separate 33-byte frames, + // so read multiples of 65, and use smaller buffers to account for ~10:1 expansion ratio + if (maxBytesToRead > 1024) { + maxBytesToRead = 1024; + } + maxBytesToRead = (maxBytesToRead / 65) * 65; + } else { + // read only integral amounts of audio unit frames. + const size_t inputUnitFrameSize = mNumChannels * mBitsPerSample / 8; + maxBytesToRead -= maxBytesToRead % inputUnitFrameSize; + } + + ssize_t n = mDataSource->readAt( + mCurrentPos, buffer->data(), + maxBytesToRead); + + if (n <= 0) { + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + buffer->set_range(0, n); + + // TODO: add capability to return data as float PCM instead of 16 bit PCM. + if (mWaveFormat == WAVE_FORMAT_PCM) { + if (mBitsPerSample == 8) { + // Convert 8-bit unsigned samples to 16-bit signed. + + // Create new buffer with 2 byte wide samples + MediaBufferBase *tmp; + CHECK_EQ(mGroup->acquire_buffer(&tmp), (status_t)OK); + tmp->set_range(0, 2 * n); + + memcpy_to_i16_from_u8((int16_t *)tmp->data(), (const uint8_t *)buffer->data(), n); + buffer->release(); + buffer = tmp; + } else if (mBitsPerSample == 24) { + // Convert 24-bit signed samples to 16-bit signed in place + const size_t numSamples = n / 3; + + memcpy_to_i16_from_p24((int16_t *)buffer->data(), (const uint8_t *)buffer->data(), numSamples); + buffer->set_range(0, 2 * numSamples); + } else if (mBitsPerSample == 32) { + // Convert 32-bit signed samples to 16-bit signed in place + const size_t numSamples = n / 4; + + memcpy_to_i16_from_i32((int16_t *)buffer->data(), (const int32_t *)buffer->data(), numSamples); + buffer->set_range(0, 2 * numSamples); + } + } else if (mWaveFormat == WAVE_FORMAT_IEEE_FLOAT) { + if (mBitsPerSample == 32) { + // Convert 32-bit float samples to 16-bit signed in place + const size_t numSamples = n / 4; + + memcpy_to_i16_from_float((int16_t *)buffer->data(), (const float *)buffer->data(), numSamples); + buffer->set_range(0, 2 * numSamples); + } + } + + int64_t timeStampUs = 0; + + if (mWaveFormat == WAVE_FORMAT_MSGSM) { + timeStampUs = 1000000LL * (mCurrentPos - mOffset) * 320 / 65 / mSampleRate; + } else { + size_t bytesPerSample = mBitsPerSample >> 3; + timeStampUs = 1000000LL * (mCurrentPos - mOffset) + / (mNumChannels * bytesPerSample) / mSampleRate; + } + + buffer->meta_data().setInt64(kKeyTime, timeStampUs); + + buffer->meta_data().setInt32(kKeyIsSyncFrame, 1); + mCurrentPos += n; + + *out = buffer; + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +static MediaExtractor* CreateExtractor( + DataSourceBase *source, + void *) { + return new WAVExtractor(source); +} + +static MediaExtractor::CreatorFunc Sniff( + DataSourceBase *source, + float *confidence, + void **, + MediaExtractor::FreeMetaFunc *) { + char header[12]; + if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return NULL; + } + + if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) { + return NULL; + } + + MediaExtractor *extractor = new WAVExtractor(source); + int numTracks = extractor->countTracks(); + delete extractor; + if (numTracks == 0) { + return NULL; + } + + *confidence = 0.3f; + + return CreateExtractor; +} + +extern "C" { +// This is the only symbol that needs to be exported +__attribute__ ((visibility ("default"))) +MediaExtractor::ExtractorDef GETEXTRACTORDEF() { + return { + MediaExtractor::EXTRACTORDEF_VERSION, + UUID("7d613858-5837-4a38-84c5-332d1cddee27"), + 1, // version + "WAV Extractor", + Sniff + }; +} + +} // extern "C" + +} // namespace android
diff --git a/media/extractors/wav/WAVExtractor.h b/media/extractors/wav/WAVExtractor.h new file mode 100644 index 0000000..467d0b7 --- /dev/null +++ b/media/extractors/wav/WAVExtractor.h
@@ -0,0 +1,66 @@ +/* + * Copyright (C) 2009 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 WAV_EXTRACTOR_H_ + +#define WAV_EXTRACTOR_H_ + +#include <utils/Errors.h> +#include <media/MediaExtractor.h> +#include <media/stagefright/MetaDataBase.h> + +namespace android { + +struct AMessage; +class DataSourceBase; +class String8; + +class WAVExtractor : public MediaExtractor { +public: + explicit WAVExtractor(DataSourceBase *source); + + virtual size_t countTracks(); + virtual MediaTrack *getTrack(size_t index); + virtual status_t getTrackMetaData(MetaDataBase& meta, size_t index, uint32_t flags); + + virtual status_t getMetaData(MetaDataBase& meta); + virtual const char * name() { return "WAVExtractor"; } + + virtual ~WAVExtractor(); + +private: + DataSourceBase *mDataSource; + status_t mInitCheck; + bool mValidFormat; + uint16_t mWaveFormat; + uint16_t mNumChannels; + uint32_t mChannelMask; + uint32_t mSampleRate; + uint16_t mBitsPerSample; + off64_t mDataOffset; + size_t mDataSize; + MetaDataBase mTrackMeta; + + status_t init(); + + WAVExtractor(const WAVExtractor &); + WAVExtractor &operator=(const WAVExtractor &); +}; + +} // namespace android + +#endif // WAV_EXTRACTOR_H_ +
diff --git a/media/extractors/wav/exports.lds b/media/extractors/wav/exports.lds new file mode 100644 index 0000000..b1309ee --- /dev/null +++ b/media/extractors/wav/exports.lds
@@ -0,0 +1 @@ +{ global: GETEXTRACTORDEF; local: *; };
diff --git a/media/img_utils/include/img_utils/DngUtils.h b/media/img_utils/include/img_utils/DngUtils.h index 1d8df9c..de8f120 100644 --- a/media/img_utils/include/img_utils/DngUtils.h +++ b/media/img_utils/include/img_utils/DngUtils.h
@@ -39,11 +39,16 @@ */ class ANDROID_API OpcodeListBuilder : public LightRefBase<OpcodeListBuilder> { public: + // Note that the Adobe DNG 1.4 spec for Bayer phase (defined for the + // FixBadPixelsConstant and FixBadPixelsList opcodes) is incorrect. It's + // inconsistent with the DNG SDK (cf. dng_negative::SetBayerMosaic and + // dng_opcode_FixBadPixelsList::IsGreen), and Adobe confirms that the + // spec should be updated to match the SDK. enum CfaLayout { - CFA_RGGB = 0, - CFA_GRBG, - CFA_GBRG, + CFA_GRBG = 0, + CFA_RGGB, CFA_BGGR, + CFA_GBRG, }; OpcodeListBuilder();
diff --git a/media/img_utils/src/DngUtils.cpp b/media/img_utils/src/DngUtils.cpp index 9dc5f05..9ac7e2a 100644 --- a/media/img_utils/src/DngUtils.cpp +++ b/media/img_utils/src/DngUtils.cpp
@@ -18,6 +18,7 @@ #include <inttypes.h> +#include <algorithm> #include <vector> #include <math.h> @@ -61,8 +62,8 @@ const float* lensShadingMap) { uint32_t activeAreaWidth = activeAreaRight - activeAreaLeft; uint32_t activeAreaHeight = activeAreaBottom - activeAreaTop; - double spacingV = 1.0 / lsmHeight; - double spacingH = 1.0 / lsmWidth; + double spacingV = 1.0 / std::max(1u, lsmHeight - 1); + double spacingH = 1.0 / std::max(1u, lsmWidth - 1); std::vector<float> redMapVector(lsmWidth * lsmHeight); float *redMap = redMapVector.data(); @@ -301,29 +302,14 @@ normalizedOCX = CLAMP(normalizedOCX, 0, 1); normalizedOCY = CLAMP(normalizedOCY, 0, 1); - // Conversion factors from Camera2 K factors to DNG spec. K factors: - // - // Note: these are necessary because our unit system assumes a - // normalized max radius of sqrt(2), whereas the DNG spec's - // WarpRectilinear opcode assumes a normalized max radius of 1. - // Thus, each K coefficient must include the domain scaling - // factor (the DNG domain is scaled by sqrt(2) to emulate the - // domain used by the Camera2 specification). - - const double c_0 = sqrt(2); - const double c_1 = 2 * sqrt(2); - const double c_2 = 4 * sqrt(2); - const double c_3 = 8 * sqrt(2); - const double c_4 = 2; - const double c_5 = 2; - - const double coeffs[] = { c_0 * kCoeffs[0], - c_1 * kCoeffs[1], - c_2 * kCoeffs[2], - c_3 * kCoeffs[3], - c_4 * kCoeffs[4], - c_5 * kCoeffs[5] }; - + double coeffs[6] = { + kCoeffs[0], + kCoeffs[1], + kCoeffs[2], + kCoeffs[3], + kCoeffs[4], + kCoeffs[5] + }; return addWarpRectilinear(/*numPlanes*/1, /*opticalCenterX*/normalizedOCX,
diff --git a/media/img_utils/src/TiffWriter.cpp b/media/img_utils/src/TiffWriter.cpp index 564474f..1711242 100644 --- a/media/img_utils/src/TiffWriter.cpp +++ b/media/img_utils/src/TiffWriter.cpp
@@ -350,7 +350,7 @@ if (nextIfd == NULL) { break; } - ifd = nextIfd; + ifd = std::move(nextIfd); } return ifd; }
diff --git a/media/libaaudio/Android.bp b/media/libaaudio/Android.bp index 6e60f24..f00f7a8 100644 --- a/media/libaaudio/Android.bp +++ b/media/libaaudio/Android.bp
@@ -28,3 +28,10 @@ first_version: "26", unversioned_until: "current", } + +cc_library_headers { + name: "libaaudio_headers", + export_include_dirs: ["include"], +} + +subdirs = ["*"]
diff --git a/media/libaaudio/Android.mk b/media/libaaudio/Android.mk deleted file mode 100644 index 5053e7d..0000000 --- a/media/libaaudio/Android.mk +++ /dev/null
@@ -1 +0,0 @@ -include $(call all-subdir-makefiles)
diff --git a/media/libaaudio/examples/Android.bp b/media/libaaudio/examples/Android.bp new file mode 100644 index 0000000..639fab2 --- /dev/null +++ b/media/libaaudio/examples/Android.bp
@@ -0,0 +1,6 @@ +subdirs = ["*"] + +cc_library_headers { + name: "libaaudio_example_utils", + export_include_dirs: ["utils"], +}
diff --git a/media/libaaudio/examples/Android.mk b/media/libaaudio/examples/Android.mk deleted file mode 100644 index 5053e7d..0000000 --- a/media/libaaudio/examples/Android.mk +++ /dev/null
@@ -1 +0,0 @@ -include $(call all-subdir-makefiles)
diff --git a/media/libaaudio/examples/input_monitor/Android.bp b/media/libaaudio/examples/input_monitor/Android.bp new file mode 100644 index 0000000..d8c5843 --- /dev/null +++ b/media/libaaudio/examples/input_monitor/Android.bp
@@ -0,0 +1,17 @@ +cc_test { + name: "input_monitor", + gtest: false, + srcs: ["src/input_monitor.cpp"], + cflags: ["-Wall", "-Werror"], + shared_libs: ["libaaudio"], + header_libs: ["libaaudio_example_utils"], +} + +cc_test { + name: "input_monitor_callback", + gtest: false, + srcs: ["src/input_monitor_callback.cpp"], + cflags: ["-Wall", "-Werror"], + shared_libs: ["libaaudio"], + header_libs: ["libaaudio_example_utils"], +}
diff --git a/media/libaaudio/examples/input_monitor/Android.mk b/media/libaaudio/examples/input_monitor/Android.mk deleted file mode 100644 index 5053e7d..0000000 --- a/media/libaaudio/examples/input_monitor/Android.mk +++ /dev/null
@@ -1 +0,0 @@ -include $(call all-subdir-makefiles)
diff --git a/media/libaaudio/examples/input_monitor/jni/Android.mk b/media/libaaudio/examples/input_monitor/jni/Android.mk index 9b1ce2c..a0b981c 100644 --- a/media/libaaudio/examples/input_monitor/jni/Android.mk +++ b/media/libaaudio/examples/input_monitor/jni/Android.mk
@@ -10,6 +10,7 @@ # NDK recommends using this kind of relative path instead of an absolute path. LOCAL_SRC_FILES:= ../src/input_monitor.cpp +LOCAL_CFLAGS := -Wall -Werror LOCAL_SHARED_LIBRARIES := libaaudio LOCAL_MODULE := input_monitor include $(BUILD_EXECUTABLE) @@ -22,6 +23,7 @@ frameworks/av/media/libaaudio/examples/utils LOCAL_SRC_FILES:= ../src/input_monitor_callback.cpp +LOCAL_CFLAGS := -Wall -Werror LOCAL_SHARED_LIBRARIES := libaaudio LOCAL_MODULE := input_monitor_callback include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/examples/input_monitor/src/input_monitor.cpp b/media/libaaudio/examples/input_monitor/src/input_monitor.cpp index 9feb118..c1ff34b 100644 --- a/media/libaaudio/examples/input_monitor/src/input_monitor.cpp +++ b/media/libaaudio/examples/input_monitor/src/input_monitor.cpp
@@ -26,24 +26,22 @@ #include "AAudioExampleUtils.h" #include "AAudioSimpleRecorder.h" -// TODO support FLOAT -#define REQUIRED_FORMAT AAUDIO_FORMAT_PCM_I16 #define MIN_FRAMES_TO_READ 48 /* arbitrary, 1 msec at 48000 Hz */ static const int FRAMES_PER_LINE = 20000; int main(int argc, const char **argv) { - AAudioArgsParser argParser; - aaudio_result_t result; - AAudioSimpleRecorder recorder; - int actualSamplesPerFrame; - int actualSampleRate; - aaudio_format_t actualDataFormat; - aaudio_sharing_mode_t actualSharingMode; + AAudioArgsParser argParser; + AAudioSimpleRecorder recorder; + AAudioStream *aaudioStream = nullptr; - AAudioStream *aaudioStream = nullptr; + aaudio_result_t result; + aaudio_format_t actualDataFormat; aaudio_stream_state_t state; + + int32_t actualSamplesPerFrame; + int32_t actualSampleRate; int32_t framesPerBurst = 0; int32_t framesPerRead = 0; int32_t framesToRecord = 0; @@ -51,19 +49,18 @@ int32_t nextFrameCount = 0; int32_t frameCount = 0; int32_t xRunCount = 0; - int64_t previousFramePosition = -1; - int16_t *data = nullptr; - float peakLevel = 0.0; - int loopCounter = 0; int32_t deviceId; + int16_t *shortData = nullptr; + float *floatData = nullptr; + float peakLevel = 0.0; + // Make printf print immediately so that debug info is not stuck // in a buffer if we hang or crash. setvbuf(stdout, nullptr, _IONBF, (size_t) 0); - printf("%s - Monitor input level using AAudio read, V0.1.2\n", argv[0]); + printf("%s - Monitor input level using AAudio read, V0.1.3\n", argv[0]); - argParser.setFormat(REQUIRED_FORMAT); if (argParser.parseArgs(argc, argv)) { return EXIT_FAILURE; } @@ -71,6 +68,7 @@ result = recorder.open(argParser); if (result != AAUDIO_OK) { fprintf(stderr, "ERROR - recorder.open() returned %d\n", result); + printf("IMPORTANT - Did you remember to enter: adb root\n"); goto finish; } aaudioStream = recorder.getStream(); @@ -98,17 +96,18 @@ printf("DataFormat: framesPerRead = %d\n",framesPerRead); actualDataFormat = AAudioStream_getFormat(aaudioStream); - printf("DataFormat: requested = %d, actual = %d\n", - REQUIRED_FORMAT, actualDataFormat); - // TODO handle other data formats - assert(actualDataFormat == REQUIRED_FORMAT); // Allocate a buffer for the PCM_16 audio data. - data = new(std::nothrow) int16_t[framesPerRead * actualSamplesPerFrame]; - if (data == nullptr) { - fprintf(stderr, "ERROR - could not allocate data buffer\n"); - result = AAUDIO_ERROR_NO_MEMORY; - goto finish; + switch (actualDataFormat) { + case AAUDIO_FORMAT_PCM_I16: + shortData = new int16_t[framesPerRead * actualSamplesPerFrame]; + break; + case AAUDIO_FORMAT_PCM_FLOAT: + floatData = new float[framesPerRead * actualSamplesPerFrame]; + break; + default: + fprintf(stderr, "UNEXPECTED FORMAT! %d", actualDataFormat); + goto finish; } // Start the stream. @@ -128,7 +127,12 @@ // Read audio data from the stream. const int64_t timeoutNanos = 1000 * NANOS_PER_MILLISECOND; int minFrames = (framesToRecord < framesPerRead) ? framesToRecord : framesPerRead; - int actual = AAudioStream_read(aaudioStream, data, minFrames, timeoutNanos); + int actual = 0; + if (actualDataFormat == AAUDIO_FORMAT_PCM_I16) { + actual = AAudioStream_read(aaudioStream, shortData, minFrames, timeoutNanos); + } else if (actualDataFormat == AAUDIO_FORMAT_PCM_FLOAT) { + actual = AAudioStream_read(aaudioStream, floatData, minFrames, timeoutNanos); + } if (actual < 0) { fprintf(stderr, "ERROR - AAudioStream_read() returned %d\n", actual); result = actual; @@ -142,7 +146,12 @@ // Peak finder. for (int frameIndex = 0; frameIndex < actual; frameIndex++) { - float sample = data[frameIndex * actualSamplesPerFrame] * (1.0/32768); + float sample = 0.0f; + if (actualDataFormat == AAUDIO_FORMAT_PCM_I16) { + sample = shortData[frameIndex * actualSamplesPerFrame] * (1.0/32768); + } else if (actualDataFormat == AAUDIO_FORMAT_PCM_FLOAT) { + sample = floatData[frameIndex * actualSamplesPerFrame]; + } if (sample > peakLevel) { peakLevel = sample; } @@ -153,17 +162,15 @@ displayPeakLevel(peakLevel); peakLevel = 0.0; nextFrameCount += FRAMES_PER_LINE; - } - // Print timestamps. - int64_t framePosition = 0; - int64_t frameTime = 0; - aaudio_result_t timeResult; - timeResult = AAudioStream_getTimestamp(aaudioStream, CLOCK_MONOTONIC, - &framePosition, &frameTime); + // Print timestamps. + int64_t framePosition = 0; + int64_t frameTime = 0; + aaudio_result_t timeResult; + timeResult = AAudioStream_getTimestamp(aaudioStream, CLOCK_MONOTONIC, + &framePosition, &frameTime); - if (timeResult == AAUDIO_OK) { - if (framePosition > (previousFramePosition + FRAMES_PER_LINE)) { + if (timeResult == AAUDIO_OK) { int64_t realTime = getNanoseconds(); int64_t framesRead = AAudioStream_getFramesRead(aaudioStream); @@ -177,11 +184,15 @@ (long long) framePosition, (long long) frameTime, latencyMillis); - previousFramePosition = framePosition; + } else { + printf("WARNING - AAudioStream_getTimestamp() returned %d\n", timeResult); } } } + state = AAudioStream_getState(aaudioStream); + printf("after loop, state = %s\n", AAudio_convertStreamStateToText(state)); + xRunCount = AAudioStream_getXRunCount(aaudioStream); printf("AAudioStream_getXRunCount %d\n", xRunCount); @@ -194,7 +205,8 @@ finish: recorder.close(); - delete[] data; + delete[] shortData; + delete[] floatData; printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result)); return (result != AAUDIO_OK) ? EXIT_FAILURE : EXIT_SUCCESS; }
diff --git a/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp b/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp index 893795b..d10f812 100644 --- a/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp +++ b/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp
@@ -26,29 +26,39 @@ #include "AAudioExampleUtils.h" #include "AAudioSimpleRecorder.h" -#define NUM_SECONDS 5 - -int main(int argc, char **argv) +int main(int argc, const char **argv) { - (void)argc; // unused - AAudioSimpleRecorder recorder; - PeakTrackerData_t myData = {0.0}; - aaudio_result_t result; + AAudioArgsParser argParser; + AAudioSimpleRecorder recorder; + PeakTrackerData_t myData = {0.0}; + AAudioStream *aaudioStream = nullptr; + aaudio_result_t result; aaudio_stream_state_t state; + + int loopsNeeded = 0; const int displayRateHz = 20; // arbitrary - const int loopsNeeded = NUM_SECONDS * displayRateHz; // Make printf print immediately so that debug info is not stuck // in a buffer if we hang or crash. setvbuf(stdout, nullptr, _IONBF, (size_t) 0); - printf("%s - Display audio input using an AAudio callback, V0.1.2\n", argv[0]); + printf("%s - Display audio input using an AAudio callback, V0.1.3\n", argv[0]); - result = recorder.open(2, 48000, AAUDIO_FORMAT_PCM_I16, - SimpleRecorderDataCallbackProc, SimpleRecorderErrorCallbackProc, &myData); + if (argParser.parseArgs(argc, argv)) { + return EXIT_FAILURE; + } + + result = recorder.open(argParser, + SimpleRecorderDataCallbackProc, + SimpleRecorderErrorCallbackProc, + &myData); if (result != AAUDIO_OK) { fprintf(stderr, "ERROR - recorder.open() returned %d\n", result); + printf("IMPORTANT - Did you remember to enter: adb root\n"); goto error; } + aaudioStream = recorder.getStream(); + argParser.compareWithStream(aaudioStream); + printf("recorder.getFramesPerSecond() = %d\n", recorder.getFramesPerSecond()); printf("recorder.getSamplesPerFrame() = %d\n", recorder.getSamplesPerFrame()); @@ -58,7 +68,9 @@ goto error; } - printf("Sleep for %d seconds while audio record in a callback thread.\n", NUM_SECONDS); + printf("Sleep for %d seconds while audio record in a callback thread.\n", + argParser.getDurationSeconds()); + loopsNeeded = argParser.getDurationSeconds() * displayRateHz; for (int i = 0; i < loopsNeeded; i++) { const struct timespec request = { .tv_sec = 0, @@ -67,7 +79,7 @@ printf("%08d: ", (int)recorder.getFramesRead()); displayPeakLevel(myData.peakLevel); - result = AAudioStream_waitForStateChange(recorder.getStream(), + result = AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_CLOSED, &state, 0); @@ -93,7 +105,8 @@ goto error; } - printf("Sleep for %d seconds while audio records in a callback thread.\n", NUM_SECONDS); + printf("Sleep for %d seconds while audio records in a callback thread.\n", + argParser.getDurationSeconds()); for (int i = 0; i < loopsNeeded; i++) { const struct timespec request = { .tv_sec = 0, @@ -102,13 +115,14 @@ printf("%08d: ", (int)recorder.getFramesRead()); displayPeakLevel(myData.peakLevel); - state = AAudioStream_getState(recorder.getStream()); + state = AAudioStream_getState(aaudioStream); if (state != AAUDIO_STREAM_STATE_STARTING && state != AAUDIO_STREAM_STATE_STARTED) { printf("Stream state is %d %s!\n", state, AAudio_convertStreamStateToText(state)); break; } } printf("Woke up now.\n"); + argParser.compareWithStream(aaudioStream); result = recorder.stop(); if (result != AAUDIO_OK) {
diff --git a/media/libaaudio/examples/loopback/Android.bp b/media/libaaudio/examples/loopback/Android.bp new file mode 100644 index 0000000..5b7d956 --- /dev/null +++ b/media/libaaudio/examples/loopback/Android.bp
@@ -0,0 +1,12 @@ +cc_test { + name: "aaudio_loopback", + gtest: false, + srcs: ["src/loopback.cpp"], + cflags: ["-Wall", "-Werror"], + static_libs: ["libsndfile"], + shared_libs: [ + "libaaudio", + "libaudioutils", + ], + header_libs: ["libaaudio_example_utils"], +}
diff --git a/media/libaaudio/examples/loopback/Android.mk b/media/libaaudio/examples/loopback/Android.mk deleted file mode 100644 index 5053e7d..0000000 --- a/media/libaaudio/examples/loopback/Android.mk +++ /dev/null
@@ -1 +0,0 @@ -include $(call all-subdir-makefiles)
diff --git a/media/libaaudio/examples/loopback/jni/Android.mk b/media/libaaudio/examples/loopback/jni/Android.mk index d78f286..aebe877 100644 --- a/media/libaaudio/examples/loopback/jni/Android.mk +++ b/media/libaaudio/examples/loopback/jni/Android.mk
@@ -9,6 +9,8 @@ # NDK recommends using this kind of relative path instead of an absolute path. LOCAL_SRC_FILES:= ../src/loopback.cpp -LOCAL_SHARED_LIBRARIES := libaaudio +LOCAL_CFLAGS := -Wall -Werror +LOCAL_STATIC_LIBRARIES := libsndfile +LOCAL_SHARED_LIBRARIES := libaaudio libaudioutils LOCAL_MODULE := aaudio_loopback include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h index 21cf341..ef9a753 100644 --- a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h +++ b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
@@ -30,6 +30,8 @@ #include <stdlib.h> #include <unistd.h> +#include <audio_utils/sndfile.h> + // Tag for machine readable results as property = value pairs #define LOOPBACK_RESULT_TAG "RESULT: " #define LOOPBACK_SAMPLE_RATE 48000 @@ -37,13 +39,18 @@ #define MILLIS_PER_SECOND 1000 #define MAX_ZEROTH_PARTIAL_BINS 40 +constexpr double MAX_ECHO_GAIN = 10.0; // based on experiments, otherwise autocorrelation too noisy +// A narrow impulse seems to have better immunity against over estimating the +// latency due to detecting subharmonics by the auto-correlator. static const float s_Impulse[] = { - 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, // silence on each side of the impulse - 0.5f, 0.9999f, 0.0f, -0.9999, -0.5f, // bipolar - -0.2f, 0.0f, 0.0f, 0.0f, 0.0f + 0.0f, 0.0f, 0.0f, 0.0f, 0.3f, // silence on each side of the impulse + 0.99f, 0.0f, -0.99f, // bipolar with one zero crossing in middle + -0.3f, 0.0f, 0.0f, 0.0f, 0.0f }; +constexpr int32_t kImpulseSizeInFrames = (int32_t)(sizeof(s_Impulse) / sizeof(s_Impulse[0])); + class PseudoRandom { public: PseudoRandom() {} @@ -156,6 +163,8 @@ const float *needle, int needleSize, LatencyReport *report) { const double threshold = 0.1; + printf("measureLatencyFromEchos: haystackSize = %d, needleSize = %d\n", + haystackSize, needleSize); // Find first peak int first = (int) (findFirstMatch(haystack, @@ -173,7 +182,7 @@ needleSize, threshold) + 0.5); - printf("first = %d, again at %d\n", first, again); + printf("measureLatencyFromEchos: first = %d, again at %d\n", first, again); first = again; // Allocate results array @@ -270,37 +279,60 @@ return mData; } + void setSampleRate(int32_t sampleRate) { + mSampleRate = sampleRate; + } + + int32_t getSampleRate() { + return mSampleRate; + } + int save(const char *fileName, bool writeShorts = true) { + SNDFILE *sndFile = nullptr; int written = 0; - const int chunkSize = 64; - FILE *fid = fopen(fileName, "wb"); - if (fid == NULL) { + SF_INFO info = { + .frames = mFrameCounter, + .samplerate = mSampleRate, + .channels = 1, + .format = SF_FORMAT_WAV | (writeShorts ? SF_FORMAT_PCM_16 : SF_FORMAT_FLOAT) + }; + + sndFile = sf_open(fileName, SFM_WRITE, &info); + if (sndFile == nullptr) { + printf("AudioRecording::save(%s) failed to open file\n", fileName); return -errno; } - if (writeShorts) { - int16_t buffer[chunkSize]; - int32_t framesLeft = mFrameCounter; - int32_t cursor = 0; - while (framesLeft) { - int32_t framesToWrite = framesLeft < chunkSize ? framesLeft : chunkSize; - for (int i = 0; i < framesToWrite; i++) { - buffer[i] = (int16_t) (mData[cursor++] * 32767); - } - written += fwrite(buffer, sizeof(int16_t), framesToWrite, fid); - framesLeft -= framesToWrite; - } - } else { - written = (int) fwrite(mData, sizeof(float), mFrameCounter, fid); - } - fclose(fid); + written = sf_writef_float(sndFile, mData, mFrameCounter); + + sf_close(sndFile); return written; } + int load(const char *fileName) { + SNDFILE *sndFile = nullptr; + SF_INFO info; + + sndFile = sf_open(fileName, SFM_READ, &info); + if (sndFile == nullptr) { + printf("AudioRecording::load(%s) failed to open file\n", fileName); + return -errno; + } + + assert(info.channels == 1); + + allocate(info.frames); + mFrameCounter = sf_readf_float(sndFile, mData, info.frames); + + sf_close(sndFile); + return mFrameCounter; + } + private: float *mData = nullptr; int32_t mFrameCounter = 0; int32_t mMaxFrames = 0; + int32_t mSampleRate = 48000; // common default }; // ==================================================================================== @@ -320,11 +352,25 @@ virtual void printStatus() {}; + virtual int getResult() { + return -1; + } + virtual bool isDone() { return false; } - void setSampleRate(int32_t sampleRate) { + virtual int save(const char *fileName) { + (void) fileName; + return AAUDIO_ERROR_UNIMPLEMENTED; + } + + virtual int load(const char *fileName) { + (void) fileName; + return AAUDIO_ERROR_UNIMPLEMENTED; + } + + virtual void setSampleRate(int32_t sampleRate) { mSampleRate = sampleRate; } @@ -368,8 +414,7 @@ static void printAudioScope(float sample) { - const int maxStars = 80 - ; // arbitrary, fits on one line + const int maxStars = 80; // arbitrary, fits on one line char c = '*'; if (sample < -1.0) { sample = -1.0; @@ -395,7 +440,13 @@ public: EchoAnalyzer() : LoopbackProcessor() { - audioRecorder.allocate(2 * LOOPBACK_SAMPLE_RATE); + mAudioRecording.allocate(2 * getSampleRate()); + mAudioRecording.setSampleRate(getSampleRate()); + } + + void setSampleRate(int32_t sampleRate) override { + LoopbackProcessor::setSampleRate(sampleRate); + mAudioRecording.setSampleRate(sampleRate); } void reset() override { @@ -406,8 +457,12 @@ mState = STATE_INITIAL_SILENCE; } + virtual int getResult() { + return mState == STATE_DONE ? 0 : -1; + } + virtual bool isDone() { - return mState == STATE_DONE; + return mState == STATE_DONE || mState == STATE_FAILED; } void setGain(float gain) { @@ -423,46 +478,47 @@ printf("EchoAnalyzer ---------------\n"); printf(LOOPBACK_RESULT_TAG "measured.gain = %f\n", mMeasuredLoopGain); printf(LOOPBACK_RESULT_TAG "echo.gain = %f\n", mEchoGain); - printf(LOOPBACK_RESULT_TAG "frame.count = %d\n", mFrameCounter); printf(LOOPBACK_RESULT_TAG "test.state = %d\n", mState); if (mMeasuredLoopGain >= 0.9999) { printf(" ERROR - clipping, turn down volume slightly\n"); } else { const float *needle = s_Impulse; int needleSize = (int) (sizeof(s_Impulse) / sizeof(float)); - float *haystack = audioRecorder.getData(); - int haystackSize = audioRecorder.size(); - int result = measureLatencyFromEchos(haystack, haystackSize, - needle, needleSize, - &latencyReport); - if (latencyReport.confidence < 0.01) { - printf(" ERROR - confidence too low = %f\n", latencyReport.confidence); + float *haystack = mAudioRecording.getData(); + int haystackSize = mAudioRecording.size(); + measureLatencyFromEchos(haystack, haystackSize, needle, needleSize, &mLatencyReport); + if (mLatencyReport.confidence < 0.01) { + printf(" ERROR - confidence too low = %f\n", mLatencyReport.confidence); } else { - double latencyMillis = 1000.0 * latencyReport.latencyInFrames / getSampleRate(); - printf(LOOPBACK_RESULT_TAG "latency.frames = %8.2f\n", latencyReport.latencyInFrames); + double latencyMillis = 1000.0 * mLatencyReport.latencyInFrames / getSampleRate(); + printf(LOOPBACK_RESULT_TAG "latency.frames = %8.2f\n", mLatencyReport.latencyInFrames); printf(LOOPBACK_RESULT_TAG "latency.msec = %8.2f\n", latencyMillis); - printf(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f\n", latencyReport.confidence); + printf(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f\n", mLatencyReport.confidence); } } - - { -#define ECHO_FILENAME "/data/oboe_echo.raw" - int written = audioRecorder.save(ECHO_FILENAME); - printf("Echo wrote %d mono samples to %s on Android device\n", written, ECHO_FILENAME); - } } void printStatus() override { - printf("state = %d, echo gain = %f ", mState, mEchoGain); + printf("st = %d, echo gain = %f ", mState, mEchoGain); } - static void sendImpulse(float *outputData, int outputChannelCount) { - for (float sample : s_Impulse) { + void sendImpulses(float *outputData, int outputChannelCount, int numFrames) { + while (numFrames-- > 0) { + float sample = s_Impulse[mSampleIndex++]; + if (mSampleIndex >= kImpulseSizeInFrames) { + mSampleIndex = 0; + } + *outputData = sample; outputData += outputChannelCount; } } + void sendOneImpulse(float *outputData, int outputChannelCount) { + mSampleIndex = 0; + sendImpulses(outputData, outputChannelCount, kImpulseSizeInFrames); + } + void process(float *inputData, int inputChannelCount, float *outputData, int outputChannelCount, int numFrames) override { @@ -488,26 +544,31 @@ break; case STATE_MEASURING_GAIN: - sendImpulse(outputData, outputChannelCount); + sendImpulses(outputData, outputChannelCount, numFrames); peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames); // If we get several in a row then go to next state. if (peak > mPulseThreshold) { if (mDownCounter-- <= 0) { - nextState = STATE_WAITING_FOR_SILENCE; //printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n", // mLoopCounter, peak); mDownCounter = 8; mMeasuredLoopGain = peak; // assumes original pulse amplitude is one // Calculate gain that will give us a nice decaying echo. mEchoGain = mDesiredEchoGain / mMeasuredLoopGain; + if (mEchoGain > MAX_ECHO_GAIN) { + printf("ERROR - loop gain too low. Increase the volume.\n"); + nextState = STATE_FAILED; + } else { + nextState = STATE_WAITING_FOR_SILENCE; + } } - } else { + } else if (numFrames > kImpulseSizeInFrames){ // ignore short callbacks mDownCounter = 8; } break; case STATE_WAITING_FOR_SILENCE: - // Output silence. + // Output silence and wait for the echos to die down. numSamples = numFrames * outputChannelCount; for (int i = 0; i < numSamples; i++) { outputData[i] = 0; @@ -526,14 +587,14 @@ break; case STATE_SENDING_PULSE: - audioRecorder.write(inputData, inputChannelCount, numFrames); - sendImpulse(outputData, outputChannelCount); + mAudioRecording.write(inputData, inputChannelCount, numFrames); + sendOneImpulse(outputData, outputChannelCount); nextState = STATE_GATHERING_ECHOS; //printf("%5d: switch to STATE_GATHERING_ECHOS\n", mLoopCounter); break; case STATE_GATHERING_ECHOS: - numWritten = audioRecorder.write(inputData, inputChannelCount, numFrames); + numWritten = mAudioRecording.write(inputData, inputChannelCount, numFrames); peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames); if (peak > mMeasuredLoopGain) { mMeasuredLoopGain = peak; // AGC might be raising gain so adjust it on the fly. @@ -567,6 +628,14 @@ mLoopCounter++; } + int save(const char *fileName) override { + return mAudioRecording.save(fileName); + } + + int load(const char *fileName) override { + return mAudioRecording.load(fileName); + } + private: enum echo_state_t { @@ -575,23 +644,23 @@ STATE_WAITING_FOR_SILENCE, STATE_SENDING_PULSE, STATE_GATHERING_ECHOS, - STATE_DONE + STATE_DONE, + STATE_FAILED }; - int mDownCounter = 500; - int mLoopCounter = 0; - int mLoopStart = 1000; - float mPulseThreshold = 0.02f; - float mSilenceThreshold = 0.002f; - float mMeasuredLoopGain = 0.0f; - float mDesiredEchoGain = 0.95f; - float mEchoGain = 1.0f; - echo_state_t mState = STATE_INITIAL_SILENCE; - int32_t mFrameCounter = 0; + int32_t mDownCounter = 500; + int32_t mLoopCounter = 0; + int32_t mSampleIndex = 0; + float mPulseThreshold = 0.02f; + float mSilenceThreshold = 0.002f; + float mMeasuredLoopGain = 0.0f; + float mDesiredEchoGain = 0.95f; + float mEchoGain = 1.0f; + echo_state_t mState = STATE_INITIAL_SILENCE; - AudioRecording audioRecorder; - LatencyReport latencyReport; - PeakDetector mPeakDetector; + AudioRecording mAudioRecording; // contains only the input after the gain detection burst + LatencyReport mLatencyReport; + // PeakDetector mPeakDetector; }; @@ -605,6 +674,10 @@ class SineAnalyzer : public LoopbackProcessor { public: + virtual int getResult() { + return mState == STATE_LOCKED ? 0 : -1; + } + void report() override { printf("SineAnalyzer ------------------\n"); printf(LOOPBACK_RESULT_TAG "peak.amplitude = %7.5f\n", mPeakAmplitude); @@ -612,7 +685,7 @@ printf(LOOPBACK_RESULT_TAG "phase.offset = %7.5f\n", mPhaseOffset); printf(LOOPBACK_RESULT_TAG "ref.phase = %7.5f\n", mPhase); printf(LOOPBACK_RESULT_TAG "frames.accumulated = %6d\n", mFramesAccumulated); - printf(LOOPBACK_RESULT_TAG "sine.period = %6d\n", mPeriod); + printf(LOOPBACK_RESULT_TAG "sine.period = %6d\n", mSinePeriod); printf(LOOPBACK_RESULT_TAG "test.state = %6d\n", mState); printf(LOOPBACK_RESULT_TAG "frame.count = %6d\n", mFrameCounter); // Did we ever get a lock? @@ -626,7 +699,7 @@ } void printStatus() override { - printf(" state = %d, glitches = %d,", mState, mGlitchCount); + printf("st = %d, #gl = %3d,", mState, mGlitchCount); } double calculateMagnitude(double *phasePtr = NULL) { @@ -651,7 +724,8 @@ void process(float *inputData, int inputChannelCount, float *outputData, int outputChannelCount, int numFrames) override { - float sample; + mProcessCount++; + float peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames); if (peak > mPeakAmplitude) { mPeakAmplitude = peak; @@ -663,6 +737,7 @@ float sinOut = sinf(mPhase); switch (mState) { + case STATE_IDLE: case STATE_IMMUNE: case STATE_WAITING_FOR_SIGNAL: break; @@ -671,7 +746,7 @@ mCosAccumulator += sample * cosf(mPhase); mFramesAccumulated++; // Must be a multiple of the period or the calculation will not be accurate. - if (mFramesAccumulated == mPeriod * 4) { + if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) { mPhaseOffset = 0.0; mMagnitude = calculateMagnitude(&mPhaseOffset); if (mMagnitude > mThreshold) { @@ -697,7 +772,22 @@ // mFrameCounter, mGlitchCount, predicted, sample); mState = STATE_IMMUNE; //printf("%5d: switch to STATE_IMMUNE\n", mFrameCounter); - mDownCounter = mPeriod; // Set duration of IMMUNE state. + mDownCounter = mSinePeriod; // Set duration of IMMUNE state. + } + + // Track incoming signal and slowly adjust magnitude to account + // for drift in the DRC or AGC. + mSinAccumulator += sample * sinOut; + mCosAccumulator += sample * cosf(mPhase); + mFramesAccumulated++; + // Must be a multiple of the period or the calculation will not be accurate. + if (mFramesAccumulated == mSinePeriod) { + const double coefficient = 0.1; + double phaseOffset = 0.0; + double magnitude = calculateMagnitude(&phaseOffset); + // One pole averaging filter. + mMagnitude = (mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient); + resetAccumulator(); } } break; } @@ -718,6 +808,9 @@ // Do these once per buffer. switch (mState) { + case STATE_IDLE: + mState = STATE_IMMUNE; // so we can tell when + break; case STATE_IMMUNE: mDownCounter -= numFrames; if (mDownCounter <= 0) { @@ -748,21 +841,29 @@ void reset() override { mGlitchCount = 0; mState = STATE_IMMUNE; - mPhaseIncrement = 2.0 * M_PI / mPeriod; - printf("phaseInc = %f for period %d\n", mPhaseIncrement, mPeriod); + mDownCounter = IMMUNE_FRAME_COUNT; + mPhaseIncrement = 2.0 * M_PI / mSinePeriod; + printf("phaseInc = %f for period %d\n", mPhaseIncrement, mSinePeriod); resetAccumulator(); + mProcessCount = 0; } private: enum sine_state_t { + STATE_IDLE, STATE_IMMUNE, STATE_WAITING_FOR_SIGNAL, STATE_WAITING_FOR_LOCK, STATE_LOCKED }; - int mPeriod = 79; + enum constants { + IMMUNE_FRAME_COUNT = 48 * 500, + PERIODS_NEEDED_FOR_LOCK = 8 + }; + + int mSinePeriod = 79; double mPhaseIncrement = 0.0; double mPhase = 0.0; double mPhaseOffset = 0.0; @@ -771,20 +872,19 @@ double mThreshold = 0.005; double mTolerance = 0.01; int32_t mFramesAccumulated = 0; + int32_t mProcessCount = 0; double mSinAccumulator = 0.0; double mCosAccumulator = 0.0; int32_t mGlitchCount = 0; double mPeakAmplitude = 0.0; - int mDownCounter = 4000; + int mDownCounter = IMMUNE_FRAME_COUNT; int32_t mFrameCounter = 0; float mOutputAmplitude = 0.75; - int32_t mZeroCrossings = 0; - PseudoRandom mWhiteNoise; float mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC. - sine_state_t mState = STATE_IMMUNE; + sine_state_t mState = STATE_IDLE; };
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp index df0df04..91ebf73 100644 --- a/media/libaaudio/examples/loopback/src/loopback.cpp +++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -19,9 +19,12 @@ #include <algorithm> #include <assert.h> #include <cctype> +#include <errno.h> #include <math.h> #include <stdio.h> #include <stdlib.h> +#include <stdlib.h> +#include <string.h> #include <unistd.h> #include <aaudio/AAudio.h> @@ -34,23 +37,32 @@ // Tag for machine readable results as property = value pairs #define RESULT_TAG "RESULT: " -#define SAMPLE_RATE 48000 #define NUM_SECONDS 5 +#define PERIOD_MILLIS 1000 #define NUM_INPUT_CHANNELS 1 -#define FILENAME "/data/oboe_input.raw" -#define APP_VERSION "0.1.22" +#define FILENAME_ALL "/data/loopback_all.wav" +#define FILENAME_ECHOS "/data/loopback_echos.wav" +#define APP_VERSION "0.2.04" + +constexpr int kNumCallbacksToDrain = 20; +constexpr int kNumCallbacksToDiscard = 20; struct LoopbackData { AAudioStream *inputStream = nullptr; int32_t inputFramesMaximum = 0; - int16_t *inputData = nullptr; - int16_t peakShort = 0; - float *conversionBuffer = nullptr; + int16_t *inputShortData = nullptr; + float *inputFloatData = nullptr; + aaudio_format_t actualInputFormat = AAUDIO_FORMAT_INVALID; int32_t actualInputChannelCount = 0; int32_t actualOutputChannelCount = 0; - int32_t inputBuffersToDiscard = 10; + int32_t numCallbacksToDrain = kNumCallbacksToDrain; + int32_t numCallbacksToDiscard = kNumCallbacksToDiscard; int32_t minNumFrames = INT32_MAX; int32_t maxNumFrames = 0; + int32_t insufficientReadCount = 0; + int32_t insufficientReadFrames = 0; + int32_t framesReadTotal = 0; + int32_t framesWrittenTotal = 0; bool isDone = false; aaudio_result_t inputError = AAUDIO_OK; @@ -58,14 +70,14 @@ SineAnalyzer sineAnalyzer; EchoAnalyzer echoAnalyzer; - AudioRecording audioRecorder; + AudioRecording audioRecording; LoopbackProcessor *loopbackProcessor; }; static void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples) { - const float scaler = 1.0f / 32768.0f; + constexpr float scaler = 1.0f / 32768.0f; for (int i = 0; i < numSamples; i++) { destination[i] = source[i] * scaler; } @@ -75,6 +87,31 @@ // ========================= CALLBACK ================================================= // ==================================================================================== // Callback function that fills the audio output buffer. + +static int32_t readFormattedData(LoopbackData *myData, int32_t numFrames) { + int32_t framesRead = AAUDIO_ERROR_INVALID_FORMAT; + if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_I16) { + framesRead = AAudioStream_read(myData->inputStream, myData->inputShortData, + numFrames, + 0 /* timeoutNanoseconds */); + } else if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_FLOAT) { + framesRead = AAudioStream_read(myData->inputStream, myData->inputFloatData, + numFrames, + 0 /* timeoutNanoseconds */); + } else { + printf("ERROR actualInputFormat = %d\n", myData->actualInputFormat); + assert(false); + } + if (framesRead < 0) { + myData->inputError = framesRead; + printf("ERROR in read = %d = %s\n", framesRead, + AAudio_convertResultToText(framesRead)); + } else { + myData->framesReadTotal += framesRead; + } + return framesRead; +} + static aaudio_data_callback_result_t MyDataCallbackProc( AAudioStream *outputStream, void *userData, @@ -87,7 +124,7 @@ float *outputData = (float *) audioData; // Read audio data from the input stream. - int32_t framesRead; + int32_t actualFramesRead; if (numFrames > myData->inputFramesMaximum) { myData->inputError = AAUDIO_ERROR_OUT_OF_RANGE; @@ -101,46 +138,86 @@ myData->minNumFrames = numFrames; } - if (myData->inputBuffersToDiscard > 0) { + // Silence the output. + int32_t numBytes = numFrames * myData->actualOutputChannelCount * sizeof(float); + memset(audioData, 0 /* value */, numBytes); + + if (myData->numCallbacksToDrain > 0) { // Drain the input. + int32_t totalFramesRead = 0; do { - framesRead = AAudioStream_read(myData->inputStream, myData->inputData, - numFrames, 0); - if (framesRead < 0) { - myData->inputError = framesRead; - printf("ERROR in read = %d", framesRead); - result = AAUDIO_CALLBACK_RESULT_STOP; - } else if (framesRead > 0) { - myData->inputBuffersToDiscard--; + actualFramesRead = readFormattedData(myData, numFrames); + if (actualFramesRead) { + totalFramesRead += actualFramesRead; } - } while(framesRead > 0); - } else { - framesRead = AAudioStream_read(myData->inputStream, myData->inputData, - numFrames, 0); - if (framesRead < 0) { - myData->inputError = framesRead; - printf("ERROR in read = %d", framesRead); + // Ignore errors because input stream may not be started yet. + } while (actualFramesRead > 0); + // Only counts if we actually got some data. + if (totalFramesRead > 0) { + myData->numCallbacksToDrain--; + } + + } else if (myData->numCallbacksToDiscard > 0) { + // Ignore. Allow the input to fill back up to equilibrium with the output. + actualFramesRead = readFormattedData(myData, numFrames); + if (actualFramesRead < 0) { result = AAUDIO_CALLBACK_RESULT_STOP; - } else if (framesRead > 0) { + } + myData->numCallbacksToDiscard--; - myData->audioRecorder.write(myData->inputData, - myData->actualInputChannelCount, - numFrames); + } else { - int32_t numSamples = framesRead * myData->actualInputChannelCount; - convertPcm16ToFloat(myData->inputData, myData->conversionBuffer, numSamples); + int32_t numInputBytes = numFrames * myData->actualInputChannelCount * sizeof(float); + memset(myData->inputFloatData, 0 /* value */, numInputBytes); - myData->loopbackProcessor->process(myData->conversionBuffer, - myData->actualInputChannelCount, - outputData, - myData->actualOutputChannelCount, - framesRead); + // Process data after equilibrium. + int64_t inputFramesWritten = AAudioStream_getFramesWritten(myData->inputStream); + int64_t inputFramesRead = AAudioStream_getFramesRead(myData->inputStream); + int64_t framesAvailable = inputFramesWritten - inputFramesRead; + actualFramesRead = readFormattedData(myData, numFrames); + if (actualFramesRead < 0) { + result = AAUDIO_CALLBACK_RESULT_STOP; + } else { + + if (actualFramesRead < numFrames) { + if(actualFramesRead < (int32_t) framesAvailable) { + printf("insufficient but numFrames = %d" + ", actualFramesRead = %d" + ", inputFramesWritten = %d" + ", inputFramesRead = %d" + ", available = %d\n", + numFrames, + actualFramesRead, + (int) inputFramesWritten, + (int) inputFramesRead, + (int) framesAvailable); + } + myData->insufficientReadCount++; + myData->insufficientReadFrames += numFrames - actualFramesRead; // deficit + } + + int32_t numSamples = actualFramesRead * myData->actualInputChannelCount; + + if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_I16) { + convertPcm16ToFloat(myData->inputShortData, myData->inputFloatData, numSamples); + } + // Save for later. + myData->audioRecording.write(myData->inputFloatData, + myData->actualInputChannelCount, + numFrames); + // Analyze the data. + myData->loopbackProcessor->process(myData->inputFloatData, + myData->actualInputChannelCount, + outputData, + myData->actualOutputChannelCount, + numFrames); myData->isDone = myData->loopbackProcessor->isDone(); if (myData->isDone) { result = AAUDIO_CALLBACK_RESULT_STOP; } } } + myData->framesWrittenTotal += numFrames; return result; } @@ -148,29 +225,29 @@ static void MyErrorCallbackProc( AAudioStream *stream __unused, void *userData __unused, - aaudio_result_t error) -{ + aaudio_result_t error) { printf("Error Callback, error: %d\n",(int)error); LoopbackData *myData = (LoopbackData *) userData; myData->outputError = error; } static void usage() { - printf("loopback: -n{numBursts} -p{outPerf} -P{inPerf} -t{test} -g{gain} -f{freq}\n"); - printf(" -c{inputChannels}\n"); - printf(" -f{freq} sine frequency\n"); - printf(" -g{gain} recirculating loopback gain\n"); - printf(" -m enable MMAP mode\n"); - printf(" -n{numBursts} buffer size, for example 2 for double buffered\n"); - printf(" -p{outPerf} set output AAUDIO_PERFORMANCE_MODE*\n"); - printf(" -P{inPerf} set input AAUDIO_PERFORMANCE_MODE*\n"); - printf(" n for _NONE\n"); - printf(" l for _LATENCY\n"); - printf(" p for _POWER_SAVING;\n"); - printf(" -t{test} select test mode\n"); - printf(" m for sine magnitude\n"); - printf(" e for echo latency (default)\n"); - printf("For example: loopback -b2 -pl -Pn\n"); + printf("Usage: aaudio_loopback [OPTION]...\n\n"); + AAudioArgsParser::usage(); + printf(" -B{frames} input capacity in frames\n"); + printf(" -C{channels} number of input channels\n"); + printf(" -F{0,1,2} input format, 1=I16, 2=FLOAT\n"); + printf(" -g{gain} recirculating loopback gain\n"); + printf(" -P{inPerf} set input AAUDIO_PERFORMANCE_MODE*\n"); + printf(" n for _NONE\n"); + printf(" l for _LATENCY\n"); + printf(" p for _POWER_SAVING\n"); + printf(" -t{test} select test mode\n"); + printf(" m for sine magnitude\n"); + printf(" e for echo latency (default)\n"); + printf(" f for file latency, analyzes %s\n\n", FILENAME_ECHOS); + printf(" -X use EXCLUSIVE mode for input\n"); + printf("Example: aaudio_loopback -n2 -pl -Pl -x\n"); } static aaudio_performance_mode_t parsePerformanceMode(char c) { @@ -196,6 +273,7 @@ enum { TEST_SINE_MAGNITUDE = 0, TEST_ECHO_LATENCY, + TEST_FILE_LATENCY, }; static int parseTestMode(char c) { @@ -208,6 +286,9 @@ case 'e': testMode = TEST_ECHO_LATENCY; break; + case 'f': + testMode = TEST_FILE_LATENCY; + break; default: printf("ERROR in value test mode %c\n", c); break; @@ -231,9 +312,10 @@ } } float gain = 0.98f / maxSample; + for (int32_t i = start; i < end; i++) { float sample = data[i]; - printf("%5.3f ", sample); // actual value + printf("%6d: %7.4f ", i, sample); // actual value sample *= gain; printAudioScope(sample); } @@ -245,33 +327,28 @@ int main(int argc, const char **argv) { - AAudioArgsParser argParser; - AAudioSimplePlayer player; - AAudioSimpleRecorder recorder; - LoopbackData loopbackData; - AAudioStream *outputStream = nullptr; + AAudioArgsParser argParser; + AAudioSimplePlayer player; + AAudioSimpleRecorder recorder; + LoopbackData loopbackData; + AAudioStream *inputStream = nullptr; + AAudioStream *outputStream = nullptr; - aaudio_result_t result = AAUDIO_OK; - aaudio_sharing_mode_t requestedInputSharingMode = AAUDIO_SHARING_MODE_SHARED; + aaudio_result_t result = AAUDIO_OK; + aaudio_sharing_mode_t requestedInputSharingMode = AAUDIO_SHARING_MODE_SHARED; int requestedInputChannelCount = NUM_INPUT_CHANNELS; - const int requestedOutputChannelCount = AAUDIO_UNSPECIFIED; - int actualSampleRate = 0; - const aaudio_format_t requestedInputFormat = AAUDIO_FORMAT_PCM_I16; - const aaudio_format_t requestedOutputFormat = AAUDIO_FORMAT_PCM_FLOAT; - aaudio_format_t actualInputFormat; - aaudio_format_t actualOutputFormat; - aaudio_performance_mode_t outputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY; - aaudio_performance_mode_t inputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY; + aaudio_format_t requestedInputFormat = AAUDIO_FORMAT_UNSPECIFIED; + int32_t requestedInputCapacity = -1; + aaudio_performance_mode_t inputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY; - int testMode = TEST_ECHO_LATENCY; - double gain = 1.0; + int32_t outputFramesPerBurst = 0; - aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED; - int32_t framesPerBurst = 0; - float *outputData = NULL; - double deviation; - double latency; - int32_t burstsPerBuffer = 1; // single buffered + aaudio_format_t actualOutputFormat = AAUDIO_FORMAT_INVALID; + int32_t actualSampleRate = 0; + int written = 0; + + int testMode = TEST_ECHO_LATENCY; + double gain = 1.0; // Make printf print immediately so that debug info is not stuck // in a buffer if we hang or crash. @@ -286,9 +363,15 @@ if (arg[0] == '-') { char option = arg[1]; switch (option) { + case 'B': + requestedInputCapacity = atoi(&arg[2]); + break; case 'C': requestedInputChannelCount = atoi(&arg[2]); break; + case 'F': + requestedInputFormat = atoi(&arg[2]); + break; case 'g': gain = atof(&arg[2]); break; @@ -321,8 +404,9 @@ } int32_t requestedDuration = argParser.getDurationSeconds(); - int32_t recordingDuration = std::min(60, requestedDuration); - loopbackData.audioRecorder.allocate(recordingDuration * SAMPLE_RATE); + int32_t requestedDurationMillis = requestedDuration * MILLIS_PER_SECOND; + int32_t timeMillis = 0; + int32_t recordingDuration = std::min(60 * 5, requestedDuration); switch(testMode) { case TEST_SINE_MAGNITUDE: @@ -332,61 +416,112 @@ loopbackData.echoAnalyzer.setGain(gain); loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer; break; + case TEST_FILE_LATENCY: { + loopbackData.echoAnalyzer.setGain(gain); + + loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer; + int read = loopbackData.loopbackProcessor->load(FILENAME_ECHOS); + printf("main() read %d mono samples from %s on Android device\n", read, FILENAME_ECHOS); + loopbackData.loopbackProcessor->report(); + return 0; + } + break; default: exit(1); break; } printf("OUTPUT stream ----------------------------------------\n"); - argParser.setFormat(requestedOutputFormat); result = player.open(argParser, MyDataCallbackProc, MyErrorCallbackProc, &loopbackData); if (result != AAUDIO_OK) { fprintf(stderr, "ERROR - player.open() returned %d\n", result); - goto finish; + exit(1); } outputStream = player.getStream(); - argParser.compareWithStream(outputStream); actualOutputFormat = AAudioStream_getFormat(outputStream); - assert(actualOutputFormat == AAUDIO_FORMAT_PCM_FLOAT); + if (actualOutputFormat != AAUDIO_FORMAT_PCM_FLOAT) { + fprintf(stderr, "ERROR - only AAUDIO_FORMAT_PCM_FLOAT supported\n"); + exit(1); + } - printf("INPUT stream ----------------------------------------\n"); + actualSampleRate = AAudioStream_getSampleRate(outputStream); + loopbackData.audioRecording.allocate(recordingDuration * actualSampleRate); + loopbackData.audioRecording.setSampleRate(actualSampleRate); + outputFramesPerBurst = AAudioStream_getFramesPerBurst(outputStream); + + argParser.compareWithStream(outputStream); + + printf("INPUT stream ----------------------------------------\n"); // Use different parameters for the input. argParser.setNumberOfBursts(AAUDIO_UNSPECIFIED); argParser.setFormat(requestedInputFormat); argParser.setPerformanceMode(inputPerformanceLevel); argParser.setChannelCount(requestedInputChannelCount); argParser.setSharingMode(requestedInputSharingMode); + + // Make sure the input buffer has plenty of capacity. + // Extra capacity on input should not increase latency if we keep it drained. + int32_t inputBufferCapacity = requestedInputCapacity; + if (inputBufferCapacity < 0) { + int32_t outputBufferCapacity = AAudioStream_getBufferCapacityInFrames(outputStream); + inputBufferCapacity = 2 * outputBufferCapacity; + } + argParser.setBufferCapacity(inputBufferCapacity); + result = recorder.open(argParser); if (result != AAUDIO_OK) { fprintf(stderr, "ERROR - recorder.open() returned %d\n", result); goto finish; } - loopbackData.inputStream = recorder.getStream(); - argParser.compareWithStream(loopbackData.inputStream); + inputStream = loopbackData.inputStream = recorder.getStream(); - // This is the number of frames that are read in one chunk by a DMA controller - // or a DSP or a mixer. - framesPerBurst = AAudioStream_getFramesPerBurst(outputStream); + { + int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(inputStream); + result = AAudioStream_setBufferSizeInFrames(inputStream, actualCapacity); + if (result < 0) { + fprintf(stderr, "ERROR - AAudioStream_setBufferSizeInFrames() returned %d\n", result); + goto finish; + } else {} + } - actualInputFormat = AAudioStream_getFormat(outputStream); - assert(actualInputFormat == AAUDIO_FORMAT_PCM_I16); + argParser.compareWithStream(inputStream); + // If the input stream is too small then we cannot satisfy the output callback. + { + int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(inputStream); + if (actualCapacity < 2 * outputFramesPerBurst) { + fprintf(stderr, "ERROR - input capacity < 2 * outputFramesPerBurst\n"); + goto finish; + } + } + + // ------- Setup loopbackData ----------------------------- + loopbackData.actualInputFormat = AAudioStream_getFormat(inputStream); loopbackData.actualInputChannelCount = recorder.getChannelCount(); loopbackData.actualOutputChannelCount = player.getChannelCount(); // Allocate a buffer for the audio data. - loopbackData.inputFramesMaximum = 32 * framesPerBurst; - loopbackData.inputBuffersToDiscard = 100; + loopbackData.inputFramesMaximum = 32 * AAudioStream_getFramesPerBurst(inputStream); - loopbackData.inputData = new int16_t[loopbackData.inputFramesMaximum - * loopbackData.actualInputChannelCount]; - loopbackData.conversionBuffer = new float[loopbackData.inputFramesMaximum * - loopbackData.actualInputChannelCount]; + if (loopbackData.actualInputFormat == AAUDIO_FORMAT_PCM_I16) { + loopbackData.inputShortData = new int16_t[loopbackData.inputFramesMaximum + * loopbackData.actualInputChannelCount]{}; + } + loopbackData.inputFloatData = new float[loopbackData.inputFramesMaximum * + loopbackData.actualInputChannelCount]{}; loopbackData.loopbackProcessor->reset(); + // Start OUTPUT first so INPUT does not overflow. + result = player.start(); + if (result != AAUDIO_OK) { + printf("ERROR - AAudioStream_requestStart(output) returned %d = %s\n", + result, AAudio_convertResultToText(result)); + goto finish; + } + result = recorder.start(); if (result != AAUDIO_OK) { printf("ERROR - AAudioStream_requestStart(input) returned %d = %s\n", @@ -394,16 +529,8 @@ goto finish; } - result = player.start(); - if (result != AAUDIO_OK) { - printf("ERROR - AAudioStream_requestStart(output) returned %d = %s\n", - result, AAudio_convertResultToText(result)); - goto finish; - } - - printf("------- sleep while the callback runs --------------\n"); - fflush(stdout); - for (int i = requestedDuration; i > 0 ; i--) { + printf("------- sleep and log while the callback runs --------------\n"); + while (timeMillis <= requestedDurationMillis) { if (loopbackData.inputError != AAUDIO_OK) { printf(" ERROR on input stream\n"); break; @@ -411,60 +538,128 @@ printf(" ERROR on output stream\n"); break; } else if (loopbackData.isDone) { - printf(" test says it is done!\n"); + printf(" Test says it is DONE!\n"); break; } else { - sleep(1); - printf("%4d: ", i); + // Log a line of stream data. + printf("%7.3f: ", 0.001 * timeMillis); // display in seconds loopbackData.loopbackProcessor->printStatus(); + printf(" insf %3d,", (int) loopbackData.insufficientReadCount); - int64_t inputFramesWritten = AAudioStream_getFramesWritten(loopbackData.inputStream); - int64_t inputFramesRead = AAudioStream_getFramesRead(loopbackData.inputStream); + int64_t inputFramesWritten = AAudioStream_getFramesWritten(inputStream); + int64_t inputFramesRead = AAudioStream_getFramesRead(inputStream); int64_t outputFramesWritten = AAudioStream_getFramesWritten(outputStream); int64_t outputFramesRead = AAudioStream_getFramesRead(outputStream); - printf(" INPUT: wr %lld rd %lld state %s, OUTPUT: wr %lld rd %lld state %s, xruns %d\n", + static const int textOffset = strlen("AAUDIO_STREAM_STATE_"); // strip this off + printf(" | INPUT: wr %7lld - rd %7lld = %5lld, st %8s, oruns %3d", (long long) inputFramesWritten, (long long) inputFramesRead, - AAudio_convertStreamStateToText(AAudioStream_getState(loopbackData.inputStream)), + (long long) (inputFramesWritten - inputFramesRead), + &AAudio_convertStreamStateToText( + AAudioStream_getState(inputStream))[textOffset], + AAudioStream_getXRunCount(inputStream)); + + printf(" | OUTPUT: wr %7lld - rd %7lld = %5lld, st %8s, uruns %3d\n", (long long) outputFramesWritten, (long long) outputFramesRead, - AAudio_convertStreamStateToText(AAudioStream_getState(outputStream)), + (long long) (outputFramesWritten - outputFramesRead), + &AAudio_convertStreamStateToText( + AAudioStream_getState(outputStream))[textOffset], AAudioStream_getXRunCount(outputStream) ); } + int32_t periodMillis = (timeMillis < 2000) ? PERIOD_MILLIS / 4 : PERIOD_MILLIS; + usleep(periodMillis * 1000); + timeMillis += periodMillis; + } + + result = player.stop(); + if (result != AAUDIO_OK) { + printf("ERROR - player.stop() returned %d = %s\n", + result, AAudio_convertResultToText(result)); + goto finish; + } + + result = recorder.stop(); + if (result != AAUDIO_OK) { + printf("ERROR - recorder.stop() returned %d = %s\n", + result, AAudio_convertResultToText(result)); + goto finish; } printf("input error = %d = %s\n", - loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError)); - - printf("AAudioStream_getXRunCount %d\n", AAudioStream_getXRunCount(outputStream)); - printf("framesRead = %8d\n", (int) AAudioStream_getFramesRead(outputStream)); - printf("framesWritten = %8d\n", (int) AAudioStream_getFramesWritten(outputStream)); - printf("min numFrames = %8d\n", (int) loopbackData.minNumFrames); - printf("max numFrames = %8d\n", (int) loopbackData.maxNumFrames); + loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError)); if (loopbackData.inputError == AAUDIO_OK) { if (testMode == TEST_SINE_MAGNITUDE) { - printAudioGraph(loopbackData.audioRecorder, 200); + printAudioGraph(loopbackData.audioRecording, 200); } + // Print again so we don't have to scroll past waveform. + printf("OUTPUT Stream ----------------------------------------\n"); + argParser.compareWithStream(outputStream); + printf("INPUT Stream ----------------------------------------\n"); + argParser.compareWithStream(inputStream); + loopbackData.loopbackProcessor->report(); } { - int written = loopbackData.audioRecorder.save(FILENAME); - printf("main() wrote %d mono samples to %s on Android device\n", written, FILENAME); + int32_t framesRead = AAudioStream_getFramesRead(inputStream); + int32_t framesWritten = AAudioStream_getFramesWritten(inputStream); + printf("Callback Results ---------------------------------------- INPUT\n"); + printf(" input overruns = %d\n", AAudioStream_getXRunCount(inputStream)); + printf(" framesWritten = %8d\n", framesWritten); + printf(" framesRead = %8d\n", framesRead); + printf(" myFramesRead = %8d\n", (int) loopbackData.framesReadTotal); + printf(" written - read = %8d\n", (int) (framesWritten - framesRead)); + printf(" insufficient # = %8d\n", (int) loopbackData.insufficientReadCount); + if (loopbackData.insufficientReadCount > 0) { + printf(" insufficient frames = %8d\n", (int) loopbackData.insufficientReadFrames); + } + } + { + int32_t framesRead = AAudioStream_getFramesRead(outputStream); + int32_t framesWritten = AAudioStream_getFramesWritten(outputStream); + printf("Callback Results ---------------------------------------- OUTPUT\n"); + printf(" output underruns = %d\n", AAudioStream_getXRunCount(outputStream)); + printf(" myFramesWritten = %8d\n", (int) loopbackData.framesWrittenTotal); + printf(" framesWritten = %8d\n", framesWritten); + printf(" framesRead = %8d\n", framesRead); + printf(" min numFrames = %8d\n", (int) loopbackData.minNumFrames); + printf(" max numFrames = %8d\n", (int) loopbackData.maxNumFrames); + } + + written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS); + if (written > 0) { + printf("main() wrote %8d mono samples to \"%s\" on Android device\n", + written, FILENAME_ECHOS); + } + + written = loopbackData.audioRecording.save(FILENAME_ALL); + if (written > 0) { + printf("main() wrote %8d mono samples to \"%s\" on Android device\n", + written, FILENAME_ALL); + } + + if (loopbackData.loopbackProcessor->getResult() < 0) { + printf("ERROR: LOOPBACK PROCESSING FAILED. Maybe because the volume was too low.\n"); + result = loopbackData.loopbackProcessor->getResult(); + } + if (loopbackData.insufficientReadCount > 3) { + printf("ERROR: LOOPBACK PROCESSING FAILED. insufficientReadCount too high\n"); + result = AAUDIO_ERROR_UNAVAILABLE; } finish: player.close(); recorder.close(); - delete[] loopbackData.conversionBuffer; - delete[] loopbackData.inputData; - delete[] outputData; + delete[] loopbackData.inputFloatData; + delete[] loopbackData.inputShortData; - printf(RESULT_TAG "error = %d = %s\n", result, AAudio_convertResultToText(result)); - if ((result != AAUDIO_OK)) { - printf("error %d = %s\n", result, AAudio_convertResultToText(result)); + printf(RESULT_TAG "result = %d \n", result); // machine readable + printf("result is %s\n", AAudio_convertResultToText(result)); // human readable + if (result != AAUDIO_OK) { + printf("FAILURE\n"); return EXIT_FAILURE; } else { printf("SUCCESS\n");
diff --git a/media/libaaudio/examples/loopback/src/loopback.sh b/media/libaaudio/examples/loopback/src/loopback.sh index bc63125..a5712b8 100644 --- a/media/libaaudio/examples/loopback/src/loopback.sh +++ b/media/libaaudio/examples/loopback/src/loopback.sh
@@ -1,10 +1,30 @@ #!/system/bin/sh # Run a loopback test in the background after a delay. -# To run the script enter: +# To run the script, enter these commands once: +# adb disable-verity +# adb reboot +# adb remount +# adb sync +# adb push loopback.sh /data/ +# For each test run: # adb shell "nohup sh /data/loopback.sh &" +# Quickly connect USB audio if needed, either manually or via Tigertail switch. +# Wait until the test completes, restore USB to host if needed, and then: +# adb pull /data/loopreport.txt +# adb pull /data/loopback_all.wav +# adb pull /data/loopback_echos.wav SLEEP_TIME=10 -TEST_COMMAND="aaudio_loopback -pl -Pl -C1 -n2 -m2 -tm -d5" +TEST_COMMAND="/data/nativetest/aaudio_loopback/aaudio_loopback -pl -Pl -C1 -n2 -m2 -te -d5" +# Partial list of options: +# -pl (output) performance mode: low latency +# -Pl input performance mode: low latency +# -C1 input channel count: 1 +# -n2 number of bursts: 2 +# -m2 mmap policy: 2 +# -t? test mode: -tm for sine magnitude, -te for echo latency, -tf for file latency +# -d5 device ID +# For full list of available options, see AAudioArgsParser.h and loopback.cpp echo "Plug in USB Mir and Fun Plug." echo "Test will start in ${SLEEP_TIME} seconds: ${TEST_COMMAND}"
diff --git a/media/libaaudio/examples/utils/AAudioArgsParser.h b/media/libaaudio/examples/utils/AAudioArgsParser.h index ada37e2..88d7401 100644 --- a/media/libaaudio/examples/utils/AAudioArgsParser.h +++ b/media/libaaudio/examples/utils/AAudioArgsParser.h
@@ -17,7 +17,10 @@ #ifndef AAUDIO_EXAMPLE_ARGS_PARSER_H #define AAUDIO_EXAMPLE_ARGS_PARSER_H -#include <cctype> +#define MAX_CHANNELS 8 + +//#include <cctype> +#include <dlfcn.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> @@ -27,7 +30,63 @@ #include "AAudioExampleUtils.h" -// TODO use this as a base class within AAudio + +static void (*s_setUsage)(AAudioStreamBuilder* builder, aaudio_usage_t usage) = nullptr; +static void (*s_setContentType)(AAudioStreamBuilder* builder, + aaudio_content_type_t contentType) = nullptr; +static void (*s_setInputPreset)(AAudioStreamBuilder* builder, + aaudio_input_preset_t inputPreset) = nullptr; + +static bool s_loadAttempted = false; +static aaudio_usage_t (*s_getUsage)(AAudioStream *stream) = nullptr; +static aaudio_content_type_t (*s_getContentType)(AAudioStream *stream) = nullptr; +static aaudio_input_preset_t (*s_getInputPreset)(AAudioStream *stream) = nullptr; + +// Link to test functions in shared library. +static void loadFutureFunctions() { + if (s_loadAttempted) return; // only try once + s_loadAttempted = true; + + void *handle = dlopen("libaaudio.so", RTLD_NOW); + if (handle != nullptr) { + s_setUsage = (void (*)(AAudioStreamBuilder *, aaudio_usage_t)) + dlsym(handle, "AAudioStreamBuilder_setUsage"); + if (s_setUsage == nullptr) goto error; + + s_setContentType = (void (*)(AAudioStreamBuilder *, aaudio_content_type_t)) + dlsym(handle, "AAudioStreamBuilder_setContentType"); + if (s_setContentType == nullptr) goto error; + + s_setInputPreset = (void (*)(AAudioStreamBuilder *, aaudio_input_preset_t)) + dlsym(handle, "AAudioStreamBuilder_setInputPreset"); + if (s_setInputPreset == nullptr) goto error; + + s_getUsage = (aaudio_usage_t (*)(AAudioStream *)) + dlsym(handle, "AAudioStream_getUsage"); + if (s_getUsage == nullptr) goto error; + + s_getContentType = (aaudio_content_type_t (*)(AAudioStream *)) + dlsym(handle, "AAudioStream_getContentType"); + if (s_getContentType == nullptr) goto error; + + s_getInputPreset = (aaudio_input_preset_t (*)(AAudioStream *)) + dlsym(handle, "AAudioStream_getInputPreset"); + if (s_getInputPreset == nullptr) goto error; + } + return; + +error: + // prevent any calls to these functions + s_setUsage = nullptr; + s_setContentType = nullptr; + s_setInputPreset = nullptr; + s_getUsage = nullptr; + s_getContentType = nullptr; + s_getInputPreset = nullptr; + dlclose(handle); + return; +} + class AAudioParameters { public: @@ -39,6 +98,10 @@ } void setChannelCount(int32_t channelCount) { + if (channelCount > MAX_CHANNELS) { + printf("Sorry, MAX of %d channels!\n", MAX_CHANNELS); + channelCount = MAX_CHANNELS; + } mChannelCount = channelCount; } @@ -82,6 +145,30 @@ mPerformanceMode = performanceMode; } + aaudio_usage_t getUsage() const { + return mUsage; + } + + void setUsage(aaudio_usage_t usage) { + mUsage = usage; + } + + aaudio_content_type_t getContentType() const { + return mContentType; + } + + void setContentType(aaudio_content_type_t contentType) { + mContentType = contentType; + } + + aaudio_input_preset_t getInputPreset() const { + return mInputPreset; + } + + void setInputPreset(aaudio_input_preset_t inputPreset) { + mInputPreset = inputPreset; + } + int32_t getDeviceId() const { return mDeviceId; } @@ -110,6 +197,24 @@ AAudioStreamBuilder_setDeviceId(builder, mDeviceId); AAudioStreamBuilder_setSharingMode(builder, mSharingMode); AAudioStreamBuilder_setPerformanceMode(builder, mPerformanceMode); + + // Call P functions if supported. + loadFutureFunctions(); + if (s_setUsage != nullptr) { + s_setUsage(builder, mUsage); + } else if (mUsage != AAUDIO_UNSPECIFIED){ + printf("WARNING: setUsage not supported"); + } + if (s_setContentType != nullptr) { + s_setContentType(builder, mContentType); + } else if (mUsage != AAUDIO_UNSPECIFIED){ + printf("WARNING: setContentType not supported"); + } + if (s_setInputPreset != nullptr) { + s_setInputPreset(builder, mInputPreset); + } else if (mUsage != AAUDIO_UNSPECIFIED){ + printf("WARNING: setInputPreset not supported"); + } } private: @@ -122,6 +227,10 @@ aaudio_sharing_mode_t mSharingMode = AAUDIO_SHARING_MODE_SHARED; aaudio_performance_mode_t mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE; + aaudio_usage_t mUsage = AAUDIO_UNSPECIFIED; + aaudio_content_type_t mContentType = AAUDIO_UNSPECIFIED; + aaudio_input_preset_t mInputPreset = AAUDIO_UNSPECIFIED; + int32_t mNumberOfBursts = AAUDIO_UNSPECIFIED; }; @@ -152,8 +261,11 @@ case 'd': setDeviceId(atoi(&arg[2])); break; - case 's': - mDurationSeconds = atoi(&arg[2]); + case 'f': + setFormat(atoi(&arg[2])); + break; + case 'i': + setInputPreset(atoi(&arg[2])); break; case 'm': { aaudio_policy_t policy = AAUDIO_POLICY_AUTO; @@ -171,9 +283,18 @@ case 'r': setSampleRate(atoi(&arg[2])); break; + case 's': + mDurationSeconds = atoi(&arg[2]); + break; + case 'u': + setUsage(atoi(&arg[2])); + break; case 'x': setSharingMode(AAUDIO_SHARING_MODE_EXCLUSIVE); break; + case 'y': + setContentType(atoi(&arg[2])); + break; default: unrecognized = true; break; @@ -201,24 +322,32 @@ } static void usage() { - printf("-c{channels} -d{duration} -m -n{burstsPerBuffer} -p{perfMode} -r{rate} -x\n"); + printf("-c{channels} -d{deviceId} -m{mmapPolicy} -n{burstsPerBuffer} -p{perfMode}"); + printf(" -r{rate} -s{seconds} -x\n"); printf(" Default values are UNSPECIFIED unless otherwise stated.\n"); printf(" -b{bufferCapacity} frames\n"); printf(" -c{channels} for example 2 for stereo\n"); printf(" -d{deviceId} default is %d\n", AAUDIO_UNSPECIFIED); - printf(" -s{duration} in seconds, default is %d\n", DEFAULT_DURATION_SECONDS); + printf(" -f{0|1|2} set format\n"); + printf(" 0 = UNSPECIFIED\n"); + printf(" 1 = PCM_I16\n"); + printf(" 2 = FLOAT\n"); + printf(" -i{inputPreset} eg. 5 for AAUDIO_INPUT_PRESET_CAMCORDER\n"); printf(" -m{0|1|2|3} set MMAP policy\n"); - printf(" 0 = _UNSPECIFIED, default\n"); - printf(" 1 = _NEVER\n"); - printf(" 2 = _AUTO, also if -m is used with no number\n"); - printf(" 3 = _ALWAYS\n"); + printf(" 0 = _UNSPECIFIED, use aaudio.mmap_policy system property, default\n"); + printf(" 1 = _NEVER, never use MMAP\n"); + printf(" 2 = _AUTO, use MMAP if available, default for -m with no number\n"); + printf(" 3 = _ALWAYS, use MMAP or fail\n"); printf(" -n{numberOfBursts} for setBufferSize\n"); printf(" -p{performanceMode} set output AAUDIO_PERFORMANCE_MODE*, default NONE\n"); printf(" n for _NONE\n"); printf(" l for _LATENCY\n"); printf(" p for _POWER_SAVING;\n"); printf(" -r{sampleRate} for example 44100\n"); + printf(" -s{duration} in seconds, default is %d\n", DEFAULT_DURATION_SECONDS); + printf(" -u{usage} eg. 14 for AAUDIO_USAGE_GAME\n"); printf(" -x to use EXCLUSIVE mode\n"); + printf(" -y{contentType} eg. 1 for AAUDIO_CONTENT_TYPE_SPEECH\n"); } static aaudio_performance_mode_t parsePerformanceMode(char c) { @@ -281,6 +410,24 @@ printf(" PerformanceMode: requested = %d, actual = %d\n", getPerformanceMode(), AAudioStream_getPerformanceMode(stream)); + + loadFutureFunctions(); + + if (s_setUsage != nullptr) { + printf(" Usage: requested = %d, actual = %d\n", + getUsage(), s_getUsage(stream)); + } + if (s_getContentType != nullptr) { + printf(" ContentType: requested = %d, actual = %d\n", + getContentType(), s_getContentType(stream)); + } + + if (AAudioStream_getDirection(stream) == AAUDIO_DIRECTION_INPUT + && s_getInputPreset != nullptr) { + printf(" InputPreset: requested = %d, actual = %d\n", + getInputPreset(), s_getInputPreset(stream)); + } + printf(" Is MMAP used? %s\n", AAudioStream_isMMapUsed(stream) ? "yes" : "no");
diff --git a/media/libaaudio/examples/utils/AAudioExampleUtils.h b/media/libaaudio/examples/utils/AAudioExampleUtils.h index c179ce6..46b8895 100644 --- a/media/libaaudio/examples/utils/AAudioExampleUtils.h +++ b/media/libaaudio/examples/utils/AAudioExampleUtils.h
@@ -18,8 +18,10 @@ #define AAUDIO_EXAMPLE_UTILS_H #include <atomic> +#include <errno.h> #include <linux/futex.h> #include <sched.h> +#include <string.h> #include <sys/syscall.h> #include <unistd.h> @@ -30,6 +32,7 @@ #define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000) #define NANOS_PER_SECOND (NANOS_PER_MILLISECOND * 1000) +template <class T = aaudio_sharing_mode_t> const char *getSharingModeText(aaudio_sharing_mode_t mode) { const char *text = "unknown"; switch (mode) { @@ -78,13 +81,15 @@ return text; } -static void convertNanosecondsToTimespec(int64_t nanoseconds, struct timespec *time) { +template <class T = int64_t> +void convertNanosecondsToTimespec(int64_t nanoseconds, struct timespec *time) { time->tv_sec = nanoseconds / NANOS_PER_SECOND; // Calculate the fractional nanoseconds. Avoids expensive % operation. time->tv_nsec = nanoseconds - (time->tv_sec * NANOS_PER_SECOND); } -static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) { +template <class T = clockid_t> +int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) { struct timespec time; int result = clock_gettime(clockId, &time); if (result < 0) { @@ -93,7 +98,8 @@ return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec; } -static void displayPeakLevel(float peakLevel) { +template <class T = float> +void displayPeakLevel(float peakLevel) { printf("%5.3f ", peakLevel); const int maxStars = 50; // arbitrary, fits on one line int numStars = (int) (peakLevel * maxStars); @@ -111,7 +117,8 @@ * @param sampleRate * @return latency in milliseconds */ -static double calculateLatencyMillis(int64_t position1, int64_t nanoseconds1, +template <class T = int64_t> +double calculateLatencyMillis(int64_t position1, int64_t nanoseconds1, int64_t position2, int64_t nanoseconds2, int64_t sampleRate) { int64_t deltaFrames = position2 - position1; @@ -125,7 +132,8 @@ // ================================================================================ // These Futex calls are common online examples. -static android::status_t sys_futex(void *addr1, int op, int val1, +template <class T = int> +android::status_t sys_futex(void *addr1, int op, int val1, struct timespec *timeout, void *addr2, int val3) { android::status_t result = (android::status_t) syscall(SYS_futex, addr1, op, val1, timeout, @@ -133,12 +141,14 @@ return (result == 0) ? 0 : -errno; } -static android::status_t futex_wake(void *addr, int numWake) { +template <class T = int> +android::status_t futex_wake(void *addr, int numWake) { // Use _PRIVATE because we are just using the futex in one process. return sys_futex(addr, FUTEX_WAKE_PRIVATE, numWake, NULL, NULL, 0); } -static android::status_t futex_wait(void *addr, int current, struct timespec *time) { +template <class T = int> +android::status_t futex_wait(void *addr, int current, struct timespec *time) { // Use _PRIVATE because we are just using the futex in one process. return sys_futex(addr, FUTEX_WAIT_PRIVATE, current, time, NULL, 0); }
diff --git a/media/libaaudio/examples/utils/AAudioSimplePlayer.h b/media/libaaudio/examples/utils/AAudioSimplePlayer.h index 606c4ba..54b77ba 100644 --- a/media/libaaudio/examples/utils/AAudioSimplePlayer.h +++ b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
@@ -19,11 +19,10 @@ #ifndef AAUDIO_SIMPLE_PLAYER_H #define AAUDIO_SIMPLE_PLAYER_H -#include <unistd.h> #include <sched.h> +#include <unistd.h> #include <aaudio/AAudio.h> -#include <atomic> #include "AAudioArgsParser.h" #include "SineGenerator.h" @@ -31,12 +30,12 @@ #define SHARING_MODE AAUDIO_SHARING_MODE_SHARED #define PERFORMANCE_MODE AAUDIO_PERFORMANCE_MODE_NONE -// Arbitrary period for glitches, once per second at 48000 Hz. -#define FORCED_UNDERRUN_PERIOD_FRAMES 48000 +// Arbitrary period for glitches +#define FORCED_UNDERRUN_PERIOD_FRAMES (2 * 48000) // How long to sleep in a callback to cause an intentional glitch. For testing. #define FORCED_UNDERRUN_SLEEP_MICROS (10 * 1000) -#define MAX_TIMESTAMPS 16 +#define MAX_TIMESTAMPS 16 typedef struct Timestamp { int64_t position; @@ -70,13 +69,6 @@ } // TODO Extract a common base class for record and playback. - /** - * Also known as "sample rate" - * Only call this after open() has been called. - */ - int32_t getFramesPerSecond() const { - return getSampleRate(); // alias - } /** * Only call this after open() has been called. @@ -172,12 +164,12 @@ result = AAudioStreamBuilder_openStream(builder, &mStream); AAudioStreamBuilder_delete(builder); + return result; } aaudio_result_t close() { if (mStream != nullptr) { - printf("call AAudioStream_close(%p)\n", mStream); fflush(stdout); AAudioStream_close(mStream); mStream = nullptr; } @@ -212,13 +204,35 @@ aaudio_result_t result = AAudioStream_requestStop(mStream); if (result != AAUDIO_OK) { printf("ERROR - AAudioStream_requestStop() returned %d %s\n", - result, AAudio_convertResultToText(result)); + result, AAudio_convertResultToText(result)); } int32_t xRunCount = AAudioStream_getXRunCount(mStream); printf("AAudioStream_getXRunCount %d\n", xRunCount); return result; } + // Pause the stream. AAudio will stop calling your callback function. + aaudio_result_t pause() { + aaudio_result_t result = AAudioStream_requestPause(mStream); + if (result != AAUDIO_OK) { + printf("ERROR - AAudioStream_requestPause() returned %d %s\n", + result, AAudio_convertResultToText(result)); + } + int32_t xRunCount = AAudioStream_getXRunCount(mStream); + printf("AAudioStream_getXRunCount %d\n", xRunCount); + return result; + } + + // Flush the stream. AAudio will stop calling your callback function. + aaudio_result_t flush() { + aaudio_result_t result = AAudioStream_requestFlush(mStream); + if (result != AAUDIO_OK) { + printf("ERROR - AAudioStream_requestFlush() returned %d %s\n", + result, AAudio_convertResultToText(result)); + } + return result; + } + AAudioStream *getStream() const { return mStream; } @@ -232,23 +246,49 @@ typedef struct SineThreadedData_s { - SineGenerator sineOsc1; - SineGenerator sineOsc2; - Timestamp timestamps[MAX_TIMESTAMPS]; - int64_t framesTotal = 0; - int64_t nextFrameToGlitch = FORCED_UNDERRUN_PERIOD_FRAMES; - int32_t minNumFrames = INT32_MAX; - int32_t maxNumFrames = 0; - int32_t timestampCount = 0; // in timestamps + SineGenerator sineOscillators[MAX_CHANNELS]; + Timestamp timestamps[MAX_TIMESTAMPS]; + int64_t framesTotal = 0; + int64_t nextFrameToGlitch = FORCED_UNDERRUN_PERIOD_FRAMES; + int32_t minNumFrames = INT32_MAX; + int32_t maxNumFrames = 0; + int32_t timestampCount = 0; // in timestamps + int32_t sampleRate = 48000; + int32_t prefixToneFrames = 0; + bool sweepSetup = false; - int scheduler = 0; - bool schedulerChecked = false; - bool forceUnderruns = false; + int scheduler = 0; + bool schedulerChecked = false; + bool forceUnderruns = false; AAudioSimplePlayer simplePlayer; int32_t callbackCount = 0; WakeUp waker{AAUDIO_OK}; + /** + * Set sampleRate first. + */ + void setupSineBlip() { + for (int i = 0; i < MAX_CHANNELS; ++i) { + double centerFrequency = 880.0 * (i + 2); + sineOscillators[i].setup(centerFrequency, sampleRate); + sineOscillators[i].setSweep(centerFrequency, centerFrequency, 0.0); + } + } + + void setupSineSweeps() { + for (int i = 0; i < MAX_CHANNELS; ++i) { + double centerFrequency = 220.0 * (i + 2); + sineOscillators[i].setup(centerFrequency, sampleRate); + double minFrequency = centerFrequency * 2.0 / 3.0; + // Change range slightly so they will go out of phase. + double maxFrequency = centerFrequency * 3.0 / 2.0; + double sweepSeconds = 5.0 + i; + sineOscillators[i].setSweep(minFrequency, maxFrequency, sweepSeconds); + } + sweepSetup = true; + } + } SineThreadedData_t; // Callback function that fills the audio output buffer. @@ -265,9 +305,11 @@ return AAUDIO_CALLBACK_RESULT_STOP; } SineThreadedData_t *sineData = (SineThreadedData_t *) userData; - sineData->callbackCount++; - sineData->framesTotal += numFrames; + // Play an initial high tone so we can tell whether the beginning was truncated. + if (!sineData->sweepSetup && sineData->framesTotal >= sineData->prefixToneFrames) { + sineData->setupSineSweeps(); + } if (sineData->forceUnderruns) { if (sineData->framesTotal > sineData->nextFrameToGlitch) { @@ -301,33 +343,32 @@ } int32_t samplesPerFrame = AAudioStream_getChannelCount(stream); - // This code only plays on the first one or two channels. - // TODO Support arbitrary number of channels. + + + int numActiveOscilators = (samplesPerFrame > MAX_CHANNELS) ? MAX_CHANNELS : samplesPerFrame; switch (AAudioStream_getFormat(stream)) { case AAUDIO_FORMAT_PCM_I16: { int16_t *audioBuffer = (int16_t *) audioData; - // Render sine waves as shorts to first channel. - sineData->sineOsc1.render(&audioBuffer[0], samplesPerFrame, numFrames); - // Render sine waves to second channel if there is one. - if (samplesPerFrame > 1) { - sineData->sineOsc2.render(&audioBuffer[1], samplesPerFrame, numFrames); + for (int i = 0; i < numActiveOscilators; ++i) { + sineData->sineOscillators[i].render(&audioBuffer[i], samplesPerFrame, + numFrames); } } - break; + break; case AAUDIO_FORMAT_PCM_FLOAT: { float *audioBuffer = (float *) audioData; - // Render sine waves as floats to first channel. - sineData->sineOsc1.render(&audioBuffer[0], samplesPerFrame, numFrames); - // Render sine waves to second channel if there is one. - if (samplesPerFrame > 1) { - sineData->sineOsc2.render(&audioBuffer[1], samplesPerFrame, numFrames); + for (int i = 0; i < numActiveOscilators; ++i) { + sineData->sineOscillators[i].render(&audioBuffer[i], samplesPerFrame, + numFrames); } } - break; + break; default: return AAUDIO_CALLBACK_RESULT_STOP; } + sineData->callbackCount++; + sineData->framesTotal += numFrames; return AAUDIO_CALLBACK_RESULT_CONTINUE; }
diff --git a/media/libaaudio/examples/utils/AAudioSimpleRecorder.h b/media/libaaudio/examples/utils/AAudioSimpleRecorder.h index 1344273..869fad0 100644 --- a/media/libaaudio/examples/utils/AAudioSimpleRecorder.h +++ b/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
@@ -178,7 +178,6 @@ aaudio_result_t close() { if (mStream != nullptr) { - printf("call AAudioStream_close(%p)\n", mStream); fflush(stdout); AAudioStream_close(mStream); mStream = nullptr; }
diff --git a/media/libaaudio/examples/utils/SineGenerator.h b/media/libaaudio/examples/utils/SineGenerator.h index a755582..9e6d46d 100644 --- a/media/libaaudio/examples/utils/SineGenerator.h +++ b/media/libaaudio/examples/utils/SineGenerator.h
@@ -31,20 +31,20 @@ } void setSweep(double frequencyLow, double frequencyHigh, double seconds) { - mPhaseIncrementLow = frequencyLow * M_PI * 2 / mFrameRate; - mPhaseIncrementHigh = frequencyHigh * M_PI * 2 / mFrameRate; - - double numFrames = seconds * mFrameRate; - mUpScaler = pow((frequencyHigh / frequencyLow), (1.0 / numFrames)); - mDownScaler = 1.0 / mUpScaler; - mGoingUp = true; - mSweeping = true; + mSweeping = seconds > 0.0; + if (mSweeping) { + mPhaseIncrementLow = frequencyLow * M_PI * 2 / mFrameRate; + mPhaseIncrementHigh = frequencyHigh * M_PI * 2 / mFrameRate; + double numFrames = seconds * mFrameRate; + mUpScaler = pow((frequencyHigh / frequencyLow), (1.0 / numFrames)); + mDownScaler = 1.0 / mUpScaler; + } } void render(int16_t *buffer, int32_t channelStride, int32_t numFrames) { int sampleIndex = 0; for (int i = 0; i < numFrames; i++) { - buffer[sampleIndex] = (int16_t) (32767 * sin(mPhase) * mAmplitude); + buffer[sampleIndex] = (int16_t) (INT16_MAX * sin(mPhase) * mAmplitude); sampleIndex += channelStride; advancePhase(); } @@ -61,6 +61,7 @@ void setAmplitude(double amplitude) { mAmplitude = amplitude; } + double getAmplitude() const { return mAmplitude; }
diff --git a/media/libaaudio/examples/write_sine/Android.bp b/media/libaaudio/examples/write_sine/Android.bp new file mode 100644 index 0000000..aa25e67 --- /dev/null +++ b/media/libaaudio/examples/write_sine/Android.bp
@@ -0,0 +1,15 @@ +cc_test { + name: "write_sine", + srcs: ["src/write_sine.cpp"], + cflags: ["-Wall", "-Werror"], + shared_libs: ["libaaudio"], + header_libs: ["libaaudio_example_utils"], +} + +cc_test { + name: "write_sine_callback", + srcs: ["src/write_sine_callback.cpp"], + cflags: ["-Wall", "-Werror"], + shared_libs: ["libaaudio"], + header_libs: ["libaaudio_example_utils"], +}
diff --git a/media/libaaudio/examples/write_sine/Android.mk b/media/libaaudio/examples/write_sine/Android.mk deleted file mode 100644 index 5053e7d..0000000 --- a/media/libaaudio/examples/write_sine/Android.mk +++ /dev/null
@@ -1 +0,0 @@ -include $(call all-subdir-makefiles)
diff --git a/media/libaaudio/examples/write_sine/jni/Android.mk b/media/libaaudio/examples/write_sine/jni/Android.mk index d630e76..1a1bd43 100644 --- a/media/libaaudio/examples/write_sine/jni/Android.mk +++ b/media/libaaudio/examples/write_sine/jni/Android.mk
@@ -10,6 +10,7 @@ # NDK recommends using this kind of relative path instead of an absolute path. LOCAL_SRC_FILES:= ../src/write_sine.cpp +LOCAL_CFLAGS := -Wall -Werror LOCAL_SHARED_LIBRARIES := libaaudio LOCAL_MODULE := write_sine include $(BUILD_EXECUTABLE) @@ -22,6 +23,7 @@ frameworks/av/media/libaaudio/examples/utils LOCAL_SRC_FILES:= ../src/write_sine_callback.cpp +LOCAL_CFLAGS := -Wall -Werror LOCAL_SHARED_LIBRARIES := libaaudio LOCAL_MODULE := write_sine_callback include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/examples/write_sine/src/write_sine.cpp b/media/libaaudio/examples/write_sine/src/write_sine.cpp index 677fb6c..8e33a31 100644 --- a/media/libaaudio/examples/write_sine/src/write_sine.cpp +++ b/media/libaaudio/examples/write_sine/src/write_sine.cpp
@@ -44,10 +44,10 @@ AAudioStream *aaudioStream = nullptr; int32_t framesPerBurst = 0; int32_t framesPerWrite = 0; - int32_t bufferCapacity = 0; int32_t framesToPlay = 0; int32_t framesLeft = 0; int32_t xRunCount = 0; + int numActiveOscilators = 0; float *floatData = nullptr; int16_t *shortData = nullptr; @@ -57,7 +57,7 @@ // in a buffer if we hang or crash. setvbuf(stdout, nullptr, _IONBF, (size_t) 0); - printf("%s - Play a sine wave using AAudio V0.1.2\n", argv[0]); + printf("%s - Play a sine wave using AAudio V0.1.3\n", argv[0]); if (argParser.parseArgs(argc, argv)) { return EXIT_FAILURE; @@ -77,8 +77,8 @@ actualSampleRate = AAudioStream_getSampleRate(aaudioStream); actualDataFormat = AAudioStream_getFormat(aaudioStream); - myData.sineOsc1.setup(440.0, actualSampleRate); - myData.sineOsc2.setup(660.0, actualSampleRate); + myData.sampleRate = actualSampleRate; + myData.setupSineSweeps(); // Some DMA might use very short bursts of 16 frames. We don't need to write such small // buffers. But it helps to use a multiple of the burst size for predictable scheduling. @@ -117,19 +117,18 @@ // Play for a while. framesToPlay = actualSampleRate * argParser.getDurationSeconds(); framesLeft = framesToPlay; + numActiveOscilators = (actualChannelCount > MAX_CHANNELS) ? MAX_CHANNELS : actualChannelCount; while (framesLeft > 0) { - + // Render as FLOAT or PCM if (actualDataFormat == AAUDIO_FORMAT_PCM_FLOAT) { - // Render sine waves to left and right channels. - myData.sineOsc1.render(&floatData[0], actualChannelCount, framesPerWrite); - if (actualChannelCount > 1) { - myData.sineOsc2.render(&floatData[1], actualChannelCount, framesPerWrite); + for (int i = 0; i < numActiveOscilators; ++i) { + myData.sineOscillators[i].render(&floatData[i], actualChannelCount, + framesPerWrite); } } else if (actualDataFormat == AAUDIO_FORMAT_PCM_I16) { - // Render sine waves to left and right channels. - myData.sineOsc1.render(&shortData[0], actualChannelCount, framesPerWrite); - if (actualChannelCount > 1) { - myData.sineOsc2.render(&shortData[1], actualChannelCount, framesPerWrite); + for (int i = 0; i < numActiveOscilators; ++i) { + myData.sineOscillators[i].render(&shortData[i], actualChannelCount, + framesPerWrite); } }
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp index 4f9cde6..e33e9f8 100644 --- a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp +++ b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
@@ -28,7 +28,7 @@ #include <aaudio/AAudio.h> #include "AAudioExampleUtils.h" #include "AAudioSimplePlayer.h" -#include "../../utils/AAudioSimplePlayer.h" +#include "AAudioArgsParser.h" /** * Open stream, play some sine waves, then close the stream. @@ -36,37 +36,39 @@ * @param argParser * @return AAUDIO_OK or negative error code */ -static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser) +static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser, + int32_t loopCount, + int32_t prefixToneMsec, + bool forceUnderruns) { SineThreadedData_t myData; AAudioSimplePlayer &player = myData.simplePlayer; aaudio_result_t result = AAUDIO_OK; bool disconnected = false; + bool bailOut = false; int64_t startedAtNanos; printf("----------------------- run complete test --------------------------\n"); myData.schedulerChecked = false; myData.callbackCount = 0; - myData.forceUnderruns = false; // set true to test AAudioStream_getXRunCount() + myData.forceUnderruns = forceUnderruns; // test AAudioStream_getXRunCount() result = player.open(argParser, SimplePlayerDataCallbackProc, SimplePlayerErrorCallbackProc, &myData); if (result != AAUDIO_OK) { - fprintf(stderr, "ERROR - player.open() returned %d\n", result); + fprintf(stderr, "ERROR - player.open() returned %s\n", + AAudio_convertResultToText(result)); goto error; } argParser.compareWithStream(player.getStream()); - // Setup sine wave generators. - { - int32_t actualSampleRate = player.getSampleRate(); - myData.sineOsc1.setup(440.0, actualSampleRate); - myData.sineOsc1.setSweep(300.0, 600.0, 5.0); - myData.sineOsc1.setAmplitude(0.2); - myData.sineOsc2.setup(660.0, actualSampleRate); - myData.sineOsc2.setSweep(350.0, 900.0, 7.0); - myData.sineOsc2.setAmplitude(0.2); + myData.sampleRate = player.getSampleRate(); + myData.prefixToneFrames = prefixToneMsec * myData.sampleRate / 1000; + if (myData.prefixToneFrames > 0) { + myData.setupSineBlip(); + } else { + myData.setupSineSweeps(); } #if 0 @@ -78,42 +80,93 @@ } #endif - result = player.start(); - if (result != AAUDIO_OK) { - fprintf(stderr, "ERROR - player.start() returned %d\n", result); - goto error; - } + for (int loopIndex = 0; loopIndex < loopCount; loopIndex++) { + // Only play data on every other loop so we can hear if there is stale data. + double amplitude; + int32_t durationSeconds; + if ((loopIndex & 1) == 0) { + printf("--------------- SINE ------\n"); + amplitude = 0.2; + durationSeconds = argParser.getDurationSeconds(); + } else { + printf("--------------- QUIET -----\n"); + amplitude = 0.0; + durationSeconds = 2; // just wait briefly when quiet + } + for (int i = 0; i < MAX_CHANNELS; ++i) { + myData.sineOscillators[i].setAmplitude(amplitude); + } - // Play a sine wave in the background. - printf("Sleep for %d seconds while audio plays in a callback thread.\n", - argParser.getDurationSeconds()); - startedAtNanos = getNanoseconds(CLOCK_MONOTONIC); - for (int second = 0; second < argParser.getDurationSeconds(); second++) - { - // Sleep a while. Wake up early if there is an error, for example a DISCONNECT. - long ret = myData.waker.wait(AAUDIO_OK, NANOS_PER_SECOND); - int64_t millis = (getNanoseconds(CLOCK_MONOTONIC) - startedAtNanos) / NANOS_PER_MILLISECOND; - result = myData.waker.get(); - printf("wait() returns %ld, aaudio_result = %d, at %6d millis" - ", second = %d, framesWritten = %8d, underruns = %d\n", - ret, result, (int) millis, - second, - (int) AAudioStream_getFramesWritten(player.getStream()), - (int) AAudioStream_getXRunCount(player.getStream())); + result = player.start(); if (result != AAUDIO_OK) { - if (result == AAUDIO_ERROR_DISCONNECTED) { - disconnected = true; + fprintf(stderr, "ERROR - player.start() returned %d\n", result); + goto error; + } + + // Play a sine wave in the background. + printf("Sleep for %d seconds while audio plays in a callback thread. %d of %d\n", + argParser.getDurationSeconds(), (loopIndex + 1), loopCount); + startedAtNanos = getNanoseconds(CLOCK_MONOTONIC); + for (int second = 0; second < durationSeconds; second++) { + // Sleep a while. Wake up early if there is an error, for example a DISCONNECT. + long ret = myData.waker.wait(AAUDIO_OK, NANOS_PER_SECOND); + int64_t millis = + (getNanoseconds(CLOCK_MONOTONIC) - startedAtNanos) / NANOS_PER_MILLISECOND; + result = myData.waker.get(); + printf("wait() returns %ld, aaudio_result = %d, at %6d millis" + ", second = %3d, framesWritten = %8d, underruns = %d\n", + ret, result, (int) millis, + second, + (int) AAudioStream_getFramesWritten(player.getStream()), + (int) AAudioStream_getXRunCount(player.getStream())); + if (result != AAUDIO_OK) { + disconnected = (result == AAUDIO_ERROR_DISCONNECTED); + bailOut = true; + break; } + } + printf("AAudio result = %d = %s\n", result, AAudio_convertResultToText(result)); + + // Alternate between using stop or pause for each sine/quiet pair. + // Repeat this pattern: {sine-stop-quiet-stop-sine-pause-quiet-pause} + if ((loopIndex & 2) == 0) { + printf("STOP, callback # = %d\n", myData.callbackCount); + result = player.stop(); + } else { + printf("PAUSE/FLUSH, callback # = %d\n", myData.callbackCount); + result = player.pause(); + if (result != AAUDIO_OK) { + goto error; + } + result = player.flush(); + } + if (result != AAUDIO_OK) { + goto error; + } + + if (bailOut) { break; } - } - printf("AAudio result = %d = %s\n", result, AAudio_convertResultToText(result)); - printf("call stop() callback # = %d\n", myData.callbackCount); - result = player.stop(); - if (result != AAUDIO_OK) { - goto error; + { + aaudio_stream_state_t state = AAudioStream_getState(player.getStream()); + aaudio_stream_state_t finalState = AAUDIO_STREAM_STATE_UNINITIALIZED; + int64_t timeoutNanos = 2000 * NANOS_PER_MILLISECOND; + result = AAudioStream_waitForStateChange(player.getStream(), state, + &finalState, timeoutNanos); + printf("waitForStateChange returns %s, state = %s\n", + AAudio_convertResultToText(result), + AAudio_convertStreamStateToText(finalState)); + int64_t written = AAudioStream_getFramesWritten(player.getStream()); + int64_t read = AAudioStream_getFramesRead(player.getStream()); + printf(" framesWritten = %lld, framesRead = %lld, diff = %d\n", + (long long) written, + (long long) read, + (int) (written - read)); + } + } + printf("call close()\n"); result = player.close(); if (result != AAUDIO_OK) { @@ -147,23 +200,59 @@ return disconnected ? AAUDIO_ERROR_DISCONNECTED : result; } +static void usage() { + AAudioArgsParser::usage(); + printf(" -l{count} loopCount start/stop, every other one is silent\n"); + printf(" -t{msec} play a high pitched tone at the beginning\n"); + printf(" -z force periodic underruns by sleeping in callback\n"); +} + int main(int argc, const char **argv) { AAudioArgsParser argParser; aaudio_result_t result; + int32_t loopCount = 1; + int32_t prefixToneMsec = 0; + bool forceUnderruns = false; // Make printf print immediately so that debug info is not stuck // in a buffer if we hang or crash. setvbuf(stdout, nullptr, _IONBF, (size_t) 0); - printf("%s - Play a sine sweep using an AAudio callback V0.1.2\n", argv[0]); + printf("%s - Play a sine sweep using an AAudio callback V0.1.4\n", argv[0]); - if (argParser.parseArgs(argc, argv)) { - return EXIT_FAILURE; + for (int i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (argParser.parseArg(arg)) { + // Handle options that are not handled by the ArgParser + if (arg[0] == '-') { + char option = arg[1]; + switch (option) { + case 'l': + loopCount = atoi(&arg[2]); + break; + case 't': + prefixToneMsec = atoi(&arg[2]); + break; + case 'z': + forceUnderruns = true; // Zzzzzzz + break; + default: + usage(); + exit(EXIT_FAILURE); + break; + } + } else { + usage(); + exit(EXIT_FAILURE); + break; + } + } } // Keep looping until we can complete the test without disconnecting. - while((result = testOpenPlayClose(argParser)) == AAUDIO_ERROR_DISCONNECTED); + while((result = testOpenPlayClose(argParser, loopCount, prefixToneMsec, forceUnderruns)) + == AAUDIO_ERROR_DISCONNECTED); return (result) ? EXIT_FAILURE : EXIT_SUCCESS; }
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h index 3c23736..5b29419 100644 --- a/media/libaaudio/include/aaudio/AAudio.h +++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -44,7 +44,15 @@ #define AAUDIO_UNSPECIFIED 0 enum { + /** + * Audio data will travel out of the device, for example through a speaker. + */ AAUDIO_DIRECTION_OUTPUT, + + + /** + * Audio data will travel into the device, for example from a microphone. + */ AAUDIO_DIRECTION_INPUT }; typedef int32_t aaudio_direction_t; @@ -52,33 +60,112 @@ enum { AAUDIO_FORMAT_INVALID = -1, AAUDIO_FORMAT_UNSPECIFIED = 0, + + /** + * This format uses the int16_t data type. + * The maximum range of the data is -32768 to 32767. + */ AAUDIO_FORMAT_PCM_I16, + + /** + * This format uses the float data type. + * The nominal range of the data is [-1.0f, 1.0f). + * Values outside that range may be clipped. + * + * See also 'floatData' at + * https://developer.android.com/reference/android/media/AudioTrack#write(float[],%20int,%20int,%20int) + */ AAUDIO_FORMAT_PCM_FLOAT }; typedef int32_t aaudio_format_t; +/** + * These result codes are returned from AAudio functions to indicate success or failure. + * Note that error return codes may change in the future so applications should generally + * not rely on specific return codes. + */ enum { + /** + * The call was successful. + */ AAUDIO_OK, AAUDIO_ERROR_BASE = -900, // TODO review + + /** + * The audio device was disconnected. This could occur, for example, when headphones + * are plugged in or unplugged. The stream cannot be used after the device is disconnected. + * Applications should stop and close the stream. + * If this error is received in an error callback then another thread should be + * used to stop and close the stream. + */ AAUDIO_ERROR_DISCONNECTED, + + /** + * An invalid parameter was passed to AAudio. + */ AAUDIO_ERROR_ILLEGAL_ARGUMENT, // reserved AAUDIO_ERROR_INTERNAL = AAUDIO_ERROR_ILLEGAL_ARGUMENT + 2, + + /** + * The requested operation is not appropriate for the current state of AAudio. + */ AAUDIO_ERROR_INVALID_STATE, // reserved // reserved + /* The server rejected the handle used to identify the stream. + */ AAUDIO_ERROR_INVALID_HANDLE = AAUDIO_ERROR_INVALID_STATE + 3, // reserved + + /** + * The function is not implemented for this stream. + */ AAUDIO_ERROR_UNIMPLEMENTED = AAUDIO_ERROR_INVALID_HANDLE + 2, + + /** + * A resource or information is unavailable. + * This could occur when an application tries to open too many streams, + * or a timestamp is not available. + */ AAUDIO_ERROR_UNAVAILABLE, AAUDIO_ERROR_NO_FREE_HANDLES, + + /** + * Memory could not be allocated. + */ AAUDIO_ERROR_NO_MEMORY, + + /** + * A NULL pointer was passed to AAudio. + * Or a NULL pointer was detected internally. + */ AAUDIO_ERROR_NULL, + + /** + * An operation took longer than expected. + */ AAUDIO_ERROR_TIMEOUT, AAUDIO_ERROR_WOULD_BLOCK, + + /** + * The requested data format is not supported. + */ AAUDIO_ERROR_INVALID_FORMAT, + + /** + * A requested was out of range. + */ AAUDIO_ERROR_OUT_OF_RANGE, + + /** + * The audio service was not available. + */ AAUDIO_ERROR_NO_SERVICE, + + /** + * The requested sample rate was not supported. + */ AAUDIO_ERROR_INVALID_RATE }; typedef int32_t aaudio_result_t; @@ -123,20 +210,200 @@ /** * No particular performance needs. Default. */ - AAUDIO_PERFORMANCE_MODE_NONE = 10, + AAUDIO_PERFORMANCE_MODE_NONE = 10, /** - * Extending battery life is most important. + * Extending battery life is more important than low latency. + * + * This mode is not supported in input streams. + * For input, mode NONE will be used if this is requested. */ - AAUDIO_PERFORMANCE_MODE_POWER_SAVING, + AAUDIO_PERFORMANCE_MODE_POWER_SAVING, /** - * Reducing latency is most important. + * Reducing latency is more important than battery life. */ - AAUDIO_PERFORMANCE_MODE_LOW_LATENCY + AAUDIO_PERFORMANCE_MODE_LOW_LATENCY }; typedef int32_t aaudio_performance_mode_t; +/** + * The USAGE attribute expresses "why" you are playing a sound, what is this sound used for. + * This information is used by certain platforms or routing policies + * to make more refined volume or routing decisions. + * + * Note that these match the equivalent values in AudioAttributes in the Android Java API. + * + * Added in API level 28. + */ +enum { + /** + * Use this for streaming media, music performance, video, podcasts, etcetera. + */ + AAUDIO_USAGE_MEDIA = 1, + + /** + * Use this for voice over IP, telephony, etcetera. + */ + AAUDIO_USAGE_VOICE_COMMUNICATION = 2, + + /** + * Use this for sounds associated with telephony such as busy tones, DTMF, etcetera. + */ + AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING = 3, + + /** + * Use this to demand the users attention. + */ + AAUDIO_USAGE_ALARM = 4, + + /** + * Use this for notifying the user when a message has arrived or some + * other background event has occured. + */ + AAUDIO_USAGE_NOTIFICATION = 5, + + /** + * Use this when the phone rings. + */ + AAUDIO_USAGE_NOTIFICATION_RINGTONE = 6, + + /** + * Use this to attract the users attention when, for example, the battery is low. + */ + AAUDIO_USAGE_NOTIFICATION_EVENT = 10, + + /** + * Use this for screen readers, etcetera. + */ + AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY = 11, + + /** + * Use this for driving or navigation directions. + */ + AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12, + + /** + * Use this for user interface sounds, beeps, etcetera. + */ + AAUDIO_USAGE_ASSISTANCE_SONIFICATION = 13, + + /** + * Use this for game audio and sound effects. + */ + AAUDIO_USAGE_GAME = 14, + + /** + * Use this for audio responses to user queries, audio instructions or help utterances. + */ + AAUDIO_USAGE_ASSISTANT = 16 +}; +typedef int32_t aaudio_usage_t; + +/** + * The CONTENT_TYPE attribute describes "what" you are playing. + * It expresses the general category of the content. This information is optional. + * But in case it is known (for instance {@link #AAUDIO_CONTENT_TYPE_MOVIE} for a + * movie streaming service or {@link #AAUDIO_CONTENT_TYPE_SPEECH} for + * an audio book application) this information might be used by the audio framework to + * enforce audio focus. + * + * Note that these match the equivalent values in AudioAttributes in the Android Java API. + * + * Added in API level 28. + */ +enum { + + /** + * Use this for spoken voice, audio books, etcetera. + */ + AAUDIO_CONTENT_TYPE_SPEECH = 1, + + /** + * Use this for pre-recorded or live music. + */ + AAUDIO_CONTENT_TYPE_MUSIC = 2, + + /** + * Use this for a movie or video soundtrack. + */ + AAUDIO_CONTENT_TYPE_MOVIE = 3, + + /** + * Use this for sound is designed to accompany a user action, + * such as a click or beep sound made when the user presses a button. + */ + AAUDIO_CONTENT_TYPE_SONIFICATION = 4 +}; +typedef int32_t aaudio_content_type_t; + +/** + * Defines the audio source. + * An audio source defines both a default physical source of audio signal, and a recording + * configuration. + * + * Note that these match the equivalent values in MediaRecorder.AudioSource in the Android Java API. + * + * Added in API level 28. + */ +enum { + /** + * Use this preset when other presets do not apply. + */ + AAUDIO_INPUT_PRESET_GENERIC = 1, + + /** + * Use this preset when recording video. + */ + AAUDIO_INPUT_PRESET_CAMCORDER = 5, + + /** + * Use this preset when doing speech recognition. + */ + AAUDIO_INPUT_PRESET_VOICE_RECOGNITION = 6, + + /** + * Use this preset when doing telephony or voice messaging. + */ + AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION = 7, + + /** + * Use this preset to obtain an input with no effects. + * Note that this input will not have automatic gain control + * so the recorded volume may be very low. + */ + AAUDIO_INPUT_PRESET_UNPROCESSED = 9, +}; +typedef int32_t aaudio_input_preset_t; + +/** + * These may be used with AAudioStreamBuilder_setSessionId(). + * + * Added in API level 28. + */ +enum { + /** + * Do not allocate a session ID. + * Effects cannot be used with this stream. + * Default. + * + * Added in API level 28. + */ + AAUDIO_SESSION_ID_NONE = -1, + + /** + * Allocate a session ID that can be used to attach and control + * effects using the Java AudioEffects API. + * Note that using this may result in higher latency. + * + * Note that this matches the value of AudioManager.AUDIO_SESSION_ID_GENERATE. + * + * Added in API level 28. + */ + AAUDIO_SESSION_ID_ALLOCATE = 0, +}; +typedef int32_t aaudio_session_id_t; + typedef struct AAudioStreamStruct AAudioStream; typedef struct AAudioStreamBuilderStruct AAudioStreamBuilder; @@ -299,8 +566,14 @@ /** * Set the requested performance mode. * + * Supported modes are AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_PERFORMANCE_MODE_POWER_SAVING + * and AAUDIO_PERFORMANCE_MODE_LOW_LATENCY. + * * The default, if you do not call this function, is AAUDIO_PERFORMANCE_MODE_NONE. * + * You may not get the mode you requested. + * You can call AAudioStream_getPerformanceMode() to find out the final mode for the stream. + * * @param builder reference provided by AAudio_createStreamBuilder() * @param mode the desired performance mode, eg. AAUDIO_PERFORMANCE_MODE_LOW_LATENCY */ @@ -308,6 +581,90 @@ aaudio_performance_mode_t mode); /** + * Set the intended use case for the stream. + * + * The AAudio system will use this information to optimize the + * behavior of the stream. + * This could, for example, affect how volume and focus is handled for the stream. + * + * The default, if you do not call this function, is AAUDIO_USAGE_MEDIA. + * + * Added in API level 28. + * + * @param builder reference provided by AAudio_createStreamBuilder() + * @param usage the desired usage, eg. AAUDIO_USAGE_GAME + */ +AAUDIO_API void AAudioStreamBuilder_setUsage(AAudioStreamBuilder* builder, + aaudio_usage_t usage); + +/** + * Set the type of audio data that the stream will carry. + * + * The AAudio system will use this information to optimize the + * behavior of the stream. + * This could, for example, affect whether a stream is paused when a notification occurs. + * + * The default, if you do not call this function, is AAUDIO_CONTENT_TYPE_MUSIC. + * + * Added in API level 28. + * + * @param builder reference provided by AAudio_createStreamBuilder() + * @param contentType the type of audio data, eg. AAUDIO_CONTENT_TYPE_SPEECH + */ +AAUDIO_API void AAudioStreamBuilder_setContentType(AAudioStreamBuilder* builder, + aaudio_content_type_t contentType); + +/** + * Set the input (capture) preset for the stream. + * + * The AAudio system will use this information to optimize the + * behavior of the stream. + * This could, for example, affect which microphones are used and how the + * recorded data is processed. + * + * The default, if you do not call this function, is AAUDIO_INPUT_PRESET_VOICE_RECOGNITION. + * That is because VOICE_RECOGNITION is the preset with the lowest latency + * on many platforms. + * + * Added in API level 28. + * + * @param builder reference provided by AAudio_createStreamBuilder() + * @param inputPreset the desired configuration for recording + */ +AAUDIO_API void AAudioStreamBuilder_setInputPreset(AAudioStreamBuilder* builder, + aaudio_input_preset_t inputPreset); + +/** Set the requested session ID. + * + * The session ID can be used to associate a stream with effects processors. + * The effects are controlled using the Android AudioEffect Java API. + * + * The default, if you do not call this function, is AAUDIO_SESSION_ID_NONE. + * + * If set to AAUDIO_SESSION_ID_ALLOCATE then a session ID will be allocated + * when the stream is opened. + * + * The allocated session ID can be obtained by calling AAudioStream_getSessionId() + * and then used with this function when opening another stream. + * This allows effects to be shared between streams. + * + * Session IDs from AAudio can be used with the Android Java APIs and vice versa. + * So a session ID from an AAudio stream can be passed to Java + * and effects applied using the Java AudioEffect API. + * + * Note that allocating or setting a session ID may result in a stream with higher latency. + * + * Allocated session IDs will always be positive and nonzero. + * + * Added in API level 28. + * + * @param builder reference provided by AAudio_createStreamBuilder() + * @param sessionId an allocated sessionID or AAUDIO_SESSION_ID_ALLOCATE + */ +AAUDIO_API void AAudioStreamBuilder_setSessionId(AAudioStreamBuilder* builder, + aaudio_session_id_t sessionId); + +/** * Return one of these values from the data callback function. */ enum { @@ -337,7 +694,13 @@ * For an input stream, this function should read and process numFrames of data * from the audioData buffer. * - * Note that this callback function should be considered a "real-time" function. + * The audio data is passed through the buffer. So do NOT call AAudioStream_read() or + * AAudioStream_write() on the stream that is making the callback. + * + * Note that numFrames can vary unless AAudioStreamBuilder_setFramesPerDataCallback() + * is called. + * + * Also note that this callback function should be considered a "real-time" function. * It must not do anything that could cause an unbounded delay because that can cause the * audio to glitch or pop. * @@ -348,6 +711,15 @@ * <li>any network operations such as streaming</li> * <li>use any mutexes or other synchronization primitives</li> * <li>sleep</li> + * <li>stop or close the stream</li> + * <li>AAudioStream_read()</li> + * <li>AAudioStream_write()</li> + * </ul> + * + * The following are OK to call from the data callback: + * <ul> + * <li>AAudioStream_get*()</li> + * <li>AAudio_convertResultToText()</li> * </ul> * * If you need to move data, eg. MIDI commands, in or out of the callback function then @@ -356,7 +728,7 @@ * @param stream reference provided by AAudioStreamBuilder_openStream() * @param userData the same address that was passed to AAudioStreamBuilder_setCallback() * @param audioData a pointer to the audio data - * @param numFrames the number of frames to be processed + * @param numFrames the number of frames to be processed, which can vary * @return AAUDIO_CALLBACK_RESULT_* */ typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)( @@ -421,6 +793,22 @@ * Prototype for the callback function that is passed to * AAudioStreamBuilder_setErrorCallback(). * + * The following may NOT be called from the error callback: + * <ul> + * <li>AAudioStream_requestStop()</li> + * <li>AAudioStream_requestPause()</li> + * <li>AAudioStream_close()</li> + * <li>AAudioStream_waitForStateChange()</li> + * <li>AAudioStream_read()</li> + * <li>AAudioStream_write()</li> + * </ul> + * + * The following are OK to call from the error callback: + * <ul> + * <li>AAudioStream_get*()</li> + * <li>AAudio_convertResultToText()</li> + * </ul> + * * @param stream reference provided by AAudioStreamBuilder_openStream() * @param userData the same address that was passed to AAudioStreamBuilder_setErrorCallback() * @param error an AAUDIO_ERROR_* value. @@ -431,18 +819,19 @@ aaudio_result_t error); /** - * Request that AAudio call this functions if any error occurs on a callback thread. + * Request that AAudio call this function if any error occurs or the stream is disconnected. * * It will be called, for example, if a headset or a USB device is unplugged causing the stream's - * device to be unavailable. - * In response, this function could signal or launch another thread to reopen a - * stream on another device. Do not reopen the stream in this callback. - * - * This will not be called because of actions by the application, such as stopping - * or closing a stream. - * + * device to be unavailable or "disconnected". * Another possible cause of error would be a timeout or an unanticipated internal error. * + * In response, this function should signal or create another thread to stop + * and close this stream. The other thread could then reopen a stream on another device. + * Do not stop or close the stream, or reopen the new stream, directly from this callback. + * + * This callback will not be called because of actions by the application, such as stopping + * or closing a stream. + * * Note that the AAudio callbacks will never be called simultaneously from multiple threads. * * @param builder reference provided by AAudio_createStreamBuilder() @@ -554,11 +943,13 @@ * This will update the current client state. * * <pre><code> - * aaudio_stream_state_t currentState; - * aaudio_result_t result = AAudioStream_getState(stream, ¤tState); - * while (result == AAUDIO_OK && currentState != AAUDIO_STREAM_STATE_PAUSING) { + * aaudio_result_t result = AAUDIO_OK; + * aaudio_stream_state_t currentState = AAudioStream_getState(stream); + * aaudio_stream_state_t inputState = currentState; + * while (result == AAUDIO_OK && currentState != AAUDIO_STREAM_STATE_PAUSED) { * result = AAudioStream_waitForStateChange( - * stream, currentState, ¤tState, MY_TIMEOUT_NANOS); + * stream, inputState, ¤tState, MY_TIMEOUT_NANOS); + * inputState = currentState; * } * </code></pre> * @@ -589,6 +980,8 @@ * * This call is "strong non-blocking" unless it has to wait for data. * + * If the call times out then zero or a partial frame count will be returned. + * * @param stream A stream created using AAudioStreamBuilder_openStream(). * @param buffer The address of the first sample. * @param numFrames Number of frames to read. Only complete frames will be written. @@ -612,6 +1005,8 @@ * * This call is "strong non-blocking" unless it has to wait for room in the buffer. * + * If the call times out then zero or a partial frame count will be returned. + * * @param stream A stream created using AAudioStreamBuilder_openStream(). * @param buffer The address of the first sample. * @param numFrames Number of frames to write. Only complete frames will be written. @@ -636,7 +1031,8 @@ * This cannot be set higher than AAudioStream_getBufferCapacityInFrames(). * * Note that you will probably not get the exact size you request. - * Call AAudioStream_getBufferSizeInFrames() to see what the actual final size is. + * You can check the return value or call AAudioStream_getBufferSizeInFrames() + * to see what the actual final size is. * * @param stream reference provided by AAudioStreamBuilder_openStream() * @param numFrames requested number of frames that can be filled without blocking @@ -683,10 +1079,10 @@ * This call can be used if the application needs to know the value of numFrames before * the stream is started. This is not normally necessary. * - * If a specific size was requested by calling AAudioStreamBuilder_setCallbackSizeInFrames() + * If a specific size was requested by calling AAudioStreamBuilder_setFramesPerDataCallback() * then this will be the same size. * - * If AAudioStreamBuilder_setCallbackSizeInFrames() was not called then this will + * If AAudioStreamBuilder_setFramesPerDataCallback() was not called then this will * return the size chosen by AAudio, or AAUDIO_UNSPECIFIED. * * AAUDIO_UNSPECIFIED indicates that the callback buffer size for this stream @@ -771,7 +1167,8 @@ /** * Passes back the number of frames that have been written since the stream was created. - * For an output stream, this will be advanced by the application calling write(). + * For an output stream, this will be advanced by the application calling write() + * or by a data callback. * For an input stream, this will be advanced by the endpoint. * * The frame position is monotonically increasing. @@ -784,7 +1181,8 @@ /** * Passes back the number of frames that have been read since the stream was created. * For an output stream, this will be advanced by the endpoint. - * For an input stream, this will be advanced by the application calling read(). + * For an input stream, this will be advanced by the application calling read() + * or by a data callback. * * The frame position is monotonically increasing. * @@ -794,6 +1192,30 @@ AAUDIO_API int64_t AAudioStream_getFramesRead(AAudioStream* stream); /** + * Passes back the session ID associated with this stream. + * + * The session ID can be used to associate a stream with effects processors. + * The effects are controlled using the Android AudioEffect Java API. + * + * If AAudioStreamBuilder_setSessionId() was called with AAUDIO_SESSION_ID_ALLOCATE + * then a new session ID should be allocated once when the stream is opened. + * + * If AAudioStreamBuilder_setSessionId() was called with a previously allocated + * session ID then that value should be returned. + * + * If AAudioStreamBuilder_setSessionId() was not called then this function should + * return AAUDIO_SESSION_ID_NONE. + * + * The sessionID for a stream should not change once the stream has been opened. + * + * Added in API level 28. + * + * @param stream reference provided by AAudioStreamBuilder_openStream() + * @return session ID or AAUDIO_SESSION_ID_NONE + */ +AAUDIO_API aaudio_session_id_t AAudioStream_getSessionId(AAudioStream* stream); + +/** * Passes back the time at which a particular frame was presented. * This can be used to synchronize audio with video or MIDI. * It can also be used to align a recorded stream with a playback stream. @@ -820,6 +1242,36 @@ int64_t *framePosition, int64_t *timeNanoseconds); +/** + * Return the use case for the stream. + * + * Added in API level 28. + * + * @param stream reference provided by AAudioStreamBuilder_openStream() + * @return frames read + */ +AAUDIO_API aaudio_usage_t AAudioStream_getUsage(AAudioStream* stream); + +/** + * Return the content type for the stream. + * + * Added in API level 28. + * + * @param stream reference provided by AAudioStreamBuilder_openStream() + * @return content type, for example AAUDIO_CONTENT_TYPE_MUSIC + */ +AAUDIO_API aaudio_content_type_t AAudioStream_getContentType(AAudioStream* stream); + +/** + * Return the input preset for the stream. + * + * Added in API level 28. + * + * @param stream reference provided by AAudioStreamBuilder_openStream() + * @return input preset, for example AAUDIO_INPUT_PRESET_CAMCORDER + */ +AAUDIO_API aaudio_input_preset_t AAudioStream_getInputPreset(AAudioStream* stream); + #ifdef __cplusplus } #endif
diff --git a/media/libaaudio/libaaudio.map.txt b/media/libaaudio/libaaudio.map.txt index 2ba5250..cbf5921 100644 --- a/media/libaaudio/libaaudio.map.txt +++ b/media/libaaudio/libaaudio.map.txt
@@ -17,6 +17,10 @@ AAudioStreamBuilder_setSharingMode; AAudioStreamBuilder_setDirection; AAudioStreamBuilder_setBufferCapacityInFrames; + AAudioStreamBuilder_setUsage; # introduced=28 + AAudioStreamBuilder_setContentType; # introduced=28 + AAudioStreamBuilder_setInputPreset; # introduced=28 + AAudioStreamBuilder_setSessionId; # introduced=28 AAudioStreamBuilder_openStream; AAudioStreamBuilder_delete; AAudioStream_close; @@ -42,8 +46,12 @@ AAudioStream_getFormat; AAudioStream_getSharingMode; AAudioStream_getDirection; + AAudioStream_getUsage; # introduced=28 + AAudioStream_getContentType; # introduced=28 + AAudioStream_getInputPreset; # introduced=28 AAudioStream_getFramesWritten; AAudioStream_getFramesRead; + AAudioStream_getSessionId; # introduced=28 AAudioStream_getTimestamp; AAudioStream_isMMapUsed; local:
diff --git a/media/libaaudio/src/Android.bp b/media/libaaudio/src/Android.bp new file mode 100644 index 0000000..b9e28a0 --- /dev/null +++ b/media/libaaudio/src/Android.bp
@@ -0,0 +1,67 @@ +cc_library { + name: "libaaudio", + + local_include_dirs: [ + "binding", + "client", + "core", + "fifo", + "legacy", + "utility", + ], + export_include_dirs: ["."], + header_libs: ["libaaudio_headers"], + export_header_lib_headers: ["libaaudio_headers"], + + srcs: [ + "core/AudioStream.cpp", + "core/AudioStreamBuilder.cpp", + "core/AAudioAudio.cpp", + "core/AAudioStreamParameters.cpp", + "legacy/AudioStreamLegacy.cpp", + "legacy/AudioStreamRecord.cpp", + "legacy/AudioStreamTrack.cpp", + "utility/AAudioUtilities.cpp", + "utility/FixedBlockAdapter.cpp", + "utility/FixedBlockReader.cpp", + "utility/FixedBlockWriter.cpp", + "utility/LinearRamp.cpp", + "fifo/FifoBuffer.cpp", + "fifo/FifoControllerBase.cpp", + "client/AudioEndpoint.cpp", + "client/AudioStreamInternal.cpp", + "client/AudioStreamInternalCapture.cpp", + "client/AudioStreamInternalPlay.cpp", + "client/IsochronousClockModel.cpp", + "binding/AudioEndpointParcelable.cpp", + "binding/AAudioBinderClient.cpp", + "binding/AAudioStreamRequest.cpp", + "binding/AAudioStreamConfiguration.cpp", + "binding/IAAudioClient.cpp", + "binding/IAAudioService.cpp", + "binding/RingBufferParcelable.cpp", + "binding/SharedMemoryParcelable.cpp", + "binding/SharedRegionParcelable.cpp", + ], + + cflags: [ + "-Wno-unused-parameter", + "-Wall", + "-Werror", + + // By default, all symbols are hidden. + // "-fvisibility=hidden", + // AAUDIO_API is used to explicitly export a function or a variable as a visible symbol. + "-DAAUDIO_API=__attribute__((visibility(\"default\")))", + ], + + shared_libs: [ + "libaudioclient", + "libaudioutils", + "liblog", + "libcutils", + "libutils", + "libbinder", + "libaudiomanager", + ], +}
diff --git a/media/libaaudio/src/Android.mk b/media/libaaudio/src/Android.mk deleted file mode 100644 index 6861248..0000000 --- a/media/libaaudio/src/Android.mk +++ /dev/null
@@ -1,128 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -# ======================= STATIC LIBRARY ========================== -# This is being built because it make AAudio testing very easy with a complete executable. -# TODO Remove this target later, when not needed. -include $(CLEAR_VARS) - -LOCAL_MODULE := libaaudio -LOCAL_MODULE_TAGS := optional - -LIBAAUDIO_DIR := $(TOP)/frameworks/av/media/libaaudio -LIBAAUDIO_SRC_DIR := $(LIBAAUDIO_DIR)/src - -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/native/include \ - system/core/base/include \ - frameworks/native/media/libaaudio/include/include \ - frameworks/av/media/libaaudio/include \ - frameworks/native/include \ - frameworks/av/media/libaudioclient/include \ - $(LOCAL_PATH) \ - $(LOCAL_PATH)/binding \ - $(LOCAL_PATH)/client \ - $(LOCAL_PATH)/core \ - $(LOCAL_PATH)/fifo \ - $(LOCAL_PATH)/legacy \ - $(LOCAL_PATH)/utility - -# If you add a file here then also add it below in the SHARED target -LOCAL_SRC_FILES = \ - core/AudioStream.cpp \ - core/AudioStreamBuilder.cpp \ - core/AAudioAudio.cpp \ - core/AAudioStreamParameters.cpp \ - legacy/AudioStreamLegacy.cpp \ - legacy/AudioStreamRecord.cpp \ - legacy/AudioStreamTrack.cpp \ - utility/AAudioUtilities.cpp \ - utility/FixedBlockAdapter.cpp \ - utility/FixedBlockReader.cpp \ - utility/FixedBlockWriter.cpp \ - utility/LinearRamp.cpp \ - fifo/FifoBuffer.cpp \ - fifo/FifoControllerBase.cpp \ - client/AudioEndpoint.cpp \ - client/AudioStreamInternal.cpp \ - client/AudioStreamInternalCapture.cpp \ - client/AudioStreamInternalPlay.cpp \ - client/IsochronousClockModel.cpp \ - binding/AudioEndpointParcelable.cpp \ - binding/AAudioBinderClient.cpp \ - binding/AAudioStreamRequest.cpp \ - binding/AAudioStreamConfiguration.cpp \ - binding/IAAudioClient.cpp \ - binding/IAAudioService.cpp \ - binding/RingBufferParcelable.cpp \ - binding/SharedMemoryParcelable.cpp \ - binding/SharedRegionParcelable.cpp - -LOCAL_CFLAGS += -Wno-unused-parameter -Wall -Werror - -# By default, all symbols are hidden. -# LOCAL_CFLAGS += -fvisibility=hidden -# AAUDIO_API is used to explicitly export a function or a variable as a visible symbol. -LOCAL_CFLAGS += -DAAUDIO_API='__attribute__((visibility("default")))' - -include $(BUILD_STATIC_LIBRARY) - -# ======================= SHARED LIBRARY ========================== -include $(CLEAR_VARS) - -LOCAL_MODULE := libaaudio -LOCAL_MODULE_TAGS := optional - -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/native/include \ - system/core/base/include \ - frameworks/native/media/libaaudio/include/include \ - frameworks/av/media/libaaudio/include \ - $(LOCAL_PATH) \ - $(LOCAL_PATH)/binding \ - $(LOCAL_PATH)/client \ - $(LOCAL_PATH)/core \ - $(LOCAL_PATH)/fifo \ - $(LOCAL_PATH)/legacy \ - $(LOCAL_PATH)/utility - -LOCAL_SRC_FILES = core/AudioStream.cpp \ - core/AudioStreamBuilder.cpp \ - core/AAudioAudio.cpp \ - core/AAudioStreamParameters.cpp \ - legacy/AudioStreamLegacy.cpp \ - legacy/AudioStreamRecord.cpp \ - legacy/AudioStreamTrack.cpp \ - utility/AAudioUtilities.cpp \ - utility/FixedBlockAdapter.cpp \ - utility/FixedBlockReader.cpp \ - utility/FixedBlockWriter.cpp \ - utility/LinearRamp.cpp \ - fifo/FifoBuffer.cpp \ - fifo/FifoControllerBase.cpp \ - client/AudioEndpoint.cpp \ - client/AudioStreamInternal.cpp \ - client/AudioStreamInternalCapture.cpp \ - client/AudioStreamInternalPlay.cpp \ - client/IsochronousClockModel.cpp \ - binding/AudioEndpointParcelable.cpp \ - binding/AAudioBinderClient.cpp \ - binding/AAudioStreamRequest.cpp \ - binding/AAudioStreamConfiguration.cpp \ - binding/IAAudioClient.cpp \ - binding/IAAudioService.cpp \ - binding/RingBufferParcelable.cpp \ - binding/SharedMemoryParcelable.cpp \ - binding/SharedRegionParcelable.cpp - -LOCAL_CFLAGS += -Wno-unused-parameter -Wall -Werror - -# By default, all symbols are hidden. -# LOCAL_CFLAGS += -fvisibility=hidden -# AAUDIO_API is used to explicitly export a function or a variable as a visible symbol. -LOCAL_CFLAGS += -DAAUDIO_API='__attribute__((visibility("default")))' - -LOCAL_SHARED_LIBRARIES := libaudioclient liblog libcutils libutils libbinder libaudiomanager - -include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libaaudio/src/binding/AAudioBinderClient.cpp b/media/libaaudio/src/binding/AAudioBinderClient.cpp index 07ee2de..dd620e3 100644 --- a/media/libaaudio/src/binding/AAudioBinderClient.cpp +++ b/media/libaaudio/src/binding/AAudioBinderClient.cpp
@@ -15,7 +15,7 @@ */ -#define LOG_TAG "AAudio" +#define LOG_TAG "AAudioBinderClient" //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -61,11 +61,11 @@ , Singleton<AAudioBinderClient>() { gKeepBinderClient = this; // so this singleton won't get deleted mAAudioClient = new AAudioClient(this); - ALOGV("AAudioBinderClient() this = %p, created mAAudioClient = %p", this, mAAudioClient.get()); + ALOGV("%s - this = %p, created mAAudioClient = %p", __func__, this, mAAudioClient.get()); } AAudioBinderClient::~AAudioBinderClient() { - ALOGV("AAudioBinderClient()::~AAudioBinderClient() destroying %p", this); + ALOGV("%s - destroying %p", __func__, this); Mutex::Autolock _l(mServiceLock); if (mAAudioService != 0) { IInterface::asBinder(mAAudioService)->unlinkToDeath(mAAudioClient); @@ -137,7 +137,7 @@ stream = service->openStream(request, configurationOutput); if (stream == AAUDIO_ERROR_NO_SERVICE) { - ALOGE("AAudioBinderClient::openStream lost connection to AAudioService."); + ALOGE("openStream lost connection to AAudioService."); dropAAudioService(); // force a reconnect } else { break;
diff --git a/media/libaaudio/src/binding/AAudioServiceMessage.h b/media/libaaudio/src/binding/AAudioServiceMessage.h index 54e8001..3981454 100644 --- a/media/libaaudio/src/binding/AAudioServiceMessage.h +++ b/media/libaaudio/src/binding/AAudioServiceMessage.h
@@ -36,15 +36,17 @@ AAUDIO_SERVICE_EVENT_PAUSED, AAUDIO_SERVICE_EVENT_STOPPED, AAUDIO_SERVICE_EVENT_FLUSHED, - AAUDIO_SERVICE_EVENT_CLOSED, AAUDIO_SERVICE_EVENT_DISCONNECTED, - AAUDIO_SERVICE_EVENT_VOLUME + AAUDIO_SERVICE_EVENT_VOLUME, + AAUDIO_SERVICE_EVENT_XRUN } aaudio_service_event_t; struct AAudioMessageEvent { aaudio_service_event_t event; - double dataDouble; - int64_t dataLong; + union { + double dataDouble; + int64_t dataLong; + }; }; typedef struct AAudioServiceMessage_s {
diff --git a/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp b/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp index 153fce3..959db61 100644 --- a/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp +++ b/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp
@@ -50,6 +50,14 @@ if (status != NO_ERROR) goto error; status = parcel->writeInt32(getBufferCapacity()); if (status != NO_ERROR) goto error; + status = parcel->writeInt32((int32_t) getUsage()); + if (status != NO_ERROR) goto error; + status = parcel->writeInt32((int32_t) getContentType()); + if (status != NO_ERROR) goto error; + status = parcel->writeInt32((int32_t) getInputPreset()); + if (status != NO_ERROR) goto error; + status = parcel->writeInt32(getSessionId()); + if (status != NO_ERROR) goto error; return NO_ERROR; error: ALOGE("AAudioStreamConfiguration.writeToParcel(): write failed = %d", status); @@ -69,16 +77,28 @@ setSamplesPerFrame(value); status = parcel->readInt32(&value); if (status != NO_ERROR) goto error; - setSharingMode(value); + setSharingMode((aaudio_sharing_mode_t) value); status = parcel->readInt32(&value); if (status != NO_ERROR) goto error; - setFormat(value); + setFormat((aaudio_format_t) value); status = parcel->readInt32(&value); if (status != NO_ERROR) goto error; setDirection((aaudio_direction_t) value); status = parcel->readInt32(&value); if (status != NO_ERROR) goto error; setBufferCapacity(value); + status = parcel->readInt32(&value); + if (status != NO_ERROR) goto error; + setUsage((aaudio_usage_t) value); + status = parcel->readInt32(&value); + if (status != NO_ERROR) goto error; + setContentType((aaudio_content_type_t) value); + status = parcel->readInt32(&value); + if (status != NO_ERROR) goto error; + setInputPreset((aaudio_input_preset_t) value); + status = parcel->readInt32(&value); + if (status != NO_ERROR) goto error; + setSessionId(value); return NO_ERROR; error: ALOGE("AAudioStreamConfiguration.readFromParcel(): read failed = %d", status);
diff --git a/media/libaaudio/src/binding/AAudioStreamRequest.cpp b/media/libaaudio/src/binding/AAudioStreamRequest.cpp index 1200ab2..c30c5b9 100644 --- a/media/libaaudio/src/binding/AAudioStreamRequest.cpp +++ b/media/libaaudio/src/binding/AAudioStreamRequest.cpp
@@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "AAudio" +#define LOG_TAG "AAudioStreamRequest" //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -58,7 +58,7 @@ return NO_ERROR; error: - ALOGE("AAudioStreamRequest.writeToParcel(): write failed = %d", status); + ALOGE("writeToParcel(): write failed = %d", status); return status; } @@ -80,7 +80,7 @@ return NO_ERROR; error: - ALOGE("AAudioStreamRequest.readFromParcel(): read failed = %d", status); + ALOGE("readFromParcel(): read failed = %d", status); return status; } @@ -89,9 +89,9 @@ } void AAudioStreamRequest::dump() const { - ALOGD("AAudioStreamRequest mUserId = %d", mUserId); - ALOGD("AAudioStreamRequest mProcessId = %d", mProcessId); - ALOGD("AAudioStreamRequest mSharingModeMatchRequired = %d", mSharingModeMatchRequired); - ALOGD("AAudioStreamRequest mInService = %d", mInService); + ALOGD("mUserId = %d", mUserId); + ALOGD("mProcessId = %d", mProcessId); + ALOGD("mSharingModeMatchRequired = %d", mSharingModeMatchRequired); + ALOGD("mInService = %d", mInService); mConfiguration.dump(); }
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp index 1a97555..61d7d27 100644 --- a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp +++ b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
@@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "AAudio" +#define LOG_TAG "AudioEndpointParcelable" //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -64,27 +64,54 @@ * The read and write must be symmetric. */ status_t AudioEndpointParcelable::writeToParcel(Parcel* parcel) const { - parcel->writeInt32(mNumSharedMemories); + status_t status = AAudioConvert_aaudioToAndroidStatus(validate()); + if (status != NO_ERROR) goto error; + + status = parcel->writeInt32(mNumSharedMemories); + if (status != NO_ERROR) goto error; + for (int i = 0; i < mNumSharedMemories; i++) { - mSharedMemories[i].writeToParcel(parcel); + status = mSharedMemories[i].writeToParcel(parcel); + if (status != NO_ERROR) goto error; } - mUpMessageQueueParcelable.writeToParcel(parcel); - mDownMessageQueueParcelable.writeToParcel(parcel); - mUpDataQueueParcelable.writeToParcel(parcel); - mDownDataQueueParcelable.writeToParcel(parcel); - return NO_ERROR; // TODO check for errors above + status = mUpMessageQueueParcelable.writeToParcel(parcel); + if (status != NO_ERROR) goto error; + status = mDownMessageQueueParcelable.writeToParcel(parcel); + if (status != NO_ERROR) goto error; + status = mUpDataQueueParcelable.writeToParcel(parcel); + if (status != NO_ERROR) goto error; + status = mDownDataQueueParcelable.writeToParcel(parcel); + if (status != NO_ERROR) goto error; + + return NO_ERROR; + +error: + ALOGE("%s returning %d", __func__, status); + return status; } status_t AudioEndpointParcelable::readFromParcel(const Parcel* parcel) { - parcel->readInt32(&mNumSharedMemories); + status_t status = parcel->readInt32(&mNumSharedMemories); + if (status != NO_ERROR) goto error; + for (int i = 0; i < mNumSharedMemories; i++) { mSharedMemories[i].readFromParcel(parcel); + if (status != NO_ERROR) goto error; } - mUpMessageQueueParcelable.readFromParcel(parcel); - mDownMessageQueueParcelable.readFromParcel(parcel); - mUpDataQueueParcelable.readFromParcel(parcel); - mDownDataQueueParcelable.readFromParcel(parcel); - return NO_ERROR; // TODO check for errors above + status = mUpMessageQueueParcelable.readFromParcel(parcel); + if (status != NO_ERROR) goto error; + status = mDownMessageQueueParcelable.readFromParcel(parcel); + if (status != NO_ERROR) goto error; + status = mUpDataQueueParcelable.readFromParcel(parcel); + if (status != NO_ERROR) goto error; + status = mDownDataQueueParcelable.readFromParcel(parcel); + if (status != NO_ERROR) goto error; + + return AAudioConvert_aaudioToAndroidStatus(validate()); + +error: + ALOGE("%s returning %d", __func__, status); + return status; } aaudio_result_t AudioEndpointParcelable::resolve(EndpointDescriptor *descriptor) { @@ -109,52 +136,28 @@ return AAudioConvert_androidToAAudioResult(err); } -aaudio_result_t AudioEndpointParcelable::validate() { - aaudio_result_t result; +aaudio_result_t AudioEndpointParcelable::validate() const { if (mNumSharedMemories < 0 || mNumSharedMemories >= MAX_SHARED_MEMORIES) { - ALOGE("AudioEndpointParcelable invalid mNumSharedMemories = %d", mNumSharedMemories); + ALOGE("invalid mNumSharedMemories = %d", mNumSharedMemories); return AAUDIO_ERROR_INTERNAL; } - for (int i = 0; i < mNumSharedMemories; i++) { - result = mSharedMemories[i].validate(); - if (result != AAUDIO_OK) { - ALOGE("AudioEndpointParcelable invalid mSharedMemories[%d] = %d", i, result); - return result; - } - } - if ((result = mUpMessageQueueParcelable.validate()) != AAUDIO_OK) { - ALOGE("AudioEndpointParcelable invalid mUpMessageQueueParcelable = %d", result); - return result; - } - if ((result = mDownMessageQueueParcelable.validate()) != AAUDIO_OK) { - ALOGE("AudioEndpointParcelable invalid mDownMessageQueueParcelable = %d", result); - return result; - } - if ((result = mUpDataQueueParcelable.validate()) != AAUDIO_OK) { - ALOGE("AudioEndpointParcelable invalid mUpDataQueueParcelable = %d", result); - return result; - } - if ((result = mDownDataQueueParcelable.validate()) != AAUDIO_OK) { - ALOGE("AudioEndpointParcelable invalid mDownDataQueueParcelable = %d", result); - return result; - } return AAUDIO_OK; } void AudioEndpointParcelable::dump() { - ALOGD("AudioEndpointParcelable ======================================= BEGIN"); - ALOGD("AudioEndpointParcelable mNumSharedMemories = %d", mNumSharedMemories); + ALOGD("======================================= BEGIN"); + ALOGD("mNumSharedMemories = %d", mNumSharedMemories); for (int i = 0; i < mNumSharedMemories; i++) { mSharedMemories[i].dump(); } - ALOGD("AudioEndpointParcelable mUpMessageQueueParcelable ========="); + ALOGD("mUpMessageQueueParcelable ========="); mUpMessageQueueParcelable.dump(); - ALOGD("AudioEndpointParcelable mDownMessageQueueParcelable ======="); + ALOGD("mDownMessageQueueParcelable ======="); mDownMessageQueueParcelable.dump(); - ALOGD("AudioEndpointParcelable mUpDataQueueParcelable ============"); + ALOGD("mUpDataQueueParcelable ============"); mUpDataQueueParcelable.dump(); - ALOGD("AudioEndpointParcelable mDownDataQueueParcelable =========="); + ALOGD("mDownDataQueueParcelable =========="); mDownDataQueueParcelable.dump(); - ALOGD("AudioEndpointParcelable ======================================= END"); + ALOGD("======================================= END"); }
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.h b/media/libaaudio/src/binding/AudioEndpointParcelable.h index aa8573f..e4f8b9e 100644 --- a/media/libaaudio/src/binding/AudioEndpointParcelable.h +++ b/media/libaaudio/src/binding/AudioEndpointParcelable.h
@@ -56,8 +56,6 @@ aaudio_result_t resolve(EndpointDescriptor *descriptor); - aaudio_result_t validate(); - aaudio_result_t close(); void dump(); @@ -70,6 +68,8 @@ RingBufferParcelable mDownDataQueueParcelable; // eg. playback private: + aaudio_result_t validate() const; + int32_t mNumSharedMemories = 0; SharedMemoryParcelable mSharedMemories[MAX_SHARED_MEMORIES]; };
diff --git a/media/libaaudio/src/binding/IAAudioService.cpp b/media/libaaudio/src/binding/IAAudioService.cpp index b3c4934..620edc7 100644 --- a/media/libaaudio/src/binding/IAAudioService.cpp +++ b/media/libaaudio/src/binding/IAAudioService.cpp
@@ -121,17 +121,11 @@ ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION passed result %d", result); return result; } - err = parcelable.readFromParcel(&reply);; + err = parcelable.readFromParcel(&reply); if (err != NO_ERROR) { ALOGE("BpAAudioService::client transact(GET_STREAM_DESCRIPTION) read endpoint %d", err); return AAudioConvert_androidToAAudioResult(err); } - //parcelable.dump(); - result = parcelable.validate(); - if (result != AAUDIO_OK) { - ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION validation fails %d", result); - return result; - } return result; } @@ -250,6 +244,7 @@ pid_t tid; int64_t nanoseconds; aaudio_result_t result; + status_t status = NO_ERROR; ALOGV("BnAAudioService::onTransact(%i) %i", code, flags); switch(code) { @@ -294,21 +289,20 @@ case GET_STREAM_DESCRIPTION: { CHECK_INTERFACE(IAAudioService, data, reply); - data.readInt32(&streamHandle); + status = data.readInt32(&streamHandle); + if (status != NO_ERROR) { + return status; + } aaudio::AudioEndpointParcelable parcelable; result = getStreamDescription(streamHandle, parcelable); if (result != AAUDIO_OK) { return AAudioConvert_aaudioToAndroidStatus(result); } - result = parcelable.validate(); - if (result != AAUDIO_OK) { - ALOGE("BnAAudioService::onTransact getStreamDescription() returns %d", result); - parcelable.dump(); - return AAudioConvert_aaudioToAndroidStatus(result); + status = reply->writeInt32(result); + if (status != NO_ERROR) { + return status; } - reply->writeInt32(result); - parcelable.writeToParcel(reply); - return NO_ERROR; + return parcelable.writeToParcel(reply); } break; case START_STREAM: {
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.cpp b/media/libaaudio/src/binding/RingBufferParcelable.cpp index 6b74b21..4996b3f 100644 --- a/media/libaaudio/src/binding/RingBufferParcelable.cpp +++ b/media/libaaudio/src/binding/RingBufferParcelable.cpp
@@ -14,13 +14,14 @@ * limitations under the License. */ -#define LOG_TAG "AAudio" +#define LOG_TAG "RingBufferParcelable" //#define LOG_NDEBUG 0 #include <utils/Log.h> #include <stdint.h> #include <binder/Parcelable.h> +#include <utility/AAudioUtilities.h> #include "binding/AAudioServiceDefinitions.h" #include "binding/SharedRegionParcelable.h" @@ -79,7 +80,10 @@ * The read and write must be symmetric. */ status_t RingBufferParcelable::writeToParcel(Parcel* parcel) const { - status_t status = parcel->writeInt32(mCapacityInFrames); + status_t status = AAudioConvert_aaudioToAndroidStatus(validate()); + if (status != NO_ERROR) goto error; + + status = parcel->writeInt32(mCapacityInFrames); if (status != NO_ERROR) goto error; if (mCapacityInFrames > 0) { status = parcel->writeInt32(mBytesPerFrame); @@ -97,7 +101,7 @@ } return NO_ERROR; error: - ALOGE("RingBufferParcelable::writeToParcel() error = %d", status); + ALOGE("%s returning %d", __func__, status); return status; } @@ -118,9 +122,9 @@ status = mDataParcelable.readFromParcel(parcel); if (status != NO_ERROR) goto error; } - return NO_ERROR; + return AAudioConvert_aaudioToAndroidStatus(validate()); error: - ALOGE("RingBufferParcelable::readFromParcel() error = %d", status); + ALOGE("%s returning %d", __func__, status); return status; } @@ -151,42 +155,29 @@ return AAUDIO_OK; } -aaudio_result_t RingBufferParcelable::validate() { - aaudio_result_t result; +aaudio_result_t RingBufferParcelable::validate() const { if (mCapacityInFrames < 0 || mCapacityInFrames >= 32 * 1024) { - ALOGE("RingBufferParcelable invalid mCapacityInFrames = %d", mCapacityInFrames); + ALOGE("invalid mCapacityInFrames = %d", mCapacityInFrames); return AAUDIO_ERROR_INTERNAL; } if (mBytesPerFrame < 0 || mBytesPerFrame >= 256) { - ALOGE("RingBufferParcelable invalid mBytesPerFrame = %d", mBytesPerFrame); + ALOGE("invalid mBytesPerFrame = %d", mBytesPerFrame); return AAUDIO_ERROR_INTERNAL; } if (mFramesPerBurst < 0 || mFramesPerBurst >= 16 * 1024) { - ALOGE("RingBufferParcelable invalid mFramesPerBurst = %d", mFramesPerBurst); + ALOGE("invalid mFramesPerBurst = %d", mFramesPerBurst); return AAUDIO_ERROR_INTERNAL; } - if ((result = mReadCounterParcelable.validate()) != AAUDIO_OK) { - ALOGE("RingBufferParcelable invalid mReadCounterParcelable = %d", result); - return result; - } - if ((result = mWriteCounterParcelable.validate()) != AAUDIO_OK) { - ALOGE("RingBufferParcelable invalid mWriteCounterParcelable = %d", result); - return result; - } - if ((result = mDataParcelable.validate()) != AAUDIO_OK) { - ALOGE("RingBufferParcelable invalid mDataParcelable = %d", result); - return result; - } return AAUDIO_OK; } void RingBufferParcelable::dump() { - ALOGD("RingBufferParcelable mCapacityInFrames = %d ---------", mCapacityInFrames); + ALOGD("mCapacityInFrames = %d ---------", mCapacityInFrames); if (mCapacityInFrames > 0) { - ALOGD("RingBufferParcelable mBytesPerFrame = %d", mBytesPerFrame); - ALOGD("RingBufferParcelable mFramesPerBurst = %d", mFramesPerBurst); - ALOGD("RingBufferParcelable mFlags = %u", mFlags); + ALOGD("mBytesPerFrame = %d", mBytesPerFrame); + ALOGD("mFramesPerBurst = %d", mFramesPerBurst); + ALOGD("mFlags = %u", mFlags); mReadCounterParcelable.dump(); mWriteCounterParcelable.dump(); mDataParcelable.dump();
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.h b/media/libaaudio/src/binding/RingBufferParcelable.h index bd562f2..1dbcf07 100644 --- a/media/libaaudio/src/binding/RingBufferParcelable.h +++ b/media/libaaudio/src/binding/RingBufferParcelable.h
@@ -66,11 +66,12 @@ aaudio_result_t resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor); - aaudio_result_t validate(); - void dump(); private: + + aaudio_result_t validate() const; + SharedRegionParcelable mReadCounterParcelable; SharedRegionParcelable mWriteCounterParcelable; SharedRegionParcelable mDataParcelable;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp index 90217ab..0b0cf77 100644 --- a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp +++ b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
@@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "AAudio" +#define LOG_TAG "SharedMemoryParcelable" //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -43,16 +43,18 @@ void SharedMemoryParcelable::setup(const unique_fd& fd, int32_t sizeInBytes) { mFd.reset(dup(fd.get())); // store a duplicate fd - ALOGV("SharedMemoryParcelable::setup(%d -> %d, %d) this = %p\n", - fd.get(), mFd.get(), sizeInBytes, this); + ALOGV("setup(%d -> %d, %d) this = %p\n", fd.get(), mFd.get(), sizeInBytes, this); mSizeInBytes = sizeInBytes; } status_t SharedMemoryParcelable::writeToParcel(Parcel* parcel) const { - status_t status = parcel->writeInt32(mSizeInBytes); + status_t status = AAudioConvert_aaudioToAndroidStatus(validate()); + if (status != NO_ERROR) return status; + + status = parcel->writeInt32(mSizeInBytes); if (status != NO_ERROR) return status; if (mSizeInBytes > 0) { - ALOGV("SharedMemoryParcelable::writeToParcel() mFd = %d, this = %p\n", mFd.get(), this); + ALOGV("writeToParcel() mFd = %d, this = %p\n", mFd.get(), this); status = parcel->writeUniqueFileDescriptor(mFd); ALOGE_IF(status != NO_ERROR, "SharedMemoryParcelable writeDupFileDescriptor failed : %d", status); @@ -62,22 +64,27 @@ status_t SharedMemoryParcelable::readFromParcel(const Parcel* parcel) { status_t status = parcel->readInt32(&mSizeInBytes); - if (status != NO_ERROR) { - return status; - } + if (status != NO_ERROR) goto error; + if (mSizeInBytes > 0) { // The Parcel owns the file descriptor and will close it later. unique_fd mmapFd; status = parcel->readUniqueFileDescriptor(&mmapFd); if (status != NO_ERROR) { - ALOGE("SharedMemoryParcelable::readFromParcel() readUniqueFileDescriptor() failed : %d", - status); - } else { - // Resolve the memory now while we still have the FD from the Parcel. - // Closing the FD will not affect the shared memory once mmap() has been called. - status = AAudioConvert_androidToAAudioResult(resolveSharedMemory(mmapFd)); + ALOGE("readFromParcel() readUniqueFileDescriptor() failed : %d", status); + goto error; } + + // Resolve the memory now while we still have the FD from the Parcel. + // Closing the FD will not affect the shared memory once mmap() has been called. + aaudio_result_t result = resolveSharedMemory(mmapFd); + status = AAudioConvert_aaudioToAndroidStatus(result); + if (status != NO_ERROR) goto error; } + + return AAudioConvert_aaudioToAndroidStatus(validate()); + +error: return status; } @@ -85,7 +92,7 @@ if (mResolvedAddress != MMAP_UNRESOLVED_ADDRESS) { int err = munmap(mResolvedAddress, mSizeInBytes); if (err < 0) { - ALOGE("SharedMemoryParcelable::close() munmap() failed %d", err); + ALOGE("close() munmap() failed %d", err); return AAudioConvert_androidToAAudioResult(err); } mResolvedAddress = MMAP_UNRESOLVED_ADDRESS; @@ -97,8 +104,7 @@ mResolvedAddress = (uint8_t *) mmap(0, mSizeInBytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0); if (mResolvedAddress == MMAP_UNRESOLVED_ADDRESS) { - ALOGE("SharedMemoryParcelable mmap() failed for fd = %d, errno = %s", - fd.get(), strerror(errno)); + ALOGE("mmap() failed for fd = %d, errno = %s", fd.get(), strerror(errno)); return AAUDIO_ERROR_INTERNAL; } return AAUDIO_OK; @@ -107,10 +113,10 @@ aaudio_result_t SharedMemoryParcelable::resolve(int32_t offsetInBytes, int32_t sizeInBytes, void **regionAddressPtr) { if (offsetInBytes < 0) { - ALOGE("SharedMemoryParcelable illegal offsetInBytes = %d", offsetInBytes); + ALOGE("illegal offsetInBytes = %d", offsetInBytes); return AAUDIO_ERROR_OUT_OF_RANGE; } else if ((offsetInBytes + sizeInBytes) > mSizeInBytes) { - ALOGE("SharedMemoryParcelable out of range, offsetInBytes = %d, " + ALOGE("out of range, offsetInBytes = %d, " "sizeInBytes = %d, mSizeInBytes = %d", offsetInBytes, sizeInBytes, mSizeInBytes); return AAUDIO_ERROR_OUT_OF_RANGE; @@ -122,16 +128,15 @@ if (mFd.get() != -1) { result = resolveSharedMemory(mFd); } else { - ALOGE("SharedMemoryParcelable has no file descriptor for shared memory."); + ALOGE("has no file descriptor for shared memory."); result = AAUDIO_ERROR_INTERNAL; } } if (result == AAUDIO_OK && mResolvedAddress != MMAP_UNRESOLVED_ADDRESS) { *regionAddressPtr = mResolvedAddress + offsetInBytes; - ALOGV("SharedMemoryParcelable mResolvedAddress = %p", mResolvedAddress); - ALOGV("SharedMemoryParcelable offset by %d, *regionAddressPtr = %p", - offsetInBytes, *regionAddressPtr); + ALOGV("mResolvedAddress = %p", mResolvedAddress); + ALOGV("offset by %d, *regionAddressPtr = %p", offsetInBytes, *regionAddressPtr); } return result; } @@ -140,16 +145,16 @@ return mSizeInBytes; } -aaudio_result_t SharedMemoryParcelable::validate() { +aaudio_result_t SharedMemoryParcelable::validate() const { if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) { - ALOGE("SharedMemoryParcelable invalid mSizeInBytes = %d", mSizeInBytes); + ALOGE("invalid mSizeInBytes = %d", mSizeInBytes); return AAUDIO_ERROR_OUT_OF_RANGE; } return AAUDIO_OK; } void SharedMemoryParcelable::dump() { - ALOGD("SharedMemoryParcelable mFd = %d", mFd.get()); - ALOGD("SharedMemoryParcelable mSizeInBytes = %d", mSizeInBytes); - ALOGD("SharedMemoryParcelable mResolvedAddress = %p", mResolvedAddress); + ALOGD("mFd = %d", mFd.get()); + ALOGD("mSizeInBytes = %d", mSizeInBytes); + ALOGD("mResolvedAddress = %p", mResolvedAddress); }
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.h b/media/libaaudio/src/binding/SharedMemoryParcelable.h index 2a634e0..82c2240 100644 --- a/media/libaaudio/src/binding/SharedMemoryParcelable.h +++ b/media/libaaudio/src/binding/SharedMemoryParcelable.h
@@ -61,8 +61,6 @@ int32_t getSizeInBytes(); - aaudio_result_t validate(); - void dump(); protected: @@ -74,6 +72,11 @@ android::base::unique_fd mFd; int32_t mSizeInBytes = 0; uint8_t *mResolvedAddress = MMAP_UNRESOLVED_ADDRESS; + +private: + + aaudio_result_t validate() const; + }; } /* namespace aaudio */
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.cpp b/media/libaaudio/src/binding/SharedRegionParcelable.cpp index 7381dcb..c776116 100644 --- a/media/libaaudio/src/binding/SharedRegionParcelable.cpp +++ b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
@@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "AAudio" +#define LOG_TAG "SharedRegionParcelable" //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -24,6 +24,7 @@ #include <binder/Parcelable.h> #include <aaudio/AAudio.h> +#include <utility/AAudioUtilities.h> #include "binding/SharedMemoryParcelable.h" #include "binding/SharedRegionParcelable.h" @@ -47,21 +48,38 @@ } status_t SharedRegionParcelable::writeToParcel(Parcel* parcel) const { - parcel->writeInt32(mSizeInBytes); + status_t status = AAudioConvert_aaudioToAndroidStatus(validate()); + if (status != NO_ERROR) goto error; + + status = parcel->writeInt32(mSizeInBytes); + if (status != NO_ERROR) goto error; if (mSizeInBytes > 0) { - parcel->writeInt32(mSharedMemoryIndex); - parcel->writeInt32(mOffsetInBytes); + status = parcel->writeInt32(mSharedMemoryIndex); + if (status != NO_ERROR) goto error; + status = parcel->writeInt32(mOffsetInBytes); + if (status != NO_ERROR) goto error; } - return NO_ERROR; // TODO check for errors above + return NO_ERROR; + +error: + ALOGE("%s returning %d", __func__, status); + return status; } status_t SharedRegionParcelable::readFromParcel(const Parcel* parcel) { - parcel->readInt32(&mSizeInBytes); + status_t status = parcel->readInt32(&mSizeInBytes); + if (status != NO_ERROR) goto error; if (mSizeInBytes > 0) { - parcel->readInt32(&mSharedMemoryIndex); - parcel->readInt32(&mOffsetInBytes); + status = parcel->readInt32(&mSharedMemoryIndex); + if (status != NO_ERROR) goto error; + status = parcel->readInt32(&mOffsetInBytes); + if (status != NO_ERROR) goto error; } - return NO_ERROR; // TODO check for errors above + return AAudioConvert_aaudioToAndroidStatus(validate()); + +error: + ALOGE("%s returning %d", __func__, status); + return status; } aaudio_result_t SharedRegionParcelable::resolve(SharedMemoryParcelable *memoryParcels, @@ -71,25 +89,25 @@ return AAUDIO_OK; } if (mSharedMemoryIndex < 0) { - ALOGE("SharedRegionParcelable invalid mSharedMemoryIndex = %d", mSharedMemoryIndex); + ALOGE("invalid mSharedMemoryIndex = %d", mSharedMemoryIndex); return AAUDIO_ERROR_INTERNAL; } SharedMemoryParcelable *memoryParcel = &memoryParcels[mSharedMemoryIndex]; return memoryParcel->resolve(mOffsetInBytes, mSizeInBytes, regionAddressPtr); } -aaudio_result_t SharedRegionParcelable::validate() { +aaudio_result_t SharedRegionParcelable::validate() const { if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) { - ALOGE("SharedRegionParcelable invalid mSizeInBytes = %d", mSizeInBytes); + ALOGE("invalid mSizeInBytes = %d", mSizeInBytes); return AAUDIO_ERROR_OUT_OF_RANGE; } if (mSizeInBytes > 0) { if (mOffsetInBytes < 0 || mOffsetInBytes >= MAX_MMAP_OFFSET_BYTES) { - ALOGE("SharedRegionParcelable invalid mOffsetInBytes = %d", mOffsetInBytes); + ALOGE("invalid mOffsetInBytes = %d", mOffsetInBytes); return AAUDIO_ERROR_OUT_OF_RANGE; } if (mSharedMemoryIndex < 0 || mSharedMemoryIndex >= MAX_SHARED_MEMORIES) { - ALOGE("SharedRegionParcelable invalid mSharedMemoryIndex = %d", mSharedMemoryIndex); + ALOGE("invalid mSharedMemoryIndex = %d", mSharedMemoryIndex); return AAUDIO_ERROR_INTERNAL; } } @@ -97,9 +115,9 @@ } void SharedRegionParcelable::dump() { - ALOGD("SharedRegionParcelable mSizeInBytes = %d -----", mSizeInBytes); + ALOGD("mSizeInBytes = %d -----", mSizeInBytes); if (mSizeInBytes > 0) { - ALOGD("SharedRegionParcelable mSharedMemoryIndex = %d", mSharedMemoryIndex); - ALOGD("SharedRegionParcelable mOffsetInBytes = %d", mOffsetInBytes); + ALOGD("mSharedMemoryIndex = %d", mSharedMemoryIndex); + ALOGD("mOffsetInBytes = %d", mOffsetInBytes); } }
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.h b/media/libaaudio/src/binding/SharedRegionParcelable.h index f6babfd..0cd8c04 100644 --- a/media/libaaudio/src/binding/SharedRegionParcelable.h +++ b/media/libaaudio/src/binding/SharedRegionParcelable.h
@@ -47,14 +47,15 @@ bool isFileDescriptorSafe(SharedMemoryParcelable *memoryParcels); - aaudio_result_t validate(); - void dump(); protected: int32_t mSharedMemoryIndex = -1; int32_t mOffsetInBytes = 0; int32_t mSizeInBytes = 0; + +private: + aaudio_result_t validate() const; }; } /* namespace aaudio */
diff --git a/media/libaaudio/src/client/AudioEndpoint.cpp b/media/libaaudio/src/client/AudioEndpoint.cpp index 604eed5..f8e34d1 100644 --- a/media/libaaudio/src/client/AudioEndpoint.cpp +++ b/media/libaaudio/src/client/AudioEndpoint.cpp
@@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "AAudio" +#define LOG_TAG "AudioEndpoint" //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -45,6 +45,7 @@ delete mUpCommandQueue; } +// TODO Consider moving to a method in RingBufferDescriptor static aaudio_result_t AudioEndpoint_validateQueueDescriptor(const char *type, const RingBufferDescriptor *descriptor) { if (descriptor == nullptr) { @@ -127,19 +128,19 @@ // ============================ up message queue ============================= const RingBufferDescriptor *descriptor = &pEndpointDescriptor->upMessageQueueDescriptor; if(descriptor->bytesPerFrame != sizeof(AAudioServiceMessage)) { - ALOGE("AudioEndpoint.configure() bytesPerFrame != sizeof(AAudioServiceMessage) = %d", + ALOGE("configure() bytesPerFrame != sizeof(AAudioServiceMessage) = %d", descriptor->bytesPerFrame); return AAUDIO_ERROR_INTERNAL; } if(descriptor->readCounterAddress == nullptr || descriptor->writeCounterAddress == nullptr) { - ALOGE("AudioEndpoint.configure() NULL counter address"); + ALOGE("configure() NULL counter address"); return AAUDIO_ERROR_NULL; } // Prevent memory leak and reuse. if(mUpCommandQueue != nullptr || mDataQueue != nullptr) { - ALOGE("AudioEndpoint.configure() endpoint already used"); + ALOGE("configure() endpoint already used"); return AAUDIO_ERROR_INTERNAL; } @@ -153,8 +154,8 @@ // ============================ data queue ============================= descriptor = &pEndpointDescriptor->dataQueueDescriptor; - ALOGV("AudioEndpoint.configure() data framesPerBurst = %d", descriptor->framesPerBurst); - ALOGV("AudioEndpoint.configure() data readCounterAddress = %p", + ALOGV("configure() data framesPerBurst = %d", descriptor->framesPerBurst); + ALOGV("configure() data readCounterAddress = %p", descriptor->readCounterAddress); // An example of free running is when the other side is read or written by hardware DMA @@ -163,7 +164,7 @@ ? descriptor->readCounterAddress // read by other side : descriptor->writeCounterAddress; // written by other side mFreeRunning = (remoteCounter == nullptr); - ALOGV("AudioEndpoint.configure() mFreeRunning = %d", mFreeRunning ? 1 : 0); + ALOGV("configure() mFreeRunning = %d", mFreeRunning ? 1 : 0); int64_t *readCounterAddress = (descriptor->readCounterAddress == nullptr) ? &mDataReadCounter @@ -258,8 +259,8 @@ } void AudioEndpoint::dump() const { - ALOGD("AudioEndpoint: data readCounter = %lld", (long long) mDataQueue->getReadCounter()); - ALOGD("AudioEndpoint: data writeCounter = %lld", (long long) mDataQueue->getWriteCounter()); + ALOGD("data readCounter = %lld", (long long) mDataQueue->getReadCounter()); + ALOGD("data writeCounter = %lld", (long long) mDataQueue->getWriteCounter()); } void AudioEndpoint::eraseDataMemory() {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp index 2fdbfaf..9204824 100644 --- a/media/libaaudio/src/client/AudioStreamInternal.cpp +++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -61,15 +61,12 @@ , mClockModel() , mAudioEndpoint() , mServiceStreamHandle(AAUDIO_HANDLE_INVALID) - , mFramesPerBurst(16) , mInService(inService) , mServiceInterface(serviceInterface) , mAtomicTimestamp() , mWakeupDelayNanos(AAudioProperty_getWakeupDelayMicros() * AAUDIO_NANOS_PER_MICROSECOND) , mMinimumSleepNanos(AAudioProperty_getMinimumSleepMicros() * AAUDIO_NANOS_PER_MICROSECOND) { - ALOGD("AudioStreamInternal(): mWakeupDelayNanos = %d, mMinimumSleepNanos = %d", - mWakeupDelayNanos, mMinimumSleepNanos); } AudioStreamInternal::~AudioStreamInternal() { @@ -79,11 +76,12 @@ aaudio_result_t result = AAUDIO_OK; int32_t capacity; + int32_t framesPerBurst; AAudioStreamRequest request; AAudioStreamConfiguration configurationOutput; if (getState() != AAUDIO_STREAM_STATE_UNINITIALIZED) { - ALOGE("AudioStreamInternal::open(): already open! state = %d", getState()); + ALOGE("%s - already open! state = %d", __func__, getState()); return AAUDIO_ERROR_INVALID_STATE; } @@ -104,7 +102,7 @@ request.setUserId(getuid()); request.setProcessId(getpid()); request.setSharingModeMatchRequired(isSharingModeMatchRequired()); - request.setInService(mInService); + request.setInService(isInService()); request.getConfiguration().setDeviceId(getDeviceId()); request.getConfiguration().setSampleRate(getSampleRate()); @@ -112,13 +110,30 @@ request.getConfiguration().setDirection(getDirection()); request.getConfiguration().setSharingMode(getSharingMode()); + request.getConfiguration().setUsage(getUsage()); + request.getConfiguration().setContentType(getContentType()); + request.getConfiguration().setInputPreset(getInputPreset()); + request.getConfiguration().setBufferCapacity(builder.getBufferCapacity()); + mDeviceChannelCount = getSamplesPerFrame(); // Assume it will be the same. Update if not. + mServiceStreamHandle = mServiceInterface.openStream(request, configurationOutput); + if (mServiceStreamHandle < 0 + && request.getConfiguration().getSamplesPerFrame() == 1 // mono? + && getDirection() == AAUDIO_DIRECTION_OUTPUT + && !isInService()) { + // if that failed then try switching from mono to stereo if OUTPUT. + // Only do this in the client. Otherwise we end up with a mono mixer in the service + // that writes to a stereo MMAP stream. + ALOGD("%s - openStream() returned %d, try switching from MONO to STEREO", + __func__, mServiceStreamHandle); + request.getConfiguration().setSamplesPerFrame(2); // stereo + mServiceStreamHandle = mServiceInterface.openStream(request, configurationOutput); + } if (mServiceStreamHandle < 0) { - result = mServiceStreamHandle; - ALOGE("AudioStreamInternal::open(): openStream() returned %d", result); - return result; + ALOGE("%s - openStream() returned %d", __func__, mServiceStreamHandle); + return mServiceStreamHandle; } result = configurationOutput.validate(); @@ -126,13 +141,22 @@ goto error; } // Save results of the open. + if (getSamplesPerFrame() == AAUDIO_UNSPECIFIED) { + setSamplesPerFrame(configurationOutput.getSamplesPerFrame()); + } + mDeviceChannelCount = configurationOutput.getSamplesPerFrame(); + setSampleRate(configurationOutput.getSampleRate()); - setSamplesPerFrame(configurationOutput.getSamplesPerFrame()); setDeviceId(configurationOutput.getDeviceId()); + setSessionId(configurationOutput.getSessionId()); setSharingMode(configurationOutput.getSharingMode()); + setUsage(configurationOutput.getUsage()); + setContentType(configurationOutput.getContentType()); + setInputPreset(configurationOutput.getInputPreset()); + // Save device format so we can do format conversion and volume scaling together. - mDeviceFormat = configurationOutput.getFormat(); + setDeviceFormat(configurationOutput.getFormat()); result = mServiceInterface.getStreamDescription(mServiceStreamHandle, mEndPointParcelable); if (result != AAUDIO_OK) { @@ -151,17 +175,18 @@ goto error; } - mFramesPerBurst = mEndpointDescriptor.dataQueueDescriptor.framesPerBurst; - capacity = mEndpointDescriptor.dataQueueDescriptor.capacityInFrames; - // Validate result from server. - if (mFramesPerBurst < 16 || mFramesPerBurst > 16 * 1024) { - ALOGE("AudioStreamInternal::open(): framesPerBurst out of range = %d", mFramesPerBurst); + framesPerBurst = mEndpointDescriptor.dataQueueDescriptor.framesPerBurst; + if (framesPerBurst < MIN_FRAMES_PER_BURST || framesPerBurst > MAX_FRAMES_PER_BURST) { + ALOGE("%s - framesPerBurst out of range = %d", __func__, framesPerBurst); result = AAUDIO_ERROR_OUT_OF_RANGE; goto error; } - if (capacity < mFramesPerBurst || capacity > 32 * 1024) { - ALOGE("AudioStreamInternal::open(): bufferCapacity out of range = %d", capacity); + mFramesPerBurst = framesPerBurst; // only save good value + + capacity = mEndpointDescriptor.dataQueueDescriptor.capacityInFrames; + if (capacity < mFramesPerBurst || capacity > MAX_BUFFER_CAPACITY_IN_FRAMES) { + ALOGE("%s - bufferCapacity out of range = %d", __func__, capacity); result = AAUDIO_ERROR_OUT_OF_RANGE; goto error; } @@ -169,16 +194,16 @@ mClockModel.setSampleRate(getSampleRate()); mClockModel.setFramesPerBurst(mFramesPerBurst); - if (getDataCallbackProc()) { + if (isDataCallbackSet()) { mCallbackFrames = builder.getFramesPerDataCallback(); if (mCallbackFrames > getBufferCapacity() / 2) { - ALOGE("AudioStreamInternal::open(): framesPerCallback too big = %d, capacity = %d", - mCallbackFrames, getBufferCapacity()); + ALOGE("%s - framesPerCallback too big = %d, capacity = %d", + __func__, mCallbackFrames, getBufferCapacity()); result = AAUDIO_ERROR_OUT_OF_RANGE; goto error; } else if (mCallbackFrames < 0) { - ALOGE("AudioStreamInternal::open(): framesPerCallback negative"); + ALOGE("%s - framesPerCallback negative", __func__); result = AAUDIO_ERROR_OUT_OF_RANGE; goto error; @@ -204,8 +229,7 @@ aaudio_result_t AudioStreamInternal::close() { aaudio_result_t result = AAUDIO_OK; - ALOGD("close(): mServiceStreamHandle = 0x%08X", - mServiceStreamHandle); + ALOGD("%s(): mServiceStreamHandle = 0x%08X", __func__, mServiceStreamHandle); if (mServiceStreamHandle != AAUDIO_HANDLE_INVALID) { // Don't close a stream while it is running. aaudio_stream_state_t currentState = getState(); @@ -216,8 +240,8 @@ result = waitForStateChange(currentState, &nextState, timeoutNanoseconds); if (result != AAUDIO_OK) { - ALOGE("close() waitForStateChange() returned %d %s", - result, AAudio_convertResultToText(result)); + ALOGE("%s() waitForStateChange() returned %d %s", + __func__, result, AAudio_convertResultToText(result)); } } setState(AAUDIO_STREAM_STATE_CLOSING); @@ -240,7 +264,7 @@ static void *aaudio_callback_thread_proc(void *context) { AudioStreamInternal *stream = (AudioStreamInternal *)context; - //LOGD("AudioStreamInternal(): oboe_callback_thread, stream = %p", stream); + //LOGD("oboe_callback_thread, stream = %p", stream); if (stream != NULL) { return stream->callbackLoop(); } else { @@ -288,7 +312,7 @@ mNeedCatchUp.request(); // Ask data processing code to catch up when first timestamp received. // Start data callback thread. - if (result == AAUDIO_OK && getDataCallbackProc() != nullptr) { + if (result == AAUDIO_OK && isDataCallbackSet()) { // Launch the callback loop thread. int64_t periodNanos = mCallbackFrames * AAUDIO_NANOS_PER_SECOND @@ -329,8 +353,13 @@ } } -aaudio_result_t AudioStreamInternal::requestStopInternal() +aaudio_result_t AudioStreamInternal::requestStop() { + aaudio_result_t result = stopCallback(); + if (result != AAUDIO_OK) { + return result; + } + if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) { ALOGE("requestStopInternal() mServiceStreamHandle invalid = 0x%08X", mServiceStreamHandle); @@ -344,16 +373,6 @@ return mServiceInterface.stopStream(mServiceStreamHandle); } -aaudio_result_t AudioStreamInternal::requestStop() -{ - aaudio_result_t result = stopCallback(); - if (result != AAUDIO_OK) { - return result; - } - result = requestStopInternal(); - return result; -} - aaudio_result_t AudioStreamInternal::registerThread() { if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) { ALOGE("registerThread() mServiceStreamHandle invalid"); @@ -373,19 +392,25 @@ } aaudio_result_t AudioStreamInternal::startClient(const android::AudioClient& client, - audio_port_handle_t *clientHandle) { + audio_port_handle_t *portHandle) { + ALOGV("%s() called", __func__); if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) { return AAUDIO_ERROR_INVALID_STATE; } - - return mServiceInterface.startClient(mServiceStreamHandle, client, clientHandle); + aaudio_result_t result = mServiceInterface.startClient(mServiceStreamHandle, + client, portHandle); + ALOGV("%s(%d) returning %d", __func__, *portHandle, result); + return result; } -aaudio_result_t AudioStreamInternal::stopClient(audio_port_handle_t clientHandle) { +aaudio_result_t AudioStreamInternal::stopClient(audio_port_handle_t portHandle) { + ALOGV("%s(%d) called", __func__, portHandle); if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) { return AAUDIO_ERROR_INVALID_STATE; } - return mServiceInterface.stopClient(mServiceStreamHandle, clientHandle); + aaudio_result_t result = mServiceInterface.stopClient(mServiceStreamHandle, portHandle); + ALOGV("%s(%d) returning %d", __func__, portHandle, result); + return result; } aaudio_result_t AudioStreamInternal::getTimestamp(clockid_t clockId, @@ -448,34 +473,30 @@ aaudio_result_t result = AAUDIO_OK; switch (message->event.event) { case AAUDIO_SERVICE_EVENT_STARTED: - ALOGD("AudioStreamInternal::onEventFromServer() got AAUDIO_SERVICE_EVENT_STARTED"); + ALOGD("%s - got AAUDIO_SERVICE_EVENT_STARTED", __func__); if (getState() == AAUDIO_STREAM_STATE_STARTING) { setState(AAUDIO_STREAM_STATE_STARTED); } break; case AAUDIO_SERVICE_EVENT_PAUSED: - ALOGD("AudioStreamInternal::onEventFromServer() got AAUDIO_SERVICE_EVENT_PAUSED"); + ALOGD("%s - got AAUDIO_SERVICE_EVENT_PAUSED", __func__); if (getState() == AAUDIO_STREAM_STATE_PAUSING) { setState(AAUDIO_STREAM_STATE_PAUSED); } break; case AAUDIO_SERVICE_EVENT_STOPPED: - ALOGD("AudioStreamInternal::onEventFromServer() got AAUDIO_SERVICE_EVENT_STOPPED"); + ALOGD("%s - got AAUDIO_SERVICE_EVENT_STOPPED", __func__); if (getState() == AAUDIO_STREAM_STATE_STOPPING) { setState(AAUDIO_STREAM_STATE_STOPPED); } break; case AAUDIO_SERVICE_EVENT_FLUSHED: - ALOGD("AudioStreamInternal::onEventFromServer() got AAUDIO_SERVICE_EVENT_FLUSHED"); + ALOGD("%s - got AAUDIO_SERVICE_EVENT_FLUSHED", __func__); if (getState() == AAUDIO_STREAM_STATE_FLUSHING) { setState(AAUDIO_STREAM_STATE_FLUSHED); onFlushFromServer(); } break; - case AAUDIO_SERVICE_EVENT_CLOSED: - ALOGD("AudioStreamInternal::onEventFromServer() got AAUDIO_SERVICE_EVENT_CLOSED"); - setState(AAUDIO_STREAM_STATE_CLOSED); - break; case AAUDIO_SERVICE_EVENT_DISCONNECTED: // Prevent hardware from looping on old data and making buzzing sounds. if (getDirection() == AAUDIO_DIRECTION_OUTPUT) { @@ -483,18 +504,18 @@ } result = AAUDIO_ERROR_DISCONNECTED; setState(AAUDIO_STREAM_STATE_DISCONNECTED); - ALOGW("WARNING - AudioStreamInternal::onEventFromServer()" - " AAUDIO_SERVICE_EVENT_DISCONNECTED - FIFO cleared"); + ALOGW("%s - AAUDIO_SERVICE_EVENT_DISCONNECTED - FIFO cleared", __func__); break; case AAUDIO_SERVICE_EVENT_VOLUME: + ALOGD("%s - AAUDIO_SERVICE_EVENT_VOLUME %lf", __func__, message->event.dataDouble); mStreamVolume = (float)message->event.dataDouble; doSetVolume(); - ALOGD("AudioStreamInternal::onEventFromServer() AAUDIO_SERVICE_EVENT_VOLUME %lf", - message->event.dataDouble); + break; + case AAUDIO_SERVICE_EVENT_XRUN: + mXRunCount = static_cast<int32_t>(message->event.dataLong); break; default: - ALOGW("WARNING - AudioStreamInternal::onEventFromServer() Unrecognized event = %d", - (int) message->event.event); + ALOGE("%s - Unrecognized event = %d", __func__, (int) message->event.event); break; } return result; @@ -519,8 +540,7 @@ break; default: - ALOGE("WARNING - drainTimestampsFromService() Unrecognized what = %d", - (int) message.what); + ALOGE("%s - unrecognized message.what = %d", __func__, (int) message.what); result = AAUDIO_ERROR_INTERNAL; break; } @@ -533,7 +553,6 @@ aaudio_result_t result = AAUDIO_OK; while (result == AAUDIO_OK) { - //ALOGD("AudioStreamInternal::processCommands() - looping, %d", result); AAudioServiceMessage message; if (mAudioEndpoint.readUpCommand(&message) != 1) { break; // no command this time, no problem @@ -552,8 +571,7 @@ break; default: - ALOGE("WARNING - processCommands() Unrecognized what = %d", - (int) message.what); + ALOGE("%s - unrecognized message.what = %d", __func__, (int) message.what); result = AAUDIO_ERROR_INTERNAL; break; } @@ -614,13 +632,13 @@ if (wakeTimeNanos > deadlineNanos) { // If we time out, just return the framesWritten so far. // TODO remove after we fix the deadline bug - ALOGW("AudioStreamInternal::processData(): entered at %lld nanos, currently %lld", + ALOGW("processData(): entered at %lld nanos, currently %lld", (long long) entryTimeNanos, (long long) currentTimeNanos); - ALOGW("AudioStreamInternal::processData(): TIMEOUT after %lld nanos", + ALOGW("processData(): TIMEOUT after %lld nanos", (long long) timeoutNanoseconds); - ALOGW("AudioStreamInternal::processData(): wakeTime = %lld, deadline = %lld nanos", + ALOGW("processData(): wakeTime = %lld, deadline = %lld nanos", (long long) wakeTimeNanos, (long long) deadlineNanos); - ALOGW("AudioStreamInternal::processData(): past deadline by %d micros", + ALOGW("processData(): past deadline by %d micros", (int)((wakeTimeNanos - deadlineNanos) / AAUDIO_NANOS_PER_MICROSECOND)); mClockModel.dump(); mAudioEndpoint.dump(); @@ -655,14 +673,29 @@ } aaudio_result_t AudioStreamInternal::setBufferSize(int32_t requestedFrames) { + int32_t adjustedFrames = requestedFrames; int32_t actualFrames = 0; - // Round to the next highest burst size. - if (getFramesPerBurst() > 0) { - int32_t numBursts = (requestedFrames + getFramesPerBurst() - 1) / getFramesPerBurst(); - requestedFrames = numBursts * getFramesPerBurst(); + int32_t maximumSize = getBufferCapacity(); + + // Clip to minimum size so that rounding up will work better. + if (adjustedFrames < 1) { + adjustedFrames = 1; } - aaudio_result_t result = mAudioEndpoint.setBufferSizeInFrames(requestedFrames, &actualFrames); + if (adjustedFrames > maximumSize) { + // Clip to maximum size. + adjustedFrames = maximumSize; + } else { + // Round to the next highest burst size. + int32_t numBursts = (adjustedFrames + mFramesPerBurst - 1) / mFramesPerBurst; + adjustedFrames = numBursts * mFramesPerBurst; + // Rounding may have gone above maximum. + if (adjustedFrames > maximumSize) { + adjustedFrames = maximumSize; + } + } + + aaudio_result_t result = mAudioEndpoint.setBufferSizeInFrames(adjustedFrames, &actualFrames); ALOGD("setBufferSize() req = %d => %d", requestedFrames, actualFrames); if (result < 0) { return result; @@ -680,7 +713,7 @@ } int32_t AudioStreamInternal::getFramesPerBurst() const { - return mEndpointDescriptor.dataQueueDescriptor.framesPerBurst; + return mFramesPerBurst; } aaudio_result_t AudioStreamInternal::joinThread(void** returnArg) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h index 47024c0..0425cd5 100644 --- a/media/libaaudio/src/client/AudioStreamInternal.h +++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -34,6 +34,12 @@ namespace aaudio { + // These are intended to be outside the range of what is normally encountered. + // TODO MAXes should probably be much bigger. + constexpr int32_t MIN_FRAMES_PER_BURST = 16; // arbitrary + constexpr int32_t MAX_FRAMES_PER_BURST = 16 * 1024; // arbitrary + constexpr int32_t MAX_BUFFER_CAPACITY_IN_FRAMES = 32 * 1024; // arbitrary + // A stream that talks to the AAudioService or directly to a HAL. class AudioStreamInternal : public AudioStream { @@ -115,8 +121,6 @@ aaudio_result_t processCommands(); - aaudio_result_t requestStopInternal(); - aaudio_result_t stopCallback(); virtual void advanceClientToMatchServerPosition() = 0; @@ -134,14 +138,19 @@ // Calculate timeout for an operation involving framesPerOperation. int64_t calculateReasonableTimeout(int32_t framesPerOperation); - aaudio_format_t mDeviceFormat = AAUDIO_FORMAT_UNSPECIFIED; + int32_t getDeviceChannelCount() const { return mDeviceChannelCount; } + + /** + * @return true if running in audio service, versus in app process + */ + bool isInService() const { return mInService; } IsochronousClockModel mClockModel; // timing model for chasing the HAL AudioEndpoint mAudioEndpoint; // source for reads or sink for writes aaudio_handle_t mServiceStreamHandle; // opaque handle returned from service - int32_t mFramesPerBurst; // frames per HAL transfer + int32_t mFramesPerBurst = MIN_FRAMES_PER_BURST; // frames per HAL transfer int32_t mXRunCount = 0; // how many underrun events? // Offset from underlying frame position. @@ -183,6 +192,8 @@ EndpointDescriptor mEndpointDescriptor; // buffer description with resolved addresses int64_t mServiceLatencyNanos = 0; + + int32_t mDeviceChannelCount = 0; }; } /* namespace aaudio */
diff --git a/media/libaaudio/src/client/AudioStreamInternalCapture.cpp b/media/libaaudio/src/client/AudioStreamInternalCapture.cpp index b792ecd..0719fe1 100644 --- a/media/libaaudio/src/client/AudioStreamInternalCapture.cpp +++ b/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
@@ -14,7 +14,8 @@ * limitations under the License. */ -#define LOG_TAG (mInService ? "AAudioService" : "AAudio") +#define LOG_TAG (mInService ? "AudioStreamInternalCapture_Service" \ + : "AudioStreamInternalCapture_Client") //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -101,7 +102,8 @@ } // If the write index passed the read index then consider it an overrun. - if (mAudioEndpoint.getEmptyFramesAvailable() < 0) { + // For shared streams, the xRunCount is passed up from the service. + if (mAudioEndpoint.isFreeRunning() && mAudioEndpoint.getEmptyFramesAvailable() < 0) { mXRunCount++; if (ATRACE_ENABLED()) { ATRACE_INT("aaOverRuns", mXRunCount); @@ -152,7 +154,7 @@ aaudio_result_t AudioStreamInternalCapture::readNowWithConversion(void *buffer, int32_t numFrames) { - // ALOGD("AudioStreamInternalCapture::readNowWithConversion(%p, %d)", + // ALOGD("readNowWithConversion(%p, %d)", // buffer, numFrames); WrappingBuffer wrappingBuffer; uint8_t *destination = (uint8_t *) buffer; @@ -174,16 +176,16 @@ int32_t numSamples = framesToProcess * getSamplesPerFrame(); // TODO factor this out into a utility function - if (mDeviceFormat == getFormat()) { + if (getDeviceFormat() == getFormat()) { memcpy(destination, wrappingBuffer.data[partIndex], numBytes); - } else if (mDeviceFormat == AAUDIO_FORMAT_PCM_I16 + } else if (getDeviceFormat() == AAUDIO_FORMAT_PCM_I16 && getFormat() == AAUDIO_FORMAT_PCM_FLOAT) { AAudioConvert_pcm16ToFloat( (const int16_t *) wrappingBuffer.data[partIndex], (float *) destination, numSamples, 1.0f); - } else if (mDeviceFormat == AAUDIO_FORMAT_PCM_FLOAT + } else if (getDeviceFormat() == AAUDIO_FORMAT_PCM_FLOAT && getFormat() == AAUDIO_FORMAT_PCM_I16) { AAudioConvert_floatToPcm16( (const float *) wrappingBuffer.data[partIndex], @@ -201,7 +203,7 @@ int32_t framesProcessed = numFrames - framesLeft; mAudioEndpoint.advanceReadIndex(framesProcessed); - //ALOGD("AudioStreamInternalCapture::readNowWithConversion() returns %d", framesProcessed); + //ALOGD("readNowWithConversion() returns %d", framesProcessed); return framesProcessed; } @@ -215,14 +217,14 @@ // Prevent retrograde motion. mLastFramesWritten = std::max(mLastFramesWritten, framesWrittenHardware + mFramesOffsetFromService); - //ALOGD("AudioStreamInternalCapture::getFramesWritten() returns %lld", + //ALOGD("getFramesWritten() returns %lld", // (long long)mLastFramesWritten); return mLastFramesWritten; } int64_t AudioStreamInternalCapture::getFramesRead() { int64_t frames = mAudioEndpoint.getDataReadCounter() + mFramesOffsetFromService; - //ALOGD("AudioStreamInternalCapture::getFramesRead() returns %lld", (long long)frames); + //ALOGD("getFramesRead() returns %lld", (long long)frames); return frames; } @@ -230,8 +232,7 @@ void *AudioStreamInternalCapture::callbackLoop() { aaudio_result_t result = AAUDIO_OK; aaudio_data_callback_result_t callbackResult = AAUDIO_CALLBACK_RESULT_CONTINUE; - AAudioStream_dataCallback appCallback = getDataCallbackProc(); - if (appCallback == nullptr) return NULL; + if (!isDataCallbackSet()) return NULL; // result might be a frame count while (mCallbackEnabled.load() && isActive() && (result >= 0)) { @@ -242,35 +243,25 @@ // This is a BLOCKING READ! result = read(mCallbackBuffer, mCallbackFrames, timeoutNanos); if ((result != mCallbackFrames)) { - ALOGE("AudioStreamInternalCapture(): callbackLoop: read() returned %d", result); + ALOGE("callbackLoop: read() returned %d", result); if (result >= 0) { // Only read some of the frames requested. Must have timed out. result = AAUDIO_ERROR_TIMEOUT; } - AAudioStream_errorCallback errorCallback = getErrorCallbackProc(); - if (errorCallback != nullptr) { - (*errorCallback)( - (AAudioStream *) this, - getErrorCallbackUserData(), - result); - } + maybeCallErrorCallback(result); break; } // Call application using the AAudio callback interface. - callbackResult = (*appCallback)( - (AAudioStream *) this, - getDataCallbackUserData(), - mCallbackBuffer, - mCallbackFrames); + callbackResult = maybeCallDataCallback(mCallbackBuffer, mCallbackFrames); if (callbackResult == AAUDIO_CALLBACK_RESULT_STOP) { - ALOGD("AudioStreamInternalCapture(): callback returned AAUDIO_CALLBACK_RESULT_STOP"); + ALOGD("callback returned AAUDIO_CALLBACK_RESULT_STOP"); break; } } - ALOGD("AudioStreamInternalCapture(): callbackLoop() exiting, result = %d, isActive() = %d", + ALOGD("callbackLoop() exiting, result = %d, isActive() = %d", result, (int) isActive()); return NULL; }
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp index 1e02eee..795ba2c 100644 --- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp +++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -14,7 +14,8 @@ * limitations under the License. */ -#define LOG_TAG (mInService ? "AAudioService" : "AAudio") +#define LOG_TAG (mInService ? "AudioStreamInternalPlay_Service" \ + : "AudioStreamInternalPlay_Client") //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -37,12 +38,26 @@ AudioStreamInternalPlay::~AudioStreamInternalPlay() {} +constexpr int kRampMSec = 10; // time to apply a change in volume -aaudio_result_t AudioStreamInternalPlay::requestPauseInternal() +aaudio_result_t AudioStreamInternalPlay::open(const AudioStreamBuilder &builder) { + aaudio_result_t result = AudioStreamInternal::open(builder); + if (result == AAUDIO_OK) { + // Sample rate is constrained to common values by now and should not overflow. + int32_t numFrames = kRampMSec * getSampleRate() / AAUDIO_MILLIS_PER_SECOND; + mVolumeRamp.setLengthInFrames(numFrames); + } + return result; +} + +aaudio_result_t AudioStreamInternalPlay::requestPause() { + aaudio_result_t result = stopCallback(); + if (result != AAUDIO_OK) { + return result; + } if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) { - ALOGE("AudioStreamInternal::requestPauseInternal() mServiceStreamHandle invalid = 0x%08X", - mServiceStreamHandle); + ALOGE("%s() mServiceStreamHandle invalid", __func__); return AAUDIO_ERROR_INVALID_STATE; } @@ -52,20 +67,9 @@ return mServiceInterface.pauseStream(mServiceStreamHandle); } -aaudio_result_t AudioStreamInternalPlay::requestPause() -{ - aaudio_result_t result = stopCallback(); - if (result != AAUDIO_OK) { - return result; - } - result = requestPauseInternal(); - return result; -} - aaudio_result_t AudioStreamInternalPlay::requestFlush() { if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) { - ALOGE("AudioStreamInternal::requestFlush() mServiceStreamHandle invalid = 0x%08X", - mServiceStreamHandle); + ALOGE("%s() mServiceStreamHandle invalid", __func__); return AAUDIO_ERROR_INVALID_STATE; } @@ -80,7 +84,7 @@ // Bump offset so caller does not see the retrograde motion in getFramesRead(). int64_t offset = writeCounter - readCounter; mFramesOffsetFromService += offset; - ALOGD("advanceClientToMatchServerPosition() readN = %lld, writeN = %lld, offset = %lld", + ALOGV("%s() readN = %lld, writeN = %lld, offset = %lld", __func__, (long long)readCounter, (long long)writeCounter, (long long)mFramesOffsetFromService); // Force writeCounter to match readCounter. @@ -94,9 +98,7 @@ // Write the data, block if needed and timeoutMillis > 0 aaudio_result_t AudioStreamInternalPlay::write(const void *buffer, int32_t numFrames, - int64_t timeoutNanoseconds) - -{ + int64_t timeoutNanoseconds) { return processData((void *)buffer, numFrames, timeoutNanoseconds); } @@ -115,7 +117,7 @@ // Still haven't got any timestamps from server. // Keep waiting until we get some valid timestamps then start writing to the // current buffer position. - ALOGD("processDataNow() wait for valid timestamps"); + ALOGV("%s() wait for valid timestamps", __func__); // Sleep very briefly and hope we get a timestamp soon. *wakeTimePtr = currentNanoTime + (2000 * AAUDIO_NANOS_PER_MICROSECOND); ATRACE_END(); @@ -139,7 +141,8 @@ } // If the read index passed the write index then consider it an underrun. - if (mAudioEndpoint.getFullFramesAvailable() < 0) { + // For shared streams, the xRunCount is passed up from the service. + if (mAudioEndpoint.isFreeRunning() && mAudioEndpoint.getFullFramesAvailable() < 0) { mXRunCount++; if (ATRACE_ENABLED()) { ATRACE_INT("aaUnderRuns", mXRunCount); @@ -196,10 +199,8 @@ aaudio_result_t AudioStreamInternalPlay::writeNowWithConversion(const void *buffer, int32_t numFrames) { - // ALOGD("AudioStreamInternal::writeNowWithConversion(%p, %d)", - // buffer, numFrames); WrappingBuffer wrappingBuffer; - uint8_t *source = (uint8_t *) buffer; + uint8_t *byteBuffer = (uint8_t *) buffer; int32_t framesLeft = numFrames; mAudioEndpoint.getEmptyFramesAvailable(&wrappingBuffer); @@ -213,70 +214,26 @@ if (framesToWrite > framesAvailable) { framesToWrite = framesAvailable; } + int32_t numBytes = getBytesPerFrame() * framesToWrite; - int32_t numSamples = framesToWrite * getSamplesPerFrame(); // Data conversion. float levelFrom; float levelTo; - bool ramping = mVolumeRamp.nextSegment(framesToWrite * getSamplesPerFrame(), - &levelFrom, &levelTo); - // The formats are validated when the stream is opened so we do not have to - // check for illegal combinations here. - // TODO factor this out into a utility function - if (getFormat() == AAUDIO_FORMAT_PCM_FLOAT) { - if (mDeviceFormat == AAUDIO_FORMAT_PCM_FLOAT) { - AAudio_linearRamp( - (const float *) source, - (float *) wrappingBuffer.data[partIndex], - framesToWrite, - getSamplesPerFrame(), - levelFrom, - levelTo); - } else if (mDeviceFormat == AAUDIO_FORMAT_PCM_I16) { - if (ramping) { - AAudioConvert_floatToPcm16( - (const float *) source, - (int16_t *) wrappingBuffer.data[partIndex], - framesToWrite, - getSamplesPerFrame(), - levelFrom, - levelTo); - } else { - AAudioConvert_floatToPcm16( - (const float *) source, - (int16_t *) wrappingBuffer.data[partIndex], - numSamples, - levelTo); - } - } - } else if (getFormat() == AAUDIO_FORMAT_PCM_I16) { - if (mDeviceFormat == AAUDIO_FORMAT_PCM_FLOAT) { - if (ramping) { - AAudioConvert_pcm16ToFloat( - (const int16_t *) source, - (float *) wrappingBuffer.data[partIndex], - framesToWrite, - getSamplesPerFrame(), - levelFrom, - levelTo); - } else { - AAudioConvert_pcm16ToFloat( - (const int16_t *) source, - (float *) wrappingBuffer.data[partIndex], - numSamples, - levelTo); - } - } else if (mDeviceFormat == AAUDIO_FORMAT_PCM_I16) { - AAudio_linearRamp( - (const int16_t *) source, - (int16_t *) wrappingBuffer.data[partIndex], - framesToWrite, - getSamplesPerFrame(), - levelFrom, - levelTo); - } - } - source += numBytes; + mVolumeRamp.nextSegment(framesToWrite, &levelFrom, &levelTo); + + AAudioDataConverter::FormattedData source( + (void *)byteBuffer, + getFormat(), + getSamplesPerFrame()); + AAudioDataConverter::FormattedData destination( + wrappingBuffer.data[partIndex], + getDeviceFormat(), + getDeviceChannelCount()); + + AAudioDataConverter::convert(source, destination, framesToWrite, + levelFrom, levelTo); + + byteBuffer += numBytes; framesLeft -= framesToWrite; } else { break; @@ -286,7 +243,6 @@ int32_t framesWritten = numFrames - framesLeft; mAudioEndpoint.advanceWriteIndex(framesWritten); - // ALOGD("AudioStreamInternal::writeNowWithConversion() returns %d", framesWritten); return framesWritten; } @@ -305,7 +261,6 @@ } else { mLastFramesRead = framesRead; } - //ALOGD("AudioStreamInternalPlay::getFramesRead() returns %lld", (long long)framesRead); return framesRead; } @@ -313,60 +268,51 @@ { int64_t framesWritten = mAudioEndpoint.getDataWriteCounter() + mFramesOffsetFromService; - //ALOGD("AudioStreamInternalPlay::getFramesWritten() returns %lld", (long long)framesWritten); return framesWritten; } // Render audio in the application callback and then write the data to the stream. void *AudioStreamInternalPlay::callbackLoop() { + ALOGD("%s() entering >>>>>>>>>>>>>>>", __func__); aaudio_result_t result = AAUDIO_OK; aaudio_data_callback_result_t callbackResult = AAUDIO_CALLBACK_RESULT_CONTINUE; - AAudioStream_dataCallback appCallback = getDataCallbackProc(); - if (appCallback == nullptr) return NULL; + if (!isDataCallbackSet()) return NULL; int64_t timeoutNanos = calculateReasonableTimeout(mCallbackFrames); // result might be a frame count while (mCallbackEnabled.load() && isActive() && (result >= 0)) { // Call application using the AAudio callback interface. - callbackResult = (*appCallback)( - (AAudioStream *) this, - getDataCallbackUserData(), - mCallbackBuffer, - mCallbackFrames); + callbackResult = maybeCallDataCallback(mCallbackBuffer, mCallbackFrames); if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) { // Write audio data to stream. This is a BLOCKING WRITE! result = write(mCallbackBuffer, mCallbackFrames, timeoutNanos); if ((result != mCallbackFrames)) { - ALOGE("AudioStreamInternalPlay(): callbackLoop: write() returned %d", result); if (result >= 0) { // Only wrote some of the frames requested. Must have timed out. result = AAUDIO_ERROR_TIMEOUT; } - AAudioStream_errorCallback errorCallback = getErrorCallbackProc(); - if (errorCallback != nullptr) { - (*errorCallback)( - (AAudioStream *) this, - getErrorCallbackUserData(), - result); - } + maybeCallErrorCallback(result); break; } } else if (callbackResult == AAUDIO_CALLBACK_RESULT_STOP) { - ALOGD("AudioStreamInternalPlay(): callback returned AAUDIO_CALLBACK_RESULT_STOP"); + ALOGV("%s(): callback returned AAUDIO_CALLBACK_RESULT_STOP", __func__); break; } } - ALOGD("AudioStreamInternalPlay(): callbackLoop() exiting, result = %d, isActive() = %d", - result, (int) isActive()); + ALOGD("%s() exiting, result = %d, isActive() = %d <<<<<<<<<<<<<<", + __func__, result, (int) isActive()); return NULL; } //------------------------------------------------------------------------------ // Implementation of PlayerBase status_t AudioStreamInternalPlay::doSetVolume() { - mVolumeRamp.setTarget(mStreamVolume * getDuckAndMuteVolume()); + float combinedVolume = mStreamVolume * getDuckAndMuteVolume(); + ALOGD("%s() mStreamVolume * duckAndMuteVolume = %f * %f = %f", + __func__, mStreamVolume, getDuckAndMuteVolume(), combinedVolume); + mVolumeRamp.setTarget(combinedVolume); return android::NO_ERROR; }
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h index d5c1b1e..977a909 100644 --- a/media/libaaudio/src/client/AudioStreamInternalPlay.h +++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -33,10 +33,22 @@ AudioStreamInternalPlay(AAudioServiceInterface &serviceInterface, bool inService = false); virtual ~AudioStreamInternalPlay(); + aaudio_result_t open(const AudioStreamBuilder &builder) override; + aaudio_result_t requestPause() override; aaudio_result_t requestFlush() override; + bool isFlushSupported() const override { + // Only implement FLUSH for OUTPUT streams. + return true; + } + + bool isPauseSupported() const override { + // Only implement PAUSE for OUTPUT streams. + return true; + } + aaudio_result_t write(const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) override; @@ -52,8 +64,6 @@ protected: - aaudio_result_t requestPauseInternal(); - void advanceClientToMatchServerPosition() override; void onFlushFromServer() override;
diff --git a/media/libaaudio/src/client/IsochronousClockModel.cpp b/media/libaaudio/src/client/IsochronousClockModel.cpp index bac69f1..95b52be 100644 --- a/media/libaaudio/src/client/IsochronousClockModel.cpp +++ b/media/libaaudio/src/client/IsochronousClockModel.cpp
@@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "AAudio" +#define LOG_TAG "IsochronousClockModel" //#define LOG_NDEBUG 0 #include <log/log.h> @@ -41,20 +41,20 @@ } void IsochronousClockModel::setPositionAndTime(int64_t framePosition, int64_t nanoTime) { - ALOGV("IsochronousClockModel::setPositionAndTime(%lld, %lld)", + ALOGV("setPositionAndTime(%lld, %lld)", (long long) framePosition, (long long) nanoTime); mMarkerFramePosition = framePosition; mMarkerNanoTime = nanoTime; } void IsochronousClockModel::start(int64_t nanoTime) { - ALOGV("IsochronousClockModel::start(nanos = %lld)\n", (long long) nanoTime); + ALOGV("start(nanos = %lld)\n", (long long) nanoTime); mMarkerNanoTime = nanoTime; mState = STATE_STARTING; } void IsochronousClockModel::stop(int64_t nanoTime) { - ALOGV("IsochronousClockModel::stop(nanos = %lld)\n", (long long) nanoTime); + ALOGV("stop(nanos = %lld)\n", (long long) nanoTime); setPositionAndTime(convertTimeToPosition(nanoTime), nanoTime); // TODO should we set position? mState = STATE_STOPPED; @@ -156,7 +156,7 @@ int64_t framesDelta = nextBurstPosition - mMarkerFramePosition; int64_t nanosDelta = convertDeltaPositionToTime(framesDelta); int64_t time = mMarkerNanoTime + nanosDelta; -// ALOGD("IsochronousClockModel::convertPositionToTime: pos = %llu --> time = %llu", +// ALOGD("convertPositionToTime: pos = %llu --> time = %llu", // (unsigned long long)framePosition, // (unsigned long long)time); return time; @@ -171,19 +171,19 @@ int64_t nextBurstPosition = mMarkerFramePosition + framesDelta; int64_t nextBurstIndex = nextBurstPosition / mFramesPerBurst; int64_t position = nextBurstIndex * mFramesPerBurst; -// ALOGD("IsochronousClockModel::convertTimeToPosition: time = %llu --> pos = %llu", +// ALOGD("convertTimeToPosition: time = %llu --> pos = %llu", // (unsigned long long)nanoTime, // (unsigned long long)position); -// ALOGD("IsochronousClockModel::convertTimeToPosition: framesDelta = %llu, mFramesPerBurst = %d", +// ALOGD("convertTimeToPosition: framesDelta = %llu, mFramesPerBurst = %d", // (long long) framesDelta, mFramesPerBurst); return position; } void IsochronousClockModel::dump() const { - ALOGD("IsochronousClockModel::mMarkerFramePosition = %lld", (long long) mMarkerFramePosition); - ALOGD("IsochronousClockModel::mMarkerNanoTime = %lld", (long long) mMarkerNanoTime); - ALOGD("IsochronousClockModel::mSampleRate = %6d", mSampleRate); - ALOGD("IsochronousClockModel::mFramesPerBurst = %6d", mFramesPerBurst); - ALOGD("IsochronousClockModel::mMaxLatenessInNanos = %6d", mMaxLatenessInNanos); - ALOGD("IsochronousClockModel::mState = %6d", mState); + ALOGD("mMarkerFramePosition = %lld", (long long) mMarkerFramePosition); + ALOGD("mMarkerNanoTime = %lld", (long long) mMarkerNanoTime); + ALOGD("mSampleRate = %6d", mSampleRate); + ALOGD("mFramesPerBurst = %6d", mFramesPerBurst); + ALOGD("mMaxLatenessInNanos = %6d", mMaxLatenessInNanos); + ALOGD("mState = %6d", mState); }
diff --git a/media/libaaudio/src/core/AAudioAudio.cpp b/media/libaaudio/src/core/AAudioAudio.cpp index 1eaee81..df0db79 100644 --- a/media/libaaudio/src/core/AAudioAudio.cpp +++ b/media/libaaudio/src/core/AAudioAudio.cpp
@@ -18,6 +18,8 @@ //#define LOG_NDEBUG 0 #include <utils/Log.h> +#include <inttypes.h> +#include <mutex> #include <time.h> #include <pthread.h> @@ -175,13 +177,38 @@ streamBuilder->setSharingMode(sharingMode); } +AAUDIO_API void AAudioStreamBuilder_setUsage(AAudioStreamBuilder* builder, + aaudio_usage_t usage) { + AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder); + streamBuilder->setUsage(usage); +} + +AAUDIO_API void AAudioStreamBuilder_setContentType(AAudioStreamBuilder* builder, + aaudio_content_type_t contentType) { + AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder); + streamBuilder->setContentType(contentType); +} + +AAUDIO_API void AAudioStreamBuilder_setInputPreset(AAudioStreamBuilder* builder, + aaudio_input_preset_t inputPreset) { + AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder); + streamBuilder->setInputPreset(inputPreset); +} + AAUDIO_API void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder* builder, - int32_t frames) + int32_t frames) { AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder); streamBuilder->setBufferCapacity(frames); } +AAUDIO_API void AAudioStreamBuilder_setSessionId(AAudioStreamBuilder* builder, + aaudio_session_id_t sessionId) +{ + AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder); + streamBuilder->setSessionId(sessionId); +} + AAUDIO_API void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder* builder, AAudioStream_dataCallback callback, void *userData) @@ -238,15 +265,26 @@ AAUDIO_API aaudio_result_t AAudioStream_close(AAudioStream* stream) { + aaudio_result_t result = AAUDIO_ERROR_NULL; AudioStream *audioStream = convertAAudioStreamToAudioStream(stream); - ALOGD("AAudioStream_close(%p)", stream); + ALOGD("AAudioStream_close(%p) called ---------------", stream); if (audioStream != nullptr) { - audioStream->close(); - audioStream->unregisterPlayerBase(); - delete audioStream; - return AAUDIO_OK; + result = audioStream->safeClose(); + // Close will only fail if called illegally, for example, from a callback. + // That would result in deleting an active stream, which would cause a crash. + if (result == AAUDIO_OK) { + audioStream->unregisterPlayerBase(); + delete audioStream; + } else { + ALOGW("%s attempt to close failed. Close it from another thread.", __func__); + } } - return AAUDIO_ERROR_NULL; + // We're potentially freeing `stream` above, so its use here makes some + // static analysis tools unhappy. Casting to uintptr_t helps assure + // said tools that we're not doing anything bad here. + ALOGD("AAudioStream_close(%#" PRIxPTR ") returned %d ---------", + reinterpret_cast<uintptr_t>(stream), result); + return result; } AAUDIO_API aaudio_result_t AAudioStream_requestStart(AAudioStream* stream) @@ -269,7 +307,7 @@ { AudioStream *audioStream = convertAAudioStreamToAudioStream(stream); ALOGD("AAudioStream_requestFlush(%p)", stream); - return audioStream->requestFlush(); + return audioStream->safeFlush(); } AAUDIO_API aaudio_result_t AAudioStream_requestStop(AAudioStream* stream) @@ -324,7 +362,7 @@ } // Don't allow writes when playing with a callback. - if (audioStream->getDataCallbackProc() != nullptr && audioStream->isActive()) { + if (audioStream->isDataCallbackActive()) { ALOGE("Cannot write to a callback stream when running."); return AAUDIO_ERROR_INVALID_STATE; } @@ -434,6 +472,30 @@ return audioStream->getSharingMode(); } +AAUDIO_API aaudio_usage_t AAudioStream_getUsage(AAudioStream* stream) +{ + AudioStream *audioStream = convertAAudioStreamToAudioStream(stream); + return audioStream->getUsage(); +} + +AAUDIO_API aaudio_content_type_t AAudioStream_getContentType(AAudioStream* stream) +{ + AudioStream *audioStream = convertAAudioStreamToAudioStream(stream); + return audioStream->getContentType(); +} + +AAUDIO_API aaudio_input_preset_t AAudioStream_getInputPreset(AAudioStream* stream) +{ + AudioStream *audioStream = convertAAudioStreamToAudioStream(stream); + return audioStream->getInputPreset(); +} + +AAUDIO_API int32_t AAudioStream_getSessionId(AAudioStream* stream) +{ + AudioStream *audioStream = convertAAudioStreamToAudioStream(stream); + return audioStream->getSessionId(); +} + AAUDIO_API int64_t AAudioStream_getFramesWritten(AAudioStream* stream) { AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.cpp b/media/libaaudio/src/core/AAudioStreamParameters.cpp index 82445e7..d56701b 100644 --- a/media/libaaudio/src/core/AAudioStreamParameters.cpp +++ b/media/libaaudio/src/core/AAudioStreamParameters.cpp
@@ -15,9 +15,9 @@ */ -#define LOG_TAG "AAudio" +#define LOG_TAG "AAudioStreamParameters" #include <utils/Log.h> -#include <hardware/audio.h> +#include <system/audio.h> #include "AAudioStreamParameters.h" @@ -38,30 +38,43 @@ mSamplesPerFrame = other.mSamplesPerFrame; mSampleRate = other.mSampleRate; mDeviceId = other.mDeviceId; + mSessionId = other.mSessionId; mSharingMode = other.mSharingMode; mAudioFormat = other.mAudioFormat; mDirection = other.mDirection; mBufferCapacity = other.mBufferCapacity; + mUsage = other.mUsage; + mContentType = other.mContentType; + mInputPreset = other.mInputPreset; } aaudio_result_t AAudioStreamParameters::validate() const { if (mSamplesPerFrame != AAUDIO_UNSPECIFIED && (mSamplesPerFrame < SAMPLES_PER_FRAME_MIN || mSamplesPerFrame > SAMPLES_PER_FRAME_MAX)) { - ALOGE("AAudioStreamParameters: channelCount out of range = %d", mSamplesPerFrame); + ALOGE("channelCount out of range = %d", mSamplesPerFrame); return AAUDIO_ERROR_OUT_OF_RANGE; } if (mDeviceId < 0) { - ALOGE("AAudioStreamParameters: deviceId out of range = %d", mDeviceId); + ALOGE("deviceId out of range = %d", mDeviceId); return AAUDIO_ERROR_OUT_OF_RANGE; } + // All Session ID values are legal. + switch (mSessionId) { + case AAUDIO_SESSION_ID_NONE: + case AAUDIO_SESSION_ID_ALLOCATE: + break; + default: + break; + } + switch (mSharingMode) { case AAUDIO_SHARING_MODE_EXCLUSIVE: case AAUDIO_SHARING_MODE_SHARED: break; default: - ALOGE("AAudioStreamParameters: illegal sharingMode = %d", mSharingMode); + ALOGE("illegal sharingMode = %d", mSharingMode); return AAUDIO_ERROR_ILLEGAL_ARGUMENT; // break; } @@ -72,19 +85,19 @@ case AAUDIO_FORMAT_PCM_FLOAT: break; // valid default: - ALOGE("AAudioStreamParameters: audioFormat not valid = %d", mAudioFormat); + ALOGE("audioFormat not valid = %d", mAudioFormat); return AAUDIO_ERROR_INVALID_FORMAT; // break; } if (mSampleRate != AAUDIO_UNSPECIFIED && (mSampleRate < SAMPLE_RATE_HZ_MIN || mSampleRate > SAMPLE_RATE_HZ_MAX)) { - ALOGE("AAudioStreamParameters: sampleRate out of range = %d", mSampleRate); + ALOGE("sampleRate out of range = %d", mSampleRate); return AAUDIO_ERROR_INVALID_RATE; } if (mBufferCapacity < 0) { - ALOGE("AAudioStreamParameters: bufferCapacity out of range = %d", mBufferCapacity); + ALOGE("bufferCapacity out of range = %d", mBufferCapacity); return AAUDIO_ERROR_OUT_OF_RANGE; } @@ -93,7 +106,55 @@ case AAUDIO_DIRECTION_OUTPUT: break; // valid default: - ALOGE("AAudioStreamParameters: direction not valid = %d", mDirection); + ALOGE("direction not valid = %d", mDirection); + return AAUDIO_ERROR_ILLEGAL_ARGUMENT; + // break; + } + + switch (mUsage) { + case AAUDIO_UNSPECIFIED: + case AAUDIO_USAGE_MEDIA: + case AAUDIO_USAGE_VOICE_COMMUNICATION: + case AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING: + case AAUDIO_USAGE_ALARM: + case AAUDIO_USAGE_NOTIFICATION: + case AAUDIO_USAGE_NOTIFICATION_RINGTONE: + case AAUDIO_USAGE_NOTIFICATION_EVENT: + case AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY: + case AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: + case AAUDIO_USAGE_ASSISTANCE_SONIFICATION: + case AAUDIO_USAGE_GAME: + case AAUDIO_USAGE_ASSISTANT: + break; // valid + default: + ALOGE("usage not valid = %d", mUsage); + return AAUDIO_ERROR_ILLEGAL_ARGUMENT; + // break; + } + + switch (mContentType) { + case AAUDIO_UNSPECIFIED: + case AAUDIO_CONTENT_TYPE_MUSIC: + case AAUDIO_CONTENT_TYPE_MOVIE: + case AAUDIO_CONTENT_TYPE_SONIFICATION: + case AAUDIO_CONTENT_TYPE_SPEECH: + break; // valid + default: + ALOGE("content type not valid = %d", mContentType); + return AAUDIO_ERROR_ILLEGAL_ARGUMENT; + // break; + } + + switch (mInputPreset) { + case AAUDIO_UNSPECIFIED: + case AAUDIO_INPUT_PRESET_GENERIC: + case AAUDIO_INPUT_PRESET_CAMCORDER: + case AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION: + case AAUDIO_INPUT_PRESET_VOICE_RECOGNITION: + case AAUDIO_INPUT_PRESET_UNPROCESSED: + break; // valid + default: + ALOGE("input preset not valid = %d", mInputPreset); return AAUDIO_ERROR_ILLEGAL_ARGUMENT; // break; } @@ -102,12 +163,15 @@ } void AAudioStreamParameters::dump() const { - ALOGD("AAudioStreamParameters mDeviceId = %d", mDeviceId); - ALOGD("AAudioStreamParameters mSampleRate = %d", mSampleRate); - ALOGD("AAudioStreamParameters mSamplesPerFrame = %d", mSamplesPerFrame); - ALOGD("AAudioStreamParameters mSharingMode = %d", (int)mSharingMode); - ALOGD("AAudioStreamParameters mAudioFormat = %d", (int)mAudioFormat); - ALOGD("AAudioStreamParameters mDirection = %d", mDirection); - ALOGD("AAudioStreamParameters mBufferCapacity = %d", mBufferCapacity); + ALOGD("mDeviceId = %6d", mDeviceId); + ALOGD("mSessionId = %6d", mSessionId); + ALOGD("mSampleRate = %6d", mSampleRate); + ALOGD("mSamplesPerFrame = %6d", mSamplesPerFrame); + ALOGD("mSharingMode = %6d", (int)mSharingMode); + ALOGD("mAudioFormat = %6d", (int)mAudioFormat); + ALOGD("mDirection = %6d", mDirection); + ALOGD("mBufferCapacity = %6d", mBufferCapacity); + ALOGD("mUsage = %6d", mUsage); + ALOGD("mContentType = %6d", mContentType); + ALOGD("mInputPreset = %6d", mInputPreset); } -
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.h b/media/libaaudio/src/core/AAudioStreamParameters.h index 5e67c93..ce5dacd 100644 --- a/media/libaaudio/src/core/AAudioStreamParameters.h +++ b/media/libaaudio/src/core/AAudioStreamParameters.h
@@ -88,6 +88,38 @@ mDirection = direction; } + aaudio_usage_t getUsage() const { + return mUsage; + } + + void setUsage(aaudio_usage_t usage) { + mUsage = usage; + } + + aaudio_content_type_t getContentType() const { + return mContentType; + } + + void setContentType(aaudio_content_type_t contentType) { + mContentType = contentType; + } + + aaudio_input_preset_t getInputPreset() const { + return mInputPreset; + } + + void setInputPreset(aaudio_input_preset_t inputPreset) { + mInputPreset = inputPreset; + } + + aaudio_session_id_t getSessionId() const { + return mSessionId; + } + + void setSessionId(aaudio_session_id_t sessionId) { + mSessionId = sessionId; + } + int32_t calculateBytesPerFrame() const { return getSamplesPerFrame() * AAudioConvert_formatToSizeInBytes(getFormat()); } @@ -109,7 +141,11 @@ aaudio_sharing_mode_t mSharingMode = AAUDIO_SHARING_MODE_SHARED; aaudio_format_t mAudioFormat = AAUDIO_FORMAT_UNSPECIFIED; aaudio_direction_t mDirection = AAUDIO_DIRECTION_OUTPUT; + aaudio_usage_t mUsage = AAUDIO_UNSPECIFIED; + aaudio_content_type_t mContentType = AAUDIO_UNSPECIFIED; + aaudio_input_preset_t mInputPreset = AAUDIO_UNSPECIFIED; int32_t mBufferCapacity = AAUDIO_UNSPECIFIED; + aaudio_session_id_t mSessionId = AAUDIO_SESSION_ID_NONE; }; } /* namespace aaudio */
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp index 8dcc37a..358021b 100644 --- a/media/libaaudio/src/core/AudioStream.cpp +++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -43,7 +43,7 @@ LOG_ALWAYS_FATAL_IF(!(getState() == AAUDIO_STREAM_STATE_CLOSED || getState() == AAUDIO_STREAM_STATE_UNINITIALIZED || getState() == AAUDIO_STREAM_STATE_DISCONNECTED), - "aaudio stream still in use, state = %s", + "~AudioStream() - still in use, state = %s", AAudio_convertStreamStateToText(getState())); mPlayerBase->clearParentReference(); // remove reference to this AudioStream @@ -74,15 +74,28 @@ } // Copy parameters from the Builder because the Builder may be deleted after this call. + // TODO AudioStream should be a subclass of AudioStreamParameters mSamplesPerFrame = builder.getSamplesPerFrame(); mSampleRate = builder.getSampleRate(); mDeviceId = builder.getDeviceId(); mFormat = builder.getFormat(); mSharingMode = builder.getSharingMode(); mSharingModeMatchRequired = builder.isSharingModeMatchRequired(); - mPerformanceMode = builder.getPerformanceMode(); + mUsage = builder.getUsage(); + if (mUsage == AAUDIO_UNSPECIFIED) { + mUsage = AAUDIO_USAGE_MEDIA; + } + mContentType = builder.getContentType(); + if (mContentType == AAUDIO_UNSPECIFIED) { + mContentType = AAUDIO_CONTENT_TYPE_MUSIC; + } + mInputPreset = builder.getInputPreset(); + if (mInputPreset == AAUDIO_UNSPECIFIED) { + mInputPreset = AAUDIO_INPUT_PRESET_VOICE_RECOGNITION; + } + // callbacks mFramesPerDataCallback = builder.getFramesPerDataCallback(); mDataCallbackProc = builder.getDataCallbackProc(); @@ -91,18 +104,159 @@ mErrorCallbackUserData = builder.getErrorCallbackUserData(); // This is very helpful for debugging in the future. Please leave it in. - ALOGI("AudioStream::open() rate = %d, channels = %d, format = %d, sharing = %s, dir = %s", + ALOGI("open() rate = %d, channels = %d, format = %d, sharing = %s, dir = %s", mSampleRate, mSamplesPerFrame, mFormat, AudioStream_convertSharingModeToShortText(mSharingMode), (getDirection() == AAUDIO_DIRECTION_OUTPUT) ? "OUTPUT" : "INPUT"); - ALOGI("AudioStream::open() device = %d, perfMode = %d, callback: %s with frames = %d", - mDeviceId, mPerformanceMode, - (mDataCallbackProc == nullptr ? "OFF" : "ON"), + ALOGI("open() device = %d, sessionId = %d, perfMode = %d, callback: %s with frames = %d", + mDeviceId, + mSessionId, + mPerformanceMode, + (isDataCallbackSet() ? "ON" : "OFF"), mFramesPerDataCallback); + ALOGI("open() usage = %d, contentType = %d, inputPreset = %d", + mUsage, mContentType, mInputPreset); return AAUDIO_OK; } +aaudio_result_t AudioStream::safeStart() { + std::lock_guard<std::mutex> lock(mStreamLock); + if (collidesWithCallback()) { + ALOGE("%s cannot be called from a callback!", __func__); + return AAUDIO_ERROR_INVALID_STATE; + } + return requestStart(); +} + +aaudio_result_t AudioStream::safePause() { + if (!isPauseSupported()) { + return AAUDIO_ERROR_UNIMPLEMENTED; + } + + std::lock_guard<std::mutex> lock(mStreamLock); + if (collidesWithCallback()) { + ALOGE("%s cannot be called from a callback!", __func__); + return AAUDIO_ERROR_INVALID_STATE; + } + + switch (getState()) { + // Proceed with pausing. + case AAUDIO_STREAM_STATE_STARTING: + case AAUDIO_STREAM_STATE_STARTED: + case AAUDIO_STREAM_STATE_DISCONNECTED: + break; + + // Transition from one inactive state to another. + case AAUDIO_STREAM_STATE_OPEN: + case AAUDIO_STREAM_STATE_STOPPED: + case AAUDIO_STREAM_STATE_FLUSHED: + setState(AAUDIO_STREAM_STATE_PAUSED); + return AAUDIO_OK; + + // Redundant? + case AAUDIO_STREAM_STATE_PAUSING: + case AAUDIO_STREAM_STATE_PAUSED: + return AAUDIO_OK; + + // Don't interfere with transitional states or when closed. + case AAUDIO_STREAM_STATE_STOPPING: + case AAUDIO_STREAM_STATE_FLUSHING: + case AAUDIO_STREAM_STATE_CLOSING: + case AAUDIO_STREAM_STATE_CLOSED: + default: + ALOGW("safePause() stream not running, state = %s", + AAudio_convertStreamStateToText(getState())); + return AAUDIO_ERROR_INVALID_STATE; + } + + return requestPause(); +} + +aaudio_result_t AudioStream::safeFlush() { + if (!isFlushSupported()) { + ALOGE("flush not supported for this stream"); + return AAUDIO_ERROR_UNIMPLEMENTED; + } + + std::lock_guard<std::mutex> lock(mStreamLock); + if (collidesWithCallback()) { + ALOGE("stream cannot be flushed from a callback!"); + return AAUDIO_ERROR_INVALID_STATE; + } + + aaudio_result_t result = AAudio_isFlushAllowed(getState()); + if (result != AAUDIO_OK) { + return result; + } + + return requestFlush(); +} + +aaudio_result_t AudioStream::safeStop() { + std::lock_guard<std::mutex> lock(mStreamLock); + if (collidesWithCallback()) { + ALOGE("stream cannot be stopped from a callback!"); + return AAUDIO_ERROR_INVALID_STATE; + } + + switch (getState()) { + // Proceed with stopping. + case AAUDIO_STREAM_STATE_STARTING: + case AAUDIO_STREAM_STATE_STARTED: + case AAUDIO_STREAM_STATE_DISCONNECTED: + break; + + // Transition from one inactive state to another. + case AAUDIO_STREAM_STATE_OPEN: + case AAUDIO_STREAM_STATE_PAUSED: + case AAUDIO_STREAM_STATE_FLUSHED: + setState(AAUDIO_STREAM_STATE_STOPPED); + return AAUDIO_OK; + + // Redundant? + case AAUDIO_STREAM_STATE_STOPPING: + case AAUDIO_STREAM_STATE_STOPPED: + return AAUDIO_OK; + + // Don't interfere with transitional states or when closed. + case AAUDIO_STREAM_STATE_PAUSING: + case AAUDIO_STREAM_STATE_FLUSHING: + case AAUDIO_STREAM_STATE_CLOSING: + case AAUDIO_STREAM_STATE_CLOSED: + default: + ALOGW("requestStop() stream not running, state = %s", + AAudio_convertStreamStateToText(getState())); + return AAUDIO_ERROR_INVALID_STATE; + } + + return requestStop(); +} + +aaudio_result_t AudioStream::safeClose() { + std::lock_guard<std::mutex> lock(mStreamLock); + if (collidesWithCallback()) { + ALOGE("%s cannot be called from a callback!", __func__); + return AAUDIO_ERROR_INVALID_STATE; + } + return close(); +} + +void AudioStream::setState(aaudio_stream_state_t state) { + ALOGV("%s(%p) from %d to %d", __func__, this, mState, state); + // CLOSED is a final state + if (mState == AAUDIO_STREAM_STATE_CLOSED) { + ALOGE("%s(%p) tried to set to %d but already CLOSED", __func__, this, state); + + // Once DISCONNECTED, we can only move to CLOSED state. + } else if (mState == AAUDIO_STREAM_STATE_DISCONNECTED + && state != AAUDIO_STREAM_STATE_CLOSED) { + ALOGE("%s(%p) tried to set to %d but already DISCONNECTED", __func__, this, state); + + } else { + mState = state; + } +} aaudio_result_t AudioStream::waitForStateChange(aaudio_stream_state_t currentState, aaudio_stream_state_t *nextState, @@ -163,7 +317,7 @@ void* threadArg) { if (mHasThread) { - ALOGE("AudioStream::createThread() - mHasThread already true"); + ALOGE("createThread() - mHasThread already true"); return AAUDIO_ERROR_INVALID_STATE; } if (threadProc == nullptr) { @@ -175,8 +329,22 @@ setPeriodNanoseconds(periodNanoseconds); int err = pthread_create(&mThread, nullptr, AudioStream_internalThreadProc, this); if (err != 0) { - return AAudioConvert_androidToAAudioResult(-errno); + android::status_t status = -errno; + ALOGE("createThread() - pthread_create() failed, %d", status); + return AAudioConvert_androidToAAudioResult(status); } else { + // TODO Use AAudioThread or maybe AndroidThread + // Name the thread with an increasing index, "AAudio_#", for debugging. + static std::atomic<uint32_t> nextThreadIndex{1}; + char name[16]; // max length for a pthread_name + uint32_t index = nextThreadIndex++; + // Wrap the index so that we do not hit the 16 char limit + // and to avoid hard-to-read large numbers. + index = index % 100000; // arbitrary + snprintf(name, sizeof(name), "AAudio_%u", index); + err = pthread_setname_np(mThread, name); + ALOGW_IF((err != 0), "Could not set name of AAudio thread. err = %d", err); + mHasThread = true; return AAUDIO_OK; } @@ -185,7 +353,7 @@ aaudio_result_t AudioStream::joinThread(void** returnArg, int64_t timeoutNanoseconds) { if (!mHasThread) { - ALOGE("AudioStream::joinThread() - but has no thread"); + ALOGE("joinThread() - but has no thread"); return AAUDIO_ERROR_INVALID_STATE; } #if 0 @@ -199,6 +367,57 @@ return err ? AAudioConvert_androidToAAudioResult(-errno) : mThreadRegistrationResult; } +aaudio_data_callback_result_t AudioStream::maybeCallDataCallback(void *audioData, + int32_t numFrames) { + aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_STOP; + AAudioStream_dataCallback dataCallback = getDataCallbackProc(); + if (dataCallback != nullptr) { + // Store thread ID of caller to detect stop() and close() calls from callback. + pid_t expected = CALLBACK_THREAD_NONE; + if (mDataCallbackThread.compare_exchange_strong(expected, gettid())) { + result = (*dataCallback)( + (AAudioStream *) this, + getDataCallbackUserData(), + audioData, + numFrames); + mDataCallbackThread.store(CALLBACK_THREAD_NONE); + } else { + ALOGW("%s() data callback already running!", __func__); + } + } + return result; +} + +void AudioStream::maybeCallErrorCallback(aaudio_result_t result) { + AAudioStream_errorCallback errorCallback = getErrorCallbackProc(); + if (errorCallback != nullptr) { + // Store thread ID of caller to detect stop() and close() calls from callback. + pid_t expected = CALLBACK_THREAD_NONE; + if (mErrorCallbackThread.compare_exchange_strong(expected, gettid())) { + (*errorCallback)( + (AAudioStream *) this, + getErrorCallbackUserData(), + result); + mErrorCallbackThread.store(CALLBACK_THREAD_NONE); + } else { + ALOGW("%s() error callback already running!", __func__); + } + } +} + +// Is this running on the same thread as a callback? +// Note: This cannot be implemented using a thread_local because that would +// require using a thread_local variable that is shared between streams. +// So a thread_local variable would prevent stopping or closing stream A from +// a callback on stream B, which is currently legal and not so terrible. +bool AudioStream::collidesWithCallback() const { + pid_t thisThread = gettid(); + // Compare the current thread ID with the thread ID of the callback + // threads to see it they match. If so then this code is being + // called from one of the stream callback functions. + return ((mErrorCallbackThread.load() == thisThread) + || (mDataCallbackThread.load() == thisThread)); +} #if AAUDIO_USE_VOLUME_SHAPER android::media::VolumeShaper::Status AudioStream::applyVolumeShaper( @@ -209,6 +428,12 @@ } #endif +void AudioStream::setDuckAndMuteVolume(float duckAndMuteVolume) { + ALOGD("%s() to %f", __func__, duckAndMuteVolume); + mDuckAndMuteVolume = duckAndMuteVolume; + doSetVolume(); // apply this change +} + AudioStream::MyPlayerBase::MyPlayerBase(AudioStream *parent) : mParent(parent) { } @@ -230,7 +455,6 @@ } } - void AudioStream::MyPlayerBase::destroy() { unregisterWithAudioManager(); }
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h index 34202d2..31b895c 100644 --- a/media/libaaudio/src/core/AudioStream.h +++ b/media/libaaudio/src/core/AudioStream.h
@@ -39,6 +39,8 @@ class AudioStreamBuilder; +constexpr pid_t CALLBACK_THREAD_NONE = 0; + /** * AAudio audio stream. */ @@ -49,14 +51,46 @@ virtual ~AudioStream(); + /** + * Lock a mutex and make sure we are not calling from a callback function. + * @return result of requestStart(); + */ + aaudio_result_t safeStart(); + + aaudio_result_t safePause(); + + aaudio_result_t safeFlush(); + + aaudio_result_t safeStop(); + + aaudio_result_t safeClose(); // =========== Begin ABSTRACT methods =========================== +protected: /* Asynchronous requests. * Use waitForStateChange() to wait for completion. */ virtual aaudio_result_t requestStart() = 0; + /** + * Check the state to see if Pause if currently legal. + * + * @param result pointer to return code + * @return true if OK to continue, if false then return result + */ + bool checkPauseStateTransition(aaudio_result_t *result); + + virtual bool isFlushSupported() const { + // Only implement FLUSH for OUTPUT streams. + return false; + } + + virtual bool isPauseSupported() const { + // Only implement PAUSE for OUTPUT streams. + return false; + } + virtual aaudio_result_t requestPause() { // Only implement this for OUTPUT streams. @@ -70,6 +104,7 @@ virtual aaudio_result_t requestStop() = 0; +public: virtual aaudio_result_t getTimestamp(clockid_t clockId, int64_t *framePosition, int64_t *timeNanoseconds) = 0; @@ -81,7 +116,6 @@ */ virtual aaudio_result_t updateStateMachine() = 0; - // =========== End ABSTRACT methods =========================== virtual aaudio_result_t waitForStateChange(aaudio_stream_state_t currentState, @@ -188,6 +222,22 @@ virtual aaudio_direction_t getDirection() const = 0; + aaudio_usage_t getUsage() const { + return mUsage; + } + + aaudio_content_type_t getContentType() const { + return mContentType; + } + + aaudio_input_preset_t getInputPreset() const { + return mInputPreset; + } + + int32_t getSessionId() const { + return mSessionId; + } + /** * This is only valid after setSamplesPerFrame() and setFormat() have been called. */ @@ -202,6 +252,20 @@ return AAudioConvert_formatToSizeInBytes(mFormat); } + /** + * This is only valid after setSamplesPerFrame() and setDeviceFormat() have been called. + */ + int32_t getBytesPerDeviceFrame() const { + return mSamplesPerFrame * getBytesPerDeviceSample(); + } + + /** + * This is only valid after setDeviceFormat() has been called. + */ + int32_t getBytesPerDeviceSample() const { + return AAudioConvert_formatToSizeInBytes(getDeviceFormat()); + } + virtual int64_t getFramesWritten() = 0; virtual int64_t getFramesRead() = 0; @@ -209,13 +273,19 @@ AAudioStream_dataCallback getDataCallbackProc() const { return mDataCallbackProc; } + AAudioStream_errorCallback getErrorCallbackProc() const { return mErrorCallbackProc; } + aaudio_data_callback_result_t maybeCallDataCallback(void *audioData, int32_t numFrames); + + void maybeCallErrorCallback(aaudio_result_t result); + void *getDataCallbackUserData() const { return mDataCallbackUserData; } + void *getErrorCallbackUserData() const { return mErrorCallbackUserData; } @@ -224,10 +294,25 @@ return mFramesPerDataCallback; } - bool isDataCallbackActive() { - return (mDataCallbackProc != nullptr) && isActive(); + /** + * @return true if data callback has been specified + */ + bool isDataCallbackSet() const { + return mDataCallbackProc != nullptr; } + /** + * @return true if data callback has been specified and stream is running + */ + bool isDataCallbackActive() const { + return isDataCallbackSet() && isActive(); + } + + /** + * @return true if called from the same thread as the callback + */ + bool collidesWithCallback() const; + // ============== I/O =========================== // A Stream will only implement read() or write() depending on its direction. virtual aaudio_result_t write(const void *buffer __unused, @@ -243,12 +328,9 @@ } // This is used by the AudioManager to duck and mute the stream when changing audio focus. - void setDuckAndMuteVolume(float duckAndMuteVolume) { - mDuckAndMuteVolume = duckAndMuteVolume; - doSetVolume(); // apply this change - } + void setDuckAndMuteVolume(float duckAndMuteVolume); - float getDuckAndMuteVolume() { + float getDuckAndMuteVolume() const { return mDuckAndMuteVolume; } @@ -288,11 +370,13 @@ return mPlayerBase->getResult(); } + // Pass pause request through PlayerBase for tracking. aaudio_result_t systemPause() { mPlayerBase->pause(); return mPlayerBase->getResult(); } + // Pass stop request through PlayerBase for tracking. aaudio_result_t systemStop() { mPlayerBase->stop(); return mPlayerBase->getResult(); @@ -331,17 +415,17 @@ android::status_t playerStart() override { // mParent should NOT be null. So go ahead and crash if it is. - mResult = mParent->requestStart(); + mResult = mParent->safeStart(); return AAudioConvert_aaudioToAndroidStatus(mResult); } android::status_t playerPause() override { - mResult = mParent->requestPause(); + mResult = mParent->safePause(); return AAudioConvert_aaudioToAndroidStatus(mResult); } android::status_t playerStop() override { - mResult = mParent->requestStop(); + mResult = mParent->safeStop(); return AAudioConvert_aaudioToAndroidStatus(mResult); } @@ -371,6 +455,7 @@ /** * This should not be called after the open() call. + * TODO for multiple setters: assert(mState == AAUDIO_STREAM_STATE_UNINITIALIZED) */ void setSampleRate(int32_t sampleRate) { mSampleRate = sampleRate; @@ -397,15 +482,26 @@ mFormat = format; } - void setState(aaudio_stream_state_t state) { - mState = state; + /** + * This should not be called after the open() call. + */ + void setDeviceFormat(aaudio_format_t format) { + mDeviceFormat = format; } + aaudio_format_t getDeviceFormat() const { + return mDeviceFormat; + } + + void setState(aaudio_stream_state_t state); + void setDeviceId(int32_t deviceId) { mDeviceId = deviceId; } - std::mutex mStreamMutex; + void setSessionId(int32_t sessionId) { + mSessionId = sessionId; + } std::atomic<bool> mCallbackEnabled{false}; @@ -413,6 +509,21 @@ protected: + /** + * Either convert the data from device format to app format and return a pointer + * to the conversion buffer, + * OR just pass back the original pointer. + * + * Note that this is only used for the INPUT path. + * + * @param audioData + * @param numFrames + * @return original pointer or the conversion buffer + */ + virtual const void * maybeConvertDeviceData(const void *audioData, int32_t numFrames) { + return audioData; + } + void setPeriodNanoseconds(int64_t periodNanoseconds) { mPeriodNanoseconds.store(periodNanoseconds, std::memory_order_release); } @@ -421,40 +532,74 @@ return mPeriodNanoseconds.load(std::memory_order_acquire); } + /** + * This should not be called after the open() call. + */ + void setUsage(aaudio_usage_t usage) { + mUsage = usage; + } + + /** + * This should not be called after the open() call. + */ + void setContentType(aaudio_content_type_t contentType) { + mContentType = contentType; + } + + /** + * This should not be called after the open() call. + */ + void setInputPreset(aaudio_input_preset_t inputPreset) { + mInputPreset = inputPreset; + } + private: + + std::mutex mStreamLock; + const android::sp<MyPlayerBase> mPlayerBase; // These do not change after open(). - int32_t mSamplesPerFrame = AAUDIO_UNSPECIFIED; - int32_t mSampleRate = AAUDIO_UNSPECIFIED; - int32_t mDeviceId = AAUDIO_UNSPECIFIED; - aaudio_sharing_mode_t mSharingMode = AAUDIO_SHARING_MODE_SHARED; - bool mSharingModeMatchRequired = false; // must match sharing mode requested - aaudio_format_t mFormat = AAUDIO_FORMAT_UNSPECIFIED; - aaudio_stream_state_t mState = AAUDIO_STREAM_STATE_UNINITIALIZED; + int32_t mSamplesPerFrame = AAUDIO_UNSPECIFIED; + int32_t mSampleRate = AAUDIO_UNSPECIFIED; + int32_t mDeviceId = AAUDIO_UNSPECIFIED; + aaudio_sharing_mode_t mSharingMode = AAUDIO_SHARING_MODE_SHARED; + bool mSharingModeMatchRequired = false; // must match sharing mode requested + aaudio_format_t mFormat = AAUDIO_FORMAT_UNSPECIFIED; + aaudio_stream_state_t mState = AAUDIO_STREAM_STATE_UNINITIALIZED; + aaudio_performance_mode_t mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE; - aaudio_performance_mode_t mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE; + aaudio_usage_t mUsage = AAUDIO_UNSPECIFIED; + aaudio_content_type_t mContentType = AAUDIO_UNSPECIFIED; + aaudio_input_preset_t mInputPreset = AAUDIO_UNSPECIFIED; + + int32_t mSessionId = AAUDIO_UNSPECIFIED; + + // Sometimes the hardware is operating with a different format from the app. + // Then we require conversion in AAudio. + aaudio_format_t mDeviceFormat = AAUDIO_FORMAT_UNSPECIFIED; // callback ---------------------------------- AAudioStream_dataCallback mDataCallbackProc = nullptr; // external callback functions void *mDataCallbackUserData = nullptr; int32_t mFramesPerDataCallback = AAUDIO_UNSPECIFIED; // frames + std::atomic<pid_t> mDataCallbackThread{CALLBACK_THREAD_NONE}; AAudioStream_errorCallback mErrorCallbackProc = nullptr; void *mErrorCallbackUserData = nullptr; + std::atomic<pid_t> mErrorCallbackThread{CALLBACK_THREAD_NONE}; // background thread ---------------------------------- - bool mHasThread = false; - pthread_t mThread; // initialized in constructor + bool mHasThread = false; + pthread_t mThread; // initialized in constructor // These are set by the application thread and then read by the audio pthread. - std::atomic<int64_t> mPeriodNanoseconds; // for tuning SCHED_FIFO threads + std::atomic<int64_t> mPeriodNanoseconds; // for tuning SCHED_FIFO threads // TODO make atomic? - aaudio_audio_thread_proc_t mThreadProc = nullptr; - void* mThreadArg = nullptr; - aaudio_result_t mThreadRegistrationResult = AAUDIO_OK; - + aaudio_audio_thread_proc_t mThreadProc = nullptr; + void *mThreadArg = nullptr; + aaudio_result_t mThreadRegistrationResult = AAUDIO_OK; };
diff --git a/media/libaaudio/src/core/AudioStreamBuilder.cpp b/media/libaaudio/src/core/AudioStreamBuilder.cpp index 09ebb3e..3a7a578 100644 --- a/media/libaaudio/src/core/AudioStreamBuilder.cpp +++ b/media/libaaudio/src/core/AudioStreamBuilder.cpp
@@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "AAudio" +#define LOG_TAG "AudioStreamBuilder" //#define LOG_NDEBUG 0 #include <utils/Log.h> @@ -87,7 +87,7 @@ break; default: - ALOGE("AudioStreamBuilder(): bad direction = %d", direction); + ALOGE("%s() bad direction = %d", __func__, direction); result = AAUDIO_ERROR_ILLEGAL_ARGUMENT; } return result; @@ -99,7 +99,7 @@ aaudio_result_t AudioStreamBuilder::build(AudioStream** streamPtr) { AudioStream *audioStream = nullptr; if (streamPtr == nullptr) { - ALOGE("AudioStreamBuilder::build() streamPtr is null"); + ALOGE("%s() streamPtr is null", __func__); return AAUDIO_ERROR_NULL; } *streamPtr = nullptr; @@ -124,13 +124,11 @@ if (mapExclusivePolicy == AAUDIO_UNSPECIFIED) { mapExclusivePolicy = AAUDIO_MMAP_EXCLUSIVE_POLICY_DEFAULT; } - ALOGD("AudioStreamBuilder(): mmapPolicy = %d, mapExclusivePolicy = %d", - mmapPolicy, mapExclusivePolicy); aaudio_sharing_mode_t sharingMode = getSharingMode(); if ((sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE) && (mapExclusivePolicy == AAUDIO_POLICY_NEVER)) { - ALOGW("AudioStreamBuilder(): EXCLUSIVE sharing mode not supported. Use SHARED."); + ALOGD("%s() EXCLUSIVE sharing mode not supported. Use SHARED.", __func__); sharingMode = AAUDIO_SHARING_MODE_SHARED; setSharingMode(sharingMode); } @@ -141,6 +139,14 @@ // TODO Support other performance settings in MMAP mode. // Disable MMAP if low latency not requested. if (getPerformanceMode() != AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) { + ALOGD("%s() MMAP not available because AAUDIO_PERFORMANCE_MODE_LOW_LATENCY not used.", + __func__); + allowMMap = false; + } + + // SessionID and Effects are only supported in Legacy mode. + if (getSessionId() != AAUDIO_SESSION_ID_NONE) { + ALOGD("%s() MMAP not available because sessionId used.", __func__); allowMMap = false; } @@ -156,7 +162,7 @@ audioStream = nullptr; if (isMMap && allowLegacy) { - ALOGD("AudioStreamBuilder.build() MMAP stream did not open so try Legacy path"); + ALOGV("%s() MMAP stream did not open so try Legacy path", __func__); // If MMAP stream failed to open then TRY using a legacy stream. result = builder_createStream(getDirection(), sharingMode, false, &audioStream); @@ -190,7 +196,7 @@ case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY: break; default: - ALOGE("AudioStreamBuilder: illegal performanceMode = %d", mPerformanceMode); + ALOGE("illegal performanceMode = %d", mPerformanceMode); return AAUDIO_ERROR_ILLEGAL_ARGUMENT; // break; } @@ -199,7 +205,7 @@ if (mFramesPerDataCallback != AAUDIO_UNSPECIFIED && (mFramesPerDataCallback < FRAMES_PER_DATA_CALLBACK_MIN || mFramesPerDataCallback > FRAMES_PER_DATA_CALLBACK_MAX)) { - ALOGE("AudioStreamBuilder: framesPerDataCallback out of range = %d", + ALOGE("framesPerDataCallback out of range = %d", mFramesPerDataCallback); return AAUDIO_ERROR_OUT_OF_RANGE; }
diff --git a/media/libaaudio/src/fifo/FifoBuffer.cpp b/media/libaaudio/src/fifo/FifoBuffer.cpp index c8ec15079f..b09258e 100644 --- a/media/libaaudio/src/fifo/FifoBuffer.cpp +++ b/media/libaaudio/src/fifo/FifoBuffer.cpp
@@ -45,7 +45,7 @@ int32_t bytesPerBuffer = bytesPerFrame * capacityInFrames; mStorage = new uint8_t[bytesPerBuffer]; mStorageOwned = true; - ALOGD("FifoBuffer: capacityInFrames = %d, bytesPerFrame = %d", + ALOGV("capacityInFrames = %d, bytesPerFrame = %d", capacityInFrames, bytesPerFrame); }
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp index ee2504d..a6b9f5d 100644 --- a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp +++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -19,10 +19,12 @@ #include <utils/Log.h> #include <stdint.h> -#include <utils/String16.h> + +#include <aaudio/AAudio.h> +#include <audio_utils/primitives.h> #include <media/AudioTrack.h> #include <media/AudioTimestamp.h> -#include <aaudio/AAudio.h> +#include <utils/String16.h> #include "core/AudioStream.h" #include "legacy/AudioStreamLegacy.h" @@ -48,19 +50,17 @@ return AudioStreamLegacy_callback; } -int32_t AudioStreamLegacy::callDataCallbackFrames(uint8_t *buffer, int32_t numFrames) { +aaudio_data_callback_result_t AudioStreamLegacy::callDataCallbackFrames(uint8_t *buffer, + int32_t numFrames) { + void *finalAudioData = buffer; if (getDirection() == AAUDIO_DIRECTION_INPUT) { // Increment before because we already got the data from the device. incrementFramesRead(numFrames); + finalAudioData = (void *) maybeConvertDeviceData(buffer, numFrames); } // Call using the AAudio callback interface. - AAudioStream_dataCallback appCallback = getDataCallbackProc(); - aaudio_data_callback_result_t callbackResult = (*appCallback)( - (AAudioStream *) this, - getDataCallbackUserData(), - buffer, - numFrames); + aaudio_data_callback_result_t callbackResult = maybeCallDataCallback(finalAudioData, numFrames); if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE && getDirection() == AAUDIO_DIRECTION_OUTPUT) { @@ -72,31 +72,40 @@ // Implement FixedBlockProcessor int32_t AudioStreamLegacy::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) { - int32_t numFrames = numBytes / getBytesPerFrame(); - return callDataCallbackFrames(buffer, numFrames); + int32_t numFrames = numBytes / getBytesPerDeviceFrame(); + return (int32_t) callDataCallbackFrames(buffer, numFrames); } void AudioStreamLegacy::processCallbackCommon(aaudio_callback_operation_t opcode, void *info) { aaudio_data_callback_result_t callbackResult; + // This illegal size can be used to tell AudioFlinger to stop calling us. + // This takes advantage of AudioFlinger killing the stream. + // TODO add to API in AudioRecord and AudioTrack + const size_t SIZE_STOP_CALLBACKS = SIZE_MAX; switch (opcode) { case AAUDIO_CALLBACK_OPERATION_PROCESS_DATA: { - checkForDisconnectRequest(); + (void) checkForDisconnectRequest(true); // Note that this code assumes an AudioTrack::Buffer is the same as // AudioRecord::Buffer // TODO define our own AudioBuffer and pass it from the subclasses. AudioTrack::Buffer *audioBuffer = static_cast<AudioTrack::Buffer *>(info); - if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED || !mCallbackEnabled.load()) { - audioBuffer->size = 0; // silence the buffer + if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED) { + ALOGW("processCallbackCommon() data, stream disconnected"); + audioBuffer->size = SIZE_STOP_CALLBACKS; + } else if (!mCallbackEnabled.load()) { + ALOGW("processCallbackCommon() stopping because callback disabled"); + audioBuffer->size = SIZE_STOP_CALLBACKS; } else { if (audioBuffer->frameCount == 0) { + ALOGW("processCallbackCommon() data, frameCount is zero"); return; } // If the caller specified an exact size then use a block size adapter. if (mBlockAdapter != nullptr) { - int32_t byteCount = audioBuffer->frameCount * getBytesPerFrame(); + int32_t byteCount = audioBuffer->frameCount * getBytesPerDeviceFrame(); callbackResult = mBlockAdapter->processVariableBlock( (uint8_t *) audioBuffer->raw, byteCount); } else { @@ -105,9 +114,12 @@ audioBuffer->frameCount); } if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) { - audioBuffer->size = audioBuffer->frameCount * getBytesPerFrame(); - } else { - audioBuffer->size = 0; + audioBuffer->size = audioBuffer->frameCount * getBytesPerDeviceFrame(); + } else { // STOP or invalid result + ALOGW("%s() callback requested stop, fake an error", __func__); + audioBuffer->size = SIZE_STOP_CALLBACKS; + // Disable the callback just in case AudioFlinger keeps trying to call us. + mCallbackEnabled.store(false); } if (updateStateMachine() != AAUDIO_OK) { @@ -130,26 +142,23 @@ } } - - -void AudioStreamLegacy::checkForDisconnectRequest() { +aaudio_result_t AudioStreamLegacy::checkForDisconnectRequest(bool errorCallbackEnabled) { if (mRequestDisconnect.isRequested()) { ALOGD("checkForDisconnectRequest() mRequestDisconnect acknowledged"); - forceDisconnect(); + forceDisconnect(errorCallbackEnabled); mRequestDisconnect.acknowledge(); mCallbackEnabled.store(false); + return AAUDIO_ERROR_DISCONNECTED; + } else { + return AAUDIO_OK; } } -void AudioStreamLegacy::forceDisconnect() { +void AudioStreamLegacy::forceDisconnect(bool errorCallbackEnabled) { if (getState() != AAUDIO_STREAM_STATE_DISCONNECTED) { setState(AAUDIO_STREAM_STATE_DISCONNECTED); - if (getErrorCallbackProc() != nullptr) { - (*getErrorCallbackProc())( - (AAudioStream *) this, - getErrorCallbackUserData(), - AAUDIO_ERROR_DISCONNECTED - ); + if (errorCallbackEnabled) { + maybeCallErrorCallback(AAUDIO_ERROR_DISCONNECTED); } } } @@ -175,19 +184,17 @@ int64_t localPosition; status_t status = extendedTimestamp->getBestTimestamp(&localPosition, timeNanoseconds, timebase, &location); - // use MonotonicCounter to prevent retrograde motion. - mTimestampPosition.update32((int32_t)localPosition); - *framePosition = mTimestampPosition.get(); + if (status == OK) { + // use MonotonicCounter to prevent retrograde motion. + mTimestampPosition.update32((int32_t) localPosition); + *framePosition = mTimestampPosition.get(); + } // ALOGD("getBestTimestamp() fposition: server = %6lld, kernel = %6lld, location = %d", // (long long) extendedTimestamp->mPosition[ExtendedTimestamp::Location::LOCATION_SERVER], // (long long) extendedTimestamp->mPosition[ExtendedTimestamp::Location::LOCATION_KERNEL], // (int)location); - if (status == WOULD_BLOCK) { - return AAUDIO_ERROR_INVALID_STATE; - } else { - return AAudioConvert_androidToAAudioResult(status); - } + return AAudioConvert_androidToAAudioResult(status); } void AudioStreamLegacy::onAudioDeviceUpdate(audio_port_handle_t deviceId)
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.h b/media/libaaudio/src/legacy/AudioStreamLegacy.h index 7e28579..494edbc 100644 --- a/media/libaaudio/src/legacy/AudioStreamLegacy.h +++ b/media/libaaudio/src/legacy/AudioStreamLegacy.h
@@ -112,12 +112,14 @@ void onAudioDeviceUpdate(audio_port_handle_t deviceId); - void checkForDisconnectRequest(); + /* + * Check to see whether a callback thread has requested a disconnected. + * @param errorCallbackEnabled set true to call errorCallback on disconnect + * @return AAUDIO_OK or AAUDIO_ERROR_DISCONNECTED + */ + aaudio_result_t checkForDisconnectRequest(bool errorCallbackEnabled); - void forceDisconnect(); - - void onStart() { mCallbackEnabled.store(true); } - void onStop() { mCallbackEnabled.store(false); } + void forceDisconnect(bool errorCallbackEnabled = true); int64_t incrementFramesWritten(int32_t frames) { return mFramesWritten.increment(frames);
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp index bc6e60c..505f2ee 100644 --- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp +++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -19,13 +19,15 @@ #include <utils/Log.h> #include <stdint.h> -#include <utils/String16.h> -#include <media/AudioRecord.h> -#include <aaudio/AAudio.h> -#include "AudioClock.h" +#include <aaudio/AAudio.h> +#include <audio_utils/primitives.h> +#include <media/AudioRecord.h> +#include <utils/String16.h> + #include "legacy/AudioStreamLegacy.h" #include "legacy/AudioStreamRecord.h" +#include "utility/AudioClock.h" #include "utility/FixedBlockWriter.h" using namespace android; @@ -63,10 +65,6 @@ size_t frameCount = (builder.getBufferCapacity() == AAUDIO_UNSPECIFIED) ? 0 : builder.getBufferCapacity(); - // TODO implement an unspecified Android format then use that. - audio_format_t format = (getFormat() == AAUDIO_FORMAT_UNSPECIFIED) - ? AUDIO_FORMAT_PCM_FLOAT - : AAudioConvert_aaudioToAndroidDataFormat(getFormat()); audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE; aaudio_performance_mode_t perfMode = getPerformanceMode(); @@ -82,6 +80,35 @@ break; } + // Preserve behavior of API 26 + if (getFormat() == AAUDIO_FORMAT_UNSPECIFIED) { + setFormat(AAUDIO_FORMAT_PCM_FLOAT); + } + + // Maybe change device format to get a FAST path. + // AudioRecord does not support FAST mode for FLOAT data. + // TODO AudioRecord should allow FLOAT data paths for FAST tracks. + // So IF the user asks for low latency FLOAT + // AND the sampleRate is likely to be compatible with FAST + // THEN request I16 and convert to FLOAT when passing to user. + // Note that hard coding 48000 Hz is not ideal because the sampleRate + // for a FAST path might not be 48000 Hz. + // It normally is but there is a chance that it is not. + // And there is no reliable way to know that in advance. + // Luckily the consequences of a wrong guess are minor. + // We just may not get a FAST track. + // But we wouldn't have anyway without this hack. + constexpr int32_t kMostLikelySampleRateForFast = 48000; + if (getFormat() == AAUDIO_FORMAT_PCM_FLOAT + && perfMode == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY + && (samplesPerFrame <= 2) // FAST only for mono and stereo + && (getSampleRate() == kMostLikelySampleRateForFast + || getSampleRate() == AAUDIO_UNSPECIFIED)) { + setDeviceFormat(AAUDIO_FORMAT_PCM_I16); + } else { + setDeviceFormat(getFormat()); + } + uint32_t notificationFrames = 0; // Setup the callback if there is one. @@ -96,47 +123,84 @@ } mCallbackBufferSize = builder.getFramesPerDataCallback(); - ALOGD("AudioStreamRecord::open(), request notificationFrames = %u, frameCount = %u", - notificationFrames, (uint)frameCount); - mAudioRecord = new AudioRecord( - mOpPackageName // const String16& opPackageName TODO does not compile - ); - if (getDeviceId() != AAUDIO_UNSPECIFIED) { - mAudioRecord->setInputDevice(getDeviceId()); - } - mAudioRecord->set( - AUDIO_SOURCE_VOICE_RECOGNITION, - getSampleRate(), - format, - channelMask, - frameCount, - callback, - callbackData, - notificationFrames, - false /*threadCanCallJava*/, - AUDIO_SESSION_ALLOCATE, - streamTransferType, - flags - // int uid = -1, - // pid_t pid = -1, - // const audio_attributes_t* pAttributes = nullptr - ); + // Don't call mAudioRecord->setInputDevice() because it will be overwritten by set()! + audio_port_handle_t selectedDeviceId = (getDeviceId() == AAUDIO_UNSPECIFIED) + ? AUDIO_PORT_HANDLE_NONE + : getDeviceId(); - // Did we get a valid track? - status_t status = mAudioRecord->initCheck(); - if (status != OK) { - close(); - ALOGE("AudioStreamRecord::open(), initCheck() returned %d", status); - return AAudioConvert_androidToAAudioResult(status); + const audio_content_type_t contentType = + AAudioConvert_contentTypeToInternal(builder.getContentType()); + const audio_source_t source = + AAudioConvert_inputPresetToAudioSource(builder.getInputPreset()); + + const audio_attributes_t attributes = { + .content_type = contentType, + .usage = AUDIO_USAGE_UNKNOWN, // only used for output + .source = source, + .flags = AUDIO_FLAG_NONE, // Different than the AUDIO_INPUT_FLAGS + .tags = "" + }; + + aaudio_session_id_t requestedSessionId = builder.getSessionId(); + audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId); + + // ----------- open the AudioRecord --------------------- + // Might retry, but never more than once. + for (int i = 0; i < 2; i ++) { + audio_format_t requestedInternalFormat = + AAudioConvert_aaudioToAndroidDataFormat(getDeviceFormat()); + + mAudioRecord = new AudioRecord( + mOpPackageName // const String16& opPackageName TODO does not compile + ); + mAudioRecord->set( + AUDIO_SOURCE_DEFAULT, // ignored because we pass attributes below + getSampleRate(), + requestedInternalFormat, + channelMask, + frameCount, + callback, + callbackData, + notificationFrames, + false /*threadCanCallJava*/, + sessionId, + streamTransferType, + flags, + AUDIO_UID_INVALID, // DEFAULT uid + -1, // DEFAULT pid + &attributes, + selectedDeviceId + ); + + // Did we get a valid track? + status_t status = mAudioRecord->initCheck(); + if (status != OK) { + close(); + ALOGE("open(), initCheck() returned %d", status); + return AAudioConvert_androidToAAudioResult(status); + } + + // Check to see if it was worth hacking the deviceFormat. + bool gotFastPath = (mAudioRecord->getFlags() & AUDIO_INPUT_FLAG_FAST) + == AUDIO_INPUT_FLAG_FAST; + if (getFormat() != getDeviceFormat() && !gotFastPath) { + // We tried to get a FAST path by switching the device format. + // But it didn't work. So we might as well reopen using the same + // format for device and for app. + ALOGD("%s() used a different device format but no FAST path, reopen", __func__); + mAudioRecord.clear(); + setDeviceFormat(getFormat()); + } else { + break; // Keep the one we just opened. + } } // Get the actual values from the AudioRecord. setSamplesPerFrame(mAudioRecord->channelCount()); - setFormat(AAudioConvert_androidToAAudioDataFormat(mAudioRecord->format())); int32_t actualSampleRate = mAudioRecord->getSampleRate(); ALOGW_IF(actualSampleRate != getSampleRate(), - "AudioStreamRecord::open() sampleRate changed from %d to %d", + "open() sampleRate changed from %d to %d", getSampleRate(), actualSampleRate); setSampleRate(actualSampleRate); @@ -149,6 +213,29 @@ mBlockAdapter = nullptr; } + // Allocate format conversion buffer if needed. + if (getDeviceFormat() == AAUDIO_FORMAT_PCM_I16 + && getFormat() == AAUDIO_FORMAT_PCM_FLOAT) { + + if (builder.getDataCallbackProc() != nullptr) { + // If we have a callback then we need to convert the data into an internal float + // array and then pass that entire array to the app. + mFormatConversionBufferSizeInFrames = + (mCallbackBufferSize != AAUDIO_UNSPECIFIED) + ? mCallbackBufferSize : getFramesPerBurst(); + int32_t numSamples = mFormatConversionBufferSizeInFrames * getSamplesPerFrame(); + mFormatConversionBufferFloat = std::make_unique<float[]>(numSamples); + } else { + // If we don't have a callback then we will read into an internal short array + // and then convert into the app float array in read(). + mFormatConversionBufferSizeInFrames = getFramesPerBurst(); + int32_t numSamples = mFormatConversionBufferSizeInFrames * getSamplesPerFrame(); + mFormatConversionBufferI16 = std::make_unique<int16_t[]>(numSamples); + } + ALOGD("%s() setup I16>FLOAT conversion buffer with %d frames", + __func__, mFormatConversionBufferSizeInFrames); + } + // Update performance mode based on the actual stream. // For example, if the sample rate does not match native then you won't get a FAST track. audio_input_flags_t actualFlags = mAudioRecord->getFlags(); @@ -164,14 +251,21 @@ // Log warning if we did not get what we asked for. ALOGW_IF(actualFlags != flags, - "AudioStreamRecord::open() flags changed from 0x%08X to 0x%08X", + "open() flags changed from 0x%08X to 0x%08X", flags, actualFlags); ALOGW_IF(actualPerformanceMode != perfMode, - "AudioStreamRecord::open() perfMode changed from %d to %d", + "open() perfMode changed from %d to %d", perfMode, actualPerformanceMode); setState(AAUDIO_STREAM_STATE_OPEN); setDeviceId(mAudioRecord->getRoutedDeviceId()); + + aaudio_session_id_t actualSessionId = + (requestedSessionId == AAUDIO_SESSION_ID_NONE) + ? AAUDIO_SESSION_ID_NONE + : (aaudio_session_id_t) mAudioRecord->getSessionId(); + setSessionId(actualSessionId); + mAudioRecord->addAudioDeviceCallback(mDeviceCallback); return AAUDIO_OK; @@ -189,6 +283,24 @@ return AudioStream::close(); } +const void * AudioStreamRecord::maybeConvertDeviceData(const void *audioData, int32_t numFrames) { + if (mFormatConversionBufferFloat.get() != nullptr) { + LOG_ALWAYS_FATAL_IF(numFrames > mFormatConversionBufferSizeInFrames, + "%s() conversion size %d too large for buffer %d", + __func__, numFrames, mFormatConversionBufferSizeInFrames); + + int32_t numSamples = numFrames * getSamplesPerFrame(); + // Only conversion supported is I16 to FLOAT + memcpy_to_float_from_i16( + mFormatConversionBufferFloat.get(), + (const int16_t *) audioData, + numSamples); + return mFormatConversionBufferFloat.get(); + } else { + return audioData; + } +} + void AudioStreamRecord::processCallback(int event, void *info) { switch (event) { case AudioRecord::EVENT_MORE_DATA: @@ -217,11 +329,13 @@ return AAudioConvert_androidToAAudioResult(err); } + // Enable callback before starting AudioTrack to avoid shutting + // down because of a race condition. + mCallbackEnabled.store(true); err = mAudioRecord->start(); if (err != OK) { return AAudioConvert_androidToAAudioResult(err); } else { - onStart(); setState(AAUDIO_STREAM_STATE_STARTING); } return AAUDIO_OK; @@ -231,15 +345,16 @@ if (mAudioRecord.get() == nullptr) { return AAUDIO_ERROR_INVALID_STATE; } - onStop(); setState(AAUDIO_STREAM_STATE_STOPPING); incrementFramesWritten(getFramesRead() - getFramesWritten()); // TODO review mTimestampPosition.set(getFramesRead()); mAudioRecord->stop(); - mFramesRead.reset32(); + mCallbackEnabled.store(false); + mFramesWritten.reset32(); // service writes frames, service position reset on flush mTimestampPosition.reset32(); - checkForDisconnectRequest(); - return AAUDIO_OK; + // Pass false to prevent errorCallback from being called after disconnect + // when app has already requested a stop(). + return checkForDisconnectRequest(false); } aaudio_result_t AudioStreamRecord::updateStateMachine() @@ -272,9 +387,10 @@ int32_t numFrames, int64_t timeoutNanoseconds) { - int32_t bytesPerFrame = getBytesPerFrame(); + int32_t bytesPerDeviceFrame = getBytesPerDeviceFrame(); int32_t numBytes; - aaudio_result_t result = AAudioConvert_framesToBytes(numFrames, bytesPerFrame, &numBytes); + // This will detect out of range values for numFrames. + aaudio_result_t result = AAudioConvert_framesToBytes(numFrames, bytesPerDeviceFrame, &numBytes); if (result != AAUDIO_OK) { return result; } @@ -285,19 +401,49 @@ // TODO add timeout to AudioRecord bool blocking = (timeoutNanoseconds > 0); - ssize_t bytesRead = mAudioRecord->read(buffer, numBytes, blocking); - if (bytesRead == WOULD_BLOCK) { + + ssize_t bytesActuallyRead = 0; + ssize_t totalBytesRead = 0; + if (mFormatConversionBufferI16.get() != nullptr) { + // Convert I16 data to float using an intermediate buffer. + float *floatBuffer = (float *) buffer; + int32_t framesLeft = numFrames; + // Perform conversion using multiple read()s if necessary. + while (framesLeft > 0) { + // Read into short internal buffer. + int32_t framesToRead = std::min(framesLeft, mFormatConversionBufferSizeInFrames); + size_t bytesToRead = framesToRead * bytesPerDeviceFrame; + bytesActuallyRead = mAudioRecord->read(mFormatConversionBufferI16.get(), bytesToRead, blocking); + if (bytesActuallyRead <= 0) { + break; + } + totalBytesRead += bytesActuallyRead; + int32_t framesToConvert = bytesActuallyRead / bytesPerDeviceFrame; + // Convert into app float buffer. + size_t numSamples = framesToConvert * getSamplesPerFrame(); + memcpy_to_float_from_i16( + floatBuffer, + mFormatConversionBufferI16.get(), + numSamples); + floatBuffer += numSamples; + framesLeft -= framesToConvert; + } + } else { + bytesActuallyRead = mAudioRecord->read(buffer, numBytes, blocking); + totalBytesRead = bytesActuallyRead; + } + if (bytesActuallyRead == WOULD_BLOCK) { return 0; - } else if (bytesRead < 0) { - // in this context, a DEAD_OBJECT is more likely to be a disconnect notification due to - // AudioRecord invalidation - if (bytesRead == DEAD_OBJECT) { + } else if (bytesActuallyRead < 0) { + // In this context, a DEAD_OBJECT is more likely to be a disconnect notification due to + // AudioRecord invalidation. + if (bytesActuallyRead == DEAD_OBJECT) { setState(AAUDIO_STREAM_STATE_DISCONNECTED); return AAUDIO_ERROR_DISCONNECTED; } - return AAudioConvert_androidToAAudioResult(bytesRead); + return AAudioConvert_androidToAAudioResult(bytesActuallyRead); } - int32_t framesRead = (int32_t)(bytesRead / bytesPerFrame); + int32_t framesRead = (int32_t)(totalBytesRead / bytesPerDeviceFrame); incrementFramesRead(framesRead); result = updateStateMachine();
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.h b/media/libaaudio/src/legacy/AudioStreamRecord.h index c1723ba..2f41d34 100644 --- a/media/libaaudio/src/legacy/AudioStreamRecord.h +++ b/media/libaaudio/src/legacy/AudioStreamRecord.h
@@ -76,6 +76,8 @@ return incrementFramesRead(frames); } + const void * maybeConvertDeviceData(const void *audioData, int32_t numFrames) override; + private: android::sp<android::AudioRecord> mAudioRecord; // adapts between variable sized blocks and fixed size blocks @@ -83,6 +85,11 @@ // TODO add 64-bit position reporting to AudioRecord and use it. android::String16 mOpPackageName; + + // Only one type of conversion buffer is used. + std::unique_ptr<float[]> mFormatConversionBufferFloat; + std::unique_ptr<int16_t[]> mFormatConversionBufferI16; + int32_t mFormatConversionBufferSizeInFrames = 0; }; } /* namespace aaudio */
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp index 0e9aaef..505cd77 100644 --- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp +++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -22,6 +22,7 @@ #include <media/AudioTrack.h> #include <aaudio/AAudio.h> +#include <system/audio.h> #include "utility/AudioClock.h" #include "legacy/AudioStreamLegacy.h" #include "legacy/AudioStreamTrack.h" @@ -113,14 +114,35 @@ } mCallbackBufferSize = builder.getFramesPerDataCallback(); - ALOGD("AudioStreamTrack::open(), request notificationFrames = %d, frameCount = %u", + ALOGD("open(), request notificationFrames = %d, frameCount = %u", notificationFrames, (uint)frameCount); - mAudioTrack = new AudioTrack(); // TODO review - if (getDeviceId() != AAUDIO_UNSPECIFIED) { - mAudioTrack->setOutputDevice(getDeviceId()); - } + + // Don't call mAudioTrack->setDeviceId() because it will be overwritten by set()! + audio_port_handle_t selectedDeviceId = (getDeviceId() == AAUDIO_UNSPECIFIED) + ? AUDIO_PORT_HANDLE_NONE + : getDeviceId(); + + const audio_content_type_t contentType = + AAudioConvert_contentTypeToInternal(builder.getContentType()); + const audio_usage_t usage = + AAudioConvert_usageToInternal(builder.getUsage()); + + const audio_attributes_t attributes = { + .content_type = contentType, + .usage = usage, + .source = AUDIO_SOURCE_DEFAULT, // only used for recording + .flags = AUDIO_FLAG_NONE, // Different than the AUDIO_OUTPUT_FLAGS + .tags = "" + }; + + static_assert(AAUDIO_UNSPECIFIED == AUDIO_SESSION_ALLOCATE, "Session IDs should match"); + + aaudio_session_id_t requestedSessionId = builder.getSessionId(); + audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId); + + mAudioTrack = new AudioTrack(); mAudioTrack->set( - (audio_stream_type_t) AUDIO_STREAM_MUSIC, + AUDIO_STREAM_DEFAULT, // ignored because we pass attributes below getSampleRate(), format, channelMask, @@ -129,17 +151,26 @@ callback, callbackData, notificationFrames, - 0 /*sharedBuffer*/, - false /*threadCanCallJava*/, - AUDIO_SESSION_ALLOCATE, - streamTransferType - ); + 0, // DEFAULT sharedBuffer*/, + false, // DEFAULT threadCanCallJava + sessionId, + streamTransferType, + NULL, // DEFAULT audio_offload_info_t + AUDIO_UID_INVALID, // DEFAULT uid + -1, // DEFAULT pid + &attributes, + // WARNING - If doNotReconnect set true then audio stops after plugging and unplugging + // headphones a few times. + false, // DEFAULT doNotReconnect, + 1.0f, // DEFAULT maxRequiredSpeed + selectedDeviceId + ); // Did we get a valid track? status_t status = mAudioTrack->initCheck(); if (status != NO_ERROR) { close(); - ALOGE("AudioStreamTrack::open(), initCheck() returned %d", status); + ALOGE("open(), initCheck() returned %d", status); return AAudioConvert_androidToAAudioResult(status); } @@ -150,10 +181,11 @@ aaudio_format_t aaudioFormat = AAudioConvert_androidToAAudioDataFormat(mAudioTrack->format()); setFormat(aaudioFormat); + setDeviceFormat(aaudioFormat); int32_t actualSampleRate = mAudioTrack->getSampleRate(); ALOGW_IF(actualSampleRate != getSampleRate(), - "AudioStreamTrack::open() sampleRate changed from %d to %d", + "open() sampleRate changed from %d to %d", getSampleRate(), actualSampleRate); setSampleRate(actualSampleRate); @@ -168,6 +200,13 @@ setState(AAUDIO_STREAM_STATE_OPEN); setDeviceId(mAudioTrack->getRoutedDeviceId()); + + aaudio_session_id_t actualSessionId = + (requestedSessionId == AAUDIO_SESSION_ID_NONE) + ? AAUDIO_SESSION_ID_NONE + : (aaudio_session_id_t) mAudioTrack->getSessionId(); + setSessionId(actualSessionId); + mAudioTrack->addAudioDeviceCallback(mDeviceCallback); // Update performance mode based on the actual stream flags. @@ -186,10 +225,10 @@ // Log warning if we did not get what we asked for. ALOGW_IF(actualFlags != flags, - "AudioStreamTrack::open() flags changed from 0x%08X to 0x%08X", + "open() flags changed from 0x%08X to 0x%08X", flags, actualFlags); ALOGW_IF(actualPerformanceMode != perfMode, - "AudioStreamTrack::open() perfMode changed from %d to %d", + "open() perfMode changed from %d to %d", perfMode, actualPerformanceMode); return AAUDIO_OK; @@ -224,10 +263,8 @@ } aaudio_result_t AudioStreamTrack::requestStart() { - std::lock_guard<std::mutex> lock(mStreamMutex); - if (mAudioTrack.get() == nullptr) { - ALOGE("AudioStreamTrack::requestStart() no AudioTrack"); + ALOGE("requestStart() no AudioTrack"); return AAUDIO_ERROR_INVALID_STATE; } // Get current position so we can detect when the track is playing. @@ -236,73 +273,62 @@ return AAudioConvert_androidToAAudioResult(err); } + // Enable callback before starting AudioTrack to avoid shutting + // down because of a race condition. + mCallbackEnabled.store(true); err = mAudioTrack->start(); if (err != OK) { return AAudioConvert_androidToAAudioResult(err); } else { - onStart(); setState(AAUDIO_STREAM_STATE_STARTING); } return AAUDIO_OK; } aaudio_result_t AudioStreamTrack::requestPause() { - std::lock_guard<std::mutex> lock(mStreamMutex); - if (mAudioTrack.get() == nullptr) { ALOGE("requestPause() no AudioTrack"); return AAUDIO_ERROR_INVALID_STATE; - } else if (getState() != AAUDIO_STREAM_STATE_STARTING - && getState() != AAUDIO_STREAM_STATE_STARTED) { - ALOGE("requestPause(), called when state is %s", - AAudio_convertStreamStateToText(getState())); - return AAUDIO_ERROR_INVALID_STATE; } - onStop(); + setState(AAUDIO_STREAM_STATE_PAUSING); mAudioTrack->pause(); - checkForDisconnectRequest(); + mCallbackEnabled.store(false); status_t err = mAudioTrack->getPosition(&mPositionWhenPausing); if (err != OK) { return AAudioConvert_androidToAAudioResult(err); } - return AAUDIO_OK; + return checkForDisconnectRequest(false); } aaudio_result_t AudioStreamTrack::requestFlush() { - std::lock_guard<std::mutex> lock(mStreamMutex); - if (mAudioTrack.get() == nullptr) { - ALOGE("AudioStreamTrack::requestFlush() no AudioTrack"); - return AAUDIO_ERROR_INVALID_STATE; - } else if (getState() != AAUDIO_STREAM_STATE_PAUSED) { - ALOGE("AudioStreamTrack::requestFlush() not paused"); + ALOGE("requestFlush() no AudioTrack"); return AAUDIO_ERROR_INVALID_STATE; } + setState(AAUDIO_STREAM_STATE_FLUSHING); incrementFramesRead(getFramesWritten() - getFramesRead()); mAudioTrack->flush(); - mFramesWritten.reset32(); + mFramesRead.reset32(); // service reads frames, service position reset on flush mTimestampPosition.reset32(); return AAUDIO_OK; } aaudio_result_t AudioStreamTrack::requestStop() { - std::lock_guard<std::mutex> lock(mStreamMutex); - if (mAudioTrack.get() == nullptr) { - ALOGE("AudioStreamTrack::requestStop() no AudioTrack"); + ALOGE("requestStop() no AudioTrack"); return AAUDIO_ERROR_INVALID_STATE; } - onStop(); + setState(AAUDIO_STREAM_STATE_STOPPING); incrementFramesRead(getFramesWritten() - getFramesRead()); // TODO review mTimestampPosition.set(getFramesWritten()); - mFramesWritten.reset32(); + mFramesRead.reset32(); // service reads frames, service position reset on stop mTimestampPosition.reset32(); mAudioTrack->stop(); - checkForDisconnectRequest(); - return AAUDIO_OK; + mCallbackEnabled.store(false); + return checkForDisconnectRequest(false);; } aaudio_result_t AudioStreamTrack::updateStateMachine()
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.h b/media/libaaudio/src/legacy/AudioStreamTrack.h index a871db4..68608de 100644 --- a/media/libaaudio/src/legacy/AudioStreamTrack.h +++ b/media/libaaudio/src/legacy/AudioStreamTrack.h
@@ -48,6 +48,16 @@ aaudio_result_t requestFlush() override; aaudio_result_t requestStop() override; + bool isFlushSupported() const override { + // Only implement FLUSH for OUTPUT streams. + return true; + } + + bool isPauseSupported() const override { + // Only implement PAUSE for OUTPUT streams. + return true; + } + aaudio_result_t getTimestamp(clockid_t clockId, int64_t *framePosition, int64_t *timeNanoseconds) override;
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp index 2450920..40ebb76 100644 --- a/media/libaaudio/src/utility/AAudioUtilities.cpp +++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -25,6 +25,9 @@ #include "aaudio/AAudio.h" #include <aaudio/AAudioTesting.h> +#include <math.h> +#include <system/audio-base.h> +#include <assert.h> #include "utility/AAudioUtilities.h" @@ -50,50 +53,21 @@ return size; } - // TODO expose and call clamp16_from_float function in primitives.h static inline int16_t clamp16_from_float(float f) { - /* Offset is used to expand the valid range of [-1.0, 1.0) into the 16 lsbs of the - * floating point significand. The normal shift is 3<<22, but the -15 offset - * is used to multiply by 32768. - */ - static const float offset = (float)(3 << (22 - 15)); - /* zero = (0x10f << 22) = 0x43c00000 (not directly used) */ - static const int32_t limneg = (0x10f << 22) /*zero*/ - 32768; /* 0x43bf8000 */ - static const int32_t limpos = (0x10f << 22) /*zero*/ + 32767; /* 0x43c07fff */ - - union { - float f; - int32_t i; - } u; - - u.f = f + offset; /* recenter valid range */ - /* Now the valid range is represented as integers between [limneg, limpos]. - * Clamp using the fact that float representation (as an integer) is an ordered set. - */ - if (u.i < limneg) - u.i = -32768; - else if (u.i > limpos) - u.i = 32767; - return u.i; /* Return lower 16 bits, the part of interest in the significand. */ + static const float scale = 1 << 15; + return (int16_t) roundf(fmaxf(fminf(f * scale, scale - 1.f), -scale)); } -// Same but without clipping. -// Convert -1.0f to +1.0f to -32768 to +32767 -static inline int16_t floatToInt16(float f) { - static const float offset = (float)(3 << (22 - 15)); - union { - float f; - int32_t i; - } u; - u.f = f + offset; /* recenter valid range */ - return u.i; /* Return lower 16 bits, the part of interest in the significand. */ +// Clip to valid range of a float sample to prevent excessive volume. +// By using fmin and fmax we also protect against NaN. +static float clipToMinMaxHeadroom(float input) { + return fmin(MAX_HEADROOM, fmax(MIN_HEADROOM, input)); } static float clipAndClampFloatToPcm16(float sample, float scaler) { // Clip to valid range of a float sample to prevent excessive volume. - if (sample > MAX_HEADROOM) sample = MAX_HEADROOM; - else if (sample < MIN_HEADROOM) sample = MIN_HEADROOM; + sample = clipToMinMaxHeadroom(sample); // Scale and convert to a short. float fval = sample * scaler; @@ -104,7 +78,7 @@ int16_t *destination, int32_t numSamples, float amplitude) { - float scaler = amplitude; + const float scaler = amplitude; for (int i = 0; i < numSamples; i++) { float sample = *source++; *destination++ = clipAndClampFloatToPcm16(sample, scaler); @@ -135,7 +109,7 @@ float *destination, int32_t numSamples, float amplitude) { - float scaler = amplitude / SHORT_SCALE; + const float scaler = amplitude / SHORT_SCALE; for (int i = 0; i < numSamples; i++) { destination[i] = source[i] * scaler; } @@ -149,7 +123,7 @@ float amplitude1, float amplitude2) { float scaler = amplitude1 / SHORT_SCALE; - float delta = (amplitude2 - amplitude1) / (SHORT_SCALE * (float) numFrames); + const float delta = (amplitude2 - amplitude1) / (SHORT_SCALE * (float) numFrames); for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) { for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) { *destination++ = *source++ * scaler; @@ -158,6 +132,7 @@ } } + // This code assumes amplitude1 and amplitude2 are between 0.0 and 1.0 void AAudio_linearRamp(const float *source, float *destination, @@ -166,14 +141,12 @@ float amplitude1, float amplitude2) { float scaler = amplitude1; - float delta = (amplitude2 - amplitude1) / numFrames; + const float delta = (amplitude2 - amplitude1) / numFrames; for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) { for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) { float sample = *source++; - // Clip to valid range of a float sample to prevent excessive volume. - if (sample > MAX_HEADROOM) sample = MAX_HEADROOM; - else if (sample < MIN_HEADROOM) sample = MIN_HEADROOM; + sample = clipToMinMaxHeadroom(sample); *destination++ = sample * scaler; } @@ -188,18 +161,267 @@ int32_t samplesPerFrame, float amplitude1, float amplitude2) { - float scaler = amplitude1 / SHORT_SCALE; - float delta = (amplitude2 - amplitude1) / (SHORT_SCALE * (float) numFrames); + // Because we are converting from int16 to 1nt16, we do not have to scale by 1/32768. + float scaler = amplitude1; + const float delta = (amplitude2 - amplitude1) / numFrames; for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) { for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) { // No need to clip because int16_t range is inherently limited. float sample = *source++ * scaler; - *destination++ = floatToInt16(sample); + *destination++ = (int16_t) roundf(sample); } scaler += delta; } } +// ************************************************************************************* +// Convert Mono To Stereo at the same time as converting format. +void AAudioConvert_formatMonoToStereo(const float *source, + int16_t *destination, + int32_t numFrames, + float amplitude) { + const float scaler = amplitude; + for (int i = 0; i < numFrames; i++) { + float sample = *source++; + int16_t sample16 = clipAndClampFloatToPcm16(sample, scaler); + *destination++ = sample16; + *destination++ = sample16; + } +} + +void AAudioConvert_formatMonoToStereo(const float *source, + int16_t *destination, + int32_t numFrames, + float amplitude1, + float amplitude2) { + // divide by numFrames so that we almost reach amplitude2 + const float delta = (amplitude2 - amplitude1) / numFrames; + for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) { + const float scaler = amplitude1 + (frameIndex * delta); + const float sample = *source++; + int16_t sample16 = clipAndClampFloatToPcm16(sample, scaler); + *destination++ = sample16; + *destination++ = sample16; + } +} + +void AAudioConvert_formatMonoToStereo(const int16_t *source, + float *destination, + int32_t numFrames, + float amplitude) { + const float scaler = amplitude / SHORT_SCALE; + for (int i = 0; i < numFrames; i++) { + float sample = source[i] * scaler; + *destination++ = sample; + *destination++ = sample; + } +} + +// This code assumes amplitude1 and amplitude2 are between 0.0 and 1.0 +void AAudioConvert_formatMonoToStereo(const int16_t *source, + float *destination, + int32_t numFrames, + float amplitude1, + float amplitude2) { + const float scaler1 = amplitude1 / SHORT_SCALE; + const float delta = (amplitude2 - amplitude1) / (SHORT_SCALE * (float) numFrames); + for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) { + float scaler = scaler1 + (frameIndex * delta); + float sample = source[frameIndex] * scaler; + *destination++ = sample; + *destination++ = sample; + } +} + +// This code assumes amplitude1 and amplitude2 are between 0.0 and 1.0 +void AAudio_linearRampMonoToStereo(const float *source, + float *destination, + int32_t numFrames, + float amplitude1, + float amplitude2) { + const float delta = (amplitude2 - amplitude1) / numFrames; + for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) { + float sample = *source++; + + // Clip to valid range of a float sample to prevent excessive volume. + sample = clipToMinMaxHeadroom(sample); + + const float scaler = amplitude1 + (frameIndex * delta); + float sampleScaled = sample * scaler; + *destination++ = sampleScaled; + *destination++ = sampleScaled; + } +} + +// This code assumes amplitude1 and amplitude2 are between 0.0 and 1.0 +void AAudio_linearRampMonoToStereo(const int16_t *source, + int16_t *destination, + int32_t numFrames, + float amplitude1, + float amplitude2) { + // Because we are converting from int16 to 1nt16, we do not have to scale by 1/32768. + const float delta = (amplitude2 - amplitude1) / numFrames; + for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) { + const float scaler = amplitude1 + (frameIndex * delta); + // No need to clip because int16_t range is inherently limited. + const float sample = *source++ * scaler; + int16_t sample16 = (int16_t) roundf(sample); + *destination++ = sample16; + *destination++ = sample16; + } +} + +// ************************************************************************************* +void AAudioDataConverter::convert( + const FormattedData &source, + const FormattedData &destination, + int32_t numFrames, + float levelFrom, + float levelTo) { + + if (source.channelCount == 1 && destination.channelCount == 2) { + convertMonoToStereo(source, + destination, + numFrames, + levelFrom, + levelTo); + } else { + // We only support mono to stereo conversion. Otherwise source and destination + // must match. + assert(source.channelCount == destination.channelCount); + convertChannelsMatch(source, + destination, + numFrames, + levelFrom, + levelTo); + } +} + +void AAudioDataConverter::convertMonoToStereo( + const FormattedData &source, + const FormattedData &destination, + int32_t numFrames, + float levelFrom, + float levelTo) { + + // The formats are validated when the stream is opened so we do not have to + // check for illegal combinations here. + if (source.format == AAUDIO_FORMAT_PCM_FLOAT) { + if (destination.format == AAUDIO_FORMAT_PCM_FLOAT) { + AAudio_linearRampMonoToStereo( + (const float *) source.data, + (float *) destination.data, + numFrames, + levelFrom, + levelTo); + } else if (destination.format == AAUDIO_FORMAT_PCM_I16) { + if (levelFrom != levelTo) { + AAudioConvert_formatMonoToStereo( + (const float *) source.data, + (int16_t *) destination.data, + numFrames, + levelFrom, + levelTo); + } else { + AAudioConvert_formatMonoToStereo( + (const float *) source.data, + (int16_t *) destination.data, + numFrames, + levelTo); + } + } + } else if (source.format == AAUDIO_FORMAT_PCM_I16) { + if (destination.format == AAUDIO_FORMAT_PCM_FLOAT) { + if (levelFrom != levelTo) { + AAudioConvert_formatMonoToStereo( + (const int16_t *) source.data, + (float *) destination.data, + numFrames, + levelFrom, + levelTo); + } else { + AAudioConvert_formatMonoToStereo( + (const int16_t *) source.data, + (float *) destination.data, + numFrames, + levelTo); + } + } else if (destination.format == AAUDIO_FORMAT_PCM_I16) { + AAudio_linearRampMonoToStereo( + (const int16_t *) source.data, + (int16_t *) destination.data, + numFrames, + levelFrom, + levelTo); + } + } +} + +void AAudioDataConverter::convertChannelsMatch( + const FormattedData &source, + const FormattedData &destination, + int32_t numFrames, + float levelFrom, + float levelTo) { + const int32_t numSamples = numFrames * source.channelCount; + + // The formats are validated when the stream is opened so we do not have to + // check for illegal combinations here. + if (source.format == AAUDIO_FORMAT_PCM_FLOAT) { + if (destination.format == AAUDIO_FORMAT_PCM_FLOAT) { + AAudio_linearRamp( + (const float *) source.data, + (float *) destination.data, + numFrames, + source.channelCount, + levelFrom, + levelTo); + } else if (destination.format == AAUDIO_FORMAT_PCM_I16) { + if (levelFrom != levelTo) { + AAudioConvert_floatToPcm16( + (const float *) source.data, + (int16_t *) destination.data, + numFrames, + source.channelCount, + levelFrom, + levelTo); + } else { + AAudioConvert_floatToPcm16( + (const float *) source.data, + (int16_t *) destination.data, + numSamples, + levelTo); + } + } + } else if (source.format == AAUDIO_FORMAT_PCM_I16) { + if (destination.format == AAUDIO_FORMAT_PCM_FLOAT) { + if (levelFrom != levelTo) { + AAudioConvert_pcm16ToFloat( + (const int16_t *) source.data, + (float *) destination.data, + numFrames, + source.channelCount, + levelFrom, + levelTo); + } else { + AAudioConvert_pcm16ToFloat( + (const int16_t *) source.data, + (float *) destination.data, + numSamples, + levelTo); + } + } else if (destination.format == AAUDIO_FORMAT_PCM_I16) { + AAudio_linearRamp( + (const int16_t *) source.data, + (int16_t *) destination.data, + numFrames, + source.channelCount, + levelFrom, + levelTo); + } + } +} + status_t AAudioConvert_aaudioToAndroidStatus(aaudio_result_t result) { // This covers the case for AAUDIO_OK and for positive results. if (result >= 0) { @@ -281,6 +503,13 @@ return result; } +audio_session_t AAudioConvert_aaudioToAndroidSessionId(aaudio_session_id_t sessionId) { + // If not a regular sessionId then convert to a safe value of AUDIO_SESSION_ALLOCATE. + return (sessionId == AAUDIO_SESSION_ID_ALLOCATE || sessionId == AAUDIO_SESSION_ID_NONE) + ? AUDIO_SESSION_ALLOCATE + : (audio_session_t) sessionId; +} + audio_format_t AAudioConvert_aaudioToAndroidDataFormat(aaudio_format_t aaudioFormat) { audio_format_t androidFormat; switch (aaudioFormat) { @@ -315,17 +544,77 @@ return aaudioFormat; } +// Make a message string from the condition. +#define STATIC_ASSERT(condition) static_assert(condition, #condition) + +audio_usage_t AAudioConvert_usageToInternal(aaudio_usage_t usage) { + // The public aaudio_content_type_t constants are supposed to have the same + // values as the internal audio_content_type_t values. + STATIC_ASSERT(AAUDIO_USAGE_MEDIA == AUDIO_USAGE_MEDIA); + STATIC_ASSERT(AAUDIO_USAGE_VOICE_COMMUNICATION == AUDIO_USAGE_VOICE_COMMUNICATION); + STATIC_ASSERT(AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING + == AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING); + STATIC_ASSERT(AAUDIO_USAGE_ALARM == AUDIO_USAGE_ALARM); + STATIC_ASSERT(AAUDIO_USAGE_NOTIFICATION == AUDIO_USAGE_NOTIFICATION); + STATIC_ASSERT(AAUDIO_USAGE_NOTIFICATION_RINGTONE + == AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE); + STATIC_ASSERT(AAUDIO_USAGE_NOTIFICATION_EVENT == AUDIO_USAGE_NOTIFICATION_EVENT); + STATIC_ASSERT(AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY == AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY); + STATIC_ASSERT(AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE + == AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE); + STATIC_ASSERT(AAUDIO_USAGE_ASSISTANCE_SONIFICATION == AUDIO_USAGE_ASSISTANCE_SONIFICATION); + STATIC_ASSERT(AAUDIO_USAGE_GAME == AUDIO_USAGE_GAME); + STATIC_ASSERT(AAUDIO_USAGE_ASSISTANT == AUDIO_USAGE_ASSISTANT); + if (usage == AAUDIO_UNSPECIFIED) { + usage = AAUDIO_USAGE_MEDIA; + } + return (audio_usage_t) usage; // same value +} + +audio_content_type_t AAudioConvert_contentTypeToInternal(aaudio_content_type_t contentType) { + // The public aaudio_content_type_t constants are supposed to have the same + // values as the internal audio_content_type_t values. + STATIC_ASSERT(AAUDIO_CONTENT_TYPE_MUSIC == AUDIO_CONTENT_TYPE_MUSIC); + STATIC_ASSERT(AAUDIO_CONTENT_TYPE_SPEECH == AUDIO_CONTENT_TYPE_SPEECH); + STATIC_ASSERT(AAUDIO_CONTENT_TYPE_SONIFICATION == AUDIO_CONTENT_TYPE_SONIFICATION); + STATIC_ASSERT(AAUDIO_CONTENT_TYPE_MOVIE == AUDIO_CONTENT_TYPE_MOVIE); + if (contentType == AAUDIO_UNSPECIFIED) { + contentType = AAUDIO_CONTENT_TYPE_MUSIC; + } + return (audio_content_type_t) contentType; // same value +} + +audio_source_t AAudioConvert_inputPresetToAudioSource(aaudio_input_preset_t preset) { + // The public aaudio_input_preset_t constants are supposed to have the same + // values as the internal audio_source_t values. + STATIC_ASSERT(AAUDIO_UNSPECIFIED == AUDIO_SOURCE_DEFAULT); + STATIC_ASSERT(AAUDIO_INPUT_PRESET_GENERIC == AUDIO_SOURCE_MIC); + STATIC_ASSERT(AAUDIO_INPUT_PRESET_CAMCORDER == AUDIO_SOURCE_CAMCORDER); + STATIC_ASSERT(AAUDIO_INPUT_PRESET_VOICE_RECOGNITION == AUDIO_SOURCE_VOICE_RECOGNITION); + STATIC_ASSERT(AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION == AUDIO_SOURCE_VOICE_COMMUNICATION); + STATIC_ASSERT(AAUDIO_INPUT_PRESET_UNPROCESSED == AUDIO_SOURCE_UNPROCESSED); + if (preset == AAUDIO_UNSPECIFIED) { + preset = AAUDIO_INPUT_PRESET_VOICE_RECOGNITION; + } + return (audio_source_t) preset; // same value +} + int32_t AAudioConvert_framesToBytes(int32_t numFrames, - int32_t bytesPerFrame, - int32_t *sizeInBytes) { - // TODO implement more elegantly - const int32_t maxChannels = 256; // ridiculously large - const int32_t maxBytesPerFrame = maxChannels * sizeof(float); - // Prevent overflow by limiting multiplicands. - if (bytesPerFrame > maxBytesPerFrame || numFrames > (0x3FFFFFFF / maxBytesPerFrame)) { - ALOGE("size overflow, numFrames = %d, frameSize = %zd", numFrames, bytesPerFrame); + int32_t bytesPerFrame, + int32_t *sizeInBytes) { + *sizeInBytes = 0; + + if (numFrames < 0 || bytesPerFrame < 0) { + ALOGE("negative size, numFrames = %d, frameSize = %d", numFrames, bytesPerFrame); return AAUDIO_ERROR_OUT_OF_RANGE; } + + // Prevent numeric overflow. + if (numFrames > (INT32_MAX / bytesPerFrame)) { + ALOGE("size overflow, numFrames = %d, frameSize = %d", numFrames, bytesPerFrame); + return AAUDIO_ERROR_OUT_OF_RANGE; + } + *sizeInBytes = numFrames * bytesPerFrame; return AAUDIO_OK; } @@ -410,3 +699,31 @@ } return prop; } + +aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state) { + aaudio_result_t result = AAUDIO_OK; + switch (state) { +// Proceed with flushing. + case AAUDIO_STREAM_STATE_OPEN: + case AAUDIO_STREAM_STATE_PAUSED: + case AAUDIO_STREAM_STATE_STOPPED: + case AAUDIO_STREAM_STATE_FLUSHED: + break; + +// Transition from one inactive state to another. + case AAUDIO_STREAM_STATE_STARTING: + case AAUDIO_STREAM_STATE_STARTED: + case AAUDIO_STREAM_STATE_STOPPING: + case AAUDIO_STREAM_STATE_PAUSING: + case AAUDIO_STREAM_STATE_FLUSHING: + case AAUDIO_STREAM_STATE_CLOSING: + case AAUDIO_STREAM_STATE_CLOSED: + case AAUDIO_STREAM_STATE_DISCONNECTED: + default: + ALOGE("can only flush stream when PAUSED, OPEN or STOPPED, state = %s", + AAudio_convertStreamStateToText(state)); + result = AAUDIO_ERROR_INVALID_STATE; + break; + } + return result; +}
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h index 3afa976..4b975e8 100644 --- a/media/libaaudio/src/utility/AAudioUtilities.h +++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -23,7 +23,7 @@ #include <sys/types.h> #include <utils/Errors.h> -#include <hardware/audio.h> +#include <system/audio.h> #include "aaudio/AAudio.h" @@ -38,6 +38,13 @@ aaudio_result_t AAudioConvert_androidToAAudioResult(android::status_t status); /** + * Convert an aaudio_session_id_t to a value that is safe to pass to AudioFlinger. + * @param sessionId + * @return safe value + */ +audio_session_t AAudioConvert_aaudioToAndroidSessionId(aaudio_session_id_t sessionId); + +/** * Convert an array of floats to an array of int16_t. * * @param source @@ -152,21 +159,81 @@ float amplitude1, float amplitude2); +class AAudioDataConverter { +public: + + struct FormattedData { + + FormattedData(void *data, aaudio_format_t format, int32_t channelCount) + : data(data) + , format(format) + , channelCount(channelCount) {} + + const void *data = nullptr; + const aaudio_format_t format = AAUDIO_FORMAT_UNSPECIFIED; + const int32_t channelCount = 1; + }; + + static void convert(const FormattedData &source, + const FormattedData &destination, + int32_t numFrames, + float levelFrom, + float levelTo); + +private: + static void convertMonoToStereo(const FormattedData &source, + const FormattedData &destination, + int32_t numFrames, + float levelFrom, + float levelTo); + + static void convertChannelsMatch(const FormattedData &source, + const FormattedData &destination, + int32_t numFrames, + float levelFrom, + float levelTo); +}; + /** * Calculate the number of bytes and prevent numeric overflow. + * The *sizeInBytes will be set to zero if there is an error. + * * @param numFrames frame count * @param bytesPerFrame size of a frame in bytes - * @param sizeInBytes total size in bytes + * @param sizeInBytes pointer to a variable to receive total size in bytes * @return AAUDIO_OK or negative error, eg. AAUDIO_ERROR_OUT_OF_RANGE */ int32_t AAudioConvert_framesToBytes(int32_t numFrames, - int32_t bytesPerFrame, - int32_t *sizeInBytes); + int32_t bytesPerFrame, + int32_t *sizeInBytes); audio_format_t AAudioConvert_aaudioToAndroidDataFormat(aaudio_format_t aaudio_format); aaudio_format_t AAudioConvert_androidToAAudioDataFormat(audio_format_t format); + +/** + * Note that this function does not validate the passed in value. + * That is done somewhere else. + * @return internal value + */ + +audio_usage_t AAudioConvert_usageToInternal(aaudio_usage_t usage); + +/** + * Note that this function does not validate the passed in value. + * That is done somewhere else. + * @return internal value + */ +audio_content_type_t AAudioConvert_contentTypeToInternal(aaudio_content_type_t contentType); + +/** + * Note that this function does not validate the passed in value. + * That is done somewhere else. + * @return internal audio source + */ +audio_source_t AAudioConvert_inputPresetToAudioSource(aaudio_input_preset_t preset); + /** * @return the size of a sample of the given format in bytes or AAUDIO_ERROR_ILLEGAL_ARGUMENT */ @@ -235,6 +302,14 @@ */ int32_t AAudioProperty_getHardwareBurstMinMicros(); + +/** + * Is flush allowed for the given state? + * @param state + * @return AAUDIO_OK if allowed or an error + */ +aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state); + /** * Try a function f until it returns true. *
diff --git a/media/libaaudio/src/utility/AudioClock.h b/media/libaaudio/src/utility/AudioClock.h index 43b71b0..d5d4ef4 100644 --- a/media/libaaudio/src/utility/AudioClock.h +++ b/media/libaaudio/src/utility/AudioClock.h
@@ -17,6 +17,7 @@ #ifndef UTILITY_AUDIO_CLOCK_H #define UTILITY_AUDIO_CLOCK_H +#include <errno.h> #include <stdint.h> #include <time.h>
diff --git a/media/libaaudio/src/utility/LinearRamp.h b/media/libaaudio/src/utility/LinearRamp.h index ff09dce..2b1b8e0 100644 --- a/media/libaaudio/src/utility/LinearRamp.h +++ b/media/libaaudio/src/utility/LinearRamp.h
@@ -87,7 +87,7 @@ std::atomic<float> mTarget; - int32_t mLengthInFrames = 48000 / 50; // 20 msec at 48000 Hz + int32_t mLengthInFrames = 48000 / 100; // 10 msec at 48000 Hz int32_t mRemaining = 0; float mLevelFrom = 0.0f; float mLevelTo = 0.0f;
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp new file mode 100644 index 0000000..68194db --- /dev/null +++ b/media/libaaudio/tests/Android.bp
@@ -0,0 +1,169 @@ +cc_defaults { + name: "libaaudio_tests_defaults", + cflags: [ + "-Wall", + "-Werror", + ], +} + +cc_test { + name: "test_aaudio_marshalling", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_marshalling.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_block_adapter", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_block_adapter.cpp"], + shared_libs: ["libaaudio"], +} + +cc_test { + name: "test_timestamps", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_timestamps.cpp"], + header_libs: ["libaaudio_example_utils"], + shared_libs: ["libaaudio"], +} + +cc_test { + name: "test_linear_ramp", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_linear_ramp.cpp"], + shared_libs: ["libaaudio"], +} + +cc_test { + name: "test_open_params", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_open_params.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_no_close", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_no_close.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_aaudio_recovery", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_recovery.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_n_streams", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_n_streams.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_bad_disconnect", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_bad_disconnect.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_various", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_various.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_session_id", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_session_id.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_aaudio_monkey", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_aaudio_monkey.cpp"], + header_libs: ["libaaudio_example_utils"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_attributes", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_attributes.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_interference", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_interference.cpp"], + shared_libs: [ + "libaaudio", + "libbinder", + "libcutils", + "libutils", + ], +} + +cc_test { + name: "test_atomic_fifo", + defaults: ["libaaudio_tests_defaults"], + srcs: ["test_atomic_fifo.cpp"], + shared_libs: ["libaaudio"], +}
diff --git a/media/libaaudio/tests/Android.mk b/media/libaaudio/tests/Android.mk deleted file mode 100644 index 8117181..0000000 --- a/media/libaaudio/tests/Android.mk +++ /dev/null
@@ -1,92 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src -LOCAL_SRC_FILES:= test_marshalling.cpp -LOCAL_SHARED_LIBRARIES := libaaudio libbinder libcutils libutils -LOCAL_MODULE := test_aaudio_marshalling -include $(BUILD_NATIVE_TEST) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src -LOCAL_SRC_FILES:= test_block_adapter.cpp -LOCAL_SHARED_LIBRARIES := libaaudio -LOCAL_MODULE := test_block_adapter -include $(BUILD_NATIVE_TEST) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src \ - frameworks/av/media/libaaudio/examples -LOCAL_SRC_FILES:= test_timestamps.cpp -LOCAL_SHARED_LIBRARIES := libaaudio -LOCAL_MODULE := test_timestamps -include $(BUILD_NATIVE_TEST) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src -LOCAL_SRC_FILES:= test_linear_ramp.cpp -LOCAL_SHARED_LIBRARIES := libaaudio -LOCAL_MODULE := test_linear_ramp -include $(BUILD_NATIVE_TEST) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src -LOCAL_SRC_FILES:= test_open_params.cpp -LOCAL_SHARED_LIBRARIES := libaaudio libbinder libcutils libutils -LOCAL_MODULE := test_open_params -include $(BUILD_NATIVE_TEST) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src -LOCAL_SRC_FILES:= test_no_close.cpp -LOCAL_SHARED_LIBRARIES := libaaudio libbinder libcutils libutils -LOCAL_MODULE := test_no_close -include $(BUILD_NATIVE_TEST) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src -LOCAL_SRC_FILES:= test_recovery.cpp -LOCAL_SHARED_LIBRARIES := libaaudio libbinder libcutils libutils -LOCAL_MODULE := test_aaudio_recovery -include $(BUILD_NATIVE_TEST) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src -LOCAL_SRC_FILES:= test_n_streams.cpp -LOCAL_SHARED_LIBRARIES := libaaudio libbinder libcutils libutils -LOCAL_MODULE := test_n_streams -include $(BUILD_NATIVE_TEST) - -include $(CLEAR_VARS) -LOCAL_C_INCLUDES := \ - $(call include-path-for, audio-utils) \ - frameworks/av/media/libaaudio/include \ - frameworks/av/media/libaaudio/src -LOCAL_SRC_FILES:= test_atomic_fifo.cpp -LOCAL_SHARED_LIBRARIES := libaaudio libbinder libcutils libutils -LOCAL_MODULE := test_atomic_fifo -include $(BUILD_NATIVE_TEST)
diff --git a/media/libaaudio/tests/test_aaudio_monkey.cpp b/media/libaaudio/tests/test_aaudio_monkey.cpp new file mode 100644 index 0000000..be54835 --- /dev/null +++ b/media/libaaudio/tests/test_aaudio_monkey.cpp
@@ -0,0 +1,307 @@ +/* + * 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. + */ + +// Try to trigger bugs by playing randomly on multiple streams. + +#include <stdio.h> +#include <stdlib.h> +#include <vector> + +#include <aaudio/AAudio.h> +#include "AAudioArgsParser.h" +#include "AAudioExampleUtils.h" +#include "AAudioSimplePlayer.h" +#include "SineGenerator.h" + +#define DEFAULT_TIMEOUT_NANOS (1 * NANOS_PER_SECOND) + +#define NUM_LOOPS 1000 +#define MAX_MICROS_DELAY (2 * 1000 * 1000) + +// TODO Consider adding an input stream. +#define PROB_START (0.20) +#define PROB_PAUSE (PROB_START + 0.10) +#define PROB_FLUSH (PROB_PAUSE + 0.10) +#define PROB_STOP (PROB_FLUSH + 0.10) +#define PROB_CLOSE (PROB_STOP + 0.10) +static_assert(PROB_CLOSE < 0.9, "Probability sum too high."); + +aaudio_data_callback_result_t AAudioMonkeyDataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames); + +void AAudioMonkeyErrorCallbackProc( + AAudioStream *stream __unused, + void *userData __unused, + aaudio_result_t error) { + printf("Error Callback, error: %d\n",(int)error); +} + +// This function is not thread safe. Only use this from a single thread. +double nextRandomDouble() { + return drand48(); +} + +class AAudioMonkey : public AAudioSimplePlayer { +public: + + AAudioMonkey(int index, AAudioArgsParser *argParser) + : mArgParser(argParser) + , mIndex(index) {} + + aaudio_result_t open() { + printf("Monkey # %d ---------------------------------------------- OPEN\n", mIndex); + double offset = mIndex * 50; + mSine1.setup(440.0, 48000); + mSine1.setSweep(300.0 + offset, 600.0 + offset, 5.0); + mSine2.setup(660.0, 48000); + mSine2.setSweep(350.0 + offset, 900.0 + offset, 7.0); + + aaudio_result_t result = AAudioSimplePlayer::open(*mArgParser, + AAudioMonkeyDataCallback, + AAudioMonkeyErrorCallbackProc, + this); + if (result != AAUDIO_OK) { + printf("ERROR - player.open() returned %d\n", result); + } + + mArgParser->compareWithStream(getStream()); + return result; + } + + bool isOpen() { + return (getStream() != nullptr); + + } + /** + * + * @return true if stream passes tests + */ + bool validate() { + if (!isOpen()) return true; // closed is OK + + // update and query stream state + aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN; + aaudio_result_t result = AAudioStream_waitForStateChange(getStream(), + AAUDIO_STREAM_STATE_UNKNOWN, &state, 0); + if (result != AAUDIO_OK) { + printf("ERROR - AAudioStream_waitForStateChange returned %d\n", result); + return false; + } + + int64_t framesRead = AAudioStream_getFramesRead(getStream()); + int64_t framesWritten = AAudioStream_getFramesWritten(getStream()); + int32_t xRuns = AAudioStream_getXRunCount(getStream()); + // Print status + printf("%30s, framesWritten = %8lld, framesRead = %8lld, xRuns = %d\n", + AAudio_convertStreamStateToText(state), + (unsigned long long) framesWritten, + (unsigned long long) framesRead, + xRuns); + + if (framesWritten < framesRead) { + printf("WARNING - UNDERFLOW - diff = %d !!!!!!!!!!!!\n", + (int) (framesWritten - framesRead)); + } + return true; + } + + aaudio_result_t invoke() { + aaudio_result_t result = AAUDIO_OK; + if (!isOpen()) { + result = open(); + if (result != AAUDIO_OK) return result; + } + + if (!validate()) { + return -1; + } + + double dice = nextRandomDouble(); + // Select an action based on a weighted probability. + if (dice < PROB_START) { + printf("start\n"); + result = AAudioStream_requestStart(getStream()); + } else if (dice < PROB_PAUSE) { + printf("pause\n"); + result = AAudioStream_requestPause(getStream()); + } else if (dice < PROB_FLUSH) { + printf("flush\n"); + result = AAudioStream_requestFlush(getStream()); + } else if (dice < PROB_STOP) { + printf("stop\n"); + result = AAudioStream_requestStop(getStream()); + } else if (dice < PROB_CLOSE) { + printf("close\n"); + result = close(); + } else { + printf("do nothing\n"); + } + + if (result == AAUDIO_ERROR_INVALID_STATE) { + printf(" got AAUDIO_ERROR_INVALID_STATE - expected from a monkey\n"); + result = AAUDIO_OK; + } + if (result == AAUDIO_OK && isOpen()) { + if (!validate()) { + result = -1; + } + } + return result; + } + + aaudio_data_callback_result_t renderAudio( + AAudioStream *stream, + void *audioData, + int32_t numFrames) { + + int32_t samplesPerFrame = AAudioStream_getChannelCount(stream); + // This code only plays on the first one or two channels. + // TODO Support arbitrary number of channels. + switch (AAudioStream_getFormat(stream)) { + case AAUDIO_FORMAT_PCM_I16: { + int16_t *audioBuffer = (int16_t *) audioData; + // Render sine waves as shorts to first channel. + mSine1.render(&audioBuffer[0], samplesPerFrame, numFrames); + // Render sine waves to second channel if there is one. + if (samplesPerFrame > 1) { + mSine2.render(&audioBuffer[1], samplesPerFrame, numFrames); + } + } + break; + case AAUDIO_FORMAT_PCM_FLOAT: { + float *audioBuffer = (float *) audioData; + // Render sine waves as floats to first channel. + mSine1.render(&audioBuffer[0], samplesPerFrame, numFrames); + // Render sine waves to second channel if there is one. + if (samplesPerFrame > 1) { + mSine2.render(&audioBuffer[1], samplesPerFrame, numFrames); + } + } + break; + default: + return AAUDIO_CALLBACK_RESULT_STOP; + } + return AAUDIO_CALLBACK_RESULT_CONTINUE; + } + +private: + const AAudioArgsParser *mArgParser; + const int mIndex; + SineGenerator mSine1; + SineGenerator mSine2; +}; + +// Callback function that fills the audio output buffer. +aaudio_data_callback_result_t AAudioMonkeyDataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames +) { + // should not happen but just in case... + if (userData == nullptr) { + printf("ERROR - AAudioMonkeyDataCallback needs userData\n"); + return AAUDIO_CALLBACK_RESULT_STOP; + } + AAudioMonkey *monkey = (AAudioMonkey *) userData; + return monkey->renderAudio(stream, audioData, numFrames); +} + + +static void usage() { + AAudioArgsParser::usage(); + printf(" -i{seed} Initial random seed\n"); + printf(" -t{count} number of monkeys in the Troop\n"); +} + +int main(int argc, const char **argv) { + AAudioArgsParser argParser; + std::vector<AAudioMonkey> monkeys; + aaudio_result_t result; + int numMonkeys = 1; + + // Make printf print immediately so that debug info is not stuck + // in a buffer if we hang or crash. + setvbuf(stdout, nullptr, _IONBF, (size_t) 0); + + printf("%s - Monkeys\n", argv[0]); + + long int seed = (long int)getNanoseconds(); // different every time by default + + for (int i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (argParser.parseArg(arg)) { + // Handle options that are not handled by the ArgParser + if (arg[0] == '-') { + char option = arg[1]; + switch (option) { + case 'i': + seed = atol(&arg[2]); + break; + case 't': + numMonkeys = atoi(&arg[2]); + break; + default: + usage(); + exit(EXIT_FAILURE); + break; + } + } else { + usage(); + exit(EXIT_FAILURE); + break; + } + } + } + + srand48(seed); + printf("seed = %ld, nextRandomDouble() = %f\n", seed, nextRandomDouble()); + + for (int m = 0; m < numMonkeys; m++) { + monkeys.emplace_back(m, &argParser); + } + + for (int i = 0; i < NUM_LOOPS; i++) { + // pick a random monkey and invoke it + double dice = nextRandomDouble(); + int monkeyIndex = floor(dice * numMonkeys); + printf("----------- Monkey #%d\n", monkeyIndex); + result = monkeys[monkeyIndex].invoke(); + if (result != AAUDIO_OK) { + goto error; + } + + // sleep some random time + dice = nextRandomDouble(); + dice = dice * dice * dice; // skew towards smaller delays + int micros = (int) (dice * MAX_MICROS_DELAY); + usleep(micros); + + // TODO consider making this multi-threaded, one thread per monkey, to catch more bugs + } + + printf("PASS\n"); + return EXIT_SUCCESS; + +error: + printf("FAIL - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result)); + usleep(1000 * 1000); // give me time to stop the logcat + return EXIT_FAILURE; +} +
diff --git a/media/libaaudio/tests/test_attributes.cpp b/media/libaaudio/tests/test_attributes.cpp new file mode 100644 index 0000000..b01af25 --- /dev/null +++ b/media/libaaudio/tests/test_attributes.cpp
@@ -0,0 +1,179 @@ +/* + * 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. + */ + +// Test AAudio attributes such as Usage, ContentType and InputPreset. + +#include <stdio.h> +#include <unistd.h> + +#include <aaudio/AAudio.h> +#include <gtest/gtest.h> + +constexpr int64_t kNanosPerSecond = 1000000000; +constexpr int kNumFrames = 256; +constexpr int kChannelCount = 2; + +constexpr int32_t DONT_SET = -1000; + +static void checkAttributes(aaudio_performance_mode_t perfMode, + aaudio_usage_t usage, + aaudio_content_type_t contentType, + aaudio_input_preset_t preset = DONT_SET, + aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT) { + + float *buffer = new float[kNumFrames * kChannelCount]; + + AAudioStreamBuilder *aaudioBuilder = nullptr; + AAudioStream *aaudioStream = nullptr; + + // Use an AAudioStreamBuilder to contain requested parameters. + ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); + + // Request stream properties. + AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); + AAudioStreamBuilder_setDirection(aaudioBuilder, direction); + + // Set the attribute in the builder. + if (usage != DONT_SET) { + AAudioStreamBuilder_setUsage(aaudioBuilder, usage); + } + if (contentType != DONT_SET) { + AAudioStreamBuilder_setContentType(aaudioBuilder, contentType); + } + if (preset != DONT_SET) { + AAudioStreamBuilder_setInputPreset(aaudioBuilder, preset); + } + + // Create an AAudioStream using the Builder. + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); + AAudioStreamBuilder_delete(aaudioBuilder); + + // Make sure we get the same attributes back from the stream. + aaudio_usage_t expectedUsage = + (usage == DONT_SET || usage == AAUDIO_UNSPECIFIED) + ? AAUDIO_USAGE_MEDIA // default + : usage; + EXPECT_EQ(expectedUsage, AAudioStream_getUsage(aaudioStream)); + + aaudio_content_type_t expectedContentType = + (contentType == DONT_SET || contentType == AAUDIO_UNSPECIFIED) + ? AAUDIO_CONTENT_TYPE_MUSIC // default + : contentType; + EXPECT_EQ(expectedContentType, AAudioStream_getContentType(aaudioStream)); + + aaudio_input_preset_t expectedPreset = + (preset == DONT_SET || preset == AAUDIO_UNSPECIFIED) + ? AAUDIO_INPUT_PRESET_VOICE_RECOGNITION // default + : preset; + EXPECT_EQ(expectedPreset, AAudioStream_getInputPreset(aaudioStream)); + + EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream)); + + if (direction == AAUDIO_DIRECTION_INPUT) { + EXPECT_EQ(kNumFrames, + AAudioStream_read(aaudioStream, buffer, kNumFrames, kNanosPerSecond)); + } else { + EXPECT_EQ(kNumFrames, + AAudioStream_write(aaudioStream, buffer, kNumFrames, kNanosPerSecond)); + } + + EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); + + EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream)); + delete[] buffer; +} + +static const aaudio_usage_t sUsages[] = { + DONT_SET, + AAUDIO_UNSPECIFIED, + AAUDIO_USAGE_MEDIA, + AAUDIO_USAGE_VOICE_COMMUNICATION, + AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING, + AAUDIO_USAGE_ALARM, + AAUDIO_USAGE_NOTIFICATION, + AAUDIO_USAGE_NOTIFICATION_RINGTONE, + AAUDIO_USAGE_NOTIFICATION_EVENT, + AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY, + AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, + AAUDIO_USAGE_ASSISTANCE_SONIFICATION, + AAUDIO_USAGE_GAME, + AAUDIO_USAGE_ASSISTANT +}; + +static const aaudio_content_type_t sContentypes[] = { + DONT_SET, + AAUDIO_UNSPECIFIED, + AAUDIO_CONTENT_TYPE_SPEECH, + AAUDIO_CONTENT_TYPE_MUSIC, + AAUDIO_CONTENT_TYPE_MOVIE, + AAUDIO_CONTENT_TYPE_SONIFICATION +}; + +static const aaudio_input_preset_t sInputPresets[] = { + DONT_SET, + AAUDIO_UNSPECIFIED, + AAUDIO_INPUT_PRESET_GENERIC, + AAUDIO_INPUT_PRESET_CAMCORDER, + AAUDIO_INPUT_PRESET_VOICE_RECOGNITION, + AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION, + AAUDIO_INPUT_PRESET_UNPROCESSED, +}; + +static void checkAttributesUsage(aaudio_performance_mode_t perfMode) { + for (aaudio_usage_t usage : sUsages) { + checkAttributes(perfMode, usage, DONT_SET); + } +} + +static void checkAttributesContentType(aaudio_input_preset_t perfMode) { + for (aaudio_content_type_t contentType : sContentypes) { + checkAttributes(perfMode, DONT_SET, contentType); + } +} + +static void checkAttributesInputPreset(aaudio_performance_mode_t perfMode) { + for (aaudio_input_preset_t inputPreset : sInputPresets) { + checkAttributes(perfMode, + DONT_SET, + DONT_SET, + inputPreset, + AAUDIO_DIRECTION_INPUT); + } +} + +TEST(test_attributes, aaudio_usage_perfnone) { + checkAttributesUsage(AAUDIO_PERFORMANCE_MODE_NONE); +} + +TEST(test_attributes, aaudio_content_type_perfnone) { + checkAttributesContentType(AAUDIO_PERFORMANCE_MODE_NONE); +} + +TEST(test_attributes, aaudio_input_preset_perfnone) { + checkAttributesInputPreset(AAUDIO_PERFORMANCE_MODE_NONE); +} + +TEST(test_attributes, aaudio_usage_lowlat) { + checkAttributesUsage(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); +} + +TEST(test_attributes, aaudio_content_type_lowlat) { + checkAttributesContentType(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); +} + +TEST(test_attributes, aaudio_input_preset_lowlat) { + checkAttributesInputPreset(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); +}
diff --git a/media/libaaudio/tests/test_bad_disconnect.cpp b/media/libaaudio/tests/test_bad_disconnect.cpp new file mode 100644 index 0000000..435990d --- /dev/null +++ b/media/libaaudio/tests/test_bad_disconnect.cpp
@@ -0,0 +1,168 @@ +/* + * 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. + */ + +/** + * Handle a DISCONNECT by only opening and starting a new stream + * without stopping and closing the old one. + * This caused the new stream to use the old disconnected device. + */ + +#include <stdio.h> +#include <thread> +#include <unistd.h> + +#include <aaudio/AAudio.h> + +#define DEFAULT_TIMEOUT_NANOS ((int64_t)1000000000) + +static void s_myErrorCallbackProc( + AAudioStream *stream, + void *userData, + aaudio_result_t error); + +struct AudioEngine { + AAudioStreamBuilder *builder = nullptr; + AAudioStream *stream = nullptr; + std::thread *thread = nullptr; + int64_t framesRead = 0; +}; + +AudioEngine s_AudioEngine; + +// Callback function that fills the audio output buffer. +static aaudio_data_callback_result_t s_myDataCallbackProc( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames +) { + (void) userData; + (void) audioData; + (void) numFrames; + s_AudioEngine.framesRead = AAudioStream_getFramesRead(stream); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +static aaudio_result_t s_StartAudio() { + int32_t framesPerBurst = 0; + int32_t deviceId = 0; + + // Use an AAudioStreamBuilder to contain requested parameters. + aaudio_result_t result = AAudio_createStreamBuilder(&s_AudioEngine.builder); + if (result != AAUDIO_OK) { + printf("AAudio_createStreamBuilder returned %s", + AAudio_convertResultToText(result)); + return result; + } + + // Request stream properties. + AAudioStreamBuilder_setFormat(s_AudioEngine.builder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setPerformanceMode(s_AudioEngine.builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(s_AudioEngine.builder, s_myDataCallbackProc, nullptr); + AAudioStreamBuilder_setErrorCallback(s_AudioEngine.builder, s_myErrorCallbackProc, nullptr); + + // Create an AAudioStream using the Builder. + result = AAudioStreamBuilder_openStream(s_AudioEngine.builder, &s_AudioEngine.stream); + if (result != AAUDIO_OK) { + printf("AAudioStreamBuilder_openStream returned %s", + AAudio_convertResultToText(result)); + return result; + } + + result = AAudioStream_requestStart(s_AudioEngine.stream); + if (result != AAUDIO_OK) { + printf("AAudioStream_requestStart returned %s", + AAudio_convertResultToText(result)); + } + + // Check to see what kind of stream we actually got. + deviceId = AAudioStream_getDeviceId(s_AudioEngine.stream); + framesPerBurst = AAudioStream_getFramesPerBurst(s_AudioEngine.stream); + + printf("-------- started: deviceId = %3d, framesPerBurst = %3d\n", deviceId, framesPerBurst); + + return result; +} + +static aaudio_result_t s_StopAudio() { + aaudio_result_t result = AAUDIO_OK; + if (s_AudioEngine.stream != nullptr) { + result = AAudioStream_requestStop(s_AudioEngine.stream); + if (result != AAUDIO_OK) { + printf("AAudioStream_requestStop returned %s\n", + AAudio_convertResultToText(result)); + } + result = AAudioStream_close(s_AudioEngine.stream); + if (result != AAUDIO_OK) { + printf("AAudioStream_close returned %s\n", + AAudio_convertResultToText(result)); + } + s_AudioEngine.stream = nullptr; + AAudioStreamBuilder_delete(s_AudioEngine.builder); + s_AudioEngine.builder = nullptr; + } + return result; +} + +static void s_StartThreadProc() { + // A good app would call s_StopAudio here! This test simulates a bad app. + s_StartAudio(); + s_AudioEngine.thread = nullptr; +} + +static void s_myErrorCallbackProc( + AAudioStream *stream __unused, + void *userData __unused, + aaudio_result_t error) { + if (error == AAUDIO_ERROR_DISCONNECTED) { + // Handle stream restart on a separate thread + if (s_AudioEngine.thread == nullptr) { + s_AudioEngine.thread = new std::thread(s_StartThreadProc); + } + } +} + +int main(int argc, char **argv) { + (void) argc; + (void) argv; + + aaudio_result_t result = AAUDIO_OK; + + // 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 Bad Disconnect V1.0\n"); + printf("\n=========== Please PLUG and UNPLUG headphones! ==============\n\n"); + printf("You should see the deviceID change on each plug event.\n"); + printf("Headphones will generally get a new deviceId each time.\n"); + printf("Speakers will have the same deviceId each time.\n"); + printf("The framesRead should reset on each plug event then increase over time.\n"); + printf("\n"); + + result = s_StartAudio(); + + if (result == AAUDIO_OK) { + for (int i = 20; i > 0; i--) { + sleep(1); + printf("playing silence #%d, framesRead = %d\n", i, (int) s_AudioEngine.framesRead); + } + } + + s_StopAudio(); + + printf("result = %d = %s\n", result, AAudio_convertResultToText(result)); +}
diff --git a/media/libaaudio/tests/test_interference.cpp b/media/libaaudio/tests/test_interference.cpp new file mode 100644 index 0000000..7eaf225 --- /dev/null +++ b/media/libaaudio/tests/test_interference.cpp
@@ -0,0 +1,97 @@ +/* + * 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. + */ + +// Play a shared stream that might use MMAP. +// Then play a second stream at a different sample rate. +// Make sure the first stream is still running. +// See: b/73369112 | AAudio disconnects shared stream if second MMAP open fails + +#include <memory.h> +#include <stdio.h> +#include <unistd.h> + +#include <android-base/macros.h> +#include <aaudio/AAudio.h> + +#include <gtest/gtest.h> + +// Callback function that fills the audio output buffer. +aaudio_data_callback_result_t MyDataCallbackProc( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames) { + (void) userData; + int32_t numSamples = AAudioStream_getChannelCount(stream) * numFrames; + aaudio_format_t format = AAudioStream_getFormat(stream); + if (format == AAUDIO_FORMAT_PCM_I16) { + memset(audioData, 0, numSamples * sizeof(int16_t)); + } else if (format == AAUDIO_FORMAT_PCM_FLOAT) { + memset(audioData, 0, numSamples * sizeof(float)); + } + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +//void foo() { // for tricking the Android Studio formatter +TEST(test_interference, aaudio_mmap_interference) { + + AAudioStreamBuilder *aaudioBuilder = nullptr; + AAudioStream *aaudioStream1 = nullptr; + AAudioStream *aaudioStream2 = nullptr; + + // Use an AAudioStreamBuilder to contain requested parameters. + ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); + + // Request stream properties. + AAudioStreamBuilder_setSampleRate(aaudioBuilder, 48000); + AAudioStreamBuilder_setDataCallback(aaudioBuilder, MyDataCallbackProc, nullptr); + AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + + // Create an AAudioStream using the Builder. + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1)); + // Start it running. + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1)); + + // Verify that the stream is running. + sleep(1); + EXPECT_LT(0, AAudioStream_getFramesRead(aaudioStream1)); + ASSERT_EQ(AAUDIO_STREAM_STATE_STARTED, AAudioStream_getState(aaudioStream1)); + + // Now try to open a second stream with a different rate. + AAudioStreamBuilder_setSampleRate(aaudioBuilder, 44100); + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream2)); + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream2)); + + // Verify that the second stream is running. + sleep(1); + EXPECT_LT(0, AAudioStream_getFramesRead(aaudioStream2)); + + EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, AAudioStream_getState(aaudioStream2)); + + // Now verify that the first stream is still running. + EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, AAudioStream_getState(aaudioStream1)); + + int32_t framesRead1_1 = AAudioStream_getFramesRead(aaudioStream1); + EXPECT_LT(0, framesRead1_1); + sleep(1); + int32_t framesRead1_2 = AAudioStream_getFramesRead(aaudioStream1); + EXPECT_LT(0, framesRead1_2); + EXPECT_LT(framesRead1_1, framesRead1_2); // advancing? + + AAudioStream_close(aaudioStream2); + AAudioStream_close(aaudioStream1); + AAudioStreamBuilder_delete(aaudioBuilder); +}
diff --git a/media/libaaudio/tests/test_linear_ramp.cpp b/media/libaaudio/tests/test_linear_ramp.cpp index 5c53982..93226ba 100644 --- a/media/libaaudio/tests/test_linear_ramp.cpp +++ b/media/libaaudio/tests/test_linear_ramp.cpp
@@ -15,13 +15,13 @@ */ #include <iostream> +#include <math.h> #include <gtest/gtest.h> #include "utility/AAudioUtilities.h" #include "utility/LinearRamp.h" - TEST(test_linear_ramp, linear_ramp_segments) { LinearRamp ramp; const float source[4] = {1.0f, 1.0f, 1.0f, 1.0f }; @@ -32,40 +32,40 @@ ramp.setLengthInFrames(8); ramp.setTarget(8.0f); - ASSERT_EQ(8, ramp.getLengthInFrames()); + EXPECT_EQ(8, ramp.getLengthInFrames()); bool ramping = ramp.nextSegment(4, &levelFrom, &levelTo); - ASSERT_EQ(1, ramping); - ASSERT_EQ(0.0f, levelFrom); - ASSERT_EQ(4.0f, levelTo); + EXPECT_EQ(1, ramping); + EXPECT_EQ(0.0f, levelFrom); + EXPECT_EQ(4.0f, levelTo); AAudio_linearRamp(source, destination, 4, 1, levelFrom, levelTo); - ASSERT_EQ(0.0f, destination[0]); - ASSERT_EQ(1.0f, destination[1]); - ASSERT_EQ(2.0f, destination[2]); - ASSERT_EQ(3.0f, destination[3]); + EXPECT_EQ(0.0f, destination[0]); + EXPECT_EQ(1.0f, destination[1]); + EXPECT_EQ(2.0f, destination[2]); + EXPECT_EQ(3.0f, destination[3]); ramping = ramp.nextSegment(4, &levelFrom, &levelTo); - ASSERT_EQ(1, ramping); - ASSERT_EQ(4.0f, levelFrom); - ASSERT_EQ(8.0f, levelTo); + EXPECT_EQ(1, ramping); + EXPECT_EQ(4.0f, levelFrom); + EXPECT_EQ(8.0f, levelTo); AAudio_linearRamp(source, destination, 4, 1, levelFrom, levelTo); - ASSERT_EQ(4.0f, destination[0]); - ASSERT_EQ(5.0f, destination[1]); - ASSERT_EQ(6.0f, destination[2]); - ASSERT_EQ(7.0f, destination[3]); + EXPECT_EQ(4.0f, destination[0]); + EXPECT_EQ(5.0f, destination[1]); + EXPECT_EQ(6.0f, destination[2]); + EXPECT_EQ(7.0f, destination[3]); ramping = ramp.nextSegment(4, &levelFrom, &levelTo); - ASSERT_EQ(0, ramping); - ASSERT_EQ(8.0f, levelFrom); - ASSERT_EQ(8.0f, levelTo); + EXPECT_EQ(0, ramping); + EXPECT_EQ(8.0f, levelFrom); + EXPECT_EQ(8.0f, levelTo); AAudio_linearRamp(source, destination, 4, 1, levelFrom, levelTo); - ASSERT_EQ(8.0f, destination[0]); - ASSERT_EQ(8.0f, destination[1]); - ASSERT_EQ(8.0f, destination[2]); - ASSERT_EQ(8.0f, destination[3]); + EXPECT_EQ(8.0f, destination[0]); + EXPECT_EQ(8.0f, destination[1]); + EXPECT_EQ(8.0f, destination[2]); + EXPECT_EQ(8.0f, destination[3]); }; @@ -80,29 +80,101 @@ ramp.setLengthInFrames(4); ramp.setTarget(8.0f); ramp.forceCurrent(4.0f); - ASSERT_EQ(4.0f, ramp.getCurrent()); + EXPECT_EQ(4.0f, ramp.getCurrent()); bool ramping = ramp.nextSegment(4, &levelFrom, &levelTo); - ASSERT_EQ(1, ramping); - ASSERT_EQ(4.0f, levelFrom); - ASSERT_EQ(8.0f, levelTo); + EXPECT_EQ(1, ramping); + EXPECT_EQ(4.0f, levelFrom); + EXPECT_EQ(8.0f, levelTo); AAudio_linearRamp(source, destination, 4, 1, levelFrom, levelTo); - ASSERT_EQ(4.0f, destination[0]); - ASSERT_EQ(5.0f, destination[1]); - ASSERT_EQ(6.0f, destination[2]); - ASSERT_EQ(7.0f, destination[3]); + EXPECT_EQ(4.0f, destination[0]); + EXPECT_EQ(5.0f, destination[1]); + EXPECT_EQ(6.0f, destination[2]); + EXPECT_EQ(7.0f, destination[3]); ramping = ramp.nextSegment(4, &levelFrom, &levelTo); - ASSERT_EQ(0, ramping); - ASSERT_EQ(8.0f, levelFrom); - ASSERT_EQ(8.0f, levelTo); + EXPECT_EQ(0, ramping); + EXPECT_EQ(8.0f, levelFrom); + EXPECT_EQ(8.0f, levelTo); AAudio_linearRamp(source, destination, 4, 1, levelFrom, levelTo); - ASSERT_EQ(8.0f, destination[0]); - ASSERT_EQ(8.0f, destination[1]); - ASSERT_EQ(8.0f, destination[2]); - ASSERT_EQ(8.0f, destination[3]); + EXPECT_EQ(8.0f, destination[0]); + EXPECT_EQ(8.0f, destination[1]); + EXPECT_EQ(8.0f, destination[2]); + EXPECT_EQ(8.0f, destination[3]); }; +constexpr int16_t kMaxI16 = INT16_MAX; +constexpr int16_t kMinI16 = INT16_MIN; +constexpr int16_t kHalfI16 = 16384; +constexpr int16_t kTenthI16 = 3277; + +//void AAudioConvert_floatToPcm16(const float *source, +// int16_t *destination, +// int32_t numSamples, +// float amplitude); +TEST(test_linear_ramp, float_to_i16) { + const float source[] = {12345.6f, 1.0f, 0.5f, 0.1f, 0.0f, -0.1f, -0.5f, -1.0f, -12345.6f}; + constexpr size_t count = sizeof(source) / sizeof(source[0]); + int16_t destination[count]; + const int16_t expected[count] = {kMaxI16, kMaxI16, kHalfI16, kTenthI16, 0, + -kTenthI16, -kHalfI16, kMinI16, kMinI16}; + + AAudioConvert_floatToPcm16(source, destination, count, 1.0f); + for (size_t i = 0; i < count; i++) { + EXPECT_EQ(expected[i], destination[i]); + } + +} + +//void AAudioConvert_pcm16ToFloat(const int16_t *source, +// float *destination, +// int32_t numSamples, +// float amplitude); +TEST(test_linear_ramp, i16_to_float) { + const int16_t source[] = {kMaxI16, kHalfI16, kTenthI16, 0, + -kTenthI16, -kHalfI16, kMinI16}; + constexpr size_t count = sizeof(source) / sizeof(source[0]); + float destination[count]; + const float expected[count] = {(32767.0f / 32768.0f), 0.5f, 0.1f, 0.0f, -0.1f, -0.5f, -1.0f}; + + AAudioConvert_pcm16ToFloat(source, destination, count, 1.0f); + for (size_t i = 0; i < count; i++) { + EXPECT_NEAR(expected[i], destination[i], 0.0001f); + } + +} + +//void AAudio_linearRamp(const int16_t *source, +// int16_t *destination, +// int32_t numFrames, +// int32_t samplesPerFrame, +// float amplitude1, +// float amplitude2); +TEST(test_linear_ramp, ramp_i16_to_i16) { + const int16_t source[] = {1, 1, 1, 1, 1, 1, 1, 1}; + constexpr size_t count = sizeof(source) / sizeof(source[0]); + int16_t destination[count]; + // Ramp will sweep from -1 to almost +1 + const int16_t expected[count] = { + -1, // from -1.00 + -1, // from -0.75 + -1, // from -0.55, round away from zero + 0, // from -0.25, round up to zero + 0, // from 0.00 + 0, // from 0.25, round down to zero + 1, // from 0.50, round away from zero + 1 // from 0.75 + }; + + // sweep across zero to test symmetry + constexpr float amplitude1 = -1.0; + constexpr float amplitude2 = 1.0; + AAudio_linearRamp(source, destination, count, 1, amplitude1, amplitude2); + for (size_t i = 0; i < count; i++) { + EXPECT_EQ(expected[i], destination[i]); + } + +}
diff --git a/media/libaaudio/tests/test_n_streams.cpp b/media/libaaudio/tests/test_n_streams.cpp index 271d024..e2d4a82 100644 --- a/media/libaaudio/tests/test_n_streams.cpp +++ b/media/libaaudio/tests/test_n_streams.cpp
@@ -71,7 +71,6 @@ AAudioStreamBuilder_delete(aaudioBuilder); -finish: return result; }
diff --git a/media/libaaudio/tests/test_open_params.cpp b/media/libaaudio/tests/test_open_params.cpp index 01b8799..3451242 100644 --- a/media/libaaudio/tests/test_open_params.cpp +++ b/media/libaaudio/tests/test_open_params.cpp
@@ -25,21 +25,6 @@ #include <gtest/gtest.h> -static const char *getSharingModeText(aaudio_sharing_mode_t mode) { - const char *modeText = "unknown"; - switch (mode) { - case AAUDIO_SHARING_MODE_EXCLUSIVE: - modeText = "EXCLUSIVE"; - break; - case AAUDIO_SHARING_MODE_SHARED: - modeText = "SHARED"; - break; - default: - break; - } - return modeText; -} - // Callback function that fills the audio output buffer. aaudio_data_callback_result_t MyDataCallbackProc( AAudioStream *stream, @@ -67,7 +52,6 @@ int32_t actualChannelCount = 0; int32_t actualSampleRate = 0; aaudio_format_t actualDataFormat = AAUDIO_FORMAT_UNSPECIFIED; - aaudio_sharing_mode_t actualSharingMode = AAUDIO_SHARING_MODE_SHARED; aaudio_direction_t actualDirection; AAudioStreamBuilder *aaudioBuilder = nullptr;
diff --git a/media/libaaudio/tests/test_recovery.cpp b/media/libaaudio/tests/test_recovery.cpp index 7268a30..6e89f83 100644 --- a/media/libaaudio/tests/test_recovery.cpp +++ b/media/libaaudio/tests/test_recovery.cpp
@@ -23,24 +23,9 @@ #define DEFAULT_TIMEOUT_NANOS ((int64_t)1000000000) -static const char *getSharingModeText(aaudio_sharing_mode_t mode) { - const char *modeText = "unknown"; - switch (mode) { - case AAUDIO_SHARING_MODE_EXCLUSIVE: - modeText = "EXCLUSIVE"; - break; - case AAUDIO_SHARING_MODE_SHARED: - modeText = "SHARED"; - break; - default: - break; - } - return modeText; -} - int main(int argc, char **argv) { (void) argc; - (void *)argv; + (void) argv; aaudio_result_t result = AAUDIO_OK; @@ -52,7 +37,6 @@ int32_t actualChannelCount = 0; int32_t actualSampleRate = 0; aaudio_format_t actualDataFormat = AAUDIO_FORMAT_PCM_FLOAT; - aaudio_sharing_mode_t actualSharingMode = AAUDIO_SHARING_MODE_SHARED; AAudioStreamBuilder *aaudioBuilder = nullptr; AAudioStream *aaudioStream = nullptr;
diff --git a/media/libaaudio/tests/test_session_id.cpp b/media/libaaudio/tests/test_session_id.cpp new file mode 100644 index 0000000..3f7d4fc --- /dev/null +++ b/media/libaaudio/tests/test_session_id.cpp
@@ -0,0 +1,166 @@ +/* + * 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. + */ + +// Test AAudio SessionId, which is used to associate Effects with a stream + +#include <stdio.h> +#include <unistd.h> + +#include <aaudio/AAudio.h> +#include <gtest/gtest.h> + +constexpr int64_t kNanosPerSecond = 1000000000; +constexpr int kNumFrames = 256; +constexpr int kChannelCount = 2; + +// Test AAUDIO_SESSION_ID_NONE default +static void checkSessionIdNone(aaudio_performance_mode_t perfMode) { + + float *buffer = new float[kNumFrames * kChannelCount]; + + AAudioStreamBuilder *aaudioBuilder = nullptr; + + AAudioStream *aaudioStream1 = nullptr; + int32_t sessionId1 = 0; + + // Use an AAudioStreamBuilder to contain requested parameters. + ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); + + // Request stream properties. + AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); + + // Create an AAudioStream using the Builder. + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1)); + + // Since we did not request or specify a SessionID, we should get NONE + sessionId1 = AAudioStream_getSessionId(aaudioStream1); + ASSERT_EQ(AAUDIO_SESSION_ID_NONE, sessionId1); + + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1)); + + ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream1, buffer, kNumFrames, kNanosPerSecond)); + + EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream1)); + + EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream1)); + delete[] buffer; + AAudioStreamBuilder_delete(aaudioBuilder); +} + +TEST(test_session_id, aaudio_session_id_none_perfnone) { + checkSessionIdNone(AAUDIO_PERFORMANCE_MODE_NONE); +} + +TEST(test_session_id, aaudio_session_id_none_lowlat) { + checkSessionIdNone(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); +} + +// Test AAUDIO_SESSION_ID_ALLOCATE +static void checkSessionIdAllocate(aaudio_performance_mode_t perfMode, + aaudio_direction_t direction) { + + float *buffer = new float[kNumFrames * kChannelCount]; + + AAudioStreamBuilder *aaudioBuilder = nullptr; + + AAudioStream *aaudioStream1 = nullptr; + int32_t sessionId1 = 0; + AAudioStream *aaudioStream2 = nullptr; + int32_t sessionId2 = 0; + + // Use an AAudioStreamBuilder to contain requested parameters. + ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); + + // Request stream properties. + AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); + // This stream could be input or output. + AAudioStreamBuilder_setDirection(aaudioBuilder, direction); + + // Ask AAudio to allocate a Session ID. + AAudioStreamBuilder_setSessionId(aaudioBuilder, AAUDIO_SESSION_ID_ALLOCATE); + + // Create an AAudioStream using the Builder. + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1)); + + // Get the allocated ID from the stream. + sessionId1 = AAudioStream_getSessionId(aaudioStream1); + + // Check for invalid session IDs. + ASSERT_NE(AAUDIO_SESSION_ID_NONE, sessionId1); + ASSERT_NE(AAUDIO_SESSION_ID_ALLOCATE, sessionId1); + + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1)); + + if (direction == AAUDIO_DIRECTION_INPUT) { + ASSERT_EQ(kNumFrames, AAudioStream_read(aaudioStream1, + buffer, kNumFrames, kNanosPerSecond)); + } else { + ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream1, + buffer, kNumFrames, kNanosPerSecond)); + } + + EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream1)); + + // Now open a second stream using the same session ID. ================== + AAudioStreamBuilder_setSessionId(aaudioBuilder, sessionId1); + + // Reverse direction for second stream. + aaudio_direction_t otherDirection = (direction == AAUDIO_DIRECTION_OUTPUT) + ? AAUDIO_DIRECTION_INPUT + : AAUDIO_DIRECTION_OUTPUT; + AAudioStreamBuilder_setDirection(aaudioBuilder, otherDirection); + + // Create an AAudioStream using the Builder. + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream2)); + + // Get the allocated ID from the stream. + // It should match the ID that we set it to in the builder. + sessionId2 = AAudioStream_getSessionId(aaudioStream2); + ASSERT_EQ(sessionId1, sessionId2); + + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream2)); + + if (otherDirection == AAUDIO_DIRECTION_INPUT) { + ASSERT_EQ(kNumFrames, AAudioStream_read(aaudioStream2, + buffer, kNumFrames, kNanosPerSecond)); + } else { + ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream2, + buffer, kNumFrames, kNanosPerSecond)); + } + + EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream2)); + + EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream2)); + + + EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream1)); + delete[] buffer; + AAudioStreamBuilder_delete(aaudioBuilder); +} + +TEST(test_session_id, aaudio_session_id_alloc_perfnone_in) { + checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_DIRECTION_INPUT); +} +TEST(test_session_id, aaudio_session_id_alloc_perfnone_out) { + checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_DIRECTION_OUTPUT); +} + +TEST(test_session_id, aaudio_session_id_alloc_lowlat_in) { + checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_DIRECTION_INPUT); +} +TEST(test_session_id, aaudio_session_id_alloc_lowlat_out) { + checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_DIRECTION_OUTPUT); +}
diff --git a/media/libaaudio/tests/test_timestamps.cpp b/media/libaaudio/tests/test_timestamps.cpp index fb363e7..dfa7815 100644 --- a/media/libaaudio/tests/test_timestamps.cpp +++ b/media/libaaudio/tests/test_timestamps.cpp
@@ -22,8 +22,7 @@ #include <aaudio/AAudio.h> #include <aaudio/AAudioTesting.h> -#include "utils/AAudioExampleUtils.h" -#include "../examples/utils/AAudioExampleUtils.h" +#include "AAudioExampleUtils.h" // Arbitrary period for glitches, once per second at 48000 Hz. #define FORCED_UNDERRUN_PERIOD_FRAMES 48000 @@ -111,8 +110,6 @@ aaudio_result_t result = AAUDIO_OK; int32_t framesPerBurst = 0; - float *buffer = nullptr; - int32_t actualChannelCount = 0; int32_t actualSampleRate = 0; int32_t originalBufferSize = 0; @@ -287,7 +284,7 @@ int main(int argc, char **argv) { (void) argc; - (void *) argv; + (void) argv; aaudio_result_t result = AAUDIO_OK;
diff --git a/media/libaaudio/tests/test_various.cpp b/media/libaaudio/tests/test_various.cpp new file mode 100644 index 0000000..4b065c9 --- /dev/null +++ b/media/libaaudio/tests/test_various.cpp
@@ -0,0 +1,628 @@ +/* + * 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. + */ + +// Test various AAudio features including AAudioStream_setBufferSizeInFrames(). + +#include <condition_variable> +#include <mutex> +#include <stdio.h> + +#include <android-base/macros.h> +#include <aaudio/AAudio.h> + +#include <gtest/gtest.h> +#include <unistd.h> + +// Callback function that does nothing. +aaudio_data_callback_result_t NoopDataCallbackProc( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames +) { + (void) stream; + (void) userData; + (void) audioData; + (void) numFrames; + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +// Test AAudioStream_setBufferSizeInFrames() + +constexpr int64_t NANOS_PER_MILLISECOND = 1000 * 1000; + +enum FunctionToCall { + CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH +}; + +void checkStateTransition(aaudio_performance_mode_t perfMode, + aaudio_stream_state_t originalState, + FunctionToCall functionToCall, + aaudio_result_t expectedResult, + aaudio_stream_state_t expectedState) { + AAudioStreamBuilder *aaudioBuilder = nullptr; + AAudioStream *aaudioStream = nullptr; + + // Use an AAudioStreamBuilder to contain requested parameters. + ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); + + // Request stream properties. + AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr); + AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); + + // Create an AAudioStream using the Builder. + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); + + // Verify Open State + aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN; + EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream, + AAUDIO_STREAM_STATE_UNKNOWN, &state, + 1000 * NANOS_PER_MILLISECOND)); + EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state); + + // Put stream into desired state. + aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_UNINITIALIZED; + if (originalState != AAUDIO_STREAM_STATE_OPEN) { + + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream)); + + if (originalState != AAUDIO_STREAM_STATE_STARTING) { + + ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream, + AAUDIO_STREAM_STATE_STARTING, + &state, + 1000 * NANOS_PER_MILLISECOND)); + ASSERT_EQ(AAUDIO_STREAM_STATE_STARTED, state); + + if (originalState == AAUDIO_STREAM_STATE_STOPPING) { + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); + } else if (originalState == AAUDIO_STREAM_STATE_STOPPED) { + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); + inputState = AAUDIO_STREAM_STATE_STOPPING; + } else if (originalState == AAUDIO_STREAM_STATE_PAUSING) { + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream)); + } else if (originalState == AAUDIO_STREAM_STATE_PAUSED) { + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream)); + inputState = AAUDIO_STREAM_STATE_PAUSING; + } + } + } + + // Wait until past transitional state. + if (inputState != AAUDIO_STREAM_STATE_UNINITIALIZED) { + ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream, + inputState, + &state, + 1000 * NANOS_PER_MILLISECOND)); + ASSERT_EQ(originalState, state); + } + + aaudio_stream_state_t transitionalState = originalState; + switch(functionToCall) { + case FunctionToCall::CALL_START: + EXPECT_EQ(expectedResult, AAudioStream_requestStart(aaudioStream)); + transitionalState = AAUDIO_STREAM_STATE_STARTING; + break; + case FunctionToCall::CALL_STOP: + EXPECT_EQ(expectedResult, AAudioStream_requestStop(aaudioStream)); + transitionalState = AAUDIO_STREAM_STATE_STOPPING; + break; + case FunctionToCall::CALL_PAUSE: + EXPECT_EQ(expectedResult, AAudioStream_requestPause(aaudioStream)); + transitionalState = AAUDIO_STREAM_STATE_PAUSING; + break; + case FunctionToCall::CALL_FLUSH: + EXPECT_EQ(expectedResult, AAudioStream_requestFlush(aaudioStream)); + transitionalState = AAUDIO_STREAM_STATE_FLUSHING; + break; + } + + 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); + } + EXPECT_EQ(expectedState, state); + if (state != expectedState) { + printf("ERROR - expected %s, actual = %s\n", + AAudio_convertStreamStateToText(expectedState), + AAudio_convertStreamStateToText(state)); + fflush(stdout); + } + + AAudioStream_close(aaudioStream); + AAudioStreamBuilder_delete(aaudioBuilder); +} + +// TODO Use parameterized tests instead of these individual specific tests. + +// OPEN ================================================================= +TEST(test_various, aaudio_state_lowlat_open_start) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_OPEN, + FunctionToCall::CALL_START, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_none_open_start) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_OPEN, + FunctionToCall::CALL_START, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_lowlat_open_stop) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_OPEN, + FunctionToCall::CALL_STOP, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STOPPED); +} + +TEST(test_various, aaudio_state_none_open_stop) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_OPEN, + FunctionToCall::CALL_STOP, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STOPPED); +} + +TEST(test_various, aaudio_state_lowlat_open_pause) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_OPEN, + FunctionToCall::CALL_PAUSE, + AAUDIO_OK, + AAUDIO_STREAM_STATE_PAUSED); +} + +TEST(test_various, aaudio_state_none_open_pause) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_OPEN, + FunctionToCall::CALL_PAUSE, + AAUDIO_OK, + AAUDIO_STREAM_STATE_PAUSED); +} + +TEST(test_various, aaudio_state_lowlat_open_flush) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_OPEN, + FunctionToCall::CALL_FLUSH, + AAUDIO_OK, + AAUDIO_STREAM_STATE_FLUSHED); +} + +TEST(test_various, aaudio_state_none_open_flush) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_OPEN, + FunctionToCall::CALL_FLUSH, + AAUDIO_OK, + AAUDIO_STREAM_STATE_FLUSHED); +} + + +// STARTED ================================================================= +TEST(test_various, aaudio_state_lowlat_started_start) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_STARTED, + FunctionToCall::CALL_START, + AAUDIO_ERROR_INVALID_STATE, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_none_started_start) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_STARTED, + FunctionToCall::CALL_START, + AAUDIO_ERROR_INVALID_STATE, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_lowlat_started_stop) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_STARTED, + FunctionToCall::CALL_STOP, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STOPPED); +} + +TEST(test_various, aaudio_state_none_started_stop) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_STARTED, + FunctionToCall::CALL_STOP, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STOPPED); +} + +TEST(test_various, aaudio_state_lowlat_started_pause) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_STARTED, + FunctionToCall::CALL_PAUSE, + AAUDIO_OK, + AAUDIO_STREAM_STATE_PAUSED); +} + +TEST(test_various, aaudio_state_none_started_pause) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_STARTED, + FunctionToCall::CALL_PAUSE, + AAUDIO_OK, + AAUDIO_STREAM_STATE_PAUSED); +} + +TEST(test_various, aaudio_state_lowlat_started_flush) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_STARTED, + FunctionToCall::CALL_FLUSH, + AAUDIO_ERROR_INVALID_STATE, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_none_started_flush) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_STARTED, + FunctionToCall::CALL_FLUSH, + AAUDIO_ERROR_INVALID_STATE, + AAUDIO_STREAM_STATE_STARTED); +} + +// STOPPED ================================================================= +TEST(test_various, aaudio_state_lowlat_stopped_start) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_STOPPED, + FunctionToCall::CALL_START, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_none_stopped_start) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_STOPPED, + FunctionToCall::CALL_START, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_lowlat_stopped_stop) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_STOPPED, + FunctionToCall::CALL_STOP, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STOPPED); +} + +TEST(test_various, aaudio_state_none_stopped_stop) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_STOPPED, + FunctionToCall::CALL_STOP, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STOPPED); +} + +TEST(test_various, aaudio_state_lowlat_stopped_pause) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_STOPPED, + FunctionToCall::CALL_PAUSE, + AAUDIO_OK, + AAUDIO_STREAM_STATE_PAUSED); +} + +TEST(test_various, aaudio_state_none_stopped_pause) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_STOPPED, + FunctionToCall::CALL_PAUSE, + AAUDIO_OK, + AAUDIO_STREAM_STATE_PAUSED); +} + +TEST(test_various, aaudio_state_lowlat_stopped_flush) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_STOPPED, + FunctionToCall::CALL_FLUSH, + AAUDIO_OK, + AAUDIO_STREAM_STATE_FLUSHED); +} + +TEST(test_various, aaudio_state_none_stopped_flush) { + checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_STOPPED, + FunctionToCall::CALL_FLUSH, + AAUDIO_OK, + AAUDIO_STREAM_STATE_FLUSHED); +} + +// PAUSED ================================================================= +TEST(test_various, aaudio_state_lowlat_paused_start) { +checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_PAUSED, + FunctionToCall::CALL_START, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_none_paused_start) { +checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_PAUSED, + FunctionToCall::CALL_START, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STARTED); +} + +TEST(test_various, aaudio_state_lowlat_paused_stop) { +checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_PAUSED, + FunctionToCall::CALL_STOP, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STOPPED); +} + +TEST(test_various, aaudio_state_none_paused_stop) { +checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_PAUSED, + FunctionToCall::CALL_STOP, + AAUDIO_OK, + AAUDIO_STREAM_STATE_STOPPED); +} + +TEST(test_various, aaudio_state_lowlat_paused_pause) { +checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_PAUSED, + FunctionToCall::CALL_PAUSE, + AAUDIO_OK, + AAUDIO_STREAM_STATE_PAUSED); +} + +TEST(test_various, aaudio_state_none_paused_pause) { +checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_PAUSED, + FunctionToCall::CALL_PAUSE, + AAUDIO_OK, + AAUDIO_STREAM_STATE_PAUSED); +} + +TEST(test_various, aaudio_state_lowlat_paused_flush) { +checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, + AAUDIO_STREAM_STATE_PAUSED, + FunctionToCall::CALL_FLUSH, + AAUDIO_OK, + AAUDIO_STREAM_STATE_FLUSHED); +} + +TEST(test_various, aaudio_state_none_paused_flush) { +checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE, + AAUDIO_STREAM_STATE_PAUSED, + FunctionToCall::CALL_FLUSH, + AAUDIO_OK, + AAUDIO_STREAM_STATE_FLUSHED); +} + +// ========================================================================== +TEST(test_various, aaudio_set_buffer_size) { + + int32_t bufferCapacity; + int32_t framesPerBurst = 0; + int32_t actualSize = 0; + + AAudioStreamBuilder *aaudioBuilder = nullptr; + AAudioStream *aaudioStream = nullptr; + + // Use an AAudioStreamBuilder to contain requested parameters. + ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); + + // Request stream properties. + AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr); + AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + + // Create an AAudioStream using the Builder. + EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); + + // This is the number of frames that are read in one chunk by a DMA controller + // or a DSP or a mixer. + framesPerBurst = AAudioStream_getFramesPerBurst(aaudioStream); + bufferCapacity = AAudioStream_getBufferCapacityInFrames(aaudioStream); + printf(" bufferCapacity = %d, remainder = %d\n", + bufferCapacity, bufferCapacity % framesPerBurst); + + actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 0); + EXPECT_GT(actualSize, 0); + EXPECT_LE(actualSize, bufferCapacity); + + actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 2 * framesPerBurst); + EXPECT_GT(actualSize, framesPerBurst); + EXPECT_LE(actualSize, bufferCapacity); + + actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity - 1); + EXPECT_GT(actualSize, framesPerBurst); + EXPECT_LE(actualSize, bufferCapacity); + + actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity); + EXPECT_GT(actualSize, framesPerBurst); + EXPECT_LE(actualSize, bufferCapacity); + + actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, bufferCapacity + 1); + EXPECT_GT(actualSize, framesPerBurst); + EXPECT_LE(actualSize, bufferCapacity); + + actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 1234567); + EXPECT_GT(actualSize, framesPerBurst); + EXPECT_LE(actualSize, bufferCapacity); + + actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, INT32_MAX); + EXPECT_GT(actualSize, framesPerBurst); + EXPECT_LE(actualSize, bufferCapacity); + + actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, INT32_MIN); + EXPECT_GT(actualSize, 0); + EXPECT_LE(actualSize, bufferCapacity); + + AAudioStream_close(aaudioStream); + AAudioStreamBuilder_delete(aaudioBuilder); +} + +// ************************************************************ +// Test to make sure that AAUDIO_CALLBACK_RESULT_STOP works. + +// Callback function that counts calls. +aaudio_data_callback_result_t CallbackOnceProc( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames +) { + (void) stream; + (void) audioData; + (void) numFrames; + + std::atomic<int32_t> *callbackCountPtr = (std::atomic<int32_t> *)userData; + (*callbackCountPtr)++; + + return AAUDIO_CALLBACK_RESULT_STOP; +} + +void checkCallbackOnce(aaudio_performance_mode_t perfMode) { + + std::atomic<int32_t> callbackCount{0}; + + AAudioStreamBuilder *aaudioBuilder = nullptr; + AAudioStream *aaudioStream = nullptr; + + // Use an AAudioStreamBuilder to contain requested parameters. + ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); + + // Request stream properties. + AAudioStreamBuilder_setDataCallback(aaudioBuilder, CallbackOnceProc, &callbackCount); + AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); + + // Create an AAudioStream using the Builder. + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); + AAudioStreamBuilder_delete(aaudioBuilder); + + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream)); + + sleep(1); // Give callback a chance to run many times. + + EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); + + EXPECT_EQ(1, callbackCount.load()); // should stop after first call + + EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream)); +} + +TEST(test_various, aaudio_callback_once_none) { + checkCallbackOnce(AAUDIO_PERFORMANCE_MODE_NONE); +} + +TEST(test_various, aaudio_callback_once_lowlat) { + checkCallbackOnce(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); +} + +// ************************************************************ +struct WakeUpCallbackData { + void wakeOther() { + // signal waiting test to wake up + { + std::lock_guard <std::mutex> lock(mutex); + finished = true; + } + conditionVariable.notify_one(); + } + + void waitForFinished() { + std::unique_lock <std::mutex> aLock(mutex); + conditionVariable.wait(aLock, [=] { return finished; }); + } + + // For signalling foreground test when callback finished + std::mutex mutex; + std::condition_variable conditionVariable; + bool finished = false; +}; + +// Test to make sure we cannot call recursively into the system from a callback. +struct DangerousData : public WakeUpCallbackData { + aaudio_result_t resultStart = AAUDIO_OK; + aaudio_result_t resultStop = AAUDIO_OK; + aaudio_result_t resultPause = AAUDIO_OK; + aaudio_result_t resultFlush = AAUDIO_OK; + aaudio_result_t resultClose = AAUDIO_OK; +}; + +// Callback function that tries to call back into the stream. +aaudio_data_callback_result_t DangerousDataCallbackProc( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames) { + (void) audioData; + (void) numFrames; + + DangerousData *data = (DangerousData *)userData; + data->resultStart = AAudioStream_requestStart(stream); + data->resultStop = AAudioStream_requestStop(stream); + data->resultPause = AAudioStream_requestPause(stream); + data->resultFlush = AAudioStream_requestFlush(stream); + data->resultClose = AAudioStream_close(stream); + + data->wakeOther(); + + return AAUDIO_CALLBACK_RESULT_STOP; +} + +//int main() { // To fix Android Studio formatting when editing. +void checkDangerousCallback(aaudio_performance_mode_t perfMode) { + DangerousData dangerousData; + AAudioStreamBuilder *aaudioBuilder = nullptr; + AAudioStream *aaudioStream = nullptr; + + // Use an AAudioStreamBuilder to contain requested parameters. + ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder)); + + // Request stream properties. + AAudioStreamBuilder_setDataCallback(aaudioBuilder, DangerousDataCallbackProc, &dangerousData); + AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode); + + // Create an AAudioStream using the Builder. + ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream)); + AAudioStreamBuilder_delete(aaudioBuilder); + + ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream)); + + dangerousData.waitForFinished(); + + EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream)); + + EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultStart); + EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultStop); + EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultPause); + EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultFlush); + EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, dangerousData.resultClose); + + EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream)); +} + +//int main() { // To fix Android Studio formatting when editing. + +TEST(test_various, aaudio_callback_blockers_none) { + checkDangerousCallback(AAUDIO_PERFORMANCE_MODE_NONE); +} + +TEST(test_various, aaudio_callback_blockers_lowlat) { + checkDangerousCallback(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); +}
diff --git a/media/libaudioclient/Android.bp b/media/libaudioclient/Android.bp index 61c946c..2df37a8 100644 --- a/media/libaudioclient/Android.bp +++ b/media/libaudioclient/Android.bp
@@ -6,7 +6,22 @@ cc_library_shared { name: "libaudioclient", + + aidl: { + export_aidl_headers: true, + local_include_dirs: ["aidl"], + include_dirs: [ + "frameworks/av/media/libaudioclient/aidl", + ], + }, + srcs: [ + // AIDL files for audioclient interfaces + // The headers for these interfaces will be available to any modules that + // include libaudioclient, at the path "aidl/package/path/BnFoo.h" + "aidl/android/media/IAudioRecord.aidl", + ":libaudioclient_aidl", + "AudioEffect.cpp", "AudioPolicy.cpp", "AudioRecord.cpp", @@ -17,7 +32,6 @@ "IAudioFlingerClient.cpp", "IAudioPolicyService.cpp", "IAudioPolicyServiceClient.cpp", - "IAudioRecord.cpp", "IAudioTrack.cpp", "IEffect.cpp", "IEffectClient.cpp", @@ -33,10 +47,12 @@ "libdl", "libaudioutils", "libaudiomanager", + "libmedia_helper", + "libmediametrics", ], export_shared_lib_headers: ["libbinder"], - local_include_dirs: ["include/media"], + local_include_dirs: ["include/media", "aidl"], header_libs: ["libaudioclient_headers"], export_header_lib_headers: ["libaudioclient_headers"], @@ -56,3 +72,11 @@ ], }, } + +// AIDL interface between libaudioclient and framework.jar +filegroup { + name: "libaudioclient_aidl", + srcs: [ + "aidl/android/media/IPlayer.aidl", + ], +}
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp index ba4acc6..f9df5b1 100644 --- a/media/libaudioclient/AudioRecord.cpp +++ b/media/libaudioclient/AudioRecord.cpp
@@ -26,6 +26,8 @@ #include <utils/Log.h> #include <private/media/AudioTrackShared.h> #include <media/IAudioFlinger.h> +#include <media/MediaAnalyticsItem.h> +#include <media/TypeConverter.h> #define WAIT_PERIOD_MS 10 @@ -65,12 +67,90 @@ // --------------------------------------------------------------------------- +static std::string audioFormatTypeString(audio_format_t value) { + std::string formatType; + if (FormatConverter::toString(value, formatType)) { + return formatType; + } + char rawbuffer[16]; // room for "%d" + snprintf(rawbuffer, sizeof(rawbuffer), "%d", value); + return rawbuffer; +} + +static std::string audioSourceString(audio_source_t value) { + std::string source; + if (SourceTypeConverter::toString(value, source)) { + return source; + } + char rawbuffer[16]; // room for "%d" + snprintf(rawbuffer, sizeof(rawbuffer), "%d", value); + return rawbuffer; +} + +void AudioRecord::MediaMetrics::gather(const AudioRecord *record) +{ + // key for media statistics is defined in the header + // attrs for media statistics + // NB: these are matched with public Java API constants defined + // in frameworks/base/media/java/android/media/AudioRecord.java + // These must be kept synchronized with the constants there. + static constexpr char kAudioRecordEncoding[] = "android.media.audiorecord.encoding"; + static constexpr char kAudioRecordSource[] = "android.media.audiorecord.source"; + static constexpr char kAudioRecordLatency[] = "android.media.audiorecord.latency"; + static constexpr char kAudioRecordSampleRate[] = "android.media.audiorecord.samplerate"; + static constexpr char kAudioRecordChannelCount[] = "android.media.audiorecord.channels"; + static constexpr char kAudioRecordCreated[] = "android.media.audiorecord.createdMs"; + static constexpr char kAudioRecordDuration[] = "android.media.audiorecord.durationMs"; + static constexpr char kAudioRecordCount[] = "android.media.audiorecord.n"; + static constexpr char kAudioRecordError[] = "android.media.audiorecord.errcode"; + static constexpr char kAudioRecordErrorFunction[] = "android.media.audiorecord.errfunc"; + + // constructor guarantees mAnalyticsItem is valid + + mAnalyticsItem->setInt32(kAudioRecordLatency, record->mLatency); + mAnalyticsItem->setInt32(kAudioRecordSampleRate, record->mSampleRate); + mAnalyticsItem->setInt32(kAudioRecordChannelCount, record->mChannelCount); + mAnalyticsItem->setCString(kAudioRecordEncoding, + audioFormatTypeString(record->mFormat).c_str()); + mAnalyticsItem->setCString(kAudioRecordSource, + audioSourceString(record->mAttributes.source).c_str()); + + // log total duration recording, including anything currently running [and count]. + nsecs_t active = 0; + if (mStartedNs != 0) { + active = systemTime() - mStartedNs; + } + mAnalyticsItem->setInt64(kAudioRecordDuration, (mDurationNs + active) / (1000 * 1000)); + mAnalyticsItem->setInt32(kAudioRecordCount, mCount); + + // XXX I don't know that this adds a lot of value, long term + if (mCreatedNs != 0) { + mAnalyticsItem->setInt64(kAudioRecordCreated, mCreatedNs / (1000 * 1000)); + } + + if (mLastError != NO_ERROR) { + mAnalyticsItem->setInt32(kAudioRecordError, mLastError); + mAnalyticsItem->setCString(kAudioRecordErrorFunction, mLastErrorFunc.c_str()); + } +} + +// hand the user a snapshot of the metrics. +status_t AudioRecord::getMetrics(MediaAnalyticsItem * &item) +{ + mMediaMetrics.gather(this); + MediaAnalyticsItem *tmp = mMediaMetrics.dup(); + if (tmp == nullptr) { + return BAD_VALUE; + } + item = tmp; + return NO_ERROR; +} + AudioRecord::AudioRecord(const String16 &opPackageName) : mActive(false), mStatus(NO_INIT), mOpPackageName(opPackageName), mSessionId(AUDIO_SESSION_ALLOCATE), mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT), - mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE), mRoutedDeviceId(AUDIO_PORT_HANDLE_NONE), - mPortId(AUDIO_PORT_HANDLE_NONE) + mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE), mRoutedDeviceId(AUDIO_PORT_HANDLE_NONE) { } @@ -89,24 +169,25 @@ audio_input_flags_t flags, uid_t uid, pid_t pid, - const audio_attributes_t* pAttributes) + const audio_attributes_t* pAttributes, + audio_port_handle_t selectedDeviceId) : mActive(false), mStatus(NO_INIT), mOpPackageName(opPackageName), mSessionId(AUDIO_SESSION_ALLOCATE), mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT), - mProxy(NULL), - mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE), - mPortId(AUDIO_PORT_HANDLE_NONE) + mProxy(NULL) { - mStatus = set(inputSource, sampleRate, format, channelMask, frameCount, cbf, user, + (void)set(inputSource, sampleRate, format, channelMask, frameCount, cbf, user, notificationFrames, false /*threadCanCallJava*/, sessionId, transferType, flags, - uid, pid, pAttributes); + uid, pid, pAttributes, selectedDeviceId); } AudioRecord::~AudioRecord() { + mMediaMetrics.gather(this); + if (mStatus == NO_ERROR) { // Make sure that callback function exits in the case where // it is looping on buffer empty condition in obtainBuffer(). @@ -148,14 +229,22 @@ audio_input_flags_t flags, uid_t uid, pid_t pid, - const audio_attributes_t* pAttributes) + const audio_attributes_t* pAttributes, + audio_port_handle_t selectedDeviceId) { + status_t status = NO_ERROR; + uint32_t channelCount; + pid_t callingPid; + pid_t myPid; + ALOGV("set(): inputSource %d, sampleRate %u, format %#x, channelMask %#x, frameCount %zu, " "notificationFrames %u, sessionId %d, transferType %d, flags %#x, opPackageName %s " "uid %d, pid %d", inputSource, sampleRate, format, channelMask, frameCount, notificationFrames, sessionId, transferType, flags, String8(mOpPackageName).string(), uid, pid); + mSelectedDeviceId = selectedDeviceId; + switch (transferType) { case TRANSFER_DEFAULT: if (cbf == NULL || threadCanCallJava) { @@ -167,7 +256,8 @@ case TRANSFER_CALLBACK: if (cbf == NULL) { ALOGE("Transfer type TRANSFER_CALLBACK but cbf == NULL"); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } break; case TRANSFER_OBTAIN: @@ -175,14 +265,16 @@ break; default: ALOGE("Invalid transfer type %d", transferType); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mTransfer = transferType; // invariant that mAudioRecord != 0 is true only after set() returns successfully if (mAudioRecord != 0) { ALOGE("Track already in use"); - return INVALID_OPERATION; + status = INVALID_OPERATION; + goto exit; } if (pAttributes == NULL) { @@ -206,16 +298,18 @@ // AudioFlinger capture only supports linear PCM if (!audio_is_valid_format(format) || !audio_is_linear_pcm(format)) { ALOGE("Format %#x is not linear pcm", format); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mFormat = format; if (!audio_is_input_channel(channelMask)) { ALOGE("Invalid channel mask %#x", channelMask); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mChannelMask = channelMask; - uint32_t channelCount = audio_channel_count_from_in_mask(channelMask); + channelCount = audio_channel_count_from_in_mask(channelMask); mChannelCount = channelCount; if (audio_is_linear_pcm(format)) { @@ -224,28 +318,24 @@ mFrameSize = sizeof(uint8_t); } - // mFrameCount is initialized in openRecord_l + // mFrameCount is initialized in createRecord_l mReqFrameCount = frameCount; mNotificationFramesReq = notificationFrames; - // mNotificationFramesAct is initialized in openRecord_l + // mNotificationFramesAct is initialized in createRecord_l - if (sessionId == AUDIO_SESSION_ALLOCATE) { - mSessionId = (audio_session_t) AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION); - } else { - mSessionId = sessionId; - } + mSessionId = sessionId; ALOGV("set(): mSessionId %d", mSessionId); - int callingpid = IPCThreadState::self()->getCallingPid(); - int mypid = getpid(); - if (uid == AUDIO_UID_INVALID || (callingpid != mypid)) { + callingPid = IPCThreadState::self()->getCallingPid(); + myPid = getpid(); + if (uid == AUDIO_UID_INVALID || (callingPid != myPid)) { mClientUid = IPCThreadState::self()->getCallingUid(); } else { mClientUid = uid; } - if (pid == -1 || (callingpid != mypid)) { - mClientPid = callingpid; + if (pid == -1 || (callingPid != myPid)) { + mClientPid = callingPid; } else { mClientPid = pid; } @@ -260,7 +350,7 @@ } // create the IAudioRecord - status_t status = openRecord_l(0 /*epoch*/, mOpPackageName); + status = createRecord_l(0 /*epoch*/, mOpPackageName); if (status != NO_ERROR) { if (mAudioRecordThread != 0) { @@ -268,10 +358,9 @@ mAudioRecordThread->requestExitAndWait(); mAudioRecordThread.clear(); } - return status; + goto exit; } - mStatus = NO_ERROR; mUserData = user; // TODO: add audio hardware input latency here mLatency = (1000LL * mFrameCount) / mSampleRate; @@ -286,7 +375,12 @@ mFramesRead = 0; mFramesReadServerOffset = 0; - return NO_ERROR; +exit: + mStatus = status; + if (status != NO_ERROR) { + mMediaMetrics.markError(status, __FUNCTION__); + } + return status; } // ------------------------------------------------------------------------- @@ -323,7 +417,7 @@ status_t status = NO_ERROR; if (!(flags & CBLK_INVALID)) { - status = mAudioRecord->start(event, triggerSession); + status = mAudioRecord->start(event, triggerSession).transactionError(); if (status == DEAD_OBJECT) { flags |= CBLK_INVALID; } @@ -344,8 +438,14 @@ get_sched_policy(0, &mPreviousSchedulingGroup); androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO); } + + // we've successfully started, log that time + mMediaMetrics.logStart(systemTime()); } + if (status != NO_ERROR) { + mMediaMetrics.markError(status, __FUNCTION__); + } return status; } @@ -370,6 +470,9 @@ setpriority(PRIO_PROCESS, 0, mPreviousPriority); set_sched_policy(0, mPreviousSchedulingGroup); } + + // we've successfully started, log that time + mMediaMetrics.logStop(systemTime()); } bool AudioRecord::stopped() const @@ -489,6 +592,7 @@ mAudioRecord->stop(); } android_atomic_or(CBLK_INVALID, &mCblk->mFlags); + mProxy->interrupt(); } } return NO_ERROR; @@ -521,6 +625,27 @@ return mRoutedDeviceId; } +status_t AudioRecord::dump(int fd, const Vector<String16>& args __unused) const +{ + String8 result; + + result.append(" AudioRecord::dump\n"); + result.appendFormat(" status(%d), active(%d), session Id(%d)\n", + mStatus, mActive, mSessionId); + result.appendFormat(" flags(%#x), req. flags(%#x), audio source(%d)\n", + mFlags, mOrigFlags, mAttributes.source); + result.appendFormat(" format(%#x), channel mask(%#x), channel count(%u), sample rate(%u)\n", + mFormat, mChannelMask, mChannelCount, mSampleRate); + result.appendFormat(" frame count(%zu), req. frame count(%zu)\n", + mFrameCount, mReqFrameCount); + result.appendFormat(" notif. frame count(%u), req. notif. frame count(%u)\n", + mNotificationFramesAct, mNotificationFramesReq); + result.appendFormat(" input(%d), latency(%u), selected device Id(%d), routed device Id(%d)\n", + mInput, mLatency, mSelectedDeviceId, mRoutedDeviceId); + ::write(fd, result.string(), result.size()); + return NO_ERROR; +} + // ------------------------------------------------------------------------- // TODO Move this macro to a common header file for enum to string conversion in audio framework. #define MEDIA_CASE_ENUM(name) case name: return #name @@ -536,70 +661,29 @@ } // must be called with mLock held -status_t AudioRecord::openRecord_l(const Modulo<uint32_t> &epoch, const String16& opPackageName) +status_t AudioRecord::createRecord_l(const Modulo<uint32_t> &epoch, const String16& opPackageName) { const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger(); + IAudioFlinger::CreateRecordInput input; + IAudioFlinger::CreateRecordOutput output; + audio_session_t originalSessionId; + sp<media::IAudioRecord> record; + void *iMemPointer; + audio_track_cblk_t* cblk; + status_t status; + if (audioFlinger == 0) { ALOGE("Could not get audioflinger"); - return NO_INIT; + status = NO_INIT; + goto exit; } - audio_io_handle_t input; - // mFlags (not mOrigFlags) is modified depending on whether fast request is accepted. // After fast request is denied, we will request again if IAudioRecord is re-created. - status_t status; - - // Not a conventional loop, but a retry loop for at most two iterations total. - // Try first maybe with FAST flag then try again without FAST flag if that fails. - // Exits loop normally via a return at the bottom, or with error via a break. - // The sp<> references will be dropped when re-entering scope. - // The lack of indentation is deliberate, to reduce code churn and ease merges. - for (;;) { - audio_config_base_t config = { - .sample_rate = mSampleRate, - .channel_mask = mChannelMask, - .format = mFormat - }; - mRoutedDeviceId = mSelectedDeviceId; - status = AudioSystem::getInputForAttr(&mAttributes, &input, - mSessionId, - // FIXME compare to AudioTrack - mClientPid, - mClientUid, - &config, - mFlags, &mRoutedDeviceId, &mPortId); - - if (status != NO_ERROR || input == AUDIO_IO_HANDLE_NONE) { - ALOGE("Could not get audio input for session %d, record source %d, sample rate %u, " - "format %#x, channel mask %#x, flags %#x", - mSessionId, mAttributes.source, mSampleRate, mFormat, mChannelMask, mFlags); - return BAD_VALUE; - } - // Now that we have a reference to an I/O handle and have not yet handed it off to AudioFlinger, // we must release it ourselves if anything goes wrong. -#if 0 - size_t afFrameCount; - status = AudioSystem::getFrameCount(input, &afFrameCount); - if (status != NO_ERROR) { - ALOGE("getFrameCount(input=%d) status %d", input, status); - break; - } -#endif - - uint32_t afSampleRate; - status = AudioSystem::getSamplingRate(input, &afSampleRate); - if (status != NO_ERROR) { - ALOGE("getSamplingRate(input=%d) status %d", input, status); - break; - } - if (mSampleRate == 0) { - mSampleRate = afSampleRate; - } - // Client can only express a preference for FAST. Server will perform additional tests. if (mFlags & AUDIO_INPUT_FLAG_FAST) { bool useCaseAllowed = @@ -618,66 +702,41 @@ if (!useCaseAllowed) { ALOGW("AUDIO_INPUT_FLAG_FAST denied, incompatible transfer = %s", convertTransferToText(mTransfer)); - } - - // sample rates must also match - bool sampleRateAllowed = mSampleRate == afSampleRate; - if (!sampleRateAllowed) { - ALOGW("AUDIO_INPUT_FLAG_FAST denied, rates do not match %u Hz, require %u Hz", - mSampleRate, afSampleRate); - } - - bool fastAllowed = useCaseAllowed && sampleRateAllowed; - if (!fastAllowed) { mFlags = (audio_input_flags_t) (mFlags & ~(AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW)); - AudioSystem::releaseInput(input, mSessionId); - continue; // retry } } - // The notification frame count is the period between callbacks, as suggested by the client - // but moderated by the server. For record, the calculations are done entirely on server side. - size_t notificationFrames = mNotificationFramesReq; - size_t frameCount = mReqFrameCount; - - audio_input_flags_t flags = mFlags; - - pid_t tid = -1; + input.attr = mAttributes; + input.config.sample_rate = mSampleRate; + input.config.channel_mask = mChannelMask; + input.config.format = mFormat; + input.clientInfo.clientUid = mClientUid; + input.clientInfo.clientPid = mClientPid; + input.clientInfo.clientTid = -1; if (mFlags & AUDIO_INPUT_FLAG_FAST) { if (mAudioRecordThread != 0) { - tid = mAudioRecordThread->getTid(); + input.clientInfo.clientTid = mAudioRecordThread->getTid(); } } + input.opPackageName = opPackageName; - size_t temp = frameCount; // temp may be replaced by a revised value of frameCount, - // but we will still need the original value also - audio_session_t originalSessionId = mSessionId; + input.flags = mFlags; + // The notification frame count is the period between callbacks, as suggested by the client + // but moderated by the server. For record, the calculations are done entirely on server side. + input.frameCount = mReqFrameCount; + input.notificationFrameCount = mNotificationFramesReq; + input.selectedDeviceId = mSelectedDeviceId; + input.sessionId = mSessionId; + originalSessionId = mSessionId; - sp<IMemory> iMem; // for cblk - sp<IMemory> bufferMem; - sp<IAudioRecord> record = audioFlinger->openRecord(input, - mSampleRate, - mFormat, - mChannelMask, - opPackageName, - &temp, - &flags, - mClientPid, - tid, - mClientUid, - &mSessionId, - ¬ificationFrames, - iMem, - bufferMem, - &status, - mPortId); - ALOGE_IF(originalSessionId != AUDIO_SESSION_ALLOCATE && mSessionId != originalSessionId, - "session ID changed from %d to %d", originalSessionId, mSessionId); + record = audioFlinger->createRecord(input, + output, + &status); if (status != NO_ERROR) { ALOGE("AudioFlinger could not create record track, status: %d", status); - break; + goto exit; } ALOG_ASSERT(record != 0); @@ -685,41 +744,41 @@ // so we are no longer responsible for releasing it. mAwaitBoost = false; - if (mFlags & AUDIO_INPUT_FLAG_FAST) { - if (flags & AUDIO_INPUT_FLAG_FAST) { - ALOGI("AUDIO_INPUT_FLAG_FAST successful; frameCount %zu -> %zu", frameCount, temp); - mAwaitBoost = true; - } else { - ALOGW("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %zu -> %zu", frameCount, temp); - mFlags = (audio_input_flags_t) (mFlags & ~(AUDIO_INPUT_FLAG_FAST | - AUDIO_INPUT_FLAG_RAW)); - continue; // retry - } + if (output.flags & AUDIO_INPUT_FLAG_FAST) { + ALOGI("AUDIO_INPUT_FLAG_FAST successful; frameCount %zu -> %zu", + mReqFrameCount, output.frameCount); + mAwaitBoost = true; } - mFlags = flags; + mFlags = output.flags; + mRoutedDeviceId = output.selectedDeviceId; + mSessionId = output.sessionId; + mSampleRate = output.sampleRate; - if (iMem == 0) { + if (output.cblk == 0) { ALOGE("Could not get control block"); - return NO_INIT; + status = NO_INIT; + goto exit; } - void *iMemPointer = iMem->pointer(); + iMemPointer = output.cblk ->pointer(); if (iMemPointer == NULL) { ALOGE("Could not get control block pointer"); - return NO_INIT; + status = NO_INIT; + goto exit; } - audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer); + cblk = static_cast<audio_track_cblk_t*>(iMemPointer); // Starting address of buffers in shared memory. // The buffers are either immediately after the control block, // or in a separate area at discretion of server. void *buffers; - if (bufferMem == 0) { + if (output.buffers == 0) { buffers = cblk + 1; } else { - buffers = bufferMem->pointer(); + buffers = output.buffers->pointer(); if (buffers == NULL) { ALOGE("Could not get buffer pointer"); - return NO_INIT; + status = NO_INIT; + goto exit; } } @@ -729,43 +788,42 @@ mDeathNotifier.clear(); } mAudioRecord = record; - mCblkMemory = iMem; - mBufferMemory = bufferMem; + mCblkMemory = output.cblk; + mBufferMemory = output.buffers; IPCThreadState::self()->flushCommands(); mCblk = cblk; - // note that temp is the (possibly revised) value of frameCount - if (temp < frameCount || (frameCount == 0 && temp == 0)) { - ALOGW("Requested frameCount %zu but received frameCount %zu", frameCount, temp); + // note that output.frameCount is the (possibly revised) value of mReqFrameCount + if (output.frameCount < mReqFrameCount || (mReqFrameCount == 0 && output.frameCount == 0)) { + ALOGW("Requested frameCount %zu but received frameCount %zu", + mReqFrameCount, output.frameCount); } - frameCount = temp; // Make sure that application is notified with sufficient margin before overrun. // The computation is done on server side. - if (mNotificationFramesReq > 0 && notificationFrames != mNotificationFramesReq) { + if (mNotificationFramesReq > 0 && output.notificationFrameCount != mNotificationFramesReq) { ALOGW("Server adjusted notificationFrames from %u to %zu for frameCount %zu", - mNotificationFramesReq, notificationFrames, frameCount); + mNotificationFramesReq, output.notificationFrameCount, output.frameCount); } - mNotificationFramesAct = (uint32_t) notificationFrames; - + mNotificationFramesAct = (uint32_t)output.notificationFrameCount; //mInput != input includes the case where mInput == AUDIO_IO_HANDLE_NONE for first creation - if (mDeviceCallback != 0 && mInput != input) { + if (mDeviceCallback != 0 && mInput != output.inputId) { if (mInput != AUDIO_IO_HANDLE_NONE) { AudioSystem::removeAudioDeviceCallback(this, mInput); } - AudioSystem::addAudioDeviceCallback(this, input); + AudioSystem::addAudioDeviceCallback(this, output.inputId); } // We retain a copy of the I/O handle, but don't own the reference - mInput = input; + mInput = output.inputId; mRefreshRemaining = true; - mFrameCount = frameCount; + mFrameCount = output.frameCount; // If IAudioRecord is re-created, don't let the requested frameCount // decrease. This can confuse clients that cache frameCount(). - if (frameCount > mReqFrameCount) { - mReqFrameCount = frameCount; + if (mFrameCount > mReqFrameCount) { + mReqFrameCount = mFrameCount; } // update proxy @@ -776,17 +834,9 @@ mDeathNotifier = new DeathNotifier(this); IInterface::asBinder(mAudioRecord)->linkToDeath(mDeathNotifier, this); - return NO_ERROR; - - // End of retry loop. - // The lack of indentation is deliberate, to reduce code churn and ease merges. - } - -// Arrive here on error, via a break - AudioSystem::releaseInput(input, mSessionId); - if (status == NO_ERROR) { - status = NO_INIT; - } +exit: + mStatus = status; + // sp<IAudioTrack> track destructor will cause releaseOutput() to be called by AudioFlinger return status; } @@ -1216,22 +1266,43 @@ ALOGW("dead IAudioRecord, creating a new one from %s()", from); ++mSequence; + const int INITIAL_RETRIES = 3; + int retries = INITIAL_RETRIES; +retry: + if (retries < INITIAL_RETRIES) { + // refresh the audio configuration cache in this process to make sure we get new + // input parameters and new IAudioRecord in createRecord_l() + AudioSystem::clearAudioConfigCache(); + } mFlags = mOrigFlags; - // if the new IAudioRecord is created, openRecord_l() will modify the + // if the new IAudioRecord is created, createRecord_l() will modify the // following member variables: mAudioRecord, mCblkMemory, mCblk, mBufferMemory. // It will also delete the strong references on previous IAudioRecord and IMemory Modulo<uint32_t> position(mProxy->getPosition()); mNewPosition = position + mUpdatePeriod; - status_t result = openRecord_l(position, mOpPackageName); - if (result == NO_ERROR) { + status_t result = createRecord_l(position, mOpPackageName); + + if (result != NO_ERROR) { + ALOGW("%s(): createRecord_l failed, do not retry", __func__); + retries = 0; + } else { if (mActive) { // callback thread or sync event hasn't changed // FIXME this fails if we have a new AudioFlinger instance - result = mAudioRecord->start(AudioSystem::SYNC_EVENT_SAME, AUDIO_SESSION_NONE); + result = mAudioRecord->start( + AudioSystem::SYNC_EVENT_SAME, AUDIO_SESSION_NONE).transactionError(); } mFramesReadServerOffset = mFramesRead; // server resets to zero so we need an offset. } + + if (result != NO_ERROR) { + ALOGW("%s() failed status %d, retries %d", __func__, result, retries); + if (--retries > 0) { + goto retry; + } + } + if (result != NO_ERROR) { ALOGW("restoreRecord_l() failed status %d", result); mActive = false; @@ -1303,6 +1374,14 @@ } } +// ------------------------------------------------------------------------- + +status_t AudioRecord::getActiveMicrophones(std::vector<media::MicrophoneInfo>* activeMicrophones) +{ + AutoMutex lock(mLock); + return mAudioRecord->getActiveMicrophones(activeMicrophones).transactionError(); +} + // ========================================================================= void AudioRecord::DeathNotifier::binderDied(const wp<IBinder>& who __unused)
diff --git a/media/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp index cdc75ac..c072901 100644 --- a/media/libaudioclient/AudioSystem.cpp +++ b/media/libaudioclient/AudioSystem.cpp
@@ -20,6 +20,8 @@ #include <utils/Log.h> #include <binder/IServiceManager.h> #include <binder/ProcessState.h> +#include <binder/IPCThreadState.h> +#include <media/AudioResamplerPublic.h> #include <media/AudioSystem.h> #include <media/IAudioFlinger.h> #include <media/IAudioPolicyService.h> @@ -38,8 +40,7 @@ sp<AudioSystem::AudioFlingerClient> AudioSystem::gAudioFlingerClient; audio_error_callback AudioSystem::gAudioErrorCallback = NULL; dynamic_policy_callback AudioSystem::gDynPolicyCallback = NULL; -record_config_callback AudioSystem::gRecordConfigCallback = NULL; - +record_config_callback AudioSystem::gRecordConfigCallback = NULL; // establish binder interface to AudioFlinger service const sp<IAudioFlinger> AudioSystem::get_audio_flinger() @@ -75,7 +76,9 @@ af = gAudioFlinger; } if (afc != 0) { + int64_t token = IPCThreadState::self()->clearCallingIdentity(); af->registerClient(afc); + IPCThreadState::self()->restoreCallingIdentity(token); } return af; } @@ -253,6 +256,31 @@ return volume ? 100 - int(dBConvertInverse * log(volume) + 0.5) : 0; } +/* static */ size_t AudioSystem::calculateMinFrameCount( + uint32_t afLatencyMs, uint32_t afFrameCount, uint32_t afSampleRate, + uint32_t sampleRate, float speed /*, uint32_t notificationsPerBufferReq*/) +{ + // Ensure that buffer depth covers at least audio hardware latency + uint32_t minBufCount = afLatencyMs / ((1000 * afFrameCount) / afSampleRate); + if (minBufCount < 2) { + minBufCount = 2; + } +#if 0 + // The notificationsPerBufferReq parameter is not yet used for non-fast tracks, + // but keeping the code here to make it easier to add later. + if (minBufCount < notificationsPerBufferReq) { + minBufCount = notificationsPerBufferReq; + } +#endif + ALOGV("calculateMinFrameCount afLatency %u afFrameCount %u afSampleRate %u " + "sampleRate %u speed %f minBufCount: %u" /*" notificationsPerBufferReq %u"*/, + afLatencyMs, afFrameCount, afSampleRate, sampleRate, speed, minBufCount + /*, notificationsPerBufferReq*/); + return minBufCount * sourceFramesNeededWithTimestretch( + sampleRate, afFrameCount, afSampleRate, speed); +} + + status_t AudioSystem::getOutputSamplingRate(uint32_t* samplingRate, audio_stream_type_t streamType) { audio_io_handle_t output; @@ -605,7 +633,7 @@ || (channelMask != mInChannelMask)) { size_t inBuffSize = af->getInputBufferSize(sampleRate, format, channelMask); if (inBuffSize == 0) { - ALOGE("AudioSystem::getInputBufferSize failed sampleRate %d format %#x channelMask %x", + ALOGE("AudioSystem::getInputBufferSize failed sampleRate %d format %#x channelMask %#x", sampleRate, format, channelMask); return BAD_VALUE; } @@ -742,7 +770,10 @@ ap = gAudioPolicyService; } if (apc != 0) { + int64_t token = IPCThreadState::self()->clearCallingIdentity(); ap->registerClient(apc); + ap->setAudioPortCallbacksEnabled(apc->isAudioPortCbEnabled()); + IPCThreadState::self()->restoreCallingIdentity(token); } return ap; @@ -822,22 +853,18 @@ } -audio_io_handle_t AudioSystem::getOutput(audio_stream_type_t stream, - uint32_t samplingRate, - audio_format_t format, - audio_channel_mask_t channelMask, - audio_output_flags_t flags, - const audio_offload_info_t *offloadInfo) +audio_io_handle_t AudioSystem::getOutput(audio_stream_type_t stream) { const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return 0; - return aps->getOutput(stream, samplingRate, format, channelMask, flags, offloadInfo); + return aps->getOutput(stream); } status_t AudioSystem::getOutputForAttr(const audio_attributes_t *attr, audio_io_handle_t *output, audio_session_t session, audio_stream_type_t *stream, + pid_t pid, uid_t uid, const audio_config_t *config, audio_output_flags_t flags, @@ -846,7 +873,7 @@ { const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return NO_INIT; - return aps->getOutputForAttr(attr, output, session, stream, uid, + return aps->getOutputForAttr(attr, output, session, stream, pid, uid, config, flags, selectedDeviceId, portId); } @@ -883,6 +910,7 @@ audio_session_t session, pid_t pid, uid_t uid, + const String16& opPackageName, const audio_config_base_t *config, audio_input_flags_t flags, audio_port_handle_t *selectedDeviceId, @@ -891,32 +919,29 @@ const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return NO_INIT; return aps->getInputForAttr( - attr, input, session, pid, uid, + attr, input, session, pid, uid, opPackageName, config, flags, selectedDeviceId, portId); } -status_t AudioSystem::startInput(audio_io_handle_t input, - audio_session_t session) +status_t AudioSystem::startInput(audio_port_handle_t portId, bool *silenced) { const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return PERMISSION_DENIED; - return aps->startInput(input, session); + return aps->startInput(portId, silenced); } -status_t AudioSystem::stopInput(audio_io_handle_t input, - audio_session_t session) +status_t AudioSystem::stopInput(audio_port_handle_t portId) { const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return PERMISSION_DENIED; - return aps->stopInput(input, session); + return aps->stopInput(portId); } -void AudioSystem::releaseInput(audio_io_handle_t input, - audio_session_t session) +void AudioSystem::releaseInput(audio_port_handle_t portId) { const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return; - aps->releaseInput(input, session); + aps->releaseInput(portId); } status_t AudioSystem::initStreamVolume(audio_stream_type_t stream, @@ -1035,11 +1060,11 @@ return af->getPrimaryOutputFrameCount(); } -status_t AudioSystem::setLowRamDevice(bool isLowRamDevice) +status_t AudioSystem::setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) { const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; - return af->setLowRamDevice(isLowRamDevice); + return af->setLowRamDevice(isLowRamDevice, totalMemory); } void AudioSystem::clearAudioConfigCache() @@ -1254,6 +1279,31 @@ return aps->getStreamVolumeDB(stream, index, device); } +status_t AudioSystem::getMicrophones(std::vector<media::MicrophoneInfo> *microphones) +{ + const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger(); + if (af == 0) return PERMISSION_DENIED; + return af->getMicrophones(microphones); +} + +status_t AudioSystem::getSurroundFormats(unsigned int *numSurroundFormats, + audio_format_t *surroundFormats, + bool *surroundFormatsEnabled, + bool reported) +{ + const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); + if (aps == 0) return PERMISSION_DENIED; + return aps->getSurroundFormats( + numSurroundFormats, surroundFormats, surroundFormatsEnabled, reported); +} + +status_t AudioSystem::setSurroundFormatEnabled(audio_format_t audioFormat, bool enabled) +{ + const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); + if (aps == 0) return PERMISSION_DENIED; + return aps->setSurroundFormatEnabled(audioFormat, enabled); +} + // --------------------------------------------------------------------------- int AudioSystem::AudioPolicyServiceClient::addAudioPortCallback(
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp index 3529d2c..ab9efe8 100644 --- a/media/libaudioclient/AudioTrack.cpp +++ b/media/libaudioclient/AudioTrack.cpp
@@ -29,8 +29,11 @@ #include <utils/Log.h> #include <private/media/AudioTrackShared.h> #include <media/IAudioFlinger.h> +#include <media/AudioParameter.h> #include <media/AudioPolicyHelper.h> #include <media/AudioResamplerPublic.h> +#include <media/MediaAnalyticsItem.h> +#include <media/TypeConverter.h> #define WAIT_PERIOD_MS 10 #define WAIT_STREAM_END_TIMEOUT_SEC 120 @@ -39,6 +42,8 @@ namespace android { // --------------------------------------------------------------------------- +using media::VolumeShaper; + // TODO: Move to a separate .h template <typename T> @@ -51,8 +56,6 @@ return x > y ? x : y; } -static const int32_t NANOS_PER_SECOND = 1000000000; - static inline nsecs_t framesToNanoseconds(ssize_t frames, uint32_t sampleRate, float speed) { return ((double)frames * 1000000000) / ((double)sampleRate * speed); @@ -97,32 +100,6 @@ return kFixPitch ? AUDIO_TIMESTRETCH_PITCH_NORMAL : pitch; } -// Must match similar computation in createTrack_l in Threads.cpp. -// TODO: Move to a common library -static size_t calculateMinFrameCount( - uint32_t afLatencyMs, uint32_t afFrameCount, uint32_t afSampleRate, - uint32_t sampleRate, float speed /*, uint32_t notificationsPerBufferReq*/) -{ - // Ensure that buffer depth covers at least audio hardware latency - uint32_t minBufCount = afLatencyMs / ((1000 * afFrameCount) / afSampleRate); - if (minBufCount < 2) { - minBufCount = 2; - } -#if 0 - // The notificationsPerBufferReq parameter is not yet used for non-fast tracks, - // but keeping the code here to make it easier to add later. - if (minBufCount < notificationsPerBufferReq) { - minBufCount = notificationsPerBufferReq; - } -#endif - ALOGV("calculateMinFrameCount afLatency %u afFrameCount %u afSampleRate %u " - "sampleRate %u speed %f minBufCount: %u" /*" notificationsPerBufferReq %u"*/, - afLatencyMs, afFrameCount, afSampleRate, sampleRate, speed, minBufCount - /*, notificationsPerBufferReq*/); - return minBufCount * sourceFramesNeededWithTimestretch( - sampleRate, afFrameCount, afSampleRate, speed); -} - // static status_t AudioTrack::getMinFrameCount( size_t* frameCount, @@ -163,8 +140,8 @@ // When called from createTrack, speed is 1.0f (normal speed). // This is rechecked again on setting playback rate (TODO: on setting sample rate, too). - *frameCount = calculateMinFrameCount(afLatency, afFrameCount, afSampleRate, sampleRate, 1.0f - /*, 0 notificationsPerBufferReq*/); + *frameCount = AudioSystem::calculateMinFrameCount(afLatency, afFrameCount, afSampleRate, + sampleRate, 1.0f /*, 0 notificationsPerBufferReq*/); // The formula above should always produce a non-zero value under normal circumstances: // AudioTrack.SAMPLE_RATE_HZ_MIN <= sampleRate <= AudioTrack.SAMPLE_RATE_HZ_MAX. @@ -181,6 +158,88 @@ // --------------------------------------------------------------------------- +static std::string audioContentTypeString(audio_content_type_t value) { + std::string contentType; + if (AudioContentTypeConverter::toString(value, contentType)) { + return contentType; + } + char rawbuffer[16]; // room for "%d" + snprintf(rawbuffer, sizeof(rawbuffer), "%d", value); + return rawbuffer; +} + +static std::string audioUsageString(audio_usage_t value) { + std::string usage; + if (UsageTypeConverter::toString(value, usage)) { + return usage; + } + char rawbuffer[16]; // room for "%d" + snprintf(rawbuffer, sizeof(rawbuffer), "%d", value); + return rawbuffer; +} + +void AudioTrack::MediaMetrics::gather(const AudioTrack *track) +{ + + // key for media statistics is defined in the header + // attrs for media statistics + // NB: these are matched with public Java API constants defined + // in frameworks/base/media/java/android/media/AudioTrack.java + // These must be kept synchronized with the constants there. + static constexpr char kAudioTrackStreamType[] = "android.media.audiotrack.streamtype"; + static constexpr char kAudioTrackContentType[] = "android.media.audiotrack.type"; + static constexpr char kAudioTrackUsage[] = "android.media.audiotrack.usage"; + static constexpr char kAudioTrackSampleRate[] = "android.media.audiotrack.samplerate"; + static constexpr char kAudioTrackChannelMask[] = "android.media.audiotrack.channelmask"; + + // NB: These are not yet exposed as public Java API constants. + static constexpr char kAudioTrackUnderrunFrames[] = "android.media.audiotrack.underrunframes"; + static constexpr char kAudioTrackStartupGlitch[] = "android.media.audiotrack.glitch.startup"; + + // only if we're in a good state... + // XXX: shall we gather alternative info if failing? + const status_t lstatus = track->initCheck(); + if (lstatus != NO_ERROR) { + ALOGD("no metrics gathered, track status=%d", (int) lstatus); + return; + } + + // constructor guarantees mAnalyticsItem is valid + + const int32_t underrunFrames = track->getUnderrunFrames(); + if (underrunFrames != 0) { + mAnalyticsItem->setInt32(kAudioTrackUnderrunFrames, underrunFrames); + } + + if (track->mTimestampStartupGlitchReported) { + mAnalyticsItem->setInt32(kAudioTrackStartupGlitch, 1); + } + + if (track->mStreamType != -1) { + // deprecated, but this will tell us who still uses it. + mAnalyticsItem->setInt32(kAudioTrackStreamType, track->mStreamType); + } + // XXX: consider including from mAttributes: source type + mAnalyticsItem->setCString(kAudioTrackContentType, + audioContentTypeString(track->mAttributes.content_type).c_str()); + mAnalyticsItem->setCString(kAudioTrackUsage, + audioUsageString(track->mAttributes.usage).c_str()); + mAnalyticsItem->setInt32(kAudioTrackSampleRate, track->mSampleRate); + mAnalyticsItem->setInt64(kAudioTrackChannelMask, track->mChannelMask); +} + +// hand the user a snapshot of the metrics. +status_t AudioTrack::getMetrics(MediaAnalyticsItem * &item) +{ + mMediaMetrics.gather(this); + MediaAnalyticsItem *tmp = mMediaMetrics.dup(); + if (tmp == nullptr) { + return BAD_VALUE; + } + item = tmp; + return NO_ERROR; +} + AudioTrack::AudioTrack() : mStatus(NO_INIT), mState(STATE_STOPPED), @@ -188,8 +247,7 @@ mPreviousSchedulingGroup(SP_DEFAULT), mPausedPosition(0), mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE), - mRoutedDeviceId(AUDIO_PORT_HANDLE_NONE), - mPortId(AUDIO_PORT_HANDLE_NONE) + mRoutedDeviceId(AUDIO_PORT_HANDLE_NONE) { mAttributes.content_type = AUDIO_CONTENT_TYPE_UNKNOWN; mAttributes.usage = AUDIO_USAGE_UNKNOWN; @@ -214,19 +272,18 @@ pid_t pid, const audio_attributes_t* pAttributes, bool doNotReconnect, - float maxRequiredSpeed) + float maxRequiredSpeed, + audio_port_handle_t selectedDeviceId) : mStatus(NO_INIT), mState(STATE_STOPPED), mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT), - mPausedPosition(0), - mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE), - mPortId(AUDIO_PORT_HANDLE_NONE) + mPausedPosition(0) { - mStatus = set(streamType, sampleRate, format, channelMask, + (void)set(streamType, sampleRate, format, channelMask, frameCount, flags, cbf, user, notificationFrames, 0 /*sharedBuffer*/, false /*threadCanCallJava*/, sessionId, transferType, - offloadInfo, uid, pid, pAttributes, doNotReconnect, maxRequiredSpeed); + offloadInfo, uid, pid, pAttributes, doNotReconnect, maxRequiredSpeed, selectedDeviceId); } AudioTrack::AudioTrack( @@ -252,10 +309,9 @@ mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT), mPausedPosition(0), - mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE), - mPortId(AUDIO_PORT_HANDLE_NONE) + mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE) { - mStatus = set(streamType, sampleRate, format, channelMask, + (void)set(streamType, sampleRate, format, channelMask, 0 /*frameCount*/, flags, cbf, user, notificationFrames, sharedBuffer, false /*threadCanCallJava*/, sessionId, transferType, offloadInfo, uid, pid, pAttributes, doNotReconnect, maxRequiredSpeed); @@ -263,6 +319,9 @@ AudioTrack::~AudioTrack() { + // pull together the numbers, before we clean up our structures + mMediaMetrics.gather(this); + if (mStatus == NO_ERROR) { // Make sure that callback function exits in the case where // it is looping on buffer full condition in obtainBuffer(). @@ -308,14 +367,22 @@ pid_t pid, const audio_attributes_t* pAttributes, bool doNotReconnect, - float maxRequiredSpeed) + float maxRequiredSpeed, + audio_port_handle_t selectedDeviceId) { + status_t status; + uint32_t channelCount; + pid_t callingPid; + pid_t myPid; + ALOGV("set(): streamType %d, sampleRate %u, format %#x, channelMask %#x, frameCount %zu, " "flags #%x, notificationFrames %d, sessionId %d, transferType %d, uid %d, pid %d", streamType, sampleRate, format, channelMask, frameCount, flags, notificationFrames, sessionId, transferType, uid, pid); mThreadCanCallJava = threadCanCallJava; + mSelectedDeviceId = selectedDeviceId; + mSessionId = sessionId; switch (transferType) { case TRANSFER_DEFAULT: @@ -330,25 +397,29 @@ case TRANSFER_CALLBACK: if (cbf == NULL || sharedBuffer != 0) { ALOGE("Transfer type TRANSFER_CALLBACK but cbf == NULL || sharedBuffer != 0"); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } break; case TRANSFER_OBTAIN: case TRANSFER_SYNC: if (sharedBuffer != 0) { ALOGE("Transfer type TRANSFER_OBTAIN but sharedBuffer != 0"); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } break; case TRANSFER_SHARED: if (sharedBuffer == 0) { ALOGE("Transfer type TRANSFER_SHARED but sharedBuffer == 0"); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } break; default: ALOGE("Invalid transfer type %d", transferType); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mSharedBuffer = sharedBuffer; mTransfer = transferType; @@ -362,7 +433,8 @@ // invariant that mAudioTrack != 0 is true only after set() returns successfully if (mAudioTrack != 0) { ALOGE("Track already in use"); - return INVALID_OPERATION; + status = INVALID_OPERATION; + goto exit; } // handle default values first. @@ -372,7 +444,8 @@ if (pAttributes == NULL) { if (uint32_t(streamType) >= AUDIO_STREAM_PUBLIC_CNT) { ALOGE("Invalid stream type %d", streamType); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mStreamType = streamType; @@ -404,16 +477,18 @@ // validate parameters if (!audio_is_valid_format(format)) { ALOGE("Invalid format %#x", format); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mFormat = format; if (!audio_is_output_channel(channelMask)) { ALOGE("Invalid channel mask %#x", channelMask); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mChannelMask = channelMask; - uint32_t channelCount = audio_channel_count_from_out_mask(channelMask); + channelCount = audio_channel_count_from_out_mask(channelMask); mChannelCount = channelCount; // force direct flag if format is not linear PCM @@ -448,7 +523,8 @@ // sampling rate must be specified for direct outputs if (sampleRate == 0 && (flags & AUDIO_OUTPUT_FLAG_DIRECT) != 0) { - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mSampleRate = sampleRate; mOriginalSampleRate = sampleRate; @@ -479,12 +555,14 @@ if (!(flags & AUDIO_OUTPUT_FLAG_FAST)) { ALOGE("notificationFrames=%d not permitted for non-fast track", notificationFrames); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } if (frameCount > 0) { ALOGE("notificationFrames=%d not permitted with non-zero frameCount=%zu", notificationFrames, frameCount); - return BAD_VALUE; + status = BAD_VALUE; + goto exit; } mNotificationFramesReq = 0; const uint32_t minNotificationsPerBuffer = 1; @@ -496,20 +574,15 @@ notificationFrames, minNotificationsPerBuffer, maxNotificationsPerBuffer); } mNotificationFramesAct = 0; - if (sessionId == AUDIO_SESSION_ALLOCATE) { - mSessionId = (audio_session_t) AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION); - } else { - mSessionId = sessionId; - } - int callingpid = IPCThreadState::self()->getCallingPid(); - int mypid = getpid(); - if (uid == AUDIO_UID_INVALID || (callingpid != mypid)) { + callingPid = IPCThreadState::self()->getCallingPid(); + myPid = getpid(); + if (uid == AUDIO_UID_INVALID || (callingPid != myPid)) { mClientUid = IPCThreadState::self()->getCallingUid(); } else { mClientUid = uid; } - if (pid == -1 || (callingpid != mypid)) { - mClientPid = callingpid; + if (pid == -1 || (callingPid != myPid)) { + mClientPid = callingPid; } else { mClientPid = pid; } @@ -524,7 +597,7 @@ } // create the IAudioTrack - status_t status = createTrack_l(); + status = createTrack_l(); if (status != NO_ERROR) { if (mAudioTrackThread != 0) { @@ -532,10 +605,9 @@ mAudioTrackThread->requestExitAndWait(); mAudioTrackThread.clear(); } - return status; + goto exit; } - mStatus = NO_ERROR; mUserData = user; mLoopCount = 0; mLoopStart = 0; @@ -562,8 +634,11 @@ mFramesWritten = 0; mFramesWrittenServerOffset = 0; mFramesWrittenAtRestore = -1; // -1 is a unique initializer. - mVolumeHandler = new VolumeHandler(); - return NO_ERROR; + mVolumeHandler = new media::VolumeHandler(); + +exit: + mStatus = status; + return status; } // ------------------------------------------------------------------------- @@ -695,6 +770,7 @@ mReleased = 0; } + mProxy->stop(); // notify server not to read beyond current client position until start(). mProxy->interrupt(); mAudioTrack->stop(); @@ -730,7 +806,7 @@ return; } AutoMutex lock(mLock); - if (mState == STATE_ACTIVE || mState == STATE_FLUSHED) { + if (mState == STATE_ACTIVE) { return; } flush_l(); @@ -1219,6 +1295,7 @@ mSelectedDeviceId = deviceId; if (mStatus == NO_ERROR) { android_atomic_or(CBLK_INVALID, &mCblk->mFlags); + mProxy->interrupt(); } } return NO_ERROR; @@ -1306,76 +1383,19 @@ status_t AudioTrack::createTrack_l() { + status_t status; + bool callbackAdded = false; + const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger(); if (audioFlinger == 0) { ALOGE("Could not get audioflinger"); - return NO_INIT; + status = NO_INIT; + goto exit; } - audio_io_handle_t output; - audio_stream_type_t streamType = mStreamType; - audio_attributes_t *attr = (mStreamType == AUDIO_STREAM_DEFAULT) ? &mAttributes : NULL; - bool callbackAdded = false; - + { // mFlags (not mOrigFlags) is modified depending on whether fast request is accepted. // After fast request is denied, we will request again if IAudioTrack is re-created. - - status_t status; - audio_config_t config = AUDIO_CONFIG_INITIALIZER; - config.sample_rate = mSampleRate; - config.channel_mask = mChannelMask; - config.format = mFormat; - config.offload_info = mOffloadInfoCopy; - mRoutedDeviceId = mSelectedDeviceId; - status = AudioSystem::getOutputForAttr(attr, &output, - mSessionId, &streamType, mClientUid, - &config, - mFlags, &mRoutedDeviceId, &mPortId); - - if (status != NO_ERROR || output == AUDIO_IO_HANDLE_NONE) { - ALOGE("Could not get audio output for session %d, stream type %d, usage %d, sample rate %u," - " format %#x, channel mask %#x, flags %#x", - mSessionId, streamType, mAttributes.usage, mSampleRate, mFormat, mChannelMask, - mFlags); - return BAD_VALUE; - } - { - // Now that we have a reference to an I/O handle and have not yet handed it off to AudioFlinger, - // we must release it ourselves if anything goes wrong. - - // Not all of these values are needed under all conditions, but it is easier to get them all - status = AudioSystem::getLatency(output, &mAfLatency); - if (status != NO_ERROR) { - ALOGE("getLatency(%d) failed status %d", output, status); - goto release; - } - ALOGV("createTrack_l() output %d afLatency %u", output, mAfLatency); - - status = AudioSystem::getFrameCount(output, &mAfFrameCount); - if (status != NO_ERROR) { - ALOGE("getFrameCount(output=%d) status %d", output, status); - goto release; - } - - // TODO consider making this a member variable if there are other uses for it later - size_t afFrameCountHAL; - status = AudioSystem::getFrameCountHAL(output, &afFrameCountHAL); - if (status != NO_ERROR) { - ALOGE("getFrameCountHAL(output=%d) status %d", output, status); - goto release; - } - ALOG_ASSERT(afFrameCountHAL > 0); - - status = AudioSystem::getSamplingRate(output, &mAfSampleRate); - if (status != NO_ERROR) { - ALOGE("getSamplingRate(output=%d) status %d", output, status); - goto release; - } - if (mSampleRate == 0) { - mSampleRate = mAfSampleRate; - mOriginalSampleRate = mAfSampleRate; - } - // Client can only express a preference for FAST. Server will perform additional tests. if (mFlags & AUDIO_OUTPUT_FLAG_FAST) { // either of these use cases: @@ -1389,130 +1409,81 @@ // use case 4: synchronous write ((mTransfer == TRANSFER_SYNC) && mThreadCanCallJava); - bool useCaseAllowed = sharedBuffer || transferAllowed; - if (!useCaseAllowed) { - ALOGW("AUDIO_OUTPUT_FLAG_FAST denied, not shared buffer and transfer = %s", - convertTransferToText(mTransfer)); - } - - // sample rates must also match - bool sampleRateAllowed = mSampleRate == mAfSampleRate; - if (!sampleRateAllowed) { - ALOGW("AUDIO_OUTPUT_FLAG_FAST denied, rates do not match %u Hz, require %u Hz", - mSampleRate, mAfSampleRate); - } - - bool fastAllowed = useCaseAllowed && sampleRateAllowed; + bool fastAllowed = sharedBuffer || transferAllowed; if (!fastAllowed) { + ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by client, not shared buffer and transfer = %s", + convertTransferToText(mTransfer)); mFlags = (audio_output_flags_t) (mFlags & ~AUDIO_OUTPUT_FLAG_FAST); } } - mNotificationFramesAct = mNotificationFramesReq; - - size_t frameCount = mReqFrameCount; - if (!audio_has_proportional_frames(mFormat)) { - - if (mSharedBuffer != 0) { - // Same comment as below about ignoring frameCount parameter for set() - frameCount = mSharedBuffer->size(); - } else if (frameCount == 0) { - frameCount = mAfFrameCount; - } - if (mNotificationFramesAct != frameCount) { - mNotificationFramesAct = frameCount; - } - } else if (mSharedBuffer != 0) { - // FIXME: Ensure client side memory buffers need - // not have additional alignment beyond sample - // (e.g. 16 bit stereo accessed as 32 bit frame). - size_t alignment = audio_bytes_per_sample(mFormat); - if (alignment & 1) { - // for AUDIO_FORMAT_PCM_24_BIT_PACKED (not exposed through Java). - alignment = 1; - } - if (mChannelCount > 1) { - // More than 2 channels does not require stronger alignment than stereo - alignment <<= 1; - } - if (((uintptr_t)mSharedBuffer->pointer() & (alignment - 1)) != 0) { - ALOGE("Invalid buffer alignment: address %p, channel count %u", - mSharedBuffer->pointer(), mChannelCount); - status = BAD_VALUE; - goto release; - } - - // When initializing a shared buffer AudioTrack via constructors, - // there's no frameCount parameter. - // But when initializing a shared buffer AudioTrack via set(), - // there _is_ a frameCount parameter. We silently ignore it. - frameCount = mSharedBuffer->size() / mFrameSize; + IAudioFlinger::CreateTrackInput input; + if (mStreamType != AUDIO_STREAM_DEFAULT) { + stream_type_to_audio_attributes(mStreamType, &input.attr); } else { - size_t minFrameCount = 0; - // For fast tracks the frame count calculations and checks are mostly done by server, - // but we try to respect the application's request for notifications per buffer. - if (mFlags & AUDIO_OUTPUT_FLAG_FAST) { - if (mNotificationsPerBufferReq > 0) { - // Avoid possible arithmetic overflow during multiplication. - // mNotificationsPerBuffer is clamped to a small integer earlier, so it is unlikely. - if (mNotificationsPerBufferReq > SIZE_MAX / afFrameCountHAL) { - ALOGE("Requested notificationPerBuffer=%u ignored for HAL frameCount=%zu", - mNotificationsPerBufferReq, afFrameCountHAL); - } else { - minFrameCount = afFrameCountHAL * mNotificationsPerBufferReq; - } - } - } else { - // for normal tracks precompute the frame count based on speed. - const float speed = !isPurePcmData_l() || isOffloadedOrDirect_l() ? 1.0f : - max(mMaxRequiredSpeed, mPlaybackRate.mSpeed); - minFrameCount = calculateMinFrameCount( - mAfLatency, mAfFrameCount, mAfSampleRate, mSampleRate, - speed /*, 0 mNotificationsPerBufferReq*/); - } - if (frameCount < minFrameCount) { - frameCount = minFrameCount; - } + input.attr = mAttributes; } - - audio_output_flags_t flags = mFlags; - - pid_t tid = -1; + input.config = AUDIO_CONFIG_INITIALIZER; + input.config.sample_rate = mSampleRate; + input.config.channel_mask = mChannelMask; + input.config.format = mFormat; + input.config.offload_info = mOffloadInfoCopy; + input.clientInfo.clientUid = mClientUid; + input.clientInfo.clientPid = mClientPid; + input.clientInfo.clientTid = -1; if (mFlags & AUDIO_OUTPUT_FLAG_FAST) { // It is currently meaningless to request SCHED_FIFO for a Java thread. Even if the // application-level code follows all non-blocking design rules, the language runtime // doesn't also follow those rules, so the thread will not benefit overall. if (mAudioTrackThread != 0 && !mThreadCanCallJava) { - tid = mAudioTrackThread->getTid(); + input.clientInfo.clientTid = mAudioTrackThread->getTid(); } } + input.sharedBuffer = mSharedBuffer; + input.notificationsPerBuffer = mNotificationsPerBufferReq; + input.speed = 1.0; + if (audio_has_proportional_frames(mFormat) && mSharedBuffer == 0 && + (mFlags & AUDIO_OUTPUT_FLAG_FAST) == 0) { + input.speed = !isPurePcmData_l() || isOffloadedOrDirect_l() ? 1.0f : + max(mMaxRequiredSpeed, mPlaybackRate.mSpeed); + } + input.flags = mFlags; + input.frameCount = mReqFrameCount; + input.notificationFrameCount = mNotificationFramesReq; + input.selectedDeviceId = mSelectedDeviceId; + input.sessionId = mSessionId; - size_t temp = frameCount; // temp may be replaced by a revised value of frameCount, - // but we will still need the original value also - audio_session_t originalSessionId = mSessionId; - sp<IAudioTrack> track = audioFlinger->createTrack(streamType, - mSampleRate, - mFormat, - mChannelMask, - &temp, - &flags, - mSharedBuffer, + IAudioFlinger::CreateTrackOutput output; + + sp<IAudioTrack> track = audioFlinger->createTrack(input, output, - mClientPid, - tid, - &mSessionId, - mClientUid, - &status, - mPortId); - ALOGE_IF(originalSessionId != AUDIO_SESSION_ALLOCATE && mSessionId != originalSessionId, - "session ID changed from %d to %d", originalSessionId, mSessionId); + &status); - if (status != NO_ERROR) { - ALOGE("AudioFlinger could not create track, status: %d", status); - goto release; + if (status != NO_ERROR || output.outputId == AUDIO_IO_HANDLE_NONE) { + ALOGE("AudioFlinger could not create track, status: %d output %d", status, output.outputId); + if (status == NO_ERROR) { + status = NO_INIT; + } + goto exit; } ALOG_ASSERT(track != 0); + mFrameCount = output.frameCount; + mNotificationFramesAct = (uint32_t)output.notificationFrameCount; + mRoutedDeviceId = output.selectedDeviceId; + mSessionId = output.sessionId; + + mSampleRate = output.sampleRate; + if (mOriginalSampleRate == 0) { + mOriginalSampleRate = mSampleRate; + } + + mAfFrameCount = output.afFrameCount; + mAfSampleRate = output.afSampleRate; + mAfLatency = output.afLatencyMs; + + mLatency = mAfLatency + (1000LL * mFrameCount) / mSampleRate; + // AudioFlinger now owns the reference to the I/O handle, // so we are no longer responsible for releasing it. @@ -1521,13 +1492,13 @@ if (iMem == 0) { ALOGE("Could not get control block"); status = NO_INIT; - goto release; + goto exit; } void *iMemPointer = iMem->pointer(); if (iMemPointer == NULL) { ALOGE("Could not get control block pointer"); status = NO_INIT; - goto release; + goto exit; } // invariant that mAudioTrack != 0 is true only after set() returns successfully if (mAudioTrack != 0) { @@ -1540,66 +1511,33 @@ audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer); mCblk = cblk; - // note that temp is the (possibly revised) value of frameCount - if (temp < frameCount || (frameCount == 0 && temp == 0)) { - // In current design, AudioTrack client checks and ensures frame count validity before - // passing it to AudioFlinger so AudioFlinger should not return a different value except - // for fast track as it uses a special method of assigning frame count. - ALOGW("Requested frameCount %zu but received frameCount %zu", frameCount, temp); - } - frameCount = temp; mAwaitBoost = false; if (mFlags & AUDIO_OUTPUT_FLAG_FAST) { - if (flags & AUDIO_OUTPUT_FLAG_FAST) { - ALOGI("AUDIO_OUTPUT_FLAG_FAST successful; frameCount %zu -> %zu", frameCount, temp); + if (output.flags & AUDIO_OUTPUT_FLAG_FAST) { + ALOGI("AUDIO_OUTPUT_FLAG_FAST successful; frameCount %zu -> %zu", + mReqFrameCount, mFrameCount); if (!mThreadCanCallJava) { mAwaitBoost = true; } } else { - ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %zu -> %zu", frameCount, - temp); + ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %zu -> %zu", mReqFrameCount, + mFrameCount); } } - mFlags = flags; - - // Make sure that application is notified with sufficient margin before underrun. - // The client can divide the AudioTrack buffer into sub-buffers, - // and expresses its desire to server as the notification frame count. - if (mSharedBuffer == 0 && audio_is_linear_pcm(mFormat)) { - size_t maxNotificationFrames; - if (mFlags & AUDIO_OUTPUT_FLAG_FAST) { - // notify every HAL buffer, regardless of the size of the track buffer - maxNotificationFrames = afFrameCountHAL; - } else { - // For normal tracks, use at least double-buffering if no sample rate conversion, - // or at least triple-buffering if there is sample rate conversion - const int nBuffering = mOriginalSampleRate == mAfSampleRate ? 2 : 3; - maxNotificationFrames = frameCount / nBuffering; - } - if (mNotificationFramesAct == 0 || mNotificationFramesAct > maxNotificationFrames) { - if (mNotificationFramesAct == 0) { - ALOGD("Client defaulted notificationFrames to %zu for frameCount %zu", - maxNotificationFrames, frameCount); - } else { - ALOGW("Client adjusted notificationFrames from %u to %zu for frameCount %zu", - mNotificationFramesAct, maxNotificationFrames, frameCount); - } - mNotificationFramesAct = (uint32_t) maxNotificationFrames; - } - } + mFlags = output.flags; //mOutput != output includes the case where mOutput == AUDIO_IO_HANDLE_NONE for first creation - if (mDeviceCallback != 0 && mOutput != output) { + if (mDeviceCallback != 0 && mOutput != output.outputId) { if (mOutput != AUDIO_IO_HANDLE_NONE) { AudioSystem::removeAudioDeviceCallback(this, mOutput); } - AudioSystem::addAudioDeviceCallback(this, output); + AudioSystem::addAudioDeviceCallback(this, output.outputId); callbackAdded = true; } // We retain a copy of the I/O handle, but don't own the reference - mOutput = output; + mOutput = output.outputId; mRefreshRemaining = true; // Starting address of buffers in shared memory. If there is a shared buffer, buffers @@ -1614,18 +1552,16 @@ if (buffers == NULL) { ALOGE("Could not get buffer pointer"); status = NO_INIT; - goto release; + goto exit; } } mAudioTrack->attachAuxEffect(mAuxEffectId); - mFrameCount = frameCount; - updateLatency_l(); // this refetches mAfLatency and sets mLatency // If IAudioTrack is re-created, don't let the requested frameCount // decrease. This can confuse clients that cache frameCount(). - if (frameCount > mReqFrameCount) { - mReqFrameCount = frameCount; + if (mFrameCount > mReqFrameCount) { + mReqFrameCount = mFrameCount; } // reset server position to 0 as we have new cblk. @@ -1634,9 +1570,9 @@ // update proxy if (mSharedBuffer == 0) { mStaticProxy.clear(); - mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize); + mProxy = new AudioTrackClientProxy(cblk, buffers, mFrameCount, mFrameSize); } else { - mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize); + mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, mFrameCount, mFrameSize); mProxy = mStaticProxy; } @@ -1659,18 +1595,17 @@ mDeathNotifier = new DeathNotifier(this); IInterface::asBinder(mAudioTrack)->linkToDeath(mDeathNotifier, this); - return NO_ERROR; } -release: - AudioSystem::releaseOutput(output, streamType, mSessionId); - if (callbackAdded) { +exit: + if (status != NO_ERROR && callbackAdded) { // note: mOutput is always valid is callbackAdded is true AudioSystem::removeAudioDeviceCallback(this, mOutput); } - if (status == NO_ERROR) { - status = NO_INIT; - } + + mStatus = status; + + // sp<IAudioTrack> track destructor will cause releaseOutput() to be called by AudioFlinger return status; } @@ -2314,6 +2249,16 @@ staticPosition = mStaticProxy->getPosition().unsignedValue(); } + // See b/74409267. Connecting to a BT A2DP device supporting multiple codecs + // causes a lot of churn on the service side, and it can reject starting + // playback of a previously created track. May also apply to other cases. + const int INITIAL_RETRIES = 3; + int retries = INITIAL_RETRIES; +retry: + if (retries < INITIAL_RETRIES) { + // See the comment for clearAudioConfigCache at the start of the function. + AudioSystem::clearAudioConfigCache(); + } mFlags = mOrigFlags; // If a new IAudioTrack is successfully created, createTrack_l() will modify the @@ -2322,7 +2267,10 @@ // If a new IAudioTrack cannot be created, the previous (dead) instance will be left intact. status_t result = createTrack_l(); - if (result == NO_ERROR) { + if (result != NO_ERROR) { + ALOGW("%s(): createTrack_l failed, do not retry", __func__); + retries = 0; + } else { // take the frames that will be lost by track recreation into account in saved position // For streaming tracks, this is the amount we obtained from the user/client // (not the number actually consumed at the server - those are already lost). @@ -2367,7 +2315,10 @@ mFramesWrittenAtRestore = mFramesWrittenServerOffset; } if (result != NO_ERROR) { - ALOGW("restoreTrack_l() failed status %d", result); + ALOGW("%s() failed status %d, retries %d", __func__, result, retries); + if (--retries > 0) { + goto retry; + } mState = STATE_STOPPED; mReleased = 0; } @@ -2406,8 +2357,8 @@ return true; // static tracks do not have issues with buffer sizing. } const size_t minFrameCount = - calculateMinFrameCount(mAfLatency, mAfFrameCount, mAfSampleRate, sampleRate, speed - /*, 0 mNotificationsPerBufferReq*/); + AudioSystem::calculateMinFrameCount(mAfLatency, mAfFrameCount, mAfSampleRate, + sampleRate, speed /*, 0 mNotificationsPerBufferReq*/); const bool allowed = mFrameCount >= minFrameCount; ALOGD_IF(!allowed, "isSampleRateSpeedAllowed_l denied " @@ -2424,6 +2375,17 @@ return mAudioTrack->setParameters(keyValuePairs); } +status_t AudioTrack::selectPresentation(int presentationId, int programId) +{ + AutoMutex lock(mLock); + AudioParameter param = AudioParameter(); + param.addInt(String8(AudioParameter::keyPresentationId), presentationId); + param.addInt(String8(AudioParameter::keyProgramId), programId); + ALOGV("PresentationId/ProgramId[%s]",param.toString().string()); + + return mAudioTrack->setParameters(param.toString()); +} + VolumeShaper::Status AudioTrack::applyVolumeShaper( const sp<VolumeShaper::Configuration>& configuration, const sp<VolumeShaper::Operation>& operation) @@ -2823,23 +2785,28 @@ status_t AudioTrack::dump(int fd, const Vector<String16>& args __unused) const { - - const size_t SIZE = 256; - char buffer[SIZE]; String8 result; result.append(" AudioTrack::dump\n"); - snprintf(buffer, 255, " stream type(%d), left - right volume(%f, %f)\n", mStreamType, - mVolume[AUDIO_INTERLEAVE_LEFT], mVolume[AUDIO_INTERLEAVE_RIGHT]); - result.append(buffer); - snprintf(buffer, 255, " format(%d), channel count(%d), frame count(%zu)\n", mFormat, - mChannelCount, mFrameCount); - result.append(buffer); - snprintf(buffer, 255, " sample rate(%u), speed(%f), status(%d)\n", - mSampleRate, mPlaybackRate.mSpeed, mStatus); - result.append(buffer); - snprintf(buffer, 255, " state(%d), latency (%d)\n", mState, mLatency); - result.append(buffer); + result.appendFormat(" status(%d), state(%d), session Id(%d), flags(%#x)\n", + mStatus, mState, mSessionId, mFlags); + result.appendFormat(" stream type(%d), left - right volume(%f, %f)\n", + (mStreamType == AUDIO_STREAM_DEFAULT) ? + audio_attributes_to_stream_type(&mAttributes) : mStreamType, + mVolume[AUDIO_INTERLEAVE_LEFT], mVolume[AUDIO_INTERLEAVE_RIGHT]); + result.appendFormat(" format(%#x), channel mask(%#x), channel count(%u)\n", + mFormat, mChannelMask, mChannelCount); + result.appendFormat(" sample rate(%u), original sample rate(%u), speed(%f)\n", + mSampleRate, mOriginalSampleRate, mPlaybackRate.mSpeed); + result.appendFormat(" frame count(%zu), req. frame count(%zu)\n", + mFrameCount, mReqFrameCount); + result.appendFormat(" notif. frame count(%u), req. notif. frame count(%u)," + " req. notif. per buff(%u)\n", + mNotificationFramesAct, mNotificationFramesReq, mNotificationsPerBufferReq); + result.appendFormat(" latency (%d), selected device Id(%d), routed device Id(%d)\n", + mLatency, mSelectedDeviceId, mRoutedDeviceId); + result.appendFormat(" output(%d) AF latency (%u) AF frame count(%zu) AF SampleRate(%u)\n", + mOutput, mAfLatency, mAfFrameCount, mAfSampleRate); ::write(fd, result.string(), result.size()); return NO_ERROR; }
diff --git a/media/libaudioclient/AudioTrackShared.cpp b/media/libaudioclient/AudioTrackShared.cpp index 7bf4f99..dced3c4 100644 --- a/media/libaudioclient/AudioTrackShared.cpp +++ b/media/libaudioclient/AudioTrackShared.cpp
@@ -393,19 +393,50 @@ // --------------------------------------------------------------------------- -__attribute__((no_sanitize("integer"))) void AudioTrackClientProxy::flush() { + sendStreamingFlushStop(true /* flush */); +} + +void AudioTrackClientProxy::stop() +{ + sendStreamingFlushStop(false /* flush */); +} + +// Sets the client-written mFlush and mStop positions, which control server behavior. +// +// @param flush indicates whether the operation is a flush or stop. +// A client stop sets mStop to the current write position; +// the server will not read past this point until start() or subsequent flush(). +// A client flush sets both mStop and mFlush to the current write position. +// This advances the server read limit (if previously set) and on the next +// server read advances the server read position to this limit. +// +void AudioTrackClientProxy::sendStreamingFlushStop(bool flush) +{ + // TODO: Replace this by 64 bit counters - avoids wrap complication. // This works for mFrameCountP2 <= 2^30 - size_t increment = mFrameCountP2 << 1; - size_t mask = increment - 1; - audio_track_cblk_t* cblk = mCblk; // mFlush is 32 bits concatenated as [ flush_counter ] [ newfront_offset ] // Should newFlush = cblk->u.mStreaming.mRear? Only problem is // if you want to flush twice to the same rear location after a 32 bit wrap. - int32_t newFlush = (cblk->u.mStreaming.mRear & mask) | - ((cblk->u.mStreaming.mFlush & ~mask) + increment); - android_atomic_release_store(newFlush, &cblk->u.mStreaming.mFlush); + + const size_t increment = mFrameCountP2 << 1; + const size_t mask = increment - 1; + // No need for client atomic synchronization on mRear, mStop, mFlush + // as AudioTrack client only read/writes to them under client lock. Server only reads. + const int32_t rearMasked = mCblk->u.mStreaming.mRear & mask; + + // update stop before flush so that the server front + // never advances beyond a (potential) previous stop's rear limit. + int32_t stopBits; // the following add can overflow + __builtin_add_overflow(mCblk->u.mStreaming.mStop & ~mask, increment, &stopBits); + android_atomic_release_store(rearMasked | stopBits, &mCblk->u.mStreaming.mStop); + + if (flush) { + int32_t flushBits; // the following add can overflow + __builtin_add_overflow(mCblk->u.mStreaming.mFlush & ~mask, increment, &flushBits); + android_atomic_release_store(rearMasked | flushBits, &mCblk->u.mStreaming.mFlush); + } } bool AudioTrackClientProxy::clearStreamEndDone() { @@ -540,6 +571,11 @@ LOG_ALWAYS_FATAL("static flush"); } +void StaticAudioTrackClientProxy::stop() +{ + ; // no special handling required for static tracks. +} + void StaticAudioTrackClientProxy::setLoop(size_t loopStart, size_t loopEnd, int loopCount) { // This can only happen on a 64-bit client @@ -638,6 +674,7 @@ if (flush != mFlush) { ALOGV("ServerProxy::flushBufferIfNeeded() mStreaming.mFlush = 0x%x, mFlush = 0x%0x", flush, mFlush); + // shouldn't matter, but for range safety use mRear instead of getRear(). int32_t rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear); int32_t front = cblk->u.mStreaming.mFront; @@ -677,6 +714,46 @@ } __attribute__((no_sanitize("integer"))) +int32_t AudioTrackServerProxy::getRear() const +{ + const int32_t stop = android_atomic_acquire_load(&mCblk->u.mStreaming.mStop); + const int32_t rear = android_atomic_acquire_load(&mCblk->u.mStreaming.mRear); + const int32_t stopLast = mStopLast.load(std::memory_order_acquire); + if (stop != stopLast) { + const int32_t front = mCblk->u.mStreaming.mFront; + const size_t overflowBit = mFrameCountP2 << 1; + const size_t mask = overflowBit - 1; + int32_t newRear = (rear & ~mask) | (stop & mask); + ssize_t filled = newRear - front; + // overflowBit is unsigned, so cast to signed for comparison. + if (filled >= (ssize_t)overflowBit) { + // front and rear offsets span the overflow bit of the p2 mask + // so rebasing newRear on the rear offset is off by the overflow bit. + ALOGV("stop wrap: filled %zx >= overflowBit %zx", filled, overflowBit); + newRear -= overflowBit; + filled -= overflowBit; + } + if (0 <= filled && (size_t) filled <= mFrameCount) { + // we're stopped, return the stop level as newRear + return newRear; + } + + // A corrupt stop. Log error and ignore. + ALOGE("mStopLast %#x -> stop %#x, front %#x, rear %#x, mask %#x, newRear %#x, " + "filled %zd=%#x", + stopLast, stop, front, rear, + (unsigned)mask, newRear, filled, (unsigned)filled); + // Don't reset mStopLast as this is const. + } + return rear; +} + +void AudioTrackServerProxy::start() +{ + mStopLast = android_atomic_acquire_load(&mCblk->u.mStreaming.mStop); +} + +__attribute__((no_sanitize("integer"))) status_t ServerProxy::obtainBuffer(Buffer* buffer, bool ackFlush) { LOG_ALWAYS_FATAL_IF(buffer == NULL || buffer->mFrameCount == 0, @@ -693,7 +770,7 @@ // See notes on barriers at ClientProxy::obtainBuffer() if (mIsOut) { flushBufferIfNeeded(); // might modify mFront - rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear); + rear = getRear(); front = cblk->u.mStreaming.mFront; } else { front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront); @@ -825,8 +902,7 @@ // FIXME should return an accurate value, but over-estimate is better than under-estimate return mFrameCount; } - // the acquire might not be necessary since not doing a subsequent read - int32_t rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear); + const int32_t rear = getRear(); ssize_t filled = rear - cblk->u.mStreaming.mFront; // pipe should not already be overfull if (!(0 <= filled && (size_t) filled <= mFrameCount)) { @@ -852,7 +928,7 @@ if (flush != mFlush) { return mFrameCount; } - const int32_t rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear); + const int32_t rear = getRear(); const ssize_t filled = rear - cblk->u.mStreaming.mFront; if (!(0 <= filled && (size_t) filled <= mFrameCount)) { return 0; // error condition, silently return 0. @@ -1149,6 +1225,12 @@ } } +int32_t StaticAudioTrackServerProxy::getRear() const +{ + LOG_ALWAYS_FATAL("getRear() not permitted for static tracks"); + return 0; +} + // --------------------------------------------------------------------------- } // namespace android
diff --git a/media/libaudioclient/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp index 14feada..9f3b742 100644 --- a/media/libaudioclient/IAudioFlinger.cpp +++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -22,7 +22,11 @@ #include <stdint.h> #include <sys/types.h> +#include <binder/IPCThreadState.h> #include <binder/Parcel.h> +#include <cutils/multiuser.h> +#include <media/TimeCheck.h> +#include <private/android_filesystem_config.h> #include "IAudioFlinger.h" @@ -30,7 +34,7 @@ enum { CREATE_TRACK = IBinder::FIRST_CALL_TRANSACTION, - OPEN_RECORD, + CREATE_RECORD, SAMPLE_RATE, RESERVED, // obsolete, was CHANNEL_COUNT FORMAT, @@ -47,6 +51,7 @@ SET_MODE, SET_MIC_MUTE, GET_MIC_MUTE, + SET_RECORD_SILENCED, SET_PARAMETERS, GET_PARAMETERS, REGISTER_CLIENT, @@ -83,6 +88,7 @@ GET_AUDIO_HW_SYNC_FOR_SESSION, SYSTEM_READY, FRAME_COUNT_HAL, + GET_MICROPHONES, }; #define MAX_ITEMS_PER_LIST 1024 @@ -95,182 +101,74 @@ { } - virtual sp<IAudioTrack> createTrack( - audio_stream_type_t streamType, - uint32_t sampleRate, - audio_format_t format, - audio_channel_mask_t channelMask, - size_t *pFrameCount, - audio_output_flags_t *flags, - const sp<IMemory>& sharedBuffer, - audio_io_handle_t output, - pid_t pid, - pid_t tid, - audio_session_t *sessionId, - int clientUid, - status_t *status, - audio_port_handle_t portId) + virtual sp<IAudioTrack> createTrack(const CreateTrackInput& input, + CreateTrackOutput& output, + status_t *status) { Parcel data, reply; sp<IAudioTrack> track; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); - data.writeInt32((int32_t) streamType); - data.writeInt32(sampleRate); - data.writeInt32(format); - data.writeInt32(channelMask); - size_t frameCount = pFrameCount != NULL ? *pFrameCount : 0; - data.writeInt64(frameCount); - audio_output_flags_t lFlags = flags != NULL ? *flags : AUDIO_OUTPUT_FLAG_NONE; - data.writeInt32(lFlags); - // haveSharedBuffer - if (sharedBuffer != 0) { - data.writeInt32(true); - data.writeStrongBinder(IInterface::asBinder(sharedBuffer)); - } else { - data.writeInt32(false); + + if (status == nullptr) { + return track; } - data.writeInt32((int32_t) output); - data.writeInt32((int32_t) pid); - data.writeInt32((int32_t) tid); - audio_session_t lSessionId = AUDIO_SESSION_ALLOCATE; - if (sessionId != NULL) { - lSessionId = *sessionId; - } - data.writeInt32(lSessionId); - data.writeInt32(clientUid); - data.writeInt32(portId); + + input.writeToParcel(&data); + status_t lStatus = remote()->transact(CREATE_TRACK, data, &reply); if (lStatus != NO_ERROR) { - ALOGE("createTrack error: %s", strerror(-lStatus)); - } else { - frameCount = reply.readInt64(); - if (pFrameCount != NULL) { - *pFrameCount = frameCount; - } - lFlags = (audio_output_flags_t)reply.readInt32(); - if (flags != NULL) { - *flags = lFlags; - } - lSessionId = (audio_session_t) reply.readInt32(); - if (sessionId != NULL) { - *sessionId = lSessionId; - } - lStatus = reply.readInt32(); - track = interface_cast<IAudioTrack>(reply.readStrongBinder()); - if (lStatus == NO_ERROR) { - if (track == 0) { - ALOGE("createTrack should have returned an IAudioTrack"); - lStatus = UNKNOWN_ERROR; - } - } else { - if (track != 0) { - ALOGE("createTrack returned an IAudioTrack but with status %d", lStatus); - track.clear(); - } - } + ALOGE("createTrack transaction error %d", lStatus); + *status = DEAD_OBJECT; + return track; } - if (status != NULL) { - *status = lStatus; + *status = reply.readInt32(); + if (*status != NO_ERROR) { + ALOGE("createTrack returned error %d", *status); + return track; } + track = interface_cast<IAudioTrack>(reply.readStrongBinder()); + if (track == 0) { + ALOGE("createTrack returned an NULL IAudioTrack with status OK"); + *status = DEAD_OBJECT; + return track; + } + output.readFromParcel(&reply); return track; } - virtual sp<IAudioRecord> openRecord( - audio_io_handle_t input, - uint32_t sampleRate, - audio_format_t format, - audio_channel_mask_t channelMask, - const String16& opPackageName, - size_t *pFrameCount, - audio_input_flags_t *flags, - pid_t pid, - pid_t tid, - int clientUid, - audio_session_t *sessionId, - size_t *notificationFrames, - sp<IMemory>& cblk, - sp<IMemory>& buffers, - status_t *status, - audio_port_handle_t portId) + virtual sp<media::IAudioRecord> createRecord(const CreateRecordInput& input, + CreateRecordOutput& output, + status_t *status) { Parcel data, reply; - sp<IAudioRecord> record; + sp<media::IAudioRecord> record; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); - data.writeInt32((int32_t) input); - data.writeInt32(sampleRate); - data.writeInt32(format); - data.writeInt32(channelMask); - data.writeString16(opPackageName); - size_t frameCount = pFrameCount != NULL ? *pFrameCount : 0; - data.writeInt64(frameCount); - audio_input_flags_t lFlags = flags != NULL ? *flags : AUDIO_INPUT_FLAG_NONE; - data.writeInt32(lFlags); - data.writeInt32((int32_t) pid); - data.writeInt32((int32_t) tid); - data.writeInt32((int32_t) clientUid); - audio_session_t lSessionId = AUDIO_SESSION_ALLOCATE; - if (sessionId != NULL) { - lSessionId = *sessionId; + + if (status == nullptr) { + return record; } - data.writeInt32(lSessionId); - data.writeInt64(notificationFrames != NULL ? *notificationFrames : 0); - data.writeInt32(portId); - cblk.clear(); - buffers.clear(); - status_t lStatus = remote()->transact(OPEN_RECORD, data, &reply); + + input.writeToParcel(&data); + + status_t lStatus = remote()->transact(CREATE_RECORD, data, &reply); if (lStatus != NO_ERROR) { - ALOGE("openRecord error: %s", strerror(-lStatus)); - } else { - frameCount = reply.readInt64(); - if (pFrameCount != NULL) { - *pFrameCount = frameCount; - } - lFlags = (audio_input_flags_t)reply.readInt32(); - if (flags != NULL) { - *flags = lFlags; - } - lSessionId = (audio_session_t) reply.readInt32(); - if (sessionId != NULL) { - *sessionId = lSessionId; - } - size_t lNotificationFrames = (size_t) reply.readInt64(); - if (notificationFrames != NULL) { - *notificationFrames = lNotificationFrames; - } - lStatus = reply.readInt32(); - record = interface_cast<IAudioRecord>(reply.readStrongBinder()); - cblk = interface_cast<IMemory>(reply.readStrongBinder()); - if (cblk != 0 && cblk->pointer() == NULL) { - cblk.clear(); - } - buffers = interface_cast<IMemory>(reply.readStrongBinder()); - if (buffers != 0 && buffers->pointer() == NULL) { - buffers.clear(); - } - if (lStatus == NO_ERROR) { - if (record == 0) { - ALOGE("openRecord should have returned an IAudioRecord"); - lStatus = UNKNOWN_ERROR; - } else if (cblk == 0) { - ALOGE("openRecord should have returned a cblk"); - lStatus = NO_MEMORY; - } - // buffers is permitted to be 0 - } else { - if (record != 0 || cblk != 0 || buffers != 0) { - ALOGE("openRecord returned an IAudioRecord, cblk, " - "or buffers but with status %d", lStatus); - } - } - if (lStatus != NO_ERROR) { - record.clear(); - cblk.clear(); - buffers.clear(); - } + ALOGE("createRecord transaction error %d", lStatus); + *status = DEAD_OBJECT; + return record; } - if (status != NULL) { - *status = lStatus; + *status = reply.readInt32(); + if (*status != NO_ERROR) { + ALOGE("createRecord returned error %d", *status); + return record; } + + record = interface_cast<media::IAudioRecord>(reply.readStrongBinder()); + if (record == 0) { + ALOGE("createRecord returned a NULL IAudioRecord with status OK"); + *status = DEAD_OBJECT; + return record; + } + output.readFromParcel(&reply); return record; } @@ -413,6 +311,15 @@ return reply.readInt32(); } + virtual void setRecordSilenced(uid_t uid, bool silenced) + { + Parcel data, reply; + data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + data.writeInt32(uid); + data.writeInt32(silenced ? 1 : 0); + remote()->transact(SET_RECORD_SILENCED, data, &reply); + } + virtual status_t setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs) { Parcel data, reply; @@ -804,14 +711,18 @@ return reply.readInt64(); } - virtual status_t setLowRamDevice(bool isLowRamDevice) + virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) override { Parcel data, reply; - data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); - data.writeInt32((int) isLowRamDevice); - remote()->transact(SET_LOW_RAM_DEVICE, data, &reply); - return reply.readInt32(); + + static_assert(NO_ERROR == 0, "NO_ERROR must be 0"); + return data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()) + ?: data.writeInt32((int) isLowRamDevice) + ?: data.writeInt64(totalMemory) + ?: remote()->transact(SET_LOW_RAM_DEVICE, data, &reply) + ?: reply.readInt32(); } + virtual status_t listAudioPorts(unsigned int *num_ports, struct audio_port *ports) { @@ -935,6 +846,18 @@ } return reply.readInt64(); } + virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones) + { + Parcel data, reply; + data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_MICROPHONES, data, &reply); + if (status != NO_ERROR || + (status = (status_t)reply.readInt32()) != NO_ERROR) { + return status; + } + status = reply.readParcelableVector(microphones); + return status; + } }; IMPLEMENT_META_INTERFACE(AudioFlinger, "android.media.IAudioFlinger"); @@ -944,21 +867,81 @@ status_t BnAudioFlinger::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { + // make sure transactions reserved to AudioPolicyManager do not come from other processes + switch (code) { + case SET_STREAM_VOLUME: + case SET_STREAM_MUTE: + case OPEN_OUTPUT: + case OPEN_DUPLICATE_OUTPUT: + case CLOSE_OUTPUT: + case SUSPEND_OUTPUT: + case RESTORE_OUTPUT: + case OPEN_INPUT: + case CLOSE_INPUT: + case INVALIDATE_STREAM: + case SET_VOICE_VOLUME: + case MOVE_EFFECTS: + case LOAD_HW_MODULE: + case LIST_AUDIO_PORTS: + case GET_AUDIO_PORT: + case CREATE_AUDIO_PATCH: + case RELEASE_AUDIO_PATCH: + case LIST_AUDIO_PATCHES: + case SET_AUDIO_PORT_CONFIG: + case SET_RECORD_SILENCED: + ALOGW("%s: transaction %d received from PID %d", + __func__, code, IPCThreadState::self()->getCallingPid()); + // return status only for non void methods + switch (code) { + case SET_RECORD_SILENCED: + break; + default: + reply->writeInt32(static_cast<int32_t> (INVALID_OPERATION)); + break; + } + return OK; + default: + break; + } + + // make sure the following transactions come from system components + switch (code) { + case SET_MASTER_VOLUME: + case SET_MASTER_MUTE: + case SET_MODE: + case SET_MIC_MUTE: + case SET_LOW_RAM_DEVICE: + case SYSTEM_READY: { + if (multiuser_get_app_id(IPCThreadState::self()->getCallingUid()) >= AID_APP_START) { + ALOGW("%s: transaction %d received from PID %d unauthorized UID %d", + __func__, code, IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + // return status only for non void methods + switch (code) { + case SYSTEM_READY: + break; + default: + reply->writeInt32(static_cast<int32_t> (INVALID_OPERATION)); + break; + } + return OK; + } + } break; + default: + break; + } + // Whitelist of relevant events to 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 switch (code) { case CREATE_TRACK: - case OPEN_RECORD: + case CREATE_RECORD: case SET_MASTER_VOLUME: case SET_MASTER_MUTE: - case SET_STREAM_VOLUME: - case SET_STREAM_MUTE: case SET_MIC_MUTE: case SET_PARAMETERS: - case OPEN_INPUT: - case SET_VOICE_VOLUME: case CREATE_EFFECT: case SYSTEM_READY: { requestLogMerge(); @@ -967,77 +950,60 @@ default: break; } + + char timeCheckString[64]; + snprintf(timeCheckString, sizeof(timeCheckString), "IAudioFlinger: %d", code); + TimeCheck check(timeCheckString); + switch (code) { case CREATE_TRACK: { CHECK_INTERFACE(IAudioFlinger, data, reply); - int streamType = data.readInt32(); - uint32_t sampleRate = data.readInt32(); - audio_format_t format = (audio_format_t) data.readInt32(); - audio_channel_mask_t channelMask = data.readInt32(); - size_t frameCount = data.readInt64(); - audio_output_flags_t flags = (audio_output_flags_t) data.readInt32(); - bool haveSharedBuffer = data.readInt32() != 0; - sp<IMemory> buffer; - if (haveSharedBuffer) { - buffer = interface_cast<IMemory>(data.readStrongBinder()); + + CreateTrackInput input; + if (input.readFromParcel((Parcel*)&data) != NO_ERROR) { + reply->writeInt32(DEAD_OBJECT); + return NO_ERROR; } - audio_io_handle_t output = (audio_io_handle_t) data.readInt32(); - pid_t pid = (pid_t) data.readInt32(); - pid_t tid = (pid_t) data.readInt32(); - audio_session_t sessionId = (audio_session_t) data.readInt32(); - int clientUid = data.readInt32(); - audio_port_handle_t portId = (audio_port_handle_t) data.readInt32(); - status_t status = NO_ERROR; - sp<IAudioTrack> track; - if ((haveSharedBuffer && (buffer == 0)) || - ((buffer != 0) && (buffer->pointer() == NULL))) { - ALOGW("CREATE_TRACK: cannot retrieve shared memory"); - status = DEAD_OBJECT; - } else { - track = createTrack( - (audio_stream_type_t) streamType, sampleRate, format, - channelMask, &frameCount, &flags, buffer, output, pid, tid, - &sessionId, clientUid, &status, portId); - LOG_ALWAYS_FATAL_IF((track != 0) != (status == NO_ERROR)); - } - reply->writeInt64(frameCount); - reply->writeInt32(flags); - reply->writeInt32(sessionId); + + status_t status; + CreateTrackOutput output; + + sp<IAudioTrack> track= createTrack(input, + output, + &status); + + LOG_ALWAYS_FATAL_IF((track != 0) != (status == NO_ERROR)); reply->writeInt32(status); + if (status != NO_ERROR) { + return NO_ERROR; + } reply->writeStrongBinder(IInterface::asBinder(track)); + output.writeToParcel(reply); return NO_ERROR; } break; - case OPEN_RECORD: { + case CREATE_RECORD: { CHECK_INTERFACE(IAudioFlinger, data, reply); - audio_io_handle_t input = (audio_io_handle_t) data.readInt32(); - uint32_t sampleRate = data.readInt32(); - audio_format_t format = (audio_format_t) data.readInt32(); - audio_channel_mask_t channelMask = data.readInt32(); - const String16& opPackageName = data.readString16(); - size_t frameCount = data.readInt64(); - audio_input_flags_t flags = (audio_input_flags_t) data.readInt32(); - pid_t pid = (pid_t) data.readInt32(); - pid_t tid = (pid_t) data.readInt32(); - int clientUid = data.readInt32(); - audio_session_t sessionId = (audio_session_t) data.readInt32(); - size_t notificationFrames = data.readInt64(); - audio_port_handle_t portId = (audio_port_handle_t) data.readInt32(); - sp<IMemory> cblk; - sp<IMemory> buffers; - status_t status = NO_ERROR; - sp<IAudioRecord> record = openRecord(input, - sampleRate, format, channelMask, opPackageName, &frameCount, &flags, - pid, tid, clientUid, &sessionId, ¬ificationFrames, cblk, buffers, - &status, portId); + + CreateRecordInput input; + if (input.readFromParcel((Parcel*)&data) != NO_ERROR) { + reply->writeInt32(DEAD_OBJECT); + return NO_ERROR; + } + + status_t status; + CreateRecordOutput output; + + sp<media::IAudioRecord> record = createRecord(input, + output, + &status); + LOG_ALWAYS_FATAL_IF((record != 0) != (status == NO_ERROR)); - reply->writeInt64(frameCount); - reply->writeInt32(flags); - reply->writeInt32(sessionId); - reply->writeInt64(notificationFrames); reply->writeInt32(status); + if (status != NO_ERROR) { + return NO_ERROR; + } reply->writeStrongBinder(IInterface::asBinder(record)); - reply->writeStrongBinder(IInterface::asBinder(cblk)); - reply->writeStrongBinder(IInterface::asBinder(buffers)); + output.writeToParcel(reply); return NO_ERROR; } break; case SAMPLE_RATE: { @@ -1127,6 +1093,15 @@ reply->writeInt32( getMicMute() ); return NO_ERROR; } break; + case SET_RECORD_SILENCED: { + CHECK_INTERFACE(IAudioFlinger, data, reply); + uid_t uid = data.readInt32(); + audio_source_t source; + data.read(&source, sizeof(audio_source_t)); + bool silenced = data.readInt32() == 1; + setRecordSilenced(uid, silenced); + return NO_ERROR; + } break; case SET_PARAMETERS: { CHECK_INTERFACE(IAudioFlinger, data, reply); audio_io_handle_t ioHandle = (audio_io_handle_t) data.readInt32(); @@ -1364,8 +1339,13 @@ } break; case SET_LOW_RAM_DEVICE: { CHECK_INTERFACE(IAudioFlinger, data, reply); - bool isLowRamDevice = data.readInt32() != 0; - reply->writeInt32(setLowRamDevice(isLowRamDevice)); + int32_t isLowRamDevice; + int64_t totalMemory; + const status_t status = + data.readInt32(&isLowRamDevice) ?: + data.readInt64(&totalMemory) ?: + setLowRamDevice(isLowRamDevice != 0, totalMemory); + (void)reply->writeInt32(status); return NO_ERROR; } break; case LIST_AUDIO_PORTS: { @@ -1481,6 +1461,16 @@ reply->writeInt64( frameCountHAL((audio_io_handle_t) data.readInt32()) ); return NO_ERROR; } break; + case GET_MICROPHONES: { + CHECK_INTERFACE(IAudioFlinger, data, reply); + std::vector<media::MicrophoneInfo> microphones; + status_t status = getMicrophones(µphones); + reply->writeInt32(status); + if (status == NO_ERROR) { + reply->writeParcelableVector(microphones); + } + return NO_ERROR; + } default: return BBinder::onTransact(code, data, reply, flags); }
diff --git a/media/libaudioclient/IAudioPolicyService.cpp b/media/libaudioclient/IAudioPolicyService.cpp index f071a02..8cd4a85 100644 --- a/media/libaudioclient/IAudioPolicyService.cpp +++ b/media/libaudioclient/IAudioPolicyService.cpp
@@ -22,11 +22,13 @@ #include <math.h> #include <sys/types.h> +#include <binder/IPCThreadState.h> #include <binder/Parcel.h> - +#include <cutils/multiuser.h> #include <media/AudioEffect.h> #include <media/IAudioPolicyService.h> - +#include <media/TimeCheck.h> +#include <private/android_filesystem_config.h> #include <system/audio.h> namespace android { @@ -78,7 +80,9 @@ SET_AUDIO_PORT_CALLBACK_ENABLED, SET_MASTER_MONO, GET_MASTER_MONO, - GET_STREAM_VOLUME_DB + GET_STREAM_VOLUME_DB, + GET_SURROUND_FORMATS, + SET_SURROUND_FORMAT_ENABLED }; #define MAX_ITEMS_PER_LIST 1024 @@ -160,28 +164,11 @@ return static_cast <audio_policy_forced_cfg_t> (reply.readInt32()); } - virtual audio_io_handle_t getOutput( - audio_stream_type_t stream, - uint32_t samplingRate, - audio_format_t format, - audio_channel_mask_t channelMask, - audio_output_flags_t flags, - const audio_offload_info_t *offloadInfo) + virtual audio_io_handle_t getOutput(audio_stream_type_t stream) { Parcel data, reply; data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); data.writeInt32(static_cast <uint32_t>(stream)); - data.writeInt32(samplingRate); - data.writeInt32(static_cast <uint32_t>(format)); - data.writeInt32(channelMask); - data.writeInt32(static_cast <uint32_t>(flags)); - // hasOffloadInfo - if (offloadInfo == NULL) { - data.writeInt32(0); - } else { - data.writeInt32(1); - data.write(offloadInfo, sizeof(audio_offload_info_t)); - } remote()->transact(GET_OUTPUT, data, &reply); return static_cast <audio_io_handle_t> (reply.readInt32()); } @@ -190,6 +177,7 @@ audio_io_handle_t *output, audio_session_t session, audio_stream_type_t *stream, + pid_t pid, uid_t uid, const audio_config_t *config, audio_output_flags_t flags, @@ -233,6 +221,7 @@ data.writeInt32(1); data.writeInt32(*stream); } + data.writeInt32(pid); data.writeInt32(uid); data.write(config, sizeof(audio_config_t)); data.writeInt32(static_cast <uint32_t>(flags)); @@ -299,6 +288,7 @@ audio_session_t session, pid_t pid, uid_t uid, + const String16& opPackageName, const audio_config_base_t *config, audio_input_flags_t flags, audio_port_handle_t *selectedDeviceId, @@ -327,6 +317,7 @@ data.writeInt32(session); data.writeInt32(pid); data.writeInt32(uid); + data.writeString16(opPackageName); data.write(config, sizeof(audio_config_base_t)); data.writeInt32(flags); data.writeInt32(*selectedDeviceId); @@ -345,35 +336,33 @@ return NO_ERROR; } - virtual status_t startInput(audio_io_handle_t input, - audio_session_t session) + virtual status_t startInput(audio_port_handle_t portId, + bool *silenced) { Parcel data, reply; data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); - data.writeInt32(input); - data.writeInt32(session); + data.writeInt32(portId); + data.writeInt32(*silenced ? 1 : 0); remote()->transact(START_INPUT, data, &reply); - return static_cast <status_t> (reply.readInt32()); + status_t status = static_cast <status_t> (reply.readInt32()); + *silenced = reply.readInt32() == 1; + return status; } - virtual status_t stopInput(audio_io_handle_t input, - audio_session_t session) + virtual status_t stopInput(audio_port_handle_t portId) { Parcel data, reply; data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); - data.writeInt32(input); - data.writeInt32(session); + data.writeInt32(portId); remote()->transact(STOP_INPUT, data, &reply); return static_cast <status_t> (reply.readInt32()); } - virtual void releaseInput(audio_io_handle_t input, - audio_session_t session) + virtual void releaseInput(audio_port_handle_t portId) { Parcel data, reply; data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); - data.writeInt32(input); - data.writeInt32(session); + data.writeInt32(portId); remote()->transact(RELEASE_INPUT, data, &reply); } @@ -842,16 +831,127 @@ } return reply.readFloat(); } + + virtual status_t getSurroundFormats(unsigned int *numSurroundFormats, + audio_format_t *surroundFormats, + bool *surroundFormatsEnabled, + bool reported) + { + if (numSurroundFormats == NULL || (*numSurroundFormats != 0 && + (surroundFormats == NULL || surroundFormatsEnabled == NULL))) { + return BAD_VALUE; + } + Parcel data, reply; + data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); + unsigned int numSurroundFormatsReq = *numSurroundFormats; + data.writeUint32(numSurroundFormatsReq); + data.writeBool(reported); + status_t status = remote()->transact(GET_SURROUND_FORMATS, data, &reply); + if (status == NO_ERROR && (status = (status_t)reply.readInt32()) == NO_ERROR) { + *numSurroundFormats = reply.readUint32(); + } + if (status == NO_ERROR) { + if (numSurroundFormatsReq > *numSurroundFormats) { + numSurroundFormatsReq = *numSurroundFormats; + } + if (numSurroundFormatsReq > 0) { + status = reply.read(surroundFormats, + numSurroundFormatsReq * sizeof(audio_format_t)); + if (status != NO_ERROR) { + return status; + } + status = reply.read(surroundFormatsEnabled, + numSurroundFormatsReq * sizeof(bool)); + } + } + return status; + } + + virtual status_t setSurroundFormatEnabled(audio_format_t audioFormat, bool enabled) + { + Parcel data, reply; + data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); + data.writeInt32(audioFormat); + data.writeBool(enabled); + status_t status = remote()->transact(SET_SURROUND_FORMAT_ENABLED, data, &reply); + if (status != NO_ERROR) { + return status; + } + return reply.readInt32(); + } }; IMPLEMENT_META_INTERFACE(AudioPolicyService, "android.media.IAudioPolicyService"); // ---------------------------------------------------------------------- - status_t BnAudioPolicyService::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { + // make sure transactions reserved to AudioFlinger do not come from other processes + switch (code) { + case START_OUTPUT: + case STOP_OUTPUT: + case RELEASE_OUTPUT: + case GET_INPUT_FOR_ATTR: + case START_INPUT: + case STOP_INPUT: + case RELEASE_INPUT: + case GET_STRATEGY_FOR_STREAM: + case GET_OUTPUT_FOR_EFFECT: + case REGISTER_EFFECT: + case UNREGISTER_EFFECT: + case SET_EFFECT_ENABLED: + case GET_OUTPUT_FOR_ATTR: + case ACQUIRE_SOUNDTRIGGER_SESSION: + case RELEASE_SOUNDTRIGGER_SESSION: + ALOGW("%s: transaction %d received from PID %d", + __func__, code, IPCThreadState::self()->getCallingPid()); + // return status only for non void methods + switch (code) { + case RELEASE_OUTPUT: + case RELEASE_INPUT: + break; + default: + reply->writeInt32(static_cast<int32_t> (INVALID_OPERATION)); + break; + } + return OK; + default: + break; + } + + // make sure the following transactions come from system components + switch (code) { + case SET_DEVICE_CONNECTION_STATE: + case HANDLE_DEVICE_CONFIG_CHANGE: + case SET_PHONE_STATE: +//FIXME: Allow SET_FORCE_USE calls from system apps until a better use case routing API is available +// case SET_FORCE_USE: + case INIT_STREAM_VOLUME: + case SET_STREAM_VOLUME: + case REGISTER_POLICY_MIXES: + case SET_MASTER_MONO: + case START_AUDIO_SOURCE: + case STOP_AUDIO_SOURCE: + case GET_SURROUND_FORMATS: + case SET_SURROUND_FORMAT_ENABLED: { + if (multiuser_get_app_id(IPCThreadState::self()->getCallingUid()) >= AID_APP_START) { + ALOGW("%s: transaction %d received from PID %d unauthorized UID %d", + __func__, code, IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + reply->writeInt32(static_cast<int32_t> (INVALID_OPERATION)); + return OK; + } + } break; + default: + break; + } + + char timeCheckString[64]; + snprintf(timeCheckString, sizeof(timeCheckString), "IAudioPolicyService: %d", code); + TimeCheck check(timeCheckString); + switch (code) { case SET_DEVICE_CONNECTION_STATE: { CHECK_INTERFACE(IAudioPolicyService, data, reply); @@ -934,22 +1034,7 @@ CHECK_INTERFACE(IAudioPolicyService, data, reply); audio_stream_type_t stream = static_cast <audio_stream_type_t>(data.readInt32()); - uint32_t samplingRate = data.readInt32(); - audio_format_t format = (audio_format_t) data.readInt32(); - audio_channel_mask_t channelMask = data.readInt32(); - audio_output_flags_t flags = - static_cast <audio_output_flags_t>(data.readInt32()); - bool hasOffloadInfo = data.readInt32() != 0; - audio_offload_info_t offloadInfo = {}; - if (hasOffloadInfo) { - data.read(&offloadInfo, sizeof(audio_offload_info_t)); - } - audio_io_handle_t output = getOutput(stream, - samplingRate, - format, - channelMask, - flags, - hasOffloadInfo ? &offloadInfo : NULL); + audio_io_handle_t output = getOutput(stream); reply->writeInt32(static_cast <int>(output)); return NO_ERROR; } break; @@ -968,6 +1053,7 @@ if (hasStream) { stream = (audio_stream_type_t)data.readInt32(); } + pid_t pid = (pid_t)data.readInt32(); uid_t uid = (uid_t)data.readInt32(); audio_config_t config; memset(&config, 0, sizeof(audio_config_t)); @@ -978,7 +1064,7 @@ audio_port_handle_t portId = (audio_port_handle_t)data.readInt32(); audio_io_handle_t output = 0; status_t status = getOutputForAttr(hasAttributes ? &attr : NULL, - &output, session, &stream, uid, + &output, session, &stream, pid, uid, &config, flags, &selectedDeviceId, &portId); reply->writeInt32(status); @@ -1031,6 +1117,7 @@ audio_session_t session = (audio_session_t)data.readInt32(); pid_t pid = (pid_t)data.readInt32(); uid_t uid = (uid_t)data.readInt32(); + const String16 opPackageName = data.readString16(); audio_config_base_t config; memset(&config, 0, sizeof(audio_config_base_t)); data.read(&config, sizeof(audio_config_base_t)); @@ -1038,7 +1125,7 @@ audio_port_handle_t selectedDeviceId = (audio_port_handle_t) data.readInt32(); audio_port_handle_t portId = (audio_port_handle_t)data.readInt32(); status_t status = getInputForAttr(&attr, &input, session, pid, uid, - &config, + opPackageName, &config, flags, &selectedDeviceId, &portId); reply->writeInt32(status); if (status == NO_ERROR) { @@ -1051,25 +1138,25 @@ case START_INPUT: { CHECK_INTERFACE(IAudioPolicyService, data, reply); - audio_io_handle_t input = static_cast <audio_io_handle_t>(data.readInt32()); - audio_session_t session = static_cast <audio_session_t>(data.readInt32()); - reply->writeInt32(static_cast <uint32_t>(startInput(input, session))); + audio_port_handle_t portId = static_cast <audio_port_handle_t>(data.readInt32()); + bool silenced = data.readInt32() == 1; + status_t status = startInput(portId, &silenced); + reply->writeInt32(static_cast <uint32_t>(status)); + reply->writeInt32(silenced ? 1 : 0); return NO_ERROR; } break; case STOP_INPUT: { CHECK_INTERFACE(IAudioPolicyService, data, reply); - audio_io_handle_t input = static_cast <audio_io_handle_t>(data.readInt32()); - audio_session_t session = static_cast <audio_session_t>(data.readInt32()); - reply->writeInt32(static_cast <uint32_t>(stopInput(input, session))); + audio_port_handle_t portId = static_cast <audio_port_handle_t>(data.readInt32()); + reply->writeInt32(static_cast <uint32_t>(stopInput(portId))); return NO_ERROR; } break; case RELEASE_INPUT: { CHECK_INTERFACE(IAudioPolicyService, data, reply); - audio_io_handle_t input = static_cast <audio_io_handle_t>(data.readInt32()); - audio_session_t session = static_cast <audio_session_t>(data.readInt32()); - releaseInput(input, session); + audio_port_handle_t portId = static_cast <audio_port_handle_t>(data.readInt32()); + releaseInput(portId); return NO_ERROR; } break; @@ -1456,6 +1543,50 @@ return NO_ERROR; } + case GET_SURROUND_FORMATS: { + CHECK_INTERFACE(IAudioPolicyService, data, reply); + unsigned int numSurroundFormatsReq = data.readUint32(); + if (numSurroundFormatsReq > MAX_ITEMS_PER_LIST) { + numSurroundFormatsReq = MAX_ITEMS_PER_LIST; + } + bool reported = data.readBool(); + unsigned int numSurroundFormats = numSurroundFormatsReq; + audio_format_t *surroundFormats = (audio_format_t *)calloc( + numSurroundFormats, sizeof(audio_format_t)); + bool *surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool)); + if (numSurroundFormatsReq > 0 && + (surroundFormats == NULL || surroundFormatsEnabled == NULL)) { + free(surroundFormats); + free(surroundFormatsEnabled); + reply->writeInt32(NO_MEMORY); + return NO_ERROR; + } + status_t status = getSurroundFormats( + &numSurroundFormats, surroundFormats, surroundFormatsEnabled, reported); + reply->writeInt32(status); + + if (status == NO_ERROR) { + reply->writeUint32(numSurroundFormats); + if (numSurroundFormatsReq > numSurroundFormats) { + numSurroundFormatsReq = numSurroundFormats; + } + reply->write(surroundFormats, numSurroundFormatsReq * sizeof(audio_format_t)); + reply->write(surroundFormatsEnabled, numSurroundFormatsReq * sizeof(bool)); + } + free(surroundFormats); + free(surroundFormatsEnabled); + return NO_ERROR; + } + + case SET_SURROUND_FORMAT_ENABLED: { + CHECK_INTERFACE(IAudioPolicyService, data, reply); + audio_format_t audioFormat = (audio_format_t) data.readInt32(); + bool enabled = data.readBool(); + status_t status = setSurroundFormatEnabled(audioFormat, enabled); + reply->writeInt32(status); + return NO_ERROR; + } + default: return BBinder::onTransact(code, data, reply, flags); }
diff --git a/media/libaudioclient/IAudioRecord.cpp b/media/libaudioclient/IAudioRecord.cpp deleted file mode 100644 index 1331c0d..0000000 --- a/media/libaudioclient/IAudioRecord.cpp +++ /dev/null
@@ -1,94 +0,0 @@ -/* -** -** Copyright 2007, 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 "IAudioRecord" -//#define LOG_NDEBUG 0 -#include <utils/Log.h> - -#include <stdint.h> -#include <sys/types.h> - -#include <binder/Parcel.h> - -#include <media/IAudioRecord.h> - -namespace android { - -enum { - UNUSED_WAS_GET_CBLK = IBinder::FIRST_CALL_TRANSACTION, - START, - STOP -}; - -class BpAudioRecord : public BpInterface<IAudioRecord> -{ -public: - explicit BpAudioRecord(const sp<IBinder>& impl) - : BpInterface<IAudioRecord>(impl) - { - } - - virtual status_t start(int /*AudioSystem::sync_event_t*/ event, audio_session_t triggerSession) - { - Parcel data, reply; - data.writeInterfaceToken(IAudioRecord::getInterfaceDescriptor()); - data.writeInt32(event); - data.writeInt32(triggerSession); - status_t status = remote()->transact(START, data, &reply); - if (status == NO_ERROR) { - status = reply.readInt32(); - } else { - ALOGW("start() error: %s", strerror(-status)); - } - return status; - } - - virtual void stop() - { - Parcel data, reply; - data.writeInterfaceToken(IAudioRecord::getInterfaceDescriptor()); - remote()->transact(STOP, data, &reply); - } - -}; - -IMPLEMENT_META_INTERFACE(AudioRecord, "android.media.IAudioRecord"); - -// ---------------------------------------------------------------------- - -status_t BnAudioRecord::onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) -{ - switch (code) { - case START: { - CHECK_INTERFACE(IAudioRecord, data, reply); - int /*AudioSystem::sync_event_t*/ event = data.readInt32(); - audio_session_t triggerSession = (audio_session_t) data.readInt32(); - reply->writeInt32(start(event, triggerSession)); - return NO_ERROR; - } break; - case STOP: { - CHECK_INTERFACE(IAudioRecord, data, reply); - stop(); - return NO_ERROR; - } break; - default: - return BBinder::onTransact(code, data, reply, flags); - } -} - -} // namespace android
diff --git a/media/libaudioclient/IAudioTrack.cpp b/media/libaudioclient/IAudioTrack.cpp index 79e864d..adff057 100644 --- a/media/libaudioclient/IAudioTrack.cpp +++ b/media/libaudioclient/IAudioTrack.cpp
@@ -28,6 +28,8 @@ namespace android { +using media::VolumeShaper; + enum { GET_CBLK = IBinder::FIRST_CALL_TRANSACTION, START, @@ -185,7 +187,7 @@ return nullptr; } sp<VolumeShaper::State> state = new VolumeShaper::State; - status = state->readFromParcel(reply); + status = state->readFromParcel(&reply); if (status != NO_ERROR) { return nullptr; } @@ -263,12 +265,12 @@ status_t status = data.readInt32(&present); if (status == NO_ERROR && present != 0) { configuration = new VolumeShaper::Configuration(); - status = configuration->readFromParcel(data); + status = configuration->readFromParcel(&data); } status = status ?: data.readInt32(&present); if (status == NO_ERROR && present != 0) { operation = new VolumeShaper::Operation(); - status = operation->readFromParcel(data); + status = operation->readFromParcel(&data); } if (status == NO_ERROR) { status = (status_t)applyVolumeShaper(configuration, operation);
diff --git a/media/libaudioclient/PlayerBase.cpp b/media/libaudioclient/PlayerBase.cpp index 7868318..b0c68e5 100644 --- a/media/libaudioclient/PlayerBase.cpp +++ b/media/libaudioclient/PlayerBase.cpp
@@ -22,6 +22,8 @@ namespace android { +using media::VolumeShaper; + //-------------------------------------------------------------------------------------------------- PlayerBase::PlayerBase() : BnPlayer(), mPanMultiplierL(1.0f), mPanMultiplierR(1.0f), @@ -117,23 +119,26 @@ //------------------------------------------------------------------------------ // Implementation of IPlayer -void PlayerBase::start() { +binder::Status PlayerBase::start() { ALOGD("PlayerBase::start() from IPlayer"); (void)startWithStatus(); + return binder::Status::ok(); } -void PlayerBase::pause() { +binder::Status PlayerBase::pause() { ALOGD("PlayerBase::pause() from IPlayer"); (void)pauseWithStatus(); + return binder::Status::ok(); } -void PlayerBase::stop() { +binder::Status PlayerBase::stop() { ALOGD("PlayerBase::stop() from IPlayer"); (void)stopWithStatus(); + return binder::Status::ok(); } -void PlayerBase::setVolume(float vol) { +binder::Status PlayerBase::setVolume(float vol) { ALOGD("PlayerBase::setVolume() from IPlayer"); { Mutex::Autolock _l(mSettingsLock); @@ -144,9 +149,10 @@ if (status != NO_ERROR) { ALOGW("PlayerBase::setVolume() error %d", status); } + return binder::Status::fromStatusT(status); } -void PlayerBase::setPan(float pan) { +binder::Status PlayerBase::setPan(float pan) { ALOGD("PlayerBase::setPan() from IPlayer"); { Mutex::Autolock _l(mSettingsLock); @@ -163,22 +169,19 @@ if (status != NO_ERROR) { ALOGW("PlayerBase::setPan() error %d", status); } + return binder::Status::fromStatusT(status); } -void PlayerBase::setStartDelayMs(int32_t delayMs __unused) { +binder::Status PlayerBase::setStartDelayMs(int32_t delayMs __unused) { ALOGW("setStartDelay() is not supported"); + return binder::Status::ok(); } -void PlayerBase::applyVolumeShaper( - const sp<VolumeShaper::Configuration>& configuration __unused, - const sp<VolumeShaper::Operation>& operation __unused) { +binder::Status PlayerBase::applyVolumeShaper( + const VolumeShaper::Configuration& configuration __unused, + const VolumeShaper::Operation& operation __unused) { ALOGW("applyVolumeShaper() is not supported"); -} - -status_t PlayerBase::onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) -{ - return BnPlayer::onTransact(code, data, reply, flags); + return binder::Status::ok(); } } // namespace android
diff --git a/media/libaudioclient/ToneGenerator.cpp b/media/libaudioclient/ToneGenerator.cpp index 9bc2594..d846d79 100644 --- a/media/libaudioclient/ToneGenerator.cpp +++ b/media/libaudioclient/ToneGenerator.cpp
@@ -20,6 +20,7 @@ #include <math.h> #include <utils/Log.h> #include <cutils/properties.h> +#include <media/AudioPolicyHelper.h> #include "media/ToneGenerator.h" @@ -740,6 +741,18 @@ { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, .repeatCnt = ToneGenerator::TONEGEN_INF, .repeatSegment = 0 }, // TONE_JAPAN_RADIO_ACK + { .segments = { { .duration = 375, .waveFreq = { 400, 0 }, 0, 0 }, + { .duration = 375, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_GB_BUSY + { .segments = { { .duration = 400, .waveFreq = { 400, 0 }, 0, 0 }, + { .duration = 350, .waveFreq = { 0 }, 0, 0 }, + { .duration = 225, .waveFreq = { 400, 0 }, 0, 0 }, + { .duration = 525, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_GB_CONGESTION { .segments = { { .duration = 400, .waveFreq = { 400, 450, 0 }, 0, 0 }, { .duration = 200, .waveFreq = { 0 }, 0, 0 }, { .duration = 400, .waveFreq = { 400, 450, 0 }, 0, 0 }, @@ -747,6 +760,10 @@ { .duration = 0, .waveFreq = { 0 }, 0, 0}}, .repeatCnt = ToneGenerator::TONEGEN_INF, .repeatSegment = 0 }, // TONE_GB_RINGTONE + { .segments = { { .duration = ToneGenerator::TONEGEN_INF, .waveFreq = { 400, 425, 450, 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_AUSTRALIA_DIAL { .segments = { { .duration = 400, .waveFreq = { 400, 450, 0 }, 0, 0 }, { .duration = 200, .waveFreq = { 0 }, 0, 0 }, { .duration = 400, .waveFreq = { 400, 450, 0 }, 0, 0 }, @@ -771,6 +788,72 @@ { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, .repeatCnt = ToneGenerator::TONEGEN_INF, .repeatSegment = 0 }, // TONE_AUSTRALIA_CONGESTION + { .segments = { { .duration = 750, .waveFreq = { 425, 0 }, 0, 0 }, + { .duration = 750, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_SG_BUSY + { .segments = { { .duration = 400, .waveFreq = { 401, 425, 449, 0 }, 0, 0 }, + { .duration = 200, .waveFreq = { 0 }, 0, 0 }, + { .duration = 400, .waveFreq = { 401, 425, 449, 0 }, 0, 0 }, + { .duration = 2000, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_SG_RINGTONE + { .segments = { { .duration = 500, .waveFreq = { 480, 620, 0 }, 0, 0 }, + { .duration = 500, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_HK_BUSY + { .segments = { { .duration = 400, .waveFreq = { 440, 480, 0 }, 0, 0 }, + { .duration = 200, .waveFreq = { 0 }, 0, 0 }, + { .duration = 400, .waveFreq = { 440, 480, 0 }, 0, 0 }, + { .duration = 3000, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_HK_RINGTONE + { .segments = { { .duration = 400, .waveFreq = { 400, 450, 0 }, 0, 0 }, + { .duration = 200, .waveFreq = { 0 }, 0, 0 }, + { .duration = 400, .waveFreq = { 400, 450, 0 }, 0, 0 }, + { .duration = 2000, .waveFreq = { 0 }, 0, 0}, + { .duration = 0, .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_IE_RINGTONE + { .segments = { { .duration = 180, .waveFreq = { 425, 0 }, 0, 0 }, + { .duration = 200, .waveFreq = { 0 }, 0, 0 }, + { .duration = 200, .waveFreq = { 425, 0 }, 0, 0 }, + { .duration = 4500, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_IE_CALL_WAITING + { .segments = { { .duration = ToneGenerator::TONEGEN_INF, .waveFreq = { 375, 400, 425, 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_INDIA_DIAL + { .segments = { { .duration = 750, .waveFreq = { 400, 0 }, 0, 0 }, + { .duration = 750, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_INDIA_BUSY + { .segments = { { .duration = 250, .waveFreq = { 400, 0 }, 0, 0 }, + { .duration = 250, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_INDIA_CONGESTION + { .segments = { { .duration = 200, .waveFreq = { 400, 0 }, 0, 0 }, + { .duration = 100, .waveFreq = { 0 }, 0, 0 }, + { .duration = 200, .waveFreq = { 400, 0 }, 0, 0 }, + { .duration = 7500, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_INDIA_CALL_WAITING + { .segments = { { .duration = 400, .waveFreq = { 375, 400, 425, 0 }, 0, 0 }, + { .duration = 200, .waveFreq = { 0 }, 0, 0 }, + { .duration = 400, .waveFreq = { 375, 400, 425, 0 }, 0, 0 }, + { .duration = 2000, .waveFreq = { 0 }, 0, 0 }, + { .duration = 0 , .waveFreq = { 0 }, 0, 0}}, + .repeatCnt = ToneGenerator::TONEGEN_INF, + .repeatSegment = 0 }, // TONE_INDIA_RINGTONE }; // Used by ToneGenerator::getToneForRegion() to convert user specified supervisory tone type @@ -797,9 +880,9 @@ TONE_SUP_RINGTONE // TONE_SUP_RINGTONE }, { // GB - TONE_SUP_DIAL, // TONE_SUP_DIAL - TONE_SUP_BUSY, // TONE_SUP_BUSY - TONE_SUP_CONGESTION, // TONE_SUP_CONGESTION + TONE_ANSI_DIAL, // TONE_SUP_DIAL + TONE_GB_BUSY, // TONE_SUP_BUSY + TONE_GB_CONGESTION, // TONE_SUP_CONGESTION TONE_SUP_RADIO_ACK, // TONE_SUP_RADIO_ACK TONE_SUP_RADIO_NOTAVAIL, // TONE_SUP_RADIO_NOTAVAIL TONE_SUP_ERROR, // TONE_SUP_ERROR @@ -807,7 +890,7 @@ TONE_GB_RINGTONE // TONE_SUP_RINGTONE }, { // AUSTRALIA - TONE_ANSI_DIAL, // TONE_SUP_DIAL + TONE_AUSTRALIA_DIAL, // TONE_SUP_DIAL TONE_AUSTRALIA_BUSY, // TONE_SUP_BUSY TONE_AUSTRALIA_CONGESTION, // TONE_SUP_CONGESTION TONE_SUP_RADIO_ACK, // TONE_SUP_RADIO_ACK @@ -815,6 +898,46 @@ TONE_SUP_ERROR, // TONE_SUP_ERROR TONE_AUSTRALIA_CALL_WAITING,// TONE_SUP_CALL_WAITING TONE_AUSTRALIA_RINGTONE // TONE_SUP_RINGTONE + }, + { // SINGAPORE + TONE_SUP_DIAL, // TONE_SUP_DIAL + TONE_SG_BUSY, // TONE_SUP_BUSY + TONE_SUP_CONGESTION, // TONE_SUP_CONGESTION + TONE_SUP_RADIO_ACK, // TONE_SUP_RADIO_ACK + TONE_SUP_RADIO_NOTAVAIL, // TONE_SUP_RADIO_NOTAVAIL + TONE_SUP_ERROR, // TONE_SUP_ERROR + TONE_SUP_CALL_WAITING, // TONE_SUP_CALL_WAITING + TONE_SG_RINGTONE // TONE_SUP_RINGTONE + }, + { // HONGKONG + TONE_SUP_DIAL, // TONE_SUP_DIAL + TONE_HK_BUSY, // TONE_SUP_BUSY + TONE_SUP_CONGESTION, // TONE_SUP_CONGESTION + TONE_SUP_RADIO_ACK, // TONE_SUP_RADIO_ACK + TONE_SUP_RADIO_NOTAVAIL, // TONE_SUP_RADIO_NOTAVAIL + TONE_SUP_ERROR, // TONE_SUP_ERROR + TONE_SUP_CALL_WAITING, // TONE_SUP_CALL_WAITING + TONE_HK_RINGTONE // TONE_SUP_RINGTONE + }, + { // IRELAND + TONE_SUP_DIAL, // TONE_SUP_DIAL + TONE_SUP_BUSY, // TONE_SUP_BUSY + TONE_SUP_CONGESTION, // TONE_SUP_CONGESTION + TONE_SUP_RADIO_ACK, // TONE_SUP_RADIO_ACK + TONE_SUP_RADIO_NOTAVAIL, // TONE_SUP_RADIO_NOTAVAIL + TONE_SUP_ERROR, // TONE_SUP_ERROR + TONE_IE_CALL_WAITING, // TONE_SUP_CALL_WAITING + TONE_IE_RINGTONE // TONE_SUP_RINGTONE + }, + { // INDIA + TONE_INDIA_DIAL, // TONE_SUP_DIAL + TONE_INDIA_BUSY, // TONE_SUP_BUSY + TONE_INDIA_CONGESTION, // TONE_SUP_CONGESTION + TONE_SUP_RADIO_ACK, // TONE_SUP_RADIO_ACK + TONE_SUP_RADIO_NOTAVAIL, // TONE_SUP_RADIO_NOTAVAIL + TONE_SUP_ERROR, // TONE_SUP_ERROR + TONE_INDIA_CALL_WAITING, // TONE_SUP_CALL_WAITING + TONE_INDIA_RINGTONE // TONE_SUP_RINGTONE } }; @@ -864,6 +987,13 @@ if (property_get("gsm.operator.iso-country", value, "") == 0) { property_get("gsm.sim.operator.iso-country", value, ""); } + // If dual sim device has two SIM cards inserted and is not registerd to any network, + // "," is set to "gsm.operator.iso-country" prop. + // In this case, "gsm.sim.operator.iso-country" prop should be used. + if (strlen(value) == 1 && strstr(value, ",") != NULL) { + property_get("gsm.sim.operator.iso-country", value, ""); + } + if (strstr(value, "us") != NULL || strstr(value, "ca") != NULL) { mRegion = ANSI; @@ -873,6 +1003,14 @@ mRegion = GB; } else if (strstr(value, "au") != NULL) { mRegion = AUSTRALIA; + } else if (strstr(value, "sg") != NULL) { + mRegion = SINGAPORE; + } else if (strstr(value, "hk") != NULL) { + mRegion = HONGKONG; + } else if (strstr(value, "ie") != NULL) { + mRegion = IRELAND; + } else if (strstr(value, "in") != NULL) { + mRegion = INDIA; } else { mRegion = CEPT; } @@ -932,7 +1070,7 @@ bool lResult = false; status_t lStatus; - if ((toneType < 0) || (toneType >= NUM_TONES)) + if (toneType >= NUM_TONES) return lResult; toneType = getToneForRegion(toneType); @@ -947,7 +1085,7 @@ } } - ALOGV("startTone"); + ALOGV("startTone toneType %d", toneType); mLock.lock(); @@ -1099,9 +1237,16 @@ mpAudioTrack = new AudioTrack(); ALOGV("AudioTrack(%p) created", mpAudioTrack.get()); + audio_attributes_t attr; + audio_stream_type_t streamType = mStreamType; + if (mStreamType == AUDIO_STREAM_VOICE_CALL) { + streamType = AUDIO_STREAM_DTMF; + } + stream_type_to_audio_attributes(streamType, &attr); + const size_t frameCount = mProcessSize; status_t status = mpAudioTrack->set( - mStreamType, + AUDIO_STREAM_DEFAULT, 0, // sampleRate AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_OUT_MONO, @@ -1113,7 +1258,11 @@ 0, // sharedBuffer mThreadCanCallJava, AUDIO_SESSION_ALLOCATE, - AudioTrack::TRANSFER_CALLBACK); + AudioTrack::TRANSFER_CALLBACK, + nullptr, + AUDIO_UID_INVALID, + -1, + &attr); if (status != NO_ERROR) { ALOGE("AudioTrack(%p) set failed with error %d", mpAudioTrack.get(), status);
diff --git a/media/libaudioclient/TrackPlayerBase.cpp b/media/libaudioclient/TrackPlayerBase.cpp index 48cd803..0a914fc 100644 --- a/media/libaudioclient/TrackPlayerBase.cpp +++ b/media/libaudioclient/TrackPlayerBase.cpp
@@ -18,6 +18,8 @@ namespace android { +using media::VolumeShaper; + //-------------------------------------------------------------------------------------------------- TrackPlayerBase::TrackPlayerBase() : PlayerBase(), mPlayerVolumeL(1.0f), mPlayerVolumeR(1.0f) @@ -103,18 +105,24 @@ } -void TrackPlayerBase::applyVolumeShaper( - const sp<VolumeShaper::Configuration>& configuration, - const sp<VolumeShaper::Operation>& operation) { +binder::Status TrackPlayerBase::applyVolumeShaper( + const VolumeShaper::Configuration& configuration, + const VolumeShaper::Operation& operation) { + + sp<VolumeShaper::Configuration> spConfiguration = new VolumeShaper::Configuration(configuration); + sp<VolumeShaper::Operation> spOperation = new VolumeShaper::Operation(operation); + if (mAudioTrack != 0) { ALOGD("TrackPlayerBase::applyVolumeShaper() from IPlayer"); - VolumeShaper::Status status = mAudioTrack->applyVolumeShaper(configuration, operation); + VolumeShaper::Status status = mAudioTrack->applyVolumeShaper(spConfiguration, spOperation); if (status < 0) { // a non-negative value is the volume shaper id. ALOGE("TrackPlayerBase::applyVolumeShaper() failed with status %d", status); } + return binder::Status::fromStatusT(status); } else { ALOGD("TrackPlayerBase::applyVolumeShaper()" - " no AudioTrack for volume control from IPlayer"); + " no AudioTrack for volume control from IPlayer"); + return binder::Status::ok(); } }
diff --git a/media/libaudioclient/aidl/android/media/IAudioRecord.aidl b/media/libaudioclient/aidl/android/media/IAudioRecord.aidl new file mode 100644 index 0000000..01e0a71 --- /dev/null +++ b/media/libaudioclient/aidl/android/media/IAudioRecord.aidl
@@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.media.MicrophoneInfo; + +/* Native code must specify namespace media (media::IAudioRecord) when referring to this class */ +interface IAudioRecord { + + /* After it's created the track is not active. Call start() to + * make it active. + */ + void start(int /*AudioSystem::sync_event_t*/ event, + int /*audio_session_t*/ triggerSession); + + /* Stop a track. If set, the callback will cease being called and + * obtainBuffer will return an error. Buffers that are already released + * will be processed, unless flush() is called. + */ + void stop(); + + /* Get a list of current active microphones. + */ + void getActiveMicrophones(out MicrophoneInfo[] activeMicrophones); +}
diff --git a/media/libaudioclient/aidl/android/media/IPlayer.aidl b/media/libaudioclient/aidl/android/media/IPlayer.aidl new file mode 100644 index 0000000..a90fcdd --- /dev/null +++ b/media/libaudioclient/aidl/android/media/IPlayer.aidl
@@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.media.VolumeShaper.Configuration; +import android.media.VolumeShaper.Operation; + +/** + * @hide + */ +interface IPlayer { + oneway void start(); + oneway void pause(); + oneway void stop(); + oneway void setVolume(float vol); + oneway void setPan(float pan); + oneway void setStartDelayMs(int delayMs); + oneway void applyVolumeShaper(in Configuration configuration, + in Operation operation); +}
diff --git a/media/libaudioclient/aidl/android/media/MicrophoneInfo.aidl b/media/libaudioclient/aidl/android/media/MicrophoneInfo.aidl new file mode 100644 index 0000000..d6e46cb --- /dev/null +++ b/media/libaudioclient/aidl/android/media/MicrophoneInfo.aidl
@@ -0,0 +1,19 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +parcelable MicrophoneInfo cpp_header "media/MicrophoneInfo.h";
diff --git a/media/libaudioclient/aidl/android/media/VolumeShaper/Configuration.aidl b/media/libaudioclient/aidl/android/media/VolumeShaper/Configuration.aidl new file mode 100644 index 0000000..fd0e60f --- /dev/null +++ b/media/libaudioclient/aidl/android/media/VolumeShaper/Configuration.aidl
@@ -0,0 +1,19 @@ +/* + * 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. + */ + +package android.media.VolumeShaper; + +parcelable Configuration cpp_header "media/VolumeShaper.h";
diff --git a/media/libaudioclient/aidl/android/media/VolumeShaper/Operation.aidl b/media/libaudioclient/aidl/android/media/VolumeShaper/Operation.aidl new file mode 100644 index 0000000..4290d9d --- /dev/null +++ b/media/libaudioclient/aidl/android/media/VolumeShaper/Operation.aidl
@@ -0,0 +1,19 @@ +/* + * 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. + */ + +package android.media.VolumeShaper; + +parcelable Operation cpp_header "media/VolumeShaper.h";
diff --git a/media/libaudioclient/aidl/android/media/VolumeShaper/State.aidl b/media/libaudioclient/aidl/android/media/VolumeShaper/State.aidl new file mode 100644 index 0000000..f6a22b8 --- /dev/null +++ b/media/libaudioclient/aidl/android/media/VolumeShaper/State.aidl
@@ -0,0 +1,19 @@ +/* + * 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. + */ + +package android.media.VolumeShaper; + +parcelable State cpp_header "media/VolumeShaper.h";
diff --git a/media/libaudioclient/include/media/AudioClient.h b/media/libaudioclient/include/media/AudioClient.h index 9efd76d..247af9e 100644 --- a/media/libaudioclient/include/media/AudioClient.h +++ b/media/libaudioclient/include/media/AudioClient.h
@@ -18,19 +18,38 @@ #ifndef ANDROID_AUDIO_CLIENT_H #define ANDROID_AUDIO_CLIENT_H +#include <binder/Parcel.h> +#include <binder/Parcelable.h> #include <system/audio.h> #include <utils/String16.h> namespace android { -class AudioClient { +class AudioClient : public Parcelable { public: AudioClient() : - clientUid(-1), clientPid(-1), packageName("") {} + clientUid(-1), clientPid(-1), clientTid(-1), packageName("") {} uid_t clientUid; pid_t clientPid; + pid_t clientTid; String16 packageName; + + status_t readFromParcel(const Parcel *parcel) override { + clientUid = parcel->readInt32(); + clientPid = parcel->readInt32(); + clientTid = parcel->readInt32(); + packageName = parcel->readString16(); + return NO_ERROR; + } + + status_t writeToParcel(Parcel *parcel) const override { + parcel->writeInt32(clientUid); + parcel->writeInt32(clientPid); + parcel->writeInt32(clientTid); + parcel->writeString16(packageName); + return NO_ERROR; + } }; }; // namespace android
diff --git a/media/libaudioclient/include/media/AudioMixer.h b/media/libaudioclient/include/media/AudioMixer.h index 2bd2d01..cf7d90f 100644 --- a/media/libaudioclient/include/media/AudioMixer.h +++ b/media/libaudioclient/include/media/AudioMixer.h
@@ -18,14 +18,17 @@ #ifndef ANDROID_AUDIO_MIXER_H #define ANDROID_AUDIO_MIXER_H +#include <pthread.h> +#include <sstream> #include <stdint.h> #include <sys/types.h> +#include <unordered_map> #include <media/AudioBufferProvider.h> #include <media/AudioResampler.h> #include <media/AudioResamplerPublic.h> #include <media/BufferProviders.h> -#include <media/nbaio/NBLog.h> +#include <media/nblog/NBLog.h> #include <system/audio.h> #include <utils/Compat.h> #include <utils/threads.h> @@ -33,6 +36,9 @@ // FIXME This is actually unity gain, which might not be max in future, expressed in U.12 #define MAX_GAIN_INT AudioMixer::UNITY_GAIN_INT +// This must match frameworks/av/services/audioflinger/Configuration.h +#define FLOAT_AUX + namespace android { // ---------------------------------------------------------------------------- @@ -40,20 +46,10 @@ class AudioMixer { public: - AudioMixer(size_t frameCount, uint32_t sampleRate, - uint32_t maxNumTracks = MAX_NUM_TRACKS); - - /*virtual*/ ~AudioMixer(); // non-virtual saves a v-table, restore if sub-classed - - - // This mixer has a hard-coded upper limit of 32 active track inputs. - // Adding support for > 32 tracks would require more than simply changing this value. - static const uint32_t MAX_NUM_TRACKS = 32; - // maximum number of channels supported by the mixer - + // Do not change these unless underlying code changes. // This mixer has a hard-coded upper limit of 8 channels for output. - static const uint32_t MAX_NUM_CHANNELS = 8; - static const uint32_t MAX_NUM_VOLUMES = 2; // stereo volume only + static constexpr uint32_t MAX_NUM_CHANNELS = FCC_8; + static constexpr uint32_t MAX_NUM_VOLUMES = FCC_2; // stereo volume only // maximum number of channels supported for the content static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX; @@ -61,12 +57,6 @@ static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0f; enum { // names - - // track names (MAX_NUM_TRACKS units) - TRACK0 = 0x1000, - - // 0x2000 is unused - // setParameter targets TRACK = 0x3000, RESAMPLE = 0x3001, @@ -105,17 +95,33 @@ // parameter 'value' is a pointer to the new playback rate. }; + AudioMixer(size_t frameCount, uint32_t sampleRate) + : mSampleRate(sampleRate) + , mFrameCount(frameCount) { + pthread_once(&sOnceControl, &sInitRoutine); + } - // For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS + // Create a new track in the mixer. + // + // \param name a unique user-provided integer associated with the track. + // If name already exists, the function will abort. + // \param channelMask output channel mask. + // \param format PCM format + // \param sessionId Session id for the track. Tracks with the same + // session id will be submixed together. + // + // \return OK on success. + // BAD_VALUE if the format does not satisfy isValidFormat() + // or the channelMask does not satisfy isValidChannelMask(). + status_t create( + int name, audio_channel_mask_t channelMask, audio_format_t format, int sessionId); - // Allocate a track name. Returns new track name if successful, -1 on failure. - // The failure could be because of an invalid channelMask or format, or that - // the track capacity of the mixer is exceeded. - int getTrackName(audio_channel_mask_t channelMask, - audio_format_t format, int sessionId); + bool exists(int name) const { + return mTracks.count(name) > 0; + } - // Free an allocated track by name - void deleteTrackName(int name); + // Free an allocated track by name. + void destroy(int name); // Enable or disable an allocated track by name void enable(int name); @@ -124,13 +130,26 @@ void setParameter(int name, int target, int param, void *value); void setBufferProvider(int name, AudioBufferProvider* bufferProvider); - void process(); - uint32_t trackNames() const { return mTrackNames; } + void process() { + (this->*mHook)(); + } size_t getUnreleasedFrames(int name) const; - static inline bool isValidPcmTrackFormat(audio_format_t format) { + std::string trackNames() const { + std::stringstream ss; + for (const auto &pair : mTracks) { + ss << pair.first << " "; + } + return ss.str(); + } + + void setNBLogWriter(NBLog::Writer *logWriter) { + mNBLogWriter = logWriter; + } + + static inline bool isValidFormat(audio_format_t format) { switch (format) { case AUDIO_FORMAT_PCM_8_BIT: case AUDIO_FORMAT_PCM_16_BIT: @@ -143,8 +162,23 @@ } } + static inline bool isValidChannelMask(audio_channel_mask_t channelMask) { + return audio_channel_mask_is_valid(channelMask); // the RemixBufferProvider is flexible. + } + private: + /* For multi-format functions (calls template functions + * in AudioMixerOps.h). The template parameters are as follows: + * + * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * USEFLOATVOL (set to true if float volume is used) + * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards) + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) + */ + enum { // FIXME this representation permits up to 8 channels NEEDS_CHANNEL_COUNT__MASK = 0x00000007, @@ -161,14 +195,67 @@ NEEDS_AUX = 0x00010000, }; - struct state_t; - struct track_t; + // hook types + enum { + PROCESSTYPE_NORESAMPLEONETRACK, // others set elsewhere + }; - typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp, - int32_t* aux); - static const int BLOCKSIZE = 16; // 4 cache lines + enum { + TRACKTYPE_NOP, + TRACKTYPE_RESAMPLE, + TRACKTYPE_NORESAMPLE, + TRACKTYPE_NORESAMPLEMONO, + }; - struct track_t { + // process hook functionality + using process_hook_t = void(AudioMixer::*)(); + + struct Track; + using hook_t = void(Track::*)(int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux); + + struct Track { + Track() + : bufferProvider(nullptr) + { + // TODO: move additional initialization here. + } + + ~Track() + { + // bufferProvider, mInputBufferProvider need not be deleted. + mResampler.reset(nullptr); + // Ensure the order of destruction of buffer providers as they + // release the upstream provider in the destructor. + mTimestretchBufferProvider.reset(nullptr); + mPostDownmixReformatBufferProvider.reset(nullptr); + mDownmixerBufferProvider.reset(nullptr); + mReformatBufferProvider.reset(nullptr); + } + + bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; } + bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate); + bool doesResample() const { return mResampler.get() != nullptr; } + void resetResampler() { if (mResampler.get() != nullptr) mResampler->reset(); } + void adjustVolumeRamp(bool aux, bool useFloat = false); + size_t getUnreleasedFrames() const { return mResampler.get() != nullptr ? + mResampler->getUnreleasedFrames() : 0; }; + + status_t prepareForDownmix(); + void unprepareForDownmix(); + status_t prepareForReformat(); + void unprepareForReformat(); + bool setPlaybackRate(const AudioPlaybackRate &playbackRate); + void reconfigureBufferProviders(); + + static hook_t getTrackHook(int trackType, uint32_t channelCount, + audio_format_t mixerInFormat, audio_format_t mixerOutFormat); + + void track__nop(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); + + template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL, + typename TO, typename TI, typename TA> + void volumeMix(TO *out, size_t outFrames, const TI *in, TA *aux, bool ramp); + uint32_t needs; // TODO: Eventually remove legacy integer volume settings @@ -178,16 +265,11 @@ }; int32_t prevVolume[MAX_NUM_VOLUMES]; - - // 16-byte boundary - int32_t volumeInc[MAX_NUM_VOLUMES]; int32_t auxInc; int32_t prevAuxLevel; - - // 16-byte boundary - int16_t auxLevel; // 0 <= auxLevel <= MAX_GAIN_INT, but signed for mul performance + uint16_t frameCount; uint8_t channelCount; // 1 or 2, redundant with (needs & NEEDS_CHANNEL_COUNT__MASK) @@ -199,22 +281,16 @@ // for how the Track buffer provider is wrapped by another one when dowmixing is required AudioBufferProvider* bufferProvider; - // 16-byte boundary - mutable AudioBufferProvider::Buffer buffer; // 8 bytes hook_t hook; - const void* in; // current location in buffer + const void *mIn; // current location in buffer - // 16-byte boundary - - AudioResampler* resampler; + std::unique_ptr<AudioResampler> mResampler; uint32_t sampleRate; int32_t* mainBuffer; int32_t* auxBuffer; - // 16-byte boundary - /* Buffer providers are constructed to translate the track input data as needed. * * TODO: perhaps make a single PlaybackConverterProvider class to move @@ -225,17 +301,17 @@ * match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer * requires reformat. For example, it may convert floating point input to * PCM_16_bit if that's required by the downmixer. - * 3) downmixerBufferProvider: If not NULL, performs the channel remixing to match + * 3) mDownmixerBufferProvider: If not NULL, performs the channel remixing to match * the number of channels required by the mixer sink. * 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from * the downmixer requirements to the mixer engine input requirements. * 5) mTimestretchBufferProvider: Adds timestretching for playback rate */ AudioBufferProvider* mInputBufferProvider; // externally provided buffer provider. - PassthruBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting. - PassthruBufferProvider* downmixerBufferProvider; // wrapper for channel conversion. - PassthruBufferProvider* mPostDownmixReformatBufferProvider; - PassthruBufferProvider* mTimestretchBufferProvider; + std::unique_ptr<PassthruBufferProvider> mReformatBufferProvider; + std::unique_ptr<PassthruBufferProvider> mDownmixerBufferProvider; + std::unique_ptr<PassthruBufferProvider> mPostDownmixReformatBufferProvider; + std::unique_ptr<PassthruBufferProvider> mTimestretchBufferProvider; int32_t sessionId; @@ -260,129 +336,74 @@ AudioPlaybackRate mPlaybackRate; - bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; } - bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate); - bool doesResample() const { return resampler != NULL; } - void resetResampler() { if (resampler != NULL) resampler->reset(); } - void adjustVolumeRamp(bool aux, bool useFloat = false); - size_t getUnreleasedFrames() const { return resampler != NULL ? - resampler->getUnreleasedFrames() : 0; }; + private: + // hooks + void track__genericResample(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); + void track__16BitsStereo(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); + void track__16BitsMono(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); - status_t prepareForDownmix(); - void unprepareForDownmix(); - status_t prepareForReformat(); - void unprepareForReformat(); - bool setPlaybackRate(const AudioPlaybackRate &playbackRate); - void reconfigureBufferProviders(); + void volumeRampStereo(int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux); + void volumeStereo(int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux); + + // multi-format track hooks + template <int MIXTYPE, typename TO, typename TI, typename TA> + void track__Resample(TO* out, size_t frameCount, TO* temp __unused, TA* aux); + template <int MIXTYPE, typename TO, typename TI, typename TA> + void track__NoResample(TO* out, size_t frameCount, TO* temp __unused, TA* aux); }; - typedef void (*process_hook_t)(state_t* state); - - // pad to 32-bytes to fill cache line - struct state_t { - uint32_t enabledTracks; - uint32_t needsChanged; - size_t frameCount; - process_hook_t hook; // one of process__*, never NULL - int32_t *outputTemp; - int32_t *resampleTemp; - NBLog::Writer* mNBLogWriter; // associated NBLog::Writer or &mDummyLog - int32_t reserved[1]; - // FIXME allocate dynamically to save some memory when maxNumTracks < MAX_NUM_TRACKS - track_t tracks[MAX_NUM_TRACKS] __attribute__((aligned(32))); - }; - - // bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc. - uint32_t mTrackNames; - - // bitmask of configured track names; ~0 if maxNumTracks == MAX_NUM_TRACKS, - // but will have fewer bits set if maxNumTracks < MAX_NUM_TRACKS - const uint32_t mConfiguredNames; - - const uint32_t mSampleRate; - - NBLog::Writer mDummyLogWriter; -public: - // Called by FastMixer to inform AudioMixer of it's associated NBLog::Writer. - // FIXME It would be safer to use TLS for this, so we don't accidentally use wrong one. - void setNBLogWriter(NBLog::Writer* log); -private: - state_t mState __attribute__((aligned(32))); - - // Call after changing either the enabled status of a track, or parameters of an enabled track. - // OK to call more often than that, but unnecessary. - void invalidateState(uint32_t mask); + // TODO: remove BLOCKSIZE unit of processing - it isn't needed anymore. + static constexpr int BLOCKSIZE = 16; bool setChannelMasks(int name, audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask); - static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, - int32_t* aux); - static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux); - static void track__16BitsStereo(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, - int32_t* aux); - static void track__16BitsMono(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, - int32_t* aux); - static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, - int32_t* aux); - static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, - int32_t* aux); + // Called when track info changes and a new process hook should be determined. + void invalidate() { + mHook = &AudioMixer::process__validate; + } - static void process__validate(state_t* state); - static void process__nop(state_t* state); - static void process__genericNoResampling(state_t* state); - static void process__genericResampling(state_t* state); - static void process__OneTrack16BitsStereoNoResampling(state_t* state); + void process__validate(); + void process__nop(); + void process__genericNoResampling(); + void process__genericResampling(); + void process__oneTrack16BitsStereoNoResampling(); - static pthread_once_t sOnceControl; - static void sInitRoutine(); - - /* multi-format volume mixing function (calls template functions - * in AudioMixerOps.h). The template parameters are as follows: - * - * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) - * USEFLOATVOL (set to true if float volume is used) - * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards) - * TO: int32_t (Q4.27) or float - * TI: int32_t (Q4.27) or int16_t (Q0.15) or float - * TA: int32_t (Q4.27) - */ - template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL, - typename TO, typename TI, typename TA> - static void volumeMix(TO *out, size_t outFrames, - const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t); - - // multi-format process hooks template <int MIXTYPE, typename TO, typename TI, typename TA> - static void process_NoResampleOneTrack(state_t* state); + void process__noResampleOneTrack(); - // multi-format track hooks - template <int MIXTYPE, typename TO, typename TI, typename TA> - static void track__Resample(track_t* t, TO* out, size_t frameCount, - TO* temp __unused, TA* aux); - template <int MIXTYPE, typename TO, typename TI, typename TA> - static void track__NoResample(track_t* t, TO* out, size_t frameCount, - TO* temp __unused, TA* aux); + static process_hook_t getProcessHook(int processType, uint32_t channelCount, + audio_format_t mixerInFormat, audio_format_t mixerOutFormat); static void convertMixerFormat(void *out, audio_format_t mixerOutFormat, void *in, audio_format_t mixerInFormat, size_t sampleCount); - // hook types - enum { - PROCESSTYPE_NORESAMPLEONETRACK, - }; - enum { - TRACKTYPE_NOP, - TRACKTYPE_RESAMPLE, - TRACKTYPE_NORESAMPLE, - TRACKTYPE_NORESAMPLEMONO, - }; + static void sInitRoutine(); - // functions for determining the proper process and track hooks. - static process_hook_t getProcessHook(int processType, uint32_t channelCount, - audio_format_t mixerInFormat, audio_format_t mixerOutFormat); - static hook_t getTrackHook(int trackType, uint32_t channelCount, - audio_format_t mixerInFormat, audio_format_t mixerOutFormat); + // initialization constants + const uint32_t mSampleRate; + const size_t mFrameCount; + + NBLog::Writer *mNBLogWriter = nullptr; // associated NBLog::Writer + + process_hook_t mHook = &AudioMixer::process__nop; // one of process__*, never nullptr + + // the size of the type (int32_t) should be the largest of all types supported + // by the mixer. + std::unique_ptr<int32_t[]> mOutputTemp; + std::unique_ptr<int32_t[]> mResampleTemp; + + // track names grouped by main buffer, in no particular order of main buffer. + // however names for a particular main buffer are in order (by construction). + std::unordered_map<void * /* mainBuffer */, std::vector<int /* name */>> mGroups; + + // track names that are enabled, in increasing order (by construction). + std::vector<int /* name */> mEnabled; + + // track smart pointers, by name, in increasing order of name. + std::map<int /* name */, std::shared_ptr<Track>> mTracks; + + static pthread_once_t sOnceControl; // initialized in constructor by first new }; // ----------------------------------------------------------------------------
diff --git a/media/libaudioclient/include/media/AudioParameter.h b/media/libaudioclient/include/media/AudioParameter.h index 1ace607..967d895 100644 --- a/media/libaudioclient/include/media/AudioParameter.h +++ b/media/libaudioclient/include/media/AudioParameter.h
@@ -58,6 +58,12 @@ static const char * const keyMonoOutput; static const char * const keyStreamHwAvSync; + // keys for presentation selection + // keyPresentationId: Audio presentation identifier + // keyProgramId: Audio presentation program identifier + static const char * const keyPresentationId; + static const char * const keyProgramId; + // keyStreamConnect / Disconnect: value is an int in audio_devices_t static const char * const keyStreamConnect; static const char * const keyStreamDisconnect; @@ -75,6 +81,11 @@ static const char * const valueListSeparator; + // keyReconfigA2dp: Ask HwModule to reconfigure A2DP offloaded codec + // keyReconfigA2dpSupported: Query if HwModule supports A2DP offload codec config + static const char * const keyReconfigA2dp; + static const char * const keyReconfigA2dpSupported; + String8 toString() const { return toStringImpl(true); } String8 keysToString() const { return toStringImpl(false); }
diff --git a/media/libaudioclient/include/media/AudioRecord.h b/media/libaudioclient/include/media/AudioRecord.h index dd72170..cf446a5 100644 --- a/media/libaudioclient/include/media/AudioRecord.h +++ b/media/libaudioclient/include/media/AudioRecord.h
@@ -17,12 +17,18 @@ #ifndef ANDROID_AUDIORECORD_H #define ANDROID_AUDIORECORD_H +#include <binder/IMemory.h> #include <cutils/sched_policy.h> #include <media/AudioSystem.h> #include <media/AudioTimestamp.h> -#include <media/IAudioRecord.h> +#include <media/MediaAnalyticsItem.h> #include <media/Modulo.h> +#include <media/MicrophoneInfo.h> +#include <utils/RefBase.h> #include <utils/threads.h> +#include <vector> + +#include "android/media/IAudioRecord.h" namespace android { @@ -182,7 +188,8 @@ audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE, uid_t uid = AUDIO_UID_INVALID, pid_t pid = -1, - const audio_attributes_t* pAttributes = NULL); + const audio_attributes_t* pAttributes = NULL, + audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE); /* Terminates the AudioRecord and unregisters it from AudioFlinger. * Also destroys all resources associated with the AudioRecord. @@ -220,7 +227,8 @@ audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE, uid_t uid = AUDIO_UID_INVALID, pid_t pid = -1, - const audio_attributes_t* pAttributes = NULL); + const audio_attributes_t* pAttributes = NULL, + audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE); /* Result of constructing the AudioRecord. This must be checked for successful initialization * before using any AudioRecord API (except for set()), because using @@ -250,6 +258,11 @@ */ uint32_t getNotificationPeriodInFrames() const { return mNotificationFramesAct; } + /* + * return metrics information for the current instance. + */ + status_t getMetrics(MediaAnalyticsItem * &item); + /* After it's created the track is not active. Call start() to * make it active. If set, the callback will start being called. * If event is not AudioSystem::SYNC_EVENT_NONE, the capture start will be delayed until @@ -516,6 +529,16 @@ /* Get the flags */ audio_input_flags_t getFlags() const { AutoMutex _l(mLock); return mFlags; } + /* Get active microphones. A empty vector of MicrophoneInfo will be passed as a parameter, + * the data will be filled when querying the hal. + */ + status_t getActiveMicrophones(std::vector<media::MicrophoneInfo>* activeMicrophones); + + /* + * Dumps the state of an audio record. + */ + status_t dump(int fd, const Vector<String16>& args) const; + private: /* copying audio record objects is not allowed */ AudioRecord(const AudioRecord& other); @@ -565,7 +588,7 @@ // caller must hold lock on mLock for all _l methods - status_t openRecord_l(const Modulo<uint32_t> &epoch, const String16& opPackageName); + status_t createRecord_l(const Modulo<uint32_t> &epoch, const String16& opPackageName); // FIXME enum is faster than strcmp() for parameter 'from' status_t restoreRecord_l(const char *from); @@ -635,7 +658,7 @@ // Next 5 fields may be changed if IAudioRecord is re-created, but always != 0 // provided the initial set() was successful - sp<IAudioRecord> mAudioRecord; + sp<media::IAudioRecord> mAudioRecord; sp<IMemory> mCblkMemory; audio_track_cblk_t* mCblk; // re-load after mLock.unlock() sp<IMemory> mBufferMemory; @@ -677,8 +700,40 @@ // May not match the app selection depending on other // activity and connected devices wp<AudioSystem::AudioDeviceCallback> mDeviceCallback; - audio_port_handle_t mPortId; // unique ID allocated by audio policy +private: + class MediaMetrics { + public: + MediaMetrics() : mAnalyticsItem(new MediaAnalyticsItem("audiorecord")), + mCreatedNs(systemTime(SYSTEM_TIME_REALTIME)), + mStartedNs(0), mDurationNs(0), mCount(0), + mLastError(NO_ERROR) { + } + ~MediaMetrics() { + // mAnalyticsItem alloc failure will be flagged in the constructor + // don't log empty records + if (mAnalyticsItem->count() > 0) { + mAnalyticsItem->selfrecord(); + } + } + void gather(const AudioRecord *record); + MediaAnalyticsItem *dup() { return mAnalyticsItem->dup(); } + + void logStart(nsecs_t when) { mStartedNs = when; mCount++; } + void logStop(nsecs_t when) { mDurationNs += (when-mStartedNs); mStartedNs = 0;} + void markError(status_t errcode, const char *func) + { mLastError = errcode; mLastErrorFunc = func;} + private: + std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem; + nsecs_t mCreatedNs; // XXX: perhaps not worth it in production + nsecs_t mStartedNs; + nsecs_t mDurationNs; + int32_t mCount; + + status_t mLastError; + std::string mLastErrorFunc; + }; + MediaMetrics mMediaMetrics; }; }; // namespace android
diff --git a/media/libaudioclient/include/media/AudioSystem.h b/media/libaudioclient/include/media/AudioSystem.h index 5a81d83..4c0f796 100644 --- a/media/libaudioclient/include/media/AudioSystem.h +++ b/media/libaudioclient/include/media/AudioSystem.h
@@ -23,11 +23,13 @@ #include <media/AudioIoDescriptor.h> #include <media/IAudioFlingerClient.h> #include <media/IAudioPolicyServiceClient.h> +#include <media/MicrophoneInfo.h> #include <system/audio.h> #include <system/audio_effect.h> #include <system/audio_policy.h> #include <utils/Errors.h> #include <utils/Mutex.h> +#include <vector> namespace android { @@ -106,6 +108,9 @@ static float linearToLog(int volume); static int logToLinear(float volume); + static size_t calculateMinFrameCount( + uint32_t afLatencyMs, uint32_t afFrameCount, uint32_t afSampleRate, + uint32_t sampleRate, float speed /*, uint32_t notificationsPerBufferReq*/); // Returned samplingRate and frameCount output values are guaranteed // to be non-zero if status == NO_ERROR @@ -209,18 +214,11 @@ static status_t setForceUse(audio_policy_force_use_t usage, audio_policy_forced_cfg_t config); static audio_policy_forced_cfg_t getForceUse(audio_policy_force_use_t usage); - // Client must successfully hand off the handle reference to AudioFlinger via createTrack(), - // or release it with releaseOutput(). - static audio_io_handle_t getOutput(audio_stream_type_t stream, - uint32_t samplingRate = 0, - audio_format_t format = AUDIO_FORMAT_DEFAULT, - audio_channel_mask_t channelMask = AUDIO_CHANNEL_OUT_STEREO, - audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE, - const audio_offload_info_t *offloadInfo = NULL); static status_t getOutputForAttr(const audio_attributes_t *attr, audio_io_handle_t *output, audio_session_t session, audio_stream_type_t *stream, + pid_t pid, uid_t uid, const audio_config_t *config, audio_output_flags_t flags, @@ -236,24 +234,23 @@ audio_stream_type_t stream, audio_session_t session); - // Client must successfully hand off the handle reference to AudioFlinger via openRecord(), + // Client must successfully hand off the handle reference to AudioFlinger via createRecord(), // or release it with releaseInput(). static status_t getInputForAttr(const audio_attributes_t *attr, audio_io_handle_t *input, audio_session_t session, pid_t pid, uid_t uid, + const String16& opPackageName, const audio_config_base_t *config, audio_input_flags_t flags, audio_port_handle_t *selectedDeviceId, audio_port_handle_t *portId); - static status_t startInput(audio_io_handle_t input, - audio_session_t session); - static status_t stopInput(audio_io_handle_t input, - audio_session_t session); - static void releaseInput(audio_io_handle_t input, - audio_session_t session); + static status_t startInput(audio_port_handle_t portId, + bool *silenced); + static status_t stopInput(audio_port_handle_t portId); + static void releaseInput(audio_port_handle_t portId); static status_t initStreamVolume(audio_stream_type_t stream, int indexMin, int indexMax); @@ -286,7 +283,7 @@ static uint32_t getPrimaryOutputSamplingRate(); static size_t getPrimaryOutputFrameCount(); - static status_t setLowRamDevice(bool isLowRamDevice); + static status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory); // Check if hw offload is possible for given format, stream type, sample rate, // bit rate, duration, video and streaming or offload property is enabled @@ -341,6 +338,17 @@ static float getStreamVolumeDB( audio_stream_type_t stream, int index, audio_devices_t device); + static status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + // numSurroundFormats holds the maximum number of formats and bool value allowed in the array. + // When numSurroundFormats is 0, surroundFormats and surroundFormatsEnabled will not be + // populated. The actual number of surround formats should be returned at numSurroundFormats. + static status_t getSurroundFormats(unsigned int *numSurroundFormats, + audio_format_t *surroundFormats, + bool *surroundFormatsEnabled, + bool reported); + static status_t setSurroundFormatEnabled(audio_format_t audioFormat, bool enabled); + // ---------------------------------------------------------------------------- class AudioPortCallback : public RefBase @@ -432,6 +440,7 @@ int addAudioPortCallback(const sp<AudioPortCallback>& callback); int removeAudioPortCallback(const sp<AudioPortCallback>& callback); + bool isAudioPortCbEnabled() const { return (mAudioPortCallbacks.size() != 0); } // DeathRecipient virtual void binderDied(const wp<IBinder>& who); @@ -450,6 +459,7 @@ Vector <sp <AudioPortCallback> > mAudioPortCallbacks; }; + static audio_io_handle_t getOutput(audio_stream_type_t stream); static const sp<AudioFlingerClient> getAudioFlingerClient(); static sp<AudioIoDescriptor> getIoDescriptor(audio_io_handle_t ioHandle);
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h index 47d87e9..3eb627d 100644 --- a/media/libaudioclient/include/media/AudioTrack.h +++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -22,6 +22,7 @@ #include <media/AudioTimestamp.h> #include <media/IAudioTrack.h> #include <media/AudioResamplerPublic.h> +#include <media/MediaAnalyticsItem.h> #include <media/Modulo.h> #include <utils/threads.h> @@ -218,6 +219,8 @@ * maxRequiredSpeed playback. Values less than 1.0f and greater than * AUDIO_TIMESTRETCH_SPEED_MAX will be clamped. For non-PCM tracks * and direct or offloaded tracks, this parameter is ignored. + * selectedDeviceId: Selected device id of the app which initially requested the AudioTrack + * to open with a specific device. * threadCanCallJava: Not present in parameter list, and so is fixed at false. */ @@ -237,7 +240,8 @@ pid_t pid = -1, const audio_attributes_t* pAttributes = NULL, bool doNotReconnect = false, - float maxRequiredSpeed = 1.0f); + float maxRequiredSpeed = 1.0f, + audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE); /* Creates an audio track and registers it with AudioFlinger. * With this constructor, the track is configured for static buffer mode. @@ -313,7 +317,8 @@ pid_t pid = -1, const audio_attributes_t* pAttributes = NULL, bool doNotReconnect = false, - float maxRequiredSpeed = 1.0f); + float maxRequiredSpeed = 1.0f, + audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE); /* Result of constructing the AudioTrack. This must be checked for successful initialization * before using any AudioTrack API (except for set()), because using @@ -381,6 +386,11 @@ /* Return the static buffer specified in constructor or set(), or 0 for streaming mode */ sp<IMemory> sharedBuffer() const { return mSharedBuffer; } + /* + * return metrics information for the current track. + */ + status_t getMetrics(MediaAnalyticsItem * &item); + /* After it's created the track is not active. Call start() to * make it active. If set, the callback will start being called. * If the track was previously paused, volume is ramped up over the first mix buffer. @@ -748,12 +758,15 @@ status_t setParameters(const String8& keyValuePairs); /* Sets the volume shaper object */ - VolumeShaper::Status applyVolumeShaper( - const sp<VolumeShaper::Configuration>& configuration, - const sp<VolumeShaper::Operation>& operation); + media::VolumeShaper::Status applyVolumeShaper( + const sp<media::VolumeShaper::Configuration>& configuration, + const sp<media::VolumeShaper::Operation>& operation); /* Gets the volume shaper state */ - sp<VolumeShaper::State> getVolumeShaperState(int id); + sp<media::VolumeShaper::State> getVolumeShaperState(int id); + + /* Selects the presentation (if available) */ + status_t selectPresentation(int presentationId, int programId); /* Get parameters */ String8 getParameters(const String8& keys); @@ -990,7 +1003,7 @@ sp<IAudioTrack> mAudioTrack; sp<IMemory> mCblkMemory; audio_track_cblk_t* mCblk; // re-load after mLock.unlock() - audio_io_handle_t mOutput; // returned by AudioSystem::getOutput() + audio_io_handle_t mOutput; // returned by AudioSystem::getOutputForAttr() sp<AudioTrackThread> mAudioTrackThread; bool mThreadCanCallJava; @@ -1160,7 +1173,7 @@ // May not match the app selection depending on other // activity and connected devices. - sp<VolumeHandler> mVolumeHandler; + sp<media::VolumeHandler> mVolumeHandler; private: class DeathNotifier : public IBinder::DeathRecipient { @@ -1178,7 +1191,25 @@ pid_t mClientPid; wp<AudioSystem::AudioDeviceCallback> mDeviceCallback; - audio_port_handle_t mPortId; // unique ID allocated by audio policy + +private: + class MediaMetrics { + public: + MediaMetrics() : mAnalyticsItem(new MediaAnalyticsItem("audiotrack")) { + } + ~MediaMetrics() { + // mAnalyticsItem alloc failure will be flagged in the constructor + // don't log empty records + if (mAnalyticsItem->count() > 0) { + mAnalyticsItem->selfrecord(); + } + } + void gather(const AudioTrack *track); + MediaAnalyticsItem *dup() { return mAnalyticsItem->dup(); } + private: + std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem; + }; + MediaMetrics mMediaMetrics; }; }; // namespace android
diff --git a/media/libaudioclient/include/media/IAudioFlinger.h b/media/libaudioclient/include/media/IAudioFlinger.h index 0ad4231..e6bf72f 100644 --- a/media/libaudioclient/include/media/IAudioFlinger.h +++ b/media/libaudioclient/include/media/IAudioFlinger.h
@@ -24,8 +24,10 @@ #include <utils/RefBase.h> #include <utils/Errors.h> #include <binder/IInterface.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <media/AudioClient.h> #include <media/IAudioTrack.h> -#include <media/IAudioRecord.h> #include <media/IAudioFlingerClient.h> #include <system/audio.h> #include <system/audio_effect.h> @@ -33,6 +35,10 @@ #include <media/IEffect.h> #include <media/IEffectClient.h> #include <utils/String8.h> +#include <media/MicrophoneInfo.h> +#include <vector> + +#include "android/media/IAudioRecord.h" namespace android { @@ -43,6 +49,271 @@ public: DECLARE_META_INTERFACE(AudioFlinger); + /* CreateTrackInput contains all input arguments sent by AudioTrack to AudioFlinger + * when calling createTrack() including arguments that will be updated by AudioFlinger + * and returned in CreateTrackOutput object + */ + class CreateTrackInput : public Parcelable { + public: + status_t readFromParcel(const Parcel *parcel) override { + /* input arguments*/ + memset(&attr, 0, sizeof(audio_attributes_t)); + if (parcel->read(&attr, sizeof(audio_attributes_t)) != NO_ERROR) { + return DEAD_OBJECT; + } + attr.tags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE -1] = '\0'; + memset(&config, 0, sizeof(audio_config_t)); + if (parcel->read(&config, sizeof(audio_config_t)) != NO_ERROR) { + return DEAD_OBJECT; + } + if (clientInfo.readFromParcel(parcel) != NO_ERROR) { + return DEAD_OBJECT; + } + if (parcel->readInt32() != 0) { + sharedBuffer = interface_cast<IMemory>(parcel->readStrongBinder()); + if (sharedBuffer == 0 || sharedBuffer->pointer() == NULL) { + return BAD_VALUE; + } + } + notificationsPerBuffer = parcel->readInt32(); + speed = parcel->readFloat(); + + /* input/output arguments*/ + (void)parcel->read(&flags, sizeof(audio_output_flags_t)); + frameCount = parcel->readInt64(); + notificationFrameCount = parcel->readInt64(); + (void)parcel->read(&selectedDeviceId, sizeof(audio_port_handle_t)); + (void)parcel->read(&sessionId, sizeof(audio_session_t)); + return NO_ERROR; + } + + status_t writeToParcel(Parcel *parcel) const override { + /* input arguments*/ + (void)parcel->write(&attr, sizeof(audio_attributes_t)); + (void)parcel->write(&config, sizeof(audio_config_t)); + (void)clientInfo.writeToParcel(parcel); + if (sharedBuffer != 0) { + (void)parcel->writeInt32(1); + (void)parcel->writeStrongBinder(IInterface::asBinder(sharedBuffer)); + } else { + (void)parcel->writeInt32(0); + } + (void)parcel->writeInt32(notificationsPerBuffer); + (void)parcel->writeFloat(speed); + + /* input/output arguments*/ + (void)parcel->write(&flags, sizeof(audio_output_flags_t)); + (void)parcel->writeInt64(frameCount); + (void)parcel->writeInt64(notificationFrameCount); + (void)parcel->write(&selectedDeviceId, sizeof(audio_port_handle_t)); + (void)parcel->write(&sessionId, sizeof(audio_session_t)); + return NO_ERROR; + } + + /* input */ + audio_attributes_t attr; + audio_config_t config; + AudioClient clientInfo; + sp<IMemory> sharedBuffer; + uint32_t notificationsPerBuffer; + float speed; + + /* input/output */ + audio_output_flags_t flags; + size_t frameCount; + size_t notificationFrameCount; + audio_port_handle_t selectedDeviceId; + audio_session_t sessionId; + }; + + /* CreateTrackOutput contains all output arguments returned by AudioFlinger to AudioTrack + * when calling createTrack() including arguments that were passed as I/O for update by + * CreateTrackInput. + */ + class CreateTrackOutput : public Parcelable { + public: + status_t readFromParcel(const Parcel *parcel) override { + /* input/output arguments*/ + (void)parcel->read(&flags, sizeof(audio_output_flags_t)); + frameCount = parcel->readInt64(); + notificationFrameCount = parcel->readInt64(); + (void)parcel->read(&selectedDeviceId, sizeof(audio_port_handle_t)); + (void)parcel->read(&sessionId, sizeof(audio_session_t)); + + /* output arguments*/ + sampleRate = parcel->readUint32(); + afFrameCount = parcel->readInt64(); + afSampleRate = parcel->readInt64(); + afLatencyMs = parcel->readInt32(); + (void)parcel->read(&outputId, sizeof(audio_io_handle_t)); + return NO_ERROR; + } + + status_t writeToParcel(Parcel *parcel) const override { + /* input/output arguments*/ + (void)parcel->write(&flags, sizeof(audio_output_flags_t)); + (void)parcel->writeInt64(frameCount); + (void)parcel->writeInt64(notificationFrameCount); + (void)parcel->write(&selectedDeviceId, sizeof(audio_port_handle_t)); + (void)parcel->write(&sessionId, sizeof(audio_session_t)); + + /* output arguments*/ + (void)parcel->writeUint32(sampleRate); + (void)parcel->writeInt64(afFrameCount); + (void)parcel->writeInt64(afSampleRate); + (void)parcel->writeInt32(afLatencyMs); + (void)parcel->write(&outputId, sizeof(audio_io_handle_t)); + return NO_ERROR; + } + + /* input/output */ + audio_output_flags_t flags; + size_t frameCount; + size_t notificationFrameCount; + audio_port_handle_t selectedDeviceId; + audio_session_t sessionId; + + /* output */ + uint32_t sampleRate; + size_t afFrameCount; + uint32_t afSampleRate; + uint32_t afLatencyMs; + audio_io_handle_t outputId; + }; + + /* CreateRecordInput contains all input arguments sent by AudioRecord to AudioFlinger + * when calling createRecord() including arguments that will be updated by AudioFlinger + * and returned in CreateRecordOutput object + */ + class CreateRecordInput : public Parcelable { + public: + status_t readFromParcel(const Parcel *parcel) override { + /* input arguments*/ + memset(&attr, 0, sizeof(audio_attributes_t)); + if (parcel->read(&attr, sizeof(audio_attributes_t)) != NO_ERROR) { + return DEAD_OBJECT; + } + attr.tags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE -1] = '\0'; + memset(&config, 0, sizeof(audio_config_base_t)); + if (parcel->read(&config, sizeof(audio_config_base_t)) != NO_ERROR) { + return DEAD_OBJECT; + } + if (clientInfo.readFromParcel(parcel) != NO_ERROR) { + return DEAD_OBJECT; + } + opPackageName = parcel->readString16(); + + /* input/output arguments*/ + (void)parcel->read(&flags, sizeof(audio_input_flags_t)); + frameCount = parcel->readInt64(); + notificationFrameCount = parcel->readInt64(); + (void)parcel->read(&selectedDeviceId, sizeof(audio_port_handle_t)); + (void)parcel->read(&sessionId, sizeof(audio_session_t)); + return NO_ERROR; + } + + status_t writeToParcel(Parcel *parcel) const override { + /* input arguments*/ + (void)parcel->write(&attr, sizeof(audio_attributes_t)); + (void)parcel->write(&config, sizeof(audio_config_base_t)); + (void)clientInfo.writeToParcel(parcel); + (void)parcel->writeString16(opPackageName); + + /* input/output arguments*/ + (void)parcel->write(&flags, sizeof(audio_input_flags_t)); + (void)parcel->writeInt64(frameCount); + (void)parcel->writeInt64(notificationFrameCount); + (void)parcel->write(&selectedDeviceId, sizeof(audio_port_handle_t)); + (void)parcel->write(&sessionId, sizeof(audio_session_t)); + return NO_ERROR; + } + + /* input */ + audio_attributes_t attr; + audio_config_base_t config; + AudioClient clientInfo; + String16 opPackageName; + + /* input/output */ + audio_input_flags_t flags; + size_t frameCount; + size_t notificationFrameCount; + audio_port_handle_t selectedDeviceId; + audio_session_t sessionId; + }; + + /* CreateRecordOutput contains all output arguments returned by AudioFlinger to AudioRecord + * when calling createRecord() including arguments that were passed as I/O for update by + * CreateRecordInput. + */ + class CreateRecordOutput : public Parcelable { + public: + status_t readFromParcel(const Parcel *parcel) override { + /* input/output arguments*/ + (void)parcel->read(&flags, sizeof(audio_input_flags_t)); + frameCount = parcel->readInt64(); + notificationFrameCount = parcel->readInt64(); + (void)parcel->read(&selectedDeviceId, sizeof(audio_port_handle_t)); + (void)parcel->read(&sessionId, sizeof(audio_session_t)); + + /* output arguments*/ + sampleRate = parcel->readUint32(); + (void)parcel->read(&inputId, sizeof(audio_io_handle_t)); + if (parcel->readInt32() != 0) { + cblk = interface_cast<IMemory>(parcel->readStrongBinder()); + if (cblk == 0 || cblk->pointer() == NULL) { + return BAD_VALUE; + } + } + if (parcel->readInt32() != 0) { + buffers = interface_cast<IMemory>(parcel->readStrongBinder()); + if (buffers == 0 || buffers->pointer() == NULL) { + return BAD_VALUE; + } + } + return NO_ERROR; + } + + status_t writeToParcel(Parcel *parcel) const override { + /* input/output arguments*/ + (void)parcel->write(&flags, sizeof(audio_input_flags_t)); + (void)parcel->writeInt64(frameCount); + (void)parcel->writeInt64(notificationFrameCount); + (void)parcel->write(&selectedDeviceId, sizeof(audio_port_handle_t)); + (void)parcel->write(&sessionId, sizeof(audio_session_t)); + + /* output arguments*/ + (void)parcel->writeUint32(sampleRate); + (void)parcel->write(&inputId, sizeof(audio_io_handle_t)); + if (cblk != 0) { + (void)parcel->writeInt32(1); + (void)parcel->writeStrongBinder(IInterface::asBinder(cblk)); + } else { + (void)parcel->writeInt32(0); + } + if (buffers != 0) { + (void)parcel->writeInt32(1); + (void)parcel->writeStrongBinder(IInterface::asBinder(buffers)); + } else { + (void)parcel->writeInt32(0); + } + + return NO_ERROR; + } + + /* input/output */ + audio_input_flags_t flags; + size_t frameCount; + size_t notificationFrameCount; + audio_port_handle_t selectedDeviceId; + audio_session_t sessionId; + + /* output */ + uint32_t sampleRate; + audio_io_handle_t inputId; + sp<IMemory> cblk; + sp<IMemory> buffers; + }; // invariant on exit for all APIs that return an sp<>: // (return value != 0) == (*status == NO_ERROR) @@ -50,45 +321,13 @@ /* create an audio track and registers it with AudioFlinger. * return null if the track cannot be created. */ - virtual sp<IAudioTrack> createTrack( - audio_stream_type_t streamType, - uint32_t sampleRate, - audio_format_t format, - audio_channel_mask_t channelMask, - size_t *pFrameCount, - audio_output_flags_t *flags, - const sp<IMemory>& sharedBuffer, - // On successful return, AudioFlinger takes over the handle - // reference and will release it when the track is destroyed. - // However on failure, the client is responsible for release. - audio_io_handle_t output, - pid_t pid, - pid_t tid, // -1 means unused, otherwise must be valid non-0 - audio_session_t *sessionId, - int clientUid, - status_t *status, - audio_port_handle_t portId) = 0; + virtual sp<IAudioTrack> createTrack(const CreateTrackInput& input, + CreateTrackOutput& output, + status_t *status) = 0; - virtual sp<IAudioRecord> openRecord( - // On successful return, AudioFlinger takes over the handle - // reference and will release it when the track is destroyed. - // However on failure, the client is responsible for release. - audio_io_handle_t input, - uint32_t sampleRate, - audio_format_t format, - audio_channel_mask_t channelMask, - const String16& callingPackage, - size_t *pFrameCount, - audio_input_flags_t *flags, - pid_t pid, - pid_t tid, // -1 means unused, otherwise must be valid non-0 - int clientUid, - audio_session_t *sessionId, - size_t *notificationFrames, - sp<IMemory>& cblk, - sp<IMemory>& buffers, // return value 0 means it follows cblk - status_t *status, - audio_port_handle_t portId) = 0; + virtual sp<media::IAudioRecord> createRecord(const CreateRecordInput& input, + CreateRecordOutput& output, + status_t *status) = 0; // FIXME Surprisingly, format/latency don't work for input handles @@ -131,6 +370,7 @@ // mic mute/state virtual status_t setMicMute(bool state) = 0; virtual bool getMicMute() const = 0; + virtual void setRecordSilenced(uid_t uid, bool silenced) = 0; virtual status_t setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs) = 0; @@ -216,8 +456,9 @@ // Intended for AudioService to inform AudioFlinger of device's low RAM attribute, // and should be called at most once. For a definition of what "low RAM" means, see - // android.app.ActivityManager.isLowRamDevice(). - virtual status_t setLowRamDevice(bool isLowRamDevice) = 0; + // android.app.ActivityManager.isLowRamDevice(). The totalMemory parameter + // is obtained from android.app.ActivityManager.MemoryInfo.totalMem. + virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) = 0; /* List available audio ports and their attributes */ virtual status_t listAudioPorts(unsigned int *num_ports, @@ -247,6 +488,9 @@ // Returns the number of frames per audio HAL buffer. virtual size_t frameCountHAL(audio_io_handle_t ioHandle) const = 0; + + /* List available microphones and their characteristics */ + virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones) = 0; };
diff --git a/media/libaudioclient/include/media/IAudioPolicyService.h b/media/libaudioclient/include/media/IAudioPolicyService.h index eec3e88..c3876af 100644 --- a/media/libaudioclient/include/media/IAudioPolicyService.h +++ b/media/libaudioclient/include/media/IAudioPolicyService.h
@@ -55,16 +55,12 @@ virtual status_t setForceUse(audio_policy_force_use_t usage, audio_policy_forced_cfg_t config) = 0; virtual audio_policy_forced_cfg_t getForceUse(audio_policy_force_use_t usage) = 0; - virtual audio_io_handle_t getOutput(audio_stream_type_t stream, - uint32_t samplingRate = 0, - audio_format_t format = AUDIO_FORMAT_DEFAULT, - audio_channel_mask_t channelMask = 0, - audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE, - const audio_offload_info_t *offloadInfo = NULL) = 0; + virtual audio_io_handle_t getOutput(audio_stream_type_t stream) = 0; virtual status_t getOutputForAttr(const audio_attributes_t *attr, audio_io_handle_t *output, audio_session_t session, audio_stream_type_t *stream, + pid_t pid, uid_t uid, const audio_config_t *config, audio_output_flags_t flags, @@ -84,16 +80,15 @@ audio_session_t session, pid_t pid, uid_t uid, + const String16& opPackageName, const audio_config_base_t *config, audio_input_flags_t flags, audio_port_handle_t *selectedDeviceId, audio_port_handle_t *portId) = 0; - virtual status_t startInput(audio_io_handle_t input, - audio_session_t session) = 0; - virtual status_t stopInput(audio_io_handle_t input, - audio_session_t session) = 0; - virtual void releaseInput(audio_io_handle_t input, - audio_session_t session) = 0; + virtual status_t startInput(audio_port_handle_t portId, + bool *silenced) = 0; + virtual status_t stopInput(audio_port_handle_t portId) = 0; + virtual void releaseInput(audio_port_handle_t portId) = 0; virtual status_t initStreamVolume(audio_stream_type_t stream, int indexMin, int indexMax) = 0; @@ -171,6 +166,12 @@ virtual status_t getMasterMono(bool *mono) = 0; virtual float getStreamVolumeDB( audio_stream_type_t stream, int index, audio_devices_t device) = 0; + + virtual status_t getSurroundFormats(unsigned int *numSurroundFormats, + audio_format_t *surroundFormats, + bool *surroundFormatsEnabled, + bool reported) = 0; + virtual status_t setSurroundFormatEnabled(audio_format_t audioFormat, bool enabled) = 0; };
diff --git a/media/libaudioclient/include/media/IAudioRecord.h b/media/libaudioclient/include/media/IAudioRecord.h deleted file mode 100644 index 7768176..0000000 --- a/media/libaudioclient/include/media/IAudioRecord.h +++ /dev/null
@@ -1,66 +0,0 @@ -/* - * Copyright (C) 2007 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 IAUDIORECORD_H_ -#define IAUDIORECORD_H_ - -#include <stdint.h> -#include <sys/types.h> - -#include <utils/RefBase.h> -#include <utils/Errors.h> -#include <binder/IInterface.h> -#include <binder/IMemory.h> -#include <system/audio.h> - -namespace android { - -// ---------------------------------------------------------------------------- - -class IAudioRecord : public IInterface -{ -public: - DECLARE_META_INTERFACE(AudioRecord); - - /* After it's created the track is not active. Call start() to - * make it active. - */ - virtual status_t start(int /*AudioSystem::sync_event_t*/ event, - audio_session_t triggerSession) = 0; - - /* Stop a track. If set, the callback will cease being called and - * obtainBuffer will return an error. Buffers that are already released - * will be processed, unless flush() is called. - */ - virtual void stop() = 0; -}; - -// ---------------------------------------------------------------------------- - -class BnAudioRecord : public BnInterface<IAudioRecord> -{ -public: - virtual status_t onTransact( uint32_t code, - const Parcel& data, - Parcel* reply, - uint32_t flags = 0); -}; - -// ---------------------------------------------------------------------------- - -}; // namespace android - -#endif /*IAUDIORECORD_H_*/
diff --git a/media/libaudioclient/include/media/IAudioTrack.h b/media/libaudioclient/include/media/IAudioTrack.h index 27a62d6..94afe3c 100644 --- a/media/libaudioclient/include/media/IAudioTrack.h +++ b/media/libaudioclient/include/media/IAudioTrack.h
@@ -77,12 +77,12 @@ virtual void signal() = 0; /* Sets the volume shaper */ - virtual VolumeShaper::Status applyVolumeShaper( - const sp<VolumeShaper::Configuration>& configuration, - const sp<VolumeShaper::Operation>& operation) = 0; + virtual media::VolumeShaper::Status applyVolumeShaper( + const sp<media::VolumeShaper::Configuration>& configuration, + const sp<media::VolumeShaper::Operation>& operation) = 0; /* gets the volume shaper state */ - virtual sp<VolumeShaper::State> getVolumeShaperState(int id) = 0; + virtual sp<media::VolumeShaper::State> getVolumeShaperState(int id) = 0; }; // ----------------------------------------------------------------------------
diff --git a/media/libaudioclient/include/media/PlayerBase.h b/media/libaudioclient/include/media/PlayerBase.h index e63090b..e7a8abc 100644 --- a/media/libaudioclient/include/media/PlayerBase.h +++ b/media/libaudioclient/include/media/PlayerBase.h
@@ -17,35 +17,31 @@ #ifndef __ANDROID_PLAYER_BASE_H__ #define __ANDROID_PLAYER_BASE_H__ -#include <audiomanager/IPlayer.h> #include <audiomanager/AudioManager.h> #include <audiomanager/IAudioManager.h> +#include "android/media/BnPlayer.h" namespace android { -class PlayerBase : public BnPlayer +class PlayerBase : public ::android::media::BnPlayer { public: explicit PlayerBase(); - virtual ~PlayerBase(); + virtual ~PlayerBase() override; virtual void destroy() = 0; //IPlayer implementation - virtual void start(); - virtual void pause(); - virtual void stop(); - virtual void setVolume(float vol); - virtual void setPan(float pan); - virtual void setStartDelayMs(int32_t delayMs); - virtual void applyVolumeShaper( - const sp<VolumeShaper::Configuration>& configuration, - const sp<VolumeShaper::Operation>& operation) override; - - virtual status_t onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); - + virtual binder::Status start() override; + virtual binder::Status pause() override; + virtual binder::Status stop() override; + virtual binder::Status setVolume(float vol) override; + virtual binder::Status setPan(float pan) override; + virtual binder::Status setStartDelayMs(int32_t delayMs) override; + virtual binder::Status applyVolumeShaper( + const media::VolumeShaper::Configuration& configuration, + const media::VolumeShaper::Operation& operation) override; status_t startWithStatus(); status_t pauseWithStatus();
diff --git a/media/libaudioclient/include/media/ToneGenerator.h b/media/libaudioclient/include/media/ToneGenerator.h index fc3d3ee..7ae6aa6 100644 --- a/media/libaudioclient/include/media/ToneGenerator.h +++ b/media/libaudioclient/include/media/ToneGenerator.h
@@ -177,7 +177,7 @@ // Region specific tones. - // These supervisory tones are different depending on the region (USA/CANADA, JAPAN, rest of the world). + // These supervisory tones are different depending on the region (USA/CANADA, JAPAN, Singapore, Hong Kong, rest of the world). // When a tone in the range [FIRST_SUP_TONE, LAST_SUP_TONE] is requested, the region is determined // from system property gsm.operator.iso-country and the proper tone descriptor is selected with the // help of sToneMappingTable[] @@ -194,12 +194,30 @@ TONE_JAPAN_BUSY, // Busy tone: 400Hz, 500ms ON, 500ms OFF... TONE_JAPAN_RADIO_ACK, // Radio path acknowlegment: 400Hz, 1s ON, 2s OFF... // GB Supervisory tones + TONE_GB_BUSY, // Busy tone: 400 Hz, 375ms ON, 375ms OFF... + TONE_GB_CONGESTION, // Congestion Tone: 400 Hz, 400ms ON, 350ms OFF, 225ms ON, 525ms OFF... TONE_GB_RINGTONE, // Ring Tone: A 400Hz + 450Hz tone repeated in a 0.4s on, 0.2s off, 0.4s on, 2.0s off pattern. // AUSTRALIA Supervisory tones + TONE_AUSTRALIA_DIAL, // Dial tone: 425 Hz tone modulated with 25 Hz, continuous TONE_AUSTRALIA_RINGTONE, // Ring tone: A 400Hz + 450Hz tone repeated in a 0.4s on, 0.2s off, 0.4s on, 2.0s off pattern. TONE_AUSTRALIA_BUSY, // Busy tone: 425 Hz repeated in a 0.375s on, 0.375s off pattern. TONE_AUSTRALIA_CALL_WAITING,// Call waiting tone: 425Hz tone repeated in a 0.2s on, 0.2s off, 0.2s on, 4.4s off pattern. TONE_AUSTRALIA_CONGESTION, // Congestion tone: 425Hz tone repeated in a 0.375s on, 0.375s off pattern + // SINGAPORE Supervisory tones + TONE_SG_BUSY, // Busy tone: 425 Hz, 750ms ON, 750ms OFF... + TONE_SG_RINGTONE, // Ring Tone: 425 Hz tone modulated with 24 Hz, 400ms ON, 200ms OFF, 400ms ON, 2s OFF... + // HONG KONG Supervisory tones + TONE_HK_BUSY, // Busy tone: 480 Hz + 620 Hz, 500ms ON, 500ms OFF... + TONE_HK_RINGTONE, // Ring Tone: 440 Hz + 480 Hz repeated with pattern 0,4s on, 0,2s off, 0,4s on and 3s off. + // IRELAND Supervisory tones + TONE_IE_RINGTONE, // Ring Tone: A 400Hz + 450Hz tone repeated in a 0.4s on, 0.2s off, 0.4s on, 2.0s off pattern. + TONE_IE_CALL_WAITING, // Call waiting tone: 425Hz tone repeated in a 0.18s on, 0.2s off, 0.2s on, 4.5s off pattern. + // INDIA supervisory tones + TONE_INDIA_DIAL, // Dial tone: 400 Hz tone modulated with 25Hz, continuous + TONE_INDIA_BUSY, // Busy tone: 400 Hz, 750ms ON, 750ms OFF... + TONE_INDIA_CONGESTION, // Congestion tone: 400 Hz, 250ms ON, 250ms OFF... + TONE_INDIA_CALL_WAITING, // Call waiting tone: 400 Hz, tone repeated in a 0.2s on, 0.1s off, 0.2s on, 7.5s off pattern. + TONE_INDIA_RINGTONE, // Ring tone: 400 Hz tone modulated with 25Hz, 0.4 on 0.2 off 0.4 on 2..0 off NUM_ALTERNATE_TONES }; @@ -208,6 +226,10 @@ JAPAN, GB, AUSTRALIA, + SINGAPORE, + HONGKONG, + IRELAND, + INDIA, CEPT, NUM_REGIONS };
diff --git a/media/libaudioclient/include/media/TrackPlayerBase.h b/media/libaudioclient/include/media/TrackPlayerBase.h index 2d113c0..66e9b3b 100644 --- a/media/libaudioclient/include/media/TrackPlayerBase.h +++ b/media/libaudioclient/include/media/TrackPlayerBase.h
@@ -32,9 +32,9 @@ virtual void destroy(); //IPlayer implementation - virtual void applyVolumeShaper( - const sp<VolumeShaper::Configuration>& configuration, - const sp<VolumeShaper::Operation>& operation); + virtual binder::Status applyVolumeShaper( + const media::VolumeShaper::Configuration& configuration, + const media::VolumeShaper::Operation& operation); //FIXME move to protected field, so far made public to minimize changes to AudioTrack logic sp<AudioTrack> mAudioTrack;
diff --git a/media/libaudioclient/tests/Android.bp b/media/libaudioclient/tests/Android.bp new file mode 100644 index 0000000..52bb2fb --- /dev/null +++ b/media/libaudioclient/tests/Android.bp
@@ -0,0 +1,35 @@ +cc_defaults { + name: "libaudioclient_tests_defaults", + cflags: [ + "-Wall", + "-Werror", + ], +} + +cc_test { + name: "test_create_audiotrack", + defaults: ["libaudioclient_tests_defaults"], + srcs: ["test_create_audiotrack.cpp", + "test_create_utils.cpp"], + shared_libs: [ + "libaudioclient", + "libbinder", + "libcutils", + "libutils", + ], + data: ["track_test_input_*.txt"], +} + +cc_test { + name: "test_create_audiorecord", + defaults: ["libaudioclient_tests_defaults"], + srcs: ["test_create_audiorecord.cpp", + "test_create_utils.cpp"], + shared_libs: [ + "libaudioclient", + "libbinder", + "libcutils", + "libutils", + ], + data: ["record_test_input_*.txt"], +}
diff --git a/media/libaudioclient/tests/record_test_input_v1.0_ref.txt b/media/libaudioclient/tests/record_test_input_v1.0_ref.txt new file mode 100644 index 0000000..e01598e --- /dev/null +++ b/media/libaudioclient/tests/record_test_input_v1.0_ref.txt
@@ -0,0 +1,33 @@ +version 1.0 +# Input file for test_create_audiorecord +# Add one line for each tested AudioRecord constructor with the following arguments: +# sampleRate format channelMask frameCount notificationFrames flags sessionId inputSource +# sample rate tests + 48000 0x1 0x10 4800 2400 0x0 0 0 + 24000 0x1 0x10 4800 2400 0x0 0 0 + 16000 0x1 0x10 4800 2400 0x0 0 0 + 8000 0x1 0x10 4800 2400 0x0 0 0 + 44100 0x1 0x10 4410 2205 0x0 0 0 + 22050 0x1 0x10 4410 2205 0x0 0 0 + 11025 0x1 0x10 4410 2205 0x0 0 0 +# format tests + 48000 0x2 0x10 4800 2400 0x0 0 0 + 48000 0x3 0x10 4800 2400 0x0 0 0 + 48000 0x5 0x10 4800 2400 0x0 0 0 +# channel mask tests + 48000 0x1 0x0C 4800 2400 0x0 0 0 +# frame count tests + 48000 0x1 0x10 0 0 0x0 0 0 + 48000 0x1 0x10 48000 0 0x0 0 0 + 48000 0x1 0x10 12000 6000 0x0 0 0 +# flags test + 48000 0x1 0x0C 0 0 0x1 0 0 + 44100 0x1 0x0C 0 0 0x5 0 0 +# session tests + 48000 0x1 0x10 0 0 0 1001 0 +# input source tests + 48000 0x1 0x10 0 0 0 0 1 + 48000 0x1 0x10 0 0 0 0 5 + 48000 0x1 0x10 0 0 0 0 6 + 48000 0x1 0x10 0 0 0 0 7 + 48000 0x1 0x10 0 0 0 0 9
diff --git a/media/libaudioclient/tests/record_test_output_v1.0_ref_walleye.txt b/media/libaudioclient/tests/record_test_output_v1.0_ref_walleye.txt new file mode 100644 index 0000000..76608eb --- /dev/null +++ b/media/libaudioclient/tests/record_test_output_v1.0_ref_walleye.txt
@@ -0,0 +1,198 @@ + +#### Test 1 status 0 + AudioRecord::dump + status(0), active(0), session Id(65) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(4800), req. frame count(4800) + notif. frame count(960), req. notif. frame count(2400) + input(150), latency(100), selected device Id(0), routed device Id(11) + +#### Test 2 status 0 + AudioRecord::dump + status(0), active(0), session Id(73) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(24000) + frame count(4800), req. frame count(4800) + notif. frame count(480), req. notif. frame count(2400) + input(158), latency(200), selected device Id(0), routed device Id(11) + +#### Test 3 status 0 + AudioRecord::dump + status(0), active(0), session Id(81) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(16000) + frame count(4800), req. frame count(4800) + notif. frame count(320), req. notif. frame count(2400) + input(166), latency(300), selected device Id(0), routed device Id(11) + +#### Test 4 status 0 + AudioRecord::dump + status(0), active(0), session Id(89) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(8000) + frame count(4800), req. frame count(4800) + notif. frame count(160), req. notif. frame count(2400) + input(174), latency(600), selected device Id(0), routed device Id(11) + +#### Test 5 status 0 + AudioRecord::dump + status(0), active(0), session Id(97) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(44100) + frame count(4410), req. frame count(4410) + notif. frame count(896), req. notif. frame count(2205) + input(182), latency(100), selected device Id(0), routed device Id(11) + +#### Test 6 status 0 + AudioRecord::dump + status(0), active(0), session Id(105) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(22050) + frame count(4410), req. frame count(4410) + notif. frame count(448), req. notif. frame count(2205) + input(190), latency(200), selected device Id(0), routed device Id(11) + +#### Test 7 status 0 + AudioRecord::dump + status(0), active(0), session Id(113) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(11025) + frame count(4410), req. frame count(4410) + notif. frame count(224), req. notif. frame count(2205) + input(198), latency(400), selected device Id(0), routed device Id(11) + +#### Test 8 status 0 + AudioRecord::dump + status(0), active(0), session Id(121) + flags(0), req. flags(0), audio source(0) + format(0x2), channel mask(0x10), channel count(1), sample rate(48000) + frame count(4800), req. frame count(4800) + notif. frame count(960), req. notif. frame count(2400) + input(206), latency(100), selected device Id(0), routed device Id(11) + +#### Test 9 status 0 + AudioRecord::dump + status(0), active(0), session Id(129) + flags(0), req. flags(0), audio source(0) + format(0x3), channel mask(0x10), channel count(1), sample rate(48000) + frame count(4800), req. frame count(4800) + notif. frame count(960), req. notif. frame count(2400) + input(214), latency(100), selected device Id(0), routed device Id(11) + +#### Test 10 status 0 + AudioRecord::dump + status(0), active(0), session Id(137) + flags(0), req. flags(0), audio source(0) + format(0x5), channel mask(0x10), channel count(1), sample rate(48000) + frame count(4800), req. frame count(4800) + notif. frame count(960), req. notif. frame count(2400) + input(222), latency(100), selected device Id(0), routed device Id(11) + +#### Test 11 status 0 + AudioRecord::dump + status(0), active(0), session Id(145) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0xc), channel count(2), sample rate(48000) + frame count(4800), req. frame count(4800) + notif. frame count(960), req. notif. frame count(2400) + input(230), latency(100), selected device Id(0), routed device Id(11) + +#### Test 12 status 0 + AudioRecord::dump + status(0), active(0), session Id(153) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(2880), req. frame count(2880) + notif. frame count(960), req. notif. frame count(0) + input(238), latency(60), selected device Id(0), routed device Id(11) + +#### Test 13 status 0 + AudioRecord::dump + status(0), active(0), session Id(161) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(48000), req. frame count(48000) + notif. frame count(960), req. notif. frame count(0) + input(246), latency(1000), selected device Id(0), routed device Id(11) + +#### Test 14 status 0 + AudioRecord::dump + status(0), active(0), session Id(169) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(12000), req. frame count(12000) + notif. frame count(960), req. notif. frame count(6000) + input(254), latency(250), selected device Id(0), routed device Id(11) + +#### Test 15 status 0 + AudioRecord::dump + status(0), active(0), session Id(177) + flags(0x1), req. flags(0x1), audio source(0) + format(0x1), channel mask(0xc), channel count(2), sample rate(48000) + frame count(4096), req. frame count(4096) + notif. frame count(96), req. notif. frame count(0) + input(262), latency(85), selected device Id(0), routed device Id(11) + +#### Test 16 status 0 + AudioRecord::dump + status(0), active(0), session Id(185) + flags(0), req. flags(0x5), audio source(0) + format(0x1), channel mask(0xc), channel count(2), sample rate(44100) + frame count(2664), req. frame count(2664) + notif. frame count(888), req. notif. frame count(0) + input(278), latency(60), selected device Id(0), routed device Id(11) + +#### Test 17 status 0 + AudioRecord::dump + status(0), active(0), session Id(1001) + flags(0), req. flags(0), audio source(0) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(2880), req. frame count(2880) + notif. frame count(960), req. notif. frame count(0) + input(286), latency(60), selected device Id(0), routed device Id(11) + +#### Test 18 status 0 + AudioRecord::dump + status(0), active(0), session Id(193) + flags(0), req. flags(0), audio source(1) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(2880), req. frame count(2880) + notif. frame count(960), req. notif. frame count(0) + input(294), latency(60), selected device Id(0), routed device Id(11) + +#### Test 19 status 0 + AudioRecord::dump + status(0), active(0), session Id(201) + flags(0), req. flags(0), audio source(5) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(2880), req. frame count(2880) + notif. frame count(960), req. notif. frame count(0) + input(302), latency(60), selected device Id(0), routed device Id(12) + +#### Test 20 status 0 + AudioRecord::dump + status(0), active(0), session Id(209) + flags(0), req. flags(0), audio source(6) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(2880), req. frame count(2880) + notif. frame count(960), req. notif. frame count(0) + input(310), latency(60), selected device Id(0), routed device Id(11) + +#### Test 21 status 0 + AudioRecord::dump + status(0), active(0), session Id(217) + flags(0), req. flags(0), audio source(7) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(2880), req. frame count(2880) + notif. frame count(960), req. notif. frame count(0) + input(318), latency(60), selected device Id(0), routed device Id(11) + +#### Test 22 status 0 + AudioRecord::dump + status(0), active(0), session Id(225) + flags(0), req. flags(0), audio source(9) + format(0x1), channel mask(0x10), channel count(1), sample rate(48000) + frame count(2880), req. frame count(2880) + notif. frame count(960), req. notif. frame count(0) + input(326), latency(60), selected device Id(0), routed device Id(11)
diff --git a/media/libaudioclient/tests/test_create_audiorecord.cpp b/media/libaudioclient/tests/test_create_audiorecord.cpp new file mode 100644 index 0000000..cf6a734 --- /dev/null +++ b/media/libaudioclient/tests/test_create_audiorecord.cpp
@@ -0,0 +1,129 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <binder/MemoryBase.h> +#include <binder/MemoryDealer.h> +#include <binder/MemoryHeapBase.h> +#include <media/AudioRecord.h> + +#include "test_create_utils.h" + +#define NUM_ARGUMENTS 8 +#define VERSION_VALUE "1.0" +#define PACKAGE_NAME "AudioRecord test" + +namespace android { + +int testRecord(FILE *inputFile, int outputFileFd) +{ + char line[MAX_INPUT_FILE_LINE_LENGTH]; + uint32_t testCount = 0; + Vector<String16> args; + int ret = 0; + + if (inputFile == nullptr) { + sp<AudioRecord> record = new AudioRecord(AUDIO_SOURCE_DEFAULT, + 0 /* sampleRate */, + AUDIO_FORMAT_DEFAULT, + AUDIO_CHANNEL_IN_MONO, + String16(PACKAGE_NAME)); + if (record == 0 || record->initCheck() != NO_ERROR) { + write(outputFileFd, "Error creating AudioRecord\n", + sizeof("Error creating AudioRecord\n")); + } else { + record->dump(outputFileFd, args); + } + return 0; + } + + // check version + if (!checkVersion(inputFile, VERSION_VALUE)) { + return 1; + } + + while (readLine(inputFile, line, MAX_INPUT_FILE_LINE_LENGTH) == 0) { + uint32_t sampleRate; + audio_format_t format; + audio_channel_mask_t channelMask; + size_t frameCount; + int32_t notificationFrames; + audio_input_flags_t flags; + audio_session_t sessionId; + audio_source_t inputSource; + audio_attributes_t attributes; + status_t status; + char statusStr[MAX_OUTPUT_FILE_LINE_LENGTH]; + bool fast = false; + + if (sscanf(line, " %u %x %x %zu %d %x %u %u", + &sampleRate, &format, &channelMask, + &frameCount, ¬ificationFrames, + &flags, &sessionId, &inputSource) != NUM_ARGUMENTS) { + fprintf(stderr, "Malformed line for test #%u in input file\n", testCount+1); + ret = 1; + continue; + } + testCount++; + + if ((flags & AUDIO_INPUT_FLAG_FAST) != 0) { + fast = true; + } + + memset(&attributes, 0, sizeof(attributes)); + attributes.source = inputSource; + + sp<AudioRecord> record = new AudioRecord(String16(PACKAGE_NAME)); + + record->set(AUDIO_SOURCE_DEFAULT, + sampleRate, + format, + channelMask, + frameCount, + fast ? callback : nullptr, + nullptr, + notificationFrames, + false, + sessionId, + fast ? AudioRecord::TRANSFER_CALLBACK : AudioRecord::TRANSFER_DEFAULT, + flags, + getuid(), + getpid(), + &attributes, + AUDIO_PORT_HANDLE_NONE); + status = record->initCheck(); + sprintf(statusStr, "\n#### Test %u status %d\n", testCount, status); + write(outputFileFd, statusStr, strlen(statusStr)); + if (status != NO_ERROR) { + continue; + } + record->dump(outputFileFd, args); + } + return ret; +} + +}; // namespace android + + +int main(int argc, char **argv) +{ + return android::main(argc, argv, android::testRecord); +} +
diff --git a/media/libaudioclient/tests/test_create_audiotrack.cpp b/media/libaudioclient/tests/test_create_audiotrack.cpp new file mode 100644 index 0000000..cf9b925 --- /dev/null +++ b/media/libaudioclient/tests/test_create_audiotrack.cpp
@@ -0,0 +1,153 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <binder/MemoryBase.h> +#include <binder/MemoryDealer.h> +#include <binder/MemoryHeapBase.h> +#include <media/AudioTrack.h> + +#include "test_create_utils.h" + +#define NUM_ARGUMENTS 10 +#define VERSION_VALUE "1.0" + +namespace android { + +int testTrack(FILE *inputFile, int outputFileFd) +{ + char line[MAX_INPUT_FILE_LINE_LENGTH]; + uint32_t testCount = 0; + Vector<String16> args; + int ret = 0; + + if (inputFile == nullptr) { + sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_DEFAULT, + 0 /* sampleRate */, + AUDIO_FORMAT_DEFAULT, + AUDIO_CHANNEL_OUT_STEREO); + if (track == 0 || track->initCheck() != NO_ERROR) { + write(outputFileFd, "Error creating AudioTrack\n", + sizeof("Error creating AudioTrack\n")); + } else { + track->dump(outputFileFd, args); + } + return 0; + } + + // check version + if (!checkVersion(inputFile, VERSION_VALUE)) { + return 1; + } + + while (readLine(inputFile, line, MAX_INPUT_FILE_LINE_LENGTH) == 0) { + uint32_t sampleRate; + audio_format_t format; + audio_channel_mask_t channelMask; + size_t frameCount; + int32_t notificationFrames; + uint32_t useSharedBuffer; + audio_output_flags_t flags; + audio_session_t sessionId; + audio_usage_t usage; + audio_content_type_t contentType; + audio_attributes_t attributes; + sp<IMemory> sharedBuffer; + sp<MemoryDealer> heap; + audio_offload_info_t offloadInfo = AUDIO_INFO_INITIALIZER; + status_t status; + char statusStr[MAX_OUTPUT_FILE_LINE_LENGTH]; + bool offload = false; + bool fast = false; + + if (sscanf(line, " %u %x %x %zu %d %u %x %u %u %u", + &sampleRate, &format, &channelMask, + &frameCount, ¬ificationFrames, &useSharedBuffer, + &flags, &sessionId, &usage, &contentType) != NUM_ARGUMENTS) { + fprintf(stderr, "Malformed line for test #%u in input file\n", testCount+1); + ret = 1; + continue; + } + testCount++; + + if (useSharedBuffer != 0) { + size_t heapSize = audio_channel_count_from_out_mask(channelMask) * + audio_bytes_per_sample(format) * frameCount; + heap = new MemoryDealer(heapSize, "AudioTrack Heap Base"); + sharedBuffer = heap->allocate(heapSize); + frameCount = 0; + notificationFrames = 0; + } + if ((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0) { + offloadInfo.sample_rate = sampleRate; + offloadInfo.channel_mask = channelMask; + offloadInfo.format = format; + offload = true; + } + if ((flags & AUDIO_OUTPUT_FLAG_FAST) != 0) { + fast = true; + } + + memset(&attributes, 0, sizeof(attributes)); + attributes.content_type = contentType; + attributes.usage = usage; + + sp<AudioTrack> track = new AudioTrack(); + + track->set(AUDIO_STREAM_DEFAULT, + sampleRate, + format, + channelMask, + frameCount, + flags, + (fast || offload) ? callback : nullptr, + nullptr, + notificationFrames, + sharedBuffer, + false, + sessionId, + ((fast && sharedBuffer == 0) || offload) ? + AudioTrack::TRANSFER_CALLBACK : AudioTrack::TRANSFER_DEFAULT, + offload ? &offloadInfo : nullptr, + getuid(), + getpid(), + &attributes, + false, + 1.0f, + AUDIO_PORT_HANDLE_NONE); + status = track->initCheck(); + sprintf(statusStr, "\n#### Test %u status %d\n", testCount, status); + write(outputFileFd, statusStr, strlen(statusStr)); + if (status != NO_ERROR) { + continue; + } + track->dump(outputFileFd, args); + } + return ret; +} + +}; // namespace android + + +int main(int argc, char **argv) +{ + return android::main(argc, argv, android::testTrack); +} +
diff --git a/media/libaudioclient/tests/test_create_utils.cpp b/media/libaudioclient/tests/test_create_utils.cpp new file mode 100644 index 0000000..8aa1f13 --- /dev/null +++ b/media/libaudioclient/tests/test_create_utils.cpp
@@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "test_create_utils.h" + +namespace android { + +int readLine(FILE *inputFile, char *line, int size) { + int ret = 0; + while (true) { + char *str = fgets(line, size, inputFile); + if (str == nullptr) { + ret = -1; + break; + } + if (feof(inputFile) != 0 || ferror(inputFile) != 0) { + ret = -1; + break; + } + if (strlen(str) != 0 && str[0] != COMMENT_CHAR) { + break; + } + } + return ret; +} + +bool checkVersion(FILE *inputFile, const char *version) +{ + char line[MAX_INPUT_FILE_LINE_LENGTH]; + char versionKey[MAX_INPUT_FILE_LINE_LENGTH]; + char versionValue[MAX_INPUT_FILE_LINE_LENGTH]; + + if (readLine(inputFile, line, MAX_INPUT_FILE_LINE_LENGTH) != 0) { + fprintf(stderr, "Missing version in input file\n"); + return false; + } + + if (sscanf(line, " %s %s", versionKey, versionValue) != 2) { + fprintf(stderr, "Malformed version in input file\n"); + return false; + } + if (strcmp(versionKey, VERSION_KEY) != 0) { + fprintf(stderr, "Malformed version in input file\n"); + return false; + } + if (strcmp(versionValue, version) != 0) { + fprintf(stderr, "Wrong input file version %s expecting %s\n", versionValue, version); + return false; + } + return true; +} + +void callback(int event __unused, void* user __unused, void *info __unused) +{ +} + +int main(int argc, char **argv, test_func_t testFunc) +{ + FILE *inputFile = nullptr; + int outputFileFd = STDOUT_FILENO; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + int ret = 0; + + if (argc > 5) { + fprintf(stderr, "Usage: %s [-i input_params.txt] [-o output_params.txt]\n", argv[0]); + return 1; + } + + argv++; + while (*argv) { + if (strcmp(*argv, "-i") == 0) { + argv++; + if (*argv) { + inputFile = fopen(*argv, "r"); + if (inputFile == nullptr) { + ret = 1; + } + } else { + ret = 1; + } + } + if (strcmp(*argv, "-o") == 0) { + argv++; + if (*argv) { + outputFileFd = open(*argv, O_WRONLY|O_CREAT, mode); + if (outputFileFd < 0) { + ret = 1; + } + } else { + ret = 1; + } + argv++; + } + if (*argv) { + argv++; + } + } + + if (ret != 0) { + return ret; + } + + ret = testFunc(inputFile, outputFileFd); + + if (inputFile) { + fclose(inputFile); + } + if (outputFileFd >= 0 && outputFileFd != STDOUT_FILENO) { + close(outputFileFd); + } + + return ret; +} + +}; // namespace android +
diff --git a/media/libaudioclient/tests/test_create_utils.h b/media/libaudioclient/tests/test_create_utils.h new file mode 100644 index 0000000..2ad646e --- /dev/null +++ b/media/libaudioclient/tests/test_create_utils.h
@@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#define MAX_INPUT_FILE_LINE_LENGTH 512 +#define MAX_OUTPUT_FILE_LINE_LENGTH 512 + +#define COMMENT_CHAR '#' +#define VERSION_KEY "version" + +namespace android { + +int readLine(FILE *inputFile, char *line, int size); + +bool checkVersion(FILE *inputFile, const char *version); + +void callback(int event, void* user, void *info); + +typedef int (*test_func_t)(FILE *inputFile, int outputFileFd); + +int main(int argc, char **argv, test_func_t testFunc); + +}; // namespace android
diff --git a/media/libaudioclient/tests/track_test_input_v1.0_ref.txt b/media/libaudioclient/tests/track_test_input_v1.0_ref.txt new file mode 100644 index 0000000..b923ff3 --- /dev/null +++ b/media/libaudioclient/tests/track_test_input_v1.0_ref.txt
@@ -0,0 +1,40 @@ +version 1.0 +# Input file for test_create_audiotrack +# Add one line for each tested AudioTrack constructor with the following arguments: +# sampleRate format channelMask frameCount notificationFrames sharedBuffer flags sessionId usage contentType +# sample rate tests + 48000 0x1 0x3 4800 2400 0 0x0 0 1 2 + 24000 0x1 0x3 4800 2400 0 0x0 0 1 2 + 16000 0x1 0x3 4800 2400 0 0x0 0 1 2 + 8000 0x1 0x3 4800 2400 0 0x0 0 1 2 + 44100 0x1 0x3 4410 2205 0 0x0 0 1 2 + 22050 0x1 0x3 4410 2205 0 0x0 0 1 2 + 11025 0x1 0x3 4410 2205 0 0x0 0 1 2 +# format tests + 48000 0x2 0x3 4800 2400 0 0x0 0 1 2 + 48000 0x3 0x3 4800 2400 0 0x0 0 1 2 + 48000 0x5 0x3 4800 2400 0 0x0 0 1 2 +# channel mask tests + 48000 0x1 0x1 4800 2400 0 0x0 0 1 2 + 48000 0x1 0x3F 4800 2400 0 0x0 0 1 2 + 48000 0x1 0x63F 4800 2400 0 0x0 0 1 2 +# framecount tests + 48000 0x1 0x3 0 0 0 0x0 0 1 2 + 48000 0x1 0x3 48000 0 0 0x0 0 1 2 + 48000 0x1 0x3 0 -2 0 0x4 0 1 2 +# shared memory tests + 48000 0x1 0x3 4800 2400 1 0x0 0 1 2 + 48000 0x1 0x3 4800 2400 1 0x4 0 1 2 +# flags test + 48000 0x1 0x3 4800 2400 0 0x4 0 1 2 + 48000 0x1 0x3 4800 2400 0 0x8 0 1 2 + 44100 0x1000000 0x3 4800 2400 0 0x11 0 1 2 +# session tests + 48000 0x1 0x3 4800 2400 0 0x0 1001 1 2 +# attributes tests + 48000 0x1 0x3 4800 2400 0 0x0 0 0 0 + 48000 0x1 0x3 4800 2400 0 0x0 0 2 1 + 48000 0x1 0x3 4800 2400 0 0x0 0 4 2 + 48000 0x1 0x3 4800 2400 0 0x0 0 5 2 + 48000 0x1 0x3 4800 2400 0 0x0 0 11 1 + 48000 0x1 0x3 4800 2400 0 0x0 0 12 1
diff --git a/media/libaudioclient/tests/track_test_output_v1.0_ref_walleye.txt b/media/libaudioclient/tests/track_test_output_v1.0_ref_walleye.txt new file mode 100644 index 0000000..5fe433c --- /dev/null +++ b/media/libaudioclient/tests/track_test_output_v1.0_ref_walleye.txt
@@ -0,0 +1,308 @@ + +#### Test 1 status 0 + AudioTrack::dump + status(0), state(1), session Id(49), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 2 status 0 + AudioTrack::dump + status(0), state(1), session Id(57), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(24000), original sample rate(24000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(1600), req. notif. frame count(2400), req. notif. per buff(0) + latency (250), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 3 status 0 + AudioTrack::dump + status(0), state(1), session Id(65), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(16000), original sample rate(16000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(1600), req. notif. frame count(2400), req. notif. per buff(0) + latency (350), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 4 status 0 + AudioTrack::dump + status(0), state(1), session Id(73), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(8000), original sample rate(8000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(1600), req. notif. frame count(2400), req. notif. per buff(0) + latency (650), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 5 status 0 + AudioTrack::dump + status(0), state(1), session Id(81), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(44100), original sample rate(44100), speed(1.000000) + frame count(4410), req. frame count(4410) + notif. frame count(1470), req. notif. frame count(2205), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 6 status 0 + AudioTrack::dump + status(0), state(1), session Id(89), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(22050), original sample rate(22050), speed(1.000000) + frame count(4410), req. frame count(4410) + notif. frame count(1470), req. notif. frame count(2205), req. notif. per buff(0) + latency (250), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 7 status 0 + AudioTrack::dump + status(0), state(1), session Id(97), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(11025), original sample rate(11025), speed(1.000000) + frame count(4410), req. frame count(4410) + notif. frame count(1470), req. notif. frame count(2205), req. notif. per buff(0) + latency (450), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 8 status 0 + AudioTrack::dump + status(0), state(1), session Id(105), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(2), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 9 status 0 + AudioTrack::dump + status(0), state(1), session Id(113), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(3), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (180), selected device Id(0), routed device Id(2) + output(29) AF latency (80) AF frame count(1920) AF SampleRate(48000) + +#### Test 10 status 0 + AudioTrack::dump + status(0), state(1), session Id(121), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(5), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (180), selected device Id(0), routed device Id(2) + output(29) AF latency (80) AF frame count(1920) AF SampleRate(48000) + +#### Test 11 status 0 + AudioTrack::dump + status(0), state(1), session Id(129), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(1), channel count(1) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 12 status 0 + AudioTrack::dump + status(0), state(1), session Id(137), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3f), channel count(6) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 13 status 0 + AudioTrack::dump + status(0), state(1), session Id(145), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(63f), channel count(8) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 14 status 0 + AudioTrack::dump + status(0), state(1), session Id(153), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(1924), req. frame count(1924) + notif. frame count(962), req. notif. frame count(0), req. notif. per buff(0) + latency (90), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 15 status 0 + AudioTrack::dump + status(0), state(1), session Id(161), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(48000), req. frame count(48000) + notif. frame count(24000), req. notif. frame count(0), req. notif. per buff(0) + latency (1050), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 16 status 0 + AudioTrack::dump + status(0), state(1), session Id(169), flags(4) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(480), req. frame count(480) + notif. frame count(240), req. notif. frame count(0), req. notif. per buff(2) + latency (60), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 17 status 0 + AudioTrack::dump + status(0), state(1), session Id(177), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(0), req. notif. frame count(0), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 18 status 0 + AudioTrack::dump + status(0), state(1), session Id(185), flags(4) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(0), req. notif. frame count(0), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 19 status 0 + AudioTrack::dump + status(0), state(1), session Id(193), flags(4) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(240), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 20 status 0 + AudioTrack::dump + status(0), state(1), session Id(201), flags(8) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (180), selected device Id(0), routed device Id(2) + output(29) AF latency (80) AF frame count(1920) AF SampleRate(48000) + +#### Test 21 status 0 + AudioTrack::dump + status(0), state(1), session Id(209), flags(11) + stream type(3), left - right volume(1.000000, 1.000000) + format(1000000), channel mask(3), channel count(2) + sample rate(44100), original sample rate(44100), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(4800), req. notif. frame count(2400), req. notif. per buff(0) + latency (204), selected device Id(0), routed device Id(2) + output(53) AF latency (96) AF frame count(262144) AF SampleRate(44100) + +#### Test 22 status 0 + AudioTrack::dump + status(0), state(1), session Id(1001), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 23 status 0 + AudioTrack::dump + status(0), state(1), session Id(217), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 24 status 0 + AudioTrack::dump + status(0), state(1), session Id(225), flags(0) + stream type(0), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (140), selected device Id(0), routed device Id(1) + output(45) AF latency (40) AF frame count(960) AF SampleRate(48000) + +#### Test 25 status 0 + AudioTrack::dump + status(0), state(1), session Id(233), flags(0) + stream type(4), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(3) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 26 status 0 + AudioTrack::dump + status(0), state(1), session Id(241), flags(0) + stream type(5), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(3) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 27 status 0 + AudioTrack::dump + status(0), state(1), session Id(249), flags(0) + stream type(10), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000) + +#### Test 28 status 0 + AudioTrack::dump + status(0), state(1), session Id(257), flags(0) + stream type(3), left - right volume(1.000000, 1.000000) + format(1), channel mask(3), channel count(2) + sample rate(48000), original sample rate(48000), speed(1.000000) + frame count(4800), req. frame count(4800) + notif. frame count(2400), req. notif. frame count(2400), req. notif. per buff(0) + latency (150), selected device Id(0), routed device Id(2) + output(13) AF latency (50) AF frame count(960) AF SampleRate(48000)
diff --git a/media/libaudiohal/2.0/Android.bp b/media/libaudiohal/2.0/Android.bp new file mode 100644 index 0000000..574b435 --- /dev/null +++ b/media/libaudiohal/2.0/Android.bp
@@ -0,0 +1,54 @@ +cc_library_shared { + name: "libaudiohal@2.0", + + srcs: [ + "DeviceHalLocal.cpp", + "DevicesFactoryHalHybrid.cpp", + "DevicesFactoryHalLocal.cpp", + "StreamHalLocal.cpp", + + "ConversionHelperHidl.cpp", + "DeviceHalHidl.cpp", + "DevicesFactoryHalHidl.cpp", + "EffectBufferHalHidl.cpp", + "EffectHalHidl.cpp", + "EffectsFactoryHalHidl.cpp", + "StreamHalHidl.cpp", + ], + + export_include_dirs: ["."], + + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "libaudiohal_deathhandler", + "libaudioutils", + "libcutils", + "liblog", + "libutils", + "libhardware", + "libbase", + "libfmq", + "libhwbinder", + "libhidlbase", + "libhidlmemory", + "libhidltransport", + "android.hardware.audio@2.0", + "android.hardware.audio.common@2.0", + "android.hardware.audio.common@2.0-util", + "android.hardware.audio.effect@2.0", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libmedia_helper", + "libmediautils", + ], + header_libs: [ + "libaudiohal_headers" + ], + + export_shared_lib_headers: [ + "libfmq", + ], +}
diff --git a/media/libaudiohal/ConversionHelperHidl.cpp b/media/libaudiohal/2.0/ConversionHelperHidl.cpp similarity index 100% rename from media/libaudiohal/ConversionHelperHidl.cpp rename to media/libaudiohal/2.0/ConversionHelperHidl.cpp
diff --git a/media/libaudiohal/ConversionHelperHidl.h b/media/libaudiohal/2.0/ConversionHelperHidl.h similarity index 100% rename from media/libaudiohal/ConversionHelperHidl.h rename to media/libaudiohal/2.0/ConversionHelperHidl.h
diff --git a/media/libaudiohal/2.0/DeviceHalHidl.cpp b/media/libaudiohal/2.0/DeviceHalHidl.cpp new file mode 100644 index 0000000..5b99d70 --- /dev/null +++ b/media/libaudiohal/2.0/DeviceHalHidl.cpp
@@ -0,0 +1,364 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> + +#define LOG_TAG "DeviceHalHidl" +//#define LOG_NDEBUG 0 + +#include <android/hardware/audio/2.0/IPrimaryDevice.h> +#include <cutils/native_handle.h> +#include <hwbinder/IPCThreadState.h> +#include <utils/Log.h> + +#include "DeviceHalHidl.h" +#include "HidlUtils.h" +#include "StreamHalHidl.h" + +using ::android::hardware::audio::common::V2_0::AudioConfig; +using ::android::hardware::audio::common::V2_0::AudioDevice; +using ::android::hardware::audio::common::V2_0::AudioInputFlag; +using ::android::hardware::audio::common::V2_0::AudioOutputFlag; +using ::android::hardware::audio::common::V2_0::AudioPatchHandle; +using ::android::hardware::audio::common::V2_0::AudioPort; +using ::android::hardware::audio::common::V2_0::AudioPortConfig; +using ::android::hardware::audio::common::V2_0::AudioMode; +using ::android::hardware::audio::common::V2_0::AudioSource; +using ::android::hardware::audio::common::V2_0::HidlUtils; +using ::android::hardware::audio::V2_0::DeviceAddress; +using ::android::hardware::audio::V2_0::IPrimaryDevice; +using ::android::hardware::audio::V2_0::ParameterValue; +using ::android::hardware::audio::V2_0::Result; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; + +namespace android { + +namespace { + +status_t deviceAddressFromHal( + audio_devices_t device, const char* halAddress, DeviceAddress* address) { + address->device = AudioDevice(device); + + if (halAddress == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) { + return OK; + } + const bool isInput = (device & AUDIO_DEVICE_BIT_IN) != 0; + if (isInput) device &= ~AUDIO_DEVICE_BIT_IN; + if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_A2DP) != 0) + || (isInput && (device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) != 0)) { + int status = sscanf(halAddress, + "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX", + &address->address.mac[0], &address->address.mac[1], &address->address.mac[2], + &address->address.mac[3], &address->address.mac[4], &address->address.mac[5]); + return status == 6 ? OK : BAD_VALUE; + } else if ((!isInput && (device & AUDIO_DEVICE_OUT_IP) != 0) + || (isInput && (device & AUDIO_DEVICE_IN_IP) != 0)) { + int status = sscanf(halAddress, + "%hhu.%hhu.%hhu.%hhu", + &address->address.ipv4[0], &address->address.ipv4[1], + &address->address.ipv4[2], &address->address.ipv4[3]); + return status == 4 ? OK : BAD_VALUE; + } else if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_USB)) != 0 + || (isInput && (device & AUDIO_DEVICE_IN_ALL_USB)) != 0) { + int status = sscanf(halAddress, + "card=%d;device=%d", + &address->address.alsa.card, &address->address.alsa.device); + return status == 2 ? OK : BAD_VALUE; + } else if ((!isInput && (device & AUDIO_DEVICE_OUT_BUS) != 0) + || (isInput && (device & AUDIO_DEVICE_IN_BUS) != 0)) { + if (halAddress != NULL) { + address->busAddress = halAddress; + return OK; + } + return BAD_VALUE; + } else if ((!isInput && (device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) != 0 + || (isInput && (device & AUDIO_DEVICE_IN_REMOTE_SUBMIX) != 0)) { + if (halAddress != NULL) { + address->rSubmixAddress = halAddress; + return OK; + } + return BAD_VALUE; + } + return OK; +} + +} // namespace + +DeviceHalHidl::DeviceHalHidl(const sp<IDevice>& device) + : ConversionHelperHidl("Device"), mDevice(device), + mPrimaryDevice(IPrimaryDevice::castFrom(device)) { +} + +DeviceHalHidl::~DeviceHalHidl() { + if (mDevice != 0) { + mDevice.clear(); + hardware::IPCThreadState::self()->flushCommands(); + } +} + +status_t DeviceHalHidl::getSupportedDevices(uint32_t*) { + // Obsolete. + return INVALID_OPERATION; +} + +status_t DeviceHalHidl::initCheck() { + if (mDevice == 0) return NO_INIT; + return processReturn("initCheck", mDevice->initCheck()); +} + +status_t DeviceHalHidl::setVoiceVolume(float volume) { + if (mDevice == 0) return NO_INIT; + if (mPrimaryDevice == 0) return INVALID_OPERATION; + return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume)); +} + +status_t DeviceHalHidl::setMasterVolume(float volume) { + if (mDevice == 0) return NO_INIT; + if (mPrimaryDevice == 0) return INVALID_OPERATION; + return processReturn("setMasterVolume", mPrimaryDevice->setMasterVolume(volume)); +} + +status_t DeviceHalHidl::getMasterVolume(float *volume) { + if (mDevice == 0) return NO_INIT; + if (mPrimaryDevice == 0) return INVALID_OPERATION; + Result retval; + Return<void> ret = mPrimaryDevice->getMasterVolume( + [&](Result r, float v) { + retval = r; + if (retval == Result::OK) { + *volume = v; + } + }); + return processReturn("getMasterVolume", ret, retval); +} + +status_t DeviceHalHidl::setMode(audio_mode_t mode) { + if (mDevice == 0) return NO_INIT; + if (mPrimaryDevice == 0) return INVALID_OPERATION; + return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode))); +} + +status_t DeviceHalHidl::setMicMute(bool state) { + if (mDevice == 0) return NO_INIT; + return processReturn("setMicMute", mDevice->setMicMute(state)); +} + +status_t DeviceHalHidl::getMicMute(bool *state) { + if (mDevice == 0) return NO_INIT; + Result retval; + Return<void> ret = mDevice->getMicMute( + [&](Result r, bool mute) { + retval = r; + if (retval == Result::OK) { + *state = mute; + } + }); + return processReturn("getMicMute", ret, retval); +} + +status_t DeviceHalHidl::setMasterMute(bool state) { + if (mDevice == 0) return NO_INIT; + return processReturn("setMasterMute", mDevice->setMasterMute(state)); +} + +status_t DeviceHalHidl::getMasterMute(bool *state) { + if (mDevice == 0) return NO_INIT; + Result retval; + Return<void> ret = mDevice->getMasterMute( + [&](Result r, bool mute) { + retval = r; + if (retval == Result::OK) { + *state = mute; + } + }); + return processReturn("getMasterMute", ret, retval); +} + +status_t DeviceHalHidl::setParameters(const String8& kvPairs) { + if (mDevice == 0) return NO_INIT; + hidl_vec<ParameterValue> hidlParams; + status_t status = parametersFromHal(kvPairs, &hidlParams); + if (status != OK) return status; + return processReturn("setParameters", mDevice->setParameters(hidlParams)); +} + +status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) { + values->clear(); + if (mDevice == 0) return NO_INIT; + hidl_vec<hidl_string> hidlKeys; + status_t status = keysFromHal(keys, &hidlKeys); + if (status != OK) return status; + Result retval; + Return<void> ret = mDevice->getParameters( + hidlKeys, + [&](Result r, const hidl_vec<ParameterValue>& parameters) { + retval = r; + if (retval == Result::OK) { + parametersToHal(parameters, values); + } + }); + return processReturn("getParameters", ret, retval); +} + +status_t DeviceHalHidl::getInputBufferSize( + const struct audio_config *config, size_t *size) { + if (mDevice == 0) return NO_INIT; + AudioConfig hidlConfig; + HidlUtils::audioConfigFromHal(*config, &hidlConfig); + Result retval; + Return<void> ret = mDevice->getInputBufferSize( + hidlConfig, + [&](Result r, uint64_t bufferSize) { + retval = r; + if (retval == Result::OK) { + *size = static_cast<size_t>(bufferSize); + } + }); + return processReturn("getInputBufferSize", ret, retval); +} + +status_t DeviceHalHidl::openOutputStream( + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + const char *address, + sp<StreamOutHalInterface> *outStream) { + if (mDevice == 0) return NO_INIT; + DeviceAddress hidlDevice; + status_t status = deviceAddressFromHal(devices, address, &hidlDevice); + if (status != OK) return status; + AudioConfig hidlConfig; + HidlUtils::audioConfigFromHal(*config, &hidlConfig); + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mDevice->openOutputStream( + handle, + hidlDevice, + hidlConfig, + AudioOutputFlag(flags), + [&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) { + retval = r; + if (retval == Result::OK) { + *outStream = new StreamOutHalHidl(result); + } + HidlUtils::audioConfigToHal(suggestedConfig, config); + }); + return processReturn("openOutputStream", ret, retval); +} + +status_t DeviceHalHidl::openInputStream( + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + audio_input_flags_t flags, + const char *address, + audio_source_t source, + sp<StreamInHalInterface> *inStream) { + if (mDevice == 0) return NO_INIT; + DeviceAddress hidlDevice; + status_t status = deviceAddressFromHal(devices, address, &hidlDevice); + if (status != OK) return status; + AudioConfig hidlConfig; + HidlUtils::audioConfigFromHal(*config, &hidlConfig); + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mDevice->openInputStream( + handle, + hidlDevice, + hidlConfig, + AudioInputFlag(flags), + AudioSource(source), + [&](Result r, const sp<IStreamIn>& result, const AudioConfig& suggestedConfig) { + retval = r; + if (retval == Result::OK) { + *inStream = new StreamInHalHidl(result); + } + HidlUtils::audioConfigToHal(suggestedConfig, config); + }); + return processReturn("openInputStream", ret, retval); +} + +status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) { + if (mDevice == 0) return NO_INIT; + return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches); +} + +status_t DeviceHalHidl::createAudioPatch( + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *patch) { + if (mDevice == 0) return NO_INIT; + hidl_vec<AudioPortConfig> hidlSources, hidlSinks; + HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources); + HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks); + Result retval; + Return<void> ret = mDevice->createAudioPatch( + hidlSources, hidlSinks, + [&](Result r, AudioPatchHandle hidlPatch) { + retval = r; + if (retval == Result::OK) { + *patch = static_cast<audio_patch_handle_t>(hidlPatch); + } + }); + return processReturn("createAudioPatch", ret, retval); +} + +status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) { + if (mDevice == 0) return NO_INIT; + return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch)); +} + +status_t DeviceHalHidl::getAudioPort(struct audio_port *port) { + if (mDevice == 0) return NO_INIT; + AudioPort hidlPort; + HidlUtils::audioPortFromHal(*port, &hidlPort); + Result retval; + Return<void> ret = mDevice->getAudioPort( + hidlPort, + [&](Result r, const AudioPort& p) { + retval = r; + if (retval == Result::OK) { + HidlUtils::audioPortToHal(p, port); + } + }); + return processReturn("getAudioPort", ret, retval); +} + +status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) { + if (mDevice == 0) return NO_INIT; + AudioPortConfig hidlConfig; + HidlUtils::audioPortConfigFromHal(*config, &hidlConfig); + return processReturn("setAudioPortConfig", mDevice->setAudioPortConfig(hidlConfig)); +} + +status_t DeviceHalHidl::getMicrophones( + std::vector<media::MicrophoneInfo> *microphonesInfo __unused) { + if (mDevice == 0) return NO_INIT; + return INVALID_OPERATION; +} + +status_t DeviceHalHidl::dump(int fd) { + if (mDevice == 0) return NO_INIT; + native_handle_t* hidlHandle = native_handle_create(1, 0); + hidlHandle->data[0] = fd; + Return<void> ret = mDevice->debugDump(hidlHandle); + native_handle_delete(hidlHandle); + return processReturn("dump", ret); +} + +} // namespace android
diff --git a/media/libaudiohal/2.0/DeviceHalHidl.h b/media/libaudiohal/2.0/DeviceHalHidl.h new file mode 100644 index 0000000..3c1cb59 --- /dev/null +++ b/media/libaudiohal/2.0/DeviceHalHidl.h
@@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_DEVICE_HAL_HIDL_H +#define ANDROID_HARDWARE_DEVICE_HAL_HIDL_H + +#include <android/hardware/audio/2.0/IDevice.h> +#include <android/hardware/audio/2.0/IPrimaryDevice.h> +#include <media/audiohal/DeviceHalInterface.h> + +#include "ConversionHelperHidl.h" + +using ::android::hardware::audio::V2_0::IDevice; +using ::android::hardware::audio::V2_0::IPrimaryDevice; +using ::android::hardware::Return; + +namespace android { + +class DeviceHalHidl : public DeviceHalInterface, public ConversionHelperHidl +{ + public: + // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t. + virtual status_t getSupportedDevices(uint32_t *devices); + + // Check to see if the audio hardware interface has been initialized. + virtual status_t initCheck(); + + // Set the audio volume of a voice call. Range is between 0.0 and 1.0. + virtual status_t setVoiceVolume(float volume); + + // Set the audio volume for all audio activities other than voice call. + virtual status_t setMasterVolume(float volume); + + // Get the current master volume value for the HAL. + virtual status_t getMasterVolume(float *volume); + + // Called when the audio mode changes. + virtual status_t setMode(audio_mode_t mode); + + // Muting control. + virtual status_t setMicMute(bool state); + virtual status_t getMicMute(bool *state); + virtual status_t setMasterMute(bool state); + virtual status_t getMasterMute(bool *state); + + // Set global audio parameters. + virtual status_t setParameters(const String8& kvPairs); + + // Get global audio parameters. + virtual status_t getParameters(const String8& keys, String8 *values); + + // Returns audio input buffer size according to parameters passed. + virtual status_t getInputBufferSize(const struct audio_config *config, + size_t *size); + + // Creates and opens the audio hardware output stream. The stream is closed + // by releasing all references to the returned object. + virtual status_t openOutputStream( + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + const char *address, + sp<StreamOutHalInterface> *outStream); + + // Creates and opens the audio hardware input stream. The stream is closed + // by releasing all references to the returned object. + virtual status_t openInputStream( + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + audio_input_flags_t flags, + const char *address, + audio_source_t source, + sp<StreamInHalInterface> *inStream); + + // Returns whether createAudioPatch and releaseAudioPatch operations are supported. + virtual status_t supportsAudioPatches(bool *supportsPatches); + + // Creates an audio patch between several source and sink ports. + virtual status_t createAudioPatch( + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *patch); + + // Releases an audio patch. + virtual status_t releaseAudioPatch(audio_patch_handle_t patch); + + // Fills the list of supported attributes for a given audio port. + virtual status_t getAudioPort(struct audio_port *port); + + // Set audio port configuration. + virtual status_t setAudioPortConfig(const struct audio_port_config *config); + + // List microphones + virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + virtual status_t dump(int fd); + + private: + friend class DevicesFactoryHalHidl; + sp<IDevice> mDevice; + sp<IPrimaryDevice> mPrimaryDevice; // Null if it's not a primary device. + + // Can not be constructed directly by clients. + explicit DeviceHalHidl(const sp<IDevice>& device); + + // The destructor automatically closes the device. + virtual ~DeviceHalHidl(); +}; + +} // namespace android + +#endif // ANDROID_HARDWARE_DEVICE_HAL_HIDL_H
diff --git a/media/libaudiohal/2.0/DeviceHalLocal.cpp b/media/libaudiohal/2.0/DeviceHalLocal.cpp new file mode 100644 index 0000000..ec3bf78 --- /dev/null +++ b/media/libaudiohal/2.0/DeviceHalLocal.cpp
@@ -0,0 +1,204 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DeviceHalLocal" +//#define LOG_NDEBUG 0 + +#include <utils/Log.h> + +#include "DeviceHalLocal.h" +#include "StreamHalLocal.h" + +namespace android { + +DeviceHalLocal::DeviceHalLocal(audio_hw_device_t *dev) + : mDev(dev) { +} + +DeviceHalLocal::~DeviceHalLocal() { + int status = audio_hw_device_close(mDev); + ALOGW_IF(status, "Error closing audio hw device %p: %s", mDev, strerror(-status)); + mDev = 0; +} + +status_t DeviceHalLocal::getSupportedDevices(uint32_t *devices) { + if (mDev->get_supported_devices == NULL) return INVALID_OPERATION; + *devices = mDev->get_supported_devices(mDev); + return OK; +} + +status_t DeviceHalLocal::initCheck() { + return mDev->init_check(mDev); +} + +status_t DeviceHalLocal::setVoiceVolume(float volume) { + return mDev->set_voice_volume(mDev, volume); +} + +status_t DeviceHalLocal::setMasterVolume(float volume) { + if (mDev->set_master_volume == NULL) return INVALID_OPERATION; + return mDev->set_master_volume(mDev, volume); +} + +status_t DeviceHalLocal::getMasterVolume(float *volume) { + if (mDev->get_master_volume == NULL) return INVALID_OPERATION; + return mDev->get_master_volume(mDev, volume); +} + +status_t DeviceHalLocal::setMode(audio_mode_t mode) { + return mDev->set_mode(mDev, mode); +} + +status_t DeviceHalLocal::setMicMute(bool state) { + return mDev->set_mic_mute(mDev, state); +} + +status_t DeviceHalLocal::getMicMute(bool *state) { + return mDev->get_mic_mute(mDev, state); +} + +status_t DeviceHalLocal::setMasterMute(bool state) { + if (mDev->set_master_mute == NULL) return INVALID_OPERATION; + return mDev->set_master_mute(mDev, state); +} + +status_t DeviceHalLocal::getMasterMute(bool *state) { + if (mDev->get_master_mute == NULL) return INVALID_OPERATION; + return mDev->get_master_mute(mDev, state); +} + +status_t DeviceHalLocal::setParameters(const String8& kvPairs) { + return mDev->set_parameters(mDev, kvPairs.string()); +} + +status_t DeviceHalLocal::getParameters(const String8& keys, String8 *values) { + char *halValues = mDev->get_parameters(mDev, keys.string()); + if (halValues != NULL) { + values->setTo(halValues); + free(halValues); + } else { + values->clear(); + } + return OK; +} + +status_t DeviceHalLocal::getInputBufferSize( + const struct audio_config *config, size_t *size) { + *size = mDev->get_input_buffer_size(mDev, config); + return OK; +} + +status_t DeviceHalLocal::openOutputStream( + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + const char *address, + sp<StreamOutHalInterface> *outStream) { + audio_stream_out_t *halStream; + ALOGV("open_output_stream handle: %d devices: %x flags: %#x" + "srate: %d format %#x channels %x address %s", + handle, devices, flags, + config->sample_rate, config->format, config->channel_mask, + address); + int openResut = mDev->open_output_stream( + mDev, handle, devices, flags, config, &halStream, address); + if (openResut == OK) { + *outStream = new StreamOutHalLocal(halStream, this); + } + ALOGV("open_output_stream status %d stream %p", openResut, halStream); + return openResut; +} + +status_t DeviceHalLocal::openInputStream( + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + audio_input_flags_t flags, + const char *address, + audio_source_t source, + sp<StreamInHalInterface> *inStream) { + audio_stream_in_t *halStream; + ALOGV("open_input_stream handle: %d devices: %x flags: %#x " + "srate: %d format %#x channels %x address %s source %d", + handle, devices, flags, + config->sample_rate, config->format, config->channel_mask, + address, source); + int openResult = mDev->open_input_stream( + mDev, handle, devices, config, &halStream, flags, address, source); + if (openResult == OK) { + *inStream = new StreamInHalLocal(halStream, this); + } + ALOGV("open_input_stream status %d stream %p", openResult, inStream); + return openResult; +} + +status_t DeviceHalLocal::supportsAudioPatches(bool *supportsPatches) { + *supportsPatches = version() >= AUDIO_DEVICE_API_VERSION_3_0; + return OK; +} + +status_t DeviceHalLocal::createAudioPatch( + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *patch) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { + return mDev->create_audio_patch( + mDev, num_sources, sources, num_sinks, sinks, patch); + } else { + return INVALID_OPERATION; + } +} + +status_t DeviceHalLocal::releaseAudioPatch(audio_patch_handle_t patch) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { + return mDev->release_audio_patch(mDev, patch); + } else { + return INVALID_OPERATION; + } +} + +status_t DeviceHalLocal::getAudioPort(struct audio_port *port) { + return mDev->get_audio_port(mDev, port); +} + +status_t DeviceHalLocal::setAudioPortConfig(const struct audio_port_config *config) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) + return mDev->set_audio_port_config(mDev, config); + else + return INVALID_OPERATION; +} + +status_t DeviceHalLocal::getMicrophones( + std::vector<media::MicrophoneInfo> *microphones __unused) { + return INVALID_OPERATION; +} + +status_t DeviceHalLocal::dump(int fd) { + return mDev->dump(mDev, fd); +} + +void DeviceHalLocal::closeOutputStream(struct audio_stream_out *stream_out) { + mDev->close_output_stream(mDev, stream_out); +} + +void DeviceHalLocal::closeInputStream(struct audio_stream_in *stream_in) { + mDev->close_input_stream(mDev, stream_in); +} + +} // namespace android
diff --git a/media/libaudiohal/2.0/DeviceHalLocal.h b/media/libaudiohal/2.0/DeviceHalLocal.h new file mode 100644 index 0000000..aec201a --- /dev/null +++ b/media/libaudiohal/2.0/DeviceHalLocal.h
@@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_DEVICE_HAL_LOCAL_H +#define ANDROID_HARDWARE_DEVICE_HAL_LOCAL_H + +#include <hardware/audio.h> +#include <media/audiohal/DeviceHalInterface.h> + +namespace android { + +class DeviceHalLocal : public DeviceHalInterface +{ + public: + // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t. + virtual status_t getSupportedDevices(uint32_t *devices); + + // Check to see if the audio hardware interface has been initialized. + virtual status_t initCheck(); + + // Set the audio volume of a voice call. Range is between 0.0 and 1.0. + virtual status_t setVoiceVolume(float volume); + + // Set the audio volume for all audio activities other than voice call. + virtual status_t setMasterVolume(float volume); + + // Get the current master volume value for the HAL. + virtual status_t getMasterVolume(float *volume); + + // Called when the audio mode changes. + virtual status_t setMode(audio_mode_t mode); + + // Muting control. + virtual status_t setMicMute(bool state); + virtual status_t getMicMute(bool *state); + virtual status_t setMasterMute(bool state); + virtual status_t getMasterMute(bool *state); + + // Set global audio parameters. + virtual status_t setParameters(const String8& kvPairs); + + // Get global audio parameters. + virtual status_t getParameters(const String8& keys, String8 *values); + + // Returns audio input buffer size according to parameters passed. + virtual status_t getInputBufferSize(const struct audio_config *config, + size_t *size); + + // Creates and opens the audio hardware output stream. The stream is closed + // by releasing all references to the returned object. + virtual status_t openOutputStream( + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + const char *address, + sp<StreamOutHalInterface> *outStream); + + // Creates and opens the audio hardware input stream. The stream is closed + // by releasing all references to the returned object. + virtual status_t openInputStream( + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + audio_input_flags_t flags, + const char *address, + audio_source_t source, + sp<StreamInHalInterface> *inStream); + + // Returns whether createAudioPatch and releaseAudioPatch operations are supported. + virtual status_t supportsAudioPatches(bool *supportsPatches); + + // Creates an audio patch between several source and sink ports. + virtual status_t createAudioPatch( + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *patch); + + // Releases an audio patch. + virtual status_t releaseAudioPatch(audio_patch_handle_t patch); + + // Fills the list of supported attributes for a given audio port. + virtual status_t getAudioPort(struct audio_port *port); + + // Set audio port configuration. + virtual status_t setAudioPortConfig(const struct audio_port_config *config); + + // List microphones + virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + virtual status_t dump(int fd); + + void closeOutputStream(struct audio_stream_out *stream_out); + void closeInputStream(struct audio_stream_in *stream_in); + + private: + audio_hw_device_t *mDev; + + friend class DevicesFactoryHalLocal; + + // Can not be constructed directly by clients. + explicit DeviceHalLocal(audio_hw_device_t *dev); + + // The destructor automatically closes the device. + virtual ~DeviceHalLocal(); + + uint32_t version() const { return mDev->common.version; } +}; + +} // namespace android + +#endif // ANDROID_HARDWARE_DEVICE_HAL_LOCAL_H
diff --git a/media/libaudiohal/2.0/DevicesFactoryHalHidl.cpp b/media/libaudiohal/2.0/DevicesFactoryHalHidl.cpp new file mode 100644 index 0000000..5b33592 --- /dev/null +++ b/media/libaudiohal/2.0/DevicesFactoryHalHidl.cpp
@@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> + +#define LOG_TAG "DevicesFactoryHalHidl" +//#define LOG_NDEBUG 0 + +#include <android/hardware/audio/2.0/IDevice.h> +#include <media/audiohal/hidl/HalDeathHandler.h> +#include <utils/Log.h> + +#include "ConversionHelperHidl.h" +#include "DeviceHalHidl.h" +#include "DevicesFactoryHalHidl.h" + +using ::android::hardware::audio::V2_0::IDevice; +using ::android::hardware::audio::V2_0::Result; +using ::android::hardware::Return; + +namespace android { + +DevicesFactoryHalHidl::DevicesFactoryHalHidl() { + mDevicesFactory = IDevicesFactory::getService(); + if (mDevicesFactory != 0) { + // It is assumed that DevicesFactory is owned by AudioFlinger + // and thus have the same lifespan. + mDevicesFactory->linkToDeath(HalDeathHandler::getInstance(), 0 /*cookie*/); + } else { + ALOGE("Failed to obtain IDevicesFactory service, terminating process."); + exit(1); + } + // The MSD factory is optional + mDevicesFactoryMsd = IDevicesFactory::getService(AUDIO_HAL_SERVICE_NAME_MSD); + // TODO: Register death handler, and add 'restart' directive to audioserver.rc +} + +DevicesFactoryHalHidl::~DevicesFactoryHalHidl() { +} + +// static +status_t DevicesFactoryHalHidl::nameFromHal(const char *name, IDevicesFactory::Device *device) { + if (strcmp(name, AUDIO_HARDWARE_MODULE_ID_PRIMARY) == 0) { + *device = IDevicesFactory::Device::PRIMARY; + return OK; + } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_A2DP) == 0) { + *device = IDevicesFactory::Device::A2DP; + return OK; + } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_USB) == 0) { + *device = IDevicesFactory::Device::USB; + return OK; + } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX) == 0) { + *device = IDevicesFactory::Device::R_SUBMIX; + return OK; + } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_STUB) == 0) { + *device = IDevicesFactory::Device::STUB; + return OK; + } + ALOGE("Invalid device name %s", name); + return BAD_VALUE; +} + +status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) { + if (mDevicesFactory == 0) return NO_INIT; + IDevicesFactory::Device hidlDevice; + status_t status = nameFromHal(name, &hidlDevice); + if (status != OK) return status; + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mDevicesFactory->openDevice( + hidlDevice, + [&](Result r, const sp<IDevice>& result) { + retval = r; + if (retval == Result::OK) { + *device = new DeviceHalHidl(result); + } + }); + if (ret.isOk()) { + if (retval == Result::OK) return OK; + else if (retval == Result::INVALID_ARGUMENTS) return BAD_VALUE; + else return NO_INIT; + } + return FAILED_TRANSACTION; +} + +} // namespace android
diff --git a/media/libaudiohal/2.0/DevicesFactoryHalHidl.h b/media/libaudiohal/2.0/DevicesFactoryHalHidl.h new file mode 100644 index 0000000..0748849 --- /dev/null +++ b/media/libaudiohal/2.0/DevicesFactoryHalHidl.h
@@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_H +#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_H + +#include <android/hardware/audio/2.0/IDevicesFactory.h> +#include <media/audiohal/DevicesFactoryHalInterface.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +#include "DeviceHalHidl.h" + +using ::android::hardware::audio::V2_0::IDevicesFactory; + +namespace android { + +class DevicesFactoryHalHidl : public DevicesFactoryHalInterface +{ + public: + // Opens a device with the specified name. To close the device, it is + // necessary to release references to the returned object. + virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device); + + private: + friend class DevicesFactoryHalHybrid; + + sp<IDevicesFactory> mDevicesFactory; + sp<IDevicesFactory> mDevicesFactoryMsd; + + static status_t nameFromHal(const char *name, IDevicesFactory::Device *device); + + // Can not be constructed directly by clients. + DevicesFactoryHalHidl(); + + virtual ~DevicesFactoryHalHidl(); +}; + +} // namespace android + +#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_H
diff --git a/media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp new file mode 100644 index 0000000..1c4be74 --- /dev/null +++ b/media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp
@@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DevicesFactoryHalHybrid" +//#define LOG_NDEBUG 0 + +#include "DevicesFactoryHalHybrid.h" +#include "DevicesFactoryHalLocal.h" +#include "DevicesFactoryHalHidl.h" + +namespace android { + +DevicesFactoryHalHybrid::DevicesFactoryHalHybrid() + : mLocalFactory(new DevicesFactoryHalLocal()), + mHidlFactory(new DevicesFactoryHalHidl()) { +} + +DevicesFactoryHalHybrid::~DevicesFactoryHalHybrid() { +} + +status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) { + if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 && + strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) { + return mHidlFactory->openDevice(name, device); + } + return mLocalFactory->openDevice(name, device); +} + +} // namespace android
diff --git a/media/libaudiohal/DevicesFactoryHalHybrid.h b/media/libaudiohal/2.0/DevicesFactoryHalHybrid.h similarity index 100% rename from media/libaudiohal/DevicesFactoryHalHybrid.h rename to media/libaudiohal/2.0/DevicesFactoryHalHybrid.h
diff --git a/media/libaudiohal/DevicesFactoryHalLocal.cpp b/media/libaudiohal/2.0/DevicesFactoryHalLocal.cpp similarity index 100% rename from media/libaudiohal/DevicesFactoryHalLocal.cpp rename to media/libaudiohal/2.0/DevicesFactoryHalLocal.cpp
diff --git a/media/libaudiohal/DevicesFactoryHalLocal.h b/media/libaudiohal/2.0/DevicesFactoryHalLocal.h similarity index 100% rename from media/libaudiohal/DevicesFactoryHalLocal.h rename to media/libaudiohal/2.0/DevicesFactoryHalLocal.h
diff --git a/media/libaudiohal/2.0/EffectBufferHalHidl.cpp b/media/libaudiohal/2.0/EffectBufferHalHidl.cpp new file mode 100644 index 0000000..226a500 --- /dev/null +++ b/media/libaudiohal/2.0/EffectBufferHalHidl.cpp
@@ -0,0 +1,144 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <atomic> + +#define LOG_TAG "EffectBufferHalHidl" +//#define LOG_NDEBUG 0 + +#include <android/hidl/allocator/1.0/IAllocator.h> +#include <hidlmemory/mapping.h> +#include <utils/Log.h> + +#include "ConversionHelperHidl.h" +#include "EffectBufferHalHidl.h" + +using ::android::hardware::Return; +using ::android::hidl::allocator::V1_0::IAllocator; + +namespace android { + +// static +uint64_t EffectBufferHalHidl::makeUniqueId() { + static std::atomic<uint64_t> counter{1}; + return counter++; +} + +status_t EffectBufferHalHidl::allocate( + size_t size, sp<EffectBufferHalInterface>* buffer) { + return mirror(nullptr, size, buffer); +} + +status_t EffectBufferHalHidl::mirror( + void* external, size_t size, sp<EffectBufferHalInterface>* buffer) { + sp<EffectBufferHalInterface> tempBuffer = new EffectBufferHalHidl(size); + status_t result = static_cast<EffectBufferHalHidl*>(tempBuffer.get())->init(); + if (result == OK) { + tempBuffer->setExternalData(external); + *buffer = tempBuffer; + } + return result; +} + +EffectBufferHalHidl::EffectBufferHalHidl(size_t size) + : mBufferSize(size), mFrameCountChanged(false), + mExternalData(nullptr), mAudioBuffer{0, {nullptr}} { + mHidlBuffer.id = makeUniqueId(); + mHidlBuffer.frameCount = 0; +} + +EffectBufferHalHidl::~EffectBufferHalHidl() { +} + +status_t EffectBufferHalHidl::init() { + sp<IAllocator> ashmem = IAllocator::getService("ashmem"); + if (ashmem == 0) { + ALOGE("Failed to retrieve ashmem allocator service"); + return NO_INIT; + } + status_t retval = NO_MEMORY; + Return<void> result = ashmem->allocate( + mBufferSize, + [&](bool success, const hidl_memory& memory) { + if (success) { + mHidlBuffer.data = memory; + retval = OK; + } + }); + if (result.isOk() && retval == OK) { + mMemory = hardware::mapMemory(mHidlBuffer.data); + if (mMemory != 0) { + mMemory->update(); + mAudioBuffer.raw = static_cast<void*>(mMemory->getPointer()); + memset(mAudioBuffer.raw, 0, mMemory->getSize()); + mMemory->commit(); + } else { + ALOGE("Failed to map allocated ashmem"); + retval = NO_MEMORY; + } + } else { + ALOGE("Failed to allocate %d bytes from ashmem", (int)mBufferSize); + } + return result.isOk() ? retval : FAILED_TRANSACTION; +} + +audio_buffer_t* EffectBufferHalHidl::audioBuffer() { + return &mAudioBuffer; +} + +void* EffectBufferHalHidl::externalData() const { + return mExternalData; +} + +void EffectBufferHalHidl::setFrameCount(size_t frameCount) { + mHidlBuffer.frameCount = frameCount; + mAudioBuffer.frameCount = frameCount; + mFrameCountChanged = true; +} + +bool EffectBufferHalHidl::checkFrameCountChange() { + bool result = mFrameCountChanged; + mFrameCountChanged = false; + return result; +} + +void EffectBufferHalHidl::setExternalData(void* external) { + mExternalData = external; +} + +void EffectBufferHalHidl::update() { + update(mBufferSize); +} + +void EffectBufferHalHidl::commit() { + commit(mBufferSize); +} + +void EffectBufferHalHidl::update(size_t size) { + if (mExternalData == nullptr) return; + mMemory->update(); + if (size > mBufferSize) size = mBufferSize; + memcpy(mAudioBuffer.raw, mExternalData, size); + mMemory->commit(); +} + +void EffectBufferHalHidl::commit(size_t size) { + if (mExternalData == nullptr) return; + if (size > mBufferSize) size = mBufferSize; + memcpy(mExternalData, mAudioBuffer.raw, size); +} + +} // namespace android
diff --git a/media/libaudiohal/2.0/EffectBufferHalHidl.h b/media/libaudiohal/2.0/EffectBufferHalHidl.h new file mode 100644 index 0000000..31e0087 --- /dev/null +++ b/media/libaudiohal/2.0/EffectBufferHalHidl.h
@@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H +#define ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H + +#include <android/hardware/audio/effect/2.0/types.h> +#include <android/hidl/memory/1.0/IMemory.h> +#include <hidl/HidlSupport.h> +#include <media/audiohal/EffectBufferHalInterface.h> +#include <system/audio_effect.h> + +using android::hardware::audio::effect::V2_0::AudioBuffer; +using android::hardware::hidl_memory; +using android::hidl::memory::V1_0::IMemory; + +namespace android { + +class EffectBufferHalHidl : public EffectBufferHalInterface +{ + public: + static status_t allocate(size_t size, sp<EffectBufferHalInterface>* buffer); + static status_t mirror(void* external, size_t size, sp<EffectBufferHalInterface>* buffer); + + virtual audio_buffer_t* audioBuffer(); + virtual void* externalData() const; + + virtual size_t getSize() const override { return mBufferSize; } + + virtual void setExternalData(void* external); + virtual void setFrameCount(size_t frameCount); + virtual bool checkFrameCountChange(); + + virtual void update(); + virtual void commit(); + virtual void update(size_t size); + virtual void commit(size_t size); + + const AudioBuffer& hidlBuffer() const { return mHidlBuffer; } + + private: + friend class EffectBufferHalInterface; + + static uint64_t makeUniqueId(); + + const size_t mBufferSize; + bool mFrameCountChanged; + void* mExternalData; + AudioBuffer mHidlBuffer; + sp<IMemory> mMemory; + audio_buffer_t mAudioBuffer; + + // Can not be constructed directly by clients. + explicit EffectBufferHalHidl(size_t size); + + virtual ~EffectBufferHalHidl(); + + status_t init(); +}; + +} // namespace android + +#endif // ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H
diff --git a/media/libaudiohal/2.0/EffectHalHidl.cpp b/media/libaudiohal/2.0/EffectHalHidl.cpp new file mode 100644 index 0000000..4fb032c --- /dev/null +++ b/media/libaudiohal/2.0/EffectHalHidl.cpp
@@ -0,0 +1,338 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "EffectHalHidl" +//#define LOG_NDEBUG 0 + +#include <hwbinder/IPCThreadState.h> +#include <media/EffectsFactoryApi.h> +#include <utils/Log.h> + +#include "ConversionHelperHidl.h" +#include "EffectBufferHalHidl.h" +#include "EffectHalHidl.h" +#include "HidlUtils.h" + +using ::android::hardware::audio::effect::V2_0::AudioBuffer; +using ::android::hardware::audio::effect::V2_0::EffectBufferAccess; +using ::android::hardware::audio::effect::V2_0::EffectConfigParameters; +using ::android::hardware::audio::effect::V2_0::MessageQueueFlagBits; +using ::android::hardware::audio::effect::V2_0::Result; +using ::android::hardware::audio::common::V2_0::HidlUtils; +using ::android::hardware::audio::common::V2_0::AudioChannelMask; +using ::android::hardware::audio::common::V2_0::AudioFormat; +using ::android::hardware::hidl_vec; +using ::android::hardware::MQDescriptorSync; +using ::android::hardware::Return; + +namespace android { + +EffectHalHidl::EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId) + : mEffect(effect), mEffectId(effectId), mBuffersChanged(true), mEfGroup(nullptr) { +} + +EffectHalHidl::~EffectHalHidl() { + if (mEffect != 0) { + close(); + mEffect.clear(); + hardware::IPCThreadState::self()->flushCommands(); + } + if (mEfGroup) { + EventFlag::deleteEventFlag(&mEfGroup); + } +} + +// static +void EffectHalHidl::effectDescriptorToHal( + const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor) { + HidlUtils::uuidToHal(descriptor.type, &halDescriptor->type); + HidlUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid); + halDescriptor->flags = static_cast<uint32_t>(descriptor.flags); + halDescriptor->cpuLoad = descriptor.cpuLoad; + halDescriptor->memoryUsage = descriptor.memoryUsage; + memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size()); + memcpy(halDescriptor->implementor, + descriptor.implementor.data(), descriptor.implementor.size()); +} + +// TODO(mnaganov): These buffer conversion functions should be shared with Effect wrapper +// via HidlUtils. Move them there when hardware/interfaces will get un-frozen again. + +// static +void EffectHalHidl::effectBufferConfigFromHal( + const buffer_config_t& halConfig, EffectBufferConfig* config) { + config->samplingRateHz = halConfig.samplingRate; + config->channels = AudioChannelMask(halConfig.channels); + config->format = AudioFormat(halConfig.format); + config->accessMode = EffectBufferAccess(halConfig.accessMode); + config->mask = EffectConfigParameters(halConfig.mask); +} + +// static +void EffectHalHidl::effectBufferConfigToHal( + const EffectBufferConfig& config, buffer_config_t* halConfig) { + halConfig->buffer.frameCount = 0; + halConfig->buffer.raw = NULL; + halConfig->samplingRate = config.samplingRateHz; + halConfig->channels = static_cast<uint32_t>(config.channels); + halConfig->bufferProvider.cookie = NULL; + halConfig->bufferProvider.getBuffer = NULL; + halConfig->bufferProvider.releaseBuffer = NULL; + halConfig->format = static_cast<uint8_t>(config.format); + halConfig->accessMode = static_cast<uint8_t>(config.accessMode); + halConfig->mask = static_cast<uint8_t>(config.mask); +} + +// static +void EffectHalHidl::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) { + effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg); + effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg); +} + +// static +void EffectHalHidl::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) { + effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg); + effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg); +} + +// static +status_t EffectHalHidl::analyzeResult(const Result& result) { + switch (result) { + case Result::OK: return OK; + case Result::INVALID_ARGUMENTS: return BAD_VALUE; + case Result::INVALID_STATE: return NOT_ENOUGH_DATA; + case Result::NOT_INITIALIZED: return NO_INIT; + case Result::NOT_SUPPORTED: return INVALID_OPERATION; + case Result::RESULT_TOO_BIG: return NO_MEMORY; + default: return NO_INIT; + } +} + +status_t EffectHalHidl::setInBuffer(const sp<EffectBufferHalInterface>& buffer) { + if (!mBuffersChanged) { + if (buffer.get() == nullptr || mInBuffer.get() == nullptr) { + mBuffersChanged = buffer.get() != mInBuffer.get(); + } else { + mBuffersChanged = buffer->audioBuffer() != mInBuffer->audioBuffer(); + } + } + mInBuffer = buffer; + return OK; +} + +status_t EffectHalHidl::setOutBuffer(const sp<EffectBufferHalInterface>& buffer) { + if (!mBuffersChanged) { + if (buffer.get() == nullptr || mOutBuffer.get() == nullptr) { + mBuffersChanged = buffer.get() != mOutBuffer.get(); + } else { + mBuffersChanged = buffer->audioBuffer() != mOutBuffer->audioBuffer(); + } + } + mOutBuffer = buffer; + return OK; +} + +status_t EffectHalHidl::process() { + return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS)); +} + +status_t EffectHalHidl::processReverse() { + return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS_REVERSE)); +} + +status_t EffectHalHidl::prepareForProcessing() { + std::unique_ptr<StatusMQ> tempStatusMQ; + Result retval; + Return<void> ret = mEffect->prepareForProcessing( + [&](Result r, const MQDescriptorSync<Result>& statusMQ) { + retval = r; + if (retval == Result::OK) { + tempStatusMQ.reset(new StatusMQ(statusMQ)); + if (tempStatusMQ->isValid() && tempStatusMQ->getEventFlagWord()) { + EventFlag::createEventFlag(tempStatusMQ->getEventFlagWord(), &mEfGroup); + } + } + }); + if (!ret.isOk() || retval != Result::OK) { + return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION; + } + if (!tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) { + ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for effects"); + ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), + "Status message queue for effects is invalid"); + ALOGE_IF(!mEfGroup, "Event flag creation for effects failed"); + return NO_INIT; + } + mStatusMQ = std::move(tempStatusMQ); + return OK; +} + +bool EffectHalHidl::needToResetBuffers() { + if (mBuffersChanged) return true; + bool inBufferFrameCountUpdated = mInBuffer->checkFrameCountChange(); + bool outBufferFrameCountUpdated = mOutBuffer->checkFrameCountChange(); + return inBufferFrameCountUpdated || outBufferFrameCountUpdated; +} + +status_t EffectHalHidl::processImpl(uint32_t mqFlag) { + if (mEffect == 0 || mInBuffer == 0 || mOutBuffer == 0) return NO_INIT; + status_t status; + if (!mStatusMQ && (status = prepareForProcessing()) != OK) { + return status; + } + if (needToResetBuffers() && (status = setProcessBuffers()) != OK) { + return status; + } + // The data is already in the buffers, just need to flush it and wake up the server side. + std::atomic_thread_fence(std::memory_order_release); + mEfGroup->wake(mqFlag); + uint32_t efState = 0; +retry: + status_t ret = mEfGroup->wait( + static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING), &efState); + if (efState & static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING)) { + Result retval = Result::NOT_INITIALIZED; + mStatusMQ->read(&retval); + if (retval == Result::OK || retval == Result::INVALID_STATE) { + // Sync back the changed contents of the buffer. + std::atomic_thread_fence(std::memory_order_acquire); + } + return analyzeResult(retval); + } + if (ret == -EAGAIN || ret == -EINTR) { + // Spurious wakeup. This normally retries no more than once. + goto retry; + } + return ret; +} + +status_t EffectHalHidl::setProcessBuffers() { + Return<Result> ret = mEffect->setProcessBuffers( + static_cast<EffectBufferHalHidl*>(mInBuffer.get())->hidlBuffer(), + static_cast<EffectBufferHalHidl*>(mOutBuffer.get())->hidlBuffer()); + if (ret.isOk() && ret == Result::OK) { + mBuffersChanged = false; + return OK; + } + return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION; +} + +status_t EffectHalHidl::command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, + uint32_t *replySize, void *pReplyData) { + if (mEffect == 0) return NO_INIT; + + // Special cases. + if (cmdCode == EFFECT_CMD_SET_CONFIG || cmdCode == EFFECT_CMD_SET_CONFIG_REVERSE) { + return setConfigImpl(cmdCode, cmdSize, pCmdData, replySize, pReplyData); + } else if (cmdCode == EFFECT_CMD_GET_CONFIG || cmdCode == EFFECT_CMD_GET_CONFIG_REVERSE) { + return getConfigImpl(cmdCode, replySize, pReplyData); + } + + // Common case. + hidl_vec<uint8_t> hidlData; + if (pCmdData != nullptr && cmdSize > 0) { + hidlData.setToExternal(reinterpret_cast<uint8_t*>(pCmdData), cmdSize); + } + status_t status; + uint32_t replySizeStub = 0; + if (replySize == nullptr || pReplyData == nullptr) replySize = &replySizeStub; + Return<void> ret = mEffect->command(cmdCode, hidlData, *replySize, + [&](int32_t s, const hidl_vec<uint8_t>& result) { + status = s; + if (status == 0) { + if (*replySize > result.size()) *replySize = result.size(); + if (pReplyData != nullptr && *replySize > 0) { + memcpy(pReplyData, &result[0], *replySize); + } + } + }); + return ret.isOk() ? status : FAILED_TRANSACTION; +} + +status_t EffectHalHidl::getDescriptor(effect_descriptor_t *pDescriptor) { + if (mEffect == 0) return NO_INIT; + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mEffect->getDescriptor( + [&](Result r, const EffectDescriptor& result) { + retval = r; + if (retval == Result::OK) { + effectDescriptorToHal(result, pDescriptor); + } + }); + return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION; +} + +status_t EffectHalHidl::close() { + if (mEffect == 0) return NO_INIT; + Return<Result> ret = mEffect->close(); + return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION; +} + +status_t EffectHalHidl::getConfigImpl( + uint32_t cmdCode, uint32_t *replySize, void *pReplyData) { + if (replySize == NULL || *replySize != sizeof(effect_config_t) || pReplyData == NULL) { + return BAD_VALUE; + } + status_t result = FAILED_TRANSACTION; + Return<void> ret; + if (cmdCode == EFFECT_CMD_GET_CONFIG) { + ret = mEffect->getConfig([&] (Result r, const EffectConfig &hidlConfig) { + result = analyzeResult(r); + if (r == Result::OK) { + effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData)); + } + }); + } else { + ret = mEffect->getConfigReverse([&] (Result r, const EffectConfig &hidlConfig) { + result = analyzeResult(r); + if (r == Result::OK) { + effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData)); + } + }); + } + if (!ret.isOk()) { + result = FAILED_TRANSACTION; + } + return result; +} + +status_t EffectHalHidl::setConfigImpl( + uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) { + if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) || + replySize == NULL || *replySize != sizeof(int32_t) || pReplyData == NULL) { + return BAD_VALUE; + } + const effect_config_t *halConfig = static_cast<effect_config_t*>(pCmdData); + if (halConfig->inputCfg.bufferProvider.getBuffer != NULL || + halConfig->inputCfg.bufferProvider.releaseBuffer != NULL || + halConfig->outputCfg.bufferProvider.getBuffer != NULL || + halConfig->outputCfg.bufferProvider.releaseBuffer != NULL) { + ALOGE("Buffer provider callbacks are not supported"); + } + EffectConfig hidlConfig; + effectConfigFromHal(*halConfig, &hidlConfig); + Return<Result> ret = cmdCode == EFFECT_CMD_SET_CONFIG ? + mEffect->setConfig(hidlConfig, nullptr, nullptr) : + mEffect->setConfigReverse(hidlConfig, nullptr, nullptr); + status_t result = FAILED_TRANSACTION; + if (ret.isOk()) { + result = analyzeResult(ret); + *static_cast<int32_t*>(pReplyData) = result; + } + return result; +} + +} // namespace android
diff --git a/media/libaudiohal/EffectHalHidl.h b/media/libaudiohal/2.0/EffectHalHidl.h similarity index 100% rename from media/libaudiohal/EffectHalHidl.h rename to media/libaudiohal/2.0/EffectHalHidl.h
diff --git a/media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp b/media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp new file mode 100644 index 0000000..0d40e6d --- /dev/null +++ b/media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp
@@ -0,0 +1,150 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "EffectsFactoryHalHidl" +//#define LOG_NDEBUG 0 + +#include <cutils/native_handle.h> + +#include "ConversionHelperHidl.h" +#include "EffectBufferHalHidl.h" +#include "EffectHalHidl.h" +#include "EffectsFactoryHalHidl.h" +#include "HidlUtils.h" + +using ::android::hardware::audio::common::V2_0::HidlUtils; +using ::android::hardware::audio::common::V2_0::Uuid; +using ::android::hardware::audio::effect::V2_0::IEffect; +using ::android::hardware::audio::effect::V2_0::Result; +using ::android::hardware::Return; + +namespace android { + +EffectsFactoryHalHidl::EffectsFactoryHalHidl() : ConversionHelperHidl("EffectsFactory") { + mEffectsFactory = IEffectsFactory::getService(); + if (mEffectsFactory == 0) { + ALOGE("Failed to obtain IEffectsFactory service, terminating process."); + exit(1); + } +} + +EffectsFactoryHalHidl::~EffectsFactoryHalHidl() { +} + +status_t EffectsFactoryHalHidl::queryAllDescriptors() { + if (mEffectsFactory == 0) return NO_INIT; + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mEffectsFactory->getAllDescriptors( + [&](Result r, const hidl_vec<EffectDescriptor>& result) { + retval = r; + if (retval == Result::OK) { + mLastDescriptors = result; + } + }); + if (ret.isOk()) { + return retval == Result::OK ? OK : NO_INIT; + } + mLastDescriptors.resize(0); + return processReturn(__FUNCTION__, ret); +} + +status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) { + status_t queryResult = queryAllDescriptors(); + if (queryResult == OK) { + *pNumEffects = mLastDescriptors.size(); + } + return queryResult; +} + +status_t EffectsFactoryHalHidl::getDescriptor( + uint32_t index, effect_descriptor_t *pDescriptor) { + // TODO: We need somehow to track the changes on the server side + // or figure out how to convert everybody to query all the descriptors at once. + // TODO: check for nullptr + if (mLastDescriptors.size() == 0) { + status_t queryResult = queryAllDescriptors(); + if (queryResult != OK) return queryResult; + } + if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND; + EffectHalHidl::effectDescriptorToHal(mLastDescriptors[index], pDescriptor); + return OK; +} + +status_t EffectsFactoryHalHidl::getDescriptor( + const effect_uuid_t *pEffectUuid, effect_descriptor_t *pDescriptor) { + // TODO: check for nullptr + if (mEffectsFactory == 0) return NO_INIT; + Uuid hidlUuid; + HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid); + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mEffectsFactory->getDescriptor(hidlUuid, + [&](Result r, const EffectDescriptor& result) { + retval = r; + if (retval == Result::OK) { + EffectHalHidl::effectDescriptorToHal(result, pDescriptor); + } + }); + if (ret.isOk()) { + if (retval == Result::OK) return OK; + else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND; + else return NO_INIT; + } + return processReturn(__FUNCTION__, ret); +} + +status_t EffectsFactoryHalHidl::createEffect( + const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t ioId, + sp<EffectHalInterface> *effect) { + if (mEffectsFactory == 0) return NO_INIT; + Uuid hidlUuid; + HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid); + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mEffectsFactory->createEffect( + hidlUuid, sessionId, ioId, + [&](Result r, const sp<IEffect>& result, uint64_t effectId) { + retval = r; + if (retval == Result::OK) { + *effect = new EffectHalHidl(result, effectId); + } + }); + if (ret.isOk()) { + if (retval == Result::OK) return OK; + else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND; + else return NO_INIT; + } + return processReturn(__FUNCTION__, ret); +} + +status_t EffectsFactoryHalHidl::dumpEffects(int fd) { + if (mEffectsFactory == 0) return NO_INIT; + native_handle_t* hidlHandle = native_handle_create(1, 0); + hidlHandle->data[0] = fd; + Return<void> ret = mEffectsFactory->debugDump(hidlHandle); + native_handle_delete(hidlHandle); + return processReturn(__FUNCTION__, ret); +} + +status_t EffectsFactoryHalHidl::allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) { + return EffectBufferHalHidl::allocate(size, buffer); +} + +status_t EffectsFactoryHalHidl::mirrorBuffer(void* external, size_t size, + sp<EffectBufferHalInterface>* buffer) { + return EffectBufferHalHidl::mirror(external, size, buffer); +} + + +} // namespace android
diff --git a/media/libaudiohal/2.0/EffectsFactoryHalHidl.h b/media/libaudiohal/2.0/EffectsFactoryHalHidl.h new file mode 100644 index 0000000..82b5481 --- /dev/null +++ b/media/libaudiohal/2.0/EffectsFactoryHalHidl.h
@@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H +#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H + +#include <android/hardware/audio/effect/2.0/IEffectsFactory.h> +#include <android/hardware/audio/effect/2.0/types.h> +#include <media/audiohal/EffectsFactoryHalInterface.h> + +#include "ConversionHelperHidl.h" + +namespace android { + +using ::android::hardware::audio::effect::V2_0::EffectDescriptor; +using ::android::hardware::audio::effect::V2_0::IEffectsFactory; +using ::android::hardware::hidl_vec; + +class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public ConversionHelperHidl +{ + public: + // Returns the number of different effects in all loaded libraries. + virtual status_t queryNumberEffects(uint32_t *pNumEffects); + + // Returns a descriptor of the next available effect. + virtual status_t getDescriptor(uint32_t index, + effect_descriptor_t *pDescriptor); + + virtual status_t getDescriptor(const effect_uuid_t *pEffectUuid, + effect_descriptor_t *pDescriptor); + + // Creates an effect engine of the specified type. + // To release the effect engine, it is necessary to release references + // to the returned effect object. + virtual status_t createEffect(const effect_uuid_t *pEffectUuid, + int32_t sessionId, int32_t ioId, + sp<EffectHalInterface> *effect); + + virtual status_t dumpEffects(int fd); + + status_t allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) override; + status_t mirrorBuffer(void* external, size_t size, + sp<EffectBufferHalInterface>* buffer) override; + + private: + friend class EffectsFactoryHalInterface; + + sp<IEffectsFactory> mEffectsFactory; + hidl_vec<EffectDescriptor> mLastDescriptors; + + // Can not be constructed directly by clients. + EffectsFactoryHalHidl(); + virtual ~EffectsFactoryHalHidl(); + + status_t queryAllDescriptors(); +}; + +} // namespace android + +#endif // ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
diff --git a/media/libaudiohal/2.0/StreamHalHidl.cpp b/media/libaudiohal/2.0/StreamHalHidl.cpp new file mode 100644 index 0000000..9869cd2 --- /dev/null +++ b/media/libaudiohal/2.0/StreamHalHidl.cpp
@@ -0,0 +1,768 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "StreamHalHidl" +//#define LOG_NDEBUG 0 + +#include <android/hardware/audio/2.0/IStreamOutCallback.h> +#include <hwbinder/IPCThreadState.h> +#include <mediautils/SchedulingPolicyService.h> +#include <utils/Log.h> + +#include "DeviceHalHidl.h" +#include "EffectHalHidl.h" +#include "StreamHalHidl.h" + +using ::android::hardware::audio::common::V2_0::AudioChannelMask; +using ::android::hardware::audio::common::V2_0::AudioFormat; +using ::android::hardware::audio::common::V2_0::ThreadInfo; +using ::android::hardware::audio::V2_0::AudioDrain; +using ::android::hardware::audio::V2_0::IStreamOutCallback; +using ::android::hardware::audio::V2_0::MessageQueueFlagBits; +using ::android::hardware::audio::V2_0::MmapBufferInfo; +using ::android::hardware::audio::V2_0::MmapPosition; +using ::android::hardware::audio::V2_0::ParameterValue; +using ::android::hardware::audio::V2_0::Result; +using ::android::hardware::audio::V2_0::TimeSpec; +using ::android::hardware::MQDescriptorSync; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ReadCommand = ::android::hardware::audio::V2_0::IStreamIn::ReadCommand; + +namespace android { + +StreamHalHidl::StreamHalHidl(IStream *stream) + : ConversionHelperHidl("Stream"), + mStream(stream), + mHalThreadPriority(HAL_THREAD_PRIORITY_DEFAULT), + mCachedBufferSize(0){ + + // Instrument audio signal power logging. + // Note: This assumes channel mask, format, and sample rate do not change after creation. + if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) { + // Obtain audio properties (see StreamHalHidl::getAudioProperties() below). + Return<void> ret = mStream->getAudioProperties( + [&](uint32_t sr, AudioChannelMask m, AudioFormat f) { + mStreamPowerLog.init(sr, + static_cast<audio_channel_mask_t>(m), + static_cast<audio_format_t>(f)); + }); + } +} + +StreamHalHidl::~StreamHalHidl() { + mStream = nullptr; +} + +status_t StreamHalHidl::getSampleRate(uint32_t *rate) { + if (!mStream) return NO_INIT; + return processReturn("getSampleRate", mStream->getSampleRate(), rate); +} + +status_t StreamHalHidl::getBufferSize(size_t *size) { + if (!mStream) return NO_INIT; + status_t status = processReturn("getBufferSize", mStream->getBufferSize(), size); + if (status == OK) { + mCachedBufferSize = *size; + } + return status; +} + +status_t StreamHalHidl::getChannelMask(audio_channel_mask_t *mask) { + if (!mStream) return NO_INIT; + return processReturn("getChannelMask", mStream->getChannelMask(), mask); +} + +status_t StreamHalHidl::getFormat(audio_format_t *format) { + if (!mStream) return NO_INIT; + return processReturn("getFormat", mStream->getFormat(), format); +} + +status_t StreamHalHidl::getAudioProperties( + uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) { + if (!mStream) return NO_INIT; + Return<void> ret = mStream->getAudioProperties( + [&](uint32_t sr, AudioChannelMask m, AudioFormat f) { + *sampleRate = sr; + *mask = static_cast<audio_channel_mask_t>(m); + *format = static_cast<audio_format_t>(f); + }); + return processReturn("getAudioProperties", ret); +} + +status_t StreamHalHidl::setParameters(const String8& kvPairs) { + if (!mStream) return NO_INIT; + hidl_vec<ParameterValue> hidlParams; + status_t status = parametersFromHal(kvPairs, &hidlParams); + if (status != OK) return status; + return processReturn("setParameters", mStream->setParameters(hidlParams)); +} + +status_t StreamHalHidl::getParameters(const String8& keys, String8 *values) { + values->clear(); + if (!mStream) return NO_INIT; + hidl_vec<hidl_string> hidlKeys; + status_t status = keysFromHal(keys, &hidlKeys); + if (status != OK) return status; + Result retval; + Return<void> ret = mStream->getParameters( + hidlKeys, + [&](Result r, const hidl_vec<ParameterValue>& parameters) { + retval = r; + if (retval == Result::OK) { + parametersToHal(parameters, values); + } + }); + return processReturn("getParameters", ret, retval); +} + +status_t StreamHalHidl::addEffect(sp<EffectHalInterface> effect) { + if (!mStream) return NO_INIT; + return processReturn("addEffect", mStream->addEffect( + static_cast<EffectHalHidl*>(effect.get())->effectId())); +} + +status_t StreamHalHidl::removeEffect(sp<EffectHalInterface> effect) { + if (!mStream) return NO_INIT; + return processReturn("removeEffect", mStream->removeEffect( + static_cast<EffectHalHidl*>(effect.get())->effectId())); +} + +status_t StreamHalHidl::standby() { + if (!mStream) return NO_INIT; + return processReturn("standby", mStream->standby()); +} + +status_t StreamHalHidl::dump(int fd) { + if (!mStream) return NO_INIT; + native_handle_t* hidlHandle = native_handle_create(1, 0); + hidlHandle->data[0] = fd; + Return<void> ret = mStream->debugDump(hidlHandle); + native_handle_delete(hidlHandle); + mStreamPowerLog.dump(fd); + return processReturn("dump", ret); +} + +status_t StreamHalHidl::start() { + if (!mStream) return NO_INIT; + return processReturn("start", mStream->start()); +} + +status_t StreamHalHidl::stop() { + if (!mStream) return NO_INIT; + return processReturn("stop", mStream->stop()); +} + +status_t StreamHalHidl::createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info) { + Result retval; + Return<void> ret = mStream->createMmapBuffer( + minSizeFrames, + [&](Result r, const MmapBufferInfo& hidlInfo) { + retval = r; + if (retval == Result::OK) { + const native_handle *handle = hidlInfo.sharedMemory.handle(); + if (handle->numFds > 0) { + info->shared_memory_fd = handle->data[0]; + info->buffer_size_frames = hidlInfo.bufferSizeFrames; + info->burst_size_frames = hidlInfo.burstSizeFrames; + // info->shared_memory_address is not needed in HIDL context + info->shared_memory_address = NULL; + } else { + retval = Result::NOT_INITIALIZED; + } + } + }); + return processReturn("createMmapBuffer", ret, retval); +} + +status_t StreamHalHidl::getMmapPosition(struct audio_mmap_position *position) { + Result retval; + Return<void> ret = mStream->getMmapPosition( + [&](Result r, const MmapPosition& hidlPosition) { + retval = r; + if (retval == Result::OK) { + position->time_nanoseconds = hidlPosition.timeNanoseconds; + position->position_frames = hidlPosition.positionFrames; + } + }); + return processReturn("getMmapPosition", ret, retval); +} + +status_t StreamHalHidl::setHalThreadPriority(int priority) { + mHalThreadPriority = priority; + return OK; +} + +status_t StreamHalHidl::getCachedBufferSize(size_t *size) { + if (mCachedBufferSize != 0) { + *size = mCachedBufferSize; + return OK; + } + return getBufferSize(size); +} + +bool StreamHalHidl::requestHalThreadPriority(pid_t threadPid, pid_t threadId) { + if (mHalThreadPriority == HAL_THREAD_PRIORITY_DEFAULT) { + return true; + } + int err = requestPriority( + threadPid, threadId, + mHalThreadPriority, false /*isForApp*/, true /*asynchronous*/); + ALOGE_IF(err, "failed to set priority %d for pid %d tid %d; error %d", + mHalThreadPriority, threadPid, threadId, err); + // Audio will still work, but latency will be higher and sometimes unacceptable. + return err == 0; +} + +namespace { + +/* Notes on callback ownership. + +This is how (Hw)Binder ownership model looks like. The server implementation +is owned by Binder framework (via sp<>). Proxies are owned by clients. +When the last proxy disappears, Binder framework releases the server impl. + +Thus, it is not needed to keep any references to StreamOutCallback (this is +the server impl) -- it will live as long as HAL server holds a strong ref to +IStreamOutCallback proxy. We clear that reference by calling 'clearCallback' +from the destructor of StreamOutHalHidl. + +The callback only keeps a weak reference to the stream. The stream is owned +by AudioFlinger. + +*/ + +struct StreamOutCallback : public IStreamOutCallback { + StreamOutCallback(const wp<StreamOutHalHidl>& stream) : mStream(stream) {} + + // IStreamOutCallback implementation + Return<void> onWriteReady() override { + sp<StreamOutHalHidl> stream = mStream.promote(); + if (stream != 0) { + stream->onWriteReady(); + } + return Void(); + } + + Return<void> onDrainReady() override { + sp<StreamOutHalHidl> stream = mStream.promote(); + if (stream != 0) { + stream->onDrainReady(); + } + return Void(); + } + + Return<void> onError() override { + sp<StreamOutHalHidl> stream = mStream.promote(); + if (stream != 0) { + stream->onError(); + } + return Void(); + } + + private: + wp<StreamOutHalHidl> mStream; +}; + +} // namespace + +StreamOutHalHidl::StreamOutHalHidl(const sp<IStreamOut>& stream) + : StreamHalHidl(stream.get()), mStream(stream), mWriterClient(0), mEfGroup(nullptr) { +} + +StreamOutHalHidl::~StreamOutHalHidl() { + if (mStream != 0) { + if (mCallback.unsafe_get()) { + processReturn("clearCallback", mStream->clearCallback()); + } + processReturn("close", mStream->close()); + mStream.clear(); + } + mCallback.clear(); + hardware::IPCThreadState::self()->flushCommands(); + if (mEfGroup) { + EventFlag::deleteEventFlag(&mEfGroup); + } +} + +status_t StreamOutHalHidl::getFrameSize(size_t *size) { + if (mStream == 0) return NO_INIT; + return processReturn("getFrameSize", mStream->getFrameSize(), size); +} + +status_t StreamOutHalHidl::getLatency(uint32_t *latency) { + if (mStream == 0) return NO_INIT; + if (mWriterClient == gettid() && mCommandMQ) { + return callWriterThread( + WriteCommand::GET_LATENCY, "getLatency", nullptr, 0, + [&](const WriteStatus& writeStatus) { + *latency = writeStatus.reply.latencyMs; + }); + } else { + return processReturn("getLatency", mStream->getLatency(), latency); + } +} + +status_t StreamOutHalHidl::setVolume(float left, float right) { + if (mStream == 0) return NO_INIT; + return processReturn("setVolume", mStream->setVolume(left, right)); +} + +status_t StreamOutHalHidl::write(const void *buffer, size_t bytes, size_t *written) { + if (mStream == 0) return NO_INIT; + *written = 0; + + if (bytes == 0 && !mDataMQ) { + // Can't determine the size for the MQ buffer. Wait for a non-empty write request. + ALOGW_IF(mCallback.unsafe_get(), "First call to async write with 0 bytes"); + return OK; + } + + status_t status; + if (!mDataMQ) { + // In case if playback starts close to the end of a compressed track, the bytes + // that need to be written is less than the actual buffer size. Need to use + // full buffer size for the MQ since otherwise after seeking back to the middle + // data will be truncated. + size_t bufferSize; + if ((status = getCachedBufferSize(&bufferSize)) != OK) { + return status; + } + if (bytes > bufferSize) bufferSize = bytes; + if ((status = prepareForWriting(bufferSize)) != OK) { + return status; + } + } + + status = callWriterThread( + WriteCommand::WRITE, "write", static_cast<const uint8_t*>(buffer), bytes, + [&] (const WriteStatus& writeStatus) { + *written = writeStatus.reply.written; + // Diagnostics of the cause of b/35813113. + ALOGE_IF(*written > bytes, + "hal reports more bytes written than asked for: %lld > %lld", + (long long)*written, (long long)bytes); + }); + mStreamPowerLog.log(buffer, *written); + return status; +} + +status_t StreamOutHalHidl::callWriterThread( + WriteCommand cmd, const char* cmdName, + const uint8_t* data, size_t dataSize, StreamOutHalHidl::WriterCallback callback) { + if (!mCommandMQ->write(&cmd)) { + ALOGE("command message queue write failed for \"%s\"", cmdName); + return -EAGAIN; + } + if (data != nullptr) { + size_t availableToWrite = mDataMQ->availableToWrite(); + if (dataSize > availableToWrite) { + ALOGW("truncating write data from %lld to %lld due to insufficient data queue space", + (long long)dataSize, (long long)availableToWrite); + dataSize = availableToWrite; + } + if (!mDataMQ->write(data, dataSize)) { + ALOGE("data message queue write failed for \"%s\"", cmdName); + } + } + mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)); + + // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422 + uint32_t efState = 0; +retry: + status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL), &efState); + if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)) { + WriteStatus writeStatus; + writeStatus.retval = Result::NOT_INITIALIZED; + if (!mStatusMQ->read(&writeStatus)) { + ALOGE("status message read failed for \"%s\"", cmdName); + } + if (writeStatus.retval == Result::OK) { + ret = OK; + callback(writeStatus); + } else { + ret = processReturn(cmdName, writeStatus.retval); + } + return ret; + } + if (ret == -EAGAIN || ret == -EINTR) { + // Spurious wakeup. This normally retries no more than once. + goto retry; + } + return ret; +} + +status_t StreamOutHalHidl::prepareForWriting(size_t bufferSize) { + std::unique_ptr<CommandMQ> tempCommandMQ; + std::unique_ptr<DataMQ> tempDataMQ; + std::unique_ptr<StatusMQ> tempStatusMQ; + Result retval; + pid_t halThreadPid, halThreadTid; + Return<void> ret = mStream->prepareForWriting( + 1, bufferSize, + [&](Result r, + const CommandMQ::Descriptor& commandMQ, + const DataMQ::Descriptor& dataMQ, + const StatusMQ::Descriptor& statusMQ, + const ThreadInfo& halThreadInfo) { + retval = r; + if (retval == Result::OK) { + tempCommandMQ.reset(new CommandMQ(commandMQ)); + tempDataMQ.reset(new DataMQ(dataMQ)); + tempStatusMQ.reset(new StatusMQ(statusMQ)); + if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) { + EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup); + } + halThreadPid = halThreadInfo.pid; + halThreadTid = halThreadInfo.tid; + } + }); + if (!ret.isOk() || retval != Result::OK) { + return processReturn("prepareForWriting", ret, retval); + } + if (!tempCommandMQ || !tempCommandMQ->isValid() || + !tempDataMQ || !tempDataMQ->isValid() || + !tempStatusMQ || !tempStatusMQ->isValid() || + !mEfGroup) { + ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing"); + ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(), + "Command message queue for writing is invalid"); + ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for writing"); + ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for writing is invalid"); + ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for writing"); + ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), + "Status message queue for writing is invalid"); + ALOGE_IF(!mEfGroup, "Event flag creation for writing failed"); + return NO_INIT; + } + requestHalThreadPriority(halThreadPid, halThreadTid); + + mCommandMQ = std::move(tempCommandMQ); + mDataMQ = std::move(tempDataMQ); + mStatusMQ = std::move(tempStatusMQ); + mWriterClient = gettid(); + return OK; +} + +status_t StreamOutHalHidl::getRenderPosition(uint32_t *dspFrames) { + if (mStream == 0) return NO_INIT; + Result retval; + Return<void> ret = mStream->getRenderPosition( + [&](Result r, uint32_t d) { + retval = r; + if (retval == Result::OK) { + *dspFrames = d; + } + }); + return processReturn("getRenderPosition", ret, retval); +} + +status_t StreamOutHalHidl::getNextWriteTimestamp(int64_t *timestamp) { + if (mStream == 0) return NO_INIT; + Result retval; + Return<void> ret = mStream->getNextWriteTimestamp( + [&](Result r, int64_t t) { + retval = r; + if (retval == Result::OK) { + *timestamp = t; + } + }); + return processReturn("getRenderPosition", ret, retval); +} + +status_t StreamOutHalHidl::setCallback(wp<StreamOutHalInterfaceCallback> callback) { + if (mStream == 0) return NO_INIT; + status_t status = processReturn( + "setCallback", mStream->setCallback(new StreamOutCallback(this))); + if (status == OK) { + mCallback = callback; + } + return status; +} + +status_t StreamOutHalHidl::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) { + if (mStream == 0) return NO_INIT; + Return<void> ret = mStream->supportsPauseAndResume( + [&](bool p, bool r) { + *supportsPause = p; + *supportsResume = r; + }); + return processReturn("supportsPauseAndResume", ret); +} + +status_t StreamOutHalHidl::pause() { + if (mStream == 0) return NO_INIT; + return processReturn("pause", mStream->pause()); +} + +status_t StreamOutHalHidl::resume() { + if (mStream == 0) return NO_INIT; + return processReturn("pause", mStream->resume()); +} + +status_t StreamOutHalHidl::supportsDrain(bool *supportsDrain) { + if (mStream == 0) return NO_INIT; + return processReturn("supportsDrain", mStream->supportsDrain(), supportsDrain); +} + +status_t StreamOutHalHidl::drain(bool earlyNotify) { + if (mStream == 0) return NO_INIT; + return processReturn( + "drain", mStream->drain(earlyNotify ? AudioDrain::EARLY_NOTIFY : AudioDrain::ALL)); +} + +status_t StreamOutHalHidl::flush() { + if (mStream == 0) return NO_INIT; + return processReturn("pause", mStream->flush()); +} + +status_t StreamOutHalHidl::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) { + if (mStream == 0) return NO_INIT; + if (mWriterClient == gettid() && mCommandMQ) { + return callWriterThread( + WriteCommand::GET_PRESENTATION_POSITION, "getPresentationPosition", nullptr, 0, + [&](const WriteStatus& writeStatus) { + *frames = writeStatus.reply.presentationPosition.frames; + timestamp->tv_sec = writeStatus.reply.presentationPosition.timeStamp.tvSec; + timestamp->tv_nsec = writeStatus.reply.presentationPosition.timeStamp.tvNSec; + }); + } else { + Result retval; + Return<void> ret = mStream->getPresentationPosition( + [&](Result r, uint64_t hidlFrames, const TimeSpec& hidlTimeStamp) { + retval = r; + if (retval == Result::OK) { + *frames = hidlFrames; + timestamp->tv_sec = hidlTimeStamp.tvSec; + timestamp->tv_nsec = hidlTimeStamp.tvNSec; + } + }); + return processReturn("getPresentationPosition", ret, retval); + } +} + +status_t StreamOutHalHidl::updateSourceMetadata(const SourceMetadata& /* sourceMetadata */) { + // Audio HAL V2.0 does not support propagating source metadata + return INVALID_OPERATION; +} + +void StreamOutHalHidl::onWriteReady() { + sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); + if (callback == 0) return; + ALOGV("asyncCallback onWriteReady"); + callback->onWriteReady(); +} + +void StreamOutHalHidl::onDrainReady() { + sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); + if (callback == 0) return; + ALOGV("asyncCallback onDrainReady"); + callback->onDrainReady(); +} + +void StreamOutHalHidl::onError() { + sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); + if (callback == 0) return; + ALOGV("asyncCallback onError"); + callback->onError(); +} + + +StreamInHalHidl::StreamInHalHidl(const sp<IStreamIn>& stream) + : StreamHalHidl(stream.get()), mStream(stream), mReaderClient(0), mEfGroup(nullptr) { +} + +StreamInHalHidl::~StreamInHalHidl() { + if (mStream != 0) { + processReturn("close", mStream->close()); + mStream.clear(); + hardware::IPCThreadState::self()->flushCommands(); + } + if (mEfGroup) { + EventFlag::deleteEventFlag(&mEfGroup); + } +} + +status_t StreamInHalHidl::getFrameSize(size_t *size) { + if (mStream == 0) return NO_INIT; + return processReturn("getFrameSize", mStream->getFrameSize(), size); +} + +status_t StreamInHalHidl::setGain(float gain) { + if (mStream == 0) return NO_INIT; + return processReturn("setGain", mStream->setGain(gain)); +} + +status_t StreamInHalHidl::read(void *buffer, size_t bytes, size_t *read) { + if (mStream == 0) return NO_INIT; + *read = 0; + + if (bytes == 0 && !mDataMQ) { + // Can't determine the size for the MQ buffer. Wait for a non-empty read request. + return OK; + } + + status_t status; + if (!mDataMQ && (status = prepareForReading(bytes)) != OK) { + return status; + } + + ReadParameters params; + params.command = ReadCommand::READ; + params.params.read = bytes; + status = callReaderThread(params, "read", + [&](const ReadStatus& readStatus) { + const size_t availToRead = mDataMQ->availableToRead(); + if (!mDataMQ->read(static_cast<uint8_t*>(buffer), std::min(bytes, availToRead))) { + ALOGE("data message queue read failed for \"read\""); + } + ALOGW_IF(availToRead != readStatus.reply.read, + "HAL read report inconsistent: mq = %d, status = %d", + (int32_t)availToRead, (int32_t)readStatus.reply.read); + *read = readStatus.reply.read; + }); + mStreamPowerLog.log(buffer, *read); + return status; +} + +status_t StreamInHalHidl::callReaderThread( + const ReadParameters& params, const char* cmdName, + StreamInHalHidl::ReaderCallback callback) { + if (!mCommandMQ->write(¶ms)) { + ALOGW("command message queue write failed"); + return -EAGAIN; + } + mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)); + + // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422 + uint32_t efState = 0; +retry: + status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState); + if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) { + ReadStatus readStatus; + readStatus.retval = Result::NOT_INITIALIZED; + if (!mStatusMQ->read(&readStatus)) { + ALOGE("status message read failed for \"%s\"", cmdName); + } + if (readStatus.retval == Result::OK) { + ret = OK; + callback(readStatus); + } else { + ret = processReturn(cmdName, readStatus.retval); + } + return ret; + } + if (ret == -EAGAIN || ret == -EINTR) { + // Spurious wakeup. This normally retries no more than once. + goto retry; + } + return ret; +} + +status_t StreamInHalHidl::prepareForReading(size_t bufferSize) { + std::unique_ptr<CommandMQ> tempCommandMQ; + std::unique_ptr<DataMQ> tempDataMQ; + std::unique_ptr<StatusMQ> tempStatusMQ; + Result retval; + pid_t halThreadPid, halThreadTid; + Return<void> ret = mStream->prepareForReading( + 1, bufferSize, + [&](Result r, + const CommandMQ::Descriptor& commandMQ, + const DataMQ::Descriptor& dataMQ, + const StatusMQ::Descriptor& statusMQ, + const ThreadInfo& halThreadInfo) { + retval = r; + if (retval == Result::OK) { + tempCommandMQ.reset(new CommandMQ(commandMQ)); + tempDataMQ.reset(new DataMQ(dataMQ)); + tempStatusMQ.reset(new StatusMQ(statusMQ)); + if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) { + EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup); + } + halThreadPid = halThreadInfo.pid; + halThreadTid = halThreadInfo.tid; + } + }); + if (!ret.isOk() || retval != Result::OK) { + return processReturn("prepareForReading", ret, retval); + } + if (!tempCommandMQ || !tempCommandMQ->isValid() || + !tempDataMQ || !tempDataMQ->isValid() || + !tempStatusMQ || !tempStatusMQ->isValid() || + !mEfGroup) { + ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing"); + ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(), + "Command message queue for writing is invalid"); + ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for reading"); + ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for reading is invalid"); + ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for reading"); + ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), + "Status message queue for reading is invalid"); + ALOGE_IF(!mEfGroup, "Event flag creation for reading failed"); + return NO_INIT; + } + requestHalThreadPriority(halThreadPid, halThreadTid); + + mCommandMQ = std::move(tempCommandMQ); + mDataMQ = std::move(tempDataMQ); + mStatusMQ = std::move(tempStatusMQ); + mReaderClient = gettid(); + return OK; +} + +status_t StreamInHalHidl::getInputFramesLost(uint32_t *framesLost) { + if (mStream == 0) return NO_INIT; + return processReturn("getInputFramesLost", mStream->getInputFramesLost(), framesLost); +} + +status_t StreamInHalHidl::getCapturePosition(int64_t *frames, int64_t *time) { + if (mStream == 0) return NO_INIT; + if (mReaderClient == gettid() && mCommandMQ) { + ReadParameters params; + params.command = ReadCommand::GET_CAPTURE_POSITION; + return callReaderThread(params, "getCapturePosition", + [&](const ReadStatus& readStatus) { + *frames = readStatus.reply.capturePosition.frames; + *time = readStatus.reply.capturePosition.time; + }); + } else { + Result retval; + Return<void> ret = mStream->getCapturePosition( + [&](Result r, uint64_t hidlFrames, uint64_t hidlTime) { + retval = r; + if (retval == Result::OK) { + *frames = hidlFrames; + *time = hidlTime; + } + }); + return processReturn("getCapturePosition", ret, retval); + } +} + +status_t StreamInHalHidl::getActiveMicrophones( + std::vector<media::MicrophoneInfo> *microphones __unused) { + if (mStream == 0) return NO_INIT; + return INVALID_OPERATION; +} + +status_t StreamInHalHidl::updateSinkMetadata(const SinkMetadata& /* sinkMetadata */) { + // Audio HAL V2.0 does not support propagating sink metadata + return INVALID_OPERATION; +} + +} // namespace android
diff --git a/media/libaudiohal/2.0/StreamHalHidl.h b/media/libaudiohal/2.0/StreamHalHidl.h new file mode 100644 index 0000000..ebad8ae --- /dev/null +++ b/media/libaudiohal/2.0/StreamHalHidl.h
@@ -0,0 +1,248 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_STREAM_HAL_HIDL_H +#define ANDROID_HARDWARE_STREAM_HAL_HIDL_H + +#include <atomic> + +#include <android/hardware/audio/2.0/IStream.h> +#include <android/hardware/audio/2.0/IStreamIn.h> +#include <android/hardware/audio/2.0/IStreamOut.h> +#include <fmq/EventFlag.h> +#include <fmq/MessageQueue.h> +#include <media/audiohal/StreamHalInterface.h> + +#include "ConversionHelperHidl.h" +#include "StreamPowerLog.h" + +using ::android::hardware::audio::V2_0::IStream; +using ::android::hardware::audio::V2_0::IStreamIn; +using ::android::hardware::audio::V2_0::IStreamOut; +using ::android::hardware::EventFlag; +using ::android::hardware::MessageQueue; +using ::android::hardware::Return; +using ReadParameters = ::android::hardware::audio::V2_0::IStreamIn::ReadParameters; +using ReadStatus = ::android::hardware::audio::V2_0::IStreamIn::ReadStatus; +using WriteCommand = ::android::hardware::audio::V2_0::IStreamOut::WriteCommand; +using WriteStatus = ::android::hardware::audio::V2_0::IStreamOut::WriteStatus; + +namespace android { + +class DeviceHalHidl; + +class StreamHalHidl : public virtual StreamHalInterface, public ConversionHelperHidl +{ + public: + // Return the sampling rate in Hz - eg. 44100. + virtual status_t getSampleRate(uint32_t *rate); + + // Return size of input/output buffer in bytes for this stream - eg. 4800. + virtual status_t getBufferSize(size_t *size); + + // Return the channel mask. + virtual status_t getChannelMask(audio_channel_mask_t *mask); + + // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT. + virtual status_t getFormat(audio_format_t *format); + + // Convenience method. + virtual status_t getAudioProperties( + uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format); + + // Set audio stream parameters. + virtual status_t setParameters(const String8& kvPairs); + + // Get audio stream parameters. + virtual status_t getParameters(const String8& keys, String8 *values); + + // Add or remove the effect on the stream. + virtual status_t addEffect(sp<EffectHalInterface> effect); + virtual status_t removeEffect(sp<EffectHalInterface> effect); + + // Put the audio hardware input/output into standby mode. + virtual status_t standby(); + + virtual status_t dump(int fd); + + // Start a stream operating in mmap mode. + virtual status_t start(); + + // Stop a stream operating in mmap mode. + virtual status_t stop(); + + // Retrieve information on the data buffer in mmap mode. + virtual status_t createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info); + + // Get current read/write position in the mmap buffer + virtual status_t getMmapPosition(struct audio_mmap_position *position); + + // Set the priority of the thread that interacts with the HAL + // (must match the priority of the audioflinger's thread that calls 'read' / 'write') + virtual status_t setHalThreadPriority(int priority); + + protected: + // Subclasses can not be constructed directly by clients. + explicit StreamHalHidl(IStream *stream); + + // The destructor automatically closes the stream. + virtual ~StreamHalHidl(); + + status_t getCachedBufferSize(size_t *size); + + bool requestHalThreadPriority(pid_t threadPid, pid_t threadId); + + // mStreamPowerLog is used for audio signal power logging. + StreamPowerLog mStreamPowerLog; + + private: + const int HAL_THREAD_PRIORITY_DEFAULT = -1; + IStream *mStream; + int mHalThreadPriority; + size_t mCachedBufferSize; +}; + +class StreamOutHalHidl : public StreamOutHalInterface, public StreamHalHidl { + public: + // Return the frame size (number of bytes per sample) of a stream. + virtual status_t getFrameSize(size_t *size); + + // Return the audio hardware driver estimated latency in milliseconds. + virtual status_t getLatency(uint32_t *latency); + + // Use this method in situations where audio mixing is done in the hardware. + virtual status_t setVolume(float left, float right); + + // Write audio buffer to driver. + virtual status_t write(const void *buffer, size_t bytes, size_t *written); + + // Return the number of audio frames written by the audio dsp to DAC since + // the output has exited standby. + virtual status_t getRenderPosition(uint32_t *dspFrames); + + // Get the local time at which the next write to the audio driver will be presented. + virtual status_t getNextWriteTimestamp(int64_t *timestamp); + + // Set the callback for notifying completion of non-blocking write and drain. + virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback); + + // Returns whether pause and resume operations are supported. + virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume); + + // Notifies to the audio driver to resume playback following a pause. + virtual status_t pause(); + + // Notifies to the audio driver to resume playback following a pause. + virtual status_t resume(); + + // Returns whether drain operation is supported. + virtual status_t supportsDrain(bool *supportsDrain); + + // Requests notification when data buffered by the driver/hardware has been played. + virtual status_t drain(bool earlyNotify); + + // Notifies to the audio driver to flush the queued data. + virtual status_t flush(); + + // Return a recent count of the number of audio frames presented to an external observer. + virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp); + + // Called when the metadata of the stream's source has been changed. + status_t updateSourceMetadata(const SourceMetadata& sourceMetadata) override; + + // Methods used by StreamOutCallback (HIDL). + void onWriteReady(); + void onDrainReady(); + void onError(); + + private: + friend class DeviceHalHidl; + typedef MessageQueue<WriteCommand, hardware::kSynchronizedReadWrite> CommandMQ; + typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ; + typedef MessageQueue<WriteStatus, hardware::kSynchronizedReadWrite> StatusMQ; + + wp<StreamOutHalInterfaceCallback> mCallback; + sp<IStreamOut> mStream; + std::unique_ptr<CommandMQ> mCommandMQ; + std::unique_ptr<DataMQ> mDataMQ; + std::unique_ptr<StatusMQ> mStatusMQ; + std::atomic<pid_t> mWriterClient; + EventFlag* mEfGroup; + + // Can not be constructed directly by clients. + StreamOutHalHidl(const sp<IStreamOut>& stream); + + virtual ~StreamOutHalHidl(); + + using WriterCallback = std::function<void(const WriteStatus& writeStatus)>; + status_t callWriterThread( + WriteCommand cmd, const char* cmdName, + const uint8_t* data, size_t dataSize, WriterCallback callback); + status_t prepareForWriting(size_t bufferSize); +}; + +class StreamInHalHidl : public StreamInHalInterface, public StreamHalHidl { + public: + // Return the frame size (number of bytes per sample) of a stream. + virtual status_t getFrameSize(size_t *size); + + // Set the input gain for the audio driver. + virtual status_t setGain(float gain); + + // Read audio buffer in from driver. + virtual status_t read(void *buffer, size_t bytes, size_t *read); + + // Return the amount of input frames lost in the audio driver. + virtual status_t getInputFramesLost(uint32_t *framesLost); + + // Return a recent count of the number of audio frames received and + // the clock time associated with that frame count. + virtual status_t getCapturePosition(int64_t *frames, int64_t *time); + + // Get active microphones + virtual status_t getActiveMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + // Called when the metadata of the stream's sink has been changed. + status_t updateSinkMetadata(const SinkMetadata& sinkMetadata) override; + + private: + friend class DeviceHalHidl; + typedef MessageQueue<ReadParameters, hardware::kSynchronizedReadWrite> CommandMQ; + typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ; + typedef MessageQueue<ReadStatus, hardware::kSynchronizedReadWrite> StatusMQ; + + sp<IStreamIn> mStream; + std::unique_ptr<CommandMQ> mCommandMQ; + std::unique_ptr<DataMQ> mDataMQ; + std::unique_ptr<StatusMQ> mStatusMQ; + std::atomic<pid_t> mReaderClient; + EventFlag* mEfGroup; + + // Can not be constructed directly by clients. + StreamInHalHidl(const sp<IStreamIn>& stream); + + virtual ~StreamInHalHidl(); + + using ReaderCallback = std::function<void(const ReadStatus& readStatus)>; + status_t callReaderThread( + const ReadParameters& params, const char* cmdName, ReaderCallback callback); + status_t prepareForReading(size_t bufferSize); +}; + +} // namespace android + +#endif // ANDROID_HARDWARE_STREAM_HAL_HIDL_H
diff --git a/media/libaudiohal/2.0/StreamHalLocal.cpp b/media/libaudiohal/2.0/StreamHalLocal.cpp new file mode 100644 index 0000000..98107e5 --- /dev/null +++ b/media/libaudiohal/2.0/StreamHalLocal.cpp
@@ -0,0 +1,347 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "StreamHalLocal" +//#define LOG_NDEBUG 0 + +#include <hardware/audio.h> +#include <utils/Log.h> + +#include "DeviceHalLocal.h" +#include "StreamHalLocal.h" + +namespace android { + +StreamHalLocal::StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device) + : mDevice(device), + mStream(stream) { + // Instrument audio signal power logging. + // Note: This assumes channel mask, format, and sample rate do not change after creation. + if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) { + mStreamPowerLog.init(mStream->get_sample_rate(mStream), + mStream->get_channels(mStream), + mStream->get_format(mStream)); + } +} + +StreamHalLocal::~StreamHalLocal() { + mStream = 0; + mDevice.clear(); +} + +status_t StreamHalLocal::getSampleRate(uint32_t *rate) { + *rate = mStream->get_sample_rate(mStream); + return OK; +} + +status_t StreamHalLocal::getBufferSize(size_t *size) { + *size = mStream->get_buffer_size(mStream); + return OK; +} + +status_t StreamHalLocal::getChannelMask(audio_channel_mask_t *mask) { + *mask = mStream->get_channels(mStream); + return OK; +} + +status_t StreamHalLocal::getFormat(audio_format_t *format) { + *format = mStream->get_format(mStream); + return OK; +} + +status_t StreamHalLocal::getAudioProperties( + uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) { + *sampleRate = mStream->get_sample_rate(mStream); + *mask = mStream->get_channels(mStream); + *format = mStream->get_format(mStream); + return OK; +} + +status_t StreamHalLocal::setParameters(const String8& kvPairs) { + return mStream->set_parameters(mStream, kvPairs.string()); +} + +status_t StreamHalLocal::getParameters(const String8& keys, String8 *values) { + char *halValues = mStream->get_parameters(mStream, keys.string()); + if (halValues != NULL) { + values->setTo(halValues); + free(halValues); + } else { + values->clear(); + } + return OK; +} + +status_t StreamHalLocal::addEffect(sp<EffectHalInterface>) { + LOG_ALWAYS_FATAL("Local streams can not have effects"); + return INVALID_OPERATION; +} + +status_t StreamHalLocal::removeEffect(sp<EffectHalInterface>) { + LOG_ALWAYS_FATAL("Local streams can not have effects"); + return INVALID_OPERATION; +} + +status_t StreamHalLocal::standby() { + return mStream->standby(mStream); +} + +status_t StreamHalLocal::dump(int fd) { + status_t status = mStream->dump(mStream, fd); + mStreamPowerLog.dump(fd); + return status; +} + +status_t StreamHalLocal::setHalThreadPriority(int) { + // Don't need to do anything as local hal is executed by audioflinger directly + // on the same thread. + return OK; +} + +StreamOutHalLocal::StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device) + : StreamHalLocal(&stream->common, device), mStream(stream) { +} + +StreamOutHalLocal::~StreamOutHalLocal() { + mCallback.clear(); + mDevice->closeOutputStream(mStream); + mStream = 0; +} + +status_t StreamOutHalLocal::getFrameSize(size_t *size) { + *size = audio_stream_out_frame_size(mStream); + return OK; +} + +status_t StreamOutHalLocal::getLatency(uint32_t *latency) { + *latency = mStream->get_latency(mStream); + return OK; +} + +status_t StreamOutHalLocal::setVolume(float left, float right) { + if (mStream->set_volume == NULL) return INVALID_OPERATION; + return mStream->set_volume(mStream, left, right); +} + +status_t StreamOutHalLocal::write(const void *buffer, size_t bytes, size_t *written) { + ssize_t writeResult = mStream->write(mStream, buffer, bytes); + if (writeResult > 0) { + *written = writeResult; + mStreamPowerLog.log(buffer, *written); + return OK; + } else { + *written = 0; + return writeResult; + } +} + +status_t StreamOutHalLocal::getRenderPosition(uint32_t *dspFrames) { + return mStream->get_render_position(mStream, dspFrames); +} + +status_t StreamOutHalLocal::getNextWriteTimestamp(int64_t *timestamp) { + if (mStream->get_next_write_timestamp == NULL) return INVALID_OPERATION; + return mStream->get_next_write_timestamp(mStream, timestamp); +} + +status_t StreamOutHalLocal::setCallback(wp<StreamOutHalInterfaceCallback> callback) { + if (mStream->set_callback == NULL) return INVALID_OPERATION; + status_t result = mStream->set_callback(mStream, StreamOutHalLocal::asyncCallback, this); + if (result == OK) { + mCallback = callback; + } + return result; +} + +// static +int StreamOutHalLocal::asyncCallback(stream_callback_event_t event, void*, void *cookie) { + // We act as if we gave a wp<StreamOutHalLocal> to HAL. This way we should handle + // correctly the case when the callback is invoked while StreamOutHalLocal's destructor is + // already running, because the destructor is invoked after the refcount has been atomically + // decremented. + wp<StreamOutHalLocal> weakSelf(static_cast<StreamOutHalLocal*>(cookie)); + sp<StreamOutHalLocal> self = weakSelf.promote(); + if (self == 0) return 0; + sp<StreamOutHalInterfaceCallback> callback = self->mCallback.promote(); + if (callback == 0) return 0; + ALOGV("asyncCallback() event %d", event); + switch (event) { + case STREAM_CBK_EVENT_WRITE_READY: + callback->onWriteReady(); + break; + case STREAM_CBK_EVENT_DRAIN_READY: + callback->onDrainReady(); + break; + case STREAM_CBK_EVENT_ERROR: + callback->onError(); + break; + default: + ALOGW("asyncCallback() unknown event %d", event); + break; + } + return 0; +} + +status_t StreamOutHalLocal::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) { + *supportsPause = mStream->pause != NULL; + *supportsResume = mStream->resume != NULL; + return OK; +} + +status_t StreamOutHalLocal::pause() { + if (mStream->pause == NULL) return INVALID_OPERATION; + return mStream->pause(mStream); +} + +status_t StreamOutHalLocal::resume() { + if (mStream->resume == NULL) return INVALID_OPERATION; + return mStream->resume(mStream); +} + +status_t StreamOutHalLocal::supportsDrain(bool *supportsDrain) { + *supportsDrain = mStream->drain != NULL; + return OK; +} + +status_t StreamOutHalLocal::drain(bool earlyNotify) { + if (mStream->drain == NULL) return INVALID_OPERATION; + return mStream->drain(mStream, earlyNotify ? AUDIO_DRAIN_EARLY_NOTIFY : AUDIO_DRAIN_ALL); +} + +status_t StreamOutHalLocal::flush() { + if (mStream->flush == NULL) return INVALID_OPERATION; + return mStream->flush(mStream); +} + +status_t StreamOutHalLocal::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) { + if (mStream->get_presentation_position == NULL) return INVALID_OPERATION; + return mStream->get_presentation_position(mStream, frames, timestamp); +} + +status_t StreamOutHalLocal::updateSourceMetadata(const SourceMetadata& sourceMetadata) { + if (mStream->update_source_metadata == nullptr) { + return INVALID_OPERATION; + } + const source_metadata_t metadata { + .track_count = sourceMetadata.tracks.size(), + // const cast is fine as it is in a const structure + .tracks = const_cast<playback_track_metadata*>(sourceMetadata.tracks.data()), + }; + mStream->update_source_metadata(mStream, &metadata); + return OK; +} + +status_t StreamOutHalLocal::start() { + if (mStream->start == NULL) return INVALID_OPERATION; + return mStream->start(mStream); +} + +status_t StreamOutHalLocal::stop() { + if (mStream->stop == NULL) return INVALID_OPERATION; + return mStream->stop(mStream); +} + +status_t StreamOutHalLocal::createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info) { + if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION; + return mStream->create_mmap_buffer(mStream, minSizeFrames, info); +} + +status_t StreamOutHalLocal::getMmapPosition(struct audio_mmap_position *position) { + if (mStream->get_mmap_position == NULL) return INVALID_OPERATION; + return mStream->get_mmap_position(mStream, position); +} + +StreamInHalLocal::StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device) + : StreamHalLocal(&stream->common, device), mStream(stream) { +} + +StreamInHalLocal::~StreamInHalLocal() { + mDevice->closeInputStream(mStream); + mStream = 0; +} + +status_t StreamInHalLocal::getFrameSize(size_t *size) { + *size = audio_stream_in_frame_size(mStream); + return OK; +} + +status_t StreamInHalLocal::setGain(float gain) { + return mStream->set_gain(mStream, gain); +} + +status_t StreamInHalLocal::read(void *buffer, size_t bytes, size_t *read) { + ssize_t readResult = mStream->read(mStream, buffer, bytes); + if (readResult > 0) { + *read = readResult; + mStreamPowerLog.log( buffer, *read); + return OK; + } else { + *read = 0; + return readResult; + } +} + +status_t StreamInHalLocal::getInputFramesLost(uint32_t *framesLost) { + *framesLost = mStream->get_input_frames_lost(mStream); + return OK; +} + +status_t StreamInHalLocal::getCapturePosition(int64_t *frames, int64_t *time) { + if (mStream->get_capture_position == NULL) return INVALID_OPERATION; + return mStream->get_capture_position(mStream, frames, time); +} + +status_t StreamInHalLocal::updateSinkMetadata(const SinkMetadata& sinkMetadata) { + if (mStream->update_sink_metadata == nullptr) { + return INVALID_OPERATION; + } + const sink_metadata_t metadata { + .track_count = sinkMetadata.tracks.size(), + // const cast is fine as it is in a const structure + .tracks = const_cast<record_track_metadata*>(sinkMetadata.tracks.data()), + }; + mStream->update_sink_metadata(mStream, &metadata); + return OK; +} + +status_t StreamInHalLocal::start() { + if (mStream->start == NULL) return INVALID_OPERATION; + return mStream->start(mStream); +} + +status_t StreamInHalLocal::stop() { + if (mStream->stop == NULL) return INVALID_OPERATION; + return mStream->stop(mStream); +} + +status_t StreamInHalLocal::createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info) { + if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION; + return mStream->create_mmap_buffer(mStream, minSizeFrames, info); +} + +status_t StreamInHalLocal::getMmapPosition(struct audio_mmap_position *position) { + if (mStream->get_mmap_position == NULL) return INVALID_OPERATION; + return mStream->get_mmap_position(mStream, position); +} + +status_t StreamInHalLocal::getActiveMicrophones( + std::vector<media::MicrophoneInfo> *microphones __unused) { + return INVALID_OPERATION; +} + +} // namespace android
diff --git a/media/libaudiohal/2.0/StreamHalLocal.h b/media/libaudiohal/2.0/StreamHalLocal.h new file mode 100644 index 0000000..cda8d0c --- /dev/null +++ b/media/libaudiohal/2.0/StreamHalLocal.h
@@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_STREAM_HAL_LOCAL_H +#define ANDROID_HARDWARE_STREAM_HAL_LOCAL_H + +#include <media/audiohal/StreamHalInterface.h> +#include "StreamPowerLog.h" + +namespace android { + +class DeviceHalLocal; + +class StreamHalLocal : public virtual StreamHalInterface +{ + public: + // Return the sampling rate in Hz - eg. 44100. + virtual status_t getSampleRate(uint32_t *rate); + + // Return size of input/output buffer in bytes for this stream - eg. 4800. + virtual status_t getBufferSize(size_t *size); + + // Return the channel mask. + virtual status_t getChannelMask(audio_channel_mask_t *mask); + + // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT. + virtual status_t getFormat(audio_format_t *format); + + // Convenience method. + virtual status_t getAudioProperties( + uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format); + + // Set audio stream parameters. + virtual status_t setParameters(const String8& kvPairs); + + // Get audio stream parameters. + virtual status_t getParameters(const String8& keys, String8 *values); + + // Add or remove the effect on the stream. + virtual status_t addEffect(sp<EffectHalInterface> effect); + virtual status_t removeEffect(sp<EffectHalInterface> effect); + + // Put the audio hardware input/output into standby mode. + virtual status_t standby(); + + virtual status_t dump(int fd); + + // Start a stream operating in mmap mode. + virtual status_t start() = 0; + + // Stop a stream operating in mmap mode. + virtual status_t stop() = 0; + + // Retrieve information on the data buffer in mmap mode. + virtual status_t createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info) = 0; + + // Get current read/write position in the mmap buffer + virtual status_t getMmapPosition(struct audio_mmap_position *position) = 0; + + // Set the priority of the thread that interacts with the HAL + // (must match the priority of the audioflinger's thread that calls 'read' / 'write') + virtual status_t setHalThreadPriority(int priority); + + protected: + // Subclasses can not be constructed directly by clients. + StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device); + + // The destructor automatically closes the stream. + virtual ~StreamHalLocal(); + + sp<DeviceHalLocal> mDevice; + + // mStreamPowerLog is used for audio signal power logging. + StreamPowerLog mStreamPowerLog; + + private: + audio_stream_t *mStream; +}; + +class StreamOutHalLocal : public StreamOutHalInterface, public StreamHalLocal { + public: + // Return the frame size (number of bytes per sample) of a stream. + virtual status_t getFrameSize(size_t *size); + + // Return the audio hardware driver estimated latency in milliseconds. + virtual status_t getLatency(uint32_t *latency); + + // Use this method in situations where audio mixing is done in the hardware. + virtual status_t setVolume(float left, float right); + + // Write audio buffer to driver. + virtual status_t write(const void *buffer, size_t bytes, size_t *written); + + // Return the number of audio frames written by the audio dsp to DAC since + // the output has exited standby. + virtual status_t getRenderPosition(uint32_t *dspFrames); + + // Get the local time at which the next write to the audio driver will be presented. + virtual status_t getNextWriteTimestamp(int64_t *timestamp); + + // Set the callback for notifying completion of non-blocking write and drain. + virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback); + + // Returns whether pause and resume operations are supported. + virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume); + + // Notifies to the audio driver to resume playback following a pause. + virtual status_t pause(); + + // Notifies to the audio driver to resume playback following a pause. + virtual status_t resume(); + + // Returns whether drain operation is supported. + virtual status_t supportsDrain(bool *supportsDrain); + + // Requests notification when data buffered by the driver/hardware has been played. + virtual status_t drain(bool earlyNotify); + + // Notifies to the audio driver to flush the queued data. + virtual status_t flush(); + + // Return a recent count of the number of audio frames presented to an external observer. + virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp); + + // Start a stream operating in mmap mode. + virtual status_t start(); + + // Stop a stream operating in mmap mode. + virtual status_t stop(); + + // Retrieve information on the data buffer in mmap mode. + virtual status_t createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info); + + // Get current read/write position in the mmap buffer + virtual status_t getMmapPosition(struct audio_mmap_position *position); + + // Called when the metadata of the stream's source has been changed. + status_t updateSourceMetadata(const SourceMetadata& sourceMetadata) override; + + private: + audio_stream_out_t *mStream; + wp<StreamOutHalInterfaceCallback> mCallback; + + friend class DeviceHalLocal; + + // Can not be constructed directly by clients. + StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device); + + virtual ~StreamOutHalLocal(); + + static int asyncCallback(stream_callback_event_t event, void *param, void *cookie); +}; + +class StreamInHalLocal : public StreamInHalInterface, public StreamHalLocal { + public: + // Return the frame size (number of bytes per sample) of a stream. + virtual status_t getFrameSize(size_t *size); + + // Set the input gain for the audio driver. + virtual status_t setGain(float gain); + + // Read audio buffer in from driver. + virtual status_t read(void *buffer, size_t bytes, size_t *read); + + // Return the amount of input frames lost in the audio driver. + virtual status_t getInputFramesLost(uint32_t *framesLost); + + // Return a recent count of the number of audio frames received and + // the clock time associated with that frame count. + virtual status_t getCapturePosition(int64_t *frames, int64_t *time); + + // Start a stream operating in mmap mode. + virtual status_t start(); + + // Stop a stream operating in mmap mode. + virtual status_t stop(); + + // Retrieve information on the data buffer in mmap mode. + virtual status_t createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info); + + // Get current read/write position in the mmap buffer + virtual status_t getMmapPosition(struct audio_mmap_position *position); + + // Get active microphones + virtual status_t getActiveMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + // Called when the metadata of the stream's sink has been changed. + status_t updateSinkMetadata(const SinkMetadata& sinkMetadata) override; + + private: + audio_stream_in_t *mStream; + + friend class DeviceHalLocal; + + // Can not be constructed directly by clients. + StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device); + + virtual ~StreamInHalLocal(); +}; + +} // namespace android + +#endif // ANDROID_HARDWARE_STREAM_HAL_LOCAL_H
diff --git a/media/libaudiohal/StreamPowerLog.h b/media/libaudiohal/2.0/StreamPowerLog.h similarity index 100% rename from media/libaudiohal/StreamPowerLog.h rename to media/libaudiohal/2.0/StreamPowerLog.h
diff --git a/media/libaudiohal/4.0/Android.bp b/media/libaudiohal/4.0/Android.bp new file mode 100644 index 0000000..833defa --- /dev/null +++ b/media/libaudiohal/4.0/Android.bp
@@ -0,0 +1,58 @@ +cc_library_shared { + name: "libaudiohal@4.0", + + srcs: [ + "DeviceHalLocal.cpp", + "DevicesFactoryHalHybrid.cpp", + "DevicesFactoryHalLocal.cpp", + "StreamHalLocal.cpp", + + "ConversionHelperHidl.cpp", + "DeviceHalHidl.cpp", + "DevicesFactoryHalHidl.cpp", + "EffectBufferHalHidl.cpp", + "EffectHalHidl.cpp", + "EffectsFactoryHalHidl.cpp", + "StreamHalHidl.cpp", + ], + + export_include_dirs: ["include"], + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + shared_libs: [ + "libaudiohal_deathhandler", + "libaudioutils", + "libbinder", + "libcutils", + "liblog", + "libutils", + "libhardware", + "libbase", + "libfmq", + "libhwbinder", + "libhidlbase", + "libhidlmemory", + "libhidltransport", + "android.hardware.audio@4.0", + "android.hardware.audio.common-util", + "android.hardware.audio.common@4.0", + "android.hardware.audio.common@4.0-util", + "android.hardware.audio.effect@4.0", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libmedia_helper", + "libmediautils", + ], + header_libs: [ + "android.hardware.audio.common.util@all-versions", + "libaudiohal_headers" + ], + + export_shared_lib_headers: [ + "libfmq", + ], +}
diff --git a/media/libaudiohal/4.0/ConversionHelperHidl.cpp b/media/libaudiohal/4.0/ConversionHelperHidl.cpp new file mode 100644 index 0000000..fe27504 --- /dev/null +++ b/media/libaudiohal/4.0/ConversionHelperHidl.cpp
@@ -0,0 +1,237 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> + +#define LOG_TAG "HalHidl" +#include <media/AudioParameter.h> +#include <utils/Log.h> + +#include "ConversionHelperHidl.h" + +using ::android::hardware::audio::V4_0::AudioMicrophoneChannelMapping; +using ::android::hardware::audio::V4_0::AudioMicrophoneDirectionality; +using ::android::hardware::audio::V4_0::AudioMicrophoneLocation; +using ::android::hardware::audio::V4_0::DeviceAddress; +using ::android::hardware::audio::V4_0::MicrophoneInfo; +using ::android::hardware::audio::V4_0::Result; + +namespace android { +namespace V4_0 { + +// static +status_t ConversionHelperHidl::keysFromHal(const String8& keys, hidl_vec<hidl_string> *hidlKeys) { + AudioParameter halKeys(keys); + if (halKeys.size() == 0) return BAD_VALUE; + hidlKeys->resize(halKeys.size()); + //FIXME: keyStreamSupportedChannels and keyStreamSupportedSamplingRates come with a + // "keyFormat=<value>" pair. We need to transform it into a single key string so that it is + // carried over to the legacy HAL via HIDL. + String8 value; + bool keepFormatValue = halKeys.size() == 2 && + (halKeys.get(String8(AudioParameter::keyStreamSupportedChannels), value) == NO_ERROR || + halKeys.get(String8(AudioParameter::keyStreamSupportedSamplingRates), 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; + halKeys.getAt(i, key, value); + formatParam.add(key, value); + key = formatParam.toString(); + } + (*hidlKeys)[i] = key.string(); + } + return OK; +} + +// static +status_t ConversionHelperHidl::parametersFromHal( + const String8& kvPairs, hidl_vec<ParameterValue> *hidlParams) { + AudioParameter params(kvPairs); + if (params.size() == 0) return BAD_VALUE; + hidlParams->resize(params.size()); + for (size_t i = 0; i < params.size(); ++i) { + String8 key, value; + status_t status = params.getAt(i, key, value); + if (status != OK) return status; + (*hidlParams)[i].key = key.string(); + (*hidlParams)[i].value = value.string(); + } + return OK; +} + +// static +void ConversionHelperHidl::parametersToHal( + const hidl_vec<ParameterValue>& parameters, String8 *values) { + AudioParameter params; + for (size_t i = 0; i < parameters.size(); ++i) { + params.add(String8(parameters[i].key.c_str()), String8(parameters[i].value.c_str())); + } + values->setTo(params.toString()); +} + +ConversionHelperHidl::ConversionHelperHidl(const char* className) + : mClassName(className) { +} + +// static +status_t ConversionHelperHidl::analyzeResult(const Result& result) { + switch (result) { + case Result::OK: return OK; + case Result::INVALID_ARGUMENTS: return BAD_VALUE; + case Result::INVALID_STATE: return NOT_ENOUGH_DATA; + case Result::NOT_INITIALIZED: return NO_INIT; + case Result::NOT_SUPPORTED: return INVALID_OPERATION; + default: return NO_INIT; + } +} + +void ConversionHelperHidl::emitError(const char* funcName, const char* description) { + ALOGE("%s %p %s: %s (from rpc)", mClassName, this, funcName, description); +} + +// TODO: Use the same implementation in the hal when it moves to a util library. +std::string deviceAddressToHal(const DeviceAddress& address) { + // HAL assumes that the address is NUL-terminated. + char halAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN]; + memset(halAddress, 0, sizeof(halAddress)); + audio_devices_t halDevice = static_cast<audio_devices_t>(address.device); + const bool isInput = (halDevice & AUDIO_DEVICE_BIT_IN) != 0; + if (isInput) halDevice &= ~AUDIO_DEVICE_BIT_IN; + if ((!isInput && (halDevice & AUDIO_DEVICE_OUT_ALL_A2DP) != 0) || + (isInput && (halDevice & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) != 0)) { + snprintf(halAddress, sizeof(halAddress), "%02X:%02X:%02X:%02X:%02X:%02X", + address.address.mac[0], address.address.mac[1], address.address.mac[2], + address.address.mac[3], address.address.mac[4], address.address.mac[5]); + } else if ((!isInput && (halDevice & AUDIO_DEVICE_OUT_IP) != 0) || + (isInput && (halDevice & AUDIO_DEVICE_IN_IP) != 0)) { + snprintf(halAddress, sizeof(halAddress), "%d.%d.%d.%d", address.address.ipv4[0], + address.address.ipv4[1], address.address.ipv4[2], address.address.ipv4[3]); + } else if ((!isInput && (halDevice & AUDIO_DEVICE_OUT_ALL_USB) != 0) || + (isInput && (halDevice & AUDIO_DEVICE_IN_ALL_USB) != 0)) { + snprintf(halAddress, sizeof(halAddress), "card=%d;device=%d", address.address.alsa.card, + address.address.alsa.device); + } else if ((!isInput && (halDevice & AUDIO_DEVICE_OUT_BUS) != 0) || + (isInput && (halDevice & AUDIO_DEVICE_IN_BUS) != 0)) { + snprintf(halAddress, sizeof(halAddress), "%s", address.busAddress.c_str()); + } else if ((!isInput && (halDevice & AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) != 0 || + (isInput && (halDevice & AUDIO_DEVICE_IN_REMOTE_SUBMIX) != 0)) { + snprintf(halAddress, sizeof(halAddress), "%s", address.rSubmixAddress.c_str()); + } else { + snprintf(halAddress, sizeof(halAddress), "%s", address.busAddress.c_str()); + } + return halAddress; +} + +//local conversion helpers + +audio_microphone_channel_mapping_t channelMappingToHal(AudioMicrophoneChannelMapping mapping) { + switch (mapping) { + case AudioMicrophoneChannelMapping::UNUSED: + return AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED; + case AudioMicrophoneChannelMapping::DIRECT: + return AUDIO_MICROPHONE_CHANNEL_MAPPING_DIRECT; + case AudioMicrophoneChannelMapping::PROCESSED: + return AUDIO_MICROPHONE_CHANNEL_MAPPING_PROCESSED; + default: + LOG_ALWAYS_FATAL("Unknown channelMappingToHal conversion %d", mapping); + } +} + +audio_microphone_location_t locationToHal(AudioMicrophoneLocation location) { + switch (location) { + case AudioMicrophoneLocation::UNKNOWN: + return AUDIO_MICROPHONE_LOCATION_UNKNOWN; + case AudioMicrophoneLocation::MAINBODY: + return AUDIO_MICROPHONE_LOCATION_MAINBODY; + case AudioMicrophoneLocation::MAINBODY_MOVABLE: + return AUDIO_MICROPHONE_LOCATION_MAINBODY_MOVABLE; + case AudioMicrophoneLocation::PERIPHERAL: + return AUDIO_MICROPHONE_LOCATION_PERIPHERAL; + default: + LOG_ALWAYS_FATAL("Unknown locationToHal conversion %d", location); + } +} +audio_microphone_directionality_t directionalityToHal(AudioMicrophoneDirectionality dir) { + switch (dir) { + case AudioMicrophoneDirectionality::UNKNOWN: + return AUDIO_MICROPHONE_DIRECTIONALITY_UNKNOWN; + case AudioMicrophoneDirectionality::OMNI: + return AUDIO_MICROPHONE_DIRECTIONALITY_OMNI; + case AudioMicrophoneDirectionality::BI_DIRECTIONAL: + return AUDIO_MICROPHONE_DIRECTIONALITY_BI_DIRECTIONAL; + case AudioMicrophoneDirectionality::CARDIOID: + return AUDIO_MICROPHONE_DIRECTIONALITY_CARDIOID; + case AudioMicrophoneDirectionality::HYPER_CARDIOID: + return AUDIO_MICROPHONE_DIRECTIONALITY_HYPER_CARDIOID; + case AudioMicrophoneDirectionality::SUPER_CARDIOID: + return AUDIO_MICROPHONE_DIRECTIONALITY_SUPER_CARDIOID; + default: + LOG_ALWAYS_FATAL("Unknown directionalityToHal conversion %d", dir); + } +} + +// static +void ConversionHelperHidl::microphoneInfoToHal(const MicrophoneInfo& src, + audio_microphone_characteristic_t *pDst) { + if (pDst != NULL) { + snprintf(pDst->device_id, sizeof(pDst->device_id), + "%s", src.deviceId.c_str()); + pDst->device = static_cast<audio_devices_t>(src.deviceAddress.device); + snprintf(pDst->address, sizeof(pDst->address), + "%s", deviceAddressToHal(src.deviceAddress).c_str()); + if (src.channelMapping.size() > AUDIO_CHANNEL_COUNT_MAX) { + ALOGW("microphoneInfoToStruct found %zu channelMapping elements. Max expected is %d", + src.channelMapping.size(), AUDIO_CHANNEL_COUNT_MAX); + } + size_t ch; + for (ch = 0; ch < src.channelMapping.size() && ch < AUDIO_CHANNEL_COUNT_MAX; ch++) { + pDst->channel_mapping[ch] = channelMappingToHal(src.channelMapping[ch]); + } + for (; ch < AUDIO_CHANNEL_COUNT_MAX; ch++) { + pDst->channel_mapping[ch] = AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED; + } + pDst->location = locationToHal(src.location); + pDst->group = (audio_microphone_group_t)src.group; + pDst->index_in_the_group = (unsigned int)src.indexInTheGroup; + pDst->sensitivity = src.sensitivity; + pDst->max_spl = src.maxSpl; + pDst->min_spl = src.minSpl; + pDst->directionality = directionalityToHal(src.directionality); + pDst->num_frequency_responses = (unsigned int)src.frequencyResponse.size(); + if (pDst->num_frequency_responses > AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES) { + ALOGW("microphoneInfoToStruct found %d frequency responses. Max expected is %d", + pDst->num_frequency_responses, AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES); + pDst->num_frequency_responses = AUDIO_MICROPHONE_MAX_FREQUENCY_RESPONSES; + } + for (size_t k = 0; k < pDst->num_frequency_responses; k++) { + pDst->frequency_responses[0][k] = src.frequencyResponse[k].frequency; + pDst->frequency_responses[1][k] = src.frequencyResponse[k].level; + } + pDst->geometric_location.x = src.position.x; + pDst->geometric_location.y = src.position.y; + pDst->geometric_location.z = src.position.z; + pDst->orientation.x = src.orientation.x; + pDst->orientation.y = src.orientation.y; + pDst->orientation.z = src.orientation.z; + } +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/ConversionHelperHidl.h b/media/libaudiohal/4.0/ConversionHelperHidl.h new file mode 100644 index 0000000..8823a8d --- /dev/null +++ b/media/libaudiohal/4.0/ConversionHelperHidl.h
@@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_CONVERSION_HELPER_HIDL_4_0_H +#define ANDROID_HARDWARE_CONVERSION_HELPER_HIDL_4_0_H + +#include <android/hardware/audio/4.0/types.h> +#include <hidl/HidlSupport.h> +#include <system/audio.h> +#include <utils/String8.h> + +using ::android::hardware::audio::V4_0::ParameterValue; +using ::android::hardware::audio::V4_0::MicrophoneInfo; +using ::android::hardware::Return; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; + +namespace android { +namespace V4_0 { + +class ConversionHelperHidl { + protected: + static status_t keysFromHal(const String8& keys, hidl_vec<hidl_string> *hidlKeys); + static status_t parametersFromHal(const String8& kvPairs, hidl_vec<ParameterValue> *hidlParams); + static void parametersToHal(const hidl_vec<ParameterValue>& parameters, String8 *values); + static void microphoneInfoToHal(const MicrophoneInfo& src, + audio_microphone_characteristic_t *pDst); + + ConversionHelperHidl(const char* className); + + template<typename R, typename T> + status_t processReturn(const char* funcName, const Return<R>& ret, T *retval) { + if (ret.isOk()) { + // This way it also works for enum class to unscoped enum conversion. + *retval = static_cast<T>(static_cast<R>(ret)); + return OK; + } + return processReturn(funcName, ret); + } + + template<typename T> + status_t processReturn(const char* funcName, const Return<T>& ret) { + if (!ret.isOk()) { + emitError(funcName, ret.description().c_str()); + } + return ret.isOk() ? OK : FAILED_TRANSACTION; + } + + status_t processReturn(const char* funcName, const Return<hardware::audio::V4_0::Result>& ret) { + if (!ret.isOk()) { + emitError(funcName, ret.description().c_str()); + } + return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION; + } + + template<typename T> + status_t processReturn( + const char* funcName, const Return<T>& ret, hardware::audio::V4_0::Result retval) { + if (!ret.isOk()) { + emitError(funcName, ret.description().c_str()); + } + return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION; + } + + private: + const char* mClassName; + + static status_t analyzeResult(const hardware::audio::V4_0::Result& result); + + void emitError(const char* funcName, const char* description); +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_CONVERSION_HELPER_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DeviceHalHidl.cpp b/media/libaudiohal/4.0/DeviceHalHidl.cpp new file mode 100644 index 0000000..6facca9 --- /dev/null +++ b/media/libaudiohal/4.0/DeviceHalHidl.cpp
@@ -0,0 +1,389 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> + +#define LOG_TAG "DeviceHalHidl" +//#define LOG_NDEBUG 0 + +#include <android/hardware/audio/4.0/IPrimaryDevice.h> +#include <cutils/native_handle.h> +#include <hwbinder/IPCThreadState.h> +#include <utils/Log.h> + +#include <common/all-versions/VersionUtils.h> + +#include "DeviceHalHidl.h" +#include "HidlUtils.h" +#include "StreamHalHidl.h" +#include "VersionUtils.h" + +using ::android::hardware::audio::common::V4_0::AudioConfig; +using ::android::hardware::audio::common::V4_0::AudioDevice; +using ::android::hardware::audio::common::V4_0::AudioInputFlag; +using ::android::hardware::audio::common::V4_0::AudioOutputFlag; +using ::android::hardware::audio::common::V4_0::AudioPatchHandle; +using ::android::hardware::audio::common::V4_0::AudioPort; +using ::android::hardware::audio::common::V4_0::AudioPortConfig; +using ::android::hardware::audio::common::V4_0::AudioMode; +using ::android::hardware::audio::common::V4_0::AudioSource; +using ::android::hardware::audio::common::V4_0::HidlUtils; +using ::android::hardware::audio::common::utils::mkEnumConverter; +using ::android::hardware::audio::V4_0::DeviceAddress; +using ::android::hardware::audio::V4_0::IPrimaryDevice; +using ::android::hardware::audio::V4_0::ParameterValue; +using ::android::hardware::audio::V4_0::Result; +using ::android::hardware::audio::V4_0::SinkMetadata; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; + +namespace android { +namespace V4_0 { + +namespace { + +status_t deviceAddressFromHal( + audio_devices_t device, const char* halAddress, DeviceAddress* address) { + address->device = AudioDevice(device); + + if (halAddress == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) { + return OK; + } + const bool isInput = (device & AUDIO_DEVICE_BIT_IN) != 0; + if (isInput) device &= ~AUDIO_DEVICE_BIT_IN; + if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_A2DP) != 0) + || (isInput && (device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) != 0)) { + int status = sscanf(halAddress, + "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX", + &address->address.mac[0], &address->address.mac[1], &address->address.mac[2], + &address->address.mac[3], &address->address.mac[4], &address->address.mac[5]); + return status == 6 ? OK : BAD_VALUE; + } else if ((!isInput && (device & AUDIO_DEVICE_OUT_IP) != 0) + || (isInput && (device & AUDIO_DEVICE_IN_IP) != 0)) { + int status = sscanf(halAddress, + "%hhu.%hhu.%hhu.%hhu", + &address->address.ipv4[0], &address->address.ipv4[1], + &address->address.ipv4[2], &address->address.ipv4[3]); + return status == 4 ? OK : BAD_VALUE; + } else if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_USB)) != 0 + || (isInput && (device & AUDIO_DEVICE_IN_ALL_USB)) != 0) { + int status = sscanf(halAddress, + "card=%d;device=%d", + &address->address.alsa.card, &address->address.alsa.device); + return status == 2 ? OK : BAD_VALUE; + } else if ((!isInput && (device & AUDIO_DEVICE_OUT_BUS) != 0) + || (isInput && (device & AUDIO_DEVICE_IN_BUS) != 0)) { + if (halAddress != NULL) { + address->busAddress = halAddress; + return OK; + } + return BAD_VALUE; + } else if ((!isInput && (device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) != 0 + || (isInput && (device & AUDIO_DEVICE_IN_REMOTE_SUBMIX) != 0)) { + if (halAddress != NULL) { + address->rSubmixAddress = halAddress; + return OK; + } + return BAD_VALUE; + } + return OK; +} + +} // namespace + +DeviceHalHidl::DeviceHalHidl(const sp<IDevice>& device) + : ConversionHelperHidl("Device"), mDevice(device), + mPrimaryDevice(IPrimaryDevice::castFrom(device)) { +} + +DeviceHalHidl::~DeviceHalHidl() { + if (mDevice != 0) { + mDevice.clear(); + hardware::IPCThreadState::self()->flushCommands(); + } +} + +status_t DeviceHalHidl::getSupportedDevices(uint32_t*) { + // Obsolete. + return INVALID_OPERATION; +} + +status_t DeviceHalHidl::initCheck() { + if (mDevice == 0) return NO_INIT; + return processReturn("initCheck", mDevice->initCheck()); +} + +status_t DeviceHalHidl::setVoiceVolume(float volume) { + if (mDevice == 0) return NO_INIT; + if (mPrimaryDevice == 0) return INVALID_OPERATION; + return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume)); +} + +status_t DeviceHalHidl::setMasterVolume(float volume) { + if (mDevice == 0) return NO_INIT; + if (mPrimaryDevice == 0) return INVALID_OPERATION; + return processReturn("setMasterVolume", mPrimaryDevice->setMasterVolume(volume)); +} + +status_t DeviceHalHidl::getMasterVolume(float *volume) { + if (mDevice == 0) return NO_INIT; + if (mPrimaryDevice == 0) return INVALID_OPERATION; + Result retval; + Return<void> ret = mPrimaryDevice->getMasterVolume( + [&](Result r, float v) { + retval = r; + if (retval == Result::OK) { + *volume = v; + } + }); + return processReturn("getMasterVolume", ret, retval); +} + +status_t DeviceHalHidl::setMode(audio_mode_t mode) { + if (mDevice == 0) return NO_INIT; + if (mPrimaryDevice == 0) return INVALID_OPERATION; + return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode))); +} + +status_t DeviceHalHidl::setMicMute(bool state) { + if (mDevice == 0) return NO_INIT; + return processReturn("setMicMute", mDevice->setMicMute(state)); +} + +status_t DeviceHalHidl::getMicMute(bool *state) { + if (mDevice == 0) return NO_INIT; + Result retval; + Return<void> ret = mDevice->getMicMute( + [&](Result r, bool mute) { + retval = r; + if (retval == Result::OK) { + *state = mute; + } + }); + return processReturn("getMicMute", ret, retval); +} + +status_t DeviceHalHidl::setMasterMute(bool state) { + if (mDevice == 0) return NO_INIT; + return processReturn("setMasterMute", mDevice->setMasterMute(state)); +} + +status_t DeviceHalHidl::getMasterMute(bool *state) { + if (mDevice == 0) return NO_INIT; + Result retval; + Return<void> ret = mDevice->getMasterMute( + [&](Result r, bool mute) { + retval = r; + if (retval == Result::OK) { + *state = mute; + } + }); + return processReturn("getMasterMute", ret, retval); +} + +status_t DeviceHalHidl::setParameters(const String8& kvPairs) { + if (mDevice == 0) return NO_INIT; + hidl_vec<ParameterValue> hidlParams; + status_t status = parametersFromHal(kvPairs, &hidlParams); + if (status != OK) return status; + // TODO: change the API so that context and kvPairs are separated + return processReturn("setParameters", + utils::setParameters(mDevice, {} /* context */, hidlParams)); +} + +status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) { + values->clear(); + if (mDevice == 0) return NO_INIT; + hidl_vec<hidl_string> hidlKeys; + status_t status = keysFromHal(keys, &hidlKeys); + if (status != OK) return status; + Result retval; + Return<void> ret = utils::getParameters(mDevice, + {} /* context */, + hidlKeys, + [&](Result r, const hidl_vec<ParameterValue>& parameters) { + retval = r; + if (retval == Result::OK) { + parametersToHal(parameters, values); + } + }); + return processReturn("getParameters", ret, retval); +} + +status_t DeviceHalHidl::getInputBufferSize( + const struct audio_config *config, size_t *size) { + if (mDevice == 0) return NO_INIT; + AudioConfig hidlConfig; + HidlUtils::audioConfigFromHal(*config, &hidlConfig); + Result retval; + Return<void> ret = mDevice->getInputBufferSize( + hidlConfig, + [&](Result r, uint64_t bufferSize) { + retval = r; + if (retval == Result::OK) { + *size = static_cast<size_t>(bufferSize); + } + }); + return processReturn("getInputBufferSize", ret, retval); +} + +status_t DeviceHalHidl::openOutputStream( + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + const char *address, + sp<StreamOutHalInterface> *outStream) { + if (mDevice == 0) return NO_INIT; + DeviceAddress hidlDevice; + status_t status = deviceAddressFromHal(devices, address, &hidlDevice); + if (status != OK) return status; + AudioConfig hidlConfig; + HidlUtils::audioConfigFromHal(*config, &hidlConfig); + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mDevice->openOutputStream( + handle, + hidlDevice, + hidlConfig, + mkEnumConverter<AudioOutputFlag>(flags), + {} /* metadata */, + [&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) { + retval = r; + if (retval == Result::OK) { + *outStream = new StreamOutHalHidl(result); + } + HidlUtils::audioConfigToHal(suggestedConfig, config); + }); + return processReturn("openOutputStream", ret, retval); +} + +status_t DeviceHalHidl::openInputStream( + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + audio_input_flags_t flags, + const char *address, + audio_source_t source, + sp<StreamInHalInterface> *inStream) { + if (mDevice == 0) return NO_INIT; + DeviceAddress hidlDevice; + status_t status = deviceAddressFromHal(devices, address, &hidlDevice); + if (status != OK) return status; + AudioConfig hidlConfig; + HidlUtils::audioConfigFromHal(*config, &hidlConfig); + Result retval = Result::NOT_INITIALIZED; + // TODO: correctly propagate the tracks sources and volume + // for now, only send the main source at 1dbfs + SinkMetadata metadata = {{{AudioSource(source), 1}}}; + Return<void> ret = mDevice->openInputStream( + handle, + hidlDevice, + hidlConfig, + flags, + metadata, + [&](Result r, const sp<IStreamIn>& result, const AudioConfig& suggestedConfig) { + retval = r; + if (retval == Result::OK) { + *inStream = new StreamInHalHidl(result); + } + HidlUtils::audioConfigToHal(suggestedConfig, config); + }); + return processReturn("openInputStream", ret, retval); +} + +status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) { + if (mDevice == 0) return NO_INIT; + return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches); +} + +status_t DeviceHalHidl::createAudioPatch( + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *patch) { + if (mDevice == 0) return NO_INIT; + hidl_vec<AudioPortConfig> hidlSources, hidlSinks; + HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources); + HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks); + Result retval; + Return<void> ret = mDevice->createAudioPatch( + hidlSources, hidlSinks, + [&](Result r, AudioPatchHandle hidlPatch) { + retval = r; + if (retval == Result::OK) { + *patch = static_cast<audio_patch_handle_t>(hidlPatch); + } + }); + return processReturn("createAudioPatch", ret, retval); +} + +status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) { + if (mDevice == 0) return NO_INIT; + return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch)); +} + +status_t DeviceHalHidl::getAudioPort(struct audio_port *port) { + if (mDevice == 0) return NO_INIT; + AudioPort hidlPort; + HidlUtils::audioPortFromHal(*port, &hidlPort); + Result retval; + Return<void> ret = mDevice->getAudioPort( + hidlPort, + [&](Result r, const AudioPort& p) { + retval = r; + if (retval == Result::OK) { + HidlUtils::audioPortToHal(p, port); + } + }); + return processReturn("getAudioPort", ret, retval); +} + +status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) { + if (mDevice == 0) return NO_INIT; + AudioPortConfig hidlConfig; + HidlUtils::audioPortConfigFromHal(*config, &hidlConfig); + return processReturn("setAudioPortConfig", mDevice->setAudioPortConfig(hidlConfig)); +} + +status_t DeviceHalHidl::getMicrophones(std::vector<media::MicrophoneInfo> *microphonesInfo) { + if (mDevice == 0) return NO_INIT; + Result retval; + Return<void> ret = mDevice->getMicrophones( + [&](Result r, hidl_vec<MicrophoneInfo> micArrayHal) { + retval = r; + for (size_t k = 0; k < micArrayHal.size(); k++) { + audio_microphone_characteristic_t dst; + //convert + microphoneInfoToHal(micArrayHal[k], &dst); + media::MicrophoneInfo microphone = media::MicrophoneInfo(dst); + microphonesInfo->push_back(microphone); + } + }); + return processReturn("getMicrophones", ret, retval); +} + +status_t DeviceHalHidl::dump(int fd) { + if (mDevice == 0) return NO_INIT; + native_handle_t* hidlHandle = native_handle_create(1, 0); + hidlHandle->data[0] = fd; + Return<void> ret = mDevice->debug(hidlHandle, {} /* options */); + native_handle_delete(hidlHandle); + return processReturn("dump", ret); +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/DeviceHalHidl.h b/media/libaudiohal/4.0/DeviceHalHidl.h new file mode 100644 index 0000000..0bd2175 --- /dev/null +++ b/media/libaudiohal/4.0/DeviceHalHidl.h
@@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_DEVICE_HAL_HIDL_4_0_H +#define ANDROID_HARDWARE_DEVICE_HAL_HIDL_4_0_H + +#include <android/hardware/audio/4.0/IDevice.h> +#include <android/hardware/audio/4.0/IPrimaryDevice.h> +#include <media/audiohal/DeviceHalInterface.h> + +#include "ConversionHelperHidl.h" + +using ::android::hardware::audio::V4_0::IDevice; +using ::android::hardware::audio::V4_0::IPrimaryDevice; +using ::android::hardware::Return; + +namespace android { +namespace V4_0 { + +class DeviceHalHidl : public DeviceHalInterface, public ConversionHelperHidl +{ + public: + // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t. + virtual status_t getSupportedDevices(uint32_t *devices); + + // Check to see if the audio hardware interface has been initialized. + virtual status_t initCheck(); + + // Set the audio volume of a voice call. Range is between 0.0 and 1.0. + virtual status_t setVoiceVolume(float volume); + + // Set the audio volume for all audio activities other than voice call. + virtual status_t setMasterVolume(float volume); + + // Get the current master volume value for the HAL. + virtual status_t getMasterVolume(float *volume); + + // Called when the audio mode changes. + virtual status_t setMode(audio_mode_t mode); + + // Muting control. + virtual status_t setMicMute(bool state); + virtual status_t getMicMute(bool *state); + virtual status_t setMasterMute(bool state); + virtual status_t getMasterMute(bool *state); + + // Set global audio parameters. + virtual status_t setParameters(const String8& kvPairs); + + // Get global audio parameters. + virtual status_t getParameters(const String8& keys, String8 *values); + + // Returns audio input buffer size according to parameters passed. + virtual status_t getInputBufferSize(const struct audio_config *config, + size_t *size); + + // Creates and opens the audio hardware output stream. The stream is closed + // by releasing all references to the returned object. + virtual status_t openOutputStream( + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + const char *address, + sp<StreamOutHalInterface> *outStream); + + // Creates and opens the audio hardware input stream. The stream is closed + // by releasing all references to the returned object. + virtual status_t openInputStream( + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + audio_input_flags_t flags, + const char *address, + audio_source_t source, + sp<StreamInHalInterface> *inStream); + + // Returns whether createAudioPatch and releaseAudioPatch operations are supported. + virtual status_t supportsAudioPatches(bool *supportsPatches); + + // Creates an audio patch between several source and sink ports. + virtual status_t createAudioPatch( + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *patch); + + // Releases an audio patch. + virtual status_t releaseAudioPatch(audio_patch_handle_t patch); + + // Fills the list of supported attributes for a given audio port. + virtual status_t getAudioPort(struct audio_port *port); + + // Set audio port configuration. + virtual status_t setAudioPortConfig(const struct audio_port_config *config); + + // List microphones + virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + virtual status_t dump(int fd); + + private: + friend class DevicesFactoryHalHidl; + sp<IDevice> mDevice; + sp<IPrimaryDevice> mPrimaryDevice; // Null if it's not a primary device. + + // Can not be constructed directly by clients. + explicit DeviceHalHidl(const sp<IDevice>& device); + + // The destructor automatically closes the device. + virtual ~DeviceHalHidl(); +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_DEVICE_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DeviceHalLocal.cpp b/media/libaudiohal/4.0/DeviceHalLocal.cpp new file mode 100644 index 0000000..a245dd9 --- /dev/null +++ b/media/libaudiohal/4.0/DeviceHalLocal.cpp
@@ -0,0 +1,213 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DeviceHalLocal" +//#define LOG_NDEBUG 0 + +#include <utils/Log.h> + +#include "DeviceHalLocal.h" +#include "StreamHalLocal.h" + +namespace android { +namespace V4_0 { + +DeviceHalLocal::DeviceHalLocal(audio_hw_device_t *dev) + : mDev(dev) { +} + +DeviceHalLocal::~DeviceHalLocal() { + int status = audio_hw_device_close(mDev); + ALOGW_IF(status, "Error closing audio hw device %p: %s", mDev, strerror(-status)); + mDev = 0; +} + +status_t DeviceHalLocal::getSupportedDevices(uint32_t *devices) { + if (mDev->get_supported_devices == NULL) return INVALID_OPERATION; + *devices = mDev->get_supported_devices(mDev); + return OK; +} + +status_t DeviceHalLocal::initCheck() { + return mDev->init_check(mDev); +} + +status_t DeviceHalLocal::setVoiceVolume(float volume) { + return mDev->set_voice_volume(mDev, volume); +} + +status_t DeviceHalLocal::setMasterVolume(float volume) { + if (mDev->set_master_volume == NULL) return INVALID_OPERATION; + return mDev->set_master_volume(mDev, volume); +} + +status_t DeviceHalLocal::getMasterVolume(float *volume) { + if (mDev->get_master_volume == NULL) return INVALID_OPERATION; + return mDev->get_master_volume(mDev, volume); +} + +status_t DeviceHalLocal::setMode(audio_mode_t mode) { + return mDev->set_mode(mDev, mode); +} + +status_t DeviceHalLocal::setMicMute(bool state) { + return mDev->set_mic_mute(mDev, state); +} + +status_t DeviceHalLocal::getMicMute(bool *state) { + return mDev->get_mic_mute(mDev, state); +} + +status_t DeviceHalLocal::setMasterMute(bool state) { + if (mDev->set_master_mute == NULL) return INVALID_OPERATION; + return mDev->set_master_mute(mDev, state); +} + +status_t DeviceHalLocal::getMasterMute(bool *state) { + if (mDev->get_master_mute == NULL) return INVALID_OPERATION; + return mDev->get_master_mute(mDev, state); +} + +status_t DeviceHalLocal::setParameters(const String8& kvPairs) { + return mDev->set_parameters(mDev, kvPairs.string()); +} + +status_t DeviceHalLocal::getParameters(const String8& keys, String8 *values) { + char *halValues = mDev->get_parameters(mDev, keys.string()); + if (halValues != NULL) { + values->setTo(halValues); + free(halValues); + } else { + values->clear(); + } + return OK; +} + +status_t DeviceHalLocal::getInputBufferSize( + const struct audio_config *config, size_t *size) { + *size = mDev->get_input_buffer_size(mDev, config); + return OK; +} + +status_t DeviceHalLocal::openOutputStream( + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + const char *address, + sp<StreamOutHalInterface> *outStream) { + audio_stream_out_t *halStream; + ALOGV("open_output_stream handle: %d devices: %x flags: %#x" + "srate: %d format %#x channels %x address %s", + handle, devices, flags, + config->sample_rate, config->format, config->channel_mask, + address); + int openResut = mDev->open_output_stream( + mDev, handle, devices, flags, config, &halStream, address); + if (openResut == OK) { + *outStream = new StreamOutHalLocal(halStream, this); + } + ALOGV("open_output_stream status %d stream %p", openResut, halStream); + return openResut; +} + +status_t DeviceHalLocal::openInputStream( + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + audio_input_flags_t flags, + const char *address, + audio_source_t source, + sp<StreamInHalInterface> *inStream) { + audio_stream_in_t *halStream; + ALOGV("open_input_stream handle: %d devices: %x flags: %#x " + "srate: %d format %#x channels %x address %s source %d", + handle, devices, flags, + config->sample_rate, config->format, config->channel_mask, + address, source); + int openResult = mDev->open_input_stream( + mDev, handle, devices, config, &halStream, flags, address, source); + if (openResult == OK) { + *inStream = new StreamInHalLocal(halStream, this); + } + ALOGV("open_input_stream status %d stream %p", openResult, inStream); + return openResult; +} + +status_t DeviceHalLocal::supportsAudioPatches(bool *supportsPatches) { + *supportsPatches = version() >= AUDIO_DEVICE_API_VERSION_3_0; + return OK; +} + +status_t DeviceHalLocal::createAudioPatch( + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *patch) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { + return mDev->create_audio_patch( + mDev, num_sources, sources, num_sinks, sinks, patch); + } else { + return INVALID_OPERATION; + } +} + +status_t DeviceHalLocal::releaseAudioPatch(audio_patch_handle_t patch) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { + return mDev->release_audio_patch(mDev, patch); + } else { + return INVALID_OPERATION; + } +} + +status_t DeviceHalLocal::getAudioPort(struct audio_port *port) { + return mDev->get_audio_port(mDev, port); +} + +status_t DeviceHalLocal::setAudioPortConfig(const struct audio_port_config *config) { + if (version() >= AUDIO_DEVICE_API_VERSION_3_0) + return mDev->set_audio_port_config(mDev, config); + else + return INVALID_OPERATION; +} + +status_t DeviceHalLocal::getMicrophones(std::vector<media::MicrophoneInfo> *microphones) { + if (mDev->get_microphones == NULL) return INVALID_OPERATION; + size_t actual_mics = AUDIO_MICROPHONE_MAX_COUNT; + audio_microphone_characteristic_t mic_array[AUDIO_MICROPHONE_MAX_COUNT]; + status_t status = mDev->get_microphones(mDev, &mic_array[0], &actual_mics); + for (size_t i = 0; i < actual_mics; i++) { + media::MicrophoneInfo microphoneInfo = media::MicrophoneInfo(mic_array[i]); + microphones->push_back(microphoneInfo); + } + return status; +} + +status_t DeviceHalLocal::dump(int fd) { + return mDev->dump(mDev, fd); +} + +void DeviceHalLocal::closeOutputStream(struct audio_stream_out *stream_out) { + mDev->close_output_stream(mDev, stream_out); +} + +void DeviceHalLocal::closeInputStream(struct audio_stream_in *stream_in) { + mDev->close_input_stream(mDev, stream_in); +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/DeviceHalLocal.h b/media/libaudiohal/4.0/DeviceHalLocal.h new file mode 100644 index 0000000..08341a4 --- /dev/null +++ b/media/libaudiohal/4.0/DeviceHalLocal.h
@@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_DEVICE_HAL_LOCAL_4_0_H +#define ANDROID_HARDWARE_DEVICE_HAL_LOCAL_4_0_H + +#include <hardware/audio.h> +#include <media/audiohal/DeviceHalInterface.h> + +namespace android { +namespace V4_0 { + +class DeviceHalLocal : public DeviceHalInterface +{ + public: + // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t. + virtual status_t getSupportedDevices(uint32_t *devices); + + // Check to see if the audio hardware interface has been initialized. + virtual status_t initCheck(); + + // Set the audio volume of a voice call. Range is between 0.0 and 1.0. + virtual status_t setVoiceVolume(float volume); + + // Set the audio volume for all audio activities other than voice call. + virtual status_t setMasterVolume(float volume); + + // Get the current master volume value for the HAL. + virtual status_t getMasterVolume(float *volume); + + // Called when the audio mode changes. + virtual status_t setMode(audio_mode_t mode); + + // Muting control. + virtual status_t setMicMute(bool state); + virtual status_t getMicMute(bool *state); + virtual status_t setMasterMute(bool state); + virtual status_t getMasterMute(bool *state); + + // Set global audio parameters. + virtual status_t setParameters(const String8& kvPairs); + + // Get global audio parameters. + virtual status_t getParameters(const String8& keys, String8 *values); + + // Returns audio input buffer size according to parameters passed. + virtual status_t getInputBufferSize(const struct audio_config *config, + size_t *size); + + // Creates and opens the audio hardware output stream. The stream is closed + // by releasing all references to the returned object. + virtual status_t openOutputStream( + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + const char *address, + sp<StreamOutHalInterface> *outStream); + + // Creates and opens the audio hardware input stream. The stream is closed + // by releasing all references to the returned object. + virtual status_t openInputStream( + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + audio_input_flags_t flags, + const char *address, + audio_source_t source, + sp<StreamInHalInterface> *inStream); + + // Returns whether createAudioPatch and releaseAudioPatch operations are supported. + virtual status_t supportsAudioPatches(bool *supportsPatches); + + // Creates an audio patch between several source and sink ports. + virtual status_t createAudioPatch( + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *patch); + + // Releases an audio patch. + virtual status_t releaseAudioPatch(audio_patch_handle_t patch); + + // Fills the list of supported attributes for a given audio port. + virtual status_t getAudioPort(struct audio_port *port); + + // Set audio port configuration. + virtual status_t setAudioPortConfig(const struct audio_port_config *config); + + // List microphones + virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + virtual status_t dump(int fd); + + void closeOutputStream(struct audio_stream_out *stream_out); + void closeInputStream(struct audio_stream_in *stream_in); + + private: + audio_hw_device_t *mDev; + + friend class DevicesFactoryHalLocal; + + // Can not be constructed directly by clients. + explicit DeviceHalLocal(audio_hw_device_t *dev); + + // The destructor automatically closes the device. + virtual ~DeviceHalLocal(); + + uint32_t version() const { return mDev->common.version; } +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_DEVICE_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp new file mode 100644 index 0000000..c83194e --- /dev/null +++ b/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp
@@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> + +#define LOG_TAG "DevicesFactoryHalHidl" +//#define LOG_NDEBUG 0 + +#include <android/hardware/audio/4.0/IDevice.h> +#include <media/audiohal/hidl/HalDeathHandler.h> +#include <utils/Log.h> + +#include "ConversionHelperHidl.h" +#include "DeviceHalHidl.h" +#include "DevicesFactoryHalHidl.h" + +using ::android::hardware::audio::V4_0::IDevice; +using ::android::hardware::audio::V4_0::Result; +using ::android::hardware::Return; + +namespace android { +namespace V4_0 { + +DevicesFactoryHalHidl::DevicesFactoryHalHidl() { + mDevicesFactory = IDevicesFactory::getService(); + if (mDevicesFactory != 0) { + // It is assumed that DevicesFactory is owned by AudioFlinger + // and thus have the same lifespan. + mDevicesFactory->linkToDeath(HalDeathHandler::getInstance(), 0 /*cookie*/); + } else { + ALOGE("Failed to obtain IDevicesFactory service, terminating process."); + exit(1); + } + // The MSD factory is optional + mDevicesFactoryMsd = IDevicesFactory::getService(AUDIO_HAL_SERVICE_NAME_MSD); + // TODO: Register death handler, and add 'restart' directive to audioserver.rc +} + +DevicesFactoryHalHidl::~DevicesFactoryHalHidl() { +} + +status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) { + if (mDevicesFactory == 0) return NO_INIT; + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mDevicesFactory->openDevice( + name, + [&](Result r, const sp<IDevice>& result) { + retval = r; + if (retval == Result::OK) { + *device = new DeviceHalHidl(result); + } + }); + if (ret.isOk()) { + if (retval == Result::OK) return OK; + else if (retval == Result::INVALID_ARGUMENTS) return BAD_VALUE; + else return NO_INIT; + } + return FAILED_TRANSACTION; +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHidl.h b/media/libaudiohal/4.0/DevicesFactoryHalHidl.h new file mode 100644 index 0000000..114889b --- /dev/null +++ b/media/libaudiohal/4.0/DevicesFactoryHalHidl.h
@@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_4_0_H +#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_4_0_H + +#include <android/hardware/audio/4.0/IDevicesFactory.h> +#include <media/audiohal/DevicesFactoryHalInterface.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +#include "DeviceHalHidl.h" + +using ::android::hardware::audio::V4_0::IDevicesFactory; + +namespace android { +namespace V4_0 { + +class DevicesFactoryHalHidl : public DevicesFactoryHalInterface +{ + public: + // Opens a device with the specified name. To close the device, it is + // necessary to release references to the returned object. + virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device); + + private: + friend class DevicesFactoryHalHybrid; + + sp<IDevicesFactory> mDevicesFactory; + sp<IDevicesFactory> mDevicesFactoryMsd; + + // Can not be constructed directly by clients. + DevicesFactoryHalHidl(); + + virtual ~DevicesFactoryHalHidl(); +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp new file mode 100644 index 0000000..7ff1ec7d --- /dev/null +++ b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
@@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DevicesFactoryHalHybrid" +//#define LOG_NDEBUG 0 + +#include <libaudiohal/4.0/DevicesFactoryHalHybrid.h> +#include "DevicesFactoryHalLocal.h" +#include "DevicesFactoryHalHidl.h" + +namespace android { +namespace V4_0 { + +DevicesFactoryHalHybrid::DevicesFactoryHalHybrid() + : mLocalFactory(new DevicesFactoryHalLocal()), + mHidlFactory(new DevicesFactoryHalHidl()) { +} + +DevicesFactoryHalHybrid::~DevicesFactoryHalHybrid() { +} + +status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) { + if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 && + strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) { + return mHidlFactory->openDevice(name, device); + } + return mLocalFactory->openDevice(name, device); +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp b/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp new file mode 100644 index 0000000..e54edd4 --- /dev/null +++ b/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp
@@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DevicesFactoryHalLocal" +//#define LOG_NDEBUG 0 + +#include <string.h> + +#include <hardware/audio.h> +#include <utils/Log.h> + +#include "DeviceHalLocal.h" +#include "DevicesFactoryHalLocal.h" + +namespace android { +namespace V4_0 { + +static status_t load_audio_interface(const char *if_name, audio_hw_device_t **dev) +{ + const hw_module_t *mod; + int rc; + + rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod); + if (rc) { + ALOGE("%s couldn't load audio hw module %s.%s (%s)", __func__, + AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc)); + goto out; + } + rc = audio_hw_device_open(mod, dev); + if (rc) { + ALOGE("%s couldn't open audio hw device in %s.%s (%s)", __func__, + AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc)); + goto out; + } + if ((*dev)->common.version < AUDIO_DEVICE_API_VERSION_MIN) { + ALOGE("%s wrong audio hw device version %04x", __func__, (*dev)->common.version); + rc = BAD_VALUE; + audio_hw_device_close(*dev); + goto out; + } + return OK; + +out: + *dev = NULL; + return rc; +} + +status_t DevicesFactoryHalLocal::openDevice(const char *name, sp<DeviceHalInterface> *device) { + audio_hw_device_t *dev; + status_t rc = load_audio_interface(name, &dev); + if (rc == OK) { + *device = new DeviceHalLocal(dev); + } + return rc; +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalLocal.h b/media/libaudiohal/4.0/DevicesFactoryHalLocal.h new file mode 100644 index 0000000..bc1c521 --- /dev/null +++ b/media/libaudiohal/4.0/DevicesFactoryHalLocal.h
@@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_DEVICES_FACTORY_HAL_LOCAL_4_0_H +#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_LOCAL_4_0_H + +#include <media/audiohal/DevicesFactoryHalInterface.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +#include "DeviceHalLocal.h" + +namespace android { +namespace V4_0 { + +class DevicesFactoryHalLocal : public DevicesFactoryHalInterface +{ + public: + // Opens a device with the specified name. To close the device, it is + // necessary to release references to the returned object. + virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device); + + private: + friend class DevicesFactoryHalHybrid; + + // Can not be constructed directly by clients. + DevicesFactoryHalLocal() {} + + virtual ~DevicesFactoryHalLocal() {} +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectBufferHalHidl.cpp b/media/libaudiohal/4.0/EffectBufferHalHidl.cpp new file mode 100644 index 0000000..957c89f --- /dev/null +++ b/media/libaudiohal/4.0/EffectBufferHalHidl.cpp
@@ -0,0 +1,146 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <atomic> + +#define LOG_TAG "EffectBufferHalHidl" +//#define LOG_NDEBUG 0 + +#include <android/hidl/allocator/1.0/IAllocator.h> +#include <hidlmemory/mapping.h> +#include <utils/Log.h> + +#include "ConversionHelperHidl.h" +#include "EffectBufferHalHidl.h" + +using ::android::hardware::Return; +using ::android::hidl::allocator::V1_0::IAllocator; + +namespace android { +namespace V4_0 { + +// static +uint64_t EffectBufferHalHidl::makeUniqueId() { + static std::atomic<uint64_t> counter{1}; + return counter++; +} + +status_t EffectBufferHalHidl::allocate( + size_t size, sp<EffectBufferHalInterface>* buffer) { + return mirror(nullptr, size, buffer); +} + +status_t EffectBufferHalHidl::mirror( + void* external, size_t size, sp<EffectBufferHalInterface>* buffer) { + sp<EffectBufferHalInterface> tempBuffer = new EffectBufferHalHidl(size); + status_t result = static_cast<EffectBufferHalHidl*>(tempBuffer.get())->init(); + if (result == OK) { + tempBuffer->setExternalData(external); + *buffer = tempBuffer; + } + return result; +} + +EffectBufferHalHidl::EffectBufferHalHidl(size_t size) + : mBufferSize(size), mFrameCountChanged(false), + mExternalData(nullptr), mAudioBuffer{0, {nullptr}} { + mHidlBuffer.id = makeUniqueId(); + mHidlBuffer.frameCount = 0; +} + +EffectBufferHalHidl::~EffectBufferHalHidl() { +} + +status_t EffectBufferHalHidl::init() { + sp<IAllocator> ashmem = IAllocator::getService("ashmem"); + if (ashmem == 0) { + ALOGE("Failed to retrieve ashmem allocator service"); + return NO_INIT; + } + status_t retval = NO_MEMORY; + Return<void> result = ashmem->allocate( + mBufferSize, + [&](bool success, const hidl_memory& memory) { + if (success) { + mHidlBuffer.data = memory; + retval = OK; + } + }); + if (result.isOk() && retval == OK) { + mMemory = hardware::mapMemory(mHidlBuffer.data); + if (mMemory != 0) { + mMemory->update(); + mAudioBuffer.raw = static_cast<void*>(mMemory->getPointer()); + memset(mAudioBuffer.raw, 0, mMemory->getSize()); + mMemory->commit(); + } else { + ALOGE("Failed to map allocated ashmem"); + retval = NO_MEMORY; + } + } else { + ALOGE("Failed to allocate %d bytes from ashmem", (int)mBufferSize); + } + return result.isOk() ? retval : FAILED_TRANSACTION; +} + +audio_buffer_t* EffectBufferHalHidl::audioBuffer() { + return &mAudioBuffer; +} + +void* EffectBufferHalHidl::externalData() const { + return mExternalData; +} + +void EffectBufferHalHidl::setFrameCount(size_t frameCount) { + mHidlBuffer.frameCount = frameCount; + mAudioBuffer.frameCount = frameCount; + mFrameCountChanged = true; +} + +bool EffectBufferHalHidl::checkFrameCountChange() { + bool result = mFrameCountChanged; + mFrameCountChanged = false; + return result; +} + +void EffectBufferHalHidl::setExternalData(void* external) { + mExternalData = external; +} + +void EffectBufferHalHidl::update() { + update(mBufferSize); +} + +void EffectBufferHalHidl::commit() { + commit(mBufferSize); +} + +void EffectBufferHalHidl::update(size_t size) { + if (mExternalData == nullptr) return; + mMemory->update(); + if (size > mBufferSize) size = mBufferSize; + memcpy(mAudioBuffer.raw, mExternalData, size); + mMemory->commit(); +} + +void EffectBufferHalHidl::commit(size_t size) { + if (mExternalData == nullptr) return; + if (size > mBufferSize) size = mBufferSize; + memcpy(mExternalData, mAudioBuffer.raw, size); +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/EffectBufferHalHidl.h b/media/libaudiohal/4.0/EffectBufferHalHidl.h new file mode 100644 index 0000000..6d578c6 --- /dev/null +++ b/media/libaudiohal/4.0/EffectBufferHalHidl.h
@@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H +#define ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H + +#include <android/hardware/audio/effect/4.0/types.h> +#include <android/hidl/memory/1.0/IMemory.h> +#include <hidl/HidlSupport.h> +#include <media/audiohal/EffectBufferHalInterface.h> +#include <system/audio_effect.h> + +using android::hardware::audio::effect::V4_0::AudioBuffer; +using android::hardware::hidl_memory; +using android::hidl::memory::V1_0::IMemory; + +namespace android { +namespace V4_0 { + +class EffectBufferHalHidl : public EffectBufferHalInterface +{ + public: + static status_t allocate(size_t size, sp<EffectBufferHalInterface>* buffer); + static status_t mirror(void* external, size_t size, sp<EffectBufferHalInterface>* buffer); + + virtual audio_buffer_t* audioBuffer(); + virtual void* externalData() const; + + virtual size_t getSize() const override { return mBufferSize; } + + virtual void setExternalData(void* external); + virtual void setFrameCount(size_t frameCount); + virtual bool checkFrameCountChange(); + + virtual void update(); + virtual void commit(); + virtual void update(size_t size); + virtual void commit(size_t size); + + const AudioBuffer& hidlBuffer() const { return mHidlBuffer; } + + private: + friend class EffectBufferHalInterface; + + static uint64_t makeUniqueId(); + + const size_t mBufferSize; + bool mFrameCountChanged; + void* mExternalData; + AudioBuffer mHidlBuffer; + sp<IMemory> mMemory; + audio_buffer_t mAudioBuffer; + + // Can not be constructed directly by clients. + explicit EffectBufferHalHidl(size_t size); + + virtual ~EffectBufferHalHidl(); + + status_t init(); +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectHalHidl.cpp b/media/libaudiohal/4.0/EffectHalHidl.cpp new file mode 100644 index 0000000..c99c4c8 --- /dev/null +++ b/media/libaudiohal/4.0/EffectHalHidl.cpp
@@ -0,0 +1,342 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "EffectHalHidl" +//#define LOG_NDEBUG 0 + +#include <common/all-versions/VersionUtils.h> +#include <hwbinder/IPCThreadState.h> +#include <media/EffectsFactoryApi.h> +#include <utils/Log.h> + +#include "ConversionHelperHidl.h" +#include "EffectBufferHalHidl.h" +#include "EffectHalHidl.h" +#include "HidlUtils.h" + +using ::android::hardware::audio::effect::V4_0::AudioBuffer; +using ::android::hardware::audio::effect::V4_0::EffectBufferAccess; +using ::android::hardware::audio::effect::V4_0::EffectConfigParameters; +using ::android::hardware::audio::effect::V4_0::MessageQueueFlagBits; +using ::android::hardware::audio::effect::V4_0::Result; +using ::android::hardware::audio::common::V4_0::HidlUtils; +using ::android::hardware::audio::common::V4_0::AudioChannelMask; +using ::android::hardware::audio::common::V4_0::AudioFormat; +using ::android::hardware::audio::common::utils::mkEnumConverter; +using ::android::hardware::hidl_vec; +using ::android::hardware::MQDescriptorSync; +using ::android::hardware::Return; + +namespace android { +namespace V4_0 { + +EffectHalHidl::EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId) + : mEffect(effect), mEffectId(effectId), mBuffersChanged(true), mEfGroup(nullptr) { +} + +EffectHalHidl::~EffectHalHidl() { + if (mEffect != 0) { + close(); + mEffect.clear(); + hardware::IPCThreadState::self()->flushCommands(); + } + if (mEfGroup) { + EventFlag::deleteEventFlag(&mEfGroup); + } +} + +// static +void EffectHalHidl::effectDescriptorToHal( + const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor) { + HidlUtils::uuidToHal(descriptor.type, &halDescriptor->type); + HidlUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid); + halDescriptor->flags = static_cast<uint32_t>(descriptor.flags); + halDescriptor->cpuLoad = descriptor.cpuLoad; + halDescriptor->memoryUsage = descriptor.memoryUsage; + memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size()); + memcpy(halDescriptor->implementor, + descriptor.implementor.data(), descriptor.implementor.size()); +} + +// TODO(mnaganov): These buffer conversion functions should be shared with Effect wrapper +// via HidlUtils. Move them there when hardware/interfaces will get un-frozen again. + +// static +void EffectHalHidl::effectBufferConfigFromHal( + const buffer_config_t& halConfig, EffectBufferConfig* config) { + config->samplingRateHz = halConfig.samplingRate; + config->channels = mkEnumConverter<AudioChannelMask>(halConfig.channels); + config->format = AudioFormat(halConfig.format); + config->accessMode = EffectBufferAccess(halConfig.accessMode); + config->mask = mkEnumConverter<EffectConfigParameters>(halConfig.mask); +} + +// static +void EffectHalHidl::effectBufferConfigToHal( + const EffectBufferConfig& config, buffer_config_t* halConfig) { + halConfig->buffer.frameCount = 0; + halConfig->buffer.raw = NULL; + halConfig->samplingRate = config.samplingRateHz; + halConfig->channels = static_cast<uint32_t>(config.channels); + halConfig->bufferProvider.cookie = NULL; + halConfig->bufferProvider.getBuffer = NULL; + halConfig->bufferProvider.releaseBuffer = NULL; + halConfig->format = static_cast<uint8_t>(config.format); + halConfig->accessMode = static_cast<uint8_t>(config.accessMode); + halConfig->mask = static_cast<uint8_t>(config.mask); +} + +// static +void EffectHalHidl::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) { + effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg); + effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg); +} + +// static +void EffectHalHidl::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) { + effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg); + effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg); +} + +// static +status_t EffectHalHidl::analyzeResult(const Result& result) { + switch (result) { + case Result::OK: return OK; + case Result::INVALID_ARGUMENTS: return BAD_VALUE; + case Result::INVALID_STATE: return NOT_ENOUGH_DATA; + case Result::NOT_INITIALIZED: return NO_INIT; + case Result::NOT_SUPPORTED: return INVALID_OPERATION; + case Result::RESULT_TOO_BIG: return NO_MEMORY; + default: return NO_INIT; + } +} + +status_t EffectHalHidl::setInBuffer(const sp<EffectBufferHalInterface>& buffer) { + if (!mBuffersChanged) { + if (buffer.get() == nullptr || mInBuffer.get() == nullptr) { + mBuffersChanged = buffer.get() != mInBuffer.get(); + } else { + mBuffersChanged = buffer->audioBuffer() != mInBuffer->audioBuffer(); + } + } + mInBuffer = buffer; + return OK; +} + +status_t EffectHalHidl::setOutBuffer(const sp<EffectBufferHalInterface>& buffer) { + if (!mBuffersChanged) { + if (buffer.get() == nullptr || mOutBuffer.get() == nullptr) { + mBuffersChanged = buffer.get() != mOutBuffer.get(); + } else { + mBuffersChanged = buffer->audioBuffer() != mOutBuffer->audioBuffer(); + } + } + mOutBuffer = buffer; + return OK; +} + +status_t EffectHalHidl::process() { + return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS)); +} + +status_t EffectHalHidl::processReverse() { + return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS_REVERSE)); +} + +status_t EffectHalHidl::prepareForProcessing() { + std::unique_ptr<StatusMQ> tempStatusMQ; + Result retval; + Return<void> ret = mEffect->prepareForProcessing( + [&](Result r, const MQDescriptorSync<Result>& statusMQ) { + retval = r; + if (retval == Result::OK) { + tempStatusMQ.reset(new StatusMQ(statusMQ)); + if (tempStatusMQ->isValid() && tempStatusMQ->getEventFlagWord()) { + EventFlag::createEventFlag(tempStatusMQ->getEventFlagWord(), &mEfGroup); + } + } + }); + if (!ret.isOk() || retval != Result::OK) { + return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION; + } + if (!tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) { + ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for effects"); + ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), + "Status message queue for effects is invalid"); + ALOGE_IF(!mEfGroup, "Event flag creation for effects failed"); + return NO_INIT; + } + mStatusMQ = std::move(tempStatusMQ); + return OK; +} + +bool EffectHalHidl::needToResetBuffers() { + if (mBuffersChanged) return true; + bool inBufferFrameCountUpdated = mInBuffer->checkFrameCountChange(); + bool outBufferFrameCountUpdated = mOutBuffer->checkFrameCountChange(); + return inBufferFrameCountUpdated || outBufferFrameCountUpdated; +} + +status_t EffectHalHidl::processImpl(uint32_t mqFlag) { + if (mEffect == 0 || mInBuffer == 0 || mOutBuffer == 0) return NO_INIT; + status_t status; + if (!mStatusMQ && (status = prepareForProcessing()) != OK) { + return status; + } + if (needToResetBuffers() && (status = setProcessBuffers()) != OK) { + return status; + } + // The data is already in the buffers, just need to flush it and wake up the server side. + std::atomic_thread_fence(std::memory_order_release); + mEfGroup->wake(mqFlag); + uint32_t efState = 0; +retry: + status_t ret = mEfGroup->wait( + static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING), &efState); + if (efState & static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING)) { + Result retval = Result::NOT_INITIALIZED; + mStatusMQ->read(&retval); + if (retval == Result::OK || retval == Result::INVALID_STATE) { + // Sync back the changed contents of the buffer. + std::atomic_thread_fence(std::memory_order_acquire); + } + return analyzeResult(retval); + } + if (ret == -EAGAIN || ret == -EINTR) { + // Spurious wakeup. This normally retries no more than once. + goto retry; + } + return ret; +} + +status_t EffectHalHidl::setProcessBuffers() { + Return<Result> ret = mEffect->setProcessBuffers( + static_cast<EffectBufferHalHidl*>(mInBuffer.get())->hidlBuffer(), + static_cast<EffectBufferHalHidl*>(mOutBuffer.get())->hidlBuffer()); + if (ret.isOk() && ret == Result::OK) { + mBuffersChanged = false; + return OK; + } + return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION; +} + +status_t EffectHalHidl::command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, + uint32_t *replySize, void *pReplyData) { + if (mEffect == 0) return NO_INIT; + + // Special cases. + if (cmdCode == EFFECT_CMD_SET_CONFIG || cmdCode == EFFECT_CMD_SET_CONFIG_REVERSE) { + return setConfigImpl(cmdCode, cmdSize, pCmdData, replySize, pReplyData); + } else if (cmdCode == EFFECT_CMD_GET_CONFIG || cmdCode == EFFECT_CMD_GET_CONFIG_REVERSE) { + return getConfigImpl(cmdCode, replySize, pReplyData); + } + + // Common case. + hidl_vec<uint8_t> hidlData; + if (pCmdData != nullptr && cmdSize > 0) { + hidlData.setToExternal(reinterpret_cast<uint8_t*>(pCmdData), cmdSize); + } + status_t status; + uint32_t replySizeStub = 0; + if (replySize == nullptr || pReplyData == nullptr) replySize = &replySizeStub; + Return<void> ret = mEffect->command(cmdCode, hidlData, *replySize, + [&](int32_t s, const hidl_vec<uint8_t>& result) { + status = s; + if (status == 0) { + if (*replySize > result.size()) *replySize = result.size(); + if (pReplyData != nullptr && *replySize > 0) { + memcpy(pReplyData, &result[0], *replySize); + } + } + }); + return ret.isOk() ? status : FAILED_TRANSACTION; +} + +status_t EffectHalHidl::getDescriptor(effect_descriptor_t *pDescriptor) { + if (mEffect == 0) return NO_INIT; + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mEffect->getDescriptor( + [&](Result r, const EffectDescriptor& result) { + retval = r; + if (retval == Result::OK) { + effectDescriptorToHal(result, pDescriptor); + } + }); + return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION; +} + +status_t EffectHalHidl::close() { + if (mEffect == 0) return NO_INIT; + Return<Result> ret = mEffect->close(); + return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION; +} + +status_t EffectHalHidl::getConfigImpl( + uint32_t cmdCode, uint32_t *replySize, void *pReplyData) { + if (replySize == NULL || *replySize != sizeof(effect_config_t) || pReplyData == NULL) { + return BAD_VALUE; + } + status_t result = FAILED_TRANSACTION; + Return<void> ret; + if (cmdCode == EFFECT_CMD_GET_CONFIG) { + ret = mEffect->getConfig([&] (Result r, const EffectConfig &hidlConfig) { + result = analyzeResult(r); + if (r == Result::OK) { + effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData)); + } + }); + } else { + ret = mEffect->getConfigReverse([&] (Result r, const EffectConfig &hidlConfig) { + result = analyzeResult(r); + if (r == Result::OK) { + effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData)); + } + }); + } + if (!ret.isOk()) { + result = FAILED_TRANSACTION; + } + return result; +} + +status_t EffectHalHidl::setConfigImpl( + uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) { + if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) || + replySize == NULL || *replySize != sizeof(int32_t) || pReplyData == NULL) { + return BAD_VALUE; + } + const effect_config_t *halConfig = static_cast<effect_config_t*>(pCmdData); + if (halConfig->inputCfg.bufferProvider.getBuffer != NULL || + halConfig->inputCfg.bufferProvider.releaseBuffer != NULL || + halConfig->outputCfg.bufferProvider.getBuffer != NULL || + halConfig->outputCfg.bufferProvider.releaseBuffer != NULL) { + ALOGE("Buffer provider callbacks are not supported"); + } + EffectConfig hidlConfig; + effectConfigFromHal(*halConfig, &hidlConfig); + Return<Result> ret = cmdCode == EFFECT_CMD_SET_CONFIG ? + mEffect->setConfig(hidlConfig, nullptr, nullptr) : + mEffect->setConfigReverse(hidlConfig, nullptr, nullptr); + status_t result = FAILED_TRANSACTION; + if (ret.isOk()) { + result = analyzeResult(ret); + *static_cast<int32_t*>(pReplyData) = result; + } + return result; +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/EffectHalHidl.h b/media/libaudiohal/4.0/EffectHalHidl.h new file mode 100644 index 0000000..5a4dab1 --- /dev/null +++ b/media/libaudiohal/4.0/EffectHalHidl.h
@@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_EFFECT_HAL_HIDL_4_0_H +#define ANDROID_HARDWARE_EFFECT_HAL_HIDL_4_0_H + +#include <android/hardware/audio/effect/4.0/IEffect.h> +#include <media/audiohal/EffectHalInterface.h> +#include <fmq/EventFlag.h> +#include <fmq/MessageQueue.h> +#include <system/audio_effect.h> + +using ::android::hardware::audio::effect::V4_0::EffectBufferConfig; +using ::android::hardware::audio::effect::V4_0::EffectConfig; +using ::android::hardware::audio::effect::V4_0::EffectDescriptor; +using ::android::hardware::audio::effect::V4_0::IEffect; +using ::android::hardware::EventFlag; +using ::android::hardware::MessageQueue; + +namespace android { +namespace V4_0 { + +class EffectHalHidl : public EffectHalInterface +{ + public: + // Set the input buffer. + virtual status_t setInBuffer(const sp<EffectBufferHalInterface>& buffer); + + // Set the output buffer. + virtual status_t setOutBuffer(const sp<EffectBufferHalInterface>& buffer); + + // Effect process function. + virtual status_t process(); + + // Process reverse stream function. This function is used to pass + // a reference stream to the effect engine. + virtual status_t processReverse(); + + // Send a command and receive a response to/from effect engine. + virtual status_t command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, + uint32_t *replySize, void *pReplyData); + + // Returns the effect descriptor. + virtual status_t getDescriptor(effect_descriptor_t *pDescriptor); + + // Free resources on the remote side. + virtual status_t close(); + + // Whether it's a local implementation. + virtual bool isLocal() const { return false; } + + uint64_t effectId() const { return mEffectId; } + + static void effectDescriptorToHal( + const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor); + + private: + friend class EffectsFactoryHalHidl; + typedef MessageQueue< + hardware::audio::effect::V4_0::Result, hardware::kSynchronizedReadWrite> StatusMQ; + + sp<IEffect> mEffect; + const uint64_t mEffectId; + sp<EffectBufferHalInterface> mInBuffer; + sp<EffectBufferHalInterface> mOutBuffer; + bool mBuffersChanged; + std::unique_ptr<StatusMQ> mStatusMQ; + EventFlag* mEfGroup; + + static status_t analyzeResult(const hardware::audio::effect::V4_0::Result& result); + static void effectBufferConfigFromHal( + const buffer_config_t& halConfig, EffectBufferConfig* config); + static void effectBufferConfigToHal( + const EffectBufferConfig& config, buffer_config_t* halConfig); + static void effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config); + static void effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig); + + // Can not be constructed directly by clients. + EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId); + + // The destructor automatically releases the effect. + virtual ~EffectHalHidl(); + + status_t getConfigImpl(uint32_t cmdCode, uint32_t *replySize, void *pReplyData); + status_t prepareForProcessing(); + bool needToResetBuffers(); + status_t processImpl(uint32_t mqFlag); + status_t setConfigImpl( + uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, + uint32_t *replySize, void *pReplyData); + status_t setProcessBuffers(); +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_EFFECT_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp b/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp new file mode 100644 index 0000000..dfed784 --- /dev/null +++ b/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp
@@ -0,0 +1,152 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "EffectsFactoryHalHidl" +//#define LOG_NDEBUG 0 + +#include <cutils/native_handle.h> +#include <libaudiohal/4.0/EffectsFactoryHalHidl.h> + +#include "ConversionHelperHidl.h" +#include "EffectBufferHalHidl.h" +#include "EffectHalHidl.h" +#include "HidlUtils.h" + +using ::android::hardware::audio::common::V4_0::HidlUtils; +using ::android::hardware::audio::common::V4_0::Uuid; +using ::android::hardware::audio::effect::V4_0::IEffect; +using ::android::hardware::audio::effect::V4_0::Result; +using ::android::hardware::Return; + +namespace android { +namespace V4_0 { + +EffectsFactoryHalHidl::EffectsFactoryHalHidl() : ConversionHelperHidl("EffectsFactory") { + mEffectsFactory = IEffectsFactory::getService(); + if (mEffectsFactory == 0) { + ALOGE("Failed to obtain IEffectsFactory service, terminating process."); + exit(1); + } +} + +EffectsFactoryHalHidl::~EffectsFactoryHalHidl() { +} + +status_t EffectsFactoryHalHidl::queryAllDescriptors() { + if (mEffectsFactory == 0) return NO_INIT; + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mEffectsFactory->getAllDescriptors( + [&](Result r, const hidl_vec<EffectDescriptor>& result) { + retval = r; + if (retval == Result::OK) { + mLastDescriptors = result; + } + }); + if (ret.isOk()) { + return retval == Result::OK ? OK : NO_INIT; + } + mLastDescriptors.resize(0); + return processReturn(__FUNCTION__, ret); +} + +status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) { + status_t queryResult = queryAllDescriptors(); + if (queryResult == OK) { + *pNumEffects = mLastDescriptors.size(); + } + return queryResult; +} + +status_t EffectsFactoryHalHidl::getDescriptor( + uint32_t index, effect_descriptor_t *pDescriptor) { + // TODO: We need somehow to track the changes on the server side + // or figure out how to convert everybody to query all the descriptors at once. + // TODO: check for nullptr + if (mLastDescriptors.size() == 0) { + status_t queryResult = queryAllDescriptors(); + if (queryResult != OK) return queryResult; + } + if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND; + EffectHalHidl::effectDescriptorToHal(mLastDescriptors[index], pDescriptor); + return OK; +} + +status_t EffectsFactoryHalHidl::getDescriptor( + const effect_uuid_t *pEffectUuid, effect_descriptor_t *pDescriptor) { + // TODO: check for nullptr + if (mEffectsFactory == 0) return NO_INIT; + Uuid hidlUuid; + HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid); + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mEffectsFactory->getDescriptor(hidlUuid, + [&](Result r, const EffectDescriptor& result) { + retval = r; + if (retval == Result::OK) { + EffectHalHidl::effectDescriptorToHal(result, pDescriptor); + } + }); + if (ret.isOk()) { + if (retval == Result::OK) return OK; + else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND; + else return NO_INIT; + } + return processReturn(__FUNCTION__, ret); +} + +status_t EffectsFactoryHalHidl::createEffect( + const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t ioId, + sp<EffectHalInterface> *effect) { + if (mEffectsFactory == 0) return NO_INIT; + Uuid hidlUuid; + HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid); + Result retval = Result::NOT_INITIALIZED; + Return<void> ret = mEffectsFactory->createEffect( + hidlUuid, sessionId, ioId, + [&](Result r, const sp<IEffect>& result, uint64_t effectId) { + retval = r; + if (retval == Result::OK) { + *effect = new EffectHalHidl(result, effectId); + } + }); + if (ret.isOk()) { + if (retval == Result::OK) return OK; + else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND; + else return NO_INIT; + } + return processReturn(__FUNCTION__, ret); +} + +status_t EffectsFactoryHalHidl::dumpEffects(int fd) { + if (mEffectsFactory == 0) return NO_INIT; + native_handle_t* hidlHandle = native_handle_create(1, 0); + hidlHandle->data[0] = fd; + Return<void> ret = mEffectsFactory->debug(hidlHandle, {} /* options */); + native_handle_delete(hidlHandle); + return processReturn(__FUNCTION__, ret); +} + +status_t EffectsFactoryHalHidl::allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) { + return EffectBufferHalHidl::allocate(size, buffer); +} + +status_t EffectsFactoryHalHidl::mirrorBuffer(void* external, size_t size, + sp<EffectBufferHalInterface>* buffer) { + return EffectBufferHalHidl::mirror(external, size, buffer); +} + + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalHidl.cpp b/media/libaudiohal/4.0/StreamHalHidl.cpp new file mode 100644 index 0000000..1c2fdb0 --- /dev/null +++ b/media/libaudiohal/4.0/StreamHalHidl.cpp
@@ -0,0 +1,817 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "StreamHalHidl" +//#define LOG_NDEBUG 0 + +#include <android/hardware/audio/4.0/IStreamOutCallback.h> +#include <hwbinder/IPCThreadState.h> +#include <mediautils/SchedulingPolicyService.h> +#include <utils/Log.h> + +#include "DeviceHalHidl.h" +#include "EffectHalHidl.h" +#include "StreamHalHidl.h" +#include "VersionUtils.h" + +using ::android::hardware::audio::common::V4_0::AudioChannelMask; +using ::android::hardware::audio::common::V4_0::AudioContentType; +using ::android::hardware::audio::common::V4_0::AudioFormat; +using ::android::hardware::audio::common::V4_0::AudioSource; +using ::android::hardware::audio::common::V4_0::AudioUsage; +using ::android::hardware::audio::common::V4_0::ThreadInfo; +using ::android::hardware::audio::V4_0::AudioDrain; +using ::android::hardware::audio::V4_0::IStreamOutCallback; +using ::android::hardware::audio::V4_0::MessageQueueFlagBits; +using ::android::hardware::audio::V4_0::MicrophoneInfo; +using ::android::hardware::audio::V4_0::MmapBufferInfo; +using ::android::hardware::audio::V4_0::MmapPosition; +using ::android::hardware::audio::V4_0::ParameterValue; +using ::android::hardware::audio::V4_0::PlaybackTrackMetadata; +using ::android::hardware::audio::V4_0::RecordTrackMetadata; +using ::android::hardware::audio::V4_0::Result; +using ::android::hardware::audio::V4_0::TimeSpec; +using ::android::hardware::MQDescriptorSync; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ReadCommand = ::android::hardware::audio::V4_0::IStreamIn::ReadCommand; + +namespace android { +namespace V4_0 { + +StreamHalHidl::StreamHalHidl(IStream *stream) + : ConversionHelperHidl("Stream"), + mStream(stream), + mHalThreadPriority(HAL_THREAD_PRIORITY_DEFAULT), + mCachedBufferSize(0){ + + // Instrument audio signal power logging. + // Note: This assumes channel mask, format, and sample rate do not change after creation. + if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) { + // Obtain audio properties (see StreamHalHidl::getAudioProperties() below). + Return<void> ret = mStream->getAudioProperties( + [&](auto sr, auto m, auto f) { + mStreamPowerLog.init(sr, + static_cast<audio_channel_mask_t>(m), + static_cast<audio_format_t>(f)); + }); + } +} + +StreamHalHidl::~StreamHalHidl() { + mStream = nullptr; +} + +status_t StreamHalHidl::getSampleRate(uint32_t *rate) { + if (!mStream) return NO_INIT; + return processReturn("getSampleRate", mStream->getSampleRate(), rate); +} + +status_t StreamHalHidl::getBufferSize(size_t *size) { + if (!mStream) return NO_INIT; + status_t status = processReturn("getBufferSize", mStream->getBufferSize(), size); + if (status == OK) { + mCachedBufferSize = *size; + } + return status; +} + +status_t StreamHalHidl::getChannelMask(audio_channel_mask_t *mask) { + if (!mStream) return NO_INIT; + return processReturn("getChannelMask", mStream->getChannelMask(), mask); +} + +status_t StreamHalHidl::getFormat(audio_format_t *format) { + if (!mStream) return NO_INIT; + return processReturn("getFormat", mStream->getFormat(), format); +} + +status_t StreamHalHidl::getAudioProperties( + uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) { + if (!mStream) return NO_INIT; + Return<void> ret = mStream->getAudioProperties( + [&](uint32_t sr, auto m, auto f) { + *sampleRate = sr; + *mask = static_cast<audio_channel_mask_t>(m); + *format = static_cast<audio_format_t>(f); + }); + return processReturn("getAudioProperties", ret); +} + +status_t StreamHalHidl::setParameters(const String8& kvPairs) { + if (!mStream) return NO_INIT; + hidl_vec<ParameterValue> hidlParams; + status_t status = parametersFromHal(kvPairs, &hidlParams); + if (status != OK) return status; + return processReturn("setParameters", + utils::setParameters(mStream, hidlParams, {} /* options */)); +} + +status_t StreamHalHidl::getParameters(const String8& keys, String8 *values) { + values->clear(); + if (!mStream) return NO_INIT; + hidl_vec<hidl_string> hidlKeys; + status_t status = keysFromHal(keys, &hidlKeys); + if (status != OK) return status; + Result retval; + Return<void> ret = utils::getParameters( + mStream, + {} /* context */, + hidlKeys, + [&](Result r, const hidl_vec<ParameterValue>& parameters) { + retval = r; + if (retval == Result::OK) { + parametersToHal(parameters, values); + } + }); + return processReturn("getParameters", ret, retval); +} + +status_t StreamHalHidl::addEffect(sp<EffectHalInterface> effect) { + if (!mStream) return NO_INIT; + return processReturn("addEffect", mStream->addEffect( + static_cast<EffectHalHidl*>(effect.get())->effectId())); +} + +status_t StreamHalHidl::removeEffect(sp<EffectHalInterface> effect) { + if (!mStream) return NO_INIT; + return processReturn("removeEffect", mStream->removeEffect( + static_cast<EffectHalHidl*>(effect.get())->effectId())); +} + +status_t StreamHalHidl::standby() { + if (!mStream) return NO_INIT; + return processReturn("standby", mStream->standby()); +} + +status_t StreamHalHidl::dump(int fd) { + if (!mStream) return NO_INIT; + native_handle_t* hidlHandle = native_handle_create(1, 0); + hidlHandle->data[0] = fd; + Return<void> ret = mStream->debug(hidlHandle, {} /* options */); + native_handle_delete(hidlHandle); + mStreamPowerLog.dump(fd); + return processReturn("dump", ret); +} + +status_t StreamHalHidl::start() { + if (!mStream) return NO_INIT; + return processReturn("start", mStream->start()); +} + +status_t StreamHalHidl::stop() { + if (!mStream) return NO_INIT; + return processReturn("stop", mStream->stop()); +} + +status_t StreamHalHidl::createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info) { + Result retval; + Return<void> ret = mStream->createMmapBuffer( + minSizeFrames, + [&](Result r, const MmapBufferInfo& hidlInfo) { + retval = r; + if (retval == Result::OK) { + const native_handle *handle = hidlInfo.sharedMemory.handle(); + if (handle->numFds > 0) { + info->shared_memory_fd = handle->data[0]; + info->buffer_size_frames = hidlInfo.bufferSizeFrames; + info->burst_size_frames = hidlInfo.burstSizeFrames; + // info->shared_memory_address is not needed in HIDL context + info->shared_memory_address = NULL; + } else { + retval = Result::NOT_INITIALIZED; + } + } + }); + return processReturn("createMmapBuffer", ret, retval); +} + +status_t StreamHalHidl::getMmapPosition(struct audio_mmap_position *position) { + Result retval; + Return<void> ret = mStream->getMmapPosition( + [&](Result r, const MmapPosition& hidlPosition) { + retval = r; + if (retval == Result::OK) { + position->time_nanoseconds = hidlPosition.timeNanoseconds; + position->position_frames = hidlPosition.positionFrames; + } + }); + return processReturn("getMmapPosition", ret, retval); +} + +status_t StreamHalHidl::setHalThreadPriority(int priority) { + mHalThreadPriority = priority; + return OK; +} + +status_t StreamHalHidl::getCachedBufferSize(size_t *size) { + if (mCachedBufferSize != 0) { + *size = mCachedBufferSize; + return OK; + } + return getBufferSize(size); +} + +bool StreamHalHidl::requestHalThreadPriority(pid_t threadPid, pid_t threadId) { + if (mHalThreadPriority == HAL_THREAD_PRIORITY_DEFAULT) { + return true; + } + int err = requestPriority( + threadPid, threadId, + mHalThreadPriority, false /*isForApp*/, true /*asynchronous*/); + ALOGE_IF(err, "failed to set priority %d for pid %d tid %d; error %d", + mHalThreadPriority, threadPid, threadId, err); + // Audio will still work, but latency will be higher and sometimes unacceptable. + return err == 0; +} + +namespace { + +/* Notes on callback ownership. + +This is how (Hw)Binder ownership model looks like. The server implementation +is owned by Binder framework (via sp<>). Proxies are owned by clients. +When the last proxy disappears, Binder framework releases the server impl. + +Thus, it is not needed to keep any references to StreamOutCallback (this is +the server impl) -- it will live as long as HAL server holds a strong ref to +IStreamOutCallback proxy. We clear that reference by calling 'clearCallback' +from the destructor of StreamOutHalHidl. + +The callback only keeps a weak reference to the stream. The stream is owned +by AudioFlinger. + +*/ + +struct StreamOutCallback : public IStreamOutCallback { + StreamOutCallback(const wp<StreamOutHalHidl>& stream) : mStream(stream) {} + + // IStreamOutCallback implementation + Return<void> onWriteReady() override { + sp<StreamOutHalHidl> stream = mStream.promote(); + if (stream != 0) { + stream->onWriteReady(); + } + return Void(); + } + + Return<void> onDrainReady() override { + sp<StreamOutHalHidl> stream = mStream.promote(); + if (stream != 0) { + stream->onDrainReady(); + } + return Void(); + } + + Return<void> onError() override { + sp<StreamOutHalHidl> stream = mStream.promote(); + if (stream != 0) { + stream->onError(); + } + return Void(); + } + + private: + wp<StreamOutHalHidl> mStream; +}; + +} // namespace + +StreamOutHalHidl::StreamOutHalHidl(const sp<IStreamOut>& stream) + : StreamHalHidl(stream.get()), mStream(stream), mWriterClient(0), mEfGroup(nullptr) { +} + +StreamOutHalHidl::~StreamOutHalHidl() { + if (mStream != 0) { + if (mCallback.unsafe_get()) { + processReturn("clearCallback", mStream->clearCallback()); + } + processReturn("close", mStream->close()); + mStream.clear(); + } + mCallback.clear(); + hardware::IPCThreadState::self()->flushCommands(); + if (mEfGroup) { + EventFlag::deleteEventFlag(&mEfGroup); + } +} + +status_t StreamOutHalHidl::getFrameSize(size_t *size) { + if (mStream == 0) return NO_INIT; + return processReturn("getFrameSize", mStream->getFrameSize(), size); +} + +status_t StreamOutHalHidl::getLatency(uint32_t *latency) { + if (mStream == 0) return NO_INIT; + if (mWriterClient == gettid() && mCommandMQ) { + return callWriterThread( + WriteCommand::GET_LATENCY, "getLatency", nullptr, 0, + [&](const WriteStatus& writeStatus) { + *latency = writeStatus.reply.latencyMs; + }); + } else { + return processReturn("getLatency", mStream->getLatency(), latency); + } +} + +status_t StreamOutHalHidl::setVolume(float left, float right) { + if (mStream == 0) return NO_INIT; + return processReturn("setVolume", mStream->setVolume(left, right)); +} + +status_t StreamOutHalHidl::write(const void *buffer, size_t bytes, size_t *written) { + if (mStream == 0) return NO_INIT; + *written = 0; + + if (bytes == 0 && !mDataMQ) { + // Can't determine the size for the MQ buffer. Wait for a non-empty write request. + ALOGW_IF(mCallback.unsafe_get(), "First call to async write with 0 bytes"); + return OK; + } + + status_t status; + if (!mDataMQ) { + // In case if playback starts close to the end of a compressed track, the bytes + // that need to be written is less than the actual buffer size. Need to use + // full buffer size for the MQ since otherwise after seeking back to the middle + // data will be truncated. + size_t bufferSize; + if ((status = getCachedBufferSize(&bufferSize)) != OK) { + return status; + } + if (bytes > bufferSize) bufferSize = bytes; + if ((status = prepareForWriting(bufferSize)) != OK) { + return status; + } + } + + status = callWriterThread( + WriteCommand::WRITE, "write", static_cast<const uint8_t*>(buffer), bytes, + [&] (const WriteStatus& writeStatus) { + *written = writeStatus.reply.written; + // Diagnostics of the cause of b/35813113. + ALOGE_IF(*written > bytes, + "hal reports more bytes written than asked for: %lld > %lld", + (long long)*written, (long long)bytes); + }); + mStreamPowerLog.log(buffer, *written); + return status; +} + +status_t StreamOutHalHidl::callWriterThread( + WriteCommand cmd, const char* cmdName, + const uint8_t* data, size_t dataSize, StreamOutHalHidl::WriterCallback callback) { + if (!mCommandMQ->write(&cmd)) { + ALOGE("command message queue write failed for \"%s\"", cmdName); + return -EAGAIN; + } + if (data != nullptr) { + size_t availableToWrite = mDataMQ->availableToWrite(); + if (dataSize > availableToWrite) { + ALOGW("truncating write data from %lld to %lld due to insufficient data queue space", + (long long)dataSize, (long long)availableToWrite); + dataSize = availableToWrite; + } + if (!mDataMQ->write(data, dataSize)) { + ALOGE("data message queue write failed for \"%s\"", cmdName); + } + } + mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)); + + // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422 + uint32_t efState = 0; +retry: + status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL), &efState); + if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)) { + WriteStatus writeStatus; + writeStatus.retval = Result::NOT_INITIALIZED; + if (!mStatusMQ->read(&writeStatus)) { + ALOGE("status message read failed for \"%s\"", cmdName); + } + if (writeStatus.retval == Result::OK) { + ret = OK; + callback(writeStatus); + } else { + ret = processReturn(cmdName, writeStatus.retval); + } + return ret; + } + if (ret == -EAGAIN || ret == -EINTR) { + // Spurious wakeup. This normally retries no more than once. + goto retry; + } + return ret; +} + +status_t StreamOutHalHidl::prepareForWriting(size_t bufferSize) { + std::unique_ptr<CommandMQ> tempCommandMQ; + std::unique_ptr<DataMQ> tempDataMQ; + std::unique_ptr<StatusMQ> tempStatusMQ; + Result retval; + pid_t halThreadPid, halThreadTid; + Return<void> ret = mStream->prepareForWriting( + 1, bufferSize, + [&](Result r, + const CommandMQ::Descriptor& commandMQ, + const DataMQ::Descriptor& dataMQ, + const StatusMQ::Descriptor& statusMQ, + const ThreadInfo& halThreadInfo) { + retval = r; + if (retval == Result::OK) { + tempCommandMQ.reset(new CommandMQ(commandMQ)); + tempDataMQ.reset(new DataMQ(dataMQ)); + tempStatusMQ.reset(new StatusMQ(statusMQ)); + if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) { + EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup); + } + halThreadPid = halThreadInfo.pid; + halThreadTid = halThreadInfo.tid; + } + }); + if (!ret.isOk() || retval != Result::OK) { + return processReturn("prepareForWriting", ret, retval); + } + if (!tempCommandMQ || !tempCommandMQ->isValid() || + !tempDataMQ || !tempDataMQ->isValid() || + !tempStatusMQ || !tempStatusMQ->isValid() || + !mEfGroup) { + ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing"); + ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(), + "Command message queue for writing is invalid"); + ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for writing"); + ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for writing is invalid"); + ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for writing"); + ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), + "Status message queue for writing is invalid"); + ALOGE_IF(!mEfGroup, "Event flag creation for writing failed"); + return NO_INIT; + } + requestHalThreadPriority(halThreadPid, halThreadTid); + + mCommandMQ = std::move(tempCommandMQ); + mDataMQ = std::move(tempDataMQ); + mStatusMQ = std::move(tempStatusMQ); + mWriterClient = gettid(); + return OK; +} + +status_t StreamOutHalHidl::getRenderPosition(uint32_t *dspFrames) { + if (mStream == 0) return NO_INIT; + Result retval; + Return<void> ret = mStream->getRenderPosition( + [&](Result r, uint32_t d) { + retval = r; + if (retval == Result::OK) { + *dspFrames = d; + } + }); + return processReturn("getRenderPosition", ret, retval); +} + +status_t StreamOutHalHidl::getNextWriteTimestamp(int64_t *timestamp) { + if (mStream == 0) return NO_INIT; + Result retval; + Return<void> ret = mStream->getNextWriteTimestamp( + [&](Result r, int64_t t) { + retval = r; + if (retval == Result::OK) { + *timestamp = t; + } + }); + return processReturn("getRenderPosition", ret, retval); +} + +status_t StreamOutHalHidl::setCallback(wp<StreamOutHalInterfaceCallback> callback) { + if (mStream == 0) return NO_INIT; + status_t status = processReturn( + "setCallback", mStream->setCallback(new StreamOutCallback(this))); + if (status == OK) { + mCallback = callback; + } + return status; +} + +status_t StreamOutHalHidl::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) { + if (mStream == 0) return NO_INIT; + Return<void> ret = mStream->supportsPauseAndResume( + [&](bool p, bool r) { + *supportsPause = p; + *supportsResume = r; + }); + return processReturn("supportsPauseAndResume", ret); +} + +status_t StreamOutHalHidl::pause() { + if (mStream == 0) return NO_INIT; + return processReturn("pause", mStream->pause()); +} + +status_t StreamOutHalHidl::resume() { + if (mStream == 0) return NO_INIT; + return processReturn("pause", mStream->resume()); +} + +status_t StreamOutHalHidl::supportsDrain(bool *supportsDrain) { + if (mStream == 0) return NO_INIT; + return processReturn("supportsDrain", mStream->supportsDrain(), supportsDrain); +} + +status_t StreamOutHalHidl::drain(bool earlyNotify) { + if (mStream == 0) return NO_INIT; + return processReturn( + "drain", mStream->drain(earlyNotify ? AudioDrain::EARLY_NOTIFY : AudioDrain::ALL)); +} + +status_t StreamOutHalHidl::flush() { + if (mStream == 0) return NO_INIT; + return processReturn("pause", mStream->flush()); +} + +status_t StreamOutHalHidl::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) { + if (mStream == 0) return NO_INIT; + if (mWriterClient == gettid() && mCommandMQ) { + return callWriterThread( + WriteCommand::GET_PRESENTATION_POSITION, "getPresentationPosition", nullptr, 0, + [&](const WriteStatus& writeStatus) { + *frames = writeStatus.reply.presentationPosition.frames; + timestamp->tv_sec = writeStatus.reply.presentationPosition.timeStamp.tvSec; + timestamp->tv_nsec = writeStatus.reply.presentationPosition.timeStamp.tvNSec; + }); + } else { + Result retval; + Return<void> ret = mStream->getPresentationPosition( + [&](Result r, uint64_t hidlFrames, const TimeSpec& hidlTimeStamp) { + retval = r; + if (retval == Result::OK) { + *frames = hidlFrames; + timestamp->tv_sec = hidlTimeStamp.tvSec; + timestamp->tv_nsec = hidlTimeStamp.tvNSec; + } + }); + return processReturn("getPresentationPosition", ret, retval); + } +} + +/** Transform a standard collection to an HIDL vector. */ +template <class Values, class ElementConverter> +static auto transformToHidlVec(const Values& values, ElementConverter converter) { + hidl_vec<decltype(converter(*values.begin()))> result{values.size()}; + using namespace std; + transform(begin(values), end(values), begin(result), converter); + return result; +} + +status_t StreamOutHalHidl::updateSourceMetadata(const SourceMetadata& sourceMetadata) { + hardware::audio::V4_0::SourceMetadata halMetadata = { + .tracks = transformToHidlVec(sourceMetadata.tracks, + [](const playback_track_metadata& metadata) -> PlaybackTrackMetadata { + return { + .usage=static_cast<AudioUsage>(metadata.usage), + .contentType=static_cast<AudioContentType>(metadata.content_type), + .gain=metadata.gain, + }; + })}; + return processReturn("updateSourceMetadata", mStream->updateSourceMetadata(halMetadata)); +} + +void StreamOutHalHidl::onWriteReady() { + sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); + if (callback == 0) return; + ALOGV("asyncCallback onWriteReady"); + callback->onWriteReady(); +} + +void StreamOutHalHidl::onDrainReady() { + sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); + if (callback == 0) return; + ALOGV("asyncCallback onDrainReady"); + callback->onDrainReady(); +} + +void StreamOutHalHidl::onError() { + sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); + if (callback == 0) return; + ALOGV("asyncCallback onError"); + callback->onError(); +} + + +StreamInHalHidl::StreamInHalHidl(const sp<IStreamIn>& stream) + : StreamHalHidl(stream.get()), mStream(stream), mReaderClient(0), mEfGroup(nullptr) { +} + +StreamInHalHidl::~StreamInHalHidl() { + if (mStream != 0) { + processReturn("close", mStream->close()); + mStream.clear(); + hardware::IPCThreadState::self()->flushCommands(); + } + if (mEfGroup) { + EventFlag::deleteEventFlag(&mEfGroup); + } +} + +status_t StreamInHalHidl::getFrameSize(size_t *size) { + if (mStream == 0) return NO_INIT; + return processReturn("getFrameSize", mStream->getFrameSize(), size); +} + +status_t StreamInHalHidl::setGain(float gain) { + if (mStream == 0) return NO_INIT; + return processReturn("setGain", mStream->setGain(gain)); +} + +status_t StreamInHalHidl::read(void *buffer, size_t bytes, size_t *read) { + if (mStream == 0) return NO_INIT; + *read = 0; + + if (bytes == 0 && !mDataMQ) { + // Can't determine the size for the MQ buffer. Wait for a non-empty read request. + return OK; + } + + status_t status; + if (!mDataMQ && (status = prepareForReading(bytes)) != OK) { + return status; + } + + ReadParameters params; + params.command = ReadCommand::READ; + params.params.read = bytes; + status = callReaderThread(params, "read", + [&](const ReadStatus& readStatus) { + const size_t availToRead = mDataMQ->availableToRead(); + if (!mDataMQ->read(static_cast<uint8_t*>(buffer), std::min(bytes, availToRead))) { + ALOGE("data message queue read failed for \"read\""); + } + ALOGW_IF(availToRead != readStatus.reply.read, + "HAL read report inconsistent: mq = %d, status = %d", + (int32_t)availToRead, (int32_t)readStatus.reply.read); + *read = readStatus.reply.read; + }); + mStreamPowerLog.log(buffer, *read); + return status; +} + +status_t StreamInHalHidl::callReaderThread( + const ReadParameters& params, const char* cmdName, + StreamInHalHidl::ReaderCallback callback) { + if (!mCommandMQ->write(¶ms)) { + ALOGW("command message queue write failed"); + return -EAGAIN; + } + mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)); + + // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422 + uint32_t efState = 0; +retry: + status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState); + if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) { + ReadStatus readStatus; + readStatus.retval = Result::NOT_INITIALIZED; + if (!mStatusMQ->read(&readStatus)) { + ALOGE("status message read failed for \"%s\"", cmdName); + } + if (readStatus.retval == Result::OK) { + ret = OK; + callback(readStatus); + } else { + ret = processReturn(cmdName, readStatus.retval); + } + return ret; + } + if (ret == -EAGAIN || ret == -EINTR) { + // Spurious wakeup. This normally retries no more than once. + goto retry; + } + return ret; +} + +status_t StreamInHalHidl::prepareForReading(size_t bufferSize) { + std::unique_ptr<CommandMQ> tempCommandMQ; + std::unique_ptr<DataMQ> tempDataMQ; + std::unique_ptr<StatusMQ> tempStatusMQ; + Result retval; + pid_t halThreadPid, halThreadTid; + Return<void> ret = mStream->prepareForReading( + 1, bufferSize, + [&](Result r, + const CommandMQ::Descriptor& commandMQ, + const DataMQ::Descriptor& dataMQ, + const StatusMQ::Descriptor& statusMQ, + const ThreadInfo& halThreadInfo) { + retval = r; + if (retval == Result::OK) { + tempCommandMQ.reset(new CommandMQ(commandMQ)); + tempDataMQ.reset(new DataMQ(dataMQ)); + tempStatusMQ.reset(new StatusMQ(statusMQ)); + if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) { + EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup); + } + halThreadPid = halThreadInfo.pid; + halThreadTid = halThreadInfo.tid; + } + }); + if (!ret.isOk() || retval != Result::OK) { + return processReturn("prepareForReading", ret, retval); + } + if (!tempCommandMQ || !tempCommandMQ->isValid() || + !tempDataMQ || !tempDataMQ->isValid() || + !tempStatusMQ || !tempStatusMQ->isValid() || + !mEfGroup) { + ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing"); + ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(), + "Command message queue for writing is invalid"); + ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for reading"); + ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for reading is invalid"); + ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for reading"); + ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), + "Status message queue for reading is invalid"); + ALOGE_IF(!mEfGroup, "Event flag creation for reading failed"); + return NO_INIT; + } + requestHalThreadPriority(halThreadPid, halThreadTid); + + mCommandMQ = std::move(tempCommandMQ); + mDataMQ = std::move(tempDataMQ); + mStatusMQ = std::move(tempStatusMQ); + mReaderClient = gettid(); + return OK; +} + +status_t StreamInHalHidl::getInputFramesLost(uint32_t *framesLost) { + if (mStream == 0) return NO_INIT; + return processReturn("getInputFramesLost", mStream->getInputFramesLost(), framesLost); +} + +status_t StreamInHalHidl::getCapturePosition(int64_t *frames, int64_t *time) { + if (mStream == 0) return NO_INIT; + if (mReaderClient == gettid() && mCommandMQ) { + ReadParameters params; + params.command = ReadCommand::GET_CAPTURE_POSITION; + return callReaderThread(params, "getCapturePosition", + [&](const ReadStatus& readStatus) { + *frames = readStatus.reply.capturePosition.frames; + *time = readStatus.reply.capturePosition.time; + }); + } else { + Result retval; + Return<void> ret = mStream->getCapturePosition( + [&](Result r, uint64_t hidlFrames, uint64_t hidlTime) { + retval = r; + if (retval == Result::OK) { + *frames = hidlFrames; + *time = hidlTime; + } + }); + return processReturn("getCapturePosition", ret, retval); + } +} + + +status_t StreamInHalHidl::getActiveMicrophones( + std::vector<media::MicrophoneInfo> *microphonesInfo) { + if (!mStream) return NO_INIT; + Result retval; + Return<void> ret = mStream->getActiveMicrophones( + [&](Result r, hidl_vec<MicrophoneInfo> micArrayHal) { + retval = r; + for (size_t k = 0; k < micArrayHal.size(); k++) { + audio_microphone_characteristic_t dst; + // convert + microphoneInfoToHal(micArrayHal[k], &dst); + media::MicrophoneInfo microphone = media::MicrophoneInfo(dst); + microphonesInfo->push_back(microphone); + } + }); + return processReturn("getActiveMicrophones", ret, retval); +} + +status_t StreamInHalHidl::updateSinkMetadata(const SinkMetadata& sinkMetadata) { + hardware::audio::V4_0::SinkMetadata halMetadata = { + .tracks = transformToHidlVec(sinkMetadata.tracks, + [](const record_track_metadata& metadata) -> RecordTrackMetadata { + return { + .source=static_cast<AudioSource>(metadata.source), + .gain=metadata.gain, + }; + })}; + return processReturn("updateSinkMetadata", mStream->updateSinkMetadata(halMetadata)); +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalHidl.h b/media/libaudiohal/4.0/StreamHalHidl.h new file mode 100644 index 0000000..2dda0f8 --- /dev/null +++ b/media/libaudiohal/4.0/StreamHalHidl.h
@@ -0,0 +1,250 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_STREAM_HAL_HIDL_4_0_H +#define ANDROID_HARDWARE_STREAM_HAL_HIDL_4_0_H + +#include <atomic> + +#include <android/hardware/audio/4.0/IStream.h> +#include <android/hardware/audio/4.0/IStreamIn.h> +#include <android/hardware/audio/4.0/IStreamOut.h> +#include <fmq/EventFlag.h> +#include <fmq/MessageQueue.h> +#include <media/audiohal/StreamHalInterface.h> + +#include "ConversionHelperHidl.h" +#include "StreamPowerLog.h" + +using ::android::hardware::audio::V4_0::IStream; +using ::android::hardware::audio::V4_0::IStreamIn; +using ::android::hardware::audio::V4_0::IStreamOut; +using ::android::hardware::EventFlag; +using ::android::hardware::MessageQueue; +using ::android::hardware::Return; +using ReadParameters = ::android::hardware::audio::V4_0::IStreamIn::ReadParameters; +using ReadStatus = ::android::hardware::audio::V4_0::IStreamIn::ReadStatus; +using WriteCommand = ::android::hardware::audio::V4_0::IStreamOut::WriteCommand; +using WriteStatus = ::android::hardware::audio::V4_0::IStreamOut::WriteStatus; + +namespace android { +namespace V4_0 { + +class DeviceHalHidl; + +class StreamHalHidl : public virtual StreamHalInterface, public ConversionHelperHidl +{ + public: + // Return the sampling rate in Hz - eg. 44100. + virtual status_t getSampleRate(uint32_t *rate); + + // Return size of input/output buffer in bytes for this stream - eg. 4800. + virtual status_t getBufferSize(size_t *size); + + // Return the channel mask. + virtual status_t getChannelMask(audio_channel_mask_t *mask); + + // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT. + virtual status_t getFormat(audio_format_t *format); + + // Convenience method. + virtual status_t getAudioProperties( + uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format); + + // Set audio stream parameters. + virtual status_t setParameters(const String8& kvPairs); + + // Get audio stream parameters. + virtual status_t getParameters(const String8& keys, String8 *values); + + // Add or remove the effect on the stream. + virtual status_t addEffect(sp<EffectHalInterface> effect); + virtual status_t removeEffect(sp<EffectHalInterface> effect); + + // Put the audio hardware input/output into standby mode. + virtual status_t standby(); + + virtual status_t dump(int fd); + + // Start a stream operating in mmap mode. + virtual status_t start(); + + // Stop a stream operating in mmap mode. + virtual status_t stop(); + + // Retrieve information on the data buffer in mmap mode. + virtual status_t createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info); + + // Get current read/write position in the mmap buffer + virtual status_t getMmapPosition(struct audio_mmap_position *position); + + // Set the priority of the thread that interacts with the HAL + // (must match the priority of the audioflinger's thread that calls 'read' / 'write') + virtual status_t setHalThreadPriority(int priority); + + protected: + // Subclasses can not be constructed directly by clients. + explicit StreamHalHidl(IStream *stream); + + // The destructor automatically closes the stream. + virtual ~StreamHalHidl(); + + status_t getCachedBufferSize(size_t *size); + + bool requestHalThreadPriority(pid_t threadPid, pid_t threadId); + + // mStreamPowerLog is used for audio signal power logging. + StreamPowerLog mStreamPowerLog; + + private: + const int HAL_THREAD_PRIORITY_DEFAULT = -1; + IStream *mStream; + int mHalThreadPriority; + size_t mCachedBufferSize; +}; + +class StreamOutHalHidl : public StreamOutHalInterface, public StreamHalHidl { + public: + // Return the frame size (number of bytes per sample) of a stream. + virtual status_t getFrameSize(size_t *size); + + // Return the audio hardware driver estimated latency in milliseconds. + virtual status_t getLatency(uint32_t *latency); + + // Use this method in situations where audio mixing is done in the hardware. + virtual status_t setVolume(float left, float right); + + // Write audio buffer to driver. + virtual status_t write(const void *buffer, size_t bytes, size_t *written); + + // Return the number of audio frames written by the audio dsp to DAC since + // the output has exited standby. + virtual status_t getRenderPosition(uint32_t *dspFrames); + + // Get the local time at which the next write to the audio driver will be presented. + virtual status_t getNextWriteTimestamp(int64_t *timestamp); + + // Set the callback for notifying completion of non-blocking write and drain. + virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback); + + // Returns whether pause and resume operations are supported. + virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume); + + // Notifies to the audio driver to resume playback following a pause. + virtual status_t pause(); + + // Notifies to the audio driver to resume playback following a pause. + virtual status_t resume(); + + // Returns whether drain operation is supported. + virtual status_t supportsDrain(bool *supportsDrain); + + // Requests notification when data buffered by the driver/hardware has been played. + virtual status_t drain(bool earlyNotify); + + // Notifies to the audio driver to flush the queued data. + virtual status_t flush(); + + // Return a recent count of the number of audio frames presented to an external observer. + virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp); + + // Called when the metadata of the stream's source has been changed. + status_t updateSourceMetadata(const SourceMetadata& sourceMetadata) override; + + // Methods used by StreamOutCallback (HIDL). + void onWriteReady(); + void onDrainReady(); + void onError(); + + private: + friend class DeviceHalHidl; + typedef MessageQueue<WriteCommand, hardware::kSynchronizedReadWrite> CommandMQ; + typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ; + typedef MessageQueue<WriteStatus, hardware::kSynchronizedReadWrite> StatusMQ; + + wp<StreamOutHalInterfaceCallback> mCallback; + sp<IStreamOut> mStream; + std::unique_ptr<CommandMQ> mCommandMQ; + std::unique_ptr<DataMQ> mDataMQ; + std::unique_ptr<StatusMQ> mStatusMQ; + std::atomic<pid_t> mWriterClient; + EventFlag* mEfGroup; + + // Can not be constructed directly by clients. + StreamOutHalHidl(const sp<IStreamOut>& stream); + + virtual ~StreamOutHalHidl(); + + using WriterCallback = std::function<void(const WriteStatus& writeStatus)>; + status_t callWriterThread( + WriteCommand cmd, const char* cmdName, + const uint8_t* data, size_t dataSize, WriterCallback callback); + status_t prepareForWriting(size_t bufferSize); +}; + +class StreamInHalHidl : public StreamInHalInterface, public StreamHalHidl { + public: + // Return the frame size (number of bytes per sample) of a stream. + virtual status_t getFrameSize(size_t *size); + + // Set the input gain for the audio driver. + virtual status_t setGain(float gain); + + // Read audio buffer in from driver. + virtual status_t read(void *buffer, size_t bytes, size_t *read); + + // Return the amount of input frames lost in the audio driver. + virtual status_t getInputFramesLost(uint32_t *framesLost); + + // Return a recent count of the number of audio frames received and + // the clock time associated with that frame count. + virtual status_t getCapturePosition(int64_t *frames, int64_t *time); + + // Get active microphones + virtual status_t getActiveMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + // Called when the metadata of the stream's sink has been changed. + status_t updateSinkMetadata(const SinkMetadata& sinkMetadata) override; + + private: + friend class DeviceHalHidl; + typedef MessageQueue<ReadParameters, hardware::kSynchronizedReadWrite> CommandMQ; + typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ; + typedef MessageQueue<ReadStatus, hardware::kSynchronizedReadWrite> StatusMQ; + + sp<IStreamIn> mStream; + std::unique_ptr<CommandMQ> mCommandMQ; + std::unique_ptr<DataMQ> mDataMQ; + std::unique_ptr<StatusMQ> mStatusMQ; + std::atomic<pid_t> mReaderClient; + EventFlag* mEfGroup; + + // Can not be constructed directly by clients. + StreamInHalHidl(const sp<IStreamIn>& stream); + + virtual ~StreamInHalHidl(); + + using ReaderCallback = std::function<void(const ReadStatus& readStatus)>; + status_t callReaderThread( + const ReadParameters& params, const char* cmdName, ReaderCallback callback); + status_t prepareForReading(size_t bufferSize); +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_STREAM_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/StreamHalLocal.cpp b/media/libaudiohal/4.0/StreamHalLocal.cpp new file mode 100644 index 0000000..e9d96bf --- /dev/null +++ b/media/libaudiohal/4.0/StreamHalLocal.cpp
@@ -0,0 +1,357 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "StreamHalLocal" +//#define LOG_NDEBUG 0 + +#include <hardware/audio.h> +#include <utils/Log.h> + +#include "DeviceHalLocal.h" +#include "StreamHalLocal.h" +#include "VersionUtils.h" + +namespace android { +namespace V4_0 { + +StreamHalLocal::StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device) + : mDevice(device), + mStream(stream) { + // Instrument audio signal power logging. + // Note: This assumes channel mask, format, and sample rate do not change after creation. + if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) { + mStreamPowerLog.init(mStream->get_sample_rate(mStream), + mStream->get_channels(mStream), + mStream->get_format(mStream)); + } +} + +StreamHalLocal::~StreamHalLocal() { + mStream = 0; + mDevice.clear(); +} + +status_t StreamHalLocal::getSampleRate(uint32_t *rate) { + *rate = mStream->get_sample_rate(mStream); + return OK; +} + +status_t StreamHalLocal::getBufferSize(size_t *size) { + *size = mStream->get_buffer_size(mStream); + return OK; +} + +status_t StreamHalLocal::getChannelMask(audio_channel_mask_t *mask) { + *mask = mStream->get_channels(mStream); + return OK; +} + +status_t StreamHalLocal::getFormat(audio_format_t *format) { + *format = mStream->get_format(mStream); + return OK; +} + +status_t StreamHalLocal::getAudioProperties( + uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) { + *sampleRate = mStream->get_sample_rate(mStream); + *mask = mStream->get_channels(mStream); + *format = mStream->get_format(mStream); + return OK; +} + +status_t StreamHalLocal::setParameters(const String8& kvPairs) { + return mStream->set_parameters(mStream, kvPairs.string()); +} + +status_t StreamHalLocal::getParameters(const String8& keys, String8 *values) { + char *halValues = mStream->get_parameters(mStream, keys.string()); + if (halValues != NULL) { + values->setTo(halValues); + free(halValues); + } else { + values->clear(); + } + return OK; +} + +status_t StreamHalLocal::addEffect(sp<EffectHalInterface>) { + LOG_ALWAYS_FATAL("Local streams can not have effects"); + return INVALID_OPERATION; +} + +status_t StreamHalLocal::removeEffect(sp<EffectHalInterface>) { + LOG_ALWAYS_FATAL("Local streams can not have effects"); + return INVALID_OPERATION; +} + +status_t StreamHalLocal::standby() { + return mStream->standby(mStream); +} + +status_t StreamHalLocal::dump(int fd) { + status_t status = mStream->dump(mStream, fd); + mStreamPowerLog.dump(fd); + return status; +} + +status_t StreamHalLocal::setHalThreadPriority(int) { + // Don't need to do anything as local hal is executed by audioflinger directly + // on the same thread. + return OK; +} + +StreamOutHalLocal::StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device) + : StreamHalLocal(&stream->common, device), mStream(stream) { +} + +StreamOutHalLocal::~StreamOutHalLocal() { + mCallback.clear(); + mDevice->closeOutputStream(mStream); + mStream = 0; +} + +status_t StreamOutHalLocal::getFrameSize(size_t *size) { + *size = audio_stream_out_frame_size(mStream); + return OK; +} + +status_t StreamOutHalLocal::getLatency(uint32_t *latency) { + *latency = mStream->get_latency(mStream); + return OK; +} + +status_t StreamOutHalLocal::setVolume(float left, float right) { + if (mStream->set_volume == NULL) return INVALID_OPERATION; + return mStream->set_volume(mStream, left, right); +} + +status_t StreamOutHalLocal::write(const void *buffer, size_t bytes, size_t *written) { + ssize_t writeResult = mStream->write(mStream, buffer, bytes); + if (writeResult > 0) { + *written = writeResult; + mStreamPowerLog.log(buffer, *written); + return OK; + } else { + *written = 0; + return writeResult; + } +} + +status_t StreamOutHalLocal::getRenderPosition(uint32_t *dspFrames) { + return mStream->get_render_position(mStream, dspFrames); +} + +status_t StreamOutHalLocal::getNextWriteTimestamp(int64_t *timestamp) { + if (mStream->get_next_write_timestamp == NULL) return INVALID_OPERATION; + return mStream->get_next_write_timestamp(mStream, timestamp); +} + +status_t StreamOutHalLocal::setCallback(wp<StreamOutHalInterfaceCallback> callback) { + if (mStream->set_callback == NULL) return INVALID_OPERATION; + status_t result = mStream->set_callback(mStream, StreamOutHalLocal::asyncCallback, this); + if (result == OK) { + mCallback = callback; + } + return result; +} + +// static +int StreamOutHalLocal::asyncCallback(stream_callback_event_t event, void*, void *cookie) { + // We act as if we gave a wp<StreamOutHalLocal> to HAL. This way we should handle + // correctly the case when the callback is invoked while StreamOutHalLocal's destructor is + // already running, because the destructor is invoked after the refcount has been atomically + // decremented. + wp<StreamOutHalLocal> weakSelf(static_cast<StreamOutHalLocal*>(cookie)); + sp<StreamOutHalLocal> self = weakSelf.promote(); + if (self == 0) return 0; + sp<StreamOutHalInterfaceCallback> callback = self->mCallback.promote(); + if (callback == 0) return 0; + ALOGV("asyncCallback() event %d", event); + switch (event) { + case STREAM_CBK_EVENT_WRITE_READY: + callback->onWriteReady(); + break; + case STREAM_CBK_EVENT_DRAIN_READY: + callback->onDrainReady(); + break; + case STREAM_CBK_EVENT_ERROR: + callback->onError(); + break; + default: + ALOGW("asyncCallback() unknown event %d", event); + break; + } + return 0; +} + +status_t StreamOutHalLocal::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) { + *supportsPause = mStream->pause != NULL; + *supportsResume = mStream->resume != NULL; + return OK; +} + +status_t StreamOutHalLocal::pause() { + if (mStream->pause == NULL) return INVALID_OPERATION; + return mStream->pause(mStream); +} + +status_t StreamOutHalLocal::resume() { + if (mStream->resume == NULL) return INVALID_OPERATION; + return mStream->resume(mStream); +} + +status_t StreamOutHalLocal::supportsDrain(bool *supportsDrain) { + *supportsDrain = mStream->drain != NULL; + return OK; +} + +status_t StreamOutHalLocal::drain(bool earlyNotify) { + if (mStream->drain == NULL) return INVALID_OPERATION; + return mStream->drain(mStream, earlyNotify ? AUDIO_DRAIN_EARLY_NOTIFY : AUDIO_DRAIN_ALL); +} + +status_t StreamOutHalLocal::flush() { + if (mStream->flush == NULL) return INVALID_OPERATION; + return mStream->flush(mStream); +} + +status_t StreamOutHalLocal::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) { + if (mStream->get_presentation_position == NULL) return INVALID_OPERATION; + return mStream->get_presentation_position(mStream, frames, timestamp); +} + +status_t StreamOutHalLocal::updateSourceMetadata(const SourceMetadata& sourceMetadata) { + if (mStream->update_source_metadata == nullptr) { + return INVALID_OPERATION; + } + const source_metadata_t metadata { + .track_count = sourceMetadata.tracks.size(), + // const cast is fine as it is in a const structure + .tracks = const_cast<playback_track_metadata*>(sourceMetadata.tracks.data()), + }; + mStream->update_source_metadata(mStream, &metadata); + return OK; +} + +status_t StreamOutHalLocal::start() { + if (mStream->start == NULL) return INVALID_OPERATION; + return mStream->start(mStream); +} + +status_t StreamOutHalLocal::stop() { + if (mStream->stop == NULL) return INVALID_OPERATION; + return mStream->stop(mStream); +} + +status_t StreamOutHalLocal::createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info) { + if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION; + return mStream->create_mmap_buffer(mStream, minSizeFrames, info); +} + +status_t StreamOutHalLocal::getMmapPosition(struct audio_mmap_position *position) { + if (mStream->get_mmap_position == NULL) return INVALID_OPERATION; + return mStream->get_mmap_position(mStream, position); +} + +StreamInHalLocal::StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device) + : StreamHalLocal(&stream->common, device), mStream(stream) { +} + +StreamInHalLocal::~StreamInHalLocal() { + mDevice->closeInputStream(mStream); + mStream = 0; +} + +status_t StreamInHalLocal::getFrameSize(size_t *size) { + *size = audio_stream_in_frame_size(mStream); + return OK; +} + +status_t StreamInHalLocal::setGain(float gain) { + return mStream->set_gain(mStream, gain); +} + +status_t StreamInHalLocal::read(void *buffer, size_t bytes, size_t *read) { + ssize_t readResult = mStream->read(mStream, buffer, bytes); + if (readResult > 0) { + *read = readResult; + mStreamPowerLog.log( buffer, *read); + return OK; + } else { + *read = 0; + return readResult; + } +} + +status_t StreamInHalLocal::getInputFramesLost(uint32_t *framesLost) { + *framesLost = mStream->get_input_frames_lost(mStream); + return OK; +} + +status_t StreamInHalLocal::getCapturePosition(int64_t *frames, int64_t *time) { + if (mStream->get_capture_position == NULL) return INVALID_OPERATION; + return mStream->get_capture_position(mStream, frames, time); +} + +status_t StreamInHalLocal::updateSinkMetadata(const SinkMetadata& sinkMetadata) { + if (mStream->update_sink_metadata == nullptr) { + return INVALID_OPERATION; + } + const sink_metadata_t metadata { + .track_count = sinkMetadata.tracks.size(), + // const cast is fine as it is in a const structure + .tracks = const_cast<record_track_metadata*>(sinkMetadata.tracks.data()), + }; + mStream->update_sink_metadata(mStream, &metadata); + return OK; +} + +status_t StreamInHalLocal::start() { + if (mStream->start == NULL) return INVALID_OPERATION; + return mStream->start(mStream); +} + +status_t StreamInHalLocal::stop() { + if (mStream->stop == NULL) return INVALID_OPERATION; + return mStream->stop(mStream); +} + +status_t StreamInHalLocal::createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info) { + if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION; + return mStream->create_mmap_buffer(mStream, minSizeFrames, info); +} + +status_t StreamInHalLocal::getMmapPosition(struct audio_mmap_position *position) { + if (mStream->get_mmap_position == NULL) return INVALID_OPERATION; + return mStream->get_mmap_position(mStream, position); +} + +status_t StreamInHalLocal::getActiveMicrophones(std::vector<media::MicrophoneInfo> *microphones) { + if (mStream->get_active_microphones == NULL) return INVALID_OPERATION; + size_t actual_mics = AUDIO_MICROPHONE_MAX_COUNT; + audio_microphone_characteristic_t mic_array[AUDIO_MICROPHONE_MAX_COUNT]; + status_t status = mStream->get_active_microphones(mStream, &mic_array[0], &actual_mics); + for (size_t i = 0; i < actual_mics; i++) { + media::MicrophoneInfo microphoneInfo = media::MicrophoneInfo(mic_array[i]); + microphones->push_back(microphoneInfo); + } + return status; +} + +} // namespace V4_0 +} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalLocal.h b/media/libaudiohal/4.0/StreamHalLocal.h new file mode 100644 index 0000000..7237509 --- /dev/null +++ b/media/libaudiohal/4.0/StreamHalLocal.h
@@ -0,0 +1,221 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_STREAM_HAL_LOCAL_4_0_H +#define ANDROID_HARDWARE_STREAM_HAL_LOCAL_4_0_H + +#include <media/audiohal/StreamHalInterface.h> +#include "StreamPowerLog.h" + +namespace android { +namespace V4_0 { + +class DeviceHalLocal; + +class StreamHalLocal : public virtual StreamHalInterface +{ + public: + // Return the sampling rate in Hz - eg. 44100. + virtual status_t getSampleRate(uint32_t *rate); + + // Return size of input/output buffer in bytes for this stream - eg. 4800. + virtual status_t getBufferSize(size_t *size); + + // Return the channel mask. + virtual status_t getChannelMask(audio_channel_mask_t *mask); + + // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT. + virtual status_t getFormat(audio_format_t *format); + + // Convenience method. + virtual status_t getAudioProperties( + uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format); + + // Set audio stream parameters. + virtual status_t setParameters(const String8& kvPairs); + + // Get audio stream parameters. + virtual status_t getParameters(const String8& keys, String8 *values); + + // Add or remove the effect on the stream. + virtual status_t addEffect(sp<EffectHalInterface> effect); + virtual status_t removeEffect(sp<EffectHalInterface> effect); + + // Put the audio hardware input/output into standby mode. + virtual status_t standby(); + + virtual status_t dump(int fd); + + // Start a stream operating in mmap mode. + virtual status_t start() = 0; + + // Stop a stream operating in mmap mode. + virtual status_t stop() = 0; + + // Retrieve information on the data buffer in mmap mode. + virtual status_t createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info) = 0; + + // Get current read/write position in the mmap buffer + virtual status_t getMmapPosition(struct audio_mmap_position *position) = 0; + + // Set the priority of the thread that interacts with the HAL + // (must match the priority of the audioflinger's thread that calls 'read' / 'write') + virtual status_t setHalThreadPriority(int priority); + + protected: + // Subclasses can not be constructed directly by clients. + StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device); + + // The destructor automatically closes the stream. + virtual ~StreamHalLocal(); + + sp<DeviceHalLocal> mDevice; + + // mStreamPowerLog is used for audio signal power logging. + StreamPowerLog mStreamPowerLog; + + private: + audio_stream_t *mStream; +}; + +class StreamOutHalLocal : public StreamOutHalInterface, public StreamHalLocal { + public: + // Return the frame size (number of bytes per sample) of a stream. + virtual status_t getFrameSize(size_t *size); + + // Return the audio hardware driver estimated latency in milliseconds. + virtual status_t getLatency(uint32_t *latency); + + // Use this method in situations where audio mixing is done in the hardware. + virtual status_t setVolume(float left, float right); + + // Write audio buffer to driver. + virtual status_t write(const void *buffer, size_t bytes, size_t *written); + + // Return the number of audio frames written by the audio dsp to DAC since + // the output has exited standby. + virtual status_t getRenderPosition(uint32_t *dspFrames); + + // Get the local time at which the next write to the audio driver will be presented. + virtual status_t getNextWriteTimestamp(int64_t *timestamp); + + // Set the callback for notifying completion of non-blocking write and drain. + virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback); + + // Returns whether pause and resume operations are supported. + virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume); + + // Notifies to the audio driver to resume playback following a pause. + virtual status_t pause(); + + // Notifies to the audio driver to resume playback following a pause. + virtual status_t resume(); + + // Returns whether drain operation is supported. + virtual status_t supportsDrain(bool *supportsDrain); + + // Requests notification when data buffered by the driver/hardware has been played. + virtual status_t drain(bool earlyNotify); + + // Notifies to the audio driver to flush the queued data. + virtual status_t flush(); + + // Return a recent count of the number of audio frames presented to an external observer. + virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp); + + // Start a stream operating in mmap mode. + virtual status_t start(); + + // Stop a stream operating in mmap mode. + virtual status_t stop(); + + // Retrieve information on the data buffer in mmap mode. + virtual status_t createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info); + + // Get current read/write position in the mmap buffer + virtual status_t getMmapPosition(struct audio_mmap_position *position); + + // Called when the metadata of the stream's source has been changed. + status_t updateSourceMetadata(const SourceMetadata& sourceMetadata) override; + + private: + audio_stream_out_t *mStream; + wp<StreamOutHalInterfaceCallback> mCallback; + + friend class DeviceHalLocal; + + // Can not be constructed directly by clients. + StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device); + + virtual ~StreamOutHalLocal(); + + static int asyncCallback(stream_callback_event_t event, void *param, void *cookie); +}; + +class StreamInHalLocal : public StreamInHalInterface, public StreamHalLocal { + public: + // Return the frame size (number of bytes per sample) of a stream. + virtual status_t getFrameSize(size_t *size); + + // Set the input gain for the audio driver. + virtual status_t setGain(float gain); + + // Read audio buffer in from driver. + virtual status_t read(void *buffer, size_t bytes, size_t *read); + + // Return the amount of input frames lost in the audio driver. + virtual status_t getInputFramesLost(uint32_t *framesLost); + + // Return a recent count of the number of audio frames received and + // the clock time associated with that frame count. + virtual status_t getCapturePosition(int64_t *frames, int64_t *time); + + // Start a stream operating in mmap mode. + virtual status_t start(); + + // Stop a stream operating in mmap mode. + virtual status_t stop(); + + // Retrieve information on the data buffer in mmap mode. + virtual status_t createMmapBuffer(int32_t minSizeFrames, + struct audio_mmap_buffer_info *info); + + // Get current read/write position in the mmap buffer + virtual status_t getMmapPosition(struct audio_mmap_position *position); + + // Get active microphones + virtual status_t getActiveMicrophones(std::vector<media::MicrophoneInfo> *microphones); + + // Called when the metadata of the stream's sink has been changed. + status_t updateSinkMetadata(const SinkMetadata& sinkMetadata) override; + + private: + audio_stream_in_t *mStream; + + friend class DeviceHalLocal; + + // Can not be constructed directly by clients. + StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device); + + virtual ~StreamInHalLocal(); +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_STREAM_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/StreamPowerLog.h b/media/libaudiohal/4.0/StreamPowerLog.h new file mode 100644 index 0000000..57b7201 --- /dev/null +++ b/media/libaudiohal/4.0/StreamPowerLog.h
@@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H +#define ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H + +#include <audio_utils/clock.h> +#include <audio_utils/PowerLog.h> +#include <cutils/properties.h> +#include <system/audio.h> + +namespace android { +namespace V4_0 { + +class StreamPowerLog { +public: + StreamPowerLog() : + mIsUserDebugOrEngBuild(is_userdebug_or_eng_build()), + mPowerLog(nullptr), + mFrameSize(0) { + // use init() to set up the power log. + } + + ~StreamPowerLog() { + power_log_destroy(mPowerLog); // OK for null mPowerLog + mPowerLog = nullptr; + } + + // A one-time initialization (do not call twice) before using StreamPowerLog. + void init(uint32_t sampleRate, audio_channel_mask_t channelMask, audio_format_t format) { + if (mPowerLog == nullptr) { + // Note: A way to get channel count for both input and output channel masks + // but does not check validity of the channel mask. + const uint32_t channelCount = popcount(audio_channel_mask_get_bits(channelMask)); + mFrameSize = channelCount * audio_bytes_per_sample(format); + if (mFrameSize > 0) { + const size_t kPowerLogFramesPerEntry = + (long long)sampleRate * kPowerLogSamplingIntervalMs / 1000; + mPowerLog = power_log_create( + sampleRate, + channelCount, + format, + kPowerLogEntries, + kPowerLogFramesPerEntry); + } + } + // mPowerLog may be NULL (not the right build, format not accepted, etc.). + } + + // Dump the power log to fd. + void dump(int fd) const { + // OK for null mPowerLog + (void)power_log_dump( + mPowerLog, fd, " " /* prefix */, kPowerLogLines, 0 /* limit_ns */); + } + + // Log the audio data contained in buffer. + void log(const void *buffer, size_t sizeInBytes) const { + if (mPowerLog != nullptr) { // mFrameSize is always nonzero if mPowerLog exists. + power_log_log( + mPowerLog, buffer, sizeInBytes / mFrameSize, audio_utils_get_real_time_ns()); + } + } + + bool isUserDebugOrEngBuild() const { + return mIsUserDebugOrEngBuild; + } + +private: + + static inline bool is_userdebug_or_eng_build() { + char value[PROPERTY_VALUE_MAX]; + (void)property_get("ro.build.type", value, "unknown"); // ignore actual length + return strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0; + } + + // Audio signal power log configuration. + static const size_t kPowerLogLines = 40; + static const size_t kPowerLogSamplingIntervalMs = 50; + static const size_t kPowerLogEntries = (1 /* minutes */ * 60 /* seconds */ * 1000 /* msec */ + / kPowerLogSamplingIntervalMs); + + const bool mIsUserDebugOrEngBuild; + power_log_t *mPowerLog; + size_t mFrameSize; +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H
diff --git a/media/libaudiohal/4.0/VersionUtils.h b/media/libaudiohal/4.0/VersionUtils.h new file mode 100644 index 0000000..1246c2e --- /dev/null +++ b/media/libaudiohal/4.0/VersionUtils.h
@@ -0,0 +1,49 @@ +/* + * 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 ANDROID_HARDWARE_VERSION_UTILS_4_0_H +#define ANDROID_HARDWARE_VERSION_UTILS_4_0_H + +#include <android/hardware/audio/4.0/types.h> +#include <hidl/HidlSupport.h> + +using ::android::hardware::audio::V4_0::ParameterValue; +using ::android::hardware::audio::V4_0::Result; +using ::android::hardware::Return; +using ::android::hardware::hidl_vec; +using ::android::hardware::hidl_string; + +namespace android { +namespace V4_0 { +namespace utils { + +template <class T, class Callback> +Return<void> getParameters(T& object, hidl_vec<ParameterValue> context, + hidl_vec<hidl_string> keys, Callback callback) { + return object->getParameters(context, keys, callback); +} + +template <class T> +Return<Result> setParameters(T& object, hidl_vec<ParameterValue> context, + hidl_vec<ParameterValue> keys) { + return object->setParameters(context, keys); +} + +} // namespace utils +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_VERSION_UTILS_4_0_H
diff --git a/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h b/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h new file mode 100644 index 0000000..abf6de0 --- /dev/null +++ b/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h
@@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H +#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H + +#include <media/audiohal/DevicesFactoryHalInterface.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +namespace android { +namespace V4_0 { + +class DevicesFactoryHalHybrid : public DevicesFactoryHalInterface +{ + public: + // Opens a device with the specified name. To close the device, it is + // necessary to release references to the returned object. + virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device); + + private: + friend class DevicesFactoryHalInterface; + + // Can not be constructed directly by clients. + DevicesFactoryHalHybrid(); + + virtual ~DevicesFactoryHalHybrid(); + + sp<DevicesFactoryHalInterface> mLocalFactory; + sp<DevicesFactoryHalInterface> mHidlFactory; +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H
diff --git a/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h b/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h new file mode 100644 index 0000000..680b7a1 --- /dev/null +++ b/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h
@@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_4_0_H +#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_4_0_H + +#include <android/hardware/audio/effect/4.0/IEffectsFactory.h> +#include <android/hardware/audio/effect/4.0/types.h> +#include <media/audiohal/EffectsFactoryHalInterface.h> + +#include "ConversionHelperHidl.h" + +namespace android { +namespace V4_0 { + +using ::android::hardware::audio::effect::V4_0::EffectDescriptor; +using ::android::hardware::audio::effect::V4_0::IEffectsFactory; +using ::android::hardware::hidl_vec; + +class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public ConversionHelperHidl +{ + public: + // Returns the number of different effects in all loaded libraries. + virtual status_t queryNumberEffects(uint32_t *pNumEffects); + + // Returns a descriptor of the next available effect. + virtual status_t getDescriptor(uint32_t index, + effect_descriptor_t *pDescriptor); + + virtual status_t getDescriptor(const effect_uuid_t *pEffectUuid, + effect_descriptor_t *pDescriptor); + + // Creates an effect engine of the specified type. + // To release the effect engine, it is necessary to release references + // to the returned effect object. + virtual status_t createEffect(const effect_uuid_t *pEffectUuid, + int32_t sessionId, int32_t ioId, + sp<EffectHalInterface> *effect); + + virtual status_t dumpEffects(int fd); + + status_t allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) override; + status_t mirrorBuffer(void* external, size_t size, + sp<EffectBufferHalInterface>* buffer) override; + + private: + friend class EffectsFactoryHalInterface; + + sp<IEffectsFactory> mEffectsFactory; + hidl_vec<EffectDescriptor> mLastDescriptors; + + // Can not be constructed directly by clients. + EffectsFactoryHalHidl(); + virtual ~EffectsFactoryHalHidl(); + + status_t queryAllDescriptors(); +}; + +} // namespace V4_0 +} // namespace android + +#endif // ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/Android.bp b/media/libaudiohal/Android.bp new file mode 100644 index 0000000..3a5df27 --- /dev/null +++ b/media/libaudiohal/Android.bp
@@ -0,0 +1,56 @@ +cc_library_shared { + name: "libaudiohal", + + srcs: [ + "DevicesFactoryHalInterface.cpp", + "EffectsFactoryHalInterface.cpp", + ], + + cflags: [ + "-Wall", + "-Werror", + ], + + shared_libs: [ + "android.hardware.audio.effect@2.0", + "android.hardware.audio.effect@4.0", + "android.hardware.audio@2.0", + "android.hardware.audio@4.0", + "libaudiohal@2.0", + "libaudiohal@4.0", + "libutils", + ], + + header_libs: [ + "libaudiohal_headers" + ] +} + +cc_library_shared { + name: "libaudiohal_deathhandler", + + srcs: [ + "HalDeathHandlerHidl.cpp", + ], + + cflags: [ + "-Wall", + "-Werror", + ], + + shared_libs: [ + "libhidlbase", + "libutils", + "liblog", + ], + + header_libs: [ + "libaudiohal_headers" + ] +} + +cc_library_headers { + name: "libaudiohal_headers", + + export_include_dirs: ["include"], +}
diff --git a/media/libaudiohal/Android.mk b/media/libaudiohal/Android.mk deleted file mode 100644 index 827908e..0000000 --- a/media/libaudiohal/Android.mk +++ /dev/null
@@ -1,71 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SHARED_LIBRARIES := \ - libaudioutils \ - libcutils \ - liblog \ - libutils \ - libhardware - -LOCAL_SRC_FILES := \ - DeviceHalLocal.cpp \ - DevicesFactoryHalHybrid.cpp \ - DevicesFactoryHalLocal.cpp \ - StreamHalLocal.cpp - -LOCAL_CFLAGS := -Wall -Werror - -ifeq ($(USE_LEGACY_LOCAL_AUDIO_HAL), true) - -# Use audiohal directly w/o hwbinder middleware. -# This is for performance comparison and debugging only. - -LOCAL_SRC_FILES += \ - EffectBufferHalLocal.cpp \ - EffectsFactoryHalLocal.cpp \ - EffectHalLocal.cpp - -LOCAL_SHARED_LIBRARIES += \ - libeffects - -LOCAL_CFLAGS += -DUSE_LEGACY_LOCAL_AUDIO_HAL - -else # if !USE_LEGACY_LOCAL_AUDIO_HAL - -LOCAL_SRC_FILES += \ - ConversionHelperHidl.cpp \ - HalDeathHandlerHidl.cpp \ - DeviceHalHidl.cpp \ - DevicesFactoryHalHidl.cpp \ - EffectBufferHalHidl.cpp \ - EffectHalHidl.cpp \ - EffectsFactoryHalHidl.cpp \ - StreamHalHidl.cpp - -LOCAL_SHARED_LIBRARIES += \ - libbase \ - libfmq \ - libhwbinder \ - libhidlbase \ - libhidlmemory \ - libhidltransport \ - android.hardware.audio@2.0 \ - android.hardware.audio.common@2.0 \ - android.hardware.audio.common@2.0-util \ - android.hardware.audio.effect@2.0 \ - android.hidl.allocator@1.0 \ - android.hidl.memory@1.0 \ - libmedia_helper \ - libmediautils - -endif # USE_LEGACY_LOCAL_AUDIO_HAL - -LOCAL_C_INCLUDES := $(LOCAL_PATH)/include - -LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include - -LOCAL_MODULE := libaudiohal - -include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libaudiohal/DeviceHalHidl.cpp b/media/libaudiohal/DeviceHalHidl.cpp deleted file mode 100644 index 49ef991..0000000 --- a/media/libaudiohal/DeviceHalHidl.cpp +++ /dev/null
@@ -1,357 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <stdio.h> - -#define LOG_TAG "DeviceHalHidl" -//#define LOG_NDEBUG 0 - -#include <android/hardware/audio/2.0/IPrimaryDevice.h> -#include <cutils/native_handle.h> -#include <hwbinder/IPCThreadState.h> -#include <utils/Log.h> - -#include "DeviceHalHidl.h" -#include "HidlUtils.h" -#include "StreamHalHidl.h" - -using ::android::hardware::audio::common::V2_0::AudioConfig; -using ::android::hardware::audio::common::V2_0::AudioDevice; -using ::android::hardware::audio::common::V2_0::AudioInputFlag; -using ::android::hardware::audio::common::V2_0::AudioOutputFlag; -using ::android::hardware::audio::common::V2_0::AudioPatchHandle; -using ::android::hardware::audio::common::V2_0::AudioPort; -using ::android::hardware::audio::common::V2_0::AudioPortConfig; -using ::android::hardware::audio::common::V2_0::AudioMode; -using ::android::hardware::audio::common::V2_0::AudioSource; -using ::android::hardware::audio::V2_0::DeviceAddress; -using ::android::hardware::audio::V2_0::IPrimaryDevice; -using ::android::hardware::audio::V2_0::ParameterValue; -using ::android::hardware::audio::V2_0::Result; -using ::android::hardware::hidl_string; -using ::android::hardware::hidl_vec; - -namespace android { - -namespace { - -status_t deviceAddressFromHal( - audio_devices_t device, const char* halAddress, DeviceAddress* address) { - address->device = AudioDevice(device); - - if (address == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) { - return OK; - } - const bool isInput = (device & AUDIO_DEVICE_BIT_IN) != 0; - if (isInput) device &= ~AUDIO_DEVICE_BIT_IN; - if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_A2DP) != 0) - || (isInput && (device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) != 0)) { - int status = sscanf(halAddress, - "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX", - &address->address.mac[0], &address->address.mac[1], &address->address.mac[2], - &address->address.mac[3], &address->address.mac[4], &address->address.mac[5]); - return status == 6 ? OK : BAD_VALUE; - } else if ((!isInput && (device & AUDIO_DEVICE_OUT_IP) != 0) - || (isInput && (device & AUDIO_DEVICE_IN_IP) != 0)) { - int status = sscanf(halAddress, - "%hhu.%hhu.%hhu.%hhu", - &address->address.ipv4[0], &address->address.ipv4[1], - &address->address.ipv4[2], &address->address.ipv4[3]); - return status == 4 ? OK : BAD_VALUE; - } else if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_USB)) != 0 - || (isInput && (device & AUDIO_DEVICE_IN_ALL_USB)) != 0) { - int status = sscanf(halAddress, - "card=%d;device=%d", - &address->address.alsa.card, &address->address.alsa.device); - return status == 2 ? OK : BAD_VALUE; - } else if ((!isInput && (device & AUDIO_DEVICE_OUT_BUS) != 0) - || (isInput && (device & AUDIO_DEVICE_IN_BUS) != 0)) { - if (halAddress != NULL) { - address->busAddress = halAddress; - return OK; - } - return BAD_VALUE; - } else if ((!isInput && (device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) != 0 - || (isInput && (device & AUDIO_DEVICE_IN_REMOTE_SUBMIX) != 0)) { - if (halAddress != NULL) { - address->rSubmixAddress = halAddress; - return OK; - } - return BAD_VALUE; - } - return OK; -} - -} // namespace - -DeviceHalHidl::DeviceHalHidl(const sp<IDevice>& device) - : ConversionHelperHidl("Device"), mDevice(device), - mPrimaryDevice(IPrimaryDevice::castFrom(device)) { -} - -DeviceHalHidl::~DeviceHalHidl() { - if (mDevice != 0) { - mDevice.clear(); - hardware::IPCThreadState::self()->flushCommands(); - } -} - -status_t DeviceHalHidl::getSupportedDevices(uint32_t*) { - // Obsolete. - return INVALID_OPERATION; -} - -status_t DeviceHalHidl::initCheck() { - if (mDevice == 0) return NO_INIT; - return processReturn("initCheck", mDevice->initCheck()); -} - -status_t DeviceHalHidl::setVoiceVolume(float volume) { - if (mDevice == 0) return NO_INIT; - if (mPrimaryDevice == 0) return INVALID_OPERATION; - return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume)); -} - -status_t DeviceHalHidl::setMasterVolume(float volume) { - if (mDevice == 0) return NO_INIT; - if (mPrimaryDevice == 0) return INVALID_OPERATION; - return processReturn("setMasterVolume", mPrimaryDevice->setMasterVolume(volume)); -} - -status_t DeviceHalHidl::getMasterVolume(float *volume) { - if (mDevice == 0) return NO_INIT; - if (mPrimaryDevice == 0) return INVALID_OPERATION; - Result retval; - Return<void> ret = mPrimaryDevice->getMasterVolume( - [&](Result r, float v) { - retval = r; - if (retval == Result::OK) { - *volume = v; - } - }); - return processReturn("getMasterVolume", ret, retval); -} - -status_t DeviceHalHidl::setMode(audio_mode_t mode) { - if (mDevice == 0) return NO_INIT; - if (mPrimaryDevice == 0) return INVALID_OPERATION; - return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode))); -} - -status_t DeviceHalHidl::setMicMute(bool state) { - if (mDevice == 0) return NO_INIT; - return processReturn("setMicMute", mDevice->setMicMute(state)); -} - -status_t DeviceHalHidl::getMicMute(bool *state) { - if (mDevice == 0) return NO_INIT; - Result retval; - Return<void> ret = mDevice->getMicMute( - [&](Result r, bool mute) { - retval = r; - if (retval == Result::OK) { - *state = mute; - } - }); - return processReturn("getMicMute", ret, retval); -} - -status_t DeviceHalHidl::setMasterMute(bool state) { - if (mDevice == 0) return NO_INIT; - return processReturn("setMasterMute", mDevice->setMasterMute(state)); -} - -status_t DeviceHalHidl::getMasterMute(bool *state) { - if (mDevice == 0) return NO_INIT; - Result retval; - Return<void> ret = mDevice->getMasterMute( - [&](Result r, bool mute) { - retval = r; - if (retval == Result::OK) { - *state = mute; - } - }); - return processReturn("getMasterMute", ret, retval); -} - -status_t DeviceHalHidl::setParameters(const String8& kvPairs) { - if (mDevice == 0) return NO_INIT; - hidl_vec<ParameterValue> hidlParams; - status_t status = parametersFromHal(kvPairs, &hidlParams); - if (status != OK) return status; - return processReturn("setParameters", mDevice->setParameters(hidlParams)); -} - -status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) { - values->clear(); - if (mDevice == 0) return NO_INIT; - hidl_vec<hidl_string> hidlKeys; - status_t status = keysFromHal(keys, &hidlKeys); - if (status != OK) return status; - Result retval; - Return<void> ret = mDevice->getParameters( - hidlKeys, - [&](Result r, const hidl_vec<ParameterValue>& parameters) { - retval = r; - if (retval == Result::OK) { - parametersToHal(parameters, values); - } - }); - return processReturn("getParameters", ret, retval); -} - -status_t DeviceHalHidl::getInputBufferSize( - const struct audio_config *config, size_t *size) { - if (mDevice == 0) return NO_INIT; - AudioConfig hidlConfig; - HidlUtils::audioConfigFromHal(*config, &hidlConfig); - Result retval; - Return<void> ret = mDevice->getInputBufferSize( - hidlConfig, - [&](Result r, uint64_t bufferSize) { - retval = r; - if (retval == Result::OK) { - *size = static_cast<size_t>(bufferSize); - } - }); - return processReturn("getInputBufferSize", ret, retval); -} - -status_t DeviceHalHidl::openOutputStream( - audio_io_handle_t handle, - audio_devices_t devices, - audio_output_flags_t flags, - struct audio_config *config, - const char *address, - sp<StreamOutHalInterface> *outStream) { - if (mDevice == 0) return NO_INIT; - DeviceAddress hidlDevice; - status_t status = deviceAddressFromHal(devices, address, &hidlDevice); - if (status != OK) return status; - AudioConfig hidlConfig; - HidlUtils::audioConfigFromHal(*config, &hidlConfig); - Result retval = Result::NOT_INITIALIZED; - Return<void> ret = mDevice->openOutputStream( - handle, - hidlDevice, - hidlConfig, - AudioOutputFlag(flags), - [&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) { - retval = r; - if (retval == Result::OK) { - *outStream = new StreamOutHalHidl(result); - } - HidlUtils::audioConfigToHal(suggestedConfig, config); - }); - return processReturn("openOutputStream", ret, retval); -} - -status_t DeviceHalHidl::openInputStream( - audio_io_handle_t handle, - audio_devices_t devices, - struct audio_config *config, - audio_input_flags_t flags, - const char *address, - audio_source_t source, - sp<StreamInHalInterface> *inStream) { - if (mDevice == 0) return NO_INIT; - DeviceAddress hidlDevice; - status_t status = deviceAddressFromHal(devices, address, &hidlDevice); - if (status != OK) return status; - AudioConfig hidlConfig; - HidlUtils::audioConfigFromHal(*config, &hidlConfig); - Result retval = Result::NOT_INITIALIZED; - Return<void> ret = mDevice->openInputStream( - handle, - hidlDevice, - hidlConfig, - AudioInputFlag(flags), - AudioSource(source), - [&](Result r, const sp<IStreamIn>& result, const AudioConfig& suggestedConfig) { - retval = r; - if (retval == Result::OK) { - *inStream = new StreamInHalHidl(result); - } - HidlUtils::audioConfigToHal(suggestedConfig, config); - }); - return processReturn("openInputStream", ret, retval); -} - -status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) { - if (mDevice == 0) return NO_INIT; - return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches); -} - -status_t DeviceHalHidl::createAudioPatch( - unsigned int num_sources, - const struct audio_port_config *sources, - unsigned int num_sinks, - const struct audio_port_config *sinks, - audio_patch_handle_t *patch) { - if (mDevice == 0) return NO_INIT; - hidl_vec<AudioPortConfig> hidlSources, hidlSinks; - HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources); - HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks); - Result retval; - Return<void> ret = mDevice->createAudioPatch( - hidlSources, hidlSinks, - [&](Result r, AudioPatchHandle hidlPatch) { - retval = r; - if (retval == Result::OK) { - *patch = static_cast<audio_patch_handle_t>(hidlPatch); - } - }); - return processReturn("createAudioPatch", ret, retval); -} - -status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) { - if (mDevice == 0) return NO_INIT; - return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch)); -} - -status_t DeviceHalHidl::getAudioPort(struct audio_port *port) { - if (mDevice == 0) return NO_INIT; - AudioPort hidlPort; - HidlUtils::audioPortFromHal(*port, &hidlPort); - Result retval; - Return<void> ret = mDevice->getAudioPort( - hidlPort, - [&](Result r, const AudioPort& p) { - retval = r; - if (retval == Result::OK) { - HidlUtils::audioPortToHal(p, port); - } - }); - return processReturn("getAudioPort", ret, retval); -} - -status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) { - if (mDevice == 0) return NO_INIT; - AudioPortConfig hidlConfig; - HidlUtils::audioPortConfigFromHal(*config, &hidlConfig); - return processReturn("setAudioPortConfig", mDevice->setAudioPortConfig(hidlConfig)); -} - -status_t DeviceHalHidl::dump(int fd) { - if (mDevice == 0) return NO_INIT; - native_handle_t* hidlHandle = native_handle_create(1, 0); - hidlHandle->data[0] = fd; - Return<void> ret = mDevice->debugDump(hidlHandle); - native_handle_delete(hidlHandle); - return processReturn("dump", ret); -} - -} // namespace android
diff --git a/media/libaudiohal/DeviceHalHidl.h b/media/libaudiohal/DeviceHalHidl.h deleted file mode 100644 index 8651b51..0000000 --- a/media/libaudiohal/DeviceHalHidl.h +++ /dev/null
@@ -1,126 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_DEVICE_HAL_HIDL_H -#define ANDROID_HARDWARE_DEVICE_HAL_HIDL_H - -#include <android/hardware/audio/2.0/IDevice.h> -#include <android/hardware/audio/2.0/IPrimaryDevice.h> -#include <media/audiohal/DeviceHalInterface.h> - -#include "ConversionHelperHidl.h" - -using ::android::hardware::audio::V2_0::IDevice; -using ::android::hardware::audio::V2_0::IPrimaryDevice; -using ::android::hardware::Return; - -namespace android { - -class DeviceHalHidl : public DeviceHalInterface, public ConversionHelperHidl -{ - public: - // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t. - virtual status_t getSupportedDevices(uint32_t *devices); - - // Check to see if the audio hardware interface has been initialized. - virtual status_t initCheck(); - - // Set the audio volume of a voice call. Range is between 0.0 and 1.0. - virtual status_t setVoiceVolume(float volume); - - // Set the audio volume for all audio activities other than voice call. - virtual status_t setMasterVolume(float volume); - - // Get the current master volume value for the HAL. - virtual status_t getMasterVolume(float *volume); - - // Called when the audio mode changes. - virtual status_t setMode(audio_mode_t mode); - - // Muting control. - virtual status_t setMicMute(bool state); - virtual status_t getMicMute(bool *state); - virtual status_t setMasterMute(bool state); - virtual status_t getMasterMute(bool *state); - - // Set global audio parameters. - virtual status_t setParameters(const String8& kvPairs); - - // Get global audio parameters. - virtual status_t getParameters(const String8& keys, String8 *values); - - // Returns audio input buffer size according to parameters passed. - virtual status_t getInputBufferSize(const struct audio_config *config, - size_t *size); - - // Creates and opens the audio hardware output stream. The stream is closed - // by releasing all references to the returned object. - virtual status_t openOutputStream( - audio_io_handle_t handle, - audio_devices_t devices, - audio_output_flags_t flags, - struct audio_config *config, - const char *address, - sp<StreamOutHalInterface> *outStream); - - // Creates and opens the audio hardware input stream. The stream is closed - // by releasing all references to the returned object. - virtual status_t openInputStream( - audio_io_handle_t handle, - audio_devices_t devices, - struct audio_config *config, - audio_input_flags_t flags, - const char *address, - audio_source_t source, - sp<StreamInHalInterface> *inStream); - - // Returns whether createAudioPatch and releaseAudioPatch operations are supported. - virtual status_t supportsAudioPatches(bool *supportsPatches); - - // Creates an audio patch between several source and sink ports. - virtual status_t createAudioPatch( - unsigned int num_sources, - const struct audio_port_config *sources, - unsigned int num_sinks, - const struct audio_port_config *sinks, - audio_patch_handle_t *patch); - - // Releases an audio patch. - virtual status_t releaseAudioPatch(audio_patch_handle_t patch); - - // Fills the list of supported attributes for a given audio port. - virtual status_t getAudioPort(struct audio_port *port); - - // Set audio port configuration. - virtual status_t setAudioPortConfig(const struct audio_port_config *config); - - virtual status_t dump(int fd); - - private: - friend class DevicesFactoryHalHidl; - sp<IDevice> mDevice; - sp<IPrimaryDevice> mPrimaryDevice; // Null if it's not a primary device. - - // Can not be constructed directly by clients. - explicit DeviceHalHidl(const sp<IDevice>& device); - - // The destructor automatically closes the device. - virtual ~DeviceHalHidl(); -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_DEVICE_HAL_HIDL_H
diff --git a/media/libaudiohal/DeviceHalLocal.cpp b/media/libaudiohal/DeviceHalLocal.cpp deleted file mode 100644 index fc098f5..0000000 --- a/media/libaudiohal/DeviceHalLocal.cpp +++ /dev/null
@@ -1,199 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "DeviceHalLocal" -//#define LOG_NDEBUG 0 - -#include <utils/Log.h> - -#include "DeviceHalLocal.h" -#include "StreamHalLocal.h" - -namespace android { - -DeviceHalLocal::DeviceHalLocal(audio_hw_device_t *dev) - : mDev(dev) { -} - -DeviceHalLocal::~DeviceHalLocal() { - int status = audio_hw_device_close(mDev); - ALOGW_IF(status, "Error closing audio hw device %p: %s", mDev, strerror(-status)); - mDev = 0; -} - -status_t DeviceHalLocal::getSupportedDevices(uint32_t *devices) { - if (mDev->get_supported_devices == NULL) return INVALID_OPERATION; - *devices = mDev->get_supported_devices(mDev); - return OK; -} - -status_t DeviceHalLocal::initCheck() { - return mDev->init_check(mDev); -} - -status_t DeviceHalLocal::setVoiceVolume(float volume) { - return mDev->set_voice_volume(mDev, volume); -} - -status_t DeviceHalLocal::setMasterVolume(float volume) { - if (mDev->set_master_volume == NULL) return INVALID_OPERATION; - return mDev->set_master_volume(mDev, volume); -} - -status_t DeviceHalLocal::getMasterVolume(float *volume) { - if (mDev->get_master_volume == NULL) return INVALID_OPERATION; - return mDev->get_master_volume(mDev, volume); -} - -status_t DeviceHalLocal::setMode(audio_mode_t mode) { - return mDev->set_mode(mDev, mode); -} - -status_t DeviceHalLocal::setMicMute(bool state) { - return mDev->set_mic_mute(mDev, state); -} - -status_t DeviceHalLocal::getMicMute(bool *state) { - return mDev->get_mic_mute(mDev, state); -} - -status_t DeviceHalLocal::setMasterMute(bool state) { - if (mDev->set_master_mute == NULL) return INVALID_OPERATION; - return mDev->set_master_mute(mDev, state); -} - -status_t DeviceHalLocal::getMasterMute(bool *state) { - if (mDev->get_master_mute == NULL) return INVALID_OPERATION; - return mDev->get_master_mute(mDev, state); -} - -status_t DeviceHalLocal::setParameters(const String8& kvPairs) { - return mDev->set_parameters(mDev, kvPairs.string()); -} - -status_t DeviceHalLocal::getParameters(const String8& keys, String8 *values) { - char *halValues = mDev->get_parameters(mDev, keys.string()); - if (halValues != NULL) { - values->setTo(halValues); - free(halValues); - } else { - values->clear(); - } - return OK; -} - -status_t DeviceHalLocal::getInputBufferSize( - const struct audio_config *config, size_t *size) { - *size = mDev->get_input_buffer_size(mDev, config); - return OK; -} - -status_t DeviceHalLocal::openOutputStream( - audio_io_handle_t handle, - audio_devices_t devices, - audio_output_flags_t flags, - struct audio_config *config, - const char *address, - sp<StreamOutHalInterface> *outStream) { - audio_stream_out_t *halStream; - ALOGV("open_output_stream handle: %d devices: %x flags: %#x" - "srate: %d format %#x channels %x address %s", - handle, devices, flags, - config->sample_rate, config->format, config->channel_mask, - address); - int openResut = mDev->open_output_stream( - mDev, handle, devices, flags, config, &halStream, address); - if (openResut == OK) { - *outStream = new StreamOutHalLocal(halStream, this); - } - ALOGV("open_output_stream status %d stream %p", openResut, halStream); - return openResut; -} - -status_t DeviceHalLocal::openInputStream( - audio_io_handle_t handle, - audio_devices_t devices, - struct audio_config *config, - audio_input_flags_t flags, - const char *address, - audio_source_t source, - sp<StreamInHalInterface> *inStream) { - audio_stream_in_t *halStream; - ALOGV("open_input_stream handle: %d devices: %x flags: %#x " - "srate: %d format %#x channels %x address %s source %d", - handle, devices, flags, - config->sample_rate, config->format, config->channel_mask, - address, source); - int openResult = mDev->open_input_stream( - mDev, handle, devices, config, &halStream, flags, address, source); - if (openResult == OK) { - *inStream = new StreamInHalLocal(halStream, this); - } - ALOGV("open_input_stream status %d stream %p", openResult, inStream); - return openResult; -} - -status_t DeviceHalLocal::supportsAudioPatches(bool *supportsPatches) { - *supportsPatches = version() >= AUDIO_DEVICE_API_VERSION_3_0; - return OK; -} - -status_t DeviceHalLocal::createAudioPatch( - unsigned int num_sources, - const struct audio_port_config *sources, - unsigned int num_sinks, - const struct audio_port_config *sinks, - audio_patch_handle_t *patch) { - if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { - return mDev->create_audio_patch( - mDev, num_sources, sources, num_sinks, sinks, patch); - } else { - return INVALID_OPERATION; - } -} - -status_t DeviceHalLocal::releaseAudioPatch(audio_patch_handle_t patch) { - if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { - return mDev->release_audio_patch(mDev, patch); - } else { - return INVALID_OPERATION; - } -} - -status_t DeviceHalLocal::getAudioPort(struct audio_port *port) { - return mDev->get_audio_port(mDev, port); -} - -status_t DeviceHalLocal::setAudioPortConfig(const struct audio_port_config *config) { - if (version() >= AUDIO_DEVICE_API_VERSION_3_0) - return mDev->set_audio_port_config(mDev, config); - else - return INVALID_OPERATION; -} - -status_t DeviceHalLocal::dump(int fd) { - return mDev->dump(mDev, fd); -} - -void DeviceHalLocal::closeOutputStream(struct audio_stream_out *stream_out) { - mDev->close_output_stream(mDev, stream_out); -} - -void DeviceHalLocal::closeInputStream(struct audio_stream_in *stream_in) { - mDev->close_input_stream(mDev, stream_in); -} - -} // namespace android
diff --git a/media/libaudiohal/DeviceHalLocal.h b/media/libaudiohal/DeviceHalLocal.h deleted file mode 100644 index 865f296..0000000 --- a/media/libaudiohal/DeviceHalLocal.h +++ /dev/null
@@ -1,124 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_DEVICE_HAL_LOCAL_H -#define ANDROID_HARDWARE_DEVICE_HAL_LOCAL_H - -#include <hardware/audio.h> -#include <media/audiohal/DeviceHalInterface.h> - -namespace android { - -class DeviceHalLocal : public DeviceHalInterface -{ - public: - // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t. - virtual status_t getSupportedDevices(uint32_t *devices); - - // Check to see if the audio hardware interface has been initialized. - virtual status_t initCheck(); - - // Set the audio volume of a voice call. Range is between 0.0 and 1.0. - virtual status_t setVoiceVolume(float volume); - - // Set the audio volume for all audio activities other than voice call. - virtual status_t setMasterVolume(float volume); - - // Get the current master volume value for the HAL. - virtual status_t getMasterVolume(float *volume); - - // Called when the audio mode changes. - virtual status_t setMode(audio_mode_t mode); - - // Muting control. - virtual status_t setMicMute(bool state); - virtual status_t getMicMute(bool *state); - virtual status_t setMasterMute(bool state); - virtual status_t getMasterMute(bool *state); - - // Set global audio parameters. - virtual status_t setParameters(const String8& kvPairs); - - // Get global audio parameters. - virtual status_t getParameters(const String8& keys, String8 *values); - - // Returns audio input buffer size according to parameters passed. - virtual status_t getInputBufferSize(const struct audio_config *config, - size_t *size); - - // Creates and opens the audio hardware output stream. The stream is closed - // by releasing all references to the returned object. - virtual status_t openOutputStream( - audio_io_handle_t handle, - audio_devices_t devices, - audio_output_flags_t flags, - struct audio_config *config, - const char *address, - sp<StreamOutHalInterface> *outStream); - - // Creates and opens the audio hardware input stream. The stream is closed - // by releasing all references to the returned object. - virtual status_t openInputStream( - audio_io_handle_t handle, - audio_devices_t devices, - struct audio_config *config, - audio_input_flags_t flags, - const char *address, - audio_source_t source, - sp<StreamInHalInterface> *inStream); - - // Returns whether createAudioPatch and releaseAudioPatch operations are supported. - virtual status_t supportsAudioPatches(bool *supportsPatches); - - // Creates an audio patch between several source and sink ports. - virtual status_t createAudioPatch( - unsigned int num_sources, - const struct audio_port_config *sources, - unsigned int num_sinks, - const struct audio_port_config *sinks, - audio_patch_handle_t *patch); - - // Releases an audio patch. - virtual status_t releaseAudioPatch(audio_patch_handle_t patch); - - // Fills the list of supported attributes for a given audio port. - virtual status_t getAudioPort(struct audio_port *port); - - // Set audio port configuration. - virtual status_t setAudioPortConfig(const struct audio_port_config *config); - - virtual status_t dump(int fd); - - void closeOutputStream(struct audio_stream_out *stream_out); - void closeInputStream(struct audio_stream_in *stream_in); - - private: - audio_hw_device_t *mDev; - - friend class DevicesFactoryHalLocal; - - // Can not be constructed directly by clients. - explicit DeviceHalLocal(audio_hw_device_t *dev); - - // The destructor automatically closes the device. - virtual ~DeviceHalLocal(); - - uint32_t version() const { return mDev->common.version; } -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_DEVICE_HAL_LOCAL_H
diff --git a/media/libaudiohal/DevicesFactoryHalHidl.cpp b/media/libaudiohal/DevicesFactoryHalHidl.cpp deleted file mode 100644 index 31da263..0000000 --- a/media/libaudiohal/DevicesFactoryHalHidl.cpp +++ /dev/null
@@ -1,95 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <string.h> - -#define LOG_TAG "DevicesFactoryHalHidl" -//#define LOG_NDEBUG 0 - -#include <android/hardware/audio/2.0/IDevice.h> -#include <media/audiohal/hidl/HalDeathHandler.h> -#include <utils/Log.h> - -#include "ConversionHelperHidl.h" -#include "DeviceHalHidl.h" -#include "DevicesFactoryHalHidl.h" - -using ::android::hardware::audio::V2_0::IDevice; -using ::android::hardware::audio::V2_0::Result; -using ::android::hardware::Return; - -namespace android { - -DevicesFactoryHalHidl::DevicesFactoryHalHidl() { - mDevicesFactory = IDevicesFactory::getService(); - if (mDevicesFactory != 0) { - // It is assumed that DevicesFactory is owned by AudioFlinger - // and thus have the same lifespan. - mDevicesFactory->linkToDeath(HalDeathHandler::getInstance(), 0 /*cookie*/); - } else { - ALOGE("Failed to obtain IDevicesFactory service, terminating process."); - exit(1); - } -} - -DevicesFactoryHalHidl::~DevicesFactoryHalHidl() { -} - -// static -status_t DevicesFactoryHalHidl::nameFromHal(const char *name, IDevicesFactory::Device *device) { - if (strcmp(name, AUDIO_HARDWARE_MODULE_ID_PRIMARY) == 0) { - *device = IDevicesFactory::Device::PRIMARY; - return OK; - } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_A2DP) == 0) { - *device = IDevicesFactory::Device::A2DP; - return OK; - } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_USB) == 0) { - *device = IDevicesFactory::Device::USB; - return OK; - } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX) == 0) { - *device = IDevicesFactory::Device::R_SUBMIX; - return OK; - } else if(strcmp(name, AUDIO_HARDWARE_MODULE_ID_STUB) == 0) { - *device = IDevicesFactory::Device::STUB; - return OK; - } - ALOGE("Invalid device name %s", name); - return BAD_VALUE; -} - -status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) { - if (mDevicesFactory == 0) return NO_INIT; - IDevicesFactory::Device hidlDevice; - status_t status = nameFromHal(name, &hidlDevice); - if (status != OK) return status; - Result retval = Result::NOT_INITIALIZED; - Return<void> ret = mDevicesFactory->openDevice( - hidlDevice, - [&](Result r, const sp<IDevice>& result) { - retval = r; - if (retval == Result::OK) { - *device = new DeviceHalHidl(result); - } - }); - if (ret.isOk()) { - if (retval == Result::OK) return OK; - else if (retval == Result::INVALID_ARGUMENTS) return BAD_VALUE; - else return NO_INIT; - } - return FAILED_TRANSACTION; -} - -} // namespace android
diff --git a/media/libaudiohal/DevicesFactoryHalHidl.h b/media/libaudiohal/DevicesFactoryHalHidl.h deleted file mode 100644 index e2f1ad1..0000000 --- a/media/libaudiohal/DevicesFactoryHalHidl.h +++ /dev/null
@@ -1,53 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_H -#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_H - -#include <android/hardware/audio/2.0/IDevicesFactory.h> -#include <media/audiohal/DevicesFactoryHalInterface.h> -#include <utils/Errors.h> -#include <utils/RefBase.h> - -#include "DeviceHalHidl.h" - -using ::android::hardware::audio::V2_0::IDevicesFactory; - -namespace android { - -class DevicesFactoryHalHidl : public DevicesFactoryHalInterface -{ - public: - // Opens a device with the specified name. To close the device, it is - // necessary to release references to the returned object. - virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device); - - private: - friend class DevicesFactoryHalHybrid; - - sp<IDevicesFactory> mDevicesFactory; - - static status_t nameFromHal(const char *name, IDevicesFactory::Device *device); - - // Can not be constructed directly by clients. - DevicesFactoryHalHidl(); - - virtual ~DevicesFactoryHalHidl(); -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_H
diff --git a/media/libaudiohal/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/DevicesFactoryHalHybrid.cpp deleted file mode 100644 index 454b03b..0000000 --- a/media/libaudiohal/DevicesFactoryHalHybrid.cpp +++ /dev/null
@@ -1,54 +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 "DevicesFactoryHalHybrid" -//#define LOG_NDEBUG 0 - -#include "DevicesFactoryHalHybrid.h" -#include "DevicesFactoryHalLocal.h" -#ifndef USE_LEGACY_LOCAL_AUDIO_HAL -#include "DevicesFactoryHalHidl.h" -#endif - -namespace android { - -// static -sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() { - return new DevicesFactoryHalHybrid(); -} - -DevicesFactoryHalHybrid::DevicesFactoryHalHybrid() - : mLocalFactory(new DevicesFactoryHalLocal()), - mHidlFactory( -#ifdef USE_LEGACY_LOCAL_AUDIO_HAL - nullptr -#else - new DevicesFactoryHalHidl() -#endif - ) { -} - -DevicesFactoryHalHybrid::~DevicesFactoryHalHybrid() { -} - -status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) { - if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) { - return mHidlFactory->openDevice(name, device); - } - return mLocalFactory->openDevice(name, device); -} - -} // namespace android
diff --git a/media/libaudiohal/DevicesFactoryHalInterface.cpp b/media/libaudiohal/DevicesFactoryHalInterface.cpp new file mode 100644 index 0000000..4c8eaf6 --- /dev/null +++ b/media/libaudiohal/DevicesFactoryHalInterface.cpp
@@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/hardware/audio/2.0/IDevicesFactory.h> +#include <android/hardware/audio/4.0/IDevicesFactory.h> + +#include <DevicesFactoryHalHybrid.h> +#include <libaudiohal/4.0/DevicesFactoryHalHybrid.h> + +namespace android { + +// static +sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() { + if (hardware::audio::V4_0::IDevicesFactory::getService() != nullptr) { + return new V4_0::DevicesFactoryHalHybrid(); + } + if (hardware::audio::V2_0::IDevicesFactory::getService() != nullptr) { + return new DevicesFactoryHalHybrid(); + } + return nullptr; +} + +} // namespace android
diff --git a/media/libaudiohal/EffectBufferHalHidl.cpp b/media/libaudiohal/EffectBufferHalHidl.cpp deleted file mode 100644 index 8b5201b..0000000 --- a/media/libaudiohal/EffectBufferHalHidl.cpp +++ /dev/null
@@ -1,146 +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. - */ - -#include <atomic> - -#define LOG_TAG "EffectBufferHalHidl" -//#define LOG_NDEBUG 0 - -#include <android/hidl/allocator/1.0/IAllocator.h> -#include <hidlmemory/mapping.h> -#include <utils/Log.h> - -#include "ConversionHelperHidl.h" -#include "EffectBufferHalHidl.h" - -using ::android::hardware::Return; -using ::android::hidl::allocator::V1_0::IAllocator; - -namespace android { - -// static -uint64_t EffectBufferHalHidl::makeUniqueId() { - static std::atomic<uint64_t> counter{1}; - return counter++; -} - -// static -status_t EffectBufferHalInterface::allocate( - size_t size, sp<EffectBufferHalInterface>* buffer) { - return mirror(nullptr, size, buffer); -} - -// static -status_t EffectBufferHalInterface::mirror( - void* external, size_t size, sp<EffectBufferHalInterface>* buffer) { - sp<EffectBufferHalInterface> tempBuffer = new EffectBufferHalHidl(size); - status_t result = static_cast<EffectBufferHalHidl*>(tempBuffer.get())->init(); - if (result == OK) { - tempBuffer->setExternalData(external); - *buffer = tempBuffer; - } - return result; -} - -EffectBufferHalHidl::EffectBufferHalHidl(size_t size) - : mBufferSize(size), mFrameCountChanged(false), - mExternalData(nullptr), mAudioBuffer{0, {nullptr}} { - mHidlBuffer.id = makeUniqueId(); - mHidlBuffer.frameCount = 0; -} - -EffectBufferHalHidl::~EffectBufferHalHidl() { -} - -status_t EffectBufferHalHidl::init() { - sp<IAllocator> ashmem = IAllocator::getService("ashmem"); - if (ashmem == 0) { - ALOGE("Failed to retrieve ashmem allocator service"); - return NO_INIT; - } - status_t retval = NO_MEMORY; - Return<void> result = ashmem->allocate( - mBufferSize, - [&](bool success, const hidl_memory& memory) { - if (success) { - mHidlBuffer.data = memory; - retval = OK; - } - }); - if (result.isOk() && retval == OK) { - mMemory = hardware::mapMemory(mHidlBuffer.data); - if (mMemory != 0) { - mMemory->update(); - mAudioBuffer.raw = static_cast<void*>(mMemory->getPointer()); - memset(mAudioBuffer.raw, 0, mMemory->getSize()); - mMemory->commit(); - } else { - ALOGE("Failed to map allocated ashmem"); - retval = NO_MEMORY; - } - } else { - ALOGE("Failed to allocate %d bytes from ashmem", (int)mBufferSize); - } - return result.isOk() ? retval : FAILED_TRANSACTION; -} - -audio_buffer_t* EffectBufferHalHidl::audioBuffer() { - return &mAudioBuffer; -} - -void* EffectBufferHalHidl::externalData() const { - return mExternalData; -} - -void EffectBufferHalHidl::setFrameCount(size_t frameCount) { - mHidlBuffer.frameCount = frameCount; - mAudioBuffer.frameCount = frameCount; - mFrameCountChanged = true; -} - -bool EffectBufferHalHidl::checkFrameCountChange() { - bool result = mFrameCountChanged; - mFrameCountChanged = false; - return result; -} - -void EffectBufferHalHidl::setExternalData(void* external) { - mExternalData = external; -} - -void EffectBufferHalHidl::update() { - update(mBufferSize); -} - -void EffectBufferHalHidl::commit() { - commit(mBufferSize); -} - -void EffectBufferHalHidl::update(size_t size) { - if (mExternalData == nullptr) return; - mMemory->update(); - if (size > mBufferSize) size = mBufferSize; - memcpy(mAudioBuffer.raw, mExternalData, size); - mMemory->commit(); -} - -void EffectBufferHalHidl::commit(size_t size) { - if (mExternalData == nullptr) return; - if (size > mBufferSize) size = mBufferSize; - memcpy(mExternalData, mAudioBuffer.raw, size); -} - -} // namespace android
diff --git a/media/libaudiohal/EffectBufferHalHidl.h b/media/libaudiohal/EffectBufferHalHidl.h deleted file mode 100644 index 66a81c2..0000000 --- a/media/libaudiohal/EffectBufferHalHidl.h +++ /dev/null
@@ -1,71 +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. - */ - -#ifndef ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H -#define ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H - -#include <android/hardware/audio/effect/2.0/types.h> -#include <android/hidl/memory/1.0/IMemory.h> -#include <hidl/HidlSupport.h> -#include <media/audiohal/EffectBufferHalInterface.h> -#include <system/audio_effect.h> - -using android::hardware::audio::effect::V2_0::AudioBuffer; -using android::hardware::hidl_memory; -using android::hidl::memory::V1_0::IMemory; - -namespace android { - -class EffectBufferHalHidl : public EffectBufferHalInterface -{ - public: - virtual audio_buffer_t* audioBuffer(); - virtual void* externalData() const; - - virtual void setExternalData(void* external); - virtual void setFrameCount(size_t frameCount); - virtual bool checkFrameCountChange(); - - virtual void update(); - virtual void commit(); - virtual void update(size_t size); - virtual void commit(size_t size); - - const AudioBuffer& hidlBuffer() const { return mHidlBuffer; } - - private: - friend class EffectBufferHalInterface; - - static uint64_t makeUniqueId(); - - const size_t mBufferSize; - bool mFrameCountChanged; - void* mExternalData; - AudioBuffer mHidlBuffer; - sp<IMemory> mMemory; - audio_buffer_t mAudioBuffer; - - // Can not be constructed directly by clients. - explicit EffectBufferHalHidl(size_t size); - - virtual ~EffectBufferHalHidl(); - - status_t init(); -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H
diff --git a/media/libaudiohal/EffectBufferHalLocal.cpp b/media/libaudiohal/EffectBufferHalLocal.cpp deleted file mode 100644 index 7951c8e..0000000 --- a/media/libaudiohal/EffectBufferHalLocal.cpp +++ /dev/null
@@ -1,91 +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 "EffectBufferHalLocal" -//#define LOG_NDEBUG 0 - -#include <utils/Log.h> - -#include "EffectBufferHalLocal.h" - -namespace android { - -// static -status_t EffectBufferHalInterface::allocate( - size_t size, sp<EffectBufferHalInterface>* buffer) { - *buffer = new EffectBufferHalLocal(size); - return OK; -} - -// static -status_t EffectBufferHalInterface::mirror( - void* external, size_t size, sp<EffectBufferHalInterface>* buffer) { - *buffer = new EffectBufferHalLocal(external, size); - return OK; -} - -EffectBufferHalLocal::EffectBufferHalLocal(size_t size) - : mOwnBuffer(new uint8_t[size]), - mBufferSize(size), mFrameCountChanged(false), - mAudioBuffer{0, {mOwnBuffer.get()}} { -} - -EffectBufferHalLocal::EffectBufferHalLocal(void* external, size_t size) - : mOwnBuffer(nullptr), - mBufferSize(size), mFrameCountChanged(false), - mAudioBuffer{0, {external}} { -} - -EffectBufferHalLocal::~EffectBufferHalLocal() { -} - -audio_buffer_t* EffectBufferHalLocal::audioBuffer() { - return &mAudioBuffer; -} - -void* EffectBufferHalLocal::externalData() const { - return mAudioBuffer.raw; -} - -void EffectBufferHalLocal::setFrameCount(size_t frameCount) { - mAudioBuffer.frameCount = frameCount; - mFrameCountChanged = true; -} - -void EffectBufferHalLocal::setExternalData(void* external) { - ALOGE_IF(mOwnBuffer != nullptr, "Attempt to set external data for allocated buffer"); - mAudioBuffer.raw = external; -} - -bool EffectBufferHalLocal::checkFrameCountChange() { - bool result = mFrameCountChanged; - mFrameCountChanged = false; - return result; -} - -void EffectBufferHalLocal::update() { -} - -void EffectBufferHalLocal::commit() { -} - -void EffectBufferHalLocal::update(size_t) { -} - -void EffectBufferHalLocal::commit(size_t) { -} - -} // namespace android
diff --git a/media/libaudiohal/EffectBufferHalLocal.h b/media/libaudiohal/EffectBufferHalLocal.h deleted file mode 100644 index d2b624b..0000000 --- a/media/libaudiohal/EffectBufferHalLocal.h +++ /dev/null
@@ -1,61 +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. - */ - -#ifndef ANDROID_HARDWARE_EFFECT_BUFFER_HAL_LOCAL_H -#define ANDROID_HARDWARE_EFFECT_BUFFER_HAL_LOCAL_H - -#include <memory> - -#include <media/audiohal/EffectBufferHalInterface.h> -#include <system/audio_effect.h> - -namespace android { - -class EffectBufferHalLocal : public EffectBufferHalInterface -{ - public: - virtual audio_buffer_t* audioBuffer(); - virtual void* externalData() const; - - virtual void setExternalData(void* external); - virtual void setFrameCount(size_t frameCount); - virtual bool checkFrameCountChange(); - - virtual void update(); - virtual void commit(); - virtual void update(size_t size); - virtual void commit(size_t size); - - private: - friend class EffectBufferHalInterface; - - std::unique_ptr<uint8_t[]> mOwnBuffer; - const size_t mBufferSize; - bool mFrameCountChanged; - audio_buffer_t mAudioBuffer; - - // Can not be constructed directly by clients. - explicit EffectBufferHalLocal(size_t size); - EffectBufferHalLocal(void* external, size_t size); - - virtual ~EffectBufferHalLocal(); - - status_t init(); -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_EFFECT_BUFFER_HAL_LOCAL_H
diff --git a/media/libaudiohal/EffectHalHidl.cpp b/media/libaudiohal/EffectHalHidl.cpp deleted file mode 100644 index 61fb6bab..0000000 --- a/media/libaudiohal/EffectHalHidl.cpp +++ /dev/null
@@ -1,329 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "EffectHalHidl" -//#define LOG_NDEBUG 0 - -#include <hwbinder/IPCThreadState.h> -#include <media/EffectsFactoryApi.h> -#include <utils/Log.h> - -#include "ConversionHelperHidl.h" -#include "EffectBufferHalHidl.h" -#include "EffectHalHidl.h" -#include "HidlUtils.h" - -using ::android::hardware::audio::effect::V2_0::AudioBuffer; -using ::android::hardware::audio::effect::V2_0::EffectBufferAccess; -using ::android::hardware::audio::effect::V2_0::EffectConfigParameters; -using ::android::hardware::audio::effect::V2_0::MessageQueueFlagBits; -using ::android::hardware::audio::effect::V2_0::Result; -using ::android::hardware::audio::common::V2_0::AudioChannelMask; -using ::android::hardware::audio::common::V2_0::AudioFormat; -using ::android::hardware::hidl_vec; -using ::android::hardware::MQDescriptorSync; -using ::android::hardware::Return; - -namespace android { - -EffectHalHidl::EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId) - : mEffect(effect), mEffectId(effectId), mBuffersChanged(true), mEfGroup(nullptr) { -} - -EffectHalHidl::~EffectHalHidl() { - if (mEffect != 0) { - close(); - mEffect.clear(); - hardware::IPCThreadState::self()->flushCommands(); - } - if (mEfGroup) { - EventFlag::deleteEventFlag(&mEfGroup); - } -} - -// static -void EffectHalHidl::effectDescriptorToHal( - const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor) { - HidlUtils::uuidToHal(descriptor.type, &halDescriptor->type); - HidlUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid); - halDescriptor->flags = static_cast<uint32_t>(descriptor.flags); - halDescriptor->cpuLoad = descriptor.cpuLoad; - halDescriptor->memoryUsage = descriptor.memoryUsage; - memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size()); - memcpy(halDescriptor->implementor, - descriptor.implementor.data(), descriptor.implementor.size()); -} - -// TODO(mnaganov): These buffer conversion functions should be shared with Effect wrapper -// via HidlUtils. Move them there when hardware/interfaces will get un-frozen again. - -// static -void EffectHalHidl::effectBufferConfigFromHal( - const buffer_config_t& halConfig, EffectBufferConfig* config) { - config->samplingRateHz = halConfig.samplingRate; - config->channels = AudioChannelMask(halConfig.channels); - config->format = AudioFormat(halConfig.format); - config->accessMode = EffectBufferAccess(halConfig.accessMode); - config->mask = EffectConfigParameters(halConfig.mask); -} - -// static -void EffectHalHidl::effectBufferConfigToHal( - const EffectBufferConfig& config, buffer_config_t* halConfig) { - halConfig->buffer.frameCount = 0; - halConfig->buffer.raw = NULL; - halConfig->samplingRate = config.samplingRateHz; - halConfig->channels = static_cast<uint32_t>(config.channels); - halConfig->bufferProvider.cookie = NULL; - halConfig->bufferProvider.getBuffer = NULL; - halConfig->bufferProvider.releaseBuffer = NULL; - halConfig->format = static_cast<uint8_t>(config.format); - halConfig->accessMode = static_cast<uint8_t>(config.accessMode); - halConfig->mask = static_cast<uint8_t>(config.mask); -} - -// static -void EffectHalHidl::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) { - effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg); - effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg); -} - -// static -void EffectHalHidl::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) { - effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg); - effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg); -} - -// static -status_t EffectHalHidl::analyzeResult(const Result& result) { - switch (result) { - case Result::OK: return OK; - case Result::INVALID_ARGUMENTS: return BAD_VALUE; - case Result::INVALID_STATE: return NOT_ENOUGH_DATA; - case Result::NOT_INITIALIZED: return NO_INIT; - case Result::NOT_SUPPORTED: return INVALID_OPERATION; - case Result::RESULT_TOO_BIG: return NO_MEMORY; - default: return NO_INIT; - } -} - -status_t EffectHalHidl::setInBuffer(const sp<EffectBufferHalInterface>& buffer) { - if (mInBuffer == 0 || buffer->audioBuffer() != mInBuffer->audioBuffer()) { - mBuffersChanged = true; - } - mInBuffer = buffer; - return OK; -} - -status_t EffectHalHidl::setOutBuffer(const sp<EffectBufferHalInterface>& buffer) { - if (mOutBuffer == 0 || buffer->audioBuffer() != mOutBuffer->audioBuffer()) { - mBuffersChanged = true; - } - mOutBuffer = buffer; - return OK; -} - -status_t EffectHalHidl::process() { - return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS)); -} - -status_t EffectHalHidl::processReverse() { - return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS_REVERSE)); -} - -status_t EffectHalHidl::prepareForProcessing() { - std::unique_ptr<StatusMQ> tempStatusMQ; - Result retval; - Return<void> ret = mEffect->prepareForProcessing( - [&](Result r, const MQDescriptorSync<Result>& statusMQ) { - retval = r; - if (retval == Result::OK) { - tempStatusMQ.reset(new StatusMQ(statusMQ)); - if (tempStatusMQ->isValid() && tempStatusMQ->getEventFlagWord()) { - EventFlag::createEventFlag(tempStatusMQ->getEventFlagWord(), &mEfGroup); - } - } - }); - if (!ret.isOk() || retval != Result::OK) { - return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION; - } - if (!tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) { - ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for effects"); - ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), - "Status message queue for effects is invalid"); - ALOGE_IF(!mEfGroup, "Event flag creation for effects failed"); - return NO_INIT; - } - mStatusMQ = std::move(tempStatusMQ); - return OK; -} - -bool EffectHalHidl::needToResetBuffers() { - if (mBuffersChanged) return true; - bool inBufferFrameCountUpdated = mInBuffer->checkFrameCountChange(); - bool outBufferFrameCountUpdated = mOutBuffer->checkFrameCountChange(); - return inBufferFrameCountUpdated || outBufferFrameCountUpdated; -} - -status_t EffectHalHidl::processImpl(uint32_t mqFlag) { - if (mEffect == 0 || mInBuffer == 0 || mOutBuffer == 0) return NO_INIT; - status_t status; - if (!mStatusMQ && (status = prepareForProcessing()) != OK) { - return status; - } - if (needToResetBuffers() && (status = setProcessBuffers()) != OK) { - return status; - } - // The data is already in the buffers, just need to flush it and wake up the server side. - std::atomic_thread_fence(std::memory_order_release); - mEfGroup->wake(mqFlag); - uint32_t efState = 0; -retry: - status_t ret = mEfGroup->wait( - static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING), &efState); - if (efState & static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING)) { - Result retval = Result::NOT_INITIALIZED; - mStatusMQ->read(&retval); - if (retval == Result::OK || retval == Result::INVALID_STATE) { - // Sync back the changed contents of the buffer. - std::atomic_thread_fence(std::memory_order_acquire); - } - return analyzeResult(retval); - } - if (ret == -EAGAIN || ret == -EINTR) { - // Spurious wakeup. This normally retries no more than once. - goto retry; - } - return ret; -} - -status_t EffectHalHidl::setProcessBuffers() { - Return<Result> ret = mEffect->setProcessBuffers( - static_cast<EffectBufferHalHidl*>(mInBuffer.get())->hidlBuffer(), - static_cast<EffectBufferHalHidl*>(mOutBuffer.get())->hidlBuffer()); - if (ret.isOk() && ret == Result::OK) { - mBuffersChanged = false; - return OK; - } - return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION; -} - -status_t EffectHalHidl::command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, - uint32_t *replySize, void *pReplyData) { - if (mEffect == 0) return NO_INIT; - - // Special cases. - if (cmdCode == EFFECT_CMD_SET_CONFIG || cmdCode == EFFECT_CMD_SET_CONFIG_REVERSE) { - return setConfigImpl(cmdCode, cmdSize, pCmdData, replySize, pReplyData); - } else if (cmdCode == EFFECT_CMD_GET_CONFIG || cmdCode == EFFECT_CMD_GET_CONFIG_REVERSE) { - return getConfigImpl(cmdCode, replySize, pReplyData); - } - - // Common case. - hidl_vec<uint8_t> hidlData; - if (pCmdData != nullptr && cmdSize > 0) { - hidlData.setToExternal(reinterpret_cast<uint8_t*>(pCmdData), cmdSize); - } - status_t status; - uint32_t replySizeStub = 0; - if (replySize == nullptr || pReplyData == nullptr) replySize = &replySizeStub; - Return<void> ret = mEffect->command(cmdCode, hidlData, *replySize, - [&](int32_t s, const hidl_vec<uint8_t>& result) { - status = s; - if (status == 0) { - if (*replySize > result.size()) *replySize = result.size(); - if (pReplyData != nullptr && *replySize > 0) { - memcpy(pReplyData, &result[0], *replySize); - } - } - }); - return ret.isOk() ? status : FAILED_TRANSACTION; -} - -status_t EffectHalHidl::getDescriptor(effect_descriptor_t *pDescriptor) { - if (mEffect == 0) return NO_INIT; - Result retval = Result::NOT_INITIALIZED; - Return<void> ret = mEffect->getDescriptor( - [&](Result r, const EffectDescriptor& result) { - retval = r; - if (retval == Result::OK) { - effectDescriptorToHal(result, pDescriptor); - } - }); - return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION; -} - -status_t EffectHalHidl::close() { - if (mEffect == 0) return NO_INIT; - Return<Result> ret = mEffect->close(); - return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION; -} - -status_t EffectHalHidl::getConfigImpl( - uint32_t cmdCode, uint32_t *replySize, void *pReplyData) { - if (replySize == NULL || *replySize != sizeof(effect_config_t) || pReplyData == NULL) { - return BAD_VALUE; - } - status_t result = FAILED_TRANSACTION; - Return<void> ret; - if (cmdCode == EFFECT_CMD_GET_CONFIG) { - ret = mEffect->getConfig([&] (Result r, const EffectConfig &hidlConfig) { - result = analyzeResult(r); - if (r == Result::OK) { - effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData)); - } - }); - } else { - ret = mEffect->getConfigReverse([&] (Result r, const EffectConfig &hidlConfig) { - result = analyzeResult(r); - if (r == Result::OK) { - effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData)); - } - }); - } - if (!ret.isOk()) { - result = FAILED_TRANSACTION; - } - return result; -} - -status_t EffectHalHidl::setConfigImpl( - uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) { - if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) || - replySize == NULL || *replySize != sizeof(int32_t) || pReplyData == NULL) { - return BAD_VALUE; - } - const effect_config_t *halConfig = static_cast<effect_config_t*>(pCmdData); - if (halConfig->inputCfg.bufferProvider.getBuffer != NULL || - halConfig->inputCfg.bufferProvider.releaseBuffer != NULL || - halConfig->outputCfg.bufferProvider.getBuffer != NULL || - halConfig->outputCfg.bufferProvider.releaseBuffer != NULL) { - ALOGE("Buffer provider callbacks are not supported"); - } - EffectConfig hidlConfig; - effectConfigFromHal(*halConfig, &hidlConfig); - Return<Result> ret = cmdCode == EFFECT_CMD_SET_CONFIG ? - mEffect->setConfig(hidlConfig, nullptr, nullptr) : - mEffect->setConfigReverse(hidlConfig, nullptr, nullptr); - status_t result = FAILED_TRANSACTION; - if (ret.isOk()) { - result = analyzeResult(ret); - *static_cast<int32_t*>(pReplyData) = result; - } - return result; -} - -} // namespace android
diff --git a/media/libaudiohal/EffectHalLocal.cpp b/media/libaudiohal/EffectHalLocal.cpp deleted file mode 100644 index dd465c3..0000000 --- a/media/libaudiohal/EffectHalLocal.cpp +++ /dev/null
@@ -1,83 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "EffectHalLocal" -//#define LOG_NDEBUG 0 - -#include <media/EffectsFactoryApi.h> -#include <utils/Log.h> - -#include "EffectHalLocal.h" - -namespace android { - -EffectHalLocal::EffectHalLocal(effect_handle_t handle) - : mHandle(handle) { -} - -EffectHalLocal::~EffectHalLocal() { - int status = EffectRelease(mHandle); - ALOGW_IF(status, "Error releasing effect %p: %s", mHandle, strerror(-status)); - mHandle = 0; -} - -status_t EffectHalLocal::setInBuffer(const sp<EffectBufferHalInterface>& buffer) { - mInBuffer = buffer; - return OK; -} - -status_t EffectHalLocal::setOutBuffer(const sp<EffectBufferHalInterface>& buffer) { - mOutBuffer = buffer; - return OK; -} - -status_t EffectHalLocal::process() { - if (mInBuffer == nullptr || mOutBuffer == nullptr) { - ALOGE_IF(mInBuffer == nullptr, "Input buffer not set"); - ALOGE_IF(mOutBuffer == nullptr, "Output buffer not set"); - return NO_INIT; - } - return (*mHandle)->process(mHandle, mInBuffer->audioBuffer(), mOutBuffer->audioBuffer()); -} - -status_t EffectHalLocal::processReverse() { - if ((*mHandle)->process_reverse != NULL) { - if (mInBuffer == nullptr || mOutBuffer == nullptr) { - ALOGE_IF(mInBuffer == nullptr, "Input buffer not set"); - ALOGE_IF(mOutBuffer == nullptr, "Output buffer not set"); - return NO_INIT; - } - return (*mHandle)->process_reverse( - mHandle, mInBuffer->audioBuffer(), mOutBuffer->audioBuffer()); - } else { - return INVALID_OPERATION; - } -} - -status_t EffectHalLocal::command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, - uint32_t *replySize, void *pReplyData) { - return (*mHandle)->command(mHandle, cmdCode, cmdSize, pCmdData, replySize, pReplyData); -} - -status_t EffectHalLocal::getDescriptor(effect_descriptor_t *pDescriptor) { - return (*mHandle)->get_descriptor(mHandle, pDescriptor); -} - -status_t EffectHalLocal::close() { - return OK; -} - -} // namespace android
diff --git a/media/libaudiohal/EffectHalLocal.h b/media/libaudiohal/EffectHalLocal.h deleted file mode 100644 index 693fb50..0000000 --- a/media/libaudiohal/EffectHalLocal.h +++ /dev/null
@@ -1,72 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_EFFECT_HAL_LOCAL_H -#define ANDROID_HARDWARE_EFFECT_HAL_LOCAL_H - -#include <hardware/audio_effect.h> -#include <media/audiohal/EffectHalInterface.h> - -namespace android { - -class EffectHalLocal : public EffectHalInterface -{ - public: - // Set the input buffer. - virtual status_t setInBuffer(const sp<EffectBufferHalInterface>& buffer); - - // Set the output buffer. - virtual status_t setOutBuffer(const sp<EffectBufferHalInterface>& buffer); - - // Effect process function. - virtual status_t process(); - - // Process reverse stream function. This function is used to pass - // a reference stream to the effect engine. - virtual status_t processReverse(); - - // Send a command and receive a response to/from effect engine. - virtual status_t command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, - uint32_t *replySize, void *pReplyData); - - // Returns the effect descriptor. - virtual status_t getDescriptor(effect_descriptor_t *pDescriptor); - - // Free resources on the remote side. - virtual status_t close(); - - // Whether it's a local implementation. - virtual bool isLocal() const { return true; } - - effect_handle_t handle() const { return mHandle; } - - private: - effect_handle_t mHandle; - sp<EffectBufferHalInterface> mInBuffer; - sp<EffectBufferHalInterface> mOutBuffer; - - friend class EffectsFactoryHalLocal; - - // Can not be constructed directly by clients. - explicit EffectHalLocal(effect_handle_t handle); - - // The destructor automatically releases the effect. - virtual ~EffectHalLocal(); -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_EFFECT_HAL_LOCAL_H
diff --git a/media/libaudiohal/EffectsFactoryHalHidl.cpp b/media/libaudiohal/EffectsFactoryHalHidl.cpp deleted file mode 100644 index a8081b7..0000000 --- a/media/libaudiohal/EffectsFactoryHalHidl.cpp +++ /dev/null
@@ -1,148 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "EffectsFactoryHalHidl" -//#define LOG_NDEBUG 0 - -#include <cutils/native_handle.h> - -#include "ConversionHelperHidl.h" -#include "EffectHalHidl.h" -#include "EffectsFactoryHalHidl.h" -#include "HidlUtils.h" - -using ::android::hardware::audio::common::V2_0::Uuid; -using ::android::hardware::audio::effect::V2_0::IEffect; -using ::android::hardware::audio::effect::V2_0::Result; -using ::android::hardware::Return; - -namespace android { - -// static -sp<EffectsFactoryHalInterface> EffectsFactoryHalInterface::create() { - return new EffectsFactoryHalHidl(); -} - -// static -bool EffectsFactoryHalInterface::isNullUuid(const effect_uuid_t *pEffectUuid) { - return memcmp(pEffectUuid, EFFECT_UUID_NULL, sizeof(effect_uuid_t)) == 0; -} - -EffectsFactoryHalHidl::EffectsFactoryHalHidl() : ConversionHelperHidl("EffectsFactory") { - mEffectsFactory = IEffectsFactory::getService(); - if (mEffectsFactory == 0) { - ALOGE("Failed to obtain IEffectsFactory service, terminating process."); - exit(1); - } -} - -EffectsFactoryHalHidl::~EffectsFactoryHalHidl() { -} - -status_t EffectsFactoryHalHidl::queryAllDescriptors() { - if (mEffectsFactory == 0) return NO_INIT; - Result retval = Result::NOT_INITIALIZED; - Return<void> ret = mEffectsFactory->getAllDescriptors( - [&](Result r, const hidl_vec<EffectDescriptor>& result) { - retval = r; - if (retval == Result::OK) { - mLastDescriptors = result; - } - }); - if (ret.isOk()) { - return retval == Result::OK ? OK : NO_INIT; - } - mLastDescriptors.resize(0); - return processReturn(__FUNCTION__, ret); -} - -status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) { - status_t queryResult = queryAllDescriptors(); - if (queryResult == OK) { - *pNumEffects = mLastDescriptors.size(); - } - return queryResult; -} - -status_t EffectsFactoryHalHidl::getDescriptor( - uint32_t index, effect_descriptor_t *pDescriptor) { - // TODO: We need somehow to track the changes on the server side - // or figure out how to convert everybody to query all the descriptors at once. - // TODO: check for nullptr - if (mLastDescriptors.size() == 0) { - status_t queryResult = queryAllDescriptors(); - if (queryResult != OK) return queryResult; - } - if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND; - EffectHalHidl::effectDescriptorToHal(mLastDescriptors[index], pDescriptor); - return OK; -} - -status_t EffectsFactoryHalHidl::getDescriptor( - const effect_uuid_t *pEffectUuid, effect_descriptor_t *pDescriptor) { - // TODO: check for nullptr - if (mEffectsFactory == 0) return NO_INIT; - Uuid hidlUuid; - HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid); - Result retval = Result::NOT_INITIALIZED; - Return<void> ret = mEffectsFactory->getDescriptor(hidlUuid, - [&](Result r, const EffectDescriptor& result) { - retval = r; - if (retval == Result::OK) { - EffectHalHidl::effectDescriptorToHal(result, pDescriptor); - } - }); - if (ret.isOk()) { - if (retval == Result::OK) return OK; - else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND; - else return NO_INIT; - } - return processReturn(__FUNCTION__, ret); -} - -status_t EffectsFactoryHalHidl::createEffect( - const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t ioId, - sp<EffectHalInterface> *effect) { - if (mEffectsFactory == 0) return NO_INIT; - Uuid hidlUuid; - HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid); - Result retval = Result::NOT_INITIALIZED; - Return<void> ret = mEffectsFactory->createEffect( - hidlUuid, sessionId, ioId, - [&](Result r, const sp<IEffect>& result, uint64_t effectId) { - retval = r; - if (retval == Result::OK) { - *effect = new EffectHalHidl(result, effectId); - } - }); - if (ret.isOk()) { - if (retval == Result::OK) return OK; - else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND; - else return NO_INIT; - } - return processReturn(__FUNCTION__, ret); -} - -status_t EffectsFactoryHalHidl::dumpEffects(int fd) { - if (mEffectsFactory == 0) return NO_INIT; - native_handle_t* hidlHandle = native_handle_create(1, 0); - hidlHandle->data[0] = fd; - Return<void> ret = mEffectsFactory->debugDump(hidlHandle); - native_handle_delete(hidlHandle); - return processReturn(__FUNCTION__, ret); -} - -} // namespace android
diff --git a/media/libaudiohal/EffectsFactoryHalHidl.h b/media/libaudiohal/EffectsFactoryHalHidl.h deleted file mode 100644 index e89f042..0000000 --- a/media/libaudiohal/EffectsFactoryHalHidl.h +++ /dev/null
@@ -1,67 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H -#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H - -#include <android/hardware/audio/effect/2.0/IEffectsFactory.h> -#include <android/hardware/audio/effect/2.0/types.h> -#include <media/audiohal/EffectsFactoryHalInterface.h> - -namespace android { - -using ::android::hardware::audio::effect::V2_0::EffectDescriptor; -using ::android::hardware::audio::effect::V2_0::IEffectsFactory; -using ::android::hardware::hidl_vec; - -class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public ConversionHelperHidl -{ - public: - // Returns the number of different effects in all loaded libraries. - virtual status_t queryNumberEffects(uint32_t *pNumEffects); - - // Returns a descriptor of the next available effect. - virtual status_t getDescriptor(uint32_t index, - effect_descriptor_t *pDescriptor); - - virtual status_t getDescriptor(const effect_uuid_t *pEffectUuid, - effect_descriptor_t *pDescriptor); - - // Creates an effect engine of the specified type. - // To release the effect engine, it is necessary to release references - // to the returned effect object. - virtual status_t createEffect(const effect_uuid_t *pEffectUuid, - int32_t sessionId, int32_t ioId, - sp<EffectHalInterface> *effect); - - virtual status_t dumpEffects(int fd); - - private: - friend class EffectsFactoryHalInterface; - - sp<IEffectsFactory> mEffectsFactory; - hidl_vec<EffectDescriptor> mLastDescriptors; - - // Can not be constructed directly by clients. - EffectsFactoryHalHidl(); - virtual ~EffectsFactoryHalHidl(); - - status_t queryAllDescriptors(); -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
diff --git a/media/libaudiohal/EffectsFactoryHalInterface.cpp b/media/libaudiohal/EffectsFactoryHalInterface.cpp new file mode 100644 index 0000000..ead1fa2 --- /dev/null +++ b/media/libaudiohal/EffectsFactoryHalInterface.cpp
@@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/hardware/audio/effect/2.0/IEffectsFactory.h> +#include <android/hardware/audio/effect/4.0/IEffectsFactory.h> + +#include <EffectsFactoryHalHidl.h> +#include <libaudiohal/4.0/EffectsFactoryHalHidl.h> + + +namespace android { + +// static +sp<EffectsFactoryHalInterface> EffectsFactoryHalInterface::create() { + if (hardware::audio::effect::V4_0::IEffectsFactory::getService() != nullptr) { + return new V4_0::EffectsFactoryHalHidl(); + } + if (hardware::audio::effect::V2_0::IEffectsFactory::getService() != nullptr) { + return new EffectsFactoryHalHidl(); + } + return nullptr; +} + +// static +bool EffectsFactoryHalInterface::isNullUuid(const effect_uuid_t *pEffectUuid) { + return memcmp(pEffectUuid, EFFECT_UUID_NULL, sizeof(effect_uuid_t)) == 0; +} + +} // namespace android
diff --git a/media/libaudiohal/EffectsFactoryHalLocal.cpp b/media/libaudiohal/EffectsFactoryHalLocal.cpp deleted file mode 100644 index bbdef5d..0000000 --- a/media/libaudiohal/EffectsFactoryHalLocal.cpp +++ /dev/null
@@ -1,63 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <media/EffectsFactoryApi.h> - -#include "EffectHalLocal.h" -#include "EffectsFactoryHalLocal.h" - -namespace android { - -// static -sp<EffectsFactoryHalInterface> EffectsFactoryHalInterface::create() { - return new EffectsFactoryHalLocal(); -} - -// static -bool EffectsFactoryHalInterface::isNullUuid(const effect_uuid_t *pEffectUuid) { - return EffectIsNullUuid(pEffectUuid); -} - -status_t EffectsFactoryHalLocal::queryNumberEffects(uint32_t *pNumEffects) { - return EffectQueryNumberEffects(pNumEffects); -} - -status_t EffectsFactoryHalLocal::getDescriptor( - uint32_t index, effect_descriptor_t *pDescriptor) { - return EffectQueryEffect(index, pDescriptor); -} - -status_t EffectsFactoryHalLocal::getDescriptor( - const effect_uuid_t *pEffectUuid, effect_descriptor_t *pDescriptor) { - return EffectGetDescriptor(pEffectUuid, pDescriptor); -} - -status_t EffectsFactoryHalLocal::createEffect( - const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t ioId, - sp<EffectHalInterface> *effect) { - effect_handle_t handle; - int result = EffectCreate(pEffectUuid, sessionId, ioId, &handle); - if (result == 0) { - *effect = new EffectHalLocal(handle); - } - return result; -} - -status_t EffectsFactoryHalLocal::dumpEffects(int fd) { - return EffectDumpEffects(fd); -} - -} // namespace android
diff --git a/media/libaudiohal/EffectsFactoryHalLocal.h b/media/libaudiohal/EffectsFactoryHalLocal.h deleted file mode 100644 index d5b81be..0000000 --- a/media/libaudiohal/EffectsFactoryHalLocal.h +++ /dev/null
@@ -1,57 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_LOCAL_H -#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_LOCAL_H - -#include <media/audiohal/EffectsFactoryHalInterface.h> - -namespace android { - -class EffectsFactoryHalLocal : public EffectsFactoryHalInterface -{ - public: - // Returns the number of different effects in all loaded libraries. - virtual status_t queryNumberEffects(uint32_t *pNumEffects); - - // Returns a descriptor of the next available effect. - virtual status_t getDescriptor(uint32_t index, - effect_descriptor_t *pDescriptor); - - virtual status_t getDescriptor(const effect_uuid_t *pEffectUuid, - effect_descriptor_t *pDescriptor); - - // Creates an effect engine of the specified type. - // To release the effect engine, it is necessary to release references - // to the returned effect object. - virtual status_t createEffect(const effect_uuid_t *pEffectUuid, - int32_t sessionId, int32_t ioId, - sp<EffectHalInterface> *effect); - - virtual status_t dumpEffects(int fd); - - private: - friend class EffectsFactoryHalInterface; - - // Can not be constructed directly by clients. - EffectsFactoryHalLocal() {} - - virtual ~EffectsFactoryHalLocal() {} -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_LOCAL_H
diff --git a/media/libaudiohal/HalDeathHandlerHidl.cpp b/media/libaudiohal/HalDeathHandlerHidl.cpp index a742671..6e33523 100644 --- a/media/libaudiohal/HalDeathHandlerHidl.cpp +++ b/media/libaudiohal/HalDeathHandlerHidl.cpp
@@ -48,12 +48,13 @@ void HalDeathHandler::serviceDied(uint64_t /*cookie*/, const wp<IBase>& /*who*/) { // No matter which of the service objects has died, - // we need to run all the registered handlers and crash our process. + // we need to run all the registered handlers and exit. std::lock_guard<std::mutex> guard(mHandlersLock); for (const auto& handler : mHandlers) { handler.second(); } - LOG_ALWAYS_FATAL("HAL server crashed, need to restart"); + ALOGE("HAL server crashed, audio server is restarting"); + _exit(1); // Avoid calling atexit handlers, as this code runs on a thread from RPC threadpool. } } // namespace android
diff --git a/media/libaudiohal/StreamHalHidl.cpp b/media/libaudiohal/StreamHalHidl.cpp deleted file mode 100644 index 0cafa36..0000000 --- a/media/libaudiohal/StreamHalHidl.cpp +++ /dev/null
@@ -1,752 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "StreamHalHidl" -//#define LOG_NDEBUG 0 - -#include <android/hardware/audio/2.0/IStreamOutCallback.h> -#include <hwbinder/IPCThreadState.h> -#include <mediautils/SchedulingPolicyService.h> -#include <utils/Log.h> - -#include "DeviceHalHidl.h" -#include "EffectHalHidl.h" -#include "StreamHalHidl.h" - -using ::android::hardware::audio::common::V2_0::AudioChannelMask; -using ::android::hardware::audio::common::V2_0::AudioFormat; -using ::android::hardware::audio::common::V2_0::ThreadInfo; -using ::android::hardware::audio::V2_0::AudioDrain; -using ::android::hardware::audio::V2_0::IStreamOutCallback; -using ::android::hardware::audio::V2_0::MessageQueueFlagBits; -using ::android::hardware::audio::V2_0::MmapBufferInfo; -using ::android::hardware::audio::V2_0::MmapPosition; -using ::android::hardware::audio::V2_0::ParameterValue; -using ::android::hardware::audio::V2_0::Result; -using ::android::hardware::audio::V2_0::TimeSpec; -using ::android::hardware::MQDescriptorSync; -using ::android::hardware::Return; -using ::android::hardware::Void; -using ReadCommand = ::android::hardware::audio::V2_0::IStreamIn::ReadCommand; - -namespace android { - -StreamHalHidl::StreamHalHidl(IStream *stream) - : ConversionHelperHidl("Stream"), - mStream(stream), - mHalThreadPriority(HAL_THREAD_PRIORITY_DEFAULT), - mCachedBufferSize(0){ - - // Instrument audio signal power logging. - // Note: This assumes channel mask, format, and sample rate do not change after creation. - if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) { - // Obtain audio properties (see StreamHalHidl::getAudioProperties() below). - Return<void> ret = mStream->getAudioProperties( - [&](uint32_t sr, AudioChannelMask m, AudioFormat f) { - mStreamPowerLog.init(sr, - static_cast<audio_channel_mask_t>(m), - static_cast<audio_format_t>(f)); - }); - } -} - -StreamHalHidl::~StreamHalHidl() { - mStream = nullptr; -} - -status_t StreamHalHidl::getSampleRate(uint32_t *rate) { - if (!mStream) return NO_INIT; - return processReturn("getSampleRate", mStream->getSampleRate(), rate); -} - -status_t StreamHalHidl::getBufferSize(size_t *size) { - if (!mStream) return NO_INIT; - status_t status = processReturn("getBufferSize", mStream->getBufferSize(), size); - if (status == OK) { - mCachedBufferSize = *size; - } - return status; -} - -status_t StreamHalHidl::getChannelMask(audio_channel_mask_t *mask) { - if (!mStream) return NO_INIT; - return processReturn("getChannelMask", mStream->getChannelMask(), mask); -} - -status_t StreamHalHidl::getFormat(audio_format_t *format) { - if (!mStream) return NO_INIT; - return processReturn("getFormat", mStream->getFormat(), format); -} - -status_t StreamHalHidl::getAudioProperties( - uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) { - if (!mStream) return NO_INIT; - Return<void> ret = mStream->getAudioProperties( - [&](uint32_t sr, AudioChannelMask m, AudioFormat f) { - *sampleRate = sr; - *mask = static_cast<audio_channel_mask_t>(m); - *format = static_cast<audio_format_t>(f); - }); - return processReturn("getAudioProperties", ret); -} - -status_t StreamHalHidl::setParameters(const String8& kvPairs) { - if (!mStream) return NO_INIT; - hidl_vec<ParameterValue> hidlParams; - status_t status = parametersFromHal(kvPairs, &hidlParams); - if (status != OK) return status; - return processReturn("setParameters", mStream->setParameters(hidlParams)); -} - -status_t StreamHalHidl::getParameters(const String8& keys, String8 *values) { - values->clear(); - if (!mStream) return NO_INIT; - hidl_vec<hidl_string> hidlKeys; - status_t status = keysFromHal(keys, &hidlKeys); - if (status != OK) return status; - Result retval; - Return<void> ret = mStream->getParameters( - hidlKeys, - [&](Result r, const hidl_vec<ParameterValue>& parameters) { - retval = r; - if (retval == Result::OK) { - parametersToHal(parameters, values); - } - }); - return processReturn("getParameters", ret, retval); -} - -status_t StreamHalHidl::addEffect(sp<EffectHalInterface> effect) { - if (!mStream) return NO_INIT; - return processReturn("addEffect", mStream->addEffect( - static_cast<EffectHalHidl*>(effect.get())->effectId())); -} - -status_t StreamHalHidl::removeEffect(sp<EffectHalInterface> effect) { - if (!mStream) return NO_INIT; - return processReturn("removeEffect", mStream->removeEffect( - static_cast<EffectHalHidl*>(effect.get())->effectId())); -} - -status_t StreamHalHidl::standby() { - if (!mStream) return NO_INIT; - return processReturn("standby", mStream->standby()); -} - -status_t StreamHalHidl::dump(int fd) { - if (!mStream) return NO_INIT; - native_handle_t* hidlHandle = native_handle_create(1, 0); - hidlHandle->data[0] = fd; - Return<void> ret = mStream->debugDump(hidlHandle); - native_handle_delete(hidlHandle); - mStreamPowerLog.dump(fd); - return processReturn("dump", ret); -} - -status_t StreamHalHidl::start() { - if (!mStream) return NO_INIT; - return processReturn("start", mStream->start()); -} - -status_t StreamHalHidl::stop() { - if (!mStream) return NO_INIT; - return processReturn("stop", mStream->stop()); -} - -status_t StreamHalHidl::createMmapBuffer(int32_t minSizeFrames, - struct audio_mmap_buffer_info *info) { - Result retval; - Return<void> ret = mStream->createMmapBuffer( - minSizeFrames, - [&](Result r, const MmapBufferInfo& hidlInfo) { - retval = r; - if (retval == Result::OK) { - const native_handle *handle = hidlInfo.sharedMemory.handle(); - if (handle->numFds > 0) { - info->shared_memory_fd = handle->data[0]; - info->buffer_size_frames = hidlInfo.bufferSizeFrames; - info->burst_size_frames = hidlInfo.burstSizeFrames; - // info->shared_memory_address is not needed in HIDL context - info->shared_memory_address = NULL; - } else { - retval = Result::NOT_INITIALIZED; - } - } - }); - return processReturn("createMmapBuffer", ret, retval); -} - -status_t StreamHalHidl::getMmapPosition(struct audio_mmap_position *position) { - Result retval; - Return<void> ret = mStream->getMmapPosition( - [&](Result r, const MmapPosition& hidlPosition) { - retval = r; - if (retval == Result::OK) { - position->time_nanoseconds = hidlPosition.timeNanoseconds; - position->position_frames = hidlPosition.positionFrames; - } - }); - return processReturn("getMmapPosition", ret, retval); -} - -status_t StreamHalHidl::setHalThreadPriority(int priority) { - mHalThreadPriority = priority; - return OK; -} - -status_t StreamHalHidl::getCachedBufferSize(size_t *size) { - if (mCachedBufferSize != 0) { - *size = mCachedBufferSize; - return OK; - } - return getBufferSize(size); -} - -bool StreamHalHidl::requestHalThreadPriority(pid_t threadPid, pid_t threadId) { - if (mHalThreadPriority == HAL_THREAD_PRIORITY_DEFAULT) { - return true; - } - int err = requestPriority( - threadPid, threadId, - mHalThreadPriority, false /*isForApp*/, true /*asynchronous*/); - ALOGE_IF(err, "failed to set priority %d for pid %d tid %d; error %d", - mHalThreadPriority, threadPid, threadId, err); - // Audio will still work, but latency will be higher and sometimes unacceptable. - return err == 0; -} - -namespace { - -/* Notes on callback ownership. - -This is how (Hw)Binder ownership model looks like. The server implementation -is owned by Binder framework (via sp<>). Proxies are owned by clients. -When the last proxy disappears, Binder framework releases the server impl. - -Thus, it is not needed to keep any references to StreamOutCallback (this is -the server impl) -- it will live as long as HAL server holds a strong ref to -IStreamOutCallback proxy. We clear that reference by calling 'clearCallback' -from the destructor of StreamOutHalHidl. - -The callback only keeps a weak reference to the stream. The stream is owned -by AudioFlinger. - -*/ - -struct StreamOutCallback : public IStreamOutCallback { - StreamOutCallback(const wp<StreamOutHalHidl>& stream) : mStream(stream) {} - - // IStreamOutCallback implementation - Return<void> onWriteReady() override { - sp<StreamOutHalHidl> stream = mStream.promote(); - if (stream != 0) { - stream->onWriteReady(); - } - return Void(); - } - - Return<void> onDrainReady() override { - sp<StreamOutHalHidl> stream = mStream.promote(); - if (stream != 0) { - stream->onDrainReady(); - } - return Void(); - } - - Return<void> onError() override { - sp<StreamOutHalHidl> stream = mStream.promote(); - if (stream != 0) { - stream->onError(); - } - return Void(); - } - - private: - wp<StreamOutHalHidl> mStream; -}; - -} // namespace - -StreamOutHalHidl::StreamOutHalHidl(const sp<IStreamOut>& stream) - : StreamHalHidl(stream.get()), mStream(stream), mWriterClient(0), mEfGroup(nullptr) { -} - -StreamOutHalHidl::~StreamOutHalHidl() { - if (mStream != 0) { - if (mCallback.unsafe_get()) { - processReturn("clearCallback", mStream->clearCallback()); - } - processReturn("close", mStream->close()); - mStream.clear(); - } - mCallback.clear(); - hardware::IPCThreadState::self()->flushCommands(); - if (mEfGroup) { - EventFlag::deleteEventFlag(&mEfGroup); - } -} - -status_t StreamOutHalHidl::getFrameSize(size_t *size) { - if (mStream == 0) return NO_INIT; - return processReturn("getFrameSize", mStream->getFrameSize(), size); -} - -status_t StreamOutHalHidl::getLatency(uint32_t *latency) { - if (mStream == 0) return NO_INIT; - if (mWriterClient == gettid() && mCommandMQ) { - return callWriterThread( - WriteCommand::GET_LATENCY, "getLatency", nullptr, 0, - [&](const WriteStatus& writeStatus) { - *latency = writeStatus.reply.latencyMs; - }); - } else { - return processReturn("getLatency", mStream->getLatency(), latency); - } -} - -status_t StreamOutHalHidl::setVolume(float left, float right) { - if (mStream == 0) return NO_INIT; - return processReturn("setVolume", mStream->setVolume(left, right)); -} - -status_t StreamOutHalHidl::write(const void *buffer, size_t bytes, size_t *written) { - if (mStream == 0) return NO_INIT; - *written = 0; - - if (bytes == 0 && !mDataMQ) { - // Can't determine the size for the MQ buffer. Wait for a non-empty write request. - ALOGW_IF(mCallback.unsafe_get(), "First call to async write with 0 bytes"); - return OK; - } - - status_t status; - if (!mDataMQ) { - // In case if playback starts close to the end of a compressed track, the bytes - // that need to be written is less than the actual buffer size. Need to use - // full buffer size for the MQ since otherwise after seeking back to the middle - // data will be truncated. - size_t bufferSize; - if ((status = getCachedBufferSize(&bufferSize)) != OK) { - return status; - } - if (bytes > bufferSize) bufferSize = bytes; - if ((status = prepareForWriting(bufferSize)) != OK) { - return status; - } - } - - status = callWriterThread( - WriteCommand::WRITE, "write", static_cast<const uint8_t*>(buffer), bytes, - [&] (const WriteStatus& writeStatus) { - *written = writeStatus.reply.written; - // Diagnostics of the cause of b/35813113. - ALOGE_IF(*written > bytes, - "hal reports more bytes written than asked for: %lld > %lld", - (long long)*written, (long long)bytes); - }); - mStreamPowerLog.log(buffer, *written); - return status; -} - -status_t StreamOutHalHidl::callWriterThread( - WriteCommand cmd, const char* cmdName, - const uint8_t* data, size_t dataSize, StreamOutHalHidl::WriterCallback callback) { - if (!mCommandMQ->write(&cmd)) { - ALOGE("command message queue write failed for \"%s\"", cmdName); - return -EAGAIN; - } - if (data != nullptr) { - size_t availableToWrite = mDataMQ->availableToWrite(); - if (dataSize > availableToWrite) { - ALOGW("truncating write data from %lld to %lld due to insufficient data queue space", - (long long)dataSize, (long long)availableToWrite); - dataSize = availableToWrite; - } - if (!mDataMQ->write(data, dataSize)) { - ALOGE("data message queue write failed for \"%s\"", cmdName); - } - } - mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)); - - // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422 - uint32_t efState = 0; -retry: - status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL), &efState); - if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)) { - WriteStatus writeStatus; - writeStatus.retval = Result::NOT_INITIALIZED; - if (!mStatusMQ->read(&writeStatus)) { - ALOGE("status message read failed for \"%s\"", cmdName); - } - if (writeStatus.retval == Result::OK) { - ret = OK; - callback(writeStatus); - } else { - ret = processReturn(cmdName, writeStatus.retval); - } - return ret; - } - if (ret == -EAGAIN || ret == -EINTR) { - // Spurious wakeup. This normally retries no more than once. - goto retry; - } - return ret; -} - -status_t StreamOutHalHidl::prepareForWriting(size_t bufferSize) { - std::unique_ptr<CommandMQ> tempCommandMQ; - std::unique_ptr<DataMQ> tempDataMQ; - std::unique_ptr<StatusMQ> tempStatusMQ; - Result retval; - pid_t halThreadPid, halThreadTid; - Return<void> ret = mStream->prepareForWriting( - 1, bufferSize, - [&](Result r, - const CommandMQ::Descriptor& commandMQ, - const DataMQ::Descriptor& dataMQ, - const StatusMQ::Descriptor& statusMQ, - const ThreadInfo& halThreadInfo) { - retval = r; - if (retval == Result::OK) { - tempCommandMQ.reset(new CommandMQ(commandMQ)); - tempDataMQ.reset(new DataMQ(dataMQ)); - tempStatusMQ.reset(new StatusMQ(statusMQ)); - if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) { - EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup); - } - halThreadPid = halThreadInfo.pid; - halThreadTid = halThreadInfo.tid; - } - }); - if (!ret.isOk() || retval != Result::OK) { - return processReturn("prepareForWriting", ret, retval); - } - if (!tempCommandMQ || !tempCommandMQ->isValid() || - !tempDataMQ || !tempDataMQ->isValid() || - !tempStatusMQ || !tempStatusMQ->isValid() || - !mEfGroup) { - ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing"); - ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(), - "Command message queue for writing is invalid"); - ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for writing"); - ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for writing is invalid"); - ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for writing"); - ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), - "Status message queue for writing is invalid"); - ALOGE_IF(!mEfGroup, "Event flag creation for writing failed"); - return NO_INIT; - } - requestHalThreadPriority(halThreadPid, halThreadTid); - - mCommandMQ = std::move(tempCommandMQ); - mDataMQ = std::move(tempDataMQ); - mStatusMQ = std::move(tempStatusMQ); - mWriterClient = gettid(); - return OK; -} - -status_t StreamOutHalHidl::getRenderPosition(uint32_t *dspFrames) { - if (mStream == 0) return NO_INIT; - Result retval; - Return<void> ret = mStream->getRenderPosition( - [&](Result r, uint32_t d) { - retval = r; - if (retval == Result::OK) { - *dspFrames = d; - } - }); - return processReturn("getRenderPosition", ret, retval); -} - -status_t StreamOutHalHidl::getNextWriteTimestamp(int64_t *timestamp) { - if (mStream == 0) return NO_INIT; - Result retval; - Return<void> ret = mStream->getNextWriteTimestamp( - [&](Result r, int64_t t) { - retval = r; - if (retval == Result::OK) { - *timestamp = t; - } - }); - return processReturn("getRenderPosition", ret, retval); -} - -status_t StreamOutHalHidl::setCallback(wp<StreamOutHalInterfaceCallback> callback) { - if (mStream == 0) return NO_INIT; - status_t status = processReturn( - "setCallback", mStream->setCallback(new StreamOutCallback(this))); - if (status == OK) { - mCallback = callback; - } - return status; -} - -status_t StreamOutHalHidl::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) { - if (mStream == 0) return NO_INIT; - Return<void> ret = mStream->supportsPauseAndResume( - [&](bool p, bool r) { - *supportsPause = p; - *supportsResume = r; - }); - return processReturn("supportsPauseAndResume", ret); -} - -status_t StreamOutHalHidl::pause() { - if (mStream == 0) return NO_INIT; - return processReturn("pause", mStream->pause()); -} - -status_t StreamOutHalHidl::resume() { - if (mStream == 0) return NO_INIT; - return processReturn("pause", mStream->resume()); -} - -status_t StreamOutHalHidl::supportsDrain(bool *supportsDrain) { - if (mStream == 0) return NO_INIT; - return processReturn("supportsDrain", mStream->supportsDrain(), supportsDrain); -} - -status_t StreamOutHalHidl::drain(bool earlyNotify) { - if (mStream == 0) return NO_INIT; - return processReturn( - "drain", mStream->drain(earlyNotify ? AudioDrain::EARLY_NOTIFY : AudioDrain::ALL)); -} - -status_t StreamOutHalHidl::flush() { - if (mStream == 0) return NO_INIT; - return processReturn("pause", mStream->flush()); -} - -status_t StreamOutHalHidl::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) { - if (mStream == 0) return NO_INIT; - if (mWriterClient == gettid() && mCommandMQ) { - return callWriterThread( - WriteCommand::GET_PRESENTATION_POSITION, "getPresentationPosition", nullptr, 0, - [&](const WriteStatus& writeStatus) { - *frames = writeStatus.reply.presentationPosition.frames; - timestamp->tv_sec = writeStatus.reply.presentationPosition.timeStamp.tvSec; - timestamp->tv_nsec = writeStatus.reply.presentationPosition.timeStamp.tvNSec; - }); - } else { - Result retval; - Return<void> ret = mStream->getPresentationPosition( - [&](Result r, uint64_t hidlFrames, const TimeSpec& hidlTimeStamp) { - retval = r; - if (retval == Result::OK) { - *frames = hidlFrames; - timestamp->tv_sec = hidlTimeStamp.tvSec; - timestamp->tv_nsec = hidlTimeStamp.tvNSec; - } - }); - return processReturn("getPresentationPosition", ret, retval); - } -} - -void StreamOutHalHidl::onWriteReady() { - sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); - if (callback == 0) return; - ALOGV("asyncCallback onWriteReady"); - callback->onWriteReady(); -} - -void StreamOutHalHidl::onDrainReady() { - sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); - if (callback == 0) return; - ALOGV("asyncCallback onDrainReady"); - callback->onDrainReady(); -} - -void StreamOutHalHidl::onError() { - sp<StreamOutHalInterfaceCallback> callback = mCallback.promote(); - if (callback == 0) return; - ALOGV("asyncCallback onError"); - callback->onError(); -} - - -StreamInHalHidl::StreamInHalHidl(const sp<IStreamIn>& stream) - : StreamHalHidl(stream.get()), mStream(stream), mReaderClient(0), mEfGroup(nullptr) { -} - -StreamInHalHidl::~StreamInHalHidl() { - if (mStream != 0) { - processReturn("close", mStream->close()); - mStream.clear(); - hardware::IPCThreadState::self()->flushCommands(); - } - if (mEfGroup) { - EventFlag::deleteEventFlag(&mEfGroup); - } -} - -status_t StreamInHalHidl::getFrameSize(size_t *size) { - if (mStream == 0) return NO_INIT; - return processReturn("getFrameSize", mStream->getFrameSize(), size); -} - -status_t StreamInHalHidl::setGain(float gain) { - if (mStream == 0) return NO_INIT; - return processReturn("setGain", mStream->setGain(gain)); -} - -status_t StreamInHalHidl::read(void *buffer, size_t bytes, size_t *read) { - if (mStream == 0) return NO_INIT; - *read = 0; - - if (bytes == 0 && !mDataMQ) { - // Can't determine the size for the MQ buffer. Wait for a non-empty read request. - return OK; - } - - status_t status; - if (!mDataMQ && (status = prepareForReading(bytes)) != OK) { - return status; - } - - ReadParameters params; - params.command = ReadCommand::READ; - params.params.read = bytes; - status = callReaderThread(params, "read", - [&](const ReadStatus& readStatus) { - const size_t availToRead = mDataMQ->availableToRead(); - if (!mDataMQ->read(static_cast<uint8_t*>(buffer), std::min(bytes, availToRead))) { - ALOGE("data message queue read failed for \"read\""); - } - ALOGW_IF(availToRead != readStatus.reply.read, - "HAL read report inconsistent: mq = %d, status = %d", - (int32_t)availToRead, (int32_t)readStatus.reply.read); - *read = readStatus.reply.read; - }); - mStreamPowerLog.log(buffer, *read); - return status; -} - -status_t StreamInHalHidl::callReaderThread( - const ReadParameters& params, const char* cmdName, - StreamInHalHidl::ReaderCallback callback) { - if (!mCommandMQ->write(¶ms)) { - ALOGW("command message queue write failed"); - return -EAGAIN; - } - mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)); - - // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422 - uint32_t efState = 0; -retry: - status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState); - if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) { - ReadStatus readStatus; - readStatus.retval = Result::NOT_INITIALIZED; - if (!mStatusMQ->read(&readStatus)) { - ALOGE("status message read failed for \"%s\"", cmdName); - } - if (readStatus.retval == Result::OK) { - ret = OK; - callback(readStatus); - } else { - ret = processReturn(cmdName, readStatus.retval); - } - return ret; - } - if (ret == -EAGAIN || ret == -EINTR) { - // Spurious wakeup. This normally retries no more than once. - goto retry; - } - return ret; -} - -status_t StreamInHalHidl::prepareForReading(size_t bufferSize) { - std::unique_ptr<CommandMQ> tempCommandMQ; - std::unique_ptr<DataMQ> tempDataMQ; - std::unique_ptr<StatusMQ> tempStatusMQ; - Result retval; - pid_t halThreadPid, halThreadTid; - Return<void> ret = mStream->prepareForReading( - 1, bufferSize, - [&](Result r, - const CommandMQ::Descriptor& commandMQ, - const DataMQ::Descriptor& dataMQ, - const StatusMQ::Descriptor& statusMQ, - const ThreadInfo& halThreadInfo) { - retval = r; - if (retval == Result::OK) { - tempCommandMQ.reset(new CommandMQ(commandMQ)); - tempDataMQ.reset(new DataMQ(dataMQ)); - tempStatusMQ.reset(new StatusMQ(statusMQ)); - if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) { - EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup); - } - halThreadPid = halThreadInfo.pid; - halThreadTid = halThreadInfo.tid; - } - }); - if (!ret.isOk() || retval != Result::OK) { - return processReturn("prepareForReading", ret, retval); - } - if (!tempCommandMQ || !tempCommandMQ->isValid() || - !tempDataMQ || !tempDataMQ->isValid() || - !tempStatusMQ || !tempStatusMQ->isValid() || - !mEfGroup) { - ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing"); - ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(), - "Command message queue for writing is invalid"); - ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for reading"); - ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for reading is invalid"); - ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for reading"); - ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(), - "Status message queue for reading is invalid"); - ALOGE_IF(!mEfGroup, "Event flag creation for reading failed"); - return NO_INIT; - } - requestHalThreadPriority(halThreadPid, halThreadTid); - - mCommandMQ = std::move(tempCommandMQ); - mDataMQ = std::move(tempDataMQ); - mStatusMQ = std::move(tempStatusMQ); - mReaderClient = gettid(); - return OK; -} - -status_t StreamInHalHidl::getInputFramesLost(uint32_t *framesLost) { - if (mStream == 0) return NO_INIT; - return processReturn("getInputFramesLost", mStream->getInputFramesLost(), framesLost); -} - -status_t StreamInHalHidl::getCapturePosition(int64_t *frames, int64_t *time) { - if (mStream == 0) return NO_INIT; - if (mReaderClient == gettid() && mCommandMQ) { - ReadParameters params; - params.command = ReadCommand::GET_CAPTURE_POSITION; - return callReaderThread(params, "getCapturePosition", - [&](const ReadStatus& readStatus) { - *frames = readStatus.reply.capturePosition.frames; - *time = readStatus.reply.capturePosition.time; - }); - } else { - Result retval; - Return<void> ret = mStream->getCapturePosition( - [&](Result r, uint64_t hidlFrames, uint64_t hidlTime) { - retval = r; - if (retval == Result::OK) { - *frames = hidlFrames; - *time = hidlTime; - } - }); - return processReturn("getCapturePosition", ret, retval); - } -} - -} // namespace android
diff --git a/media/libaudiohal/StreamHalHidl.h b/media/libaudiohal/StreamHalHidl.h deleted file mode 100644 index d4ab943..0000000 --- a/media/libaudiohal/StreamHalHidl.h +++ /dev/null
@@ -1,239 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_STREAM_HAL_HIDL_H -#define ANDROID_HARDWARE_STREAM_HAL_HIDL_H - -#include <atomic> - -#include <android/hardware/audio/2.0/IStream.h> -#include <android/hardware/audio/2.0/IStreamIn.h> -#include <android/hardware/audio/2.0/IStreamOut.h> -#include <fmq/EventFlag.h> -#include <fmq/MessageQueue.h> -#include <media/audiohal/StreamHalInterface.h> - -#include "ConversionHelperHidl.h" -#include "StreamPowerLog.h" - -using ::android::hardware::audio::V2_0::IStream; -using ::android::hardware::audio::V2_0::IStreamIn; -using ::android::hardware::audio::V2_0::IStreamOut; -using ::android::hardware::EventFlag; -using ::android::hardware::MessageQueue; -using ::android::hardware::Return; -using ReadParameters = ::android::hardware::audio::V2_0::IStreamIn::ReadParameters; -using ReadStatus = ::android::hardware::audio::V2_0::IStreamIn::ReadStatus; -using WriteCommand = ::android::hardware::audio::V2_0::IStreamOut::WriteCommand; -using WriteStatus = ::android::hardware::audio::V2_0::IStreamOut::WriteStatus; - -namespace android { - -class DeviceHalHidl; - -class StreamHalHidl : public virtual StreamHalInterface, public ConversionHelperHidl -{ - public: - // Return the sampling rate in Hz - eg. 44100. - virtual status_t getSampleRate(uint32_t *rate); - - // Return size of input/output buffer in bytes for this stream - eg. 4800. - virtual status_t getBufferSize(size_t *size); - - // Return the channel mask. - virtual status_t getChannelMask(audio_channel_mask_t *mask); - - // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT. - virtual status_t getFormat(audio_format_t *format); - - // Convenience method. - virtual status_t getAudioProperties( - uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format); - - // Set audio stream parameters. - virtual status_t setParameters(const String8& kvPairs); - - // Get audio stream parameters. - virtual status_t getParameters(const String8& keys, String8 *values); - - // Add or remove the effect on the stream. - virtual status_t addEffect(sp<EffectHalInterface> effect); - virtual status_t removeEffect(sp<EffectHalInterface> effect); - - // Put the audio hardware input/output into standby mode. - virtual status_t standby(); - - virtual status_t dump(int fd); - - // Start a stream operating in mmap mode. - virtual status_t start(); - - // Stop a stream operating in mmap mode. - virtual status_t stop(); - - // Retrieve information on the data buffer in mmap mode. - virtual status_t createMmapBuffer(int32_t minSizeFrames, - struct audio_mmap_buffer_info *info); - - // Get current read/write position in the mmap buffer - virtual status_t getMmapPosition(struct audio_mmap_position *position); - - // Set the priority of the thread that interacts with the HAL - // (must match the priority of the audioflinger's thread that calls 'read' / 'write') - virtual status_t setHalThreadPriority(int priority); - - protected: - // Subclasses can not be constructed directly by clients. - explicit StreamHalHidl(IStream *stream); - - // The destructor automatically closes the stream. - virtual ~StreamHalHidl(); - - status_t getCachedBufferSize(size_t *size); - - bool requestHalThreadPriority(pid_t threadPid, pid_t threadId); - - // mStreamPowerLog is used for audio signal power logging. - StreamPowerLog mStreamPowerLog; - - private: - const int HAL_THREAD_PRIORITY_DEFAULT = -1; - IStream *mStream; - int mHalThreadPriority; - size_t mCachedBufferSize; -}; - -class StreamOutHalHidl : public StreamOutHalInterface, public StreamHalHidl { - public: - // Return the frame size (number of bytes per sample) of a stream. - virtual status_t getFrameSize(size_t *size); - - // Return the audio hardware driver estimated latency in milliseconds. - virtual status_t getLatency(uint32_t *latency); - - // Use this method in situations where audio mixing is done in the hardware. - virtual status_t setVolume(float left, float right); - - // Write audio buffer to driver. - virtual status_t write(const void *buffer, size_t bytes, size_t *written); - - // Return the number of audio frames written by the audio dsp to DAC since - // the output has exited standby. - virtual status_t getRenderPosition(uint32_t *dspFrames); - - // Get the local time at which the next write to the audio driver will be presented. - virtual status_t getNextWriteTimestamp(int64_t *timestamp); - - // Set the callback for notifying completion of non-blocking write and drain. - virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback); - - // Returns whether pause and resume operations are supported. - virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume); - - // Notifies to the audio driver to resume playback following a pause. - virtual status_t pause(); - - // Notifies to the audio driver to resume playback following a pause. - virtual status_t resume(); - - // Returns whether drain operation is supported. - virtual status_t supportsDrain(bool *supportsDrain); - - // Requests notification when data buffered by the driver/hardware has been played. - virtual status_t drain(bool earlyNotify); - - // Notifies to the audio driver to flush the queued data. - virtual status_t flush(); - - // Return a recent count of the number of audio frames presented to an external observer. - virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp); - - // Methods used by StreamOutCallback (HIDL). - void onWriteReady(); - void onDrainReady(); - void onError(); - - private: - friend class DeviceHalHidl; - typedef MessageQueue<WriteCommand, hardware::kSynchronizedReadWrite> CommandMQ; - typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ; - typedef MessageQueue<WriteStatus, hardware::kSynchronizedReadWrite> StatusMQ; - - wp<StreamOutHalInterfaceCallback> mCallback; - sp<IStreamOut> mStream; - std::unique_ptr<CommandMQ> mCommandMQ; - std::unique_ptr<DataMQ> mDataMQ; - std::unique_ptr<StatusMQ> mStatusMQ; - std::atomic<pid_t> mWriterClient; - EventFlag* mEfGroup; - - // Can not be constructed directly by clients. - StreamOutHalHidl(const sp<IStreamOut>& stream); - - virtual ~StreamOutHalHidl(); - - using WriterCallback = std::function<void(const WriteStatus& writeStatus)>; - status_t callWriterThread( - WriteCommand cmd, const char* cmdName, - const uint8_t* data, size_t dataSize, WriterCallback callback); - status_t prepareForWriting(size_t bufferSize); -}; - -class StreamInHalHidl : public StreamInHalInterface, public StreamHalHidl { - public: - // Return the frame size (number of bytes per sample) of a stream. - virtual status_t getFrameSize(size_t *size); - - // Set the input gain for the audio driver. - virtual status_t setGain(float gain); - - // Read audio buffer in from driver. - virtual status_t read(void *buffer, size_t bytes, size_t *read); - - // Return the amount of input frames lost in the audio driver. - virtual status_t getInputFramesLost(uint32_t *framesLost); - - // Return a recent count of the number of audio frames received and - // the clock time associated with that frame count. - virtual status_t getCapturePosition(int64_t *frames, int64_t *time); - - private: - friend class DeviceHalHidl; - typedef MessageQueue<ReadParameters, hardware::kSynchronizedReadWrite> CommandMQ; - typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ; - typedef MessageQueue<ReadStatus, hardware::kSynchronizedReadWrite> StatusMQ; - - sp<IStreamIn> mStream; - std::unique_ptr<CommandMQ> mCommandMQ; - std::unique_ptr<DataMQ> mDataMQ; - std::unique_ptr<StatusMQ> mStatusMQ; - std::atomic<pid_t> mReaderClient; - EventFlag* mEfGroup; - - // Can not be constructed directly by clients. - StreamInHalHidl(const sp<IStreamIn>& stream); - - virtual ~StreamInHalHidl(); - - using ReaderCallback = std::function<void(const ReadStatus& readStatus)>; - status_t callReaderThread( - const ReadParameters& params, const char* cmdName, ReaderCallback callback); - status_t prepareForReading(size_t bufferSize); -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_STREAM_HAL_HIDL_H
diff --git a/media/libaudiohal/StreamHalLocal.cpp b/media/libaudiohal/StreamHalLocal.cpp deleted file mode 100644 index dc17f5c..0000000 --- a/media/libaudiohal/StreamHalLocal.cpp +++ /dev/null
@@ -1,319 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "StreamHalLocal" -//#define LOG_NDEBUG 0 - -#include <hardware/audio.h> -#include <utils/Log.h> - -#include "DeviceHalLocal.h" -#include "EffectHalLocal.h" -#include "StreamHalLocal.h" - -namespace android { - -StreamHalLocal::StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device) - : mDevice(device), - mStream(stream) { - // Instrument audio signal power logging. - // Note: This assumes channel mask, format, and sample rate do not change after creation. - if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) { - mStreamPowerLog.init(mStream->get_sample_rate(mStream), - mStream->get_channels(mStream), - mStream->get_format(mStream)); - } -} - -StreamHalLocal::~StreamHalLocal() { - mStream = 0; - mDevice.clear(); -} - -status_t StreamHalLocal::getSampleRate(uint32_t *rate) { - *rate = mStream->get_sample_rate(mStream); - return OK; -} - -status_t StreamHalLocal::getBufferSize(size_t *size) { - *size = mStream->get_buffer_size(mStream); - return OK; -} - -status_t StreamHalLocal::getChannelMask(audio_channel_mask_t *mask) { - *mask = mStream->get_channels(mStream); - return OK; -} - -status_t StreamHalLocal::getFormat(audio_format_t *format) { - *format = mStream->get_format(mStream); - return OK; -} - -status_t StreamHalLocal::getAudioProperties( - uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) { - *sampleRate = mStream->get_sample_rate(mStream); - *mask = mStream->get_channels(mStream); - *format = mStream->get_format(mStream); - return OK; -} - -status_t StreamHalLocal::setParameters(const String8& kvPairs) { - return mStream->set_parameters(mStream, kvPairs.string()); -} - -status_t StreamHalLocal::getParameters(const String8& keys, String8 *values) { - char *halValues = mStream->get_parameters(mStream, keys.string()); - if (halValues != NULL) { - values->setTo(halValues); - free(halValues); - } else { - values->clear(); - } - return OK; -} - -status_t StreamHalLocal::addEffect(sp<EffectHalInterface> effect) { - LOG_ALWAYS_FATAL_IF(!effect->isLocal(), "Only local effects can be added for a local stream"); - return mStream->add_audio_effect(mStream, - static_cast<EffectHalLocal*>(effect.get())->handle()); -} - -status_t StreamHalLocal::removeEffect(sp<EffectHalInterface> effect) { - LOG_ALWAYS_FATAL_IF(!effect->isLocal(), "Only local effects can be removed for a local stream"); - return mStream->remove_audio_effect(mStream, - static_cast<EffectHalLocal*>(effect.get())->handle()); -} - -status_t StreamHalLocal::standby() { - return mStream->standby(mStream); -} - -status_t StreamHalLocal::dump(int fd) { - status_t status = mStream->dump(mStream, fd); - mStreamPowerLog.dump(fd); - return status; -} - -status_t StreamHalLocal::setHalThreadPriority(int) { - // Don't need to do anything as local hal is executed by audioflinger directly - // on the same thread. - return OK; -} - -StreamOutHalLocal::StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device) - : StreamHalLocal(&stream->common, device), mStream(stream) { -} - -StreamOutHalLocal::~StreamOutHalLocal() { - mCallback.clear(); - mDevice->closeOutputStream(mStream); - mStream = 0; -} - -status_t StreamOutHalLocal::getFrameSize(size_t *size) { - *size = audio_stream_out_frame_size(mStream); - return OK; -} - -status_t StreamOutHalLocal::getLatency(uint32_t *latency) { - *latency = mStream->get_latency(mStream); - return OK; -} - -status_t StreamOutHalLocal::setVolume(float left, float right) { - if (mStream->set_volume == NULL) return INVALID_OPERATION; - return mStream->set_volume(mStream, left, right); -} - -status_t StreamOutHalLocal::write(const void *buffer, size_t bytes, size_t *written) { - ssize_t writeResult = mStream->write(mStream, buffer, bytes); - if (writeResult > 0) { - *written = writeResult; - mStreamPowerLog.log(buffer, *written); - return OK; - } else { - *written = 0; - return writeResult; - } -} - -status_t StreamOutHalLocal::getRenderPosition(uint32_t *dspFrames) { - return mStream->get_render_position(mStream, dspFrames); -} - -status_t StreamOutHalLocal::getNextWriteTimestamp(int64_t *timestamp) { - if (mStream->get_next_write_timestamp == NULL) return INVALID_OPERATION; - return mStream->get_next_write_timestamp(mStream, timestamp); -} - -status_t StreamOutHalLocal::setCallback(wp<StreamOutHalInterfaceCallback> callback) { - if (mStream->set_callback == NULL) return INVALID_OPERATION; - status_t result = mStream->set_callback(mStream, StreamOutHalLocal::asyncCallback, this); - if (result == OK) { - mCallback = callback; - } - return result; -} - -// static -int StreamOutHalLocal::asyncCallback(stream_callback_event_t event, void*, void *cookie) { - // We act as if we gave a wp<StreamOutHalLocal> to HAL. This way we should handle - // correctly the case when the callback is invoked while StreamOutHalLocal's destructor is - // already running, because the destructor is invoked after the refcount has been atomically - // decremented. - wp<StreamOutHalLocal> weakSelf(static_cast<StreamOutHalLocal*>(cookie)); - sp<StreamOutHalLocal> self = weakSelf.promote(); - if (self == 0) return 0; - sp<StreamOutHalInterfaceCallback> callback = self->mCallback.promote(); - if (callback == 0) return 0; - ALOGV("asyncCallback() event %d", event); - switch (event) { - case STREAM_CBK_EVENT_WRITE_READY: - callback->onWriteReady(); - break; - case STREAM_CBK_EVENT_DRAIN_READY: - callback->onDrainReady(); - break; - case STREAM_CBK_EVENT_ERROR: - callback->onError(); - break; - default: - ALOGW("asyncCallback() unknown event %d", event); - break; - } - return 0; -} - -status_t StreamOutHalLocal::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) { - *supportsPause = mStream->pause != NULL; - *supportsResume = mStream->resume != NULL; - return OK; -} - -status_t StreamOutHalLocal::pause() { - if (mStream->pause == NULL) return INVALID_OPERATION; - return mStream->pause(mStream); -} - -status_t StreamOutHalLocal::resume() { - if (mStream->resume == NULL) return INVALID_OPERATION; - return mStream->resume(mStream); -} - -status_t StreamOutHalLocal::supportsDrain(bool *supportsDrain) { - *supportsDrain = mStream->drain != NULL; - return OK; -} - -status_t StreamOutHalLocal::drain(bool earlyNotify) { - if (mStream->drain == NULL) return INVALID_OPERATION; - return mStream->drain(mStream, earlyNotify ? AUDIO_DRAIN_EARLY_NOTIFY : AUDIO_DRAIN_ALL); -} - -status_t StreamOutHalLocal::flush() { - if (mStream->flush == NULL) return INVALID_OPERATION; - return mStream->flush(mStream); -} - -status_t StreamOutHalLocal::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) { - if (mStream->get_presentation_position == NULL) return INVALID_OPERATION; - return mStream->get_presentation_position(mStream, frames, timestamp); -} - -status_t StreamOutHalLocal::start() { - if (mStream->start == NULL) return INVALID_OPERATION; - return mStream->start(mStream); -} - -status_t StreamOutHalLocal::stop() { - if (mStream->stop == NULL) return INVALID_OPERATION; - return mStream->stop(mStream); -} - -status_t StreamOutHalLocal::createMmapBuffer(int32_t minSizeFrames, - struct audio_mmap_buffer_info *info) { - if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION; - return mStream->create_mmap_buffer(mStream, minSizeFrames, info); -} - -status_t StreamOutHalLocal::getMmapPosition(struct audio_mmap_position *position) { - if (mStream->get_mmap_position == NULL) return INVALID_OPERATION; - return mStream->get_mmap_position(mStream, position); -} - -StreamInHalLocal::StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device) - : StreamHalLocal(&stream->common, device), mStream(stream) { -} - -StreamInHalLocal::~StreamInHalLocal() { - mDevice->closeInputStream(mStream); - mStream = 0; -} - -status_t StreamInHalLocal::getFrameSize(size_t *size) { - *size = audio_stream_in_frame_size(mStream); - return OK; -} - -status_t StreamInHalLocal::setGain(float gain) { - return mStream->set_gain(mStream, gain); -} - -status_t StreamInHalLocal::read(void *buffer, size_t bytes, size_t *read) { - ssize_t readResult = mStream->read(mStream, buffer, bytes); - if (readResult > 0) { - *read = readResult; - mStreamPowerLog.log( buffer, *read); - return OK; - } else { - *read = 0; - return readResult; - } -} - -status_t StreamInHalLocal::getInputFramesLost(uint32_t *framesLost) { - *framesLost = mStream->get_input_frames_lost(mStream); - return OK; -} - -status_t StreamInHalLocal::getCapturePosition(int64_t *frames, int64_t *time) { - if (mStream->get_capture_position == NULL) return INVALID_OPERATION; - return mStream->get_capture_position(mStream, frames, time); -} - -status_t StreamInHalLocal::start() { - if (mStream->start == NULL) return INVALID_OPERATION; - return mStream->start(mStream); -} - -status_t StreamInHalLocal::stop() { - if (mStream->stop == NULL) return INVALID_OPERATION; - return mStream->stop(mStream); -} - -status_t StreamInHalLocal::createMmapBuffer(int32_t minSizeFrames, - struct audio_mmap_buffer_info *info) { - if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION; - return mStream->create_mmap_buffer(mStream, minSizeFrames, info); -} - -status_t StreamInHalLocal::getMmapPosition(struct audio_mmap_position *position) { - if (mStream->get_mmap_position == NULL) return INVALID_OPERATION; - return mStream->get_mmap_position(mStream, position); -} - -} // namespace android
diff --git a/media/libaudiohal/StreamHalLocal.h b/media/libaudiohal/StreamHalLocal.h deleted file mode 100644 index c7136df..0000000 --- a/media/libaudiohal/StreamHalLocal.h +++ /dev/null
@@ -1,210 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_STREAM_HAL_LOCAL_H -#define ANDROID_HARDWARE_STREAM_HAL_LOCAL_H - -#include <media/audiohal/StreamHalInterface.h> -#include "StreamPowerLog.h" - -namespace android { - -class DeviceHalLocal; - -class StreamHalLocal : public virtual StreamHalInterface -{ - public: - // Return the sampling rate in Hz - eg. 44100. - virtual status_t getSampleRate(uint32_t *rate); - - // Return size of input/output buffer in bytes for this stream - eg. 4800. - virtual status_t getBufferSize(size_t *size); - - // Return the channel mask. - virtual status_t getChannelMask(audio_channel_mask_t *mask); - - // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT. - virtual status_t getFormat(audio_format_t *format); - - // Convenience method. - virtual status_t getAudioProperties( - uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format); - - // Set audio stream parameters. - virtual status_t setParameters(const String8& kvPairs); - - // Get audio stream parameters. - virtual status_t getParameters(const String8& keys, String8 *values); - - // Add or remove the effect on the stream. - virtual status_t addEffect(sp<EffectHalInterface> effect); - virtual status_t removeEffect(sp<EffectHalInterface> effect); - - // Put the audio hardware input/output into standby mode. - virtual status_t standby(); - - virtual status_t dump(int fd); - - // Start a stream operating in mmap mode. - virtual status_t start() = 0; - - // Stop a stream operating in mmap mode. - virtual status_t stop() = 0; - - // Retrieve information on the data buffer in mmap mode. - virtual status_t createMmapBuffer(int32_t minSizeFrames, - struct audio_mmap_buffer_info *info) = 0; - - // Get current read/write position in the mmap buffer - virtual status_t getMmapPosition(struct audio_mmap_position *position) = 0; - - // Set the priority of the thread that interacts with the HAL - // (must match the priority of the audioflinger's thread that calls 'read' / 'write') - virtual status_t setHalThreadPriority(int priority); - - protected: - // Subclasses can not be constructed directly by clients. - StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device); - - // The destructor automatically closes the stream. - virtual ~StreamHalLocal(); - - sp<DeviceHalLocal> mDevice; - - // mStreamPowerLog is used for audio signal power logging. - StreamPowerLog mStreamPowerLog; - - private: - audio_stream_t *mStream; -}; - -class StreamOutHalLocal : public StreamOutHalInterface, public StreamHalLocal { - public: - // Return the frame size (number of bytes per sample) of a stream. - virtual status_t getFrameSize(size_t *size); - - // Return the audio hardware driver estimated latency in milliseconds. - virtual status_t getLatency(uint32_t *latency); - - // Use this method in situations where audio mixing is done in the hardware. - virtual status_t setVolume(float left, float right); - - // Write audio buffer to driver. - virtual status_t write(const void *buffer, size_t bytes, size_t *written); - - // Return the number of audio frames written by the audio dsp to DAC since - // the output has exited standby. - virtual status_t getRenderPosition(uint32_t *dspFrames); - - // Get the local time at which the next write to the audio driver will be presented. - virtual status_t getNextWriteTimestamp(int64_t *timestamp); - - // Set the callback for notifying completion of non-blocking write and drain. - virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback); - - // Returns whether pause and resume operations are supported. - virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume); - - // Notifies to the audio driver to resume playback following a pause. - virtual status_t pause(); - - // Notifies to the audio driver to resume playback following a pause. - virtual status_t resume(); - - // Returns whether drain operation is supported. - virtual status_t supportsDrain(bool *supportsDrain); - - // Requests notification when data buffered by the driver/hardware has been played. - virtual status_t drain(bool earlyNotify); - - // Notifies to the audio driver to flush the queued data. - virtual status_t flush(); - - // Return a recent count of the number of audio frames presented to an external observer. - virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp); - - // Start a stream operating in mmap mode. - virtual status_t start(); - - // Stop a stream operating in mmap mode. - virtual status_t stop(); - - // Retrieve information on the data buffer in mmap mode. - virtual status_t createMmapBuffer(int32_t minSizeFrames, - struct audio_mmap_buffer_info *info); - - // Get current read/write position in the mmap buffer - virtual status_t getMmapPosition(struct audio_mmap_position *position); - - private: - audio_stream_out_t *mStream; - wp<StreamOutHalInterfaceCallback> mCallback; - - friend class DeviceHalLocal; - - // Can not be constructed directly by clients. - StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device); - - virtual ~StreamOutHalLocal(); - - static int asyncCallback(stream_callback_event_t event, void *param, void *cookie); -}; - -class StreamInHalLocal : public StreamInHalInterface, public StreamHalLocal { - public: - // Return the frame size (number of bytes per sample) of a stream. - virtual status_t getFrameSize(size_t *size); - - // Set the input gain for the audio driver. - virtual status_t setGain(float gain); - - // Read audio buffer in from driver. - virtual status_t read(void *buffer, size_t bytes, size_t *read); - - // Return the amount of input frames lost in the audio driver. - virtual status_t getInputFramesLost(uint32_t *framesLost); - - // Return a recent count of the number of audio frames received and - // the clock time associated with that frame count. - virtual status_t getCapturePosition(int64_t *frames, int64_t *time); - - // Start a stream operating in mmap mode. - virtual status_t start(); - - // Stop a stream operating in mmap mode. - virtual status_t stop(); - - // Retrieve information on the data buffer in mmap mode. - virtual status_t createMmapBuffer(int32_t minSizeFrames, - struct audio_mmap_buffer_info *info); - - // Get current read/write position in the mmap buffer - virtual status_t getMmapPosition(struct audio_mmap_position *position); - - private: - audio_stream_in_t *mStream; - - friend class DeviceHalLocal; - - // Can not be constructed directly by clients. - StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device); - - virtual ~StreamInHalLocal(); -}; - -} // namespace android - -#endif // ANDROID_HARDWARE_STREAM_HAL_LOCAL_H
diff --git a/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h b/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h index caf01be..7de8eb3 100644 --- a/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h +++ b/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h
@@ -17,6 +17,7 @@ #ifndef ANDROID_HARDWARE_DEVICE_HAL_INTERFACE_H #define ANDROID_HARDWARE_DEVICE_HAL_INTERFACE_H +#include <media/MicrophoneInfo.h> #include <system/audio.h> #include <utils/Errors.h> #include <utils/RefBase.h> @@ -105,6 +106,9 @@ // Set audio port configuration. virtual status_t setAudioPortConfig(const struct audio_port_config *config) = 0; + // List microphones + virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones) = 0; + virtual status_t dump(int fd) = 0; protected:
diff --git a/media/libaudiohal/include/media/audiohal/EffectBufferHalInterface.h b/media/libaudiohal/include/media/audiohal/EffectBufferHalInterface.h index e862f6e..d0603cd 100644 --- a/media/libaudiohal/include/media/audiohal/EffectBufferHalInterface.h +++ b/media/libaudiohal/include/media/audiohal/EffectBufferHalInterface.h
@@ -26,6 +26,7 @@ // Abstraction for an audio buffer. It may be a "mirror" for // a buffer that the effect chain doesn't own, or a buffer owned by // the effect chain. +// Buffers are created from EffectsFactoryHalInterface class EffectBufferHalInterface : public RefBase { public: @@ -37,6 +38,8 @@ return externalData() != nullptr ? externalData() : audioBuffer()->raw; } + virtual size_t getSize() const = 0; + virtual void setExternalData(void* external) = 0; virtual void setFrameCount(size_t frameCount) = 0; virtual bool checkFrameCountChange() = 0; // returns whether frame count has been updated @@ -47,9 +50,6 @@ virtual void update(size_t size) = 0; // copies partial data from external buffer virtual void commit(size_t size) = 0; // copies partial data to external buffer - static status_t allocate(size_t size, sp<EffectBufferHalInterface>* buffer); - static status_t mirror(void* external, size_t size, sp<EffectBufferHalInterface>* buffer); - protected: // Subclasses can not be constructed directly by clients. EffectBufferHalInterface() {}
diff --git a/media/libaudiohal/include/media/audiohal/EffectsFactoryHalInterface.h b/media/libaudiohal/include/media/audiohal/EffectsFactoryHalInterface.h index a616e86..316a46c 100644 --- a/media/libaudiohal/include/media/audiohal/EffectsFactoryHalInterface.h +++ b/media/libaudiohal/include/media/audiohal/EffectsFactoryHalInterface.h
@@ -48,6 +48,10 @@ static sp<EffectsFactoryHalInterface> create(); + virtual status_t allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) = 0; + virtual status_t mirrorBuffer(void* external, size_t size, + sp<EffectBufferHalInterface>* buffer) = 0; + // Helper function to compare effect uuid to EFFECT_UUID_NULL. static bool isNullUuid(const effect_uuid_t *pEffectUuid);
diff --git a/media/libaudiohal/include/media/audiohal/StreamHalInterface.h b/media/libaudiohal/include/media/audiohal/StreamHalInterface.h index 7419c34..c969e28 100644 --- a/media/libaudiohal/include/media/audiohal/StreamHalInterface.h +++ b/media/libaudiohal/include/media/audiohal/StreamHalInterface.h
@@ -17,7 +17,10 @@ #ifndef ANDROID_HARDWARE_STREAM_HAL_INTERFACE_H #define ANDROID_HARDWARE_STREAM_HAL_INTERFACE_H +#include <vector> + #include <media/audiohal/EffectHalInterface.h> +#include <media/MicrophoneInfo.h> #include <system/audio.h> #include <utils/Errors.h> #include <utils/RefBase.h> @@ -142,6 +145,15 @@ // Return a recent count of the number of audio frames presented to an external observer. virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp) = 0; + struct SourceMetadata { + std::vector<playback_track_metadata_t> tracks; + }; + /** + * Called when the metadata of the stream's source has been changed. + * @param sourceMetadata Description of the audio that is played by the clients. + */ + virtual status_t updateSourceMetadata(const SourceMetadata& sourceMetadata) = 0; + protected: virtual ~StreamOutHalInterface() {} }; @@ -161,6 +173,18 @@ // the clock time associated with that frame count. virtual status_t getCapturePosition(int64_t *frames, int64_t *time) = 0; + // Get active microphones + virtual status_t getActiveMicrophones(std::vector<media::MicrophoneInfo> *microphones) = 0; + + struct SinkMetadata { + std::vector<record_track_metadata_t> tracks; + }; + /** + * Called when the metadata of the stream's sink has been changed. + * @param sinkMetadata Description of the audio that is suggested by the clients. + */ + virtual status_t updateSinkMetadata(const SinkMetadata& sinkMetadata) = 0; + protected: virtual ~StreamInHalInterface() {} };
diff --git a/media/libaudioprocessing/Android.mk b/media/libaudioprocessing/Android.mk index c850984..da1ecc2 100644 --- a/media/libaudioprocessing/Android.mk +++ b/media/libaudioprocessing/Android.mk
@@ -24,6 +24,7 @@ libcutils \ liblog \ libnbaio \ + libnblog \ libsonic \ libutils \
diff --git a/media/libaudioprocessing/AudioMixer.cpp b/media/libaudioprocessing/AudioMixer.cpp index 238925d..f6f817a 100644 --- a/media/libaudioprocessing/AudioMixer.cpp +++ b/media/libaudioprocessing/AudioMixer.cpp
@@ -62,21 +62,22 @@ #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) #endif -// TODO: Move these macro/inlines to a header file. -template <typename T> -static inline -T max(const T& x, const T& y) { - return x > y ? x : y; -} - // Set kUseNewMixer to true to use the new mixer engine always. Otherwise the // original code will be used for stereo sinks, the new mixer for multichannel. -static const bool kUseNewMixer = true; +static constexpr bool kUseNewMixer = true; // Set kUseFloat to true to allow floating input into the mixer engine. // If kUseNewMixer is false, this is ignored or may be overridden internally // because of downmix/upmix support. -static const bool kUseFloat = true; +static constexpr bool kUseFloat = true; + +#ifdef FLOAT_AUX +using TYPE_AUX = float; +static_assert(kUseNewMixer && kUseFloat, + "kUseNewMixer and kUseFloat must be true for FLOAT_AUX option"); +#else +using TYPE_AUX = int32_t; // q4.27 +#endif // Set to default copy buffer size in frames for input processing. static const size_t kCopyBufferFrameCount = 256; @@ -85,88 +86,28 @@ // ---------------------------------------------------------------------------- -template <typename T> -T min(const T& a, const T& b) -{ - return a < b ? a : b; -} - -// ---------------------------------------------------------------------------- - -// Ensure mConfiguredNames bitmask is initialized properly on all architectures. -// The value of 1 << x is undefined in C when x >= 32. - -AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate, uint32_t maxNumTracks) - : mTrackNames(0), mConfiguredNames((maxNumTracks >= 32 ? 0 : 1 << maxNumTracks) - 1), - mSampleRate(sampleRate) -{ - ALOG_ASSERT(maxNumTracks <= MAX_NUM_TRACKS, "maxNumTracks %u > MAX_NUM_TRACKS %u", - maxNumTracks, MAX_NUM_TRACKS); - - // AudioMixer is not yet capable of more than 32 active track inputs - ALOG_ASSERT(32 >= MAX_NUM_TRACKS, "bad MAX_NUM_TRACKS %d", MAX_NUM_TRACKS); - - pthread_once(&sOnceControl, &sInitRoutine); - - mState.enabledTracks= 0; - mState.needsChanged = 0; - mState.frameCount = frameCount; - mState.hook = process__nop; - mState.outputTemp = NULL; - mState.resampleTemp = NULL; - mState.mNBLogWriter = &mDummyLogWriter; - // mState.reserved - - // FIXME Most of the following initialization is probably redundant since - // tracks[i] should only be referenced if (mTrackNames & (1 << i)) != 0 - // and mTrackNames is initially 0. However, leave it here until that's verified. - track_t* t = mState.tracks; - for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) { - t->resampler = NULL; - t->downmixerBufferProvider = NULL; - t->mReformatBufferProvider = NULL; - t->mTimestretchBufferProvider = NULL; - t++; - } - -} - -AudioMixer::~AudioMixer() -{ - track_t* t = mState.tracks; - for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) { - delete t->resampler; - delete t->downmixerBufferProvider; - delete t->mReformatBufferProvider; - delete t->mTimestretchBufferProvider; - t++; - } - delete [] mState.outputTemp; - delete [] mState.resampleTemp; -} - -void AudioMixer::setNBLogWriter(NBLog::Writer *logWriter) -{ - mState.mNBLogWriter = logWriter; -} - static inline audio_format_t selectMixerInFormat(audio_format_t inputFormat __unused) { return kUseFloat && kUseNewMixer ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT; } -int AudioMixer::getTrackName(audio_channel_mask_t channelMask, - audio_format_t format, int sessionId) +status_t AudioMixer::create( + int name, audio_channel_mask_t channelMask, audio_format_t format, int sessionId) { - if (!isValidPcmTrackFormat(format)) { - ALOGE("AudioMixer::getTrackName invalid format (%#x)", format); - return -1; + LOG_ALWAYS_FATAL_IF(exists(name), "name %d already exists", name); + + if (!isValidChannelMask(channelMask)) { + ALOGE("%s invalid channelMask: %#x", __func__, channelMask); + return BAD_VALUE; } - uint32_t names = (~mTrackNames) & mConfiguredNames; - if (names != 0) { - int n = __builtin_ctz(names); - ALOGV("add track (%d)", n); + if (!isValidFormat(format)) { + ALOGE("%s invalid format: %#x", __func__, format); + return BAD_VALUE; + } + + auto t = std::make_shared<Track>(); + { + // TODO: move initialization to the Track constructor. // assume default parameters for the track, except where noted below - track_t* t = &mState.tracks[n]; t->needs = 0; // Integer volume. @@ -207,17 +148,12 @@ // no initialization needed // t->buffer.frameCount t->hook = NULL; - t->in = NULL; - t->resampler = NULL; + t->mIn = NULL; t->sampleRate = mSampleRate; // setParameter(name, TRACK, MAIN_BUFFER, mixBuffer) is required before enable(name) t->mainBuffer = NULL; t->auxBuffer = NULL; t->mInputBufferProvider = NULL; - t->mReformatBufferProvider = NULL; - t->downmixerBufferProvider = NULL; - t->mPostDownmixReformatBufferProvider = NULL; - t->mTimestretchBufferProvider = NULL; t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT; t->mFormat = format; t->mMixerInFormat = selectMixerInFormat(format); @@ -230,96 +166,83 @@ status_t status = t->prepareForDownmix(); if (status != OK) { ALOGE("AudioMixer::getTrackName invalid channelMask (%#x)", channelMask); - return -1; + return BAD_VALUE; } // prepareForDownmix() may change mDownmixRequiresFormat ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat); t->prepareForReformat(); - mTrackNames |= 1 << n; - return TRACK0 + n; - } - ALOGE("AudioMixer::getTrackName out of available tracks"); - return -1; -} -void AudioMixer::invalidateState(uint32_t mask) -{ - if (mask != 0) { - mState.needsChanged |= mask; - mState.hook = process__validate; + mTracks[name] = t; + return OK; } - } +} // Called when channel masks have changed for a track name // TODO: Fix DownmixerBufferProvider not to (possibly) change mixer input format, // which will simplify this logic. bool AudioMixer::setChannelMasks(int name, audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask) { - track_t &track = mState.tracks[name]; + LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name); + const std::shared_ptr<Track> &track = mTracks[name]; - if (trackChannelMask == track.channelMask - && mixerChannelMask == track.mMixerChannelMask) { + if (trackChannelMask == track->channelMask + && mixerChannelMask == track->mMixerChannelMask) { return false; // no need to change } // always recompute for both channel masks even if only one has changed. const uint32_t trackChannelCount = audio_channel_count_from_out_mask(trackChannelMask); const uint32_t mixerChannelCount = audio_channel_count_from_out_mask(mixerChannelMask); - const bool mixerChannelCountChanged = track.mMixerChannelCount != mixerChannelCount; ALOG_ASSERT((trackChannelCount <= MAX_NUM_CHANNELS_TO_DOWNMIX) && trackChannelCount && mixerChannelCount); - track.channelMask = trackChannelMask; - track.channelCount = trackChannelCount; - track.mMixerChannelMask = mixerChannelMask; - track.mMixerChannelCount = mixerChannelCount; + track->channelMask = trackChannelMask; + track->channelCount = trackChannelCount; + track->mMixerChannelMask = mixerChannelMask; + track->mMixerChannelCount = mixerChannelCount; // channel masks have changed, does this track need a downmixer? // update to try using our desired format (if we aren't already using it) - const audio_format_t prevDownmixerFormat = track.mDownmixRequiresFormat; - const status_t status = mState.tracks[name].prepareForDownmix(); + const status_t status = track->prepareForDownmix(); ALOGE_IF(status != OK, "prepareForDownmix error %d, track channel mask %#x, mixer channel mask %#x", - status, track.channelMask, track.mMixerChannelMask); + status, track->channelMask, track->mMixerChannelMask); - if (prevDownmixerFormat != track.mDownmixRequiresFormat) { - track.prepareForReformat(); // because of downmixer, track format may change! - } + // always do reformat since channel mask changed, + // do it after downmix since track format may change! + track->prepareForReformat(); - if (track.resampler && mixerChannelCountChanged) { + if (track->mResampler.get() != nullptr) { // resampler channels may have changed. - const uint32_t resetToSampleRate = track.sampleRate; - delete track.resampler; - track.resampler = NULL; - track.sampleRate = mSampleRate; // without resampler, track rate is device sample rate. + const uint32_t resetToSampleRate = track->sampleRate; + track->mResampler.reset(nullptr); + track->sampleRate = mSampleRate; // without resampler, track rate is device sample rate. // recreate the resampler with updated format, channels, saved sampleRate. - track.setResampler(resetToSampleRate /*trackSampleRate*/, mSampleRate /*devSampleRate*/); + track->setResampler(resetToSampleRate /*trackSampleRate*/, mSampleRate /*devSampleRate*/); } return true; } -void AudioMixer::track_t::unprepareForDownmix() { +void AudioMixer::Track::unprepareForDownmix() { ALOGV("AudioMixer::unprepareForDownmix(%p)", this); - if (mPostDownmixReformatBufferProvider != nullptr) { + if (mPostDownmixReformatBufferProvider.get() != nullptr) { // release any buffers held by the mPostDownmixReformatBufferProvider - // before deallocating the downmixerBufferProvider. + // before deallocating the mDownmixerBufferProvider. mPostDownmixReformatBufferProvider->reset(); } mDownmixRequiresFormat = AUDIO_FORMAT_INVALID; - if (downmixerBufferProvider != NULL) { + if (mDownmixerBufferProvider.get() != nullptr) { // this track had previously been configured with a downmixer, delete it - ALOGV(" deleting old downmixer"); - delete downmixerBufferProvider; - downmixerBufferProvider = NULL; + mDownmixerBufferProvider.reset(nullptr); reconfigureBufferProviders(); } else { ALOGV(" nothing to do, no downmixer to delete"); } } -status_t AudioMixer::track_t::prepareForDownmix() +status_t AudioMixer::Track::prepareForDownmix() { ALOGV("AudioMixer::prepareForDownmix(%p) with mask 0x%x", this, channelMask); @@ -337,40 +260,35 @@ if (audio_channel_mask_get_representation(channelMask) == AUDIO_CHANNEL_REPRESENTATION_POSITION && DownmixerBufferProvider::isMultichannelCapable()) { - DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(channelMask, + mDownmixerBufferProvider.reset(new DownmixerBufferProvider(channelMask, mMixerChannelMask, AUDIO_FORMAT_PCM_16_BIT /* TODO: use mMixerInFormat, now only PCM 16 */, - sampleRate, sessionId, kCopyBufferFrameCount); - - if (pDbp->isValid()) { // if constructor completed properly + sampleRate, sessionId, kCopyBufferFrameCount)); + if (static_cast<DownmixerBufferProvider *>(mDownmixerBufferProvider.get())->isValid()) { mDownmixRequiresFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix - downmixerBufferProvider = pDbp; reconfigureBufferProviders(); return NO_ERROR; } - delete pDbp; + // mDownmixerBufferProvider reset below. } // Effect downmixer does not accept the channel conversion. Let's use our remixer. - RemixBufferProvider* pRbp = new RemixBufferProvider(channelMask, - mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount); + mDownmixerBufferProvider.reset(new RemixBufferProvider(channelMask, + mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount)); // Remix always finds a conversion whereas Downmixer effect above may fail. - downmixerBufferProvider = pRbp; reconfigureBufferProviders(); return NO_ERROR; } -void AudioMixer::track_t::unprepareForReformat() { +void AudioMixer::Track::unprepareForReformat() { ALOGV("AudioMixer::unprepareForReformat(%p)", this); bool requiresReconfigure = false; - if (mReformatBufferProvider != NULL) { - delete mReformatBufferProvider; - mReformatBufferProvider = NULL; + if (mReformatBufferProvider.get() != nullptr) { + mReformatBufferProvider.reset(nullptr); requiresReconfigure = true; } - if (mPostDownmixReformatBufferProvider != NULL) { - delete mPostDownmixReformatBufferProvider; - mPostDownmixReformatBufferProvider = NULL; + if (mPostDownmixReformatBufferProvider.get() != nullptr) { + mPostDownmixReformatBufferProvider.reset(nullptr); requiresReconfigure = true; } if (requiresReconfigure) { @@ -378,7 +296,7 @@ } } -status_t AudioMixer::track_t::prepareForReformat() +status_t AudioMixer::Track::prepareForReformat() { ALOGV("AudioMixer::prepareForReformat(%p) with format %#x", this, mFormat); // discard previous reformatters @@ -388,19 +306,27 @@ ? mDownmixRequiresFormat : mMixerInFormat; bool requiresReconfigure = false; if (mFormat != targetFormat) { - mReformatBufferProvider = new ReformatBufferProvider( + mReformatBufferProvider.reset(new ReformatBufferProvider( audio_channel_count_from_out_mask(channelMask), mFormat, targetFormat, - kCopyBufferFrameCount); + kCopyBufferFrameCount)); + requiresReconfigure = true; + } else if (mFormat == AUDIO_FORMAT_PCM_FLOAT) { + // Input and output are floats, make sure application did not provide > 3db samples + // that would break volume application (b/68099072) + // TODO: add a trusted source flag to avoid the overhead + mReformatBufferProvider.reset(new ClampFloatBufferProvider( + audio_channel_count_from_out_mask(channelMask), + kCopyBufferFrameCount)); requiresReconfigure = true; } if (targetFormat != mMixerInFormat) { - mPostDownmixReformatBufferProvider = new ReformatBufferProvider( + mPostDownmixReformatBufferProvider.reset(new ReformatBufferProvider( audio_channel_count_from_out_mask(mMixerChannelMask), targetFormat, mMixerInFormat, - kCopyBufferFrameCount); + kCopyBufferFrameCount)); requiresReconfigure = true; } if (requiresReconfigure) { @@ -409,74 +335,59 @@ return NO_ERROR; } -void AudioMixer::track_t::reconfigureBufferProviders() +void AudioMixer::Track::reconfigureBufferProviders() { bufferProvider = mInputBufferProvider; - if (mReformatBufferProvider) { + if (mReformatBufferProvider.get() != nullptr) { mReformatBufferProvider->setBufferProvider(bufferProvider); - bufferProvider = mReformatBufferProvider; + bufferProvider = mReformatBufferProvider.get(); } - if (downmixerBufferProvider) { - downmixerBufferProvider->setBufferProvider(bufferProvider); - bufferProvider = downmixerBufferProvider; + if (mDownmixerBufferProvider.get() != nullptr) { + mDownmixerBufferProvider->setBufferProvider(bufferProvider); + bufferProvider = mDownmixerBufferProvider.get(); } - if (mPostDownmixReformatBufferProvider) { + if (mPostDownmixReformatBufferProvider.get() != nullptr) { mPostDownmixReformatBufferProvider->setBufferProvider(bufferProvider); - bufferProvider = mPostDownmixReformatBufferProvider; + bufferProvider = mPostDownmixReformatBufferProvider.get(); } - if (mTimestretchBufferProvider) { + if (mTimestretchBufferProvider.get() != nullptr) { mTimestretchBufferProvider->setBufferProvider(bufferProvider); - bufferProvider = mTimestretchBufferProvider; + bufferProvider = mTimestretchBufferProvider.get(); } } -void AudioMixer::deleteTrackName(int name) +void AudioMixer::destroy(int name) { - ALOGV("AudioMixer::deleteTrackName(%d)", name); - name -= TRACK0; - LOG_ALWAYS_FATAL_IF(name < 0 || name >= (int)MAX_NUM_TRACKS, "bad track name %d", name); + LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name); ALOGV("deleteTrackName(%d)", name); - track_t& track(mState.tracks[ name ]); - if (track.enabled) { - track.enabled = false; - invalidateState(1<<name); + + if (mTracks[name]->enabled) { + invalidate(); } - // delete the resampler - delete track.resampler; - track.resampler = NULL; - // delete the downmixer - mState.tracks[name].unprepareForDownmix(); - // delete the reformatter - mState.tracks[name].unprepareForReformat(); - // delete the timestretch provider - delete track.mTimestretchBufferProvider; - track.mTimestretchBufferProvider = NULL; - mTrackNames &= ~(1<<name); + mTracks.erase(name); // deallocate track } void AudioMixer::enable(int name) { - name -= TRACK0; - ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); - track_t& track = mState.tracks[name]; + LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name); + const std::shared_ptr<Track> &track = mTracks[name]; - if (!track.enabled) { - track.enabled = true; + if (!track->enabled) { + track->enabled = true; ALOGV("enable(%d)", name); - invalidateState(1 << name); + invalidate(); } } void AudioMixer::disable(int name) { - name -= TRACK0; - ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); - track_t& track = mState.tracks[name]; + LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name); + const std::shared_ptr<Track> &track = mTracks[name]; - if (track.enabled) { - track.enabled = false; + if (track->enabled) { + track->enabled = false; ALOGV("disable(%d)", name); - invalidateState(1 << name); + invalidate(); } } @@ -554,7 +465,8 @@ ALOGD_IF(*pPrevVolume != *pSetVolume, "previous float ramp hasn't finished," " prev:%f set_to:%f", *pPrevVolume, *pSetVolume); const float inc = (newVolume - *pPrevVolume) / ramp; // could be inf, nan, subnormal - const float maxv = max(newVolume, *pPrevVolume); // could be inf, cannot be nan, subnormal + // could be inf, cannot be nan, subnormal + const float maxv = std::max(newVolume, *pPrevVolume); if (isnormal(inc) // inc must be a normal number (no subnormals, infinite, nan) && maxv + inc != maxv) { // inc must make forward progress @@ -607,9 +519,8 @@ void AudioMixer::setParameter(int name, int target, int param, void *value) { - name -= TRACK0; - ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); - track_t& track = mState.tracks[name]; + LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name); + const std::shared_ptr<Track> &track = mTracks[name]; int valueInt = static_cast<int>(reinterpret_cast<uintptr_t>(value)); int32_t *valueBuf = reinterpret_cast<int32_t*>(value); @@ -621,33 +532,33 @@ case CHANNEL_MASK: { const audio_channel_mask_t trackChannelMask = static_cast<audio_channel_mask_t>(valueInt); - if (setChannelMasks(name, trackChannelMask, track.mMixerChannelMask)) { + if (setChannelMasks(name, trackChannelMask, track->mMixerChannelMask)) { ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", trackChannelMask); - invalidateState(1 << name); + invalidate(); } } break; case MAIN_BUFFER: - if (track.mainBuffer != valueBuf) { - track.mainBuffer = valueBuf; + if (track->mainBuffer != valueBuf) { + track->mainBuffer = valueBuf; ALOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf); - invalidateState(1 << name); + invalidate(); } break; case AUX_BUFFER: - if (track.auxBuffer != valueBuf) { - track.auxBuffer = valueBuf; + if (track->auxBuffer != valueBuf) { + track->auxBuffer = valueBuf; ALOGV("setParameter(TRACK, AUX_BUFFER, %p)", valueBuf); - invalidateState(1 << name); + invalidate(); } break; case FORMAT: { audio_format_t format = static_cast<audio_format_t>(valueInt); - if (track.mFormat != format) { + if (track->mFormat != format) { ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format); - track.mFormat = format; + track->mFormat = format; ALOGV("setParameter(TRACK, FORMAT, %#x)", format); - track.prepareForReformat(); - invalidateState(1 << name); + track->prepareForReformat(); + invalidate(); } } break; // FIXME do we want to support setting the downmix type from AudioFlinger? @@ -656,17 +567,17 @@ break */ case MIXER_FORMAT: { audio_format_t format = static_cast<audio_format_t>(valueInt); - if (track.mMixerFormat != format) { - track.mMixerFormat = format; + if (track->mMixerFormat != format) { + track->mMixerFormat = format; ALOGV("setParameter(TRACK, MIXER_FORMAT, %#x)", format); } } break; case MIXER_CHANNEL_MASK: { const audio_channel_mask_t mixerChannelMask = static_cast<audio_channel_mask_t>(valueInt); - if (setChannelMasks(name, track.channelMask, mixerChannelMask)) { + if (setChannelMasks(name, track->channelMask, mixerChannelMask)) { ALOGV("setParameter(TRACK, MIXER_CHANNEL_MASK, %#x)", mixerChannelMask); - invalidateState(1 << name); + invalidate(); } } break; default: @@ -678,21 +589,20 @@ switch (param) { case SAMPLE_RATE: ALOG_ASSERT(valueInt > 0, "bad sample rate %d", valueInt); - if (track.setResampler(uint32_t(valueInt), mSampleRate)) { + if (track->setResampler(uint32_t(valueInt), mSampleRate)) { ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)", uint32_t(valueInt)); - invalidateState(1 << name); + invalidate(); } break; case RESET: - track.resetResampler(); - invalidateState(1 << name); + track->resetResampler(); + invalidate(); break; case REMOVE: - delete track.resampler; - track.resampler = NULL; - track.sampleRate = mSampleRate; - invalidateState(1 << name); + track->mResampler.reset(nullptr); + track->sampleRate = mSampleRate; + invalidate(); break; default: LOG_ALWAYS_FATAL("setParameter resample: bad param %d", param); @@ -704,26 +614,28 @@ switch (param) { case AUXLEVEL: if (setVolumeRampVariables(*reinterpret_cast<float*>(value), - target == RAMP_VOLUME ? mState.frameCount : 0, - &track.auxLevel, &track.prevAuxLevel, &track.auxInc, - &track.mAuxLevel, &track.mPrevAuxLevel, &track.mAuxInc)) { + target == RAMP_VOLUME ? mFrameCount : 0, + &track->auxLevel, &track->prevAuxLevel, &track->auxInc, + &track->mAuxLevel, &track->mPrevAuxLevel, &track->mAuxInc)) { ALOGV("setParameter(%s, AUXLEVEL: %04x)", - target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track.auxLevel); - invalidateState(1 << name); + target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track->auxLevel); + invalidate(); } break; default: if ((unsigned)param >= VOLUME0 && (unsigned)param < VOLUME0 + MAX_NUM_VOLUMES) { if (setVolumeRampVariables(*reinterpret_cast<float*>(value), - target == RAMP_VOLUME ? mState.frameCount : 0, - &track.volume[param - VOLUME0], &track.prevVolume[param - VOLUME0], - &track.volumeInc[param - VOLUME0], - &track.mVolume[param - VOLUME0], &track.mPrevVolume[param - VOLUME0], - &track.mVolumeInc[param - VOLUME0])) { + target == RAMP_VOLUME ? mFrameCount : 0, + &track->volume[param - VOLUME0], + &track->prevVolume[param - VOLUME0], + &track->volumeInc[param - VOLUME0], + &track->mVolume[param - VOLUME0], + &track->mPrevVolume[param - VOLUME0], + &track->mVolumeInc[param - VOLUME0])) { ALOGV("setParameter(%s, VOLUME%d: %04x)", target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0, - track.volume[param - VOLUME0]); - invalidateState(1 << name); + track->volume[param - VOLUME0]); + invalidate(); } } else { LOG_ALWAYS_FATAL("setParameter volume: bad param %d", param); @@ -736,16 +648,16 @@ const AudioPlaybackRate *playbackRate = reinterpret_cast<AudioPlaybackRate*>(value); ALOGW_IF(!isAudioPlaybackRateValid(*playbackRate), - "bad parameters speed %f, pitch %f",playbackRate->mSpeed, - playbackRate->mPitch); - if (track.setPlaybackRate(*playbackRate)) { + "bad parameters speed %f, pitch %f", + playbackRate->mSpeed, playbackRate->mPitch); + if (track->setPlaybackRate(*playbackRate)) { ALOGV("setParameter(TIMESTRETCH, PLAYBACK_RATE, STRETCH_MODE, FALLBACK_MODE " "%f %f %d %d", playbackRate->mSpeed, playbackRate->mPitch, playbackRate->mStretchMode, playbackRate->mFallbackMode); - // invalidateState(1 << name); + // invalidate(); (should not require reconfigure) } } break; default: @@ -758,12 +670,12 @@ } } -bool AudioMixer::track_t::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate) +bool AudioMixer::Track::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate) { - if (trackSampleRate != devSampleRate || resampler != NULL) { + if (trackSampleRate != devSampleRate || mResampler.get() != nullptr) { if (sampleRate != trackSampleRate) { sampleRate = trackSampleRate; - if (resampler == NULL) { + if (mResampler.get() == nullptr) { ALOGV("Creating resampler from track %d Hz to device %d Hz", trackSampleRate, devSampleRate); AudioResampler::src_quality quality; @@ -779,15 +691,15 @@ // TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer // but if none exists, it is the channel count (1 for mono). - const int resamplerChannelCount = downmixerBufferProvider != NULL + const int resamplerChannelCount = mDownmixerBufferProvider.get() != nullptr ? mMixerChannelCount : channelCount; ALOGVV("Creating resampler:" " format(%#x) channels(%d) devSampleRate(%u) quality(%d)\n", mMixerInFormat, resamplerChannelCount, devSampleRate, quality); - resampler = AudioResampler::create( + mResampler.reset(AudioResampler::create( mMixerInFormat, resamplerChannelCount, - devSampleRate, quality); + devSampleRate, quality)); } return true; } @@ -795,25 +707,25 @@ return false; } -bool AudioMixer::track_t::setPlaybackRate(const AudioPlaybackRate &playbackRate) +bool AudioMixer::Track::setPlaybackRate(const AudioPlaybackRate &playbackRate) { - if ((mTimestretchBufferProvider == NULL && + if ((mTimestretchBufferProvider.get() == nullptr && fabs(playbackRate.mSpeed - mPlaybackRate.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA && fabs(playbackRate.mPitch - mPlaybackRate.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA) || isAudioPlaybackRateEqual(playbackRate, mPlaybackRate)) { return false; } mPlaybackRate = playbackRate; - if (mTimestretchBufferProvider == NULL) { + if (mTimestretchBufferProvider.get() == nullptr) { // TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer // but if none exists, it is the channel count (1 for mono). - const int timestretchChannelCount = downmixerBufferProvider != NULL + const int timestretchChannelCount = mDownmixerBufferProvider.get() != nullptr ? mMixerChannelCount : channelCount; - mTimestretchBufferProvider = new TimestretchBufferProvider(timestretchChannelCount, - mMixerInFormat, sampleRate, playbackRate); + mTimestretchBufferProvider.reset(new TimestretchBufferProvider(timestretchChannelCount, + mMixerInFormat, sampleRate, playbackRate)); reconfigureBufferProviders(); } else { - reinterpret_cast<TimestretchBufferProvider*>(mTimestretchBufferProvider) + static_cast<TimestretchBufferProvider*>(mTimestretchBufferProvider.get()) ->setPlaybackRate(playbackRate); } return true; @@ -832,7 +744,7 @@ * * There is a bit of duplicated code here, but it keeps backward compatibility. */ -inline void AudioMixer::track_t::adjustVolumeRamp(bool aux, bool useFloat) +inline void AudioMixer::Track::adjustVolumeRamp(bool aux, bool useFloat) { if (useFloat) { for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) { @@ -861,113 +773,98 @@ } } } - /* TODO: aux is always integer regardless of output buffer type */ + if (aux) { - if (((auxInc>0) && (((prevAuxLevel+auxInc)>>16) >= auxLevel)) || - ((auxInc<0) && (((prevAuxLevel+auxInc)>>16) <= auxLevel))) { +#ifdef FLOAT_AUX + if (useFloat) { + if ((mAuxInc > 0.f && mPrevAuxLevel + mAuxInc >= mAuxLevel) || + (mAuxInc < 0.f && mPrevAuxLevel + mAuxInc <= mAuxLevel)) { + auxInc = 0; + prevAuxLevel = auxLevel << 16; + mAuxInc = 0.f; + mPrevAuxLevel = mAuxLevel; + } + } else +#endif + if ((auxInc > 0 && ((prevAuxLevel + auxInc) >> 16) >= auxLevel) || + (auxInc < 0 && ((prevAuxLevel + auxInc) >> 16) <= auxLevel)) { auxInc = 0; prevAuxLevel = auxLevel << 16; - mAuxInc = 0.; + mAuxInc = 0.f; mPrevAuxLevel = mAuxLevel; - } else { - //ALOGV("aux ramp: %d %d %d", auxLevel << 16, prevAuxLevel, auxInc); } } } size_t AudioMixer::getUnreleasedFrames(int name) const { - name -= TRACK0; - if (uint32_t(name) < MAX_NUM_TRACKS) { - return mState.tracks[name].getUnreleasedFrames(); + const auto it = mTracks.find(name); + if (it != mTracks.end()) { + return it->second->getUnreleasedFrames(); } return 0; } void AudioMixer::setBufferProvider(int name, AudioBufferProvider* bufferProvider) { - name -= TRACK0; - ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name); + const std::shared_ptr<Track> &track = mTracks[name]; - if (mState.tracks[name].mInputBufferProvider == bufferProvider) { + if (track->mInputBufferProvider == bufferProvider) { return; // don't reset any buffer providers if identical. } - if (mState.tracks[name].mReformatBufferProvider != NULL) { - mState.tracks[name].mReformatBufferProvider->reset(); - } else if (mState.tracks[name].downmixerBufferProvider != NULL) { - mState.tracks[name].downmixerBufferProvider->reset(); - } else if (mState.tracks[name].mPostDownmixReformatBufferProvider != NULL) { - mState.tracks[name].mPostDownmixReformatBufferProvider->reset(); - } else if (mState.tracks[name].mTimestretchBufferProvider != NULL) { - mState.tracks[name].mTimestretchBufferProvider->reset(); + if (track->mReformatBufferProvider.get() != nullptr) { + track->mReformatBufferProvider->reset(); + } else if (track->mDownmixerBufferProvider != nullptr) { + track->mDownmixerBufferProvider->reset(); + } else if (track->mPostDownmixReformatBufferProvider.get() != nullptr) { + track->mPostDownmixReformatBufferProvider->reset(); + } else if (track->mTimestretchBufferProvider.get() != nullptr) { + track->mTimestretchBufferProvider->reset(); } - mState.tracks[name].mInputBufferProvider = bufferProvider; - mState.tracks[name].reconfigureBufferProviders(); + track->mInputBufferProvider = bufferProvider; + track->reconfigureBufferProviders(); } - -void AudioMixer::process() +void AudioMixer::process__validate() { - mState.hook(&mState); -} - - -void AudioMixer::process__validate(state_t* state) -{ - ALOGW_IF(!state->needsChanged, - "in process__validate() but nothing's invalid"); - - uint32_t changed = state->needsChanged; - state->needsChanged = 0; // clear the validation flag - - // recompute which tracks are enabled / disabled - uint32_t enabled = 0; - uint32_t disabled = 0; - while (changed) { - const int i = 31 - __builtin_clz(changed); - const uint32_t mask = 1<<i; - changed &= ~mask; - track_t& t = state->tracks[i]; - (t.enabled ? enabled : disabled) |= mask; - } - state->enabledTracks &= ~disabled; - state->enabledTracks |= enabled; - - // compute everything we need... - int countActiveTracks = 0; // TODO: fix all16BitsStereNoResample logic to // either properly handle muted tracks (it should ignore them) // or remove altogether as an obsolete optimization. bool all16BitsStereoNoResample = true; bool resampling = false; bool volumeRamp = false; - uint32_t en = state->enabledTracks; - while (en) { - const int i = 31 - __builtin_clz(en); - en &= ~(1<<i); - countActiveTracks++; - track_t& t = state->tracks[i]; + mEnabled.clear(); + mGroups.clear(); + for (const auto &pair : mTracks) { + const int name = pair.first; + const std::shared_ptr<Track> &t = pair.second; + if (!t->enabled) continue; + + mEnabled.emplace_back(name); // we add to mEnabled in order of name. + mGroups[t->mainBuffer].emplace_back(name); // mGroups also in order of name. + uint32_t n = 0; // FIXME can overflow (mask is only 3 bits) - n |= NEEDS_CHANNEL_1 + t.channelCount - 1; - if (t.doesResample()) { + n |= NEEDS_CHANNEL_1 + t->channelCount - 1; + if (t->doesResample()) { n |= NEEDS_RESAMPLE; } - if (t.auxLevel != 0 && t.auxBuffer != NULL) { + if (t->auxLevel != 0 && t->auxBuffer != NULL) { n |= NEEDS_AUX; } - if (t.volumeInc[0]|t.volumeInc[1]) { + if (t->volumeInc[0]|t->volumeInc[1]) { volumeRamp = true; - } else if (!t.doesResample() && t.volumeRL == 0) { + } else if (!t->doesResample() && t->volumeRL == 0) { n |= NEEDS_MUTE; } - t.needs = n; + t->needs = n; if (n & NEEDS_MUTE) { - t.hook = track__nop; + t->hook = &Track::track__nop; } else { if (n & NEEDS_AUX) { all16BitsStereoNoResample = false; @@ -975,23 +872,23 @@ if (n & NEEDS_RESAMPLE) { all16BitsStereoNoResample = false; resampling = true; - t.hook = getTrackHook(TRACKTYPE_RESAMPLE, t.mMixerChannelCount, - t.mMixerInFormat, t.mMixerFormat); + t->hook = Track::getTrackHook(TRACKTYPE_RESAMPLE, t->mMixerChannelCount, + t->mMixerInFormat, t->mMixerFormat); ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2, "Track %d needs downmix + resample", i); } else { if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){ - t.hook = getTrackHook( - (t.mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK - && t.channelMask == AUDIO_CHANNEL_OUT_MONO) + t->hook = Track::getTrackHook( + (t->mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK + && t->channelMask == AUDIO_CHANNEL_OUT_MONO) ? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE, - t.mMixerChannelCount, - t.mMixerInFormat, t.mMixerFormat); + t->mMixerChannelCount, + t->mMixerInFormat, t->mMixerFormat); all16BitsStereoNoResample = false; } if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2){ - t.hook = getTrackHook(TRACKTYPE_NORESAMPLE, t.mMixerChannelCount, - t.mMixerInFormat, t.mMixerFormat); + t->hook = Track::getTrackHook(TRACKTYPE_NORESAMPLE, t->mMixerChannelCount, + t->mMixerInFormat, t->mMixerFormat); ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2, "Track %d needs downmix", i); } @@ -1000,137 +897,125 @@ } // select the processing hooks - state->hook = process__nop; - if (countActiveTracks > 0) { + mHook = &AudioMixer::process__nop; + if (mEnabled.size() > 0) { if (resampling) { - if (!state->outputTemp) { - state->outputTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount]; + if (mOutputTemp.get() == nullptr) { + mOutputTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]); } - if (!state->resampleTemp) { - state->resampleTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount]; + if (mResampleTemp.get() == nullptr) { + mResampleTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]); } - state->hook = process__genericResampling; + mHook = &AudioMixer::process__genericResampling; } else { - if (state->outputTemp) { - delete [] state->outputTemp; - state->outputTemp = NULL; - } - if (state->resampleTemp) { - delete [] state->resampleTemp; - state->resampleTemp = NULL; - } - state->hook = process__genericNoResampling; + // we keep temp arrays around. + mHook = &AudioMixer::process__genericNoResampling; if (all16BitsStereoNoResample && !volumeRamp) { - if (countActiveTracks == 1) { - const int i = 31 - __builtin_clz(state->enabledTracks); - track_t& t = state->tracks[i]; - if ((t.needs & NEEDS_MUTE) == 0) { + if (mEnabled.size() == 1) { + const std::shared_ptr<Track> &t = mTracks[mEnabled[0]]; + if ((t->needs & NEEDS_MUTE) == 0) { // The check prevents a muted track from acquiring a process hook. // // This is dangerous if the track is MONO as that requires // special case handling due to implicit channel duplication. // Stereo or Multichannel should actually be fine here. - state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, - t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat); + mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, + t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat); } } } } } - ALOGV("mixer configuration change: %d activeTracks (%08x) " + ALOGV("mixer configuration change: %zu " "all16BitsStereoNoResample=%d, resampling=%d, volumeRamp=%d", - countActiveTracks, state->enabledTracks, - all16BitsStereoNoResample, resampling, volumeRamp); + mEnabled.size(), all16BitsStereoNoResample, resampling, volumeRamp); - state->hook(state); + process(); // Now that the volume ramp has been done, set optimal state and // track hooks for subsequent mixer process - if (countActiveTracks > 0) { + if (mEnabled.size() > 0) { bool allMuted = true; - uint32_t en = state->enabledTracks; - while (en) { - const int i = 31 - __builtin_clz(en); - en &= ~(1<<i); - track_t& t = state->tracks[i]; - if (!t.doesResample() && t.volumeRL == 0) { - t.needs |= NEEDS_MUTE; - t.hook = track__nop; + + for (const int name : mEnabled) { + const std::shared_ptr<Track> &t = mTracks[name]; + if (!t->doesResample() && t->volumeRL == 0) { + t->needs |= NEEDS_MUTE; + t->hook = &Track::track__nop; } else { allMuted = false; } } if (allMuted) { - state->hook = process__nop; + mHook = &AudioMixer::process__nop; } else if (all16BitsStereoNoResample) { - if (countActiveTracks == 1) { - const int i = 31 - __builtin_clz(state->enabledTracks); - track_t& t = state->tracks[i]; + if (mEnabled.size() == 1) { + //const int i = 31 - __builtin_clz(enabledTracks); + const std::shared_ptr<Track> &t = mTracks[mEnabled[0]]; // Muted single tracks handled by allMuted above. - state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, - t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat); + mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, + t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat); } } } } - -void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount, - int32_t* temp, int32_t* aux) +void AudioMixer::Track::track__genericResample( + int32_t* out, size_t outFrameCount, int32_t* temp, int32_t* aux) { ALOGVV("track__genericResample\n"); - t->resampler->setSampleRate(t->sampleRate); + mResampler->setSampleRate(sampleRate); // ramp gain - resample to temp buffer and scale/mix in 2nd step if (aux != NULL) { // always resample with unity gain when sending to auxiliary buffer to be able // to apply send level after resampling - t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); - memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(int32_t)); - t->resampler->resample(temp, outFrameCount, t->bufferProvider); - if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) { - volumeRampStereo(t, out, outFrameCount, temp, aux); + mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(int32_t)); + mResampler->resample(temp, outFrameCount, bufferProvider); + if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) { + volumeRampStereo(out, outFrameCount, temp, aux); } else { - volumeStereo(t, out, outFrameCount, temp, aux); + volumeStereo(out, outFrameCount, temp, aux); } } else { - if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) { - t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) { + mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t)); - t->resampler->resample(temp, outFrameCount, t->bufferProvider); - volumeRampStereo(t, out, outFrameCount, temp, aux); + mResampler->resample(temp, outFrameCount, bufferProvider); + volumeRampStereo(out, outFrameCount, temp, aux); } // constant gain else { - t->resampler->setVolume(t->mVolume[0], t->mVolume[1]); - t->resampler->resample(out, outFrameCount, t->bufferProvider); + mResampler->setVolume(mVolume[0], mVolume[1]); + mResampler->resample(out, outFrameCount, bufferProvider); } } } -void AudioMixer::track__nop(track_t* t __unused, int32_t* out __unused, +void AudioMixer::Track::track__nop(int32_t* out __unused, size_t outFrameCount __unused, int32_t* temp __unused, int32_t* aux __unused) { } -void AudioMixer::volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, - int32_t* aux) +void AudioMixer::Track::volumeRampStereo( + int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux) { - int32_t vl = t->prevVolume[0]; - int32_t vr = t->prevVolume[1]; - const int32_t vlInc = t->volumeInc[0]; - const int32_t vrInc = t->volumeInc[1]; + int32_t vl = prevVolume[0]; + int32_t vr = prevVolume[1]; + const int32_t vlInc = volumeInc[0]; + const int32_t vrInc = volumeInc[1]; //ALOGD("[0] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", - // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // t, vlInc/65536.0f, vl/65536.0f, volume[0], // (vl + vlInc*frameCount)/65536.0f, frameCount); // ramp volume if (CC_UNLIKELY(aux != NULL)) { - int32_t va = t->prevAuxLevel; - const int32_t vaInc = t->auxInc; + int32_t va = prevAuxLevel; + const int32_t vaInc = auxInc; int32_t l; int32_t r; @@ -1144,7 +1029,7 @@ vr += vrInc; va += vaInc; } while (--frameCount); - t->prevAuxLevel = va; + prevAuxLevel = va; } else { do { *out++ += (vl >> 16) * (*temp++ >> 12); @@ -1153,19 +1038,19 @@ vr += vrInc; } while (--frameCount); } - t->prevVolume[0] = vl; - t->prevVolume[1] = vr; - t->adjustVolumeRamp(aux != NULL); + prevVolume[0] = vl; + prevVolume[1] = vr; + adjustVolumeRamp(aux != NULL); } -void AudioMixer::volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, - int32_t* aux) +void AudioMixer::Track::volumeStereo( + int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux) { - const int16_t vl = t->volume[0]; - const int16_t vr = t->volume[1]; + const int16_t vl = volume[0]; + const int16_t vr = volume[1]; if (CC_UNLIKELY(aux != NULL)) { - const int16_t va = t->auxLevel; + const int16_t va = auxLevel; do { int16_t l = (int16_t)(*temp++ >> 12); int16_t r = (int16_t)(*temp++ >> 12); @@ -1187,25 +1072,25 @@ } } -void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount, - int32_t* temp __unused, int32_t* aux) +void AudioMixer::Track::track__16BitsStereo( + int32_t* out, size_t frameCount, int32_t* temp __unused, int32_t* aux) { ALOGVV("track__16BitsStereo\n"); - const int16_t *in = static_cast<const int16_t *>(t->in); + const int16_t *in = static_cast<const int16_t *>(mIn); if (CC_UNLIKELY(aux != NULL)) { int32_t l; int32_t r; // ramp gain - if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) { - int32_t vl = t->prevVolume[0]; - int32_t vr = t->prevVolume[1]; - int32_t va = t->prevAuxLevel; - const int32_t vlInc = t->volumeInc[0]; - const int32_t vrInc = t->volumeInc[1]; - const int32_t vaInc = t->auxInc; + if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) { + int32_t vl = prevVolume[0]; + int32_t vr = prevVolume[1]; + int32_t va = prevAuxLevel; + const int32_t vlInc = volumeInc[0]; + const int32_t vrInc = volumeInc[1]; + const int32_t vaInc = auxInc; // ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", - // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // t, vlInc/65536.0f, vl/65536.0f, volume[0], // (vl + vlInc*frameCount)/65536.0f, frameCount); do { @@ -1219,16 +1104,16 @@ va += vaInc; } while (--frameCount); - t->prevVolume[0] = vl; - t->prevVolume[1] = vr; - t->prevAuxLevel = va; - t->adjustVolumeRamp(true); + prevVolume[0] = vl; + prevVolume[1] = vr; + prevAuxLevel = va; + adjustVolumeRamp(true); } // constant gain else { - const uint32_t vrl = t->volumeRL; - const int16_t va = (int16_t)t->auxLevel; + const uint32_t vrl = volumeRL; + const int16_t va = (int16_t)auxLevel; do { uint32_t rl = *reinterpret_cast<const uint32_t *>(in); int16_t a = (int16_t)(((int32_t)in[0] + in[1]) >> 1); @@ -1242,14 +1127,14 @@ } } else { // ramp gain - if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) { - int32_t vl = t->prevVolume[0]; - int32_t vr = t->prevVolume[1]; - const int32_t vlInc = t->volumeInc[0]; - const int32_t vrInc = t->volumeInc[1]; + if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) { + int32_t vl = prevVolume[0]; + int32_t vr = prevVolume[1]; + const int32_t vlInc = volumeInc[0]; + const int32_t vrInc = volumeInc[1]; // ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", - // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // t, vlInc/65536.0f, vl/65536.0f, volume[0], // (vl + vlInc*frameCount)/65536.0f, frameCount); do { @@ -1259,14 +1144,14 @@ vr += vrInc; } while (--frameCount); - t->prevVolume[0] = vl; - t->prevVolume[1] = vr; - t->adjustVolumeRamp(false); + prevVolume[0] = vl; + prevVolume[1] = vr; + adjustVolumeRamp(false); } // constant gain else { - const uint32_t vrl = t->volumeRL; + const uint32_t vrl = volumeRL; do { uint32_t rl = *reinterpret_cast<const uint32_t *>(in); in += 2; @@ -1276,27 +1161,27 @@ } while (--frameCount); } } - t->in = in; + mIn = in; } -void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount, - int32_t* temp __unused, int32_t* aux) +void AudioMixer::Track::track__16BitsMono( + int32_t* out, size_t frameCount, int32_t* temp __unused, int32_t* aux) { ALOGVV("track__16BitsMono\n"); - const int16_t *in = static_cast<int16_t const *>(t->in); + const int16_t *in = static_cast<int16_t const *>(mIn); if (CC_UNLIKELY(aux != NULL)) { // ramp gain - if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) { - int32_t vl = t->prevVolume[0]; - int32_t vr = t->prevVolume[1]; - int32_t va = t->prevAuxLevel; - const int32_t vlInc = t->volumeInc[0]; - const int32_t vrInc = t->volumeInc[1]; - const int32_t vaInc = t->auxInc; + if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) { + int32_t vl = prevVolume[0]; + int32_t vr = prevVolume[1]; + int32_t va = prevAuxLevel; + const int32_t vlInc = volumeInc[0]; + const int32_t vrInc = volumeInc[1]; + const int32_t vaInc = auxInc; // ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", - // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // t, vlInc/65536.0f, vl/65536.0f, volume[0], // (vl + vlInc*frameCount)/65536.0f, frameCount); do { @@ -1309,16 +1194,16 @@ va += vaInc; } while (--frameCount); - t->prevVolume[0] = vl; - t->prevVolume[1] = vr; - t->prevAuxLevel = va; - t->adjustVolumeRamp(true); + prevVolume[0] = vl; + prevVolume[1] = vr; + prevAuxLevel = va; + adjustVolumeRamp(true); } // constant gain else { - const int16_t vl = t->volume[0]; - const int16_t vr = t->volume[1]; - const int16_t va = (int16_t)t->auxLevel; + const int16_t vl = volume[0]; + const int16_t vr = volume[1]; + const int16_t va = (int16_t)auxLevel; do { int16_t l = *in++; out[0] = mulAdd(l, vl, out[0]); @@ -1330,14 +1215,14 @@ } } else { // ramp gain - if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) { - int32_t vl = t->prevVolume[0]; - int32_t vr = t->prevVolume[1]; - const int32_t vlInc = t->volumeInc[0]; - const int32_t vrInc = t->volumeInc[1]; + if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) { + int32_t vl = prevVolume[0]; + int32_t vr = prevVolume[1]; + const int32_t vlInc = volumeInc[0]; + const int32_t vrInc = volumeInc[1]; // ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", - // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // t, vlInc/65536.0f, vl/65536.0f, volume[0], // (vl + vlInc*frameCount)/65536.0f, frameCount); do { @@ -1348,14 +1233,14 @@ vr += vrInc; } while (--frameCount); - t->prevVolume[0] = vl; - t->prevVolume[1] = vr; - t->adjustVolumeRamp(false); + prevVolume[0] = vl; + prevVolume[1] = vr; + adjustVolumeRamp(false); } // constant gain else { - const int16_t vl = t->volume[0]; - const int16_t vr = t->volume[1]; + const int16_t vl = volume[0]; + const int16_t vr = volume[1]; do { int16_t l = *in++; out[0] = mulAdd(l, vl, out[0]); @@ -1364,273 +1249,213 @@ } while (--frameCount); } } - t->in = in; + mIn = in; } // no-op case -void AudioMixer::process__nop(state_t* state) +void AudioMixer::process__nop() { ALOGVV("process__nop\n"); - uint32_t e0 = state->enabledTracks; - while (e0) { + + for (const auto &pair : mGroups) { // process by group of tracks with same output buffer to // avoid multiple memset() on same buffer - uint32_t e1 = e0, e2 = e0; - int i = 31 - __builtin_clz(e1); - { - track_t& t1 = state->tracks[i]; - e2 &= ~(1<<i); - while (e2) { - i = 31 - __builtin_clz(e2); - e2 &= ~(1<<i); - track_t& t2 = state->tracks[i]; - if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { - e1 &= ~(1<<i); - } - } - e0 &= ~(e1); + const auto &group = pair.second; - memset(t1.mainBuffer, 0, state->frameCount * t1.mMixerChannelCount - * audio_bytes_per_sample(t1.mMixerFormat)); - } + const std::shared_ptr<Track> &t = mTracks[group[0]]; + memset(t->mainBuffer, 0, + mFrameCount * t->mMixerChannelCount + * audio_bytes_per_sample(t->mMixerFormat)); - while (e1) { - i = 31 - __builtin_clz(e1); - e1 &= ~(1<<i); - { - track_t& t3 = state->tracks[i]; - size_t outFrames = state->frameCount; - while (outFrames) { - t3.buffer.frameCount = outFrames; - t3.bufferProvider->getNextBuffer(&t3.buffer); - if (t3.buffer.raw == NULL) break; - outFrames -= t3.buffer.frameCount; - t3.bufferProvider->releaseBuffer(&t3.buffer); - } + // now consume data + for (const int name : group) { + const std::shared_ptr<Track> &t = mTracks[name]; + size_t outFrames = mFrameCount; + while (outFrames) { + t->buffer.frameCount = outFrames; + t->bufferProvider->getNextBuffer(&t->buffer); + if (t->buffer.raw == NULL) break; + outFrames -= t->buffer.frameCount; + t->bufferProvider->releaseBuffer(&t->buffer); } } } } // generic code without resampling -void AudioMixer::process__genericNoResampling(state_t* state) +void AudioMixer::process__genericNoResampling() { ALOGVV("process__genericNoResampling\n"); int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32))); - // acquire each track's buffer - uint32_t enabledTracks = state->enabledTracks; - uint32_t e0 = enabledTracks; - while (e0) { - const int i = 31 - __builtin_clz(e0); - e0 &= ~(1<<i); - track_t& t = state->tracks[i]; - t.buffer.frameCount = state->frameCount; - t.bufferProvider->getNextBuffer(&t.buffer); - t.frameCount = t.buffer.frameCount; - t.in = t.buffer.raw; - } + for (const auto &pair : mGroups) { + // process by group of tracks with same output main buffer to + // avoid multiple memset() on same buffer + const auto &group = pair.second; - e0 = enabledTracks; - while (e0) { - // process by group of tracks with same output buffer to - // optimize cache use - uint32_t e1 = e0, e2 = e0; - int j = 31 - __builtin_clz(e1); - track_t& t1 = state->tracks[j]; - e2 &= ~(1<<j); - while (e2) { - j = 31 - __builtin_clz(e2); - e2 &= ~(1<<j); - track_t& t2 = state->tracks[j]; - if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { - e1 &= ~(1<<j); - } + // acquire buffer + for (const int name : group) { + const std::shared_ptr<Track> &t = mTracks[name]; + t->buffer.frameCount = mFrameCount; + t->bufferProvider->getNextBuffer(&t->buffer); + t->frameCount = t->buffer.frameCount; + t->mIn = t->buffer.raw; } - e0 &= ~(e1); - // this assumes output 16 bits stereo, no resampling - int32_t *out = t1.mainBuffer; + + int32_t *out = (int *)pair.first; size_t numFrames = 0; do { + const size_t frameCount = std::min((size_t)BLOCKSIZE, mFrameCount - numFrames); memset(outTemp, 0, sizeof(outTemp)); - e2 = e1; - while (e2) { - const int i = 31 - __builtin_clz(e2); - e2 &= ~(1<<i); - track_t& t = state->tracks[i]; - size_t outFrames = BLOCKSIZE; + for (const int name : group) { + const std::shared_ptr<Track> &t = mTracks[name]; int32_t *aux = NULL; - if (CC_UNLIKELY(t.needs & NEEDS_AUX)) { - aux = t.auxBuffer + numFrames; + if (CC_UNLIKELY(t->needs & NEEDS_AUX)) { + aux = t->auxBuffer + numFrames; } - while (outFrames) { - // t.in == NULL can happen if the track was flushed just after having + for (int outFrames = frameCount; outFrames > 0; ) { + // t->in == nullptr can happen if the track was flushed just after having // been enabled for mixing. - if (t.in == NULL) { - enabledTracks &= ~(1<<i); - e1 &= ~(1<<i); + if (t->mIn == nullptr) { break; } - size_t inFrames = (t.frameCount > outFrames)?outFrames:t.frameCount; + size_t inFrames = (t->frameCount > outFrames)?outFrames:t->frameCount; if (inFrames > 0) { - t.hook(&t, outTemp + (BLOCKSIZE - outFrames) * t.mMixerChannelCount, - inFrames, state->resampleTemp, aux); - t.frameCount -= inFrames; + (t.get()->*t->hook)( + outTemp + (frameCount - outFrames) * t->mMixerChannelCount, + inFrames, mResampleTemp.get() /* naked ptr */, aux); + t->frameCount -= inFrames; outFrames -= inFrames; if (CC_UNLIKELY(aux != NULL)) { aux += inFrames; } } - if (t.frameCount == 0 && outFrames) { - t.bufferProvider->releaseBuffer(&t.buffer); - t.buffer.frameCount = (state->frameCount - numFrames) - - (BLOCKSIZE - outFrames); - t.bufferProvider->getNextBuffer(&t.buffer); - t.in = t.buffer.raw; - if (t.in == NULL) { - enabledTracks &= ~(1<<i); - e1 &= ~(1<<i); + if (t->frameCount == 0 && outFrames) { + t->bufferProvider->releaseBuffer(&t->buffer); + t->buffer.frameCount = (mFrameCount - numFrames) - + (frameCount - outFrames); + t->bufferProvider->getNextBuffer(&t->buffer); + t->mIn = t->buffer.raw; + if (t->mIn == nullptr) { break; } - t.frameCount = t.buffer.frameCount; + t->frameCount = t->buffer.frameCount; } } } - convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat, - BLOCKSIZE * t1.mMixerChannelCount); + const std::shared_ptr<Track> &t1 = mTracks[group[0]]; + convertMixerFormat(out, t1->mMixerFormat, outTemp, t1->mMixerInFormat, + frameCount * t1->mMixerChannelCount); // TODO: fix ugly casting due to choice of out pointer type out = reinterpret_cast<int32_t*>((uint8_t*)out - + BLOCKSIZE * t1.mMixerChannelCount - * audio_bytes_per_sample(t1.mMixerFormat)); - numFrames += BLOCKSIZE; - } while (numFrames < state->frameCount); - } + + frameCount * t1->mMixerChannelCount + * audio_bytes_per_sample(t1->mMixerFormat)); + numFrames += frameCount; + } while (numFrames < mFrameCount); - // release each track's buffer - e0 = enabledTracks; - while (e0) { - const int i = 31 - __builtin_clz(e0); - e0 &= ~(1<<i); - track_t& t = state->tracks[i]; - t.bufferProvider->releaseBuffer(&t.buffer); + // release each track's buffer + for (const int name : group) { + const std::shared_ptr<Track> &t = mTracks[name]; + t->bufferProvider->releaseBuffer(&t->buffer); + } } } - // generic code with resampling -void AudioMixer::process__genericResampling(state_t* state) +void AudioMixer::process__genericResampling() { ALOGVV("process__genericResampling\n"); - // this const just means that local variable outTemp doesn't change - int32_t* const outTemp = state->outputTemp; - size_t numFrames = state->frameCount; + int32_t * const outTemp = mOutputTemp.get(); // naked ptr + size_t numFrames = mFrameCount; - uint32_t e0 = state->enabledTracks; - while (e0) { - // process by group of tracks with same output buffer - // to optimize cache use - uint32_t e1 = e0, e2 = e0; - int j = 31 - __builtin_clz(e1); - track_t& t1 = state->tracks[j]; - e2 &= ~(1<<j); - while (e2) { - j = 31 - __builtin_clz(e2); - e2 &= ~(1<<j); - track_t& t2 = state->tracks[j]; - if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { - e1 &= ~(1<<j); - } - } - e0 &= ~(e1); - int32_t *out = t1.mainBuffer; - memset(outTemp, 0, sizeof(*outTemp) * t1.mMixerChannelCount * state->frameCount); - while (e1) { - const int i = 31 - __builtin_clz(e1); - e1 &= ~(1<<i); - track_t& t = state->tracks[i]; + for (const auto &pair : mGroups) { + const auto &group = pair.second; + const std::shared_ptr<Track> &t1 = mTracks[group[0]]; + + // clear temp buffer + memset(outTemp, 0, sizeof(*outTemp) * t1->mMixerChannelCount * mFrameCount); + for (const int name : group) { + const std::shared_ptr<Track> &t = mTracks[name]; int32_t *aux = NULL; - if (CC_UNLIKELY(t.needs & NEEDS_AUX)) { - aux = t.auxBuffer; + if (CC_UNLIKELY(t->needs & NEEDS_AUX)) { + aux = t->auxBuffer; } // this is a little goofy, on the resampling case we don't // acquire/release the buffers because it's done by // the resampler. - if (t.needs & NEEDS_RESAMPLE) { - t.hook(&t, outTemp, numFrames, state->resampleTemp, aux); + if (t->needs & NEEDS_RESAMPLE) { + (t.get()->*t->hook)(outTemp, numFrames, mResampleTemp.get() /* naked ptr */, aux); } else { size_t outFrames = 0; while (outFrames < numFrames) { - t.buffer.frameCount = numFrames - outFrames; - t.bufferProvider->getNextBuffer(&t.buffer); - t.in = t.buffer.raw; - // t.in == NULL can happen if the track was flushed just after having + t->buffer.frameCount = numFrames - outFrames; + t->bufferProvider->getNextBuffer(&t->buffer); + t->mIn = t->buffer.raw; + // t->mIn == nullptr can happen if the track was flushed just after having // been enabled for mixing. - if (t.in == NULL) break; + if (t->mIn == nullptr) break; - if (CC_UNLIKELY(aux != NULL)) { - aux += outFrames; - } - t.hook(&t, outTemp + outFrames * t.mMixerChannelCount, t.buffer.frameCount, - state->resampleTemp, aux); - outFrames += t.buffer.frameCount; - t.bufferProvider->releaseBuffer(&t.buffer); + (t.get()->*t->hook)( + outTemp + outFrames * t->mMixerChannelCount, t->buffer.frameCount, + mResampleTemp.get() /* naked ptr */, + aux != nullptr ? aux + outFrames : nullptr); + outFrames += t->buffer.frameCount; + + t->bufferProvider->releaseBuffer(&t->buffer); } } } - convertMixerFormat(out, t1.mMixerFormat, - outTemp, t1.mMixerInFormat, numFrames * t1.mMixerChannelCount); + convertMixerFormat(t1->mainBuffer, t1->mMixerFormat, + outTemp, t1->mMixerInFormat, numFrames * t1->mMixerChannelCount); } } // one track, 16 bits stereo without resampling is the most common case -void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) +void AudioMixer::process__oneTrack16BitsStereoNoResampling() { - ALOGVV("process__OneTrack16BitsStereoNoResampling\n"); - // This method is only called when state->enabledTracks has exactly - // one bit set. The asserts below would verify this, but are commented out - // since the whole point of this method is to optimize performance. - //ALOG_ASSERT(0 != state->enabledTracks, "no tracks enabled"); - const int i = 31 - __builtin_clz(state->enabledTracks); - //ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled"); - const track_t& t = state->tracks[i]; + ALOGVV("process__oneTrack16BitsStereoNoResampling\n"); + LOG_ALWAYS_FATAL_IF(mEnabled.size() != 0, + "%zu != 1 tracks enabled", mEnabled.size()); + const int name = mEnabled[0]; + const std::shared_ptr<Track> &t = mTracks[name]; - AudioBufferProvider::Buffer& b(t.buffer); + AudioBufferProvider::Buffer& b(t->buffer); - int32_t* out = t.mainBuffer; + int32_t* out = t->mainBuffer; float *fout = reinterpret_cast<float*>(out); - size_t numFrames = state->frameCount; + size_t numFrames = mFrameCount; - const int16_t vl = t.volume[0]; - const int16_t vr = t.volume[1]; - const uint32_t vrl = t.volumeRL; + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + const uint32_t vrl = t->volumeRL; while (numFrames) { b.frameCount = numFrames; - t.bufferProvider->getNextBuffer(&b); + t->bufferProvider->getNextBuffer(&b); const int16_t *in = b.i16; // in == NULL can happen if the track was flushed just after having // been enabled for mixing. if (in == NULL || (((uintptr_t)in) & 3)) { - if ( AUDIO_FORMAT_PCM_FLOAT == t.mMixerFormat ) { + if ( AUDIO_FORMAT_PCM_FLOAT == t->mMixerFormat ) { memset((char*)fout, 0, numFrames - * t.mMixerChannelCount * audio_bytes_per_sample(t.mMixerFormat)); + * t->mMixerChannelCount * audio_bytes_per_sample(t->mMixerFormat)); } else { memset((char*)out, 0, numFrames - * t.mMixerChannelCount * audio_bytes_per_sample(t.mMixerFormat)); + * t->mMixerChannelCount * audio_bytes_per_sample(t->mMixerFormat)); } ALOGE_IF((((uintptr_t)in) & 3), - "process__OneTrack16BitsStereoNoResampling: misaligned buffer" + "process__oneTrack16BitsStereoNoResampling: misaligned buffer" " %p track %d, channels %d, needs %08x, volume %08x vfl %f vfr %f", - in, i, t.channelCount, t.needs, vrl, t.mVolume[0], t.mVolume[1]); + in, name, t->channelCount, t->needs, vrl, t->mVolume[0], t->mVolume[1]); return; } size_t outFrames = b.frameCount; - switch (t.mMixerFormat) { + switch (t->mMixerFormat) { case AUDIO_FORMAT_PCM_FLOAT: do { uint32_t rl = *reinterpret_cast<const uint32_t *>(in); @@ -1668,10 +1493,10 @@ } break; default: - LOG_ALWAYS_FATAL("bad mixer format: %d", t.mMixerFormat); + LOG_ALWAYS_FATAL("bad mixer format: %d", t->mMixerFormat); } numFrames -= b.frameCount; - t.bufferProvider->releaseBuffer(&b); + t->bufferProvider->releaseBuffer(&b); } } @@ -1694,7 +1519,7 @@ /* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) * TO: int32_t (Q4.27) or float * TI: int32_t (Q4.27) or int16_t (Q0.15) or float - * TA: int32_t (Q4.27) + * TA: int32_t (Q4.27) or float */ template <int MIXTYPE, typename TO, typename TI, typename TV, typename TA, typename TAV> @@ -1738,7 +1563,7 @@ /* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) * TO: int32_t (Q4.27) or float * TI: int32_t (Q4.27) or int16_t (Q0.15) or float - * TA: int32_t (Q4.27) + * TA: int32_t (Q4.27) or float */ template <int MIXTYPE, typename TO, typename TI, typename TV, typename TA, typename TAV> @@ -1778,34 +1603,46 @@ * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards) * TO: int32_t (Q4.27) or float * TI: int32_t (Q4.27) or int16_t (Q0.15) or float - * TA: int32_t (Q4.27) + * TA: int32_t (Q4.27) or float */ template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL, typename TO, typename TI, typename TA> -void AudioMixer::volumeMix(TO *out, size_t outFrames, - const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t) +void AudioMixer::Track::volumeMix(TO *out, size_t outFrames, + const TI *in, TA *aux, bool ramp) { if (USEFLOATVOL) { if (ramp) { - volumeRampMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux, - t->mPrevVolume, t->mVolumeInc, &t->prevAuxLevel, t->auxInc); + volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux, + mPrevVolume, mVolumeInc, +#ifdef FLOAT_AUX + &mPrevAuxLevel, mAuxInc +#else + &prevAuxLevel, auxInc +#endif + ); if (ADJUSTVOL) { - t->adjustVolumeRamp(aux != NULL, true); + adjustVolumeRamp(aux != NULL, true); } } else { - volumeMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux, - t->mVolume, t->auxLevel); + volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux, + mVolume, +#ifdef FLOAT_AUX + mAuxLevel +#else + auxLevel +#endif + ); } } else { if (ramp) { - volumeRampMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux, - t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc); + volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux, + prevVolume, volumeInc, &prevAuxLevel, auxInc); if (ADJUSTVOL) { - t->adjustVolumeRamp(aux != NULL); + adjustVolumeRamp(aux != NULL); } } else { - volumeMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux, - t->volume, t->auxLevel); + volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux, + volume, auxLevel); } } } @@ -1820,19 +1657,18 @@ * TA: int32_t (Q4.27) */ template <int MIXTYPE, typename TO, typename TI, typename TA> -void AudioMixer::process_NoResampleOneTrack(state_t* state) +void AudioMixer::process__noResampleOneTrack() { - ALOGVV("process_NoResampleOneTrack\n"); - // CLZ is faster than CTZ on ARM, though really not sure if true after 31 - clz. - const int i = 31 - __builtin_clz(state->enabledTracks); - ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled"); - track_t *t = &state->tracks[i]; + ALOGVV("process__noResampleOneTrack\n"); + LOG_ALWAYS_FATAL_IF(mEnabled.size() != 1, + "%zu != 1 tracks enabled", mEnabled.size()); + const std::shared_ptr<Track> &t = mTracks[mEnabled[0]]; const uint32_t channels = t->mMixerChannelCount; TO* out = reinterpret_cast<TO*>(t->mainBuffer); TA* aux = reinterpret_cast<TA*>(t->auxBuffer); const bool ramp = t->needsRamp(); - for (size_t numFrames = state->frameCount; numFrames; ) { + for (size_t numFrames = mFrameCount; numFrames > 0; ) { AudioBufferProvider::Buffer& b(t->buffer); // get input buffer b.frameCount = numFrames; @@ -1844,19 +1680,19 @@ if (in == NULL || (((uintptr_t)in) & 3)) { memset(out, 0, numFrames * channels * audio_bytes_per_sample(t->mMixerFormat)); - ALOGE_IF((((uintptr_t)in) & 3), "process_NoResampleOneTrack: bus error: " + ALOGE_IF((((uintptr_t)in) & 3), "process__noResampleOneTrack: bus error: " "buffer %p track %p, channels %d, needs %#x", - in, t, t->channelCount, t->needs); + in, &t, t->channelCount, t->needs); return; } const size_t outFrames = b.frameCount; - volumeMix<MIXTYPE, is_same<TI, float>::value, false> ( - out, outFrames, in, aux, ramp, t); + t->volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, false /* ADJUSTVOL */> ( + out, outFrames, in, aux, ramp); out += outFrames * channels; if (aux != NULL) { - aux += channels; + aux += outFrames; } numFrames -= b.frameCount; @@ -1874,59 +1710,59 @@ * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) * TO: int32_t (Q4.27) or float * TI: int32_t (Q4.27) or int16_t (Q0.15) or float - * TA: int32_t (Q4.27) + * TA: int32_t (Q4.27) or float */ template <int MIXTYPE, typename TO, typename TI, typename TA> -void AudioMixer::track__Resample(track_t* t, TO* out, size_t outFrameCount, TO* temp, TA* aux) +void AudioMixer::Track::track__Resample(TO* out, size_t outFrameCount, TO* temp, TA* aux) { ALOGVV("track__Resample\n"); - t->resampler->setSampleRate(t->sampleRate); - const bool ramp = t->needsRamp(); + mResampler->setSampleRate(sampleRate); + const bool ramp = needsRamp(); if (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. - t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); - memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(TO)); - t->resampler->resample((int32_t*)temp, outFrameCount, t->bufferProvider); + mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(TO)); + mResampler->resample((int32_t*)temp, outFrameCount, bufferProvider); - volumeMix<MIXTYPE, is_same<TI, float>::value, true>( - out, outFrameCount, temp, aux, ramp, t); + volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>( + out, outFrameCount, temp, aux, ramp); } else { // constant volume gain - t->resampler->setVolume(t->mVolume[0], t->mVolume[1]); - t->resampler->resample((int32_t*)out, outFrameCount, t->bufferProvider); + mResampler->setVolume(mVolume[0], mVolume[1]); + mResampler->resample((int32_t*)out, outFrameCount, bufferProvider); } } /* This track hook is called to mix a track, when no resampling is required. - * The input buffer should be present in t->in. + * The input buffer should be present in in. * * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) * TO: int32_t (Q4.27) or float * TI: int32_t (Q4.27) or int16_t (Q0.15) or float - * TA: int32_t (Q4.27) + * TA: int32_t (Q4.27) or float */ template <int MIXTYPE, typename TO, typename TI, typename TA> -void AudioMixer::track__NoResample(track_t* t, TO* out, size_t frameCount, - TO* temp __unused, TA* aux) +void AudioMixer::Track::track__NoResample(TO* out, size_t frameCount, TO* temp __unused, TA* aux) { ALOGVV("track__NoResample\n"); - const TI *in = static_cast<const TI *>(t->in); + const TI *in = static_cast<const TI *>(mIn); - volumeMix<MIXTYPE, is_same<TI, float>::value, true>( - out, frameCount, in, aux, t->needsRamp(), t); + volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>( + out, frameCount, in, aux, needsRamp()); // MIXTYPE_MONOEXPAND reads a single input channel and expands to NCHAN output channels. // MIXTYPE_MULTI reads NCHAN input channels and places to NCHAN output channels. - in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * t->mMixerChannelCount; - t->in = in; + in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * mMixerChannelCount; + mIn = in; } /* The Mixer engine generates either int32_t (Q4_27) or float data. * We use this function to convert the engine buffers * to the desired mixer output format, either int16_t (Q.15) or float. */ +/* static */ void AudioMixer::convertMixerFormat(void *out, audio_format_t mixerOutFormat, void *in, audio_format_t mixerInFormat, size_t sampleCount) { @@ -1947,11 +1783,10 @@ case AUDIO_FORMAT_PCM_16_BIT: switch (mixerOutFormat) { case AUDIO_FORMAT_PCM_FLOAT: - memcpy_to_float_from_q4_27((float*)out, (int32_t*)in, sampleCount); + memcpy_to_float_from_q4_27((float*)out, (const int32_t*)in, sampleCount); break; case AUDIO_FORMAT_PCM_16_BIT: - // two int16_t are produced per iteration - ditherAndClamp((int32_t*)out, (int32_t*)in, sampleCount >> 1); + memcpy_to_i16_from_q4_27((int16_t*)out, (const int32_t*)in, sampleCount); break; default: LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); @@ -1966,19 +1801,20 @@ /* Returns the proper track hook to use for mixing the track into the output buffer. */ -AudioMixer::hook_t AudioMixer::getTrackHook(int trackType, uint32_t channelCount, +/* static */ +AudioMixer::hook_t AudioMixer::Track::getTrackHook(int trackType, uint32_t channelCount, audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused) { if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) { switch (trackType) { case TRACKTYPE_NOP: - return track__nop; + return &Track::track__nop; case TRACKTYPE_RESAMPLE: - return track__genericResample; + return &Track::track__genericResample; case TRACKTYPE_NORESAMPLEMONO: - return track__16BitsMono; + return &Track::track__16BitsMono; case TRACKTYPE_NORESAMPLE: - return track__16BitsStereo; + return &Track::track__16BitsStereo; default: LOG_ALWAYS_FATAL("bad trackType: %d", trackType); break; @@ -1987,15 +1823,15 @@ LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS); switch (trackType) { case TRACKTYPE_NOP: - return track__nop; + return &Track::track__nop; case TRACKTYPE_RESAMPLE: switch (mixerInFormat) { case AUDIO_FORMAT_PCM_FLOAT: - return (AudioMixer::hook_t) - track__Resample<MIXTYPE_MULTI, float /*TO*/, float /*TI*/, int32_t /*TA*/>; + return (AudioMixer::hook_t) &Track::track__Resample< + MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>; case AUDIO_FORMAT_PCM_16_BIT: - return (AudioMixer::hook_t)\ - track__Resample<MIXTYPE_MULTI, int32_t, int16_t, int32_t>; + return (AudioMixer::hook_t) &Track::track__Resample< + MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>; default: LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); break; @@ -2004,11 +1840,11 @@ case TRACKTYPE_NORESAMPLEMONO: switch (mixerInFormat) { case AUDIO_FORMAT_PCM_FLOAT: - return (AudioMixer::hook_t) - track__NoResample<MIXTYPE_MONOEXPAND, float, float, int32_t>; + return (AudioMixer::hook_t) &Track::track__NoResample< + MIXTYPE_MONOEXPAND, float /*TO*/, float /*TI*/, TYPE_AUX>; case AUDIO_FORMAT_PCM_16_BIT: - return (AudioMixer::hook_t) - track__NoResample<MIXTYPE_MONOEXPAND, int32_t, int16_t, int32_t>; + return (AudioMixer::hook_t) &Track::track__NoResample< + MIXTYPE_MONOEXPAND, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>; default: LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); break; @@ -2017,11 +1853,11 @@ case TRACKTYPE_NORESAMPLE: switch (mixerInFormat) { case AUDIO_FORMAT_PCM_FLOAT: - return (AudioMixer::hook_t) - track__NoResample<MIXTYPE_MULTI, float, float, int32_t>; + return (AudioMixer::hook_t) &Track::track__NoResample< + MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>; case AUDIO_FORMAT_PCM_16_BIT: - return (AudioMixer::hook_t) - track__NoResample<MIXTYPE_MULTI, int32_t, int16_t, int32_t>; + return (AudioMixer::hook_t) &Track::track__NoResample< + MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>; default: LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); break; @@ -2041,7 +1877,9 @@ * a stereo output track, the input track cannot be MONO. This should be * prevented by the caller. */ -AudioMixer::process_hook_t AudioMixer::getProcessHook(int processType, uint32_t channelCount, +/* static */ +AudioMixer::process_hook_t AudioMixer::getProcessHook( + int processType, uint32_t channelCount, audio_format_t mixerInFormat, audio_format_t mixerOutFormat) { if (processType != PROCESSTYPE_NORESAMPLEONETRACK) { // Only NORESAMPLEONETRACK @@ -2049,18 +1887,18 @@ return NULL; } if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) { - return process__OneTrack16BitsStereoNoResampling; + return &AudioMixer::process__oneTrack16BitsStereoNoResampling; } LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS); switch (mixerInFormat) { case AUDIO_FORMAT_PCM_FLOAT: switch (mixerOutFormat) { case AUDIO_FORMAT_PCM_FLOAT: - return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, - float /*TO*/, float /*TI*/, int32_t /*TA*/>; + return &AudioMixer::process__noResampleOneTrack< + MIXTYPE_MULTI_SAVEONLY, float /*TO*/, float /*TI*/, TYPE_AUX>; case AUDIO_FORMAT_PCM_16_BIT: - return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, - int16_t, float, int32_t>; + return &AudioMixer::process__noResampleOneTrack< + MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/, float /*TI*/, TYPE_AUX>; default: LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); break; @@ -2069,11 +1907,11 @@ case AUDIO_FORMAT_PCM_16_BIT: switch (mixerOutFormat) { case AUDIO_FORMAT_PCM_FLOAT: - return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, - float, int16_t, int32_t>; + return &AudioMixer::process__noResampleOneTrack< + MIXTYPE_MULTI_SAVEONLY, float /*TO*/, int16_t /*TI*/, TYPE_AUX>; case AUDIO_FORMAT_PCM_16_BIT: - return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, - int16_t, int16_t, int32_t>; + return &AudioMixer::process__noResampleOneTrack< + MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/, int16_t /*TI*/, TYPE_AUX>; default: LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); break;
diff --git a/media/libaudioprocessing/AudioMixerOps.h b/media/libaudioprocessing/AudioMixerOps.h index 8d74024..f33e361 100644 --- a/media/libaudioprocessing/AudioMixerOps.h +++ b/media/libaudioprocessing/AudioMixerOps.h
@@ -188,13 +188,13 @@ template<> inline void MixAccum<float, int16_t>(float *auxaccum, int16_t value) { - static const float norm = 1. / (1 << 15); + static constexpr float norm = 1. / (1 << 15); *auxaccum += norm * value; } template<> inline void MixAccum<float, int32_t>(float *auxaccum, int32_t value) { - static const float norm = 1. / (1 << 27); + static constexpr float norm = 1. / (1 << 27); *auxaccum += norm * value; } @@ -238,6 +238,7 @@ * NCHAN represents number of input and output channels. * TO: int32_t (Q4.27) or float * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) or float * TV: int32_t (U4.28) or int16_t (U4.12) or float * vol: represents a volume array. * @@ -247,7 +248,8 @@ * Single input channel. NCHAN represents number of output channels. * TO: int32_t (Q4.27) or float * TI: int32_t (Q4.27) or int16_t (Q0.15) or float - * TV: int32_t (U4.28) or int16_t (U4.12) or float + * TA: int32_t (Q4.27) or float + * TV/TAV: int32_t (U4.28) or int16_t (U4.12) or float * Input channel count is 1. * vol: represents volume array. * @@ -257,7 +259,8 @@ * NCHAN represents number of input and output channels. * TO: int16_t (Q.15) or float * TI: int32_t (Q4.27) or int16_t (Q0.15) or float - * TV: int32_t (U4.28) or int16_t (U4.12) or float + * TA: int32_t (Q4.27) or float + * TV/TAV: int32_t (U4.28) or int16_t (U4.12) or float * vol: represents a volume array. * * MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer.
diff --git a/media/libaudioprocessing/AudioResamplerDyn.cpp b/media/libaudioprocessing/AudioResamplerDyn.cpp index 8f7b982..eeeecce 100644 --- a/media/libaudioprocessing/AudioResamplerDyn.cpp +++ b/media/libaudioprocessing/AudioResamplerDyn.cpp
@@ -38,6 +38,9 @@ //#define DEBUG_RESAMPLER +// use this for our buffer alignment. Should be at least 32 bytes. +constexpr size_t CACHE_LINE_SIZE = 64; + namespace android { /* @@ -94,7 +97,10 @@ // create new buffer TI* state = NULL; - (void)posix_memalign(reinterpret_cast<void**>(&state), 32, stateCount*sizeof(*state)); + (void)posix_memalign( + reinterpret_cast<void **>(&state), + CACHE_LINE_SIZE /* alignment */, + stateCount * sizeof(*state)); memset(state, 0, stateCount*sizeof(*state)); // attempt to preserve state @@ -185,6 +191,16 @@ // setSampleRate() for 1:1. (May be removed if precalculated filters are used.) mInSampleRate = 0; mConstants.set(128, 8, mSampleRate, mSampleRate); // TODO: set better + + // fetch property based resampling parameters + mPropertyEnableAtSampleRate = property_get_int32( + "ro.audio.resampler.psd.enable_at_samplerate", mPropertyEnableAtSampleRate); + mPropertyHalfFilterLength = property_get_int32( + "ro.audio.resampler.psd.halflength", mPropertyHalfFilterLength); + mPropertyStopbandAttenuation = property_get_int32( + "ro.audio.resampler.psd.stopband", mPropertyStopbandAttenuation); + mPropertyCutoffPercent = property_get_int32( + "ro.audio.resampler.psd.cutoff_percent", mPropertyCutoffPercent); } template<typename TC, typename TI, typename TO> @@ -215,6 +231,8 @@ } } +// TODO: update to C++11 + template<typename T> T max(T a, T b) {return a > b ? a : b;} template<typename T> T absdiff(T a, T b) {return a > b ? a - b : b - a;} @@ -223,37 +241,74 @@ void AudioResamplerDyn<TC, TI, TO>::createKaiserFir(Constants &c, double stopBandAtten, int inSampleRate, int outSampleRate, double tbwCheat) { - TC* buf = NULL; - static const double atten = 0.9998; // to avoid ripple overflow - double fcr; - double tbw = firKaiserTbw(c.mHalfNumCoefs, stopBandAtten); + // compute the normalized transition bandwidth + const double tbw = firKaiserTbw(c.mHalfNumCoefs, stopBandAtten); + const double halfbw = tbw / 2.; - (void)posix_memalign(reinterpret_cast<void**>(&buf), 32, (c.mL+1)*c.mHalfNumCoefs*sizeof(TC)); + double fcr; // compute fcr, the 3 dB amplitude cut-off. if (inSampleRate < outSampleRate) { // upsample - fcr = max(0.5*tbwCheat - tbw/2, tbw/2); + fcr = max(0.5 * tbwCheat - halfbw, halfbw); } else { // downsample - fcr = max(0.5*tbwCheat*outSampleRate/inSampleRate - tbw/2, tbw/2); + fcr = max(0.5 * tbwCheat * outSampleRate / inSampleRate - halfbw, halfbw); } - // create and set filter - firKaiserGen(buf, c.mL, c.mHalfNumCoefs, stopBandAtten, fcr, atten); - c.mFirCoefs = buf; - if (mCoefBuffer) { - free(mCoefBuffer); - } - mCoefBuffer = buf; -#ifdef DEBUG_RESAMPLER + createKaiserFir(c, stopBandAtten, fcr); +} + +template<typename TC, typename TI, typename TO> +void AudioResamplerDyn<TC, TI, TO>::createKaiserFir(Constants &c, + double stopBandAtten, double fcr) { + // compute the normalized transition bandwidth + const double tbw = firKaiserTbw(c.mHalfNumCoefs, stopBandAtten); + const int phases = c.mL; + const int halfLength = c.mHalfNumCoefs; + + // create buffer + TC *coefs = nullptr; + int ret = posix_memalign( + reinterpret_cast<void **>(&coefs), + CACHE_LINE_SIZE /* alignment */, + (phases + 1) * halfLength * sizeof(TC)); + LOG_ALWAYS_FATAL_IF(ret != 0, "Cannot allocate buffer memory, ret %d", ret); + c.mFirCoefs = coefs; + free(mCoefBuffer); + mCoefBuffer = coefs; + + // square the computed minimum passband value (extra safety). + double attenuation = + computeWindowedSincMinimumPassbandValue(stopBandAtten); + attenuation *= attenuation; + + // design filter + firKaiserGen(coefs, phases, halfLength, stopBandAtten, fcr, attenuation); + + // update the design criteria + mNormalizedCutoffFrequency = fcr; + mNormalizedTransitionBandwidth = tbw; + mFilterAttenuation = attenuation; + mStopbandAttenuationDb = stopBandAtten; + mPassbandRippleDb = computeWindowedSincPassbandRippleDb(stopBandAtten); + +#if 0 + // Keep this debug code in case an app causes resampler design issues. + const double halfbw = tbw / 2.; // print basic filter stats - printf("L:%d hnc:%d stopBandAtten:%lf fcr:%lf atten:%lf tbw:%lf\n", - c.mL, c.mHalfNumCoefs, stopBandAtten, fcr, atten, tbw); - // test the filter and report results - double fp = (fcr - tbw/2)/c.mL; - double fs = (fcr + tbw/2)/c.mL; + ALOGD("L:%d hnc:%d stopBandAtten:%lf fcr:%lf atten:%lf tbw:%lf\n", + c.mL, c.mHalfNumCoefs, stopBandAtten, fcr, attenuation, tbw); + + // test the filter and report results. + // Since this is a polyphase filter, normalized fp and fs must be scaled. + const double fp = (fcr - halfbw) / phases; + const double fs = (fcr + halfbw) / phases; + double passMin, passMax, passRipple; double stopMax, stopRipple; - testFir(buf, c.mL, c.mHalfNumCoefs, fp, fs, /*passSteps*/ 1000, /*stopSteps*/ 100000, + + const int32_t passSteps = 1000; + + testFir(coefs, c.mL, c.mHalfNumCoefs, fp, fs, passSteps, passSteps * c.ML /*stopSteps*/, passMin, passMax, passRipple, stopMax, stopRipple); - printf("passband(%lf, %lf): %.8lf %.8lf %.8lf\n", 0., fp, passMin, passMax, passRipple); - printf("stopband(%lf, %lf): %.8lf %.3lf\n", fs, 0.5, stopMax, stopRipple); + ALOGD("passband(%lf, %lf): %.8lf %.8lf %.8lf\n", 0., fp, passMin, passMax, passRipple); + ALOGD("stopband(%lf, %lf): %.8lf %.3lf\n", fs, 0.5, stopMax, stopRipple); #endif } @@ -304,6 +359,11 @@ mFilterSampleRate = inSampleRate; mFilterQuality = getQuality(); + double stopBandAtten; + double tbwCheat = 1.; // how much we "cheat" into aliasing + int halfLength; + double fcr = 0.; + // Begin Kaiser Filter computation // // The quantization floor for S16 is about 96db - 10*log_10(#length) + 3dB. @@ -313,52 +373,60 @@ // 96-98dB // - double stopBandAtten; - double tbwCheat = 1.; // how much we "cheat" into aliasing - int halfLength; - if (mFilterQuality == DYN_HIGH_QUALITY) { - // 32b coefficients, 64 length + if (mPropertyEnableAtSampleRate >= 0 && mSampleRate >= mPropertyEnableAtSampleRate) { + // An alternative method which allows allows a greater fcr + // at the expense of potential aliasing. + halfLength = mPropertyHalfFilterLength; + stopBandAtten = mPropertyStopbandAttenuation; useS32 = true; - stopBandAtten = 98.; - if (inSampleRate >= mSampleRate * 4) { - halfLength = 48; - } else if (inSampleRate >= mSampleRate * 2) { - halfLength = 40; - } else { - halfLength = 32; - } - } else if (mFilterQuality == DYN_LOW_QUALITY) { - // 16b coefficients, 16-32 length - useS32 = false; - stopBandAtten = 80.; - if (inSampleRate >= mSampleRate * 4) { - halfLength = 24; - } else if (inSampleRate >= mSampleRate * 2) { - halfLength = 16; - } else { - halfLength = 8; - } - if (inSampleRate <= mSampleRate) { - tbwCheat = 1.05; - } else { - tbwCheat = 1.03; - } - } else { // DYN_MED_QUALITY - // 16b coefficients, 32-64 length - // note: > 64 length filters with 16b coefs can have quantization noise problems - useS32 = false; - stopBandAtten = 84.; - if (inSampleRate >= mSampleRate * 4) { - halfLength = 32; - } else if (inSampleRate >= mSampleRate * 2) { - halfLength = 24; - } else { - halfLength = 16; - } - if (inSampleRate <= mSampleRate) { - tbwCheat = 1.03; - } else { - tbwCheat = 1.01; + fcr = mInSampleRate <= mSampleRate + ? 0.5 : 0.5 * mSampleRate / mInSampleRate; + fcr *= mPropertyCutoffPercent / 100.; + } else { + if (mFilterQuality == DYN_HIGH_QUALITY) { + // 32b coefficients, 64 length + useS32 = true; + stopBandAtten = 98.; + if (inSampleRate >= mSampleRate * 4) { + halfLength = 48; + } else if (inSampleRate >= mSampleRate * 2) { + halfLength = 40; + } else { + halfLength = 32; + } + } else if (mFilterQuality == DYN_LOW_QUALITY) { + // 16b coefficients, 16-32 length + useS32 = false; + stopBandAtten = 80.; + if (inSampleRate >= mSampleRate * 4) { + halfLength = 24; + } else if (inSampleRate >= mSampleRate * 2) { + halfLength = 16; + } else { + halfLength = 8; + } + if (inSampleRate <= mSampleRate) { + tbwCheat = 1.05; + } else { + tbwCheat = 1.03; + } + } else { // DYN_MED_QUALITY + // 16b coefficients, 32-64 length + // note: > 64 length filters with 16b coefs can have quantization noise problems + useS32 = false; + stopBandAtten = 84.; + if (inSampleRate >= mSampleRate * 4) { + halfLength = 32; + } else if (inSampleRate >= mSampleRate * 2) { + halfLength = 24; + } else { + halfLength = 16; + } + if (inSampleRate <= mSampleRate) { + tbwCheat = 1.03; + } else { + tbwCheat = 1.01; + } } } @@ -390,8 +458,12 @@ // create the filter mConstants.set(phases, halfLength, inSampleRate, mSampleRate); - createKaiserFir(mConstants, stopBandAtten, - inSampleRate, mSampleRate, tbwCheat); + if (fcr > 0.) { + createKaiserFir(mConstants, stopBandAtten, fcr); + } else { + createKaiserFir(mConstants, stopBandAtten, + inSampleRate, mSampleRate, tbwCheat); + } } // End Kaiser filter // update phase and state based on the new filter.
diff --git a/media/libaudioprocessing/AudioResamplerDyn.h b/media/libaudioprocessing/AudioResamplerDyn.h index 1840fc7..92144d0 100644 --- a/media/libaudioprocessing/AudioResamplerDyn.h +++ b/media/libaudioprocessing/AudioResamplerDyn.h
@@ -55,6 +55,39 @@ virtual size_t resample(int32_t* out, size_t outFrameCount, AudioBufferProvider* provider); + // Make available key design criteria for testing + int getHalfLength() const { + return mConstants.mHalfNumCoefs; + } + + const TC *getFilterCoefs() const { + return mConstants.mFirCoefs; + } + + int getPhases() const { + return mConstants.mL; + } + + double getStopbandAttenuationDb() const { + return mStopbandAttenuationDb; + } + + double getPassbandRippleDb() const { + return mPassbandRippleDb; + } + + double getNormalizedTransitionBandwidth() const { + return mNormalizedTransitionBandwidth; + } + + double getFilterAttenuation() const { + return mFilterAttenuation; + } + + double getNormalizedCutoffFrequency() const { + return mNormalizedCutoffFrequency; + } + private: class Constants { // stores the filter constants. @@ -112,6 +145,8 @@ void createKaiserFir(Constants &c, double stopBandAtten, int inSampleRate, int outSampleRate, double tbwCheat); + void createKaiserFir(Constants &c, double stopBandAtten, double fcr); + template<int CHANNELS, bool LOCKED, int STRIDE> size_t resample(TO* out, size_t outFrameCount, AudioBufferProvider* provider); @@ -127,6 +162,38 @@ int32_t mFilterSampleRate; // designed filter sample rate. src_quality mFilterQuality; // designed filter quality. void* mCoefBuffer; // if a filter is created, this is not null + + // Property selected design parameters. + // This will enable fixed high quality resampling. + + // 32 char PROP_NAME_MAX limit enforced before Android O + + // Use for sample rates greater than or equal to this value. + // Set to non-negative to enable, negative to disable. + int32_t mPropertyEnableAtSampleRate = 48000; + // "ro.audio.resampler.psd.enable_at_samplerate" + + // Specify HALF the resampling filter length. + // Set to a value which is a multiple of 4. + int32_t mPropertyHalfFilterLength = 32; + // "ro.audio.resampler.psd.halflength" + + // Specify the stopband attenuation in positive dB. + // Set to a value greater or equal to 20. + int32_t mPropertyStopbandAttenuation = 90; + // "ro.audio.resampler.psd.stopband" + + // Specify the cutoff frequency as a percentage of Nyquist. + // Set to a value between 50 and 100. + int32_t mPropertyCutoffPercent = 100; + // "ro.audio.resampler.psd.cutoff_percent" + + // Filter creation design parameters, see setSampleRate() + double mStopbandAttenuationDb = 0.; + double mPassbandRippleDb = 0.; + double mNormalizedTransitionBandwidth = 0.; + double mFilterAttenuation = 0.; + double mNormalizedCutoffFrequency = 0.; }; } // namespace android
diff --git a/media/libaudioprocessing/AudioResamplerFirGen.h b/media/libaudioprocessing/AudioResamplerFirGen.h index ad18965..39cafeb 100644 --- a/media/libaudioprocessing/AudioResamplerFirGen.h +++ b/media/libaudioprocessing/AudioResamplerFirGen.h
@@ -546,8 +546,9 @@ } wstart += wstep; } - // renormalize - this is only needed for integer filter types - double norm = 1./((1ULL<<(sizeof(T)*8-1))*L); + // renormalize - this is needed for integer filter types, use 1 for float or double. + constexpr int64_t integralShift = std::is_integral<T>::value ? (sizeof(T) * 8 - 1) : 0; + const double norm = 1. / (L << integralShift); firMin = fmin * norm; firMax = fmax * norm; @@ -557,9 +558,12 @@ * evaluates the |H(f)| lowpass band characteristics. * * This function tests the lowpass characteristics for the overall polyphase filter, - * and is used to verify the design. For this case, fp should be set to the + * and is used to verify the design. + * + * For a polyphase filter (L > 1), typically fp should be set to the * passband normalized frequency from 0 to 0.5 for the overall filter (thus it * is the designed polyphase bank value / L). Likewise for fs. + * Similarly the stopSteps should be L * passSteps for equivalent accuracy. * * @param coef is the designed polyphase filter banks * @@ -610,6 +614,74 @@ } /* + * Estimate the windowed sinc minimum passband value. + * + * This is the minimum value for a windowed sinc filter in its passband, + * which is identical to the scaling required not to cause overflow of a 0dBFS signal. + * The actual value used to attenuate the filter amplitude should be slightly + * smaller than this (suggest squaring) as this is just an estimate. + * + * As a windowed sinc has a passband ripple commensurate to the stopband attenuation + * due to Gibb's phenomenon from truncating the sinc, we derive this value from + * the design stopbandAttenuationDb (a positive value). + */ +static inline double computeWindowedSincMinimumPassbandValue( + double stopBandAttenuationDb) { + return 1. - pow(10. /* base */, stopBandAttenuationDb * (-1. / 20.)); +} + +/* + * Compute the windowed sinc passband ripple from stopband attenuation. + * + * As a windowed sinc has an passband ripple commensurate to the stopband attenuation + * due to Gibb's phenomenon from truncating the sinc, we derive this value from + * the design stopbandAttenuationDb (a positive value). + */ +static inline double computeWindowedSincPassbandRippleDb( + double stopBandAttenuationDb) { + return -20. * log10(computeWindowedSincMinimumPassbandValue(stopBandAttenuationDb)); +} + +/* + * Kaiser window Beta value + * + * Formula 3.2.5, 3.2.7, Vaidyanathan, _Multirate Systems and Filter Banks_, p. 48 + * Formula 7.75, Oppenheim and Schafer, _Discrete-time Signal Processing, 3e_, p. 542 + * + * See also: http://melodi.ee.washington.edu/courses/ee518/notes/lec17.pdf + * + * Kaiser window and beta parameter + * + * | 0.1102*(A - 8.7) A > 50 + * Beta = | 0.5842*(A - 21)^0.4 + 0.07886*(A - 21) 21 < A <= 50 + * | 0. A <= 21 + * + * with A is the desired stop-band attenuation in positive dBFS + * + * 30 dB 2.210 + * 40 dB 3.384 + * 50 dB 4.538 + * 60 dB 5.658 + * 70 dB 6.764 + * 80 dB 7.865 + * 90 dB 8.960 + * 100 dB 10.056 + * + * For some values of stopBandAttenuationDb the function may be computed + * at compile time. + */ +static inline constexpr double computeBeta(double stopBandAttenuationDb) { + if (stopBandAttenuationDb > 50.) { + return 0.1102 * (stopBandAttenuationDb - 8.7); + } + const double offset = stopBandAttenuationDb - 21.; + if (offset > 0.) { + return 0.5842 * pow(offset, 0.4) + 0.07886 * offset; + } + return 0.; +} + +/* * Calculates the overall polyphase filter based on a windowed sinc function. * * The windowed sinc is an odd length symmetric filter of exactly L*halfNumCoef*2+1 @@ -642,31 +714,8 @@ template <typename T> static inline void firKaiserGen(T* coef, int L, int halfNumCoef, double stopBandAtten, double fcr, double atten) { - // - // Formula 3.2.5, 3.2.7, Vaidyanathan, _Multirate Systems and Filter Banks_, p. 48 - // Formula 7.75, Oppenheim and Schafer, _Discrete-time Signal Processing, 3e_, p. 542 - // - // See also: http://melodi.ee.washington.edu/courses/ee518/notes/lec17.pdf - // - // Kaiser window and beta parameter - // - // | 0.1102*(A - 8.7) A > 50 - // beta = | 0.5842*(A - 21)^0.4 + 0.07886*(A - 21) 21 <= A <= 50 - // | 0. A < 21 - // - // with A is the desired stop-band attenuation in dBFS - // - // 30 dB 2.210 - // 40 dB 3.384 - // 50 dB 4.538 - // 60 dB 5.658 - // 70 dB 6.764 - // 80 dB 7.865 - // 90 dB 8.960 - // 100 dB 10.056 - const int N = L * halfNumCoef; // non-negative half - const double beta = 0.1102 * (stopBandAtten - 8.7); // >= 50dB always + const double beta = computeBeta(stopBandAtten); const double xstep = (2. * M_PI) * fcr / L; const double xfrac = 1. / N; const double yscale = atten * L / (I0(beta) * M_PI); @@ -696,9 +745,9 @@ sg.advance(); } - if (is_same<T, int16_t>::value) { // int16_t needs noise shaping + if (std::is_same<T, int16_t>::value) { // int16_t needs noise shaping *coef++ = static_cast<T>(toint(y, 1ULL<<(sizeof(T)*8-1), err)); - } else if (is_same<T, int32_t>::value) { + } else if (std::is_same<T, int32_t>::value) { *coef++ = static_cast<T>(toint(y, 1ULL<<(sizeof(T)*8-1))); } else { // assumed float or double *coef++ = static_cast<T>(y);
diff --git a/media/libaudioprocessing/BufferProviders.cpp b/media/libaudioprocessing/BufferProviders.cpp index 862fef6..2d9e1cb 100644 --- a/media/libaudioprocessing/BufferProviders.cpp +++ b/media/libaudioprocessing/BufferProviders.cpp
@@ -183,7 +183,7 @@ mOutFrameSize = audio_bytes_per_sample(format) * audio_channel_count_from_out_mask(outputChannelMask); status_t status; - status = EffectBufferHalInterface::mirror( + status = mEffectsFactory->mirrorBuffer( nullptr, mInFrameSize * bufferFrameCount, &mInBuffer); if (status != 0) { ALOGE("DownmixerBufferProvider() error %d while creating input buffer", status); @@ -191,7 +191,7 @@ mEffectsFactory.clear(); return; } - status = EffectBufferHalInterface::mirror( + status = mEffectsFactory->mirrorBuffer( nullptr, mOutFrameSize * bufferFrameCount, &mOutBuffer); if (status != 0) { ALOGE("DownmixerBufferProvider() error %d while creating output buffer", status); @@ -376,6 +376,23 @@ memcpy_by_audio_format(dst, mOutputFormat, src, mInputFormat, frames * mChannelCount); } +ClampFloatBufferProvider::ClampFloatBufferProvider(int32_t channelCount, size_t bufferFrameCount) : + CopyBufferProvider( + channelCount * audio_bytes_per_sample(AUDIO_FORMAT_PCM_FLOAT), + channelCount * audio_bytes_per_sample(AUDIO_FORMAT_PCM_FLOAT), + bufferFrameCount), + mChannelCount(channelCount) +{ + ALOGV("ClampFloatBufferProvider(%p)(%u)", this, channelCount); +} + +void ClampFloatBufferProvider::copyFrames(void *dst, const void *src, size_t frames) +{ + memcpy_to_float_from_float_with_clamping((float*)dst, (const float*)src, + frames * mChannelCount, + FLOAT_NOMINAL_RANGE_HEADROOM); +} + TimestretchBufferProvider::TimestretchBufferProvider(int32_t channelCount, audio_format_t format, uint32_t sampleRate, const AudioPlaybackRate &playbackRate) : mChannelCount(channelCount),
diff --git a/media/libaudioprocessing/OWNERS b/media/libaudioprocessing/OWNERS new file mode 100644 index 0000000..96d0ea0 --- /dev/null +++ b/media/libaudioprocessing/OWNERS
@@ -0,0 +1,3 @@ +gkasten@google.com +hunga@google.com +rago@google.com
diff --git a/media/libaudioprocessing/tests/build_and_run_all_unit_tests.sh b/media/libaudioprocessing/tests/build_and_run_all_unit_tests.sh index 704d095..efef417 100755 --- a/media/libaudioprocessing/tests/build_and_run_all_unit_tests.sh +++ b/media/libaudioprocessing/tests/build_and_run_all_unit_tests.sh
@@ -14,8 +14,8 @@ echo "waiting for device" adb root && adb wait-for-device remount -adb push $OUT/system/lib/libaudioresampler.so /system/lib -adb push $OUT/system/lib64/libaudioresampler.so /system/lib64 +adb push $OUT/system/lib/libaudioprocessing.so /system/lib +adb push $OUT/system/lib64/libaudioprocessing.so /system/lib64 adb push $OUT/data/nativetest/resampler_tests/resampler_tests /data/nativetest/resampler_tests/resampler_tests adb push $OUT/data/nativetest64/resampler_tests/resampler_tests /data/nativetest64/resampler_tests/resampler_tests
diff --git a/media/libaudioprocessing/tests/resampler_tests.cpp b/media/libaudioprocessing/tests/resampler_tests.cpp index a23c000..e1623f7 100644 --- a/media/libaudioprocessing/tests/resampler_tests.cpp +++ b/media/libaudioprocessing/tests/resampler_tests.cpp
@@ -29,6 +29,7 @@ #include <unistd.h> #include <iostream> +#include <memory> #include <utility> #include <vector> @@ -37,6 +38,8 @@ #include <media/AudioBufferProvider.h> #include <media/AudioResampler.h> +#include "../AudioResamplerDyn.h" +#include "../AudioResamplerFirGen.h" #include "test_utils.h" template <typename T> @@ -242,6 +245,60 @@ delete resampler; } +void testFilterResponse( + size_t channels, unsigned inputFreq, unsigned outputFreq) +{ + // create resampler + using ResamplerType = android::AudioResamplerDyn<float, float, float>; + std::unique_ptr<ResamplerType> rdyn( + static_cast<ResamplerType *>( + android::AudioResampler::create( + AUDIO_FORMAT_PCM_FLOAT, + channels, + outputFreq, + android::AudioResampler::DYN_HIGH_QUALITY))); + rdyn->setSampleRate(inputFreq); + + // get design parameters + const int phases = rdyn->getPhases(); + const int halfLength = rdyn->getHalfLength(); + const float *coefs = rdyn->getFilterCoefs(); + const double fcr = rdyn->getNormalizedCutoffFrequency(); + const double tbw = rdyn->getNormalizedTransitionBandwidth(); + const double attenuation = rdyn->getFilterAttenuation(); + const double stopbandDb = rdyn->getStopbandAttenuationDb(); + const double passbandDb = rdyn->getPassbandRippleDb(); + const double fp = fcr - tbw / 2; + const double fs = fcr + tbw / 2; + + printf("inputFreq:%d outputFreq:%d design" + " phases:%d halfLength:%d" + " fcr:%lf fp:%lf fs:%lf tbw:%lf" + " attenuation:%lf stopRipple:%.lf passRipple:%lf" + "\n", + inputFreq, outputFreq, + phases, halfLength, + fcr, fp, fs, tbw, + attenuation, stopbandDb, passbandDb); + + // verify design parameters + constexpr int32_t passSteps = 1000; + double passMin, passMax, passRipple, stopMax, stopRipple; + android::testFir(coefs, phases, halfLength, fp / phases, fs / phases, + passSteps, phases * passSteps /* stopSteps */, + passMin, passMax, passRipple, + stopMax, stopRipple); + printf("inputFreq:%d outputFreq:%d verify" + " passMin:%lf passMax:%lf passRipple:%lf stopMax:%lf stopRipple:%lf" + "\n", + inputFreq, outputFreq, + passMin, passMax, passRipple, stopMax, stopRipple); + + ASSERT_GT(stopRipple, 60.); // enough stopband attenuation + ASSERT_LT(passRipple, 0.2); // small passband ripple + ASSERT_GT(passMin, 0.99); // we do not attenuate the signal (ideally 1.) +} + /* Buffer increment test * * We compare a reference output, where we consume and process the entire @@ -484,3 +541,30 @@ } } +TEST(audioflinger_resampler, filterresponse) { + std::vector<int> inSampleRates{ + 8000, + 11025, + 12000, + 16000, + 22050, + 24000, + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, + }; + std::vector<int> outSampleRates{ + 48000, + 96000, + }; + + for (int outSampleRate : outSampleRates) { + for (int inSampleRate : inSampleRates) { + testFilterResponse(2 /* channels */, inSampleRate, outSampleRate); + } + } +}
diff --git a/media/libaudioprocessing/tests/test-mixer.cpp b/media/libaudioprocessing/tests/test-mixer.cpp index 75dbf91..bc9d2a6 100644 --- a/media/libaudioprocessing/tests/test-mixer.cpp +++ b/media/libaudioprocessing/tests/test-mixer.cpp
@@ -143,10 +143,6 @@ usage(progname); return EXIT_FAILURE; } - if ((unsigned)argc > AudioMixer::MAX_NUM_TRACKS) { - fprintf(stderr, "too many tracks: %d > %u", argc, AudioMixer::MAX_NUM_TRACKS); - return EXIT_FAILURE; - } size_t outputFrames = 0; @@ -246,9 +242,10 @@ for (size_t i = 0; i < providers.size(); ++i) { //printf("track %d out of %d\n", i, providers.size()); uint32_t channelMask = audio_channel_out_mask_from_count(providers[i].getNumChannels()); - int32_t name = mixer->getTrackName(channelMask, - formats[i], AUDIO_SESSION_OUTPUT_MIX); - ALOG_ASSERT(name >= 0); + const int name = i; + const status_t status = mixer->create( + name, channelMask, formats[i], AUDIO_SESSION_OUTPUT_MIX); + LOG_ALWAYS_FATAL_IF(status != OK); names[i] = name; mixer->setBufferProvider(name, &providers[i]); mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER, @@ -315,9 +312,10 @@ writeFile(outputFilename, outputAddr, outputSampleRate, outputChannels, outputFrames, useMixerFloat); if (auxFilename) { - // Aux buffer is always in q4_27 format for now. - // memcpy_to_i16_from_q4_27(), but with stereo frame count (not sample count) - ditherAndClamp((int32_t*)auxAddr, (int32_t*)auxAddr, outputFrames >> 1); + // Aux buffer is always in q4_27 format for O and earlier. + // memcpy_to_i16_from_q4_27((int16_t*)auxAddr, (const int32_t*)auxAddr, outputFrames); + // Aux buffer is always in float format for P. + memcpy_to_i16_from_float((int16_t*)auxAddr, (const float*)auxAddr, outputFrames); writeFile(auxFilename, auxAddr, outputSampleRate, 1, outputFrames, false); }
diff --git a/media/libcpustats/OWNERS b/media/libcpustats/OWNERS new file mode 100644 index 0000000..f9cb567 --- /dev/null +++ b/media/libcpustats/OWNERS
@@ -0,0 +1 @@ +gkasten@google.com
diff --git a/media/libeffects/OWNERS b/media/libeffects/OWNERS index 7e3de13..7f9ae81 100644 --- a/media/libeffects/OWNERS +++ b/media/libeffects/OWNERS
@@ -1,3 +1,4 @@ +hunga@google.com krocard@google.com mnaganov@google.com rago@google.com
diff --git a/media/libeffects/config/Android.bp b/media/libeffects/config/Android.bp index 4398a91..5fa9da9 100644 --- a/media/libeffects/config/Android.bp +++ b/media/libeffects/config/Android.bp
@@ -1,10 +1,15 @@ // Effect configuration -cc_library_shared { +cc_library { name: "libeffectsconfig", vendor_available: true, srcs: ["src/EffectsConfig.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ "liblog", "libtinyxml2",
diff --git a/media/libeffects/config/include/media/EffectsConfig.h b/media/libeffects/config/include/media/EffectsConfig.h index 811730c..fa0415b 100644 --- a/media/libeffects/config/include/media/EffectsConfig.h +++ b/media/libeffects/config/include/media/EffectsConfig.h
@@ -32,8 +32,13 @@ namespace android { namespace effectsConfig { -/** Default path of effect configuration file. */ -constexpr char DEFAULT_PATH[] = "/vendor/etc/audio_effects.xml"; +/** Default path of effect configuration file. Relative to DEFAULT_LOCATIONS. */ +constexpr const char* DEFAULT_NAME = "audio_effects.xml"; + +/** Default path of effect configuration file. + * The /vendor partition is the recommended one, the others are deprecated. + */ +constexpr const char* DEFAULT_LOCATIONS[] = {"/odm/etc", "/vendor/etc", "/system/etc"}; /** Directories where the effect libraries will be search for. */ constexpr const char* LD_EFFECT_LIBRARY_PATH[] = @@ -91,13 +96,16 @@ /** Parsed config, nullptr if the xml lib could not load the file */ std::unique_ptr<Config> parsedConfig; size_t nbSkippedElement; //< Number of skipped invalid library, effect or processing chain + const std::string configPath; //< Path to the loaded configuration }; /** Parses the provided effect configuration. * Parsing do not stop of first invalid element, but continues to the next. + * @param[in] path of the configuration file do load + * if nullptr, look for DEFAULT_NAME in DEFAULT_LOCATIONS. * @see ParsingResult::nbSkippedElement */ -ParsingResult parse(const char* path = DEFAULT_PATH); +ParsingResult parse(const char* path = nullptr); } // namespace effectsConfig } // namespace android
diff --git a/media/libeffects/config/src/EffectsConfig.cpp b/media/libeffects/config/src/EffectsConfig.cpp index 97462f8..351b1ee 100644 --- a/media/libeffects/config/src/EffectsConfig.cpp +++ b/media/libeffects/config/src/EffectsConfig.cpp
@@ -20,6 +20,7 @@ #include <cstdint> #include <functional> #include <string> +#include <unistd.h> #include <tinyxml2.h> #include <log/log.h> @@ -85,7 +86,7 @@ constexpr std::enable_if<false, Enum> STREAM_NAME_MAP; /** All output stream types which support effects. - * This need to be kept in sink with the xsd streamOutputType. + * This need to be kept in sync with the xsd streamOutputType. */ template <> constexpr std::pair<audio_stream_type_t, const char*> STREAM_NAME_MAP<audio_stream_type_t>[] = { @@ -102,7 +103,7 @@ }; /** All input stream types which support effects. - * This need to be kept in sink with the xsd streamOutputType. + * This need to be kept in sync with the xsd streamOutputType. */ template <> constexpr std::pair<audio_source_t, const char*> STREAM_NAME_MAP<audio_source_t>[] = { @@ -142,7 +143,7 @@ } /** Find an element in a collection by its name. - * @return nullptr if not found, the ellements address if found. + * @return nullptr if not found, the element address if found. */ template <class T> T* findByName(const char* name, std::vector<T>& collection) { @@ -202,7 +203,7 @@ auto parseProxy = [&xmlEffect, &parseImpl](const char* tag, EffectImpl& proxyLib) { auto* xmlProxyLib = xmlEffect.FirstChildElement(tag); if (xmlProxyLib == nullptr) { - ALOGE("effectProxy must contain a <%s>: %s", tag, dump(*xmlProxyLib)); + ALOGE("effectProxy must contain a <%s>: %s", tag, dump(xmlEffect)); return false; } return parseImpl(*xmlProxyLib, proxyLib); @@ -249,15 +250,14 @@ return true; } -}; // namespace - -ParsingResult parse(const char* path) { +/** Internal version of the public parse(const char* path) where path always exist. */ +ParsingResult parseWithPath(std::string&& path) { XMLDocument doc; - doc.LoadFile(path); + doc.LoadFile(path.c_str()); if (doc.Error()) { - ALOGE("Failed to parse %s: Tinyxml2 error (%d): %s %s", path, - doc.ErrorID(), doc.GetErrorStr1(), doc.GetErrorStr2()); - return {nullptr, 0}; + ALOGE("Failed to parse %s: Tinyxml2 error (%d): %s", path.c_str(), + doc.ErrorID(), doc.ErrorStr()); + return {nullptr, 0, std::move(path)}; } auto config = std::make_unique<Config>(); @@ -295,7 +295,29 @@ } } } - return {std::move(config), nbSkippedElements}; + return {std::move(config), nbSkippedElements, std::move(path)}; +} + +}; // namespace + +ParsingResult parse(const char* path) { + if (path != nullptr) { + return parseWithPath(path); + } + + for (std::string location : DEFAULT_LOCATIONS) { + std::string defaultPath = location + '/' + DEFAULT_NAME; + if (access(defaultPath.c_str(), R_OK) != 0) { + continue; + } + auto result = parseWithPath(std::move(defaultPath)); + if (result.parsedConfig != nullptr) { + return result; + } + } + + ALOGE("Could not parse effect configuration in any of the default locations."); + return {nullptr, 0, ""}; } } // namespace effectsConfig
diff --git a/media/libeffects/data/audio_effects.conf b/media/libeffects/data/audio_effects.conf index 14a171b..dd729c5 100644 --- a/media/libeffects/data/audio_effects.conf +++ b/media/libeffects/data/audio_effects.conf
@@ -38,6 +38,9 @@ loudness_enhancer { path /vendor/lib/soundfx/libldnhncr.so } + dynamics_processing { + path /vendor/lib/soundfx/libdynproc.so + } } # Default pre-processing library. Add to audio_effect.conf "libraries" section if @@ -129,6 +132,10 @@ library loudness_enhancer uuid fa415329-2034-4bea-b5dc-5b381c8d1e2c } + dynamics_processing { + library dynamics_processing + uuid e0e6539b-1781-7261-676f-6d7573696340 + } } # Default pre-processing effects. Add to audio_effect.conf "effects" section if
diff --git a/media/libeffects/data/audio_effects.xml b/media/libeffects/data/audio_effects.xml new file mode 100644 index 0000000..3f85052 --- /dev/null +++ b/media/libeffects/data/audio_effects.xml
@@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<audio_effects_conf version="2.0" xmlns="http://schemas.android.com/audio/audio_effects_conf/v2_0"> + <!-- List of effect libraries to load. + Each library element must contain a "name" attribute and a "path" attribute giving the + name of a library .so file in /vendor/lib/soundfx on the target + + If offloadable effects are present, the AOSP library libeffectproxy.so must be listed as + well as one library for the SW implementation and one library for the DSP implementation: + <library name="proxy" path="libeffectproxy.so"/> + <library name="some_fx_sw" path="lib_some_fx_sw.so"/> + <library name="some_fx_hw" path="lib_some_fx_hw.so"/> + + If the audio HAL implements support for AOSP software audio pre-processing effects, + the following library must be added: + <library name="pre_processing" path="libaudiopreprocessing.so"/> + --> + <libraries> + <library name="bundle" path="libbundlewrapper.so"/> + <library name="reverb" path="libreverbwrapper.so"/> + <library name="visualizer" path="libvisualizer.so"/> + <library name="downmix" path="libdownmix.so"/> + <library name="loudness_enhancer" path="libldnhncr.so"/> + <library name="dynamics_processing" path="libdynproc.so"/> + </libraries> + + <!-- list of effects to load. + Each "effect" element must contain a "name", "library" and a "uuid" attribute. + The value of the "library" element must correspond to the name of one library element in + the "libraries" element. + The "name" attribute is indicative, only the value of the "uuid" attribute designates + the effect. + The uuid is the implementation specific UUID as specified by the effect vendor. This is not + the generic effect type UUID. + + Offloadable effects are described by an "effectProxy" element which contains one "libsw" + element containing the "uuid" and "library" for the SW implementation and one "libhw" + element containing the "uuid" and "library" for the DSP implementation. + The "uuid" value for the "effectProxy" element must be unique and will override the default + uuid in the AOSP proxy effect implementation. + + If the audio HAL implements support for AOSP software audio pre-processing effects, + the following effects can be added: + <effect name="agc" library="pre_processing" uuid="aa8130e0-66fc-11e0-bad0-0002a5d5c51b"/> + <effect name="aec" library="pre_processing" uuid="bb392ec0-8d4d-11e0-a896-0002a5d5c51b"/> + <effect name="ns" library="pre_processing" uuid="c06c8400-8e06-11e0-9cb6-0002a5d5c51b"/> + --> + + <effects> + <effect name="bassboost" library="bundle" uuid="8631f300-72e2-11df-b57e-0002a5d5c51b"/> + <effect name="virtualizer" library="bundle" uuid="1d4033c0-8557-11df-9f2d-0002a5d5c51b"/> + <effect name="equalizer" library="bundle" uuid="ce772f20-847d-11df-bb17-0002a5d5c51b"/> + <effect name="volume" library="bundle" uuid="119341a0-8469-11df-81f9-0002a5d5c51b"/> + <effect name="reverb_env_aux" library="reverb" uuid="4a387fc0-8ab3-11df-8bad-0002a5d5c51b"/> + <effect name="reverb_env_ins" library="reverb" uuid="c7a511a0-a3bb-11df-860e-0002a5d5c51b"/> + <effect name="reverb_pre_aux" library="reverb" uuid="f29a1400-a3bb-11df-8ddc-0002a5d5c51b"/> + <effect name="reverb_pre_ins" library="reverb" uuid="172cdf00-a3bc-11df-a72f-0002a5d5c51b"/> + <effect name="visualizer" library="visualizer" uuid="d069d9e0-8329-11df-9168-0002a5d5c51b"/> + <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"/> + </effects> + + <!-- Audio pre processor configurations. + The pre processor configuration is described in a "preprocess" element and consists in a + list of elements each describing pre processor settings for a given use case or "stream". + Each stream element has a "type" attribute corresponding to the input source used. + Valid types are: + "mic", "camcorder", "voice_recognition", "voice_communication" + Each "stream" element contains a list of "apply" elements indicating one effect to apply. + The effect to apply is designated by its name in the "effects" elements. + + <preprocess> + <stream type="voice_communication"> + <apply effect="aec"/> + <apply effect="ns"/> + </stream> + </preprocess> + --> + + <!-- Audio post processor configurations. + The post processor configuration is described in a "postprocess" element and consists in a + list of elements each describing post processor settings for a given use case or "stream". + Each stream element has a "type" attribute corresponding to the stream type used. + Valid types are: + "music", "ring", "alarm", "notification", "voice_call" + Each "stream" element contains a list of "apply" elements indicating one effect to apply. + The effect to apply is designated by its name in the "effects" elements. + + <postprocess> + <stream type="music"> + <apply effect="music_post_proc"/> + </stream> + <stream type="voice_call"> + <apply effect="voice_post_proc"/> + </stream> + <stream type="notification"> + <apply effect="notification_post_proc"/> + </stream> + </postprocess> + --> + +</audio_effects_conf>
diff --git a/media/libeffects/dynamicsproc/Android.mk b/media/libeffects/dynamicsproc/Android.mk new file mode 100644 index 0000000..7be0c49 --- /dev/null +++ b/media/libeffects/dynamicsproc/Android.mk
@@ -0,0 +1,43 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +# DynamicsProcessing library +include $(CLEAR_VARS) + +LOCAL_VENDOR_MODULE := true + +EIGEN_PATH := external/eigen +LOCAL_C_INCLUDES += $(EIGEN_PATH) + +LOCAL_SRC_FILES:= \ + EffectDynamicsProcessing.cpp \ + dsp/DPBase.cpp \ + dsp/DPFrequency.cpp + +LOCAL_CFLAGS+= -O2 -fvisibility=hidden +LOCAL_CFLAGS += -Wall -Werror + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + liblog \ + +LOCAL_MODULE_RELATIVE_PATH := soundfx +LOCAL_MODULE:= libdynproc + +LOCAL_HEADER_LIBRARIES := \ + libaudioeffects + +include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libeffects/dynamicsproc/EffectDynamicsProcessing.cpp b/media/libeffects/dynamicsproc/EffectDynamicsProcessing.cpp new file mode 100644 index 0000000..0b883f1 --- /dev/null +++ b/media/libeffects/dynamicsproc/EffectDynamicsProcessing.cpp
@@ -0,0 +1,1300 @@ +/* + * 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 "EffectDP" +//#define LOG_NDEBUG 0 + +#include <assert.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <new> + +#include <log/log.h> + +#include <audio_effects/effect_dynamicsprocessing.h> +#include <dsp/DPBase.h> +#include <dsp/DPFrequency.h> + +//#define VERY_VERY_VERBOSE_LOGGING +#ifdef VERY_VERY_VERBOSE_LOGGING +#define ALOGVV ALOGV +#else +#define ALOGVV(a...) do { } while (false) +#endif + +// union to hold command values +using value_t = union { + int32_t i; + float f; +}; + +// effect_handle_t interface implementation for DP effect +extern const struct effect_interface_s gDPInterface; + +// AOSP Dynamics Processing UUID: e0e6539b-1781-7261-676f-6d7573696340 +const effect_descriptor_t gDPDescriptor = { + {0x7261676f, 0x6d75, 0x7369, 0x6364, {0x28, 0xe2, 0xfd, 0x3a, 0xc3, 0x9e}}, // type + {0xe0e6539b, 0x1781, 0x7261, 0x676f, {0x6d, 0x75, 0x73, 0x69, 0x63, 0x40}}, // uuid + EFFECT_CONTROL_API_VERSION, + (EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_LAST | EFFECT_FLAG_VOLUME_CTRL), + 0, // TODO + 1, + "Dynamics Processing", + "The Android Open Source Project", +}; + +enum dp_state_e { + DYNAMICS_PROCESSING_STATE_UNINITIALIZED, + DYNAMICS_PROCESSING_STATE_INITIALIZED, + DYNAMICS_PROCESSING_STATE_ACTIVE, +}; + +struct DynamicsProcessingContext { + const struct effect_interface_s *mItfe; + effect_config_t mConfig; + uint8_t mState; + + dp_fx::DPBase * mPDynamics; //the effect (or current effect) + int32_t mCurrentVariant; + float mPreferredFrameDuration; +}; + +// The value offset of an effect parameter is computed by rounding up +// the parameter size to the next 32 bit alignment. +static inline uint32_t computeParamVOffset(const effect_param_t *p) { + return ((p->psize + sizeof(int32_t) - 1) / sizeof(int32_t)) * + sizeof(int32_t); +} + +//--- local function prototypes +int DP_setParameter(DynamicsProcessingContext *pContext, + uint32_t paramSize, + void *pParam, + uint32_t valueSize, + void *pValue); +int DP_getParameter(DynamicsProcessingContext *pContext, + uint32_t paramSize, + void *pParam, + uint32_t *pValueSize, + void *pValue); +int DP_getParameterCmdSize(uint32_t paramSize, + void *pParam); +void DP_expectedParamValueSizes(uint32_t paramSize, + void *pParam, + bool isSet, + uint32_t *pCmdSize, + uint32_t *pValueSize); +// +//--- Local functions (not directly used by effect interface) +// + +void DP_reset(DynamicsProcessingContext *pContext) +{ + ALOGV("> DP_reset(%p)", pContext); + if (pContext->mPDynamics != NULL) { + pContext->mPDynamics->reset(); + } else { + ALOGE("DP_reset(%p): null DynamicsProcessing", pContext); + } +} + +//---------------------------------------------------------------------------- +// DP_setConfig() +//---------------------------------------------------------------------------- +// Purpose: Set input and output audio configuration. +// +// Inputs: +// pContext: effect engine context +// pConfig: pointer to effect_config_t structure holding input and output +// configuration parameters +// +// Outputs: +// +//---------------------------------------------------------------------------- + +int DP_setConfig(DynamicsProcessingContext *pContext, effect_config_t *pConfig) +{ + ALOGV("DP_setConfig(%p)", pContext); + + if (pConfig->inputCfg.samplingRate != pConfig->outputCfg.samplingRate) return -EINVAL; + if (pConfig->inputCfg.channels != pConfig->outputCfg.channels) return -EINVAL; + if (pConfig->inputCfg.format != pConfig->outputCfg.format) return -EINVAL; + if (pConfig->outputCfg.accessMode != EFFECT_BUFFER_ACCESS_WRITE && + pConfig->outputCfg.accessMode != EFFECT_BUFFER_ACCESS_ACCUMULATE) return -EINVAL; + if (pConfig->inputCfg.format != AUDIO_FORMAT_PCM_FLOAT) return -EINVAL; + + pContext->mConfig = *pConfig; + + DP_reset(pContext); + + return 0; +} + +//---------------------------------------------------------------------------- +// DP_getConfig() +//---------------------------------------------------------------------------- +// Purpose: Get input and output audio configuration. +// +// Inputs: +// pContext: effect engine context +// pConfig: pointer to effect_config_t structure holding input and output +// configuration parameters +// +// Outputs: +// +//---------------------------------------------------------------------------- + +void DP_getConfig(DynamicsProcessingContext *pContext, effect_config_t *pConfig) +{ + *pConfig = pContext->mConfig; +} + +//---------------------------------------------------------------------------- +// DP_init() +//---------------------------------------------------------------------------- +// Purpose: Initialize engine with default configuration. +// +// Inputs: +// pContext: effect engine context +// +// Outputs: +// +//---------------------------------------------------------------------------- + +int DP_init(DynamicsProcessingContext *pContext) +{ + ALOGV("DP_init(%p)", pContext); + + pContext->mItfe = &gDPInterface; + pContext->mPDynamics = NULL; + pContext->mState = DYNAMICS_PROCESSING_STATE_UNINITIALIZED; + + pContext->mConfig.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ; + pContext->mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_STEREO; + pContext->mConfig.inputCfg.format = AUDIO_FORMAT_PCM_FLOAT; + pContext->mConfig.inputCfg.samplingRate = 48000; + pContext->mConfig.inputCfg.bufferProvider.getBuffer = NULL; + pContext->mConfig.inputCfg.bufferProvider.releaseBuffer = NULL; + pContext->mConfig.inputCfg.bufferProvider.cookie = NULL; + pContext->mConfig.inputCfg.mask = EFFECT_CONFIG_ALL; + pContext->mConfig.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE; + pContext->mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO; + pContext->mConfig.outputCfg.format = AUDIO_FORMAT_PCM_FLOAT; + pContext->mConfig.outputCfg.samplingRate = 48000; + pContext->mConfig.outputCfg.bufferProvider.getBuffer = NULL; + pContext->mConfig.outputCfg.bufferProvider.releaseBuffer = NULL; + pContext->mConfig.outputCfg.bufferProvider.cookie = NULL; + pContext->mConfig.outputCfg.mask = EFFECT_CONFIG_ALL; + + pContext->mCurrentVariant = -1; //none + pContext->mPreferredFrameDuration = 0; //none + + DP_setConfig(pContext, &pContext->mConfig); + pContext->mState = DYNAMICS_PROCESSING_STATE_INITIALIZED; + return 0; +} + +void DP_changeVariant(DynamicsProcessingContext *pContext, int newVariant) { + ALOGV("DP_changeVariant from %d to %d", pContext->mCurrentVariant, newVariant); + switch(newVariant) { + case VARIANT_FAVOR_FREQUENCY_RESOLUTION: { + pContext->mCurrentVariant = VARIANT_FAVOR_FREQUENCY_RESOLUTION; + delete pContext->mPDynamics; + pContext->mPDynamics = new dp_fx::DPFrequency(); + break; + } + default: { + ALOGW("DynamicsProcessing variant %d not available for creation", newVariant); + break; + } + } //switch +} + +static inline bool isPowerOf2(unsigned long n) { + return (n & (n - 1)) == 0; +} + +void DP_configureVariant(DynamicsProcessingContext *pContext, int newVariant) { + ALOGV("DP_configureVariant %d", newVariant); + switch(newVariant) { + case VARIANT_FAVOR_FREQUENCY_RESOLUTION: { + int32_t minBlockSize = (int32_t)dp_fx::DPFrequency::getMinBockSize(); + int32_t desiredBlock = pContext->mPreferredFrameDuration * + pContext->mConfig.inputCfg.samplingRate / 1000.0f; + int32_t currentBlock = desiredBlock; + ALOGV(" sampling rate: %d, desiredBlock size %0.2f (%d) samples", + pContext->mConfig.inputCfg.samplingRate, pContext->mPreferredFrameDuration, + desiredBlock); + if (desiredBlock < minBlockSize) { + currentBlock = minBlockSize; + } else if (!isPowerOf2(desiredBlock)) { + //find next highest power of 2. + currentBlock = 1 << (32 - __builtin_clz(desiredBlock)); + } + ((dp_fx::DPFrequency*)pContext->mPDynamics)->configure(currentBlock, + currentBlock/2, + pContext->mConfig.inputCfg.samplingRate); + break; + } + default: { + ALOGE("DynamicsProcessing variant %d not available to configure", newVariant); + break; + } + } +} + +// +//--- Effect Library Interface Implementation +// + +int DPLib_Release(effect_handle_t handle) { + DynamicsProcessingContext * pContext = (DynamicsProcessingContext *)handle; + + ALOGV("DPLib_Release %p", handle); + if (pContext == NULL) { + return -EINVAL; + } + delete pContext->mPDynamics; + delete pContext; + + return 0; +} + +int DPLib_Create(const effect_uuid_t *uuid, + int32_t sessionId __unused, + int32_t ioId __unused, + effect_handle_t *pHandle) { + ALOGV("DPLib_Create()"); + + if (pHandle == NULL || uuid == NULL) { + return -EINVAL; + } + + if (memcmp(uuid, &gDPDescriptor.uuid, sizeof(*uuid)) != 0) { + return -EINVAL; + } + + DynamicsProcessingContext *pContext = new DynamicsProcessingContext; + *pHandle = (effect_handle_t)pContext; + int ret = DP_init(pContext); + if (ret < 0) { + ALOGW("DPLib_Create() init failed"); + DPLib_Release(*pHandle); + return ret; + } + + ALOGV("DPLib_Create context is %p", pContext); + return 0; +} + +int DPLib_GetDescriptor(const effect_uuid_t *uuid, + effect_descriptor_t *pDescriptor) { + + if (pDescriptor == NULL || uuid == NULL){ + ALOGE("DPLib_GetDescriptor() called with NULL pointer"); + return -EINVAL; + } + + if (memcmp(uuid, &gDPDescriptor.uuid, sizeof(*uuid)) == 0) { + *pDescriptor = gDPDescriptor; + return 0; + } + + return -EINVAL; +} /* end DPLib_GetDescriptor */ + +// +//--- Effect Control Interface Implementation +// +int DP_process(effect_handle_t self, audio_buffer_t *inBuffer, + audio_buffer_t *outBuffer) { + DynamicsProcessingContext * pContext = (DynamicsProcessingContext *)self; + + if (pContext == NULL) { + ALOGE("DP_process() called with NULL context"); + return -EINVAL; + } + + if (inBuffer == NULL || inBuffer->raw == NULL || + outBuffer == NULL || outBuffer->raw == NULL || + inBuffer->frameCount != outBuffer->frameCount || + inBuffer->frameCount == 0) { + ALOGE("inBuffer or outBuffer are NULL or have problems with frame count"); + return -EINVAL; + } + if (pContext->mState != DYNAMICS_PROCESSING_STATE_ACTIVE) { + ALOGE("mState is not DYNAMICS_PROCESSING_STATE_ACTIVE. Current mState %d", + pContext->mState); + return -ENODATA; + } + //if dynamics exist... + if (pContext->mPDynamics != NULL) { + int32_t channelCount = (int32_t)audio_channel_count_from_out_mask( + pContext->mConfig.inputCfg.channels); + pContext->mPDynamics->processSamples(inBuffer->f32, inBuffer->f32, + inBuffer->frameCount * channelCount); + + if (inBuffer->raw != outBuffer->raw) { + if (pContext->mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) { + for (size_t i = 0; i < outBuffer->frameCount * channelCount; i++) { + outBuffer->f32[i] += inBuffer->f32[i]; + } + } else { + memcpy(outBuffer->raw, inBuffer->raw, + outBuffer->frameCount * channelCount * sizeof(float)); + } + } + } else { + //do nothing. no effect created yet. warning. + ALOGW("Warning: no DynamicsProcessing engine available"); + return -EINVAL; + } + return 0; +} + +//helper function +bool DP_checkSizesInt(uint32_t paramSize, uint32_t valueSize, uint32_t expectedParams, + uint32_t expectedValues) { + if (paramSize < expectedParams * sizeof(int32_t)) { + ALOGE("Invalid paramSize: %u expected %u", paramSize, + (uint32_t)(expectedParams * sizeof(int32_t))); + return false; + } + if (valueSize < expectedValues * sizeof(int32_t)) { + ALOGE("Invalid valueSize %u expected %u", valueSize, + (uint32_t)(expectedValues * sizeof(int32_t))); + return false; + } + return true; +} + +static dp_fx::DPChannel* DP_getChannel(DynamicsProcessingContext *pContext, + int32_t channel) { + if (pContext->mPDynamics == NULL) { + return NULL; + } + dp_fx::DPChannel *pChannel = pContext->mPDynamics->getChannel(channel); + ALOGE_IF(pChannel == NULL, "DPChannel NULL. invalid channel %d", channel); + return pChannel; +} + +static dp_fx::DPEq* DP_getEq(DynamicsProcessingContext *pContext, int32_t channel, + int32_t eqType) { + dp_fx::DPChannel *pChannel = DP_getChannel(pContext, channel); + if (pChannel == NULL) { + return NULL; + } + dp_fx::DPEq *pEq = (eqType == DP_PARAM_PRE_EQ ? pChannel->getPreEq() : + (eqType == DP_PARAM_POST_EQ ? pChannel->getPostEq() : NULL)); + ALOGE_IF(pEq == NULL,"DPEq NULL invalid eq"); + return pEq; +} + +static dp_fx::DPEqBand* DP_getEqBand(DynamicsProcessingContext *pContext, int32_t channel, + int32_t eqType, int32_t band) { + dp_fx::DPEq *pEq = DP_getEq(pContext, channel, eqType); + if (pEq == NULL) { + return NULL; + } + dp_fx::DPEqBand *pEqBand = pEq->getBand(band); + ALOGE_IF(pEqBand == NULL, "DPEqBand NULL. invalid band %d", band); + return pEqBand; +} + +static dp_fx::DPMbc* DP_getMbc(DynamicsProcessingContext *pContext, int32_t channel) { + dp_fx::DPChannel * pChannel = DP_getChannel(pContext, channel); + if (pChannel == NULL) { + return NULL; + } + dp_fx::DPMbc *pMbc = pChannel->getMbc(); + ALOGE_IF(pMbc == NULL, "DPMbc NULL invalid MBC"); + return pMbc; +} + +static dp_fx::DPMbcBand* DP_getMbcBand(DynamicsProcessingContext *pContext, int32_t channel, + int32_t band) { + dp_fx::DPMbc *pMbc = DP_getMbc(pContext, channel); + if (pMbc == NULL) { + return NULL; + } + dp_fx::DPMbcBand *pMbcBand = pMbc->getBand(band); + ALOGE_IF(pMbcBand == NULL, "pMbcBand NULL. invalid band %d", band); + return pMbcBand; +} + +int DP_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, + void *pCmdData, uint32_t *replySize, void *pReplyData) { + + DynamicsProcessingContext * pContext = (DynamicsProcessingContext *)self; + + if (pContext == NULL || pContext->mState == DYNAMICS_PROCESSING_STATE_UNINITIALIZED) { + ALOGE("DP_command() called with NULL context or uninitialized state."); + return -EINVAL; + } + + ALOGV("DP_command command %d cmdSize %d",cmdCode, cmdSize); + switch (cmdCode) { + case EFFECT_CMD_INIT: + if (pReplyData == NULL || *replySize != sizeof(int)) { + ALOGE("EFFECT_CMD_INIT wrong replyData or repySize"); + return -EINVAL; + } + *(int *) pReplyData = DP_init(pContext); + break; + case EFFECT_CMD_SET_CONFIG: + if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) + || pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) { + ALOGE("EFFECT_CMD_SET_CONFIG error with pCmdData, cmdSize, pReplyData or replySize"); + return -EINVAL; + } + *(int *) pReplyData = DP_setConfig(pContext, + (effect_config_t *) pCmdData); + break; + case EFFECT_CMD_GET_CONFIG: + if (pReplyData == NULL || + *replySize != sizeof(effect_config_t)) { + ALOGE("EFFECT_CMD_GET_CONFIG wrong replyData or repySize"); + return -EINVAL; + } + DP_getConfig(pContext, (effect_config_t *)pReplyData); + break; + case EFFECT_CMD_RESET: + DP_reset(pContext); + break; + case EFFECT_CMD_ENABLE: + if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) { + ALOGE("EFFECT_CMD_ENABLE wrong replyData or repySize"); + return -EINVAL; + } + if (pContext->mState != DYNAMICS_PROCESSING_STATE_INITIALIZED) { + ALOGE("EFFECT_CMD_ENABLE state not initialized"); + *(int *)pReplyData = -ENOSYS; + } else { + pContext->mState = DYNAMICS_PROCESSING_STATE_ACTIVE; + ALOGV("EFFECT_CMD_ENABLE() OK"); + *(int *)pReplyData = 0; + } + break; + case EFFECT_CMD_DISABLE: + if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) { + ALOGE("EFFECT_CMD_DISABLE wrong replyData or repySize"); + return -EINVAL; + } + if (pContext->mState != DYNAMICS_PROCESSING_STATE_ACTIVE) { + ALOGE("EFFECT_CMD_DISABLE state not active"); + *(int *)pReplyData = -ENOSYS; + } else { + pContext->mState = DYNAMICS_PROCESSING_STATE_INITIALIZED; + ALOGV("EFFECT_CMD_DISABLE() OK"); + *(int *)pReplyData = 0; + } + break; + case EFFECT_CMD_GET_PARAM: { + if (pCmdData == NULL || pReplyData == NULL || replySize == NULL) { + ALOGE("null pCmdData or pReplyData or replySize"); + return -EINVAL; + } + effect_param_t *pEffectParam = (effect_param_t *) pCmdData; + uint32_t expectedCmdSize = DP_getParameterCmdSize(pEffectParam->psize, + pEffectParam->data); + if (cmdSize != expectedCmdSize || *replySize < expectedCmdSize) { + ALOGE("error cmdSize: %d, expetedCmdSize: %d, replySize: %d", + cmdSize, expectedCmdSize, *replySize); + return -EINVAL; + } + + ALOGVV("DP_command expectedCmdSize: %d", expectedCmdSize); + memcpy(pReplyData, pCmdData, expectedCmdSize); + effect_param_t *p = (effect_param_t *)pReplyData; + + uint32_t voffset = computeParamVOffset(p); + + p->status = DP_getParameter(pContext, + p->psize, + p->data, + &p->vsize, + p->data + voffset); + *replySize = sizeof(effect_param_t) + voffset + p->vsize; + + ALOGVV("DP_command replysize %u, status %d" , *replySize, p->status); + break; + } + case EFFECT_CMD_SET_PARAM: { + if (pCmdData == NULL || + cmdSize < (sizeof(effect_param_t) + sizeof(int32_t) + sizeof(int32_t)) || + pReplyData == NULL || replySize == NULL || *replySize != sizeof(int32_t)) { + ALOGE("\tLVM_ERROR : DynamicsProcessing cmdCode Case: " + "EFFECT_CMD_SET_PARAM: ERROR"); + return -EINVAL; + } + + effect_param_t * const p = (effect_param_t *) pCmdData; + const uint32_t voffset = computeParamVOffset(p); + + *(int *)pReplyData = DP_setParameter(pContext, + p->psize, + (void *)p->data, + p->vsize, + p->data + voffset); + break; + } + case EFFECT_CMD_SET_VOLUME: { + ALOGV("EFFECT_CMD_SET_VOLUME"); + // if pReplyData is NULL, VOL_CTRL is delegated to another effect + if (pReplyData == NULL || replySize == NULL || *replySize < ((int)sizeof(int32_t) * 2)) { + ALOGV("no VOLUME data to return"); + break; + } + if (pCmdData == NULL || cmdSize < ((int)sizeof(uint32_t) * 2)) { + ALOGE("\tLVM_ERROR : DynamicsProcessing EFFECT_CMD_SET_VOLUME ERROR"); + return -EINVAL; + } + + const int32_t unityGain = 1 << 24; + //channel count + int32_t channelCount = (int32_t)audio_channel_count_from_out_mask( + pContext->mConfig.inputCfg.channels); + for (int32_t ch = 0; ch < channelCount; ch++) { + + dp_fx::DPChannel * pChannel = DP_getChannel(pContext, ch); + if (pChannel == NULL) { + ALOGE("%s EFFECT_CMD_SET_VOLUME invalid channel %d", __func__, ch); + return -EINVAL; + break; + } + + int32_t offset = ch; + if (ch > 1) { + // FIXME: limited to 2 unique channels. If more channels present, use value for + // first channel + offset = 0; + } + const float gain = (float)*((uint32_t *)pCmdData + offset) / unityGain; + const float gainDb = linearToDb(gain); + ALOGVV("%s EFFECT_CMD_SET_VOLUME channel %d, engine outputlevel %f (%0.2f dB)", + __func__, ch, gain, gainDb); + pChannel->setOutputGain(gainDb); + } + + const int32_t volRet[2] = {unityGain, unityGain}; // Apply no volume before effect. + memcpy(pReplyData, volRet, sizeof(volRet)); + break; + } + case EFFECT_CMD_SET_DEVICE: + case EFFECT_CMD_SET_AUDIO_MODE: + break; + + default: + ALOGW("DP_command invalid command %d",cmdCode); + return -EINVAL; + } + + return 0; +} + +//register expected cmd size +int DP_getParameterCmdSize(uint32_t paramSize, + void *pParam) { + if (paramSize < sizeof(int32_t)) { + return 0; + } + int32_t param = *(int32_t*)pParam; + switch(param) { + case DP_PARAM_GET_CHANNEL_COUNT: //paramcmd + case DP_PARAM_ENGINE_ARCHITECTURE: + //effect + param + return (int)(sizeof(effect_param_t) + sizeof(uint32_t)); + case DP_PARAM_INPUT_GAIN: //paramcmd + param + case DP_PARAM_LIMITER: + case DP_PARAM_PRE_EQ: + case DP_PARAM_POST_EQ: + case DP_PARAM_MBC: + //effect + param + return (int)(sizeof(effect_param_t) + 2 * sizeof(uint32_t)); + case DP_PARAM_PRE_EQ_BAND: + case DP_PARAM_POST_EQ_BAND: + case DP_PARAM_MBC_BAND: + return (int)(sizeof(effect_param_t) + 3 * sizeof(uint32_t)); + } + return 0; +} + +int DP_getParameter(DynamicsProcessingContext *pContext, + uint32_t paramSize, + void *pParam, + uint32_t *pValueSize, + void *pValue) { + int status = 0; + int32_t *params = (int32_t *)pParam; + static_assert(sizeof(float) == sizeof(int32_t) && sizeof(float) == sizeof(value_t) && + alignof(float) == alignof(int32_t) && alignof(float) == alignof(value_t), + "Size/alignment mismatch for float/int32_t/value_t"); + value_t *values = reinterpret_cast<value_t*>(pValue); + + ALOGVV("%s start", __func__); +#ifdef VERY_VERY_VERBOSE_LOGGING + for (size_t i = 0; i < paramSize/sizeof(int32_t); i++) { + ALOGVV("Param[%zu] %d", i, params[i]); + } +#endif + if (paramSize < sizeof(int32_t)) { + ALOGE("%s invalid paramSize: %u", __func__, paramSize); + return -EINVAL; + } + const int32_t command = params[0]; + switch (command) { + case DP_PARAM_GET_CHANNEL_COUNT: { + if (!DP_checkSizesInt(paramSize,*pValueSize, 1 /*params*/, 1 /*values*/)) { + ALOGE("%s DP_PARAM_GET_CHANNEL_COUNT (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } + *pValueSize = sizeof(uint32_t); + *(uint32_t *)pValue = (uint32_t)audio_channel_count_from_out_mask( + pContext->mConfig.inputCfg.channels); + ALOGVV("%s DP_PARAM_GET_CHANNEL_COUNT channels %d", __func__, *(int32_t *)pValue); + break; + } + case DP_PARAM_ENGINE_ARCHITECTURE: { + ALOGVV("engine architecture paramsize: %d valuesize %d",paramSize, *pValueSize); + if (!DP_checkSizesInt(paramSize, *pValueSize, 1 /*params*/, 9 /*values*/)) { + ALOGE("%s DP_PARAM_ENGINE_ARCHITECTURE (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = { PARAM_ENGINE_ARCHITECTURE }; +// Number[] values = { 0 /*0 variant */, +// 0.0f /* 1 preferredFrameDuration */, +// 0 /*2 preEqInUse */, +// 0 /*3 preEqBandCount */, +// 0 /*4 mbcInUse */, +// 0 /*5 mbcBandCount*/, +// 0 /*6 postEqInUse */, +// 0 /*7 postEqBandCount */, +// 0 /*8 limiterInUse */}; + if (pContext->mPDynamics == NULL) { + ALOGE("%s DP_PARAM_ENGINE_ARCHITECTURE error mPDynamics is NULL", __func__); + status = -EINVAL; + break; + } + values[0].i = pContext->mCurrentVariant; + values[1].f = pContext->mPreferredFrameDuration; + values[2].i = pContext->mPDynamics->isPreEQInUse(); + values[3].i = pContext->mPDynamics->getPreEqBandCount(); + values[4].i = pContext->mPDynamics->isMbcInUse(); + values[5].i = pContext->mPDynamics->getMbcBandCount(); + values[6].i = pContext->mPDynamics->isPostEqInUse(); + values[7].i = pContext->mPDynamics->getPostEqBandCount(); + values[8].i = pContext->mPDynamics->isLimiterInUse(); + + *pValueSize = sizeof(value_t) * 9; + + ALOGVV(" variant %d, preferredFrameDuration: %f, preEqInuse %d, bands %d, mbcinuse %d," + "mbcbands %d, posteqInUse %d, bands %d, limiterinuse %d", + values[0].i, values[1].f, values[2].i, values[3].i, values[4].i, values[5].i, + values[6].i, values[7].i, values[8].i); + break; + } + case DP_PARAM_INPUT_GAIN: { + ALOGVV("engine get PARAM_INPUT_GAIN paramsize: %d valuesize %d",paramSize, *pValueSize); + if (!DP_checkSizesInt(paramSize, *pValueSize, 2 /*params*/, 1 /*values*/)) { + ALOGE("%s get PARAM_INPUT_GAIN invalid sizes.", __func__); + status = -EINVAL; + break; + } + + const int32_t channel = params[1]; + dp_fx::DPChannel * pChannel = DP_getChannel(pContext, channel); + if (pChannel == NULL) { + ALOGE("%s get PARAM_INPUT_GAIN invalid channel %d", __func__, channel); + status = -EINVAL; + break; + } + values[0].f = pChannel->getInputGain(); + *pValueSize = sizeof(value_t) * 1; + + ALOGVV(" channel: %d, input gain %f\n", channel, values[0].f); + break; + } + case DP_PARAM_PRE_EQ: + case DP_PARAM_POST_EQ: { + ALOGVV("engine get PARAM_*_EQ paramsize: %d valuesize %d",paramSize, *pValueSize); + if (!DP_checkSizesInt(paramSize, *pValueSize, 2 /*params*/, 3 /*values*/)) { + ALOGE("%s get PARAM_*_EQ (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = {paramSet == PARAM_PRE_EQ ? PARAM_PRE_EQ : PARAM_POST_EQ, +// channelIndex}; +// Number[] values = {0 /*0 in use */, +// 0 /*1 enabled*/, +// 0 /*2 band count */}; + const int32_t channel = params[1]; + + dp_fx::DPEq *pEq = DP_getEq(pContext, channel, command); + if (pEq == NULL) { + ALOGE("%s get PARAM_*_EQ invalid eq", __func__); + status = -EINVAL; + break; + } + values[0].i = pEq->isInUse(); + values[1].i = pEq->isEnabled(); + values[2].i = pEq->getBandCount(); + *pValueSize = sizeof(value_t) * 3; + + ALOGVV(" %s channel: %d, inUse::%d, enabled:%d, bandCount:%d\n", + (command == DP_PARAM_PRE_EQ ? "preEq" : "postEq"), channel, + values[0].i, values[1].i, values[2].i); + break; + } + case DP_PARAM_PRE_EQ_BAND: + case DP_PARAM_POST_EQ_BAND: { + ALOGVV("engine get PARAM_*_EQ_BAND paramsize: %d valuesize %d",paramSize, *pValueSize); + if (!DP_checkSizesInt(paramSize, *pValueSize, 3 /*params*/, 3 /*values*/)) { + ALOGE("%s get PARAM_*_EQ_BAND (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = {paramSet, +// channelIndex, +// bandIndex}; +// Number[] values = {(eqBand.isEnabled() ? 1 : 0), +// eqBand.getCutoffFrequency(), +// eqBand.getGain()}; + const int32_t channel = params[1]; + const int32_t band = params[2]; + int eqCommand = (command == DP_PARAM_PRE_EQ_BAND ? DP_PARAM_PRE_EQ : + (command == DP_PARAM_POST_EQ_BAND ? DP_PARAM_POST_EQ : -1)); + + dp_fx::DPEqBand *pEqBand = DP_getEqBand(pContext, channel, eqCommand, band); + if (pEqBand == NULL) { + ALOGE("%s get PARAM_*_EQ_BAND invalid channel %d or band %d", __func__, channel, band); + status = -EINVAL; + break; + } + + values[0].i = pEqBand->isEnabled(); + values[1].f = pEqBand->getCutoffFrequency(); + values[2].f = pEqBand->getGain(); + *pValueSize = sizeof(value_t) * 3; + + ALOGVV("%s channel: %d, band::%d, enabled:%d, cutoffFrequency:%f, gain%f\n", + (command == DP_PARAM_PRE_EQ_BAND ? "preEqBand" : "postEqBand"), channel, band, + values[0].i, values[1].f, values[2].f); + break; + } + case DP_PARAM_MBC: { + ALOGVV("engine get PDP_PARAM_MBC paramsize: %d valuesize %d",paramSize, *pValueSize); + if (!DP_checkSizesInt(paramSize, *pValueSize, 2 /*params*/, 3 /*values*/)) { + ALOGE("%s get PDP_PARAM_MBC (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } + +// Number[] params = {PARAM_MBC, +// channelIndex}; +// Number[] values = {0 /*0 in use */, +// 0 /*1 enabled*/, +// 0 /*2 band count */}; + + const int32_t channel = params[1]; + + dp_fx::DPMbc *pMbc = DP_getMbc(pContext, channel); + if (pMbc == NULL) { + ALOGE("%s get PDP_PARAM_MBC invalid MBC", __func__); + status = -EINVAL; + break; + } + + values[0].i = pMbc->isInUse(); + values[1].i = pMbc->isEnabled(); + values[2].i = pMbc->getBandCount(); + *pValueSize = sizeof(value_t) * 3; + + ALOGVV("DP_PARAM_MBC channel: %d, inUse::%d, enabled:%d, bandCount:%d\n", channel, + values[0].i, values[1].i, values[2].i); + break; + } + case DP_PARAM_MBC_BAND: { + ALOGVV("engine get DP_PARAM_MBC_BAND paramsize: %d valuesize %d",paramSize, *pValueSize); + if (!DP_checkSizesInt(paramSize, *pValueSize, 3 /*params*/, 11 /*values*/)) { + ALOGE("%s get DP_PARAM_MBC_BAND (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = {PARAM_MBC_BAND, +// channelIndex, +// bandIndex}; +// Number[] values = {0 /*0 enabled */, +// 0.0f /*1 cutoffFrequency */, +// 0.0f /*2 AttackTime */, +// 0.0f /*3 ReleaseTime */, +// 0.0f /*4 Ratio */, +// 0.0f /*5 Threshold */, +// 0.0f /*6 KneeWidth */, +// 0.0f /*7 NoiseGateThreshold */, +// 0.0f /*8 ExpanderRatio */, +// 0.0f /*9 PreGain */, +// 0.0f /*10 PostGain*/}; + + const int32_t channel = params[1]; + const int32_t band = params[2]; + + dp_fx::DPMbcBand *pMbcBand = DP_getMbcBand(pContext, channel, band); + if (pMbcBand == NULL) { + ALOGE("%s get PARAM_MBC_BAND invalid channel %d or band %d", __func__, channel, band); + status = -EINVAL; + break; + } + + values[0].i = pMbcBand->isEnabled(); + values[1].f = pMbcBand->getCutoffFrequency(); + values[2].f = pMbcBand->getAttackTime(); + values[3].f = pMbcBand->getReleaseTime(); + values[4].f = pMbcBand->getRatio(); + values[5].f = pMbcBand->getThreshold(); + values[6].f = pMbcBand->getKneeWidth(); + values[7].f = pMbcBand->getNoiseGateThreshold(); + values[8].f = pMbcBand->getExpanderRatio(); + values[9].f = pMbcBand->getPreGain(); + values[10].f = pMbcBand->getPostGain(); + + *pValueSize = sizeof(value_t) * 11; + ALOGVV(" mbcBand channel: %d, band::%d, enabled:%d, cutoffFrequency:%f, attackTime:%f," + "releaseTime:%f, ratio:%f, threshold:%f, kneeWidth:%f, noiseGateThreshold:%f," + "expanderRatio:%f, preGain:%f, postGain:%f\n", channel, band, values[0].i, + values[1].f, values[2].f, values[3].f, values[4].f, values[5].f, values[6].f, + values[7].f, values[8].f, values[9].f, values[10].f); + break; + } + case DP_PARAM_LIMITER: { + ALOGVV("engine get DP_PARAM_LIMITER paramsize: %d valuesize %d",paramSize, *pValueSize); + if (!DP_checkSizesInt(paramSize, *pValueSize, 2 /*params*/, 8 /*values*/)) { + ALOGE("%s DP_PARAM_LIMITER (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } + + int32_t channel = params[1]; +// Number[] values = {0 /*0 in use (int)*/, +// 0 /*1 enabled (int)*/, +// 0 /*2 link group (int)*/, +// 0.0f /*3 attack time (float)*/, +// 0.0f /*4 release time (float)*/, +// 0.0f /*5 ratio (float)*/, +// 0.0f /*6 threshold (float)*/, +// 0.0f /*7 post gain(float)*/}; + dp_fx::DPChannel * pChannel = DP_getChannel(pContext, channel); + if (pChannel == NULL) { + ALOGE("%s DP_PARAM_LIMITER invalid channel %d", __func__, channel); + status = -EINVAL; + break; + } + dp_fx::DPLimiter *pLimiter = pChannel->getLimiter(); + if (pLimiter == NULL) { + ALOGE("%s DP_PARAM_LIMITER null LIMITER", __func__); + status = -EINVAL; + break; + } + values[0].i = pLimiter->isInUse(); + values[1].i = pLimiter->isEnabled(); + values[2].i = pLimiter->getLinkGroup(); + values[3].f = pLimiter->getAttackTime(); + values[4].f = pLimiter->getReleaseTime(); + values[5].f = pLimiter->getRatio(); + values[6].f = pLimiter->getThreshold(); + values[7].f = pLimiter->getPostGain(); + + *pValueSize = sizeof(value_t) * 8; + + ALOGVV(" Limiter channel: %d, inUse::%d, enabled:%d, linkgroup:%d attackTime:%f," + "releaseTime:%f, ratio:%f, threshold:%f, postGain:%f\n", + channel, values[0].i/*inUse*/, values[1].i/*enabled*/, values[2].i/*linkGroup*/, + values[3].f/*attackTime*/, values[4].f/*releaseTime*/, + values[5].f/*ratio*/, values[6].f/*threshold*/, + values[7].f/*postGain*/); + break; + } + default: + ALOGE("%s invalid param %d", __func__, params[0]); + status = -EINVAL; + break; + } + + ALOGVV("%s end param: %d, status: %d", __func__, params[0], status); + return status; +} /* end DP_getParameter */ + +int DP_setParameter(DynamicsProcessingContext *pContext, + uint32_t paramSize, + void *pParam, + uint32_t valueSize, + void *pValue) { + int status = 0; + int32_t *params = (int32_t *)pParam; + static_assert(sizeof(float) == sizeof(int32_t) && sizeof(float) == sizeof(value_t) && + alignof(float) == alignof(int32_t) && alignof(float) == alignof(value_t), + "Size/alignment mismatch for float/int32_t/value_t"); + value_t *values = reinterpret_cast<value_t*>(pValue); + + ALOGVV("%s start", __func__); + if (paramSize < sizeof(int32_t)) { + ALOGE("%s invalid paramSize: %u", __func__, paramSize); + return -EINVAL; + } + const int32_t command = params[0]; + switch (command) { + case DP_PARAM_ENGINE_ARCHITECTURE: { + ALOGVV("engine architecture paramsize: %d valuesize %d",paramSize, valueSize); + if (!DP_checkSizesInt(paramSize, valueSize, 1 /*params*/, 9 /*values*/)) { + ALOGE("%s DP_PARAM_ENGINE_ARCHITECTURE (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = { PARAM_ENGINE_ARCHITECTURE }; +// Number[] values = { variant /* variant */, +// preferredFrameDuration, +// (preEqInUse ? 1 : 0), +// preEqBandCount, +// (mbcInUse ? 1 : 0), +// mbcBandCount, +// (postEqInUse ? 1 : 0), +// postEqBandCount, +// (limiterInUse ? 1 : 0)}; + const int32_t variant = values[0].i; + const float preferredFrameDuration = values[1].f; + const int32_t preEqInUse = values[2].i; + const int32_t preEqBandCount = values[3].i; + const int32_t mbcInUse = values[4].i; + const int32_t mbcBandCount = values[5].i; + const int32_t postEqInUse = values[6].i; + const int32_t postEqBandCount = values[7].i; + const int32_t limiterInUse = values[8].i; + ALOGVV("variant %d, preEqInuse %d, bands %d, mbcinuse %d, mbcbands %d, posteqInUse %d," + "bands %d, limiterinuse %d", variant, preEqInUse, preEqBandCount, mbcInUse, + mbcBandCount, postEqInUse, postEqBandCount, limiterInUse); + + //set variant (instantiate effect) + //initArchitecture for effect + DP_changeVariant(pContext, variant); + if (pContext->mPDynamics == NULL) { + ALOGE("%s DP_PARAM_ENGINE_ARCHITECTURE error setting variant %d", __func__, variant); + status = -EINVAL; + break; + } + pContext->mPreferredFrameDuration = preferredFrameDuration; + pContext->mPDynamics->init((uint32_t)audio_channel_count_from_out_mask( + pContext->mConfig.inputCfg.channels), + preEqInUse != 0, (uint32_t)preEqBandCount, + mbcInUse != 0, (uint32_t)mbcBandCount, + postEqInUse != 0, (uint32_t)postEqBandCount, + limiterInUse != 0); + + DP_configureVariant(pContext, variant); + break; + } + case DP_PARAM_INPUT_GAIN: { + ALOGVV("engine DP_PARAM_INPUT_GAIN paramsize: %d valuesize %d",paramSize, valueSize); + if (!DP_checkSizesInt(paramSize, valueSize, 2 /*params*/, 1 /*values*/)) { + ALOGE("%s DP_PARAM_INPUT_GAIN invalid sizes.", __func__); + status = -EINVAL; + break; + } + + const int32_t channel = params[1]; + dp_fx::DPChannel * pChannel = DP_getChannel(pContext, channel); + if (pChannel == NULL) { + ALOGE("%s DP_PARAM_INPUT_GAIN invalid channel %d", __func__, channel); + status = -EINVAL; + break; + } + const float gain = values[0].f; + ALOGVV("%s DP_PARAM_INPUT_GAIN channel %d, level %f", __func__, channel, gain); + pChannel->setInputGain(gain); + break; + } + case DP_PARAM_PRE_EQ: + case DP_PARAM_POST_EQ: { + ALOGVV("engine DP_PARAM_*_EQ paramsize: %d valuesize %d",paramSize, valueSize); + if (!DP_checkSizesInt(paramSize, valueSize, 2 /*params*/, 3 /*values*/)) { + ALOGE("%s DP_PARAM_*_EQ (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = {paramSet, +// channelIndex}; +// Number[] values = { (eq.isInUse() ? 1 : 0), +// (eq.isEnabled() ? 1 : 0), +// bandCount}; + const int32_t channel = params[1]; + + const int32_t enabled = values[1].i; + const int32_t bandCount = values[2].i; + ALOGVV(" %s channel: %d, inUse::%d, enabled:%d, bandCount:%d\n", + (command == DP_PARAM_PRE_EQ ? "preEq" : "postEq"), channel, values[0].i, + values[2].i, bandCount); + + dp_fx::DPEq *pEq = DP_getEq(pContext, channel, command); + if (pEq == NULL) { + ALOGE("%s set PARAM_*_EQ invalid channel %d or command %d", __func__, channel, + command); + status = -EINVAL; + break; + } + + pEq->setEnabled(enabled != 0); + //fail if bandcountis different? maybe. + if ((int32_t)pEq->getBandCount() != bandCount) { + ALOGW("%s warning, trying to set different bandcount from %d to %d", __func__, + pEq->getBandCount(), bandCount); + } + break; + } + case DP_PARAM_PRE_EQ_BAND: + case DP_PARAM_POST_EQ_BAND: { + ALOGVV("engine set PARAM_*_EQ_BAND paramsize: %d valuesize %d",paramSize, valueSize); + if (!DP_checkSizesInt(paramSize, valueSize, 3 /*params*/, 3 /*values*/)) { + ALOGE("%s PARAM_*_EQ_BAND (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] values = { channelIndex, +// bandIndex, +// (eqBand.isEnabled() ? 1 : 0), +// eqBand.getCutoffFrequency(), +// eqBand.getGain()}; + +// Number[] params = {paramSet, +// channelIndex, +// bandIndex}; +// Number[] values = {(eqBand.isEnabled() ? 1 : 0), +// eqBand.getCutoffFrequency(), +// eqBand.getGain()}; + + const int32_t channel = params[1]; + const int32_t band = params[2]; + + const int32_t enabled = values[0].i; + const float cutoffFrequency = values[1].f; + const float gain = values[2].f; + + + ALOGVV(" %s channel: %d, band::%d, enabled:%d, cutoffFrequency:%f, gain%f\n", + (command == DP_PARAM_PRE_EQ_BAND ? "preEqBand" : "postEqBand"), channel, band, + enabled, cutoffFrequency, gain); + + int eqCommand = (command == DP_PARAM_PRE_EQ_BAND ? DP_PARAM_PRE_EQ : + (command == DP_PARAM_POST_EQ_BAND ? DP_PARAM_POST_EQ : -1)); + dp_fx::DPEq *pEq = DP_getEq(pContext, channel, eqCommand); + if (pEq == NULL) { + ALOGE("%s set PARAM_*_EQ_BAND invalid channel %d or command %d", __func__, channel, + command); + status = -EINVAL; + break; + } + + dp_fx::DPEqBand eqBand; + eqBand.init(enabled != 0, cutoffFrequency, gain); + pEq->setBand(band, eqBand); + break; + } + case DP_PARAM_MBC: { + ALOGVV("engine DP_PARAM_MBC paramsize: %d valuesize %d",paramSize, valueSize); + if (!DP_checkSizesInt(paramSize, valueSize, 2 /*params*/, 3 /*values*/)) { + ALOGE("%s DP_PARAM_MBC (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = { PARAM_MBC, +// channelIndex}; +// Number[] values = {(mbc.isInUse() ? 1 : 0), +// (mbc.isEnabled() ? 1 : 0), +// bandCount}; + const int32_t channel = params[1]; + + const int32_t enabled = values[1].i; + const int32_t bandCount = values[2].i; + ALOGVV("MBC channel: %d, inUse::%d, enabled:%d, bandCount:%d\n", channel, values[0].i, + enabled, bandCount); + + dp_fx::DPMbc *pMbc = DP_getMbc(pContext, channel); + if (pMbc == NULL) { + ALOGE("%s set DP_PARAM_MBC invalid channel %d ", __func__, channel); + status = -EINVAL; + break; + } + + pMbc->setEnabled(enabled != 0); + //fail if bandcountis different? maybe. + if ((int32_t)pMbc->getBandCount() != bandCount) { + ALOGW("%s warning, trying to set different bandcount from %d to %d", __func__, + pMbc->getBandCount(), bandCount); + } + break; + } + case DP_PARAM_MBC_BAND: { + ALOGVV("engine set DP_PARAM_MBC_BAND paramsize: %d valuesize %d ",paramSize, valueSize); + if (!DP_checkSizesInt(paramSize, valueSize, 3 /*params*/, 11 /*values*/)) { + ALOGE("%s DP_PARAM_MBC_BAND: (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = { PARAM_MBC_BAND, +// channelIndex, +// bandIndex}; +// Number[] values = {(mbcBand.isEnabled() ? 1 : 0), +// mbcBand.getCutoffFrequency(), +// mbcBand.getAttackTime(), +// mbcBand.getReleaseTime(), +// mbcBand.getRatio(), +// mbcBand.getThreshold(), +// mbcBand.getKneeWidth(), +// mbcBand.getNoiseGateThreshold(), +// mbcBand.getExpanderRatio(), +// mbcBand.getPreGain(), +// mbcBand.getPostGain()}; + + const int32_t channel = params[1]; + const int32_t band = params[2]; + + const int32_t enabled = values[0].i; + const float cutoffFrequency = values[1].f; + const float attackTime = values[2].f; + const float releaseTime = values[3].f; + const float ratio = values[4].f; + const float threshold = values[5].f; + const float kneeWidth = values[6].f; + const float noiseGateThreshold = values[7].f; + const float expanderRatio = values[8].f; + const float preGain = values[9].f; + const float postGain = values[10].f; + + ALOGVV(" mbcBand channel: %d, band::%d, enabled:%d, cutoffFrequency:%f, attackTime:%f," + "releaseTime:%f, ratio:%f, threshold:%f, kneeWidth:%f, noiseGateThreshold:%f," + "expanderRatio:%f, preGain:%f, postGain:%f\n", + channel, band, enabled, cutoffFrequency, attackTime, releaseTime, ratio, + threshold, kneeWidth, noiseGateThreshold, expanderRatio, preGain, postGain); + + dp_fx::DPMbc *pMbc = DP_getMbc(pContext, channel); + if (pMbc == NULL) { + ALOGE("%s set DP_PARAM_MBC_BAND invalid channel %d", __func__, channel); + status = -EINVAL; + break; + } + + dp_fx::DPMbcBand mbcBand; + mbcBand.init(enabled != 0, cutoffFrequency, attackTime, releaseTime, ratio, threshold, + kneeWidth, noiseGateThreshold, expanderRatio, preGain, postGain); + pMbc->setBand(band, mbcBand); + break; + } + case DP_PARAM_LIMITER: { + ALOGVV("engine DP_PARAM_LIMITER paramsize: %d valuesize %d",paramSize, valueSize); + if (!DP_checkSizesInt(paramSize, valueSize, 2 /*params*/, 8 /*values*/)) { + ALOGE("%s DP_PARAM_LIMITER (cmd %d) invalid sizes.", __func__, command); + status = -EINVAL; + break; + } +// Number[] params = { PARAM_LIMITER, +// channelIndex}; +// Number[] values = {(limiter.isInUse() ? 1 : 0), +// (limiter.isEnabled() ? 1 : 0), +// limiter.getLinkGroup(), +// limiter.getAttackTime(), +// limiter.getReleaseTime(), +// limiter.getRatio(), +// limiter.getThreshold(), +// limiter.getPostGain()}; + + const int32_t channel = params[1]; + + const int32_t inUse = values[0].i; + const int32_t enabled = values[1].i; + const int32_t linkGroup = values[2].i; + const float attackTime = values[3].f; + const float releaseTime = values[4].f; + const float ratio = values[5].f; + const float threshold = values[6].f; + const float postGain = values[7].f; + + ALOGVV(" Limiter channel: %d, inUse::%d, enabled:%d, linkgroup:%d attackTime:%f," + "releaseTime:%f, ratio:%f, threshold:%f, postGain:%f\n", channel, inUse, + enabled, linkGroup, attackTime, releaseTime, ratio, threshold, postGain); + + dp_fx::DPChannel * pChannel = DP_getChannel(pContext, channel); + if (pChannel == NULL) { + ALOGE("%s DP_PARAM_LIMITER invalid channel %d", __func__, channel); + status = -EINVAL; + break; + } + dp_fx::DPLimiter limiter; + limiter.init(inUse != 0, enabled != 0, linkGroup, attackTime, releaseTime, ratio, + threshold, postGain); + pChannel->setLimiter(limiter); + break; + } + default: + ALOGE("%s invalid param %d", __func__, params[0]); + status = -EINVAL; + break; + } + + ALOGVV("%s end param: %d, status: %d", __func__, params[0], status); + return status; +} /* end DP_setParameter */ + +/* Effect Control Interface Implementation: get_descriptor */ +int DP_getDescriptor(effect_handle_t self, + effect_descriptor_t *pDescriptor) +{ + DynamicsProcessingContext * pContext = (DynamicsProcessingContext *) self; + + if (pContext == NULL || pDescriptor == NULL) { + ALOGE("DP_getDescriptor() invalid param"); + return -EINVAL; + } + + *pDescriptor = gDPDescriptor; + + return 0; +} /* end DP_getDescriptor */ + + +// effect_handle_t interface implementation for Dynamics Processing effect +const struct effect_interface_s gDPInterface = { + DP_process, + DP_command, + DP_getDescriptor, + NULL, +}; + +extern "C" { +// 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 = "Dynamics Processing Library", + .implementor = "The Android Open Source Project", + .create_effect = DPLib_Create, + .release_effect = DPLib_Release, + .get_descriptor = DPLib_GetDescriptor, +}; + +}; // extern "C" +
diff --git a/media/libstagefright/matroska/MODULE_LICENSE_APACHE2 b/media/libeffects/dynamicsproc/MODULE_LICENSE_APACHE2 similarity index 100% rename from media/libstagefright/matroska/MODULE_LICENSE_APACHE2 rename to media/libeffects/dynamicsproc/MODULE_LICENSE_APACHE2
diff --git a/media/libeffects/dynamicsproc/NOTICE b/media/libeffects/dynamicsproc/NOTICE new file mode 100644 index 0000000..31cc6e9 --- /dev/null +++ b/media/libeffects/dynamicsproc/NOTICE
@@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +
diff --git a/media/libeffects/dynamicsproc/dsp/DPBase.cpp b/media/libeffects/dynamicsproc/dsp/DPBase.cpp new file mode 100644 index 0000000..ac758e0 --- /dev/null +++ b/media/libeffects/dynamicsproc/dsp/DPBase.cpp
@@ -0,0 +1,265 @@ +/* + * 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 "DPBase" +//#define LOG_NDEBUG 0 + +#include <log/log.h> +#include "DPBase.h" +#include "DPFrequency.h" + +namespace dp_fx { + +DPStage::DPStage() : mInUse(DP_DEFAULT_STAGE_INUSE), + mEnabled(DP_DEFAULT_STAGE_ENABLED) { +} + +void DPStage::init(bool inUse, bool enabled) { + mInUse = inUse; + mEnabled = enabled; +} + +//---- +DPBandStage::DPBandStage() : mBandCount(0) { +} + +void DPBandStage::init(bool inUse, bool enabled, int bandCount) { + DPStage::init(inUse, enabled); + mBandCount = inUse ? bandCount : 0; +} + +//--- +DPBandBase::DPBandBase() { + init(DP_DEFAULT_BAND_ENABLED, + DP_DEFAULT_BAND_CUTOFF_FREQUENCY_HZ); +} + +void DPBandBase::init(bool enabled, float cutoffFrequency){ + mEnabled = enabled; + mCutoofFrequencyHz = cutoffFrequency; +} + +//----- +DPEqBand::DPEqBand() { + init(DP_DEFAULT_BAND_ENABLED, + DP_DEFAULT_BAND_CUTOFF_FREQUENCY_HZ, + DP_DEFAULT_GAIN_DB); +} + +void DPEqBand::init(bool enabled, float cutoffFrequency, float gain) { + DPBandBase::init(enabled, cutoffFrequency); + setGain(gain); +} + +float DPEqBand::getGain() const{ + return mGainDb; +} + +void DPEqBand::setGain(float gain) { + mGainDb = gain; +} + +//------ +DPMbcBand::DPMbcBand() { + init(DP_DEFAULT_BAND_ENABLED, + DP_DEFAULT_BAND_CUTOFF_FREQUENCY_HZ, + DP_DEFAULT_ATTACK_TIME_MS, + DP_DEFAULT_RELEASE_TIME_MS, + DP_DEFAULT_RATIO, + DP_DEFAULT_THRESHOLD_DB, + DP_DEFAULT_KNEE_WIDTH_DB, + DP_DEFAULT_NOISE_GATE_THRESHOLD_DB, + DP_DEFAULT_EXPANDER_RATIO, + DP_DEFAULT_GAIN_DB, + DP_DEFAULT_GAIN_DB); +} + +void DPMbcBand::init(bool enabled, float cutoffFrequency, float attackTime, float releaseTime, + float ratio, float threshold, float kneeWidth, float noiseGateThreshold, + float expanderRatio, float preGain, float postGain) { + DPBandBase::init(enabled, cutoffFrequency); + setAttackTime(attackTime); + setReleaseTime(releaseTime); + setRatio(ratio); + setThreshold(threshold); + setKneeWidth(kneeWidth); + setNoiseGateThreshold(noiseGateThreshold); + setExpanderRatio(expanderRatio); + setPreGain(preGain); + setPostGain(postGain); +} + +//------ +DPEq::DPEq() { +} + +void DPEq::init(bool inUse, bool enabled, uint32_t bandCount) { + DPBandStage::init(inUse, enabled, bandCount); + mBands.resize(getBandCount()); +} + +DPEqBand * DPEq::getBand(uint32_t band) { + if (band < getBandCount()) { + return &mBands[band]; + } + return NULL; +} + +void DPEq::setBand(uint32_t band, DPEqBand &src) { + if (band < getBandCount()) { + mBands[band] = src; + } +} + +//------ +DPMbc::DPMbc() { +} + +void DPMbc::init(bool inUse, bool enabled, uint32_t bandCount) { + DPBandStage::init(inUse, enabled, bandCount); + if (isInUse()) { + mBands.resize(bandCount); + } else { + mBands.resize(0); + } +} + +DPMbcBand * DPMbc::getBand(uint32_t band) { + if (band < getBandCount()) { + return &mBands[band]; + } + return NULL; +} + +void DPMbc::setBand(uint32_t band, DPMbcBand &src) { + if (band < getBandCount()) { + mBands[band] = src; + } +} + +//------ +DPLimiter::DPLimiter() { + init(DP_DEFAULT_STAGE_INUSE, + DP_DEFAULT_STAGE_ENABLED, + DP_DEFAULT_LINK_GROUP, + DP_DEFAULT_ATTACK_TIME_MS, + DP_DEFAULT_RELEASE_TIME_MS, + DP_DEFAULT_RATIO, + DP_DEFAULT_THRESHOLD_DB, + DP_DEFAULT_GAIN_DB); +} + +void DPLimiter::init(bool inUse, bool enabled, uint32_t linkGroup, float attackTime, float releaseTime, + float ratio, float threshold, float postGain) { + DPStage::init(inUse, enabled); + setLinkGroup(linkGroup); + setAttackTime(attackTime); + setReleaseTime(releaseTime); + setRatio(ratio); + setThreshold(threshold); + setPostGain(postGain); +} + +//---- +DPChannel::DPChannel() : mInitialized(false), mInputGainDb(0), mOutputGainDb(0), + mPreEqInUse(false), mMbcInUse(false), mPostEqInUse(false), mLimiterInUse(false) { +} + +void DPChannel::init(float inputGain, bool preEqInUse, uint32_t preEqBandCount, + bool mbcInUse, uint32_t mbcBandCount, bool postEqInUse, uint32_t postEqBandCount, + bool limiterInUse) { + setInputGain(inputGain); + mPreEqInUse = preEqInUse; + mMbcInUse = mbcInUse; + mPostEqInUse = postEqInUse; + mLimiterInUse = limiterInUse; + + mPreEq.init(mPreEqInUse, false, preEqBandCount); + mMbc.init(mMbcInUse, false, mbcBandCount); + mPostEq.init(mPostEqInUse, false, postEqBandCount); + mLimiter.init(mLimiterInUse, false, 0, 50, 120, 2, -30, 0); + mInitialized = true; +} + +DPEq* DPChannel::getPreEq() { + if (!mInitialized) { + return NULL; + } + return &mPreEq; +} + +DPMbc* DPChannel::getMbc() { + if (!mInitialized) { + return NULL; + } + return &mMbc; +} + +DPEq* DPChannel::getPostEq() { + if (!mInitialized) { + return NULL; + } + return &mPostEq; +} + +DPLimiter* DPChannel::getLimiter() { + if (!mInitialized) { + return NULL; + } + return &mLimiter; +} + +void DPChannel::setLimiter(DPLimiter &limiter) { + if (!mInitialized) { + return; + } + mLimiter = limiter; +} + +//---- +DPBase::DPBase() : mInitialized(false), mChannelCount(0), mPreEqInUse(false), mPreEqBandCount(0), + mMbcInUse(false), mMbcBandCount(0), mPostEqInUse(false), mPostEqBandCount(0), + mLimiterInUse(false) { +} + +void DPBase::init(uint32_t channelCount, bool preEqInUse, uint32_t preEqBandCount, + bool mbcInUse, uint32_t mbcBandCount, bool postEqInUse, uint32_t postEqBandCount, + bool limiterInUse) { + ALOGV("DPBase::init"); + mChannelCount = channelCount; + mPreEqInUse = preEqInUse; + mPreEqBandCount = preEqBandCount; + mMbcInUse = mbcInUse; + mMbcBandCount = mbcBandCount; + mPostEqInUse = postEqInUse; + mPostEqBandCount = postEqBandCount; + mLimiterInUse = limiterInUse; + mChannel.resize(mChannelCount); + for (size_t ch = 0; ch < mChannelCount; ch++) { + mChannel[ch].init(0, preEqInUse, preEqBandCount, mbcInUse, mbcBandCount, + postEqInUse, postEqBandCount, limiterInUse); + } + mInitialized = true; +} + +DPChannel* DPBase::getChannel(uint32_t channelIndex) { + if (!mInitialized || channelIndex < 0 || channelIndex >= mChannel.size()) { + return NULL; + } + return & mChannel[channelIndex]; +} + +} //namespace dp_fx
diff --git a/media/libeffects/dynamicsproc/dsp/DPBase.h b/media/libeffects/dynamicsproc/dsp/DPBase.h new file mode 100644 index 0000000..e74f91d --- /dev/null +++ b/media/libeffects/dynamicsproc/dsp/DPBase.h
@@ -0,0 +1,362 @@ +/* + * 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 DPBASE_H_ +#define DPBASE_H_ + + +#include <stdint.h> +#include <cmath> +#include <vector> +#include <android/log.h> + +namespace dp_fx { + +#define DP_DEFAULT_BAND_ENABLED false +#define DP_DEFAULT_BAND_CUTOFF_FREQUENCY_HZ 1000 +#define DP_DEFAULT_ATTACK_TIME_MS 50 +#define DP_DEFAULT_RELEASE_TIME_MS 120 +#define DP_DEFAULT_RATIO 2 +#define DP_DEFAULT_THRESHOLD_DB -30 +#define DP_DEFAULT_KNEE_WIDTH_DB 0 +#define DP_DEFAULT_NOISE_GATE_THRESHOLD_DB -90 +#define DP_DEFAULT_EXPANDER_RATIO 1 +#define DP_DEFAULT_GAIN_DB 0 +#define DP_DEFAULT_STAGE_INUSE false +#define DP_DEFAULT_STAGE_ENABLED false +#define DP_DEFAULT_LINK_GROUP 0 + + + +class DPStage { +public: + DPStage(); + ~DPStage() = default; + void init(bool inUse, bool enabled); + bool isInUse() const { + return mInUse; + } + bool isEnabled() const { + return mEnabled; + } + void setEnabled(bool enabled) { + mEnabled = enabled; + } +private: + bool mInUse; + bool mEnabled; +}; + +class DPBandStage : public DPStage { +public: + DPBandStage(); + ~DPBandStage() = default; + void init(bool inUse, bool enabled, int bandCount); + uint32_t getBandCount() const { + return mBandCount; + } + void setBandCount(uint32_t bandCount) { + mBandCount = bandCount; + } +private: + uint32_t mBandCount; +}; + +class DPBandBase { +public: + DPBandBase(); + ~DPBandBase() = default; + void init(bool enabled, float cutoffFrequency); + bool isEnabled() const { + return mEnabled; + } + void setEnabled(bool enabled) { + mEnabled = enabled; + } + float getCutoffFrequency() const { + return mCutoofFrequencyHz; + } + void setCutoffFrequency(float cutoffFrequency) { + mCutoofFrequencyHz = cutoffFrequency; + } +private: + bool mEnabled; + float mCutoofFrequencyHz; +}; + +class DPEqBand : public DPBandBase { +public: + DPEqBand(); + ~DPEqBand() = default; + void init(bool enabled, float cutoffFrequency, float gain); + float getGain() const; + void setGain(float gain); +private: + float mGainDb; +}; + +class DPMbcBand : public DPBandBase { +public: + DPMbcBand(); + ~DPMbcBand() = default; + void init(bool enabled, float cutoffFrequency, float attackTime, float releaseTime, + float ratio, float threshold, float kneeWidth, float noiseGateThreshold, + float expanderRatio, float preGain, float postGain); + float getAttackTime() const { + return mAttackTimeMs; + } + void setAttackTime(float attackTime) { + mAttackTimeMs = attackTime; + } + float getReleaseTime() const { + return mReleaseTimeMs; + } + void setReleaseTime(float releaseTime) { + mReleaseTimeMs = releaseTime; + } + float getRatio() const { + return mRatio; + } + void setRatio(float ratio) { + mRatio = ratio; + } + float getThreshold() const { + return mThresholdDb; + } + void setThreshold(float threshold) { + mThresholdDb = threshold; + } + float getKneeWidth() const { + return mKneeWidthDb; + } + void setKneeWidth(float kneeWidth) { + mKneeWidthDb = kneeWidth; + } + float getNoiseGateThreshold() const { + return mNoiseGateThresholdDb; + } + void setNoiseGateThreshold(float noiseGateThreshold) { + mNoiseGateThresholdDb = noiseGateThreshold; + } + float getExpanderRatio() const { + return mExpanderRatio; + } + void setExpanderRatio(float expanderRatio) { + mExpanderRatio = expanderRatio; + } + float getPreGain() const { + return mPreGainDb; + } + void setPreGain(float preGain) { + mPreGainDb = preGain; + } + float getPostGain() const { + return mPostGainDb; + } + void setPostGain(float postGain) { + mPostGainDb = postGain; + } +private: + float mAttackTimeMs; + float mReleaseTimeMs; + float mRatio; + float mThresholdDb; + float mKneeWidthDb; + float mNoiseGateThresholdDb; + float mExpanderRatio; + float mPreGainDb; + float mPostGainDb; +}; + +class DPEq : public DPBandStage { +public: + DPEq(); + ~DPEq() = default; + void init(bool inUse, bool enabled, uint32_t bandCount); + DPEqBand * getBand(uint32_t band); + void setBand(uint32_t band, DPEqBand &src); +private: + std::vector<DPEqBand> mBands; +}; + +class DPMbc : public DPBandStage { +public: + DPMbc(); + ~DPMbc() = default; + void init(bool inUse, bool enabled, uint32_t bandCount); + DPMbcBand * getBand(uint32_t band); + void setBand(uint32_t band, DPMbcBand &src); +private: + std::vector<DPMbcBand> mBands; +}; + +class DPLimiter : public DPStage { +public: + DPLimiter(); + ~DPLimiter() = default; + void init(bool inUse, bool enabled, uint32_t linkGroup, float attackTime, float releaseTime, + float ratio, float threshold, float postGain); + uint32_t getLinkGroup() const { + return mLinkGroup; + } + void setLinkGroup(uint32_t linkGroup) { + mLinkGroup = linkGroup; + } + float getAttackTime() const { + return mAttackTimeMs; + } + void setAttackTime(float attackTime) { + mAttackTimeMs = attackTime; + } + float getReleaseTime() const { + return mReleaseTimeMs; + } + void setReleaseTime(float releaseTime) { + mReleaseTimeMs = releaseTime; + } + float getRatio() const { + return mRatio; + } + void setRatio(float ratio) { + mRatio = ratio; + } + float getThreshold() const { + return mThresholdDb; + } + void setThreshold(float threshold) { + mThresholdDb = threshold; + } + float getPostGain() const { + return mPostGainDb; + } + void setPostGain(float postGain) { + mPostGainDb = postGain; + } +private: + uint32_t mLinkGroup; + float mAttackTimeMs; + float mReleaseTimeMs; + float mRatio; + float mThresholdDb; + float mPostGainDb; +}; + +class DPChannel { +public: + DPChannel(); + ~DPChannel() = default; + void init(float inputGain, bool preEqInUse, uint32_t preEqBandCount, + bool mbcInUse, uint32_t mbcBandCount, bool postEqInUse, uint32_t postEqBandCount, + bool limiterInUse); + + float getInputGain() const { + if (!mInitialized) { + return 0; + } + return mInputGainDb; + } + void setInputGain(float gain) { + mInputGainDb = gain; + } + + float getOutputGain() const { + if (!mInitialized) { + return 0; + } + return mOutputGainDb; + } + void setOutputGain(float gain) { + mOutputGainDb = gain; + } + + DPEq* getPreEq(); + DPMbc* getMbc(); + DPEq* getPostEq(); + DPLimiter *getLimiter(); + void setLimiter(DPLimiter &limiter); + +private: + bool mInitialized; + float mInputGainDb; + float mOutputGainDb; + + DPEq mPreEq; + DPMbc mMbc; + DPEq mPostEq; + DPLimiter mLimiter; + + bool mPreEqInUse; + bool mMbcInUse; + bool mPostEqInUse; + bool mLimiterInUse; +}; + +class DPBase { +public: + DPBase(); + virtual ~DPBase() = default; + + void init(uint32_t channelCount, bool preEqInUse, uint32_t preEqBandCount, + bool mbcInUse, uint32_t mbcBandCount, bool postEqInUse, uint32_t postEqBandCount, + bool limiterInUse); + virtual size_t processSamples(const float *in, float *out, size_t samples) = 0; + virtual void reset() = 0; + + DPChannel* getChannel(uint32_t channelIndex); + uint32_t getChannelCount() const { + return mChannelCount; + } + uint32_t getPreEqBandCount() const { + return mPreEqBandCount; + } + uint32_t getMbcBandCount() const { + return mMbcBandCount; + } + uint32_t getPostEqBandCount() const { + return mPostEqBandCount; + } + bool isPreEQInUse() const { + return mPreEqInUse; + } + bool isMbcInUse() const { + return mMbcInUse; + } + bool isPostEqInUse() const { + return mPostEqInUse; + } + bool isLimiterInUse() const { + return mLimiterInUse; + } + +private: + bool mInitialized; + //general + uint32_t mChannelCount; + bool mPreEqInUse; + uint32_t mPreEqBandCount; + bool mMbcInUse; + uint32_t mMbcBandCount; + bool mPostEqInUse; + uint32_t mPostEqBandCount; + bool mLimiterInUse; + + std::vector<DPChannel> mChannel; +}; + +} //namespace dp_fx + + +#endif // DPBASE_H_
diff --git a/media/libeffects/dynamicsproc/dsp/DPFrequency.cpp b/media/libeffects/dynamicsproc/dsp/DPFrequency.cpp new file mode 100644 index 0000000..d06fd70 --- /dev/null +++ b/media/libeffects/dynamicsproc/dsp/DPFrequency.cpp
@@ -0,0 +1,675 @@ +/* + * 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 "DPFrequency" +//#define LOG_NDEBUG 0 + +#include <log/log.h> +#include "DPFrequency.h" +#include <algorithm> + +namespace dp_fx { + +using Eigen::MatrixXd; +#define MAX_BLOCKSIZE 16384 //For this implementation +#define MIN_BLOCKSIZE 8 + +#define CIRCULAR_BUFFER_UPSAMPLE 4 //4 times buffer size + +static constexpr float MIN_ENVELOPE = 1e-6f; //-120 dB +//helper functionS +static inline bool isPowerOf2(unsigned long n) { + return (n & (n - 1)) == 0; +} +static constexpr float EPSILON = 0.0000001f; + +static inline bool isZero(float f) { + return fabs(f) <= EPSILON; +} + +template <class T> +bool compareEquality(T a, T b) { + return (a == b); +} + +template <> bool compareEquality<float>(float a, float b) { + return isZero(a - b); +} + +//TODO: avoid using macro for estimating change and assignment. +#define IS_CHANGED(c, a, b) { c |= !compareEquality(a,b); \ + (a) = (b); } + +//ChannelBuffers helper +void ChannelBuffer::initBuffers(unsigned int blockSize, unsigned int overlapSize, + unsigned int halfFftSize, unsigned int samplingRate, DPBase &dpBase) { + ALOGV("ChannelBuffer::initBuffers blockSize %d, overlap %d, halfFft %d", + blockSize, overlapSize, halfFftSize); + + mSamplingRate = samplingRate; + mBlockSize = blockSize; + + cBInput.resize(mBlockSize * CIRCULAR_BUFFER_UPSAMPLE); + cBOutput.resize(mBlockSize * CIRCULAR_BUFFER_UPSAMPLE); + + //fill input with half block size... + for (unsigned int k = 0; k < mBlockSize/2; k++) { + cBInput.write(0); + } + + //temp vectors + input.resize(mBlockSize); + output.resize(mBlockSize); + outTail.resize(overlapSize); + + //module vectors + mPreEqFactorVector.resize(halfFftSize, 1.0); + mPostEqFactorVector.resize(halfFftSize, 1.0); + + mPreEqBands.resize(dpBase.getPreEqBandCount()); + mMbcBands.resize(dpBase.getMbcBandCount()); + mPostEqBands.resize(dpBase.getPostEqBandCount()); + ALOGV("mPreEqBands %zu, mMbcBands %zu, mPostEqBands %zu",mPreEqBands.size(), + mMbcBands.size(), mPostEqBands.size()); + + DPChannel *pChannel = dpBase.getChannel(0); + if (pChannel != nullptr) { + mPreEqInUse = pChannel->getPreEq()->isInUse(); + mMbcInUse = pChannel->getMbc()->isInUse(); + mPostEqInUse = pChannel->getPostEq()->isInUse(); + mLimiterInUse = pChannel->getLimiter()->isInUse(); + } + + mLimiterParams.linkGroup = -1; //no group. +} + +void ChannelBuffer::computeBinStartStop(BandParams &bp, size_t binStart) { + + bp.binStart = binStart; + bp.binStop = (int)(0.5 + bp.freqCutoffHz * mBlockSize / mSamplingRate); +} + +//== LinkedLimiters Helper +void LinkedLimiters::reset() { + mGroupsMap.clear(); +} + +void LinkedLimiters::update(int32_t group, int index) { + mGroupsMap[group].push_back(index); +} + +void LinkedLimiters::remove(int index) { + //check all groups and if index is found, remove it. + //if group is empty afterwards, remove it. + for (auto it = mGroupsMap.begin(); it != mGroupsMap.end(); ) { + for (auto itIndex = it->second.begin(); itIndex != it->second.end(); ) { + if (*itIndex == index) { + itIndex = it->second.erase(itIndex); + } else { + ++itIndex; + } + } + if (it->second.size() == 0) { + it = mGroupsMap.erase(it); + } else { + ++it; + } + } +} + +//== DPFrequency +void DPFrequency::reset() { +} + +size_t DPFrequency::getMinBockSize() { + return MIN_BLOCKSIZE; +} + +size_t DPFrequency::getMaxBockSize() { + return MAX_BLOCKSIZE; +} + +void DPFrequency::configure(size_t blockSize, size_t overlapSize, + size_t samplingRate) { + ALOGV("configure"); + mBlockSize = blockSize; + if (mBlockSize > MAX_BLOCKSIZE) { + mBlockSize = MAX_BLOCKSIZE; + } else if (mBlockSize < MIN_BLOCKSIZE) { + mBlockSize = MIN_BLOCKSIZE; + } else { + if (!isPowerOf2(blockSize)) { + //find next highest power of 2. + mBlockSize = 1 << (32 - __builtin_clz(blockSize)); + } + } + + mHalfFFTSize = 1 + mBlockSize / 2; //including Nyquist bin + mOverlapSize = std::min(overlapSize, mBlockSize/2); + + int channelcount = getChannelCount(); + mSamplingRate = samplingRate; + mChannelBuffers.resize(channelcount); + for (int ch = 0; ch < channelcount; ch++) { + mChannelBuffers[ch].initBuffers(mBlockSize, mOverlapSize, mHalfFFTSize, + mSamplingRate, *this); + } + + //effective number of frames processed per second + mBlocksPerSecond = (float)mSamplingRate / (mBlockSize - mOverlapSize); + + fill_window(mVWindow, RDSP_WINDOW_HANNING_FLAT_TOP, mBlockSize, mOverlapSize); + + //compute window rms for energy compensation + mWindowRms = 0; + for (size_t i = 0; i < mVWindow.size(); i++) { + mWindowRms += mVWindow[i] * mVWindow[i]; + } + + //Making sure window rms is not zero. + mWindowRms = std::max(sqrt(mWindowRms / mVWindow.size()), MIN_ENVELOPE); +} + +void DPFrequency::updateParameters(ChannelBuffer &cb, int channelIndex) { + DPChannel *pChannel = getChannel(channelIndex); + + if (pChannel == nullptr) { + ALOGE("Error: updateParameters null DPChannel %d", channelIndex); + return; + } + + //===Input Gain and preEq + { + bool changed = false; + IS_CHANGED(changed, cb.inputGainDb, pChannel->getInputGain()); + //===EqPre + if (cb.mPreEqInUse) { + DPEq *pPreEq = pChannel->getPreEq(); + if (pPreEq == nullptr) { + ALOGE("Error: updateParameters null PreEq for channel: %d", channelIndex); + return; + } + IS_CHANGED(changed, cb.mPreEqEnabled, pPreEq->isEnabled()); + if (cb.mPreEqEnabled) { + for (unsigned int b = 0; b < getPreEqBandCount(); b++) { + DPEqBand *pEqBand = pPreEq->getBand(b); + if (pEqBand == nullptr) { + ALOGE("Error: updateParameters null PreEqBand for band %d", b); + return; //failed. + } + ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPreEqBands[b]; + IS_CHANGED(changed, pEqBandParams->enabled, pEqBand->isEnabled()); + IS_CHANGED(changed, pEqBandParams->freqCutoffHz, + pEqBand->getCutoffFrequency()); + IS_CHANGED(changed, pEqBandParams->gainDb, pEqBand->getGain()); + } + } + } + + if (changed) { + float inputGainFactor = dBtoLinear(cb.inputGainDb); + if (cb.mPreEqInUse && cb.mPreEqEnabled) { + ALOGV("preEq changed, recomputing! channel %d", channelIndex); + size_t binNext = 0; + for (unsigned int b = 0; b < getPreEqBandCount(); b++) { + ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPreEqBands[b]; + + //frequency translation + cb.computeBinStartStop(*pEqBandParams, binNext); + binNext = pEqBandParams->binStop + 1; + float factor = dBtoLinear(pEqBandParams->gainDb); + if (!pEqBandParams->enabled) { + factor = inputGainFactor; + } + for (size_t k = pEqBandParams->binStart; + k <= pEqBandParams->binStop && k < mHalfFFTSize; k++) { + cb.mPreEqFactorVector[k] = factor * inputGainFactor; + } + } + } else { + ALOGV("only input gain changed, recomputing!"); + //populate PreEq factor with input gain factor. + for (size_t k = 0; k < mHalfFFTSize; k++) { + cb.mPreEqFactorVector[k] = inputGainFactor; + } + } + } + } //inputGain and preEq + + //===EqPost + if (cb.mPostEqInUse) { + bool changed = false; + + DPEq *pPostEq = pChannel->getPostEq(); + if (pPostEq == nullptr) { + ALOGE("Error: updateParameters null postEq for channel: %d", channelIndex); + return; //failed. + } + IS_CHANGED(changed, cb.mPostEqEnabled, pPostEq->isEnabled()); + if (cb.mPostEqEnabled) { + for (unsigned int b = 0; b < getPostEqBandCount(); b++) { + DPEqBand *pEqBand = pPostEq->getBand(b); + if (pEqBand == nullptr) { + ALOGE("Error: updateParameters PostEqBand NULL for band %d", b); + return; //failed. + } + ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPostEqBands[b]; + IS_CHANGED(changed, pEqBandParams->enabled, pEqBand->isEnabled()); + IS_CHANGED(changed, pEqBandParams->freqCutoffHz, + pEqBand->getCutoffFrequency()); + IS_CHANGED(changed, pEqBandParams->gainDb, pEqBand->getGain()); + } + if (changed) { + ALOGV("postEq changed, recomputing! channel %d", channelIndex); + size_t binNext = 0; + for (unsigned int b = 0; b < getPostEqBandCount(); b++) { + ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPostEqBands[b]; + + //frequency translation + cb.computeBinStartStop(*pEqBandParams, binNext); + binNext = pEqBandParams->binStop + 1; + float factor = dBtoLinear(pEqBandParams->gainDb); + if (!pEqBandParams->enabled) { + factor = 1.0; + } + for (size_t k = pEqBandParams->binStart; + k <= pEqBandParams->binStop && k < mHalfFFTSize; k++) { + cb.mPostEqFactorVector[k] = factor; + } + } + } + } //enabled + } + + //===MBC + if (cb.mMbcInUse) { + DPMbc *pMbc = pChannel->getMbc(); + if (pMbc == nullptr) { + ALOGE("Error: updateParameters Mbc NULL for channel: %d", channelIndex); + return; + } + cb.mMbcEnabled = pMbc->isEnabled(); + if (cb.mMbcEnabled) { + bool changed = false; + for (unsigned int b = 0; b < getMbcBandCount(); b++) { + DPMbcBand *pMbcBand = pMbc->getBand(b); + if (pMbcBand == nullptr) { + ALOGE("Error: updateParameters MbcBand NULL for band %d", b); + return; //failed. + } + ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[b]; + pMbcBandParams->enabled = pMbcBand->isEnabled(); + IS_CHANGED(changed, pMbcBandParams->freqCutoffHz, + pMbcBand->getCutoffFrequency()); + + pMbcBandParams->gainPreDb = pMbcBand->getPreGain(); + pMbcBandParams->gainPostDb = pMbcBand->getPostGain(); + pMbcBandParams->attackTimeMs = pMbcBand->getAttackTime(); + pMbcBandParams->releaseTimeMs = pMbcBand->getReleaseTime(); + pMbcBandParams->ratio = pMbcBand->getRatio(); + pMbcBandParams->thresholdDb = pMbcBand->getThreshold(); + pMbcBandParams->kneeWidthDb = pMbcBand->getKneeWidth(); + pMbcBandParams->noiseGateThresholdDb = pMbcBand->getNoiseGateThreshold(); + pMbcBandParams->expanderRatio = pMbcBand->getExpanderRatio(); + + } + + if (changed) { + ALOGV("mbc changed, recomputing! channel %d", channelIndex); + size_t binNext= 0; + for (unsigned int b = 0; b < getMbcBandCount(); b++) { + ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[b]; + + pMbcBandParams->previousEnvelope = 0; + + //frequency translation + cb.computeBinStartStop(*pMbcBandParams, binNext); + binNext = pMbcBandParams->binStop + 1; + } + } + } + } + + //===Limiter + if (cb.mLimiterInUse) { + bool changed = false; + DPLimiter *pLimiter = pChannel->getLimiter(); + if (pLimiter == nullptr) { + ALOGE("Error: updateParameters Limiter NULL for channel: %d", channelIndex); + return; + } + cb.mLimiterEnabled = pLimiter->isEnabled(); + if (cb.mLimiterEnabled) { + IS_CHANGED(changed, cb.mLimiterParams.linkGroup , + (int32_t)pLimiter->getLinkGroup()); + cb.mLimiterParams.attackTimeMs = pLimiter->getAttackTime(); + cb.mLimiterParams.releaseTimeMs = pLimiter->getReleaseTime(); + cb.mLimiterParams.ratio = pLimiter->getRatio(); + cb.mLimiterParams.thresholdDb = pLimiter->getThreshold(); + cb.mLimiterParams.postGainDb = pLimiter->getPostGain(); + } + + if (changed) { + ALOGV("limiter changed, recomputing linkGroups for %d", channelIndex); + mLinkedLimiters.remove(channelIndex); //in case it was already there. + mLinkedLimiters.update(cb.mLimiterParams.linkGroup, channelIndex); + } + } + + //=== Output Gain + cb.outputGainDb = pChannel->getOutputGain(); +} + +size_t DPFrequency::processSamples(const float *in, float *out, size_t samples) { + const float *pIn = in; + float *pOut = out; + + int channelCount = mChannelBuffers.size(); + if (channelCount < 1) { + ALOGW("warning: no Channels ready for processing"); + return 0; + } + + //**Check if parameters have changed and update + for (int ch = 0; ch < channelCount; ch++) { + updateParameters(mChannelBuffers[ch], ch); + } + + //**separate into channels + for (size_t k = 0; k < samples; k += channelCount) { + for (int ch = 0; ch < channelCount; ch++) { + mChannelBuffers[ch].cBInput.write(*pIn++); + } + } + + //**process all channelBuffers + processChannelBuffers(mChannelBuffers); + + //** estimate how much data is available in ALL channels + size_t available = mChannelBuffers[0].cBOutput.availableToRead(); + for (int ch = 1; ch < channelCount; ch++) { + available = std::min(available, mChannelBuffers[ch].cBOutput.availableToRead()); + } + + //** make sure to output just what the buffer can handle + if (available > samples/channelCount) { + available = samples/channelCount; + } + + //**Prepend zeroes if necessary + size_t fill = samples - (channelCount * available); + for (size_t k = 0; k < fill; k++) { + *pOut++ = 0; + } + + //**interleave channels + for (size_t k = 0; k < available; k++) { + for (int ch = 0; ch < channelCount; ch++) { + *pOut++ = mChannelBuffers[ch].cBOutput.read(); + } + } + + return samples; +} + +size_t DPFrequency::processChannelBuffers(CBufferVector &channelBuffers) { + const int channelCount = channelBuffers.size(); + size_t processedSamples = 0; + size_t processFrames = mBlockSize - mOverlapSize; + + size_t available = channelBuffers[0].cBInput.availableToRead(); + for (int ch = 1; ch < channelCount; ch++) { + available = std::min(available, channelBuffers[ch].cBInput.availableToRead()); + } + + while (available >= processFrames) { + //First pass + for (int ch = 0; ch < channelCount; ch++) { + ChannelBuffer * pCb = &channelBuffers[ch]; + //move tail of previous + std::copy(pCb->input.begin() + processFrames, + pCb->input.end(), + pCb->input.begin()); + + //read new available data + for (unsigned int k = 0; k < processFrames; k++) { + pCb->input[mOverlapSize + k] = pCb->cBInput.read(); + } + //first stages: fft, preEq, mbc, postEq and start of Limiter + processedSamples += processFirstStages(*pCb); + } + + //**compute linked limiters and update levels if needed + processLinkedLimiters(channelBuffers); + + //final pass. + for (int ch = 0; ch < channelCount; ch++) { + ChannelBuffer * pCb = &channelBuffers[ch]; + + //linked limiter and ifft + processLastStages(*pCb); + + //mix tail (and capture new tail + for (unsigned int k = 0; k < mOverlapSize; k++) { + pCb->output[k] += pCb->outTail[k]; + pCb->outTail[k] = pCb->output[processFrames + k]; //new tail + } + + //output data + for (unsigned int k = 0; k < processFrames; k++) { + pCb->cBOutput.write(pCb->output[k]); + } + } + available -= processFrames; + } + return processedSamples; +} +size_t DPFrequency::processFirstStages(ChannelBuffer &cb) { + + //##apply window + Eigen::Map<Eigen::VectorXf> eWindow(&mVWindow[0], mVWindow.size()); + Eigen::Map<Eigen::VectorXf> eInput(&cb.input[0], cb.input.size()); + + Eigen::VectorXf eWin = eInput.cwiseProduct(eWindow); //apply window + + //##fft + //Note: we are using eigen with the default scaling, which ensures that + // IFFT( FFT(x) ) = x. + // TODO: optimize by using the noscale option, and compensate with dB scale offsets + mFftServer.fwd(cb.complexTemp, eWin); + + size_t cSize = cb.complexTemp.size(); + size_t maxBin = std::min(cSize/2, mHalfFFTSize); + + //== EqPre (always runs) + for (size_t k = 0; k < maxBin; k++) { + cb.complexTemp[k] *= cb.mPreEqFactorVector[k]; + } + + //== MBC + if (cb.mMbcInUse && cb.mMbcEnabled) { + for (size_t band = 0; band < cb.mMbcBands.size(); band++) { + ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[band]; + float fEnergySum = 0; + + //apply pre gain. + float preGainFactor = dBtoLinear(pMbcBandParams->gainPreDb); + float preGainSquared = preGainFactor * preGainFactor; + + for (size_t k = pMbcBandParams->binStart; k <= pMbcBandParams->binStop; k++) { + fEnergySum += std::norm(cb.complexTemp[k]) * preGainSquared; //mag squared + } + + //Eigen FFT is full spectrum, even if the source was real data. + // Each half spectrum has half the energy. This is taken into account with the * 2 + // factor in the energy computations. + // energy = sqrt(sum_components_squared) number_points + // in here, the fEnergySum is duplicated to account for the second half spectrum, + // and the windowRms is used to normalize by the expected energy reduction + // caused by the window used (expected for steady state signals) + fEnergySum = sqrt(fEnergySum * 2) / (mBlockSize * mWindowRms); + + // updates computed per frame advance. + float fTheta = 0.0; + float fFAttSec = pMbcBandParams->attackTimeMs / 1000; //in seconds + float fFRelSec = pMbcBandParams->releaseTimeMs / 1000; //in seconds + + if (fEnergySum > pMbcBandParams->previousEnvelope) { + fTheta = exp(-1.0 / (fFAttSec * mBlocksPerSecond)); + } else { + fTheta = exp(-1.0 / (fFRelSec * mBlocksPerSecond)); + } + + float fEnv = (1.0 - fTheta) * fEnergySum + fTheta * pMbcBandParams->previousEnvelope; + //preserve for next iteration + pMbcBandParams->previousEnvelope = fEnv; + + if (fEnv < MIN_ENVELOPE) { + fEnv = MIN_ENVELOPE; + } + const float envDb = linearToDb(fEnv); + float newLevelDb = envDb; + //using shorter variables for code clarity + const float thresholdDb = pMbcBandParams->thresholdDb; + const float ratio = pMbcBandParams->ratio; + const float kneeWidthDbHalf = pMbcBandParams->kneeWidthDb / 2; + const float noiseGateThresholdDb = pMbcBandParams->noiseGateThresholdDb; + const float expanderRatio = pMbcBandParams->expanderRatio; + + //find segment + if (envDb > thresholdDb + kneeWidthDbHalf) { + //compression segment + newLevelDb = envDb + ((1 / ratio) - 1) * (envDb - thresholdDb); + } else if (envDb > thresholdDb - kneeWidthDbHalf) { + //knee-compression segment + float temp = (envDb - thresholdDb + kneeWidthDbHalf); + newLevelDb = envDb + ((1 / ratio) - 1) * + temp * temp / (kneeWidthDbHalf * 4); + } else if (envDb < noiseGateThresholdDb) { + //expander segment + newLevelDb = noiseGateThresholdDb - + expanderRatio * (noiseGateThresholdDb - envDb); + } + + float newFactor = dBtoLinear(newLevelDb - envDb); + + //apply post gain. + newFactor *= dBtoLinear(pMbcBandParams->gainPostDb); + + //apply to this band + for (size_t k = pMbcBandParams->binStart; k <= pMbcBandParams->binStop; k++) { + cb.complexTemp[k] *= newFactor; + } + + } //end per band process + + } //end MBC + + //== EqPost + if (cb.mPostEqInUse && cb.mPostEqEnabled) { + for (size_t k = 0; k < maxBin; k++) { + cb.complexTemp[k] *= cb.mPostEqFactorVector[k]; + } + } + + //== Limiter. First Pass + if (cb.mLimiterInUse && cb.mLimiterEnabled) { + float fEnergySum = 0; + for (size_t k = 0; k < maxBin; k++) { + fEnergySum += std::norm(cb.complexTemp[k]); + } + + //see explanation above for energy computation logic + fEnergySum = sqrt(fEnergySum * 2) / (mBlockSize * mWindowRms); + float fTheta = 0.0; + float fFAttSec = cb.mLimiterParams.attackTimeMs / 1000; //in seconds + float fFRelSec = cb.mLimiterParams.releaseTimeMs / 1000; //in seconds + + if (fEnergySum > cb.mLimiterParams.previousEnvelope) { + fTheta = exp(-1.0 / (fFAttSec * mBlocksPerSecond)); + } else { + fTheta = exp(-1.0 / (fFRelSec * mBlocksPerSecond)); + } + + float fEnv = (1.0 - fTheta) * fEnergySum + fTheta * cb.mLimiterParams.previousEnvelope; + //preserve for next iteration + cb.mLimiterParams.previousEnvelope = fEnv; + + const float envDb = linearToDb(fEnv); + float newFactorDb = 0; + //using shorter variables for code clarity + const float thresholdDb = cb.mLimiterParams.thresholdDb; + const float ratio = cb.mLimiterParams.ratio; + + if (envDb > thresholdDb) { + //limiter segment + newFactorDb = ((1 / ratio) - 1) * (envDb - thresholdDb); + } + + float newFactor = dBtoLinear(newFactorDb); + + cb.mLimiterParams.newFactor = newFactor; + + } //end Limiter + return mBlockSize; +} + +void DPFrequency::processLinkedLimiters(CBufferVector &channelBuffers) { + + const int channelCount = channelBuffers.size(); + for (auto &groupPair : mLinkedLimiters.mGroupsMap) { + float minFactor = 1.0; + //estimate minfactor for all linked + for(int index : groupPair.second) { + if (index >= 0 && index < channelCount) { + minFactor = std::min(channelBuffers[index].mLimiterParams.newFactor, minFactor); + } + } + //apply minFactor + for(int index : groupPair.second) { + if (index >= 0 && index < channelCount) { + channelBuffers[index].mLimiterParams.linkFactor = minFactor; + } + } + } +} + +size_t DPFrequency::processLastStages(ChannelBuffer &cb) { + + float outputGainFactor = dBtoLinear(cb.outputGainDb); + //== Limiter. last Pass + if (cb.mLimiterInUse && cb.mLimiterEnabled) { + //compute factor, with post-gain + float factor = cb.mLimiterParams.linkFactor * dBtoLinear(cb.mLimiterParams.postGainDb); + outputGainFactor *= factor; + } + + //apply to all if != 1.0 + if (!compareEquality(outputGainFactor, 1.0f)) { + size_t cSize = cb.complexTemp.size(); + size_t maxBin = std::min(cSize/2, mHalfFFTSize); + for (size_t k = 0; k < maxBin; k++) { + cb.complexTemp[k] *= outputGainFactor; + } + } + + //##ifft directly to output. + Eigen::Map<Eigen::VectorXf> eOutput(&cb.output[0], cb.output.size()); + mFftServer.inv(eOutput, cb.complexTemp); + return mBlockSize; +} + +} //namespace dp_fx
diff --git a/media/libeffects/dynamicsproc/dsp/DPFrequency.h b/media/libeffects/dynamicsproc/dsp/DPFrequency.h new file mode 100644 index 0000000..be8771d --- /dev/null +++ b/media/libeffects/dynamicsproc/dsp/DPFrequency.h
@@ -0,0 +1,160 @@ +/* + * 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 DPFREQUENCY_H_ +#define DPFREQUENCY_H_ + +#include <Eigen/Dense> +#include <unsupported/Eigen/FFT> + +#include "RDsp.h" +#include "SHCircularBuffer.h" + +#include "DPBase.h" + + +namespace dp_fx { + +using FXBuffer = SHCircularBuffer<float>; + +class ChannelBuffer { +public: + FXBuffer cBInput; // Circular Buffer input + FXBuffer cBOutput; // Circular Buffer output + FloatVec input; // time domain temp vector for input + FloatVec output; // time domain temp vector for output + FloatVec outTail; // time domain temp vector for output tail (for overlap-add method) + + Eigen::VectorXcf complexTemp; // complex temp vector for frequency domain operations + + //Current parameters + float inputGainDb; + float outputGainDb; + struct BandParams { + bool enabled; + float freqCutoffHz; + size_t binStart; + size_t binStop; + }; + struct EqBandParams : public BandParams { + float gainDb; + }; + struct MbcBandParams : public BandParams { + float gainPreDb; + float gainPostDb; + float attackTimeMs; + float releaseTimeMs; + float ratio; + float thresholdDb; + float kneeWidthDb; + float noiseGateThresholdDb; + float expanderRatio; + + //Historic values + float previousEnvelope; + }; + struct LimiterParams { + int32_t linkGroup; + float attackTimeMs; + float releaseTimeMs; + float ratio; + float thresholdDb; + float postGainDb; + + //Historic values + float previousEnvelope; + float newFactor; + float linkFactor; + }; + + bool mPreEqInUse; + bool mPreEqEnabled; + std::vector<EqBandParams> mPreEqBands; + + bool mMbcInUse; + bool mMbcEnabled; + std::vector<MbcBandParams> mMbcBands; + + bool mPostEqInUse; + bool mPostEqEnabled; + std::vector<EqBandParams> mPostEqBands; + + bool mLimiterInUse; + bool mLimiterEnabled; + LimiterParams mLimiterParams; + FloatVec mPreEqFactorVector; // temp pre-computed vector to shape spectrum at preEQ stage + FloatVec mPostEqFactorVector; // temp pre-computed vector to shape spectrum at postEQ stage + + void initBuffers(unsigned int blockSize, unsigned int overlapSize, unsigned int halfFftSize, + unsigned int samplingRate, DPBase &dpBase); + void computeBinStartStop(BandParams &bp, size_t binStart); +private: + unsigned int mSamplingRate; + unsigned int mBlockSize; + +}; + +using CBufferVector = std::vector<ChannelBuffer>; + +using GroupsMap = std::map<int32_t, IntVec>; + +class LinkedLimiters { +public: + void reset(); + void update(int32_t group, int index); + void remove(int index); + GroupsMap mGroupsMap; +}; + +class DPFrequency : public DPBase { +public: + virtual size_t processSamples(const float *in, float *out, size_t samples); + virtual void reset(); + void configure(size_t blockSize, size_t overlapSize, size_t samplingRate); + static size_t getMinBockSize(); + static size_t getMaxBockSize(); + +private: + void updateParameters(ChannelBuffer &cb, int channelIndex); + size_t processMono(ChannelBuffer &cb); + size_t processOneVector(FloatVec &output, FloatVec &input, ChannelBuffer &cb); + + size_t processChannelBuffers(CBufferVector &channelBuffers); + size_t processFirstStages(ChannelBuffer &cb); + size_t processLastStages(ChannelBuffer &cb); + void processLinkedLimiters(CBufferVector &channelBuffers); + + size_t mBlockSize; + size_t mHalfFFTSize; + size_t mOverlapSize; + size_t mSamplingRate; + + float mBlocksPerSecond; + + CBufferVector mChannelBuffers; + + LinkedLimiters mLinkedLimiters; + + //dsp + FloatVec mVWindow; //window class. + float mWindowRms; + Eigen::FFT<float> mFftServer; +}; + +} //namespace dp_fx + +#endif // DPFREQUENCY_H_
diff --git a/media/libeffects/dynamicsproc/dsp/RDsp.h b/media/libeffects/dynamicsproc/dsp/RDsp.h new file mode 100644 index 0000000..cfa1305 --- /dev/null +++ b/media/libeffects/dynamicsproc/dsp/RDsp.h
@@ -0,0 +1,175 @@ +/* + * 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 RDSP_H +#define RDSP_H + +#include <complex> +#include <log/log.h> +#include <vector> +#include <map> +using FloatVec = std::vector<float>; +using IntVec = std::vector<int>; +using ComplexVec = std::vector<std::complex<float>>; + +// ======= +// Helper Functions +// ======= +template <class T> +static T dBtoLinear(T valueDb) { + return pow (10, valueDb / 20.0); +} + +template <class T> +static T linearToDb(T value) { + return 20 * log10(value); +} + +// ======= +// DSP window creation +// ======= + +#define TWOPI (M_PI * 2) + +enum rdsp_window_type { + RDSP_WINDOW_RECTANGULAR, + RDSP_WINDOW_TRIANGULAR, + RDSP_WINDOW_TRIANGULAR_FLAT_TOP, + RDSP_WINDOW_HAMMING, + RDSP_WINDOW_HAMMING_FLAT_TOP, + RDSP_WINDOW_HANNING, + RDSP_WINDOW_HANNING_FLAT_TOP, +}; + +template <typename T> +static void fillRectangular(T &v) { + const size_t size = v.size(); + for (size_t i = 0; i < size; i++) { + v[i] = 1.0; + } +} //rectangular + +template <typename T> +static void fillTriangular(T &v, size_t overlap) { + const size_t size = v.size(); + //ramp up + size_t i = 0; + if (overlap > 0) { + for (; i < overlap; i++) { + v[i] = (2.0 * i + 1) / (2 * overlap); + } + } + + //flat top + for (; i < size - overlap; i++) { + v[i] = 1.0; + } + + //ramp down + if (overlap > 0) { + for (; i < size; i++) { + v[i] = (2.0 * (size - i) - 1) / (2 * overlap); + } + } +} //triangular + +template <typename T> +static void fillHamming(T &v, size_t overlap) { + const size_t size = v.size(); + const size_t twoOverlap = 2 * overlap; + size_t i = 0; + if (overlap > 0) { + for (; i < overlap; i++) { + v[i] = 0.54 - 0.46 * cos(TWOPI * i /(twoOverlap - 1)); + } + } + + //flat top + for (; i < size - overlap; i++) { + v[i] = 1.0; + } + + //ramp down + if (overlap > 0) { + for (; i < size; i++) { + int k = i - ((int)size - 2 * overlap); + v[i] = 0.54 - 0.46 * cos(TWOPI * k / (twoOverlap - 1)); + } + } +} //hamming + +template <typename T> +static void fillHanning(T &v, size_t overlap) { + const size_t size = v.size(); + const size_t twoOverlap = 2 * overlap; + //ramp up + size_t i = 0; + if (overlap > 0) { + for (; i < overlap; i++) { + v[i] = 0.5 * (1.0 - cos(TWOPI * i / (twoOverlap - 1))); + } + } + + //flat top + for (; i < size - overlap; i++) { + v[i] = 1.0; + } + + //ramp down + if (overlap > 0) { + for (; i < size; i++) { + int k = i - ((int)size - 2 * overlap); + v[i] = 0.5 * (1.0 - cos(TWOPI * k / (twoOverlap - 1))); + } + } +} + +template <typename T> +static void fill_window(T &v, int type, size_t size, size_t overlap) { + if (overlap > size / 2) { + overlap = size / 2; + } + v.resize(size); + + switch (type) { + case RDSP_WINDOW_RECTANGULAR: + fillRectangular(v); + break; + case RDSP_WINDOW_TRIANGULAR: + fillTriangular(v, size / 2); + break; + case RDSP_WINDOW_TRIANGULAR_FLAT_TOP: + fillTriangular(v, overlap); + break; + case RDSP_WINDOW_HAMMING: + fillHamming(v, size / 2); + break; + case RDSP_WINDOW_HAMMING_FLAT_TOP: + fillHamming(v, overlap); + break; + case RDSP_WINDOW_HANNING: + fillHanning(v, size / 2); + break; + case RDSP_WINDOW_HANNING_FLAT_TOP: + fillHanning(v, overlap); + break; + default: + ALOGE("Error: unknown window type %d", type); + } +} + +//}; +#endif //RDSP_H
diff --git a/media/libeffects/dynamicsproc/dsp/SHCircularBuffer.h b/media/libeffects/dynamicsproc/dsp/SHCircularBuffer.h new file mode 100644 index 0000000..c139cd8 --- /dev/null +++ b/media/libeffects/dynamicsproc/dsp/SHCircularBuffer.h
@@ -0,0 +1,81 @@ +/* + * 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 SHCIRCULARBUFFER_H +#define SHCIRCULARBUFFER_H + +#include <log/log.h> +#include <vector> + +template <class T> +class SHCircularBuffer { + +public: + SHCircularBuffer() : mReadIndex(0), mWriteIndex(0), mReadAvailable(0) { + } + + explicit SHCircularBuffer(size_t maxSize) { + resize(maxSize); + } + void resize(size_t maxSize) { + mBuffer.resize(maxSize); + mReadIndex = 0; + mWriteIndex = 0; + mReadAvailable = 0; + } + inline void write(T value) { + if (availableToWrite()) { + mBuffer[mWriteIndex++] = value; + if (mWriteIndex >= getSize()) { + mWriteIndex = 0; + } + mReadAvailable++; + } else { + ALOGE("Error: SHCircularBuffer no space to write. allocated size %zu ", getSize()); + } + } + inline T read() { + T value = T(); + if (availableToRead()) { + value = mBuffer[mReadIndex++]; + if (mReadIndex >= getSize()) { + mReadIndex = 0; + } + mReadAvailable--; + } else { + ALOGW("Warning: SHCircularBuffer no data available to read. Default value returned"); + } + return value; + } + inline size_t availableToRead() const { + return mReadAvailable; + } + inline size_t availableToWrite() const { + return getSize() - mReadAvailable; + } + inline size_t getSize() const { + return mBuffer.size(); + } + +private: + std::vector<T> mBuffer; + size_t mReadIndex; + size_t mWriteIndex; + size_t mReadAvailable; +}; + + +#endif //SHCIRCULARBUFFER_H
diff --git a/media/libeffects/factory/EffectsFactory.c b/media/libeffects/factory/EffectsFactory.c index cd0e765..c1ce513 100644 --- a/media/libeffects/factory/EffectsFactory.c +++ b/media/libeffects/factory/EffectsFactory.c
@@ -261,7 +261,6 @@ effect_descriptor_t *d = NULL; effect_handle_t itfe; effect_entry_t *fx; - int found = 0; int ret; if (uuid == NULL || pHandle == NULL) { @@ -428,8 +427,6 @@ ///////////////////////////////////////////////// int init() { - int hdl; - if (gInitDone) { return 0; } @@ -552,7 +549,6 @@ list_elem_t *e = gLibraryList; lib_entry_t *l = NULL; effect_descriptor_t *d = NULL; - int found = 0; int ret = 0; dprintf(fd, "Libraries loaded:\n");
diff --git a/media/libeffects/factory/EffectsXmlConfigLoader.cpp b/media/libeffects/factory/EffectsXmlConfigLoader.cpp index 438b787..052a88b 100644 --- a/media/libeffects/factory/EffectsXmlConfigLoader.cpp +++ b/media/libeffects/factory/EffectsXmlConfigLoader.cpp
@@ -327,7 +327,8 @@ &gSkippedEffects, &gSubEffectList); ALOGE_IF(result.nbSkippedElement != 0, "%zu errors during loading of configuration: %s", - result.nbSkippedElement, path ?: effectsConfig::DEFAULT_PATH); + result.nbSkippedElement, + result.configPath.empty() ? "No config file found" : result.configPath.c_str()); return result.nbSkippedElement; }
diff --git a/media/libeffects/lvm/lib/Bass/src/LVDBE_Coeffs.h b/media/libeffects/lvm/lib/Bass/src/LVDBE_Coeffs.h index f32ed30..4ecaf14 100644 --- a/media/libeffects/lvm/lib/Bass/src/LVDBE_Coeffs.h +++ b/media/libeffects/lvm/lib/Bass/src/LVDBE_Coeffs.h
@@ -534,246 +534,246 @@ /* Coefficients for centre frequency 55Hz */ #define HPF_Fs8000_Fc55_A0 0.958849f -#define HPF_Fs8000_Fc55_A1 -1.917698f +#define HPF_Fs8000_Fc55_A1 (-1.917698f) #define HPF_Fs8000_Fc55_A2 0.958849f -#define HPF_Fs8000_Fc55_B1 -1.939001f +#define HPF_Fs8000_Fc55_B1 (-1.939001f) #define HPF_Fs8000_Fc55_B2 0.940807f #define HPF_Fs11025_Fc55_A0 0.966909f -#define HPF_Fs11025_Fc55_A1 -1.933818f +#define HPF_Fs11025_Fc55_A1 (-1.933818f) #define HPF_Fs11025_Fc55_A2 0.966909f -#define HPF_Fs11025_Fc55_B1 -1.955732f +#define HPF_Fs11025_Fc55_B1 (-1.955732f) #define HPF_Fs11025_Fc55_B2 0.956690f #define HPF_Fs12000_Fc55_A0 0.968650f -#define HPF_Fs12000_Fc55_A1 -1.937300f +#define HPF_Fs12000_Fc55_A1 (-1.937300f) #define HPF_Fs12000_Fc55_A2 0.968650f -#define HPF_Fs12000_Fc55_B1 -1.959327f +#define HPF_Fs12000_Fc55_B1 (-1.959327f) #define HPF_Fs12000_Fc55_B2 0.960138f #define HPF_Fs16000_Fc55_A0 0.973588f -#define HPF_Fs16000_Fc55_A1 -1.947176f +#define HPF_Fs16000_Fc55_A1 (-1.947176f) #define HPF_Fs16000_Fc55_A2 0.973588f -#define HPF_Fs16000_Fc55_B1 -1.969494f +#define HPF_Fs16000_Fc55_B1 (-1.969494f) #define HPF_Fs16000_Fc55_B2 0.969952f #define HPF_Fs22050_Fc55_A0 0.977671f -#define HPF_Fs22050_Fc55_A1 -1.955343f +#define HPF_Fs22050_Fc55_A1 (-1.955343f) #define HPF_Fs22050_Fc55_A2 0.977671f -#define HPF_Fs22050_Fc55_B1 -1.977863f +#define HPF_Fs22050_Fc55_B1 (-1.977863f) #define HPF_Fs22050_Fc55_B2 0.978105f #define HPF_Fs24000_Fc55_A0 0.978551f -#define HPF_Fs24000_Fc55_A1 -1.957102f +#define HPF_Fs24000_Fc55_A1 (-1.957102f) #define HPF_Fs24000_Fc55_A2 0.978551f -#define HPF_Fs24000_Fc55_B1 -1.979662f +#define HPF_Fs24000_Fc55_B1 (-1.979662f) #define HPF_Fs24000_Fc55_B2 0.979866f #define HPF_Fs32000_Fc55_A0 0.981042f -#define HPF_Fs32000_Fc55_A1 -1.962084f +#define HPF_Fs32000_Fc55_A1 (-1.962084f) #define HPF_Fs32000_Fc55_A2 0.981042f -#define HPF_Fs32000_Fc55_B1 -1.984746f +#define HPF_Fs32000_Fc55_B1 (-1.984746f) #define HPF_Fs32000_Fc55_B2 0.984861f #define HPF_Fs44100_Fc55_A0 0.983097f -#define HPF_Fs44100_Fc55_A1 -1.966194f +#define HPF_Fs44100_Fc55_A1 (-1.966194f) #define HPF_Fs44100_Fc55_A2 0.983097f -#define HPF_Fs44100_Fc55_B1 -1.988931f +#define HPF_Fs44100_Fc55_B1 (-1.988931f) #define HPF_Fs44100_Fc55_B2 0.988992f #define HPF_Fs48000_Fc55_A0 0.983539f -#define HPF_Fs48000_Fc55_A1 -1.967079f +#define HPF_Fs48000_Fc55_A1 (-1.967079f) #define HPF_Fs48000_Fc55_A2 0.983539f -#define HPF_Fs48000_Fc55_B1 -1.989831f +#define HPF_Fs48000_Fc55_B1 (-1.989831f) #define HPF_Fs48000_Fc55_B2 0.989882f #ifdef HIGHER_FS #define HPF_Fs96000_Fc55_A0 0.986040f -#define HPF_Fs96000_Fc55_A1 -1.972080f +#define HPF_Fs96000_Fc55_A1 (-1.972080f) #define HPF_Fs96000_Fc55_A2 0.986040f -#define HPF_Fs96000_Fc55_B1 -1.994915f +#define HPF_Fs96000_Fc55_B1 (-1.994915f) #define HPF_Fs96000_Fc55_B2 0.994928f #define HPF_Fs192000_Fc55_A0 0.987294f -#define HPF_Fs192000_Fc55_A1 -1.974588f +#define HPF_Fs192000_Fc55_A1 (-1.974588f) #define HPF_Fs192000_Fc55_A2 0.987294f -#define HPF_Fs192000_Fc55_B1 -1.997458f +#define HPF_Fs192000_Fc55_B1 (-1.997458f) #define HPF_Fs192000_Fc55_B2 0.997461f #endif /* Coefficients for centre frequency 66Hz */ #define HPF_Fs8000_Fc66_A0 0.953016f -#define HPF_Fs8000_Fc66_A1 -1.906032f +#define HPF_Fs8000_Fc66_A1 (-1.906032f) #define HPF_Fs8000_Fc66_A2 0.953016f -#define HPF_Fs8000_Fc66_B1 -1.926810f +#define HPF_Fs8000_Fc66_B1 (-1.926810f) #define HPF_Fs8000_Fc66_B2 0.929396f #define HPF_Fs11025_Fc66_A0 0.962638f -#define HPF_Fs11025_Fc66_A1 -1.925275f +#define HPF_Fs11025_Fc66_A1 (-1.925275f) #define HPF_Fs11025_Fc66_A2 0.962638f -#define HPF_Fs11025_Fc66_B1 -1.946881f +#define HPF_Fs11025_Fc66_B1 (-1.946881f) #define HPF_Fs11025_Fc66_B2 0.948256f #define HPF_Fs12000_Fc66_A0 0.964718f -#define HPF_Fs12000_Fc66_A1 -1.929435f +#define HPF_Fs12000_Fc66_A1 (-1.929435f) #define HPF_Fs12000_Fc66_A2 0.964718f -#define HPF_Fs12000_Fc66_B1 -1.951196f +#define HPF_Fs12000_Fc66_B1 (-1.951196f) #define HPF_Fs12000_Fc66_B2 0.952359f #define HPF_Fs16000_Fc66_A0 0.970622f -#define HPF_Fs16000_Fc66_A1 -1.941244f +#define HPF_Fs16000_Fc66_A1 (-1.941244f) #define HPF_Fs16000_Fc66_A2 0.970622f -#define HPF_Fs16000_Fc66_B1 -1.963394f +#define HPF_Fs16000_Fc66_B1 (-1.963394f) #define HPF_Fs16000_Fc66_B2 0.964052f #define HPF_Fs22050_Fc66_A0 0.975509f -#define HPF_Fs22050_Fc66_A1 -1.951019f +#define HPF_Fs22050_Fc66_A1 (-1.951019f) #define HPF_Fs22050_Fc66_A2 0.975509f -#define HPF_Fs22050_Fc66_B1 -1.973436f +#define HPF_Fs22050_Fc66_B1 (-1.973436f) #define HPF_Fs22050_Fc66_B2 0.973784f #define HPF_Fs24000_Fc66_A0 0.976563f -#define HPF_Fs24000_Fc66_A1 -1.953125f +#define HPF_Fs24000_Fc66_A1 (-1.953125f) #define HPF_Fs24000_Fc66_A2 0.976563f -#define HPF_Fs24000_Fc66_B1 -1.975594f +#define HPF_Fs24000_Fc66_B1 (-1.975594f) #define HPF_Fs24000_Fc66_B2 0.975889f #define HPF_Fs32000_Fc66_A0 0.979547f -#define HPF_Fs32000_Fc66_A1 -1.959093f +#define HPF_Fs32000_Fc66_A1 (-1.959093f) #define HPF_Fs32000_Fc66_A2 0.979547f -#define HPF_Fs32000_Fc66_B1 -1.981695f +#define HPF_Fs32000_Fc66_B1 (-1.981695f) #define HPF_Fs32000_Fc66_B2 0.981861f #define HPF_Fs44100_Fc66_A0 0.982010f -#define HPF_Fs44100_Fc66_A1 -1.964019f +#define HPF_Fs44100_Fc66_A1 (-1.964019f) #define HPF_Fs44100_Fc66_A2 0.982010f -#define HPF_Fs44100_Fc66_B1 -1.986718f +#define HPF_Fs44100_Fc66_B1 (-1.986718f) #define HPF_Fs44100_Fc66_B2 0.986805f #define HPF_Fs48000_Fc66_A0 0.982540f -#define HPF_Fs48000_Fc66_A1 -1.965079f +#define HPF_Fs48000_Fc66_A1 (-1.965079f) #define HPF_Fs48000_Fc66_A2 0.982540f -#define HPF_Fs48000_Fc66_B1 -1.987797f +#define HPF_Fs48000_Fc66_B1 (-1.987797f) #define HPF_Fs48000_Fc66_B2 0.987871f #ifdef HIGHER_FS #define HPF_Fs96000_Fc66_A0 0.985539f -#define HPF_Fs96000_Fc66_A1 -1.971077f +#define HPF_Fs96000_Fc66_A1 (-1.971077f) #define HPF_Fs96000_Fc66_A2 0.985539f -#define HPF_Fs96000_Fc66_B1 -1.993898f +#define HPF_Fs96000_Fc66_B1 (-1.993898f) #define HPF_Fs96000_Fc66_B2 0.993917f #define HPF_Fs192000_Fc66_A0 0.987043f -#define HPF_Fs192000_Fc66_A1 -1.974086f +#define HPF_Fs192000_Fc66_A1 (-1.974086f) #define HPF_Fs192000_Fc66_A2 0.987043f -#define HPF_Fs192000_Fc66_B1 -1.996949f +#define HPF_Fs192000_Fc66_B1 (-1.996949f) #define HPF_Fs192000_Fc66_B2 0.996954f #endif /* Coefficients for centre frequency 78Hz */ #define HPF_Fs8000_Fc78_A0 0.946693f -#define HPF_Fs8000_Fc78_A1 -1.893387f +#define HPF_Fs8000_Fc78_A1 (-1.893387f) #define HPF_Fs8000_Fc78_A2 0.946693f -#define HPF_Fs8000_Fc78_B1 -1.913517f +#define HPF_Fs8000_Fc78_B1 (-1.913517f) #define HPF_Fs8000_Fc78_B2 0.917105f #define HPF_Fs11025_Fc78_A0 0.957999f -#define HPF_Fs11025_Fc78_A1 -1.915998f +#define HPF_Fs11025_Fc78_A1 (-1.915998f) #define HPF_Fs11025_Fc78_A2 0.957999f -#define HPF_Fs11025_Fc78_B1 -1.937229f +#define HPF_Fs11025_Fc78_B1 (-1.937229f) #define HPF_Fs11025_Fc78_B2 0.939140f #define HPF_Fs12000_Fc78_A0 0.960446f -#define HPF_Fs12000_Fc78_A1 -1.920892f +#define HPF_Fs12000_Fc78_A1 (-1.920892f) #define HPF_Fs12000_Fc78_A2 0.960446f -#define HPF_Fs12000_Fc78_B1 -1.942326f +#define HPF_Fs12000_Fc78_B1 (-1.942326f) #define HPF_Fs12000_Fc78_B2 0.943944f #define HPF_Fs16000_Fc78_A0 0.967397f -#define HPF_Fs16000_Fc78_A1 -1.934794f +#define HPF_Fs16000_Fc78_A1 (-1.934794f) #define HPF_Fs16000_Fc78_A2 0.967397f -#define HPF_Fs16000_Fc78_B1 -1.956740f +#define HPF_Fs16000_Fc78_B1 (-1.956740f) #define HPF_Fs16000_Fc78_B2 0.957656f #define HPF_Fs22050_Fc78_A0 0.973156f -#define HPF_Fs22050_Fc78_A1 -1.946313f +#define HPF_Fs22050_Fc78_A1 (-1.946313f) #define HPF_Fs22050_Fc78_A2 0.973156f -#define HPF_Fs22050_Fc78_B1 -1.968607f +#define HPF_Fs22050_Fc78_B1 (-1.968607f) #define HPF_Fs22050_Fc78_B2 0.969092f #define HPF_Fs24000_Fc78_A0 0.974398f -#define HPF_Fs24000_Fc78_A1 -1.948797f +#define HPF_Fs24000_Fc78_A1 (-1.948797f) #define HPF_Fs24000_Fc78_A2 0.974398f -#define HPF_Fs24000_Fc78_B1 -1.971157f +#define HPF_Fs24000_Fc78_B1 (-1.971157f) #define HPF_Fs24000_Fc78_B2 0.971568f #define HPF_Fs32000_Fc78_A0 0.977918f -#define HPF_Fs32000_Fc78_A1 -1.955836f +#define HPF_Fs32000_Fc78_A1 (-1.955836f) #define HPF_Fs32000_Fc78_A2 0.977918f -#define HPF_Fs32000_Fc78_B1 -1.978367f +#define HPF_Fs32000_Fc78_B1 (-1.978367f) #define HPF_Fs32000_Fc78_B2 0.978599f #define HPF_Fs44100_Fc78_A0 0.980824f -#define HPF_Fs44100_Fc78_A1 -1.961649f +#define HPF_Fs44100_Fc78_A1 (-1.961649f) #define HPF_Fs44100_Fc78_A2 0.980824f -#define HPF_Fs44100_Fc78_B1 -1.984303f +#define HPF_Fs44100_Fc78_B1 (-1.984303f) #define HPF_Fs44100_Fc78_B2 0.984425f #define HPF_Fs48000_Fc78_A0 0.981450f -#define HPF_Fs48000_Fc78_A1 -1.962900f +#define HPF_Fs48000_Fc78_A1 (-1.962900f) #define HPF_Fs48000_Fc78_A2 0.981450f -#define HPF_Fs48000_Fc78_B1 -1.985578f +#define HPF_Fs48000_Fc78_B1 (-1.985578f) #define HPF_Fs48000_Fc78_B2 0.985681f #ifdef HIGHER_FS #define HPF_Fs96000_Fc78_A0 0.984992f -#define HPF_Fs96000_Fc78_A1 -1.969984f +#define HPF_Fs96000_Fc78_A1 (-1.969984f) #define HPF_Fs96000_Fc78_A2 0.984992f -#define HPF_Fs96000_Fc78_B1 -1.992789f +#define HPF_Fs96000_Fc78_B1 (-1.992789f) #define HPF_Fs96000_Fc78_B2 0.992815f #define HPF_Fs192000_Fc78_A0 0.986769f -#define HPF_Fs192000_Fc78_A1 -1.973539f +#define HPF_Fs192000_Fc78_A1 (-1.973539f) #define HPF_Fs192000_Fc78_A2 0.986769f -#define HPF_Fs192000_Fc78_B1 -1.996394f +#define HPF_Fs192000_Fc78_B1 (-1.996394f) #define HPF_Fs192000_Fc78_B2 0.996401f #endif /* Coefficients for centre frequency 90Hz */ #define HPF_Fs8000_Fc90_A0 0.940412f -#define HPF_Fs8000_Fc90_A1 -1.880825f +#define HPF_Fs8000_Fc90_A1 (-1.880825f) #define HPF_Fs8000_Fc90_A2 0.940412f -#define HPF_Fs8000_Fc90_B1 -1.900231f +#define HPF_Fs8000_Fc90_B1 (-1.900231f) #define HPF_Fs8000_Fc90_B2 0.904977f #define HPF_Fs11025_Fc90_A0 0.953383f -#define HPF_Fs11025_Fc90_A1 -1.906766f +#define HPF_Fs11025_Fc90_A1 (-1.906766f) #define HPF_Fs11025_Fc90_A2 0.953383f -#define HPF_Fs11025_Fc90_B1 -1.927579f +#define HPF_Fs11025_Fc90_B1 (-1.927579f) #define HPF_Fs11025_Fc90_B2 0.930111f #define HPF_Fs12000_Fc90_A0 0.956193f -#define HPF_Fs12000_Fc90_A1 -1.912387f +#define HPF_Fs12000_Fc90_A1 (-1.912387f) #define HPF_Fs12000_Fc90_A2 0.956193f -#define HPF_Fs12000_Fc90_B1 -1.933459f +#define HPF_Fs12000_Fc90_B1 (-1.933459f) #define HPF_Fs12000_Fc90_B2 0.935603f #define HPF_Fs16000_Fc90_A0 0.964183f -#define HPF_Fs16000_Fc90_A1 -1.928365f +#define HPF_Fs16000_Fc90_A1 (-1.928365f) #define HPF_Fs16000_Fc90_A2 0.964183f -#define HPF_Fs16000_Fc90_B1 -1.950087f +#define HPF_Fs16000_Fc90_B1 (-1.950087f) #define HPF_Fs16000_Fc90_B2 0.951303f #define HPF_Fs22050_Fc90_A0 0.970809f -#define HPF_Fs22050_Fc90_A1 -1.941618f +#define HPF_Fs22050_Fc90_A1 (-1.941618f) #define HPF_Fs22050_Fc90_A2 0.970809f -#define HPF_Fs22050_Fc90_B1 -1.963778f +#define HPF_Fs22050_Fc90_B1 (-1.963778f) #define HPF_Fs22050_Fc90_B2 0.964423f #define HPF_Fs24000_Fc90_A0 0.972239f -#define HPF_Fs24000_Fc90_A1 -1.944477f +#define HPF_Fs24000_Fc90_A1 (-1.944477f) #define HPF_Fs24000_Fc90_A2 0.972239f -#define HPF_Fs24000_Fc90_B1 -1.966721f +#define HPF_Fs24000_Fc90_B1 (-1.966721f) #define HPF_Fs24000_Fc90_B2 0.967266f #define HPF_Fs32000_Fc90_A0 0.976292f -#define HPF_Fs32000_Fc90_A1 -1.952584f +#define HPF_Fs32000_Fc90_A1 (-1.952584f) #define HPF_Fs32000_Fc90_A2 0.976292f -#define HPF_Fs32000_Fc90_B1 -1.975040f +#define HPF_Fs32000_Fc90_B1 (-1.975040f) #define HPF_Fs32000_Fc90_B2 0.975347f #define HPF_Fs44100_Fc90_A0 0.979641f -#define HPF_Fs44100_Fc90_A1 -1.959282f +#define HPF_Fs44100_Fc90_A1 (-1.959282f) #define HPF_Fs44100_Fc90_A2 0.979641f -#define HPF_Fs44100_Fc90_B1 -1.981888f +#define HPF_Fs44100_Fc90_B1 (-1.981888f) #define HPF_Fs44100_Fc90_B2 0.982050f #define HPF_Fs48000_Fc90_A0 0.980362f -#define HPF_Fs48000_Fc90_A1 -1.960724f +#define HPF_Fs48000_Fc90_A1 (-1.960724f) #define HPF_Fs48000_Fc90_A2 0.980362f -#define HPF_Fs48000_Fc90_B1 -1.983359f +#define HPF_Fs48000_Fc90_B1 (-1.983359f) #define HPF_Fs48000_Fc90_B2 0.983497f #ifdef HIGHER_FS #define HPF_Fs96000_Fc90_A0 0.984446f -#define HPF_Fs96000_Fc90_A1 -1.968892f +#define HPF_Fs96000_Fc90_A1 (-1.968892f) #define HPF_Fs96000_Fc90_A2 0.984446f -#define HPF_Fs96000_Fc90_B1 -1.991680f +#define HPF_Fs96000_Fc90_B1 (-1.991680f) #define HPF_Fs96000_Fc90_B2 0.991714f #define HPF_Fs192000_Fc90_A0 0.986496f -#define HPF_Fs192000_Fc90_A1 -1.972992f +#define HPF_Fs192000_Fc90_A1 (-1.972992f) #define HPF_Fs192000_Fc90_A2 0.986496f -#define HPF_Fs192000_Fc90_B1 -1.995840f +#define HPF_Fs192000_Fc90_B1 (-1.995840f) #define HPF_Fs192000_Fc90_B2 0.995848f #endif @@ -786,244 +786,244 @@ /* Coefficients for centre frequency 55Hz */ #define BPF_Fs8000_Fc55_A0 0.009197f #define BPF_Fs8000_Fc55_A1 0.000000f -#define BPF_Fs8000_Fc55_A2 -0.009197f -#define BPF_Fs8000_Fc55_B1 -1.979545f +#define BPF_Fs8000_Fc55_A2 (-0.009197f) +#define BPF_Fs8000_Fc55_B1 (-1.979545f) #define BPF_Fs8000_Fc55_B2 0.981393f #define BPF_Fs11025_Fc55_A0 0.006691f #define BPF_Fs11025_Fc55_A1 0.000000f -#define BPF_Fs11025_Fc55_A2 -0.006691f -#define BPF_Fs11025_Fc55_B1 -1.985488f +#define BPF_Fs11025_Fc55_A2 (-0.006691f) +#define BPF_Fs11025_Fc55_B1 (-1.985488f) #define BPF_Fs11025_Fc55_B2 0.986464f #define BPF_Fs12000_Fc55_A0 0.006150f #define BPF_Fs12000_Fc55_A1 0.000000f -#define BPF_Fs12000_Fc55_A2 -0.006150f -#define BPF_Fs12000_Fc55_B1 -1.986733f +#define BPF_Fs12000_Fc55_A2 (-0.006150f) +#define BPF_Fs12000_Fc55_B1 (-1.986733f) #define BPF_Fs12000_Fc55_B2 0.987557f #define BPF_Fs16000_Fc55_A0 0.004620f #define BPF_Fs16000_Fc55_A1 0.000000f -#define BPF_Fs16000_Fc55_A2 -0.004620f -#define BPF_Fs16000_Fc55_B1 -1.990189f +#define BPF_Fs16000_Fc55_A2 (-0.004620f) +#define BPF_Fs16000_Fc55_B1 (-1.990189f) #define BPF_Fs16000_Fc55_B2 0.990653f #define BPF_Fs22050_Fc55_A0 0.003357f #define BPF_Fs22050_Fc55_A1 0.000000f -#define BPF_Fs22050_Fc55_A2 -0.003357f -#define BPF_Fs22050_Fc55_B1 -1.992964f +#define BPF_Fs22050_Fc55_A2 (-0.003357f) +#define BPF_Fs22050_Fc55_B1 (-1.992964f) #define BPF_Fs22050_Fc55_B2 0.993209f #define BPF_Fs24000_Fc55_A0 0.003085f #define BPF_Fs24000_Fc55_A1 0.000000f -#define BPF_Fs24000_Fc55_A2 -0.003085f -#define BPF_Fs24000_Fc55_B1 -1.993552f +#define BPF_Fs24000_Fc55_A2 (-0.003085f) +#define BPF_Fs24000_Fc55_B1 (-1.993552f) #define BPF_Fs24000_Fc55_B2 0.993759f #define BPF_Fs32000_Fc55_A0 0.002315f #define BPF_Fs32000_Fc55_A1 0.000000f -#define BPF_Fs32000_Fc55_A2 -0.002315f -#define BPF_Fs32000_Fc55_B1 -1.995199f +#define BPF_Fs32000_Fc55_A2 (-0.002315f) +#define BPF_Fs32000_Fc55_B1 (-1.995199f) #define BPF_Fs32000_Fc55_B2 0.995316f #define BPF_Fs44100_Fc55_A0 0.001681f #define BPF_Fs44100_Fc55_A1 0.000000f -#define BPF_Fs44100_Fc55_A2 -0.001681f -#define BPF_Fs44100_Fc55_B1 -1.996537f +#define BPF_Fs44100_Fc55_A2 (-0.001681f) +#define BPF_Fs44100_Fc55_B1 (-1.996537f) #define BPF_Fs44100_Fc55_B2 0.996599f #define BPF_Fs48000_Fc55_A0 0.001545f #define BPF_Fs48000_Fc55_A1 0.000000f -#define BPF_Fs48000_Fc55_A2 -0.001545f -#define BPF_Fs48000_Fc55_B1 -1.996823f +#define BPF_Fs48000_Fc55_A2 (-0.001545f) +#define BPF_Fs48000_Fc55_B1 (-1.996823f) #define BPF_Fs48000_Fc55_B2 0.996875f #ifdef HIGHER_FS #define BPF_Fs96000_Fc55_A0 0.000762f #define BPF_Fs96000_Fc55_A1 0.000000f -#define BPF_Fs96000_Fc55_A2 -0.000762f -#define BPF_Fs96000_Fc55_B1 -1.998461f +#define BPF_Fs96000_Fc55_A2 (-0.000762f) +#define BPF_Fs96000_Fc55_B1 (-1.998461f) #define BPF_Fs96000_Fc55_B2 0.998477f #define BPF_Fs192000_Fc55_A0 0.000381f #define BPF_Fs192000_Fc55_A1 0.000000f -#define BPF_Fs192000_Fc55_A2 -0.000381f -#define BPF_Fs192000_Fc55_B1 -1.999234f +#define BPF_Fs192000_Fc55_A2 (-0.000381f) +#define BPF_Fs192000_Fc55_B1 (-1.999234f) #define BPF_Fs192000_Fc55_B2 0.999238f #endif /* Coefficients for centre frequency 66Hz */ #define BPF_Fs8000_Fc66_A0 0.012648f #define BPF_Fs8000_Fc66_A1 0.000000f -#define BPF_Fs8000_Fc66_A2 -0.012648f -#define BPF_Fs8000_Fc66_B1 -1.971760f +#define BPF_Fs8000_Fc66_A2 (-0.012648f) +#define BPF_Fs8000_Fc66_B1 (-1.971760f) #define BPF_Fs8000_Fc66_B2 0.974412f #define BPF_Fs11025_Fc66_A0 0.009209f #define BPF_Fs11025_Fc66_A1 0.000000f -#define BPF_Fs11025_Fc66_A2 -0.009209f -#define BPF_Fs11025_Fc66_B1 -1.979966f +#define BPF_Fs11025_Fc66_A2 (-0.009209f) +#define BPF_Fs11025_Fc66_B1 (-1.979966f) #define BPF_Fs11025_Fc66_B2 0.981368f #define BPF_Fs12000_Fc66_A0 0.008468f #define BPF_Fs12000_Fc66_A1 0.000000f -#define BPF_Fs12000_Fc66_A2 -0.008468f -#define BPF_Fs12000_Fc66_B1 -1.981685f +#define BPF_Fs12000_Fc66_A2 (-0.008468f) +#define BPF_Fs12000_Fc66_B1 (-1.981685f) #define BPF_Fs12000_Fc66_B2 0.982869f #define BPF_Fs16000_Fc66_A0 0.006364f #define BPF_Fs16000_Fc66_A1 0.000000f -#define BPF_Fs16000_Fc66_A2 -0.006364f -#define BPF_Fs16000_Fc66_B1 -1.986457f +#define BPF_Fs16000_Fc66_A2 (-0.006364f) +#define BPF_Fs16000_Fc66_B1 (-1.986457f) #define BPF_Fs16000_Fc66_B2 0.987124f #define BPF_Fs22050_Fc66_A0 0.004626f #define BPF_Fs22050_Fc66_A1 0.000000f -#define BPF_Fs22050_Fc66_A2 -0.004626f -#define BPF_Fs22050_Fc66_B1 -1.990288f +#define BPF_Fs22050_Fc66_A2 (-0.004626f) +#define BPF_Fs22050_Fc66_B1 (-1.990288f) #define BPF_Fs22050_Fc66_B2 0.990641f #define BPF_Fs24000_Fc66_A0 0.004252f #define BPF_Fs24000_Fc66_A1 0.000000f -#define BPF_Fs24000_Fc66_A2 -0.004252f -#define BPF_Fs24000_Fc66_B1 -1.991100f +#define BPF_Fs24000_Fc66_A2 (-0.004252f) +#define BPF_Fs24000_Fc66_B1 (-1.991100f) #define BPF_Fs24000_Fc66_B2 0.991398f #define BPF_Fs32000_Fc66_A0 0.003192f #define BPF_Fs32000_Fc66_A1 0.000000f -#define BPF_Fs32000_Fc66_A2 -0.003192f -#define BPF_Fs32000_Fc66_B1 -1.993374f +#define BPF_Fs32000_Fc66_A2 (-0.003192f) +#define BPF_Fs32000_Fc66_B1 (-1.993374f) #define BPF_Fs32000_Fc66_B2 0.993541f #define BPF_Fs44100_Fc66_A0 0.002318f #define BPF_Fs44100_Fc66_A1 0.000000f -#define BPF_Fs44100_Fc66_A2 -0.002318f -#define BPF_Fs44100_Fc66_B1 -1.995221f +#define BPF_Fs44100_Fc66_A2 (-0.002318f) +#define BPF_Fs44100_Fc66_B1 (-1.995221f) #define BPF_Fs44100_Fc66_B2 0.995309f #define BPF_Fs48000_Fc66_A0 0.002131f #define BPF_Fs48000_Fc66_A1 0.000000f -#define BPF_Fs48000_Fc66_A2 -0.002131f -#define BPF_Fs48000_Fc66_B1 -1.995615f +#define BPF_Fs48000_Fc66_A2 (-0.002131f) +#define BPF_Fs48000_Fc66_B1 (-1.995615f) #define BPF_Fs48000_Fc66_B2 0.995690f #ifdef HIGHER_FS #define BPF_Fs96000_Fc66_A0 0.001055f #define BPF_Fs96000_Fc66_A1 0.000000f -#define BPF_Fs96000_Fc66_A2 -0.001055f -#define BPF_Fs96000_Fc66_B1 -1.997868f +#define BPF_Fs96000_Fc66_A2 (-0.001055f) +#define BPF_Fs96000_Fc66_B1 (-1.997868f) #define BPF_Fs96000_Fc66_B2 0.997891f #define BPF_Fs192000_Fc66_A0 0.000528f #define BPF_Fs192000_Fc66_A1 0.000000f -#define BPF_Fs192000_Fc66_A2 -0.000528f -#define BPF_Fs192000_Fc66_B1 -1.998939f +#define BPF_Fs192000_Fc66_A2 (-0.000528f) +#define BPF_Fs192000_Fc66_B1 (-1.998939f) #define BPF_Fs192000_Fc66_B2 0.998945f #endif /* Coefficients for centre frequency 78Hz */ #define BPF_Fs8000_Fc78_A0 0.018572f #define BPF_Fs8000_Fc78_A1 0.000000f -#define BPF_Fs8000_Fc78_A2 -0.018572f -#define BPF_Fs8000_Fc78_B1 -1.958745f +#define BPF_Fs8000_Fc78_A2 (-0.018572f) +#define BPF_Fs8000_Fc78_B1 (-1.958745f) #define BPF_Fs8000_Fc78_B2 0.962427f #define BPF_Fs11025_Fc78_A0 0.013545f #define BPF_Fs11025_Fc78_A1 0.000000f -#define BPF_Fs11025_Fc78_A2 -0.013545f -#define BPF_Fs11025_Fc78_B1 -1.970647f +#define BPF_Fs11025_Fc78_A2 (-0.013545f) +#define BPF_Fs11025_Fc78_B1 (-1.970647f) #define BPF_Fs11025_Fc78_B2 0.972596f #define BPF_Fs12000_Fc78_A0 0.012458f #define BPF_Fs12000_Fc78_A1 0.000000f -#define BPF_Fs12000_Fc78_A2 -0.012458f -#define BPF_Fs12000_Fc78_B1 -1.973148f +#define BPF_Fs12000_Fc78_A2 (-0.012458f) +#define BPF_Fs12000_Fc78_B1 (-1.973148f) #define BPF_Fs12000_Fc78_B2 0.974795f #define BPF_Fs16000_Fc78_A0 0.009373f #define BPF_Fs16000_Fc78_A1 0.000000f -#define BPF_Fs16000_Fc78_A2 -0.009373f -#define BPF_Fs16000_Fc78_B1 -1.980108f +#define BPF_Fs16000_Fc78_A2 (-0.009373f) +#define BPF_Fs16000_Fc78_B1 (-1.980108f) #define BPF_Fs16000_Fc78_B2 0.981037f #define BPF_Fs22050_Fc78_A0 0.006819f #define BPF_Fs22050_Fc78_A1 0.000000f -#define BPF_Fs22050_Fc78_A2 -0.006819f -#define BPF_Fs22050_Fc78_B1 -1.985714f +#define BPF_Fs22050_Fc78_A2 (-0.006819f) +#define BPF_Fs22050_Fc78_B1 (-1.985714f) #define BPF_Fs22050_Fc78_B2 0.986204f #define BPF_Fs24000_Fc78_A0 0.006268f #define BPF_Fs24000_Fc78_A1 0.000000f -#define BPF_Fs24000_Fc78_A2 -0.006268f -#define BPF_Fs24000_Fc78_B1 -1.986904f +#define BPF_Fs24000_Fc78_A2 (-0.006268f) +#define BPF_Fs24000_Fc78_B1 (-1.986904f) #define BPF_Fs24000_Fc78_B2 0.987318f #define BPF_Fs32000_Fc78_A0 0.004709f #define BPF_Fs32000_Fc78_A1 0.000000f -#define BPF_Fs32000_Fc78_A2 -0.004709f -#define BPF_Fs32000_Fc78_B1 -1.990240f +#define BPF_Fs32000_Fc78_A2 (-0.004709f) +#define BPF_Fs32000_Fc78_B1 (-1.990240f) #define BPF_Fs32000_Fc78_B2 0.990473f #define BPF_Fs44100_Fc78_A0 0.003421f #define BPF_Fs44100_Fc78_A1 0.000000f -#define BPF_Fs44100_Fc78_A2 -0.003421f -#define BPF_Fs44100_Fc78_B1 -1.992955f +#define BPF_Fs44100_Fc78_A2 (-0.003421f) +#define BPF_Fs44100_Fc78_B1 (-1.992955f) #define BPF_Fs44100_Fc78_B2 0.993078f #define BPF_Fs48000_Fc78_A0 0.003144f #define BPF_Fs48000_Fc78_A1 0.000000f -#define BPF_Fs48000_Fc78_A2 -0.003144f -#define BPF_Fs48000_Fc78_B1 -1.993535f +#define BPF_Fs48000_Fc78_A2 (-0.003144f) +#define BPF_Fs48000_Fc78_B1 (-1.993535f) #define BPF_Fs48000_Fc78_B2 0.993639f #ifdef HIGHER_FS #define BPF_Fs96000_Fc78_A0 0.001555f #define BPF_Fs96000_Fc78_A1 0.000000f -#define BPF_Fs96000_Fc78_A2 -0.0015555f -#define BPF_Fs96000_Fc78_B1 -1.996860f +#define BPF_Fs96000_Fc78_A2 (-0.0015555f) +#define BPF_Fs96000_Fc78_B1 (-1.996860f) #define BPF_Fs96000_Fc78_B2 0.996891f #define BPF_Fs192000_Fc78_A0 0.000778f #define BPF_Fs192000_Fc78_A1 0.000000f -#define BPF_Fs192000_Fc78_A2 -0.000778f -#define BPF_Fs192000_Fc78_B1 -1.998437f +#define BPF_Fs192000_Fc78_A2 (-0.000778f) +#define BPF_Fs192000_Fc78_B1 (-1.998437f) #define BPF_Fs192000_Fc78_B2 0.998444f #endif /* Coefficients for centre frequency 90Hz */ #define BPF_Fs8000_Fc90_A0 0.022760f #define BPF_Fs8000_Fc90_A1 0.000000f -#define BPF_Fs8000_Fc90_A2 -0.022760f -#define BPF_Fs8000_Fc90_B1 -1.949073f +#define BPF_Fs8000_Fc90_A2 (-0.022760f) +#define BPF_Fs8000_Fc90_B1 (-1.949073f) #define BPF_Fs8000_Fc90_B2 0.953953f #define BPF_Fs11025_Fc90_A0 0.016619f #define BPF_Fs11025_Fc90_A1 0.000000f -#define BPF_Fs11025_Fc90_A2 -0.016619f -#define BPF_Fs11025_Fc90_B1 -1.963791f +#define BPF_Fs11025_Fc90_A2 (-0.016619f) +#define BPF_Fs11025_Fc90_B1 (-1.963791f) #define BPF_Fs11025_Fc90_B2 0.966377f #define BPF_Fs12000_Fc90_A0 0.015289f #define BPF_Fs12000_Fc90_A1 0.000000f -#define BPF_Fs12000_Fc90_A2 -0.015289f -#define BPF_Fs12000_Fc90_B1 -1.966882f +#define BPF_Fs12000_Fc90_A2 (-0.015289f) +#define BPF_Fs12000_Fc90_B1 (-1.966882f) #define BPF_Fs12000_Fc90_B2 0.969067f #define BPF_Fs16000_Fc90_A0 0.011511f #define BPF_Fs16000_Fc90_A1 0.000000f -#define BPF_Fs16000_Fc90_A2 -0.011511f -#define BPF_Fs16000_Fc90_B1 -1.975477f +#define BPF_Fs16000_Fc90_A2 (-0.011511f) +#define BPF_Fs16000_Fc90_B1 (-1.975477f) #define BPF_Fs16000_Fc90_B2 0.976711f #define BPF_Fs22050_Fc90_A0 0.008379f #define BPF_Fs22050_Fc90_A1 0.000000f -#define BPF_Fs22050_Fc90_A2 -0.008379f -#define BPF_Fs22050_Fc90_B1 -1.982395f +#define BPF_Fs22050_Fc90_A2 (-0.008379f) +#define BPF_Fs22050_Fc90_B1 (-1.982395f) #define BPF_Fs22050_Fc90_B2 0.983047f #define BPF_Fs24000_Fc90_A0 0.007704f #define BPF_Fs24000_Fc90_A1 0.000000f -#define BPF_Fs24000_Fc90_A2 -0.007704f -#define BPF_Fs24000_Fc90_B1 -1.983863f +#define BPF_Fs24000_Fc90_A2 (-0.007704f) +#define BPF_Fs24000_Fc90_B1 (-1.983863f) #define BPF_Fs24000_Fc90_B2 0.984414f #define BPF_Fs32000_Fc90_A0 0.005789f #define BPF_Fs32000_Fc90_A1 0.000000f -#define BPF_Fs32000_Fc90_A2 -0.005789f -#define BPF_Fs32000_Fc90_B1 -1.987977f +#define BPF_Fs32000_Fc90_A2 (-0.005789f) +#define BPF_Fs32000_Fc90_B1 (-1.987977f) #define BPF_Fs32000_Fc90_B2 0.988288f #define BPF_Fs44100_Fc90_A0 0.004207f #define BPF_Fs44100_Fc90_A1 0.000000f -#define BPF_Fs44100_Fc90_A2 -0.004207f -#define BPF_Fs44100_Fc90_B1 -1.991324f +#define BPF_Fs44100_Fc90_A2 (-0.004207f) +#define BPF_Fs44100_Fc90_B1 (-1.991324f) #define BPF_Fs44100_Fc90_B2 0.991488f #define BPF_Fs48000_Fc90_A0 0.003867f #define BPF_Fs48000_Fc90_A1 0.000000f -#define BPF_Fs48000_Fc90_A2 -0.003867f -#define BPF_Fs48000_Fc90_B1 -1.992038f +#define BPF_Fs48000_Fc90_A2 (-0.003867f) +#define BPF_Fs48000_Fc90_B1 (-1.992038f) #define BPF_Fs48000_Fc90_B2 0.992177f #ifdef HIGHER_FS #define BPF_Fs96000_Fc90_A0 0.001913f #define BPF_Fs96000_Fc90_A1 0.000000f -#define BPF_Fs96000_Fc90_A2 -0.001913f -#define BPF_Fs96000_Fc90_B1 -1.996134f +#define BPF_Fs96000_Fc90_A2 (-0.001913f) +#define BPF_Fs96000_Fc90_B1 (-1.996134f) #define BPF_Fs96000_Fc90_B2 0.996174f #define BPF_Fs192000_Fc90_A0 0.000958f #define BPF_Fs192000_Fc90_A1 0.000000f -#define BPF_Fs192000_Fc90_A2 -0.000958f -#define BPF_Fs192000_Fc90_B1 -1.998075f +#define BPF_Fs192000_Fc90_A2 (-0.000958f) +#define BPF_Fs192000_Fc90_B1 (-1.998075f) #define BPF_Fs192000_Fc90_B2 0.998085f #endif
diff --git a/media/libeffects/lvm/lib/Bundle/src/LVM_Coeffs.h b/media/libeffects/lvm/lib/Bundle/src/LVM_Coeffs.h index 353560c..8c04847 100644 --- a/media/libeffects/lvm/lib/Bundle/src/LVM_Coeffs.h +++ b/media/libeffects/lvm/lib/Bundle/src/LVM_Coeffs.h
@@ -69,55 +69,55 @@ #define HPF_Fs22050_Gain6_B2 0.000000 /* Gain = 7.000000 dB */ #define HPF_Fs22050_Gain7_A0 1.390177 -#define HPF_Fs22050_Gain7_A1 -0.020144 +#define HPF_Fs22050_Gain7_A1 (-0.020144) #define HPF_Fs22050_Gain7_A2 0.000000 #define HPF_Fs22050_Gain7_B1 0.370033 #define HPF_Fs22050_Gain7_B2 0.000000 /* Gain = 8.000000 dB */ #define HPF_Fs22050_Gain8_A0 1.476219 -#define HPF_Fs22050_Gain8_A1 -0.106187 +#define HPF_Fs22050_Gain8_A1 (-0.106187) #define HPF_Fs22050_Gain8_A2 0.000000 #define HPF_Fs22050_Gain8_B1 0.370033 #define HPF_Fs22050_Gain8_B2 0.000000 /* Gain = 9.000000 dB */ #define HPF_Fs22050_Gain9_A0 1.572761 -#define HPF_Fs22050_Gain9_A1 -0.202728 +#define HPF_Fs22050_Gain9_A1 (-0.202728) #define HPF_Fs22050_Gain9_A2 0.000000 #define HPF_Fs22050_Gain9_B1 0.370033 #define HPF_Fs22050_Gain9_B2 0.000000 /* Gain = 10.000000 dB */ #define HPF_Fs22050_Gain10_A0 1.681082 -#define HPF_Fs22050_Gain10_A1 -0.311049 +#define HPF_Fs22050_Gain10_A1 (-0.311049) #define HPF_Fs22050_Gain10_A2 0.000000 #define HPF_Fs22050_Gain10_B1 0.370033 #define HPF_Fs22050_Gain10_B2 0.000000 /* Gain = 11.000000 dB */ #define HPF_Fs22050_Gain11_A0 1.802620 -#define HPF_Fs22050_Gain11_A1 -0.432588 +#define HPF_Fs22050_Gain11_A1 (-0.432588) #define HPF_Fs22050_Gain11_A2 0.000000 #define HPF_Fs22050_Gain11_B1 0.370033 #define HPF_Fs22050_Gain11_B2 0.000000 /* Gain = 12.000000 dB */ #define HPF_Fs22050_Gain12_A0 1.938989 -#define HPF_Fs22050_Gain12_A1 -0.568956 +#define HPF_Fs22050_Gain12_A1 (-0.568956) #define HPF_Fs22050_Gain12_A2 0.000000 #define HPF_Fs22050_Gain12_B1 0.370033 #define HPF_Fs22050_Gain12_B2 0.000000 /* Gain = 13.000000 dB */ #define HPF_Fs22050_Gain13_A0 2.091997 -#define HPF_Fs22050_Gain13_A1 -0.721964 +#define HPF_Fs22050_Gain13_A1 (-0.721964) #define HPF_Fs22050_Gain13_A2 0.000000 #define HPF_Fs22050_Gain13_B1 0.370033 #define HPF_Fs22050_Gain13_B2 0.000000 /* Gain = 14.000000 dB */ #define HPF_Fs22050_Gain14_A0 2.263674 -#define HPF_Fs22050_Gain14_A1 -0.893641 +#define HPF_Fs22050_Gain14_A1 (-0.893641) #define HPF_Fs22050_Gain14_A2 0.000000 #define HPF_Fs22050_Gain14_B1 0.370033 #define HPF_Fs22050_Gain14_B2 0.000000 /* Gain = 15.000000 dB */ #define HPF_Fs22050_Gain15_A0 2.456300 -#define HPF_Fs22050_Gain15_A1 -1.086267 +#define HPF_Fs22050_Gain15_A1 (-1.086267) #define HPF_Fs22050_Gain15_A2 0.000000 #define HPF_Fs22050_Gain15_B1 0.370033 #define HPF_Fs22050_Gain15_B2 0.000000 @@ -148,342 +148,342 @@ #define HPF_Fs24000_Gain4_B2 0.000000 /* Gain = 5.000000 dB */ #define HPF_Fs24000_Gain5_A0 1.284870 -#define HPF_Fs24000_Gain5_A1 -0.016921 +#define HPF_Fs24000_Gain5_A1 (-0.016921) #define HPF_Fs24000_Gain5_A2 0.000000 #define HPF_Fs24000_Gain5_B1 0.267949 #define HPF_Fs24000_Gain5_B2 0.000000 /* Gain = 6.000000 dB */ #define HPF_Fs24000_Gain6_A0 1.364291 -#define HPF_Fs24000_Gain6_A1 -0.096342 +#define HPF_Fs24000_Gain6_A1 (-0.096342) #define HPF_Fs24000_Gain6_A2 0.000000 #define HPF_Fs24000_Gain6_B1 0.267949 #define HPF_Fs24000_Gain6_B2 0.000000 /* Gain = 7.000000 dB */ #define HPF_Fs24000_Gain7_A0 1.453403 -#define HPF_Fs24000_Gain7_A1 -0.185454 +#define HPF_Fs24000_Gain7_A1 (-0.185454) #define HPF_Fs24000_Gain7_A2 0.000000 #define HPF_Fs24000_Gain7_B1 0.267949 #define HPF_Fs24000_Gain7_B2 0.000000 /* Gain = 8.000000 dB */ #define HPF_Fs24000_Gain8_A0 1.553389 -#define HPF_Fs24000_Gain8_A1 -0.285440 +#define HPF_Fs24000_Gain8_A1 (-0.285440) #define HPF_Fs24000_Gain8_A2 0.000000 #define HPF_Fs24000_Gain8_B1 0.267949 #define HPF_Fs24000_Gain8_B2 0.000000 /* Gain = 9.000000 dB */ #define HPF_Fs24000_Gain9_A0 1.665574 -#define HPF_Fs24000_Gain9_A1 -0.397625 +#define HPF_Fs24000_Gain9_A1 (-0.397625) #define HPF_Fs24000_Gain9_A2 0.000000 #define HPF_Fs24000_Gain9_B1 0.267949 #define HPF_Fs24000_Gain9_B2 0.000000 /* Gain = 10.000000 dB */ #define HPF_Fs24000_Gain10_A0 1.791449 -#define HPF_Fs24000_Gain10_A1 -0.523499 +#define HPF_Fs24000_Gain10_A1 (-0.523499) #define HPF_Fs24000_Gain10_A2 0.000000 #define HPF_Fs24000_Gain10_B1 0.267949 #define HPF_Fs24000_Gain10_B2 0.000000 /* Gain = 11.000000 dB */ #define HPF_Fs24000_Gain11_A0 1.932682 -#define HPF_Fs24000_Gain11_A1 -0.664733 +#define HPF_Fs24000_Gain11_A1 (-0.664733) #define HPF_Fs24000_Gain11_A2 0.000000 #define HPF_Fs24000_Gain11_B1 0.267949 #define HPF_Fs24000_Gain11_B2 0.000000 /* Gain = 12.000000 dB */ #define HPF_Fs24000_Gain12_A0 2.091148 -#define HPF_Fs24000_Gain12_A1 -0.823199 +#define HPF_Fs24000_Gain12_A1 (-0.823199) #define HPF_Fs24000_Gain12_A2 0.000000 #define HPF_Fs24000_Gain12_B1 0.267949 #define HPF_Fs24000_Gain12_B2 0.000000 /* Gain = 13.000000 dB */ #define HPF_Fs24000_Gain13_A0 2.268950 -#define HPF_Fs24000_Gain13_A1 -1.001001 +#define HPF_Fs24000_Gain13_A1 (-1.001001) #define HPF_Fs24000_Gain13_A2 0.000000 #define HPF_Fs24000_Gain13_B1 0.267949 #define HPF_Fs24000_Gain13_B2 0.000000 /* Gain = 14.000000 dB */ #define HPF_Fs24000_Gain14_A0 2.468447 -#define HPF_Fs24000_Gain14_A1 -1.200498 +#define HPF_Fs24000_Gain14_A1 (-1.200498) #define HPF_Fs24000_Gain14_A2 0.000000 #define HPF_Fs24000_Gain14_B1 0.267949 #define HPF_Fs24000_Gain14_B2 0.000000 /* Gain = 15.000000 dB */ #define HPF_Fs24000_Gain15_A0 2.692287 -#define HPF_Fs24000_Gain15_A1 -1.424338 +#define HPF_Fs24000_Gain15_A1 (-1.424338) #define HPF_Fs24000_Gain15_A2 0.000000 #define HPF_Fs24000_Gain15_B1 0.267949 #define HPF_Fs24000_Gain15_B2 0.000000 /* Coefficients for sample rate 32000Hz */ /* Gain = 1.000000 dB */ #define HPF_Fs32000_Gain1_A0 1.061009 -#define HPF_Fs32000_Gain1_A1 -0.061009 +#define HPF_Fs32000_Gain1_A1 (-0.061009) #define HPF_Fs32000_Gain1_A2 0.000000 -#define HPF_Fs32000_Gain1_B1 -0.000000 +#define HPF_Fs32000_Gain1_B1 (-0.000000) #define HPF_Fs32000_Gain1_B2 0.000000 /* Gain = 2.000000 dB */ #define HPF_Fs32000_Gain2_A0 1.129463 -#define HPF_Fs32000_Gain2_A1 -0.129463 +#define HPF_Fs32000_Gain2_A1 (-0.129463) #define HPF_Fs32000_Gain2_A2 0.000000 -#define HPF_Fs32000_Gain2_B1 -0.000000 +#define HPF_Fs32000_Gain2_B1 (-0.000000) #define HPF_Fs32000_Gain2_B2 0.000000 /* Gain = 3.000000 dB */ #define HPF_Fs32000_Gain3_A0 1.206267 -#define HPF_Fs32000_Gain3_A1 -0.206267 +#define HPF_Fs32000_Gain3_A1 (-0.206267) #define HPF_Fs32000_Gain3_A2 0.000000 -#define HPF_Fs32000_Gain3_B1 -0.000000 +#define HPF_Fs32000_Gain3_B1 (-0.000000) #define HPF_Fs32000_Gain3_B2 0.000000 /* Gain = 4.000000 dB */ #define HPF_Fs32000_Gain4_A0 1.292447 -#define HPF_Fs32000_Gain4_A1 -0.292447 +#define HPF_Fs32000_Gain4_A1 (-0.292447) #define HPF_Fs32000_Gain4_A2 0.000000 -#define HPF_Fs32000_Gain4_B1 -0.000000 +#define HPF_Fs32000_Gain4_B1 (-0.000000) #define HPF_Fs32000_Gain4_B2 0.000000 /* Gain = 5.000000 dB */ #define HPF_Fs32000_Gain5_A0 1.389140 -#define HPF_Fs32000_Gain5_A1 -0.389140 +#define HPF_Fs32000_Gain5_A1 (-0.389140) #define HPF_Fs32000_Gain5_A2 0.000000 -#define HPF_Fs32000_Gain5_B1 -0.000000 +#define HPF_Fs32000_Gain5_B1 (-0.000000) #define HPF_Fs32000_Gain5_B2 0.000000 /* Gain = 6.000000 dB */ #define HPF_Fs32000_Gain6_A0 1.497631 -#define HPF_Fs32000_Gain6_A1 -0.497631 +#define HPF_Fs32000_Gain6_A1 (-0.497631) #define HPF_Fs32000_Gain6_A2 0.000000 -#define HPF_Fs32000_Gain6_B1 -0.000000 +#define HPF_Fs32000_Gain6_B1 (-0.000000) #define HPF_Fs32000_Gain6_B2 0.000000 /* Gain = 7.000000 dB */ #define HPF_Fs32000_Gain7_A0 1.619361 -#define HPF_Fs32000_Gain7_A1 -0.619361 +#define HPF_Fs32000_Gain7_A1 (-0.619361) #define HPF_Fs32000_Gain7_A2 0.000000 -#define HPF_Fs32000_Gain7_B1 -0.000000 +#define HPF_Fs32000_Gain7_B1 (-0.000000) #define HPF_Fs32000_Gain7_B2 0.000000 /* Gain = 8.000000 dB */ #define HPF_Fs32000_Gain8_A0 1.755943 -#define HPF_Fs32000_Gain8_A1 -0.755943 +#define HPF_Fs32000_Gain8_A1 (-0.755943) #define HPF_Fs32000_Gain8_A2 0.000000 -#define HPF_Fs32000_Gain8_B1 -0.000000 +#define HPF_Fs32000_Gain8_B1 (-0.000000) #define HPF_Fs32000_Gain8_B2 0.000000 /* Gain = 9.000000 dB */ #define HPF_Fs32000_Gain9_A0 1.909191 -#define HPF_Fs32000_Gain9_A1 -0.909191 +#define HPF_Fs32000_Gain9_A1 (-0.909191) #define HPF_Fs32000_Gain9_A2 0.000000 -#define HPF_Fs32000_Gain9_B1 -0.000000 +#define HPF_Fs32000_Gain9_B1 (-0.000000) #define HPF_Fs32000_Gain9_B2 0.000000 /* Gain = 10.000000 dB */ #define HPF_Fs32000_Gain10_A0 2.081139 -#define HPF_Fs32000_Gain10_A1 -1.081139 +#define HPF_Fs32000_Gain10_A1 (-1.081139) #define HPF_Fs32000_Gain10_A2 0.000000 -#define HPF_Fs32000_Gain10_B1 -0.000000 +#define HPF_Fs32000_Gain10_B1 (-0.000000) #define HPF_Fs32000_Gain10_B2 0.000000 /* Gain = 11.000000 dB */ #define HPF_Fs32000_Gain11_A0 2.274067 -#define HPF_Fs32000_Gain11_A1 -1.274067 +#define HPF_Fs32000_Gain11_A1 (-1.274067) #define HPF_Fs32000_Gain11_A2 0.000000 -#define HPF_Fs32000_Gain11_B1 -0.000000 +#define HPF_Fs32000_Gain11_B1 (-0.000000) #define HPF_Fs32000_Gain11_B2 0.000000 /* Gain = 12.000000 dB */ #define HPF_Fs32000_Gain12_A0 2.490536 -#define HPF_Fs32000_Gain12_A1 -1.490536 +#define HPF_Fs32000_Gain12_A1 (-1.490536) #define HPF_Fs32000_Gain12_A2 0.000000 -#define HPF_Fs32000_Gain12_B1 -0.000000 +#define HPF_Fs32000_Gain12_B1 (-0.000000) #define HPF_Fs32000_Gain12_B2 0.000000 /* Gain = 13.000000 dB */ #define HPF_Fs32000_Gain13_A0 2.733418 -#define HPF_Fs32000_Gain13_A1 -1.733418 +#define HPF_Fs32000_Gain13_A1 (-1.733418) #define HPF_Fs32000_Gain13_A2 0.000000 -#define HPF_Fs32000_Gain13_B1 -0.000000 +#define HPF_Fs32000_Gain13_B1 (-0.000000) #define HPF_Fs32000_Gain13_B2 0.000000 /* Gain = 14.000000 dB */ #define HPF_Fs32000_Gain14_A0 3.005936 -#define HPF_Fs32000_Gain14_A1 -2.005936 +#define HPF_Fs32000_Gain14_A1 (-2.005936) #define HPF_Fs32000_Gain14_A2 0.000000 -#define HPF_Fs32000_Gain14_B1 -0.000000 +#define HPF_Fs32000_Gain14_B1 (-0.000000) #define HPF_Fs32000_Gain14_B2 0.000000 /* Gain = 15.000000 dB */ #define HPF_Fs32000_Gain15_A0 3.311707 -#define HPF_Fs32000_Gain15_A1 -2.311707 +#define HPF_Fs32000_Gain15_A1 (-2.311707) #define HPF_Fs32000_Gain15_A2 0.000000 -#define HPF_Fs32000_Gain15_B1 -0.000000 +#define HPF_Fs32000_Gain15_B1 (-0.000000) #define HPF_Fs32000_Gain15_B2 0.000000 /* Coefficients for sample rate 44100Hz */ /* Gain = 1.000000 dB */ #define HPF_Fs44100_Gain1_A0 1.074364 -#define HPF_Fs44100_Gain1_A1 -0.293257 +#define HPF_Fs44100_Gain1_A1 (-0.293257) #define HPF_Fs44100_Gain1_A2 0.000000 -#define HPF_Fs44100_Gain1_B1 -0.218894 +#define HPF_Fs44100_Gain1_B1 (-0.218894) #define HPF_Fs44100_Gain1_B2 0.000000 /* Gain = 2.000000 dB */ #define HPF_Fs44100_Gain2_A0 1.157801 -#define HPF_Fs44100_Gain2_A1 -0.376695 +#define HPF_Fs44100_Gain2_A1 (-0.376695) #define HPF_Fs44100_Gain2_A2 0.000000 -#define HPF_Fs44100_Gain2_B1 -0.218894 +#define HPF_Fs44100_Gain2_B1 (-0.218894) #define HPF_Fs44100_Gain2_B2 0.000000 /* Gain = 3.000000 dB */ #define HPF_Fs44100_Gain3_A0 1.251420 -#define HPF_Fs44100_Gain3_A1 -0.470313 +#define HPF_Fs44100_Gain3_A1 (-0.470313) #define HPF_Fs44100_Gain3_A2 0.000000 -#define HPF_Fs44100_Gain3_B1 -0.218894 +#define HPF_Fs44100_Gain3_B1 (-0.218894) #define HPF_Fs44100_Gain3_B2 0.000000 /* Gain = 4.000000 dB */ #define HPF_Fs44100_Gain4_A0 1.356461 -#define HPF_Fs44100_Gain4_A1 -0.575355 +#define HPF_Fs44100_Gain4_A1 (-0.575355) #define HPF_Fs44100_Gain4_A2 0.000000 -#define HPF_Fs44100_Gain4_B1 -0.218894 +#define HPF_Fs44100_Gain4_B1 (-0.218894) #define HPF_Fs44100_Gain4_B2 0.000000 /* Gain = 5.000000 dB */ #define HPF_Fs44100_Gain5_A0 1.474320 -#define HPF_Fs44100_Gain5_A1 -0.693213 +#define HPF_Fs44100_Gain5_A1 (-0.693213) #define HPF_Fs44100_Gain5_A2 0.000000 -#define HPF_Fs44100_Gain5_B1 -0.218894 +#define HPF_Fs44100_Gain5_B1 (-0.218894) #define HPF_Fs44100_Gain5_B2 0.000000 /* Gain = 6.000000 dB */ #define HPF_Fs44100_Gain6_A0 1.606559 -#define HPF_Fs44100_Gain6_A1 -0.825453 +#define HPF_Fs44100_Gain6_A1 (-0.825453) #define HPF_Fs44100_Gain6_A2 0.000000 -#define HPF_Fs44100_Gain6_B1 -0.218894 +#define HPF_Fs44100_Gain6_B1 (-0.218894) #define HPF_Fs44100_Gain6_B2 0.000000 /* Gain = 7.000000 dB */ #define HPF_Fs44100_Gain7_A0 1.754935 -#define HPF_Fs44100_Gain7_A1 -0.973828 +#define HPF_Fs44100_Gain7_A1 (-0.973828) #define HPF_Fs44100_Gain7_A2 0.000000 -#define HPF_Fs44100_Gain7_B1 -0.218894 +#define HPF_Fs44100_Gain7_B1 (-0.218894) #define HPF_Fs44100_Gain7_B2 0.000000 /* Gain = 8.000000 dB */ #define HPF_Fs44100_Gain8_A0 1.921414 -#define HPF_Fs44100_Gain8_A1 -1.140308 +#define HPF_Fs44100_Gain8_A1 (-1.140308) #define HPF_Fs44100_Gain8_A2 0.000000 -#define HPF_Fs44100_Gain8_B1 -0.218894 +#define HPF_Fs44100_Gain8_B1 (-0.218894) #define HPF_Fs44100_Gain8_B2 0.000000 /* Gain = 9.000000 dB */ #define HPF_Fs44100_Gain9_A0 2.108208 -#define HPF_Fs44100_Gain9_A1 -1.327101 +#define HPF_Fs44100_Gain9_A1 (-1.327101) #define HPF_Fs44100_Gain9_A2 0.000000 -#define HPF_Fs44100_Gain9_B1 -0.218894 +#define HPF_Fs44100_Gain9_B1 (-0.218894) #define HPF_Fs44100_Gain9_B2 0.000000 /* Gain = 10.000000 dB */ #define HPF_Fs44100_Gain10_A0 2.317793 -#define HPF_Fs44100_Gain10_A1 -1.536687 +#define HPF_Fs44100_Gain10_A1 (-1.536687) #define HPF_Fs44100_Gain10_A2 0.000000 -#define HPF_Fs44100_Gain10_B1 -0.218894 +#define HPF_Fs44100_Gain10_B1 (-0.218894) #define HPF_Fs44100_Gain10_B2 0.000000 /* Gain = 11.000000 dB */ #define HPF_Fs44100_Gain11_A0 2.552952 -#define HPF_Fs44100_Gain11_A1 -1.771846 +#define HPF_Fs44100_Gain11_A1 (-1.771846) #define HPF_Fs44100_Gain11_A2 0.000000 -#define HPF_Fs44100_Gain11_B1 -0.218894 +#define HPF_Fs44100_Gain11_B1 (-0.218894) #define HPF_Fs44100_Gain11_B2 0.000000 /* Gain = 12.000000 dB */ #define HPF_Fs44100_Gain12_A0 2.816805 -#define HPF_Fs44100_Gain12_A1 -2.035698 +#define HPF_Fs44100_Gain12_A1 (-2.035698) #define HPF_Fs44100_Gain12_A2 0.000000 -#define HPF_Fs44100_Gain12_B1 -0.218894 +#define HPF_Fs44100_Gain12_B1 (-0.218894) #define HPF_Fs44100_Gain12_B2 0.000000 /* Gain = 13.000000 dB */ #define HPF_Fs44100_Gain13_A0 3.112852 -#define HPF_Fs44100_Gain13_A1 -2.331746 +#define HPF_Fs44100_Gain13_A1 (-2.331746) #define HPF_Fs44100_Gain13_A2 0.000000 -#define HPF_Fs44100_Gain13_B1 -0.218894 +#define HPF_Fs44100_Gain13_B1 (-0.218894) #define HPF_Fs44100_Gain13_B2 0.000000 /* Gain = 14.000000 dB */ #define HPF_Fs44100_Gain14_A0 3.445023 -#define HPF_Fs44100_Gain14_A1 -2.663916 +#define HPF_Fs44100_Gain14_A1 (-2.663916) #define HPF_Fs44100_Gain14_A2 0.000000 -#define HPF_Fs44100_Gain14_B1 -0.218894 +#define HPF_Fs44100_Gain14_B1 (-0.218894) #define HPF_Fs44100_Gain14_B2 0.000000 /* Gain = 15.000000 dB */ #define HPF_Fs44100_Gain15_A0 3.817724 -#define HPF_Fs44100_Gain15_A1 -3.036618 +#define HPF_Fs44100_Gain15_A1 (-3.036618) #define HPF_Fs44100_Gain15_A2 0.000000 -#define HPF_Fs44100_Gain15_B1 -0.218894 +#define HPF_Fs44100_Gain15_B1 (-0.218894) #define HPF_Fs44100_Gain15_B2 0.000000 /* Coefficients for sample rate 48000Hz */ /* Gain = 1.000000 dB */ #define HPF_Fs48000_Gain1_A0 1.077357 -#define HPF_Fs48000_Gain1_A1 -0.345306 +#define HPF_Fs48000_Gain1_A1 (-0.345306) #define HPF_Fs48000_Gain1_A2 0.000000 -#define HPF_Fs48000_Gain1_B1 -0.267949 +#define HPF_Fs48000_Gain1_B1 (-0.267949) #define HPF_Fs48000_Gain1_B2 0.000000 /* Gain = 2.000000 dB */ #define HPF_Fs48000_Gain2_A0 1.164152 -#define HPF_Fs48000_Gain2_A1 -0.432101 +#define HPF_Fs48000_Gain2_A1 (-0.432101) #define HPF_Fs48000_Gain2_A2 0.000000 -#define HPF_Fs48000_Gain2_B1 -0.267949 +#define HPF_Fs48000_Gain2_B1 (-0.267949) #define HPF_Fs48000_Gain2_B2 0.000000 /* Gain = 3.000000 dB */ #define HPF_Fs48000_Gain3_A0 1.261538 -#define HPF_Fs48000_Gain3_A1 -0.529488 +#define HPF_Fs48000_Gain3_A1 (-0.529488) #define HPF_Fs48000_Gain3_A2 0.000000 -#define HPF_Fs48000_Gain3_B1 -0.267949 +#define HPF_Fs48000_Gain3_B1 (-0.267949) #define HPF_Fs48000_Gain3_B2 0.000000 /* Gain = 4.000000 dB */ #define HPF_Fs48000_Gain4_A0 1.370807 -#define HPF_Fs48000_Gain4_A1 -0.638757 +#define HPF_Fs48000_Gain4_A1 (-0.638757) #define HPF_Fs48000_Gain4_A2 0.000000 -#define HPF_Fs48000_Gain4_B1 -0.267949 +#define HPF_Fs48000_Gain4_B1 (-0.267949) #define HPF_Fs48000_Gain4_B2 0.000000 /* Gain = 5.000000 dB */ #define HPF_Fs48000_Gain5_A0 1.493409 -#define HPF_Fs48000_Gain5_A1 -0.761359 +#define HPF_Fs48000_Gain5_A1 (-0.761359) #define HPF_Fs48000_Gain5_A2 0.000000 -#define HPF_Fs48000_Gain5_B1 -0.267949 +#define HPF_Fs48000_Gain5_B1 (-0.267949) #define HPF_Fs48000_Gain5_B2 0.000000 /* Gain = 6.000000 dB */ #define HPF_Fs48000_Gain6_A0 1.630971 -#define HPF_Fs48000_Gain6_A1 -0.898920 +#define HPF_Fs48000_Gain6_A1 (-0.898920) #define HPF_Fs48000_Gain6_A2 0.000000 -#define HPF_Fs48000_Gain6_B1 -0.267949 +#define HPF_Fs48000_Gain6_B1 (-0.267949) #define HPF_Fs48000_Gain6_B2 0.000000 /* Gain = 7.000000 dB */ #define HPF_Fs48000_Gain7_A0 1.785318 -#define HPF_Fs48000_Gain7_A1 -1.053267 +#define HPF_Fs48000_Gain7_A1 (-1.053267) #define HPF_Fs48000_Gain7_A2 0.000000 -#define HPF_Fs48000_Gain7_B1 -0.267949 +#define HPF_Fs48000_Gain7_B1 (-0.267949) #define HPF_Fs48000_Gain7_B2 0.000000 /* Gain = 8.000000 dB */ #define HPF_Fs48000_Gain8_A0 1.958498 -#define HPF_Fs48000_Gain8_A1 -1.226447 +#define HPF_Fs48000_Gain8_A1 (-1.226447) #define HPF_Fs48000_Gain8_A2 0.000000 -#define HPF_Fs48000_Gain8_B1 -0.267949 +#define HPF_Fs48000_Gain8_B1 (-0.267949) #define HPF_Fs48000_Gain8_B2 0.000000 /* Gain = 9.000000 dB */ #define HPF_Fs48000_Gain9_A0 2.152809 -#define HPF_Fs48000_Gain9_A1 -1.420758 +#define HPF_Fs48000_Gain9_A1 (-1.420758) #define HPF_Fs48000_Gain9_A2 0.000000 -#define HPF_Fs48000_Gain9_B1 -0.267949 +#define HPF_Fs48000_Gain9_B1 (-0.267949) #define HPF_Fs48000_Gain9_B2 0.000000 /* Gain = 10.000000 dB */ #define HPF_Fs48000_Gain10_A0 2.370829 -#define HPF_Fs48000_Gain10_A1 -1.638778 +#define HPF_Fs48000_Gain10_A1 (-1.638778) #define HPF_Fs48000_Gain10_A2 0.000000 -#define HPF_Fs48000_Gain10_B1 -0.267949 +#define HPF_Fs48000_Gain10_B1 (-0.267949) #define HPF_Fs48000_Gain10_B2 0.000000 /* Gain = 11.000000 dB */ #define HPF_Fs48000_Gain11_A0 2.615452 -#define HPF_Fs48000_Gain11_A1 -1.883401 +#define HPF_Fs48000_Gain11_A1 (-1.883401) #define HPF_Fs48000_Gain11_A2 0.000000 -#define HPF_Fs48000_Gain11_B1 -0.267949 +#define HPF_Fs48000_Gain11_B1 (-0.267949) #define HPF_Fs48000_Gain11_B2 0.000000 /* Gain = 12.000000 dB */ #define HPF_Fs48000_Gain12_A0 2.889924 -#define HPF_Fs48000_Gain12_A1 -2.157873 +#define HPF_Fs48000_Gain12_A1 (-2.157873) #define HPF_Fs48000_Gain12_A2 0.000000 -#define HPF_Fs48000_Gain12_B1 -0.267949 +#define HPF_Fs48000_Gain12_B1 (-0.267949) #define HPF_Fs48000_Gain12_B2 0.000000 /* Gain = 13.000000 dB */ #define HPF_Fs48000_Gain13_A0 3.197886 -#define HPF_Fs48000_Gain13_A1 -2.465835 +#define HPF_Fs48000_Gain13_A1 (-2.465835) #define HPF_Fs48000_Gain13_A2 0.000000 -#define HPF_Fs48000_Gain13_B1 -0.267949 +#define HPF_Fs48000_Gain13_B1 (-0.267949) #define HPF_Fs48000_Gain13_B2 0.000000 /* Gain = 14.000000 dB */ #define HPF_Fs48000_Gain14_A0 3.543425 -#define HPF_Fs48000_Gain14_A1 -2.811374 +#define HPF_Fs48000_Gain14_A1 (-2.811374) #define HPF_Fs48000_Gain14_A2 0.000000 -#define HPF_Fs48000_Gain14_B1 -0.267949 +#define HPF_Fs48000_Gain14_B1 (-0.267949) #define HPF_Fs48000_Gain14_B2 0.000000 /* Gain = 15.000000 dB */ #define HPF_Fs48000_Gain15_A0 3.931127 -#define HPF_Fs48000_Gain15_A1 -3.199076 +#define HPF_Fs48000_Gain15_A1 (-3.199076) #define HPF_Fs48000_Gain15_A2 0.000000 -#define HPF_Fs48000_Gain15_B1 -0.267949 +#define HPF_Fs48000_Gain15_B1 (-0.267949) #define HPF_Fs48000_Gain15_B2 0.000000 #ifdef HIGHER_FS @@ -491,185 +491,185 @@ /* Coefficients for sample rate 96000Hz */ /* Gain = 1.000000 dB */ #define HPF_Fs96000_Gain1_A0 1.096233 -#define HPF_Fs96000_Gain1_A1 -0.673583 +#define HPF_Fs96000_Gain1_A1 (-0.673583) #define HPF_Fs96000_Gain1_A2 0.000000 -#define HPF_Fs96000_Gain1_B1 -0.577350 +#define HPF_Fs96000_Gain1_B1 (-0.577350) #define HPF_Fs96000_Gain1_B2 0.000000 /* Gain = 2.000000 dB */ #define HPF_Fs96000_Gain2_A0 1.204208 -#define HPF_Fs96000_Gain2_A1 -0.781558 +#define HPF_Fs96000_Gain2_A1 (-0.781558) #define HPF_Fs96000_Gain2_A2 0.000000 -#define HPF_Fs96000_Gain2_B1 -0.577350 +#define HPF_Fs96000_Gain2_B1 (-0.577350) #define HPF_Fs96000_Gain2_B2 0.000000 /* Gain = 3.000000 dB */ #define HPF_Fs96000_Gain3_A0 1.325358 -#define HPF_Fs96000_Gain3_A1 -0.902708 +#define HPF_Fs96000_Gain3_A1 (-0.902708) #define HPF_Fs96000_Gain3_A2 0.000000 -#define HPF_Fs96000_Gain3_B1 -0.577350 +#define HPF_Fs96000_Gain3_B1 (-0.577350) #define HPF_Fs96000_Gain3_B2 0.000000 /* Gain = 4.000000 dB */ #define HPF_Fs96000_Gain4_A0 1.461291 -#define HPF_Fs96000_Gain4_A1 -1.038641 +#define HPF_Fs96000_Gain4_A1 (-1.038641) #define HPF_Fs96000_Gain4_A2 0.000000 -#define HPF_Fs96000_Gain4_B1 -0.577350 +#define HPF_Fs96000_Gain4_B1 (-0.577350) #define HPF_Fs96000_Gain4_B2 0.000000 /* Gain = 5.000000 dB */ #define HPF_Fs96000_Gain5_A0 1.613810 -#define HPF_Fs96000_Gain5_A1 -1.191160 +#define HPF_Fs96000_Gain5_A1 (-1.191160) #define HPF_Fs96000_Gain5_A2 0.000000 -#define HPF_Fs96000_Gain5_B1 -0.577350 +#define HPF_Fs96000_Gain5_B1 (-0.577350) #define HPF_Fs96000_Gain5_B2 0.000000 /* Gain = 6.000000 dB */ #define HPF_Fs96000_Gain6_A0 1.784939 -#define HPF_Fs96000_Gain6_A1 -1.362289 +#define HPF_Fs96000_Gain6_A1 (-1.362289) #define HPF_Fs96000_Gain6_A2 0.000000 -#define HPF_Fs96000_Gain6_B1 -0.577350 +#define HPF_Fs96000_Gain6_B1 (-0.577350) #define HPF_Fs96000_Gain6_B2 0.000000 /* Gain = 7.000000 dB */ #define HPF_Fs96000_Gain7_A0 1.976949 -#define HPF_Fs96000_Gain7_A1 -1.554299 +#define HPF_Fs96000_Gain7_A1 (-1.554299) #define HPF_Fs96000_Gain7_A2 0.000000 -#define HPF_Fs96000_Gain7_B1 -0.577350 +#define HPF_Fs96000_Gain7_B1 (-0.577350) #define HPF_Fs96000_Gain7_B2 0.000000 /* Gain = 8.000000 dB */ #define HPF_Fs96000_Gain8_A0 2.192387 -#define HPF_Fs96000_Gain8_A1 -1.769738 +#define HPF_Fs96000_Gain8_A1 (-1.769738) #define HPF_Fs96000_Gain8_A2 0.000000 -#define HPF_Fs96000_Gain8_B1 -0.577350 +#define HPF_Fs96000_Gain8_B1 (-0.577350) #define HPF_Fs96000_Gain8_B2 0.000000 /* Gain = 9.000000 dB */ #define HPF_Fs96000_Gain9_A0 2.434113 -#define HPF_Fs96000_Gain9_A1 -2.011464 +#define HPF_Fs96000_Gain9_A1 (-2.011464) #define HPF_Fs96000_Gain9_A2 0.000000 -#define HPF_Fs96000_Gain9_B1 -0.577350 +#define HPF_Fs96000_Gain9_B1 (-0.577350) #define HPF_Fs96000_Gain9_B2 0.000000 /* Gain = 10.000000 dB */ #define HPF_Fs96000_Gain10_A0 2.705335 -#define HPF_Fs96000_Gain10_A1 -2.282685 +#define HPF_Fs96000_Gain10_A1 (-2.282685) #define HPF_Fs96000_Gain10_A2 0.000000 -#define HPF_Fs96000_Gain10_B1 -0.577350 +#define HPF_Fs96000_Gain10_B1 (-0.577350) #define HPF_Fs96000_Gain10_B2 0.000000 /* Gain = 11.000000 dB */ #define HPF_Fs96000_Gain11_A0 3.009650 -#define HPF_Fs96000_Gain11_A1 -2.587000 +#define HPF_Fs96000_Gain11_A1 (-2.587000) #define HPF_Fs96000_Gain11_A2 0.000000 -#define HPF_Fs96000_Gain11_B1 -0.577350 +#define HPF_Fs96000_Gain11_B1 (-0.577350) #define HPF_Fs96000_Gain11_B2 0.000000 /* Gain = 12.000000 dB */ #define HPF_Fs96000_Gain12_A0 3.351097 -#define HPF_Fs96000_Gain12_A1 -2.928447 +#define HPF_Fs96000_Gain12_A1 (-2.928447) #define HPF_Fs96000_Gain12_A2 0.000000 -#define HPF_Fs96000_Gain12_B1 -0.577350 +#define HPF_Fs96000_Gain12_B1 (-0.577350) #define HPF_Fs96000_Gain12_B2 0.000000 /* Gain = 13.000000 dB */ #define HPF_Fs96000_Gain13_A0 3.734207 -#define HPF_Fs96000_Gain13_A1 -3.311558 +#define HPF_Fs96000_Gain13_A1 (-3.311558) #define HPF_Fs96000_Gain13_A2 0.000000 -#define HPF_Fs96000_Gain13_B1 -0.577350 +#define HPF_Fs96000_Gain13_B1 (-0.577350) #define HPF_Fs96000_Gain13_B2 0.000000 /* Gain = 14.000000 dB */ #define HPF_Fs96000_Gain14_A0 4.164064 -#define HPF_Fs96000_Gain14_A1 -3.741414 +#define HPF_Fs96000_Gain14_A1 (-3.741414) #define HPF_Fs96000_Gain14_A2 0.000000 -#define HPF_Fs96000_Gain14_B1 -0.577350 +#define HPF_Fs96000_Gain14_B1 (-0.577350) #define HPF_Fs96000_Gain14_B2 0.000000 /* Gain = 15.000000 dB */ #define HPF_Fs96000_Gain15_A0 4.646371 -#define HPF_Fs96000_Gain15_A1 -4.223721 +#define HPF_Fs96000_Gain15_A1 (-4.223721) #define HPF_Fs96000_Gain15_A2 0.000000 -#define HPF_Fs96000_Gain15_B1 -0.577350 +#define HPF_Fs96000_Gain15_B1 (-0.577350) #define HPF_Fs96000_Gain15_B2 0.000000 /* Coefficients for sample rate 192000Hz */ /* Gain = 1.000000 dB */ #define HPF_Fs192000_Gain1_A0 1.107823 -#define HPF_Fs192000_Gain1_A1 -0.875150 +#define HPF_Fs192000_Gain1_A1 (-0.875150) #define HPF_Fs192000_Gain1_A2 0.000000 -#define HPF_Fs192000_Gain1_B1 -0.767327 +#define HPF_Fs192000_Gain1_B1 (-0.767327) #define HPF_Fs192000_Gain1_B2 0.000000 /* Gain = 2.000000 dB */ #define HPF_Fs192000_Gain2_A0 1.228803 -#define HPF_Fs192000_Gain2_A1 -0.996130 +#define HPF_Fs192000_Gain2_A1 (-0.996130) #define HPF_Fs192000_Gain2_A2 0.000000 -#define HPF_Fs192000_Gain2_B1 -0.767327 +#define HPF_Fs192000_Gain2_B1 (-0.767327) #define HPF_Fs192000_Gain2_B2 0.000000 /* Gain = 3.000000 dB */ #define HPF_Fs192000_Gain3_A0 1.364544 -#define HPF_Fs192000_Gain3_A1 -1.131871 +#define HPF_Fs192000_Gain3_A1 (-1.131871) #define HPF_Fs192000_Gain3_A2 0.000000 -#define HPF_Fs192000_Gain3_B1 -0.767327 +#define HPF_Fs192000_Gain3_B1 (-0.767327) #define HPF_Fs192000_Gain3_B2 0.000000 /* Gain = 4.000000 dB */ #define HPF_Fs192000_Gain4_A0 1.516849 -#define HPF_Fs192000_Gain4_A1 -1.284176 +#define HPF_Fs192000_Gain4_A1 (-1.284176) #define HPF_Fs192000_Gain4_A2 0.000000 -#define HPF_Fs192000_Gain4_B1 -0.767327 +#define HPF_Fs192000_Gain4_B1 (-0.767327) #define HPF_Fs192000_Gain4_B2 0.000000 /* Gain = 5.000000 dB */ #define HPF_Fs192000_Gain5_A0 1.687737 -#define HPF_Fs192000_Gain5_A1 -1.455064 +#define HPF_Fs192000_Gain5_A1 (-1.455064) #define HPF_Fs192000_Gain5_A2 0.000000 -#define HPF_Fs192000_Gain5_B1 -0.767327 +#define HPF_Fs192000_Gain5_B1 (-0.767327) #define HPF_Fs192000_Gain5_B2 0.000000 /* Gain = 6.000000 dB */ #define HPF_Fs192000_Gain6_A0 1.879477 -#define HPF_Fs192000_Gain6_A1 -1.646804 +#define HPF_Fs192000_Gain6_A1 (-1.646804) #define HPF_Fs192000_Gain6_A2 0.000000 -#define HPF_Fs192000_Gain6_B1 -0.767327 +#define HPF_Fs192000_Gain6_B1 (-0.767327) #define HPF_Fs192000_Gain6_B2 0.000000 /* Gain = 7.000000 dB */ #define HPF_Fs192000_Gain7_A0 2.094613 -#define HPF_Fs192000_Gain7_A1 -1.861940 +#define HPF_Fs192000_Gain7_A1 (-1.861940) #define HPF_Fs192000_Gain7_A2 0.000000 -#define HPF_Fs192000_Gain7_B1 -0.767327 +#define HPF_Fs192000_Gain7_B1 (-0.767327) #define HPF_Fs192000_Gain7_B2 0.000000 /* Gain = 8.000000 dB */ #define HPF_Fs192000_Gain8_A0 2.335999 -#define HPF_Fs192000_Gain8_A1 -2.103326 +#define HPF_Fs192000_Gain8_A1 (-2.103326) #define HPF_Fs192000_Gain8_A2 0.000000 -#define HPF_Fs192000_Gain8_B1 -0.767327 +#define HPF_Fs192000_Gain8_B1 (-0.767327) #define HPF_Fs192000_Gain8_B2 0.000000 /* Gain = 9.000000 dB */ #define HPF_Fs192000_Gain9_A0 2.606839 -#define HPF_Fs192000_Gain9_A1 -2.374166 +#define HPF_Fs192000_Gain9_A1 (-2.374166) #define HPF_Fs192000_Gain9_A2 0.000000 -#define HPF_Fs192000_Gain9_B1 -0.767327 +#define HPF_Fs192000_Gain9_B1 (-0.767327) #define HPF_Fs192000_Gain9_B2 0.000000 /* Gain = 10.000000 dB */ #define HPF_Fs192000_Gain10_A0 2.910726 -#define HPF_Fs192000_Gain10_A1 -2.678053 +#define HPF_Fs192000_Gain10_A1 (-2.678053) #define HPF_Fs192000_Gain10_A2 0.000000 -#define HPF_Fs192000_Gain10_B1 -0.767327 +#define HPF_Fs192000_Gain10_B1 (-0.767327) #define HPF_Fs192000_Gain10_B2 0.000000 /* Gain = 11.000000 dB */ #define HPF_Fs192000_Gain11_A0 3.251693 -#define HPF_Fs192000_Gain11_A1 -3.019020 +#define HPF_Fs192000_Gain11_A1 (-3.019020) #define HPF_Fs192000_Gain11_A2 0.000000 -#define HPF_Fs192000_Gain11_B1 -0.767327 +#define HPF_Fs192000_Gain11_B1 (-0.767327) #define HPF_Fs192000_Gain11_B2 0.000000 /* Gain = 12.000000 dB */ #define HPF_Fs192000_Gain12_A0 3.634264 -#define HPF_Fs192000_Gain12_A1 -3.401591 +#define HPF_Fs192000_Gain12_A1 (-3.401591) #define HPF_Fs192000_Gain12_A2 0.000000 -#define HPF_Fs192000_Gain12_B1 -0.767327 +#define HPF_Fs192000_Gain12_B1 (-0.767327) #define HPF_Fs192000_Gain12_B2 0.000000 /* Gain = 13.000000 dB */ #define HPF_Fs192000_Gain13_A0 4.063516 -#define HPF_Fs192000_Gain13_A1 -3.830843 +#define HPF_Fs192000_Gain13_A1 (-3.830843) #define HPF_Fs192000_Gain13_A2 0.000000 -#define HPF_Fs192000_Gain13_B1 -0.767327 +#define HPF_Fs192000_Gain13_B1 (-0.767327) #define HPF_Fs192000_Gain13_B2 0.000000 /* Gain = 14.000000 dB */ #define HPF_Fs192000_Gain14_A0 4.545145 -#define HPF_Fs192000_Gain14_A1 -4.312472 +#define HPF_Fs192000_Gain14_A1 (-4.312472) #define HPF_Fs192000_Gain14_A2 0.000000 -#define HPF_Fs192000_Gain14_B1 -0.767327 +#define HPF_Fs192000_Gain14_B1 (-0.767327) #define HPF_Fs192000_Gain14_B2 0.000000 /* Gain = 15.000000 dB */ #define HPF_Fs192000_Gain15_A0 5.085542 -#define HPF_Fs192000_Gain15_A1 -4.852868 +#define HPF_Fs192000_Gain15_A1 (-4.852868) #define HPF_Fs192000_Gain15_A2 0.000000 -#define HPF_Fs192000_Gain15_B1 -0.767327 +#define HPF_Fs192000_Gain15_B1 (-0.767327) #define HPF_Fs192000_Gain15_B2 0.000000 #endif
diff --git a/media/libeffects/lvm/lib/Common/lib/LVM_Types.h b/media/libeffects/lvm/lib/Common/lib/LVM_Types.h index cb15b60..ea16072 100644 --- a/media/libeffects/lvm/lib/Common/lib/LVM_Types.h +++ b/media/libeffects/lvm/lib/Common/lib/LVM_Types.h
@@ -44,9 +44,6 @@ #define LVM_MAXINT_8 127 /* Maximum positive integer size */ #define LVM_MAXINT_16 32767 -#ifdef BUILD_FLOAT -#define LVM_MAXFLOAT 1.0f -#endif #define LVM_MAXINT_32 2147483647 #define LVM_MAXENUM 2147483647 @@ -99,8 +96,32 @@ typedef uint32_t LVM_UINT32; /* Unsigned 32-bit word */ #ifdef BUILD_FLOAT -typedef float LVM_FLOAT; /* single precission floating point*/ -#endif + +#define LVM_MAXFLOAT 1.f + +typedef float LVM_FLOAT; /* single precision floating point */ + +// If NATIVE_FLOAT_BUFFER is defined, we expose effects as floating point format; +// otherwise we expose as integer 16 bit and translate to float for the effect libraries. +// Hence, NATIVE_FLOAT_BUFFER should only be enabled under BUILD_FLOAT compilation. + +#define NATIVE_FLOAT_BUFFER + +#endif // BUILD_FLOAT + +// Select whether we expose int16_t or float buffers. +#ifdef NATIVE_FLOAT_BUFFER + +#define EFFECT_BUFFER_FORMAT AUDIO_FORMAT_PCM_FLOAT +typedef float effect_buffer_t; + +#else // NATIVE_FLOAT_BUFFER + +#define EFFECT_BUFFER_FORMAT AUDIO_FORMAT_PCM_16_BIT +typedef int16_t effect_buffer_t; + +#endif // NATIVE_FLOAT_BUFFER + /****************************************************************************************/ /* */ /* Standard Enumerated types */
diff --git a/media/libeffects/lvm/lib/Eq/src/LVEQNB_Coeffs.h b/media/libeffects/lvm/lib/Eq/src/LVEQNB_Coeffs.h index f0deb6c..42ea46f 100644 --- a/media/libeffects/lvm/lib/Eq/src/LVEQNB_Coeffs.h +++ b/media/libeffects/lvm/lib/Eq/src/LVEQNB_Coeffs.h
@@ -26,21 +26,21 @@ /* */ /************************************************************************************/ #ifdef BUILD_FLOAT -#define LVEQNB_Gain_Neg15_dB -0.822172f -#define LVEQNB_Gain_Neg14_dB -0.800474f -#define LVEQNB_Gain_Neg13_dB -0.776128f -#define LVEQNB_Gain_Neg12_dB -0.748811f -#define LVEQNB_Gain_Neg11_dB -0.718162f -#define LVEQNB_Gain_Neg10_dB -0.683772f -#define LVEQNB_Gain_Neg9_dB -0.645187f -#define LVEQNB_Gain_Neg8_dB -0.601893f -#define LVEQNB_Gain_Neg7_dB -0.553316f -#define LVEQNB_Gain_Neg6_dB -0.498813f -#define LVEQNB_Gain_Neg5_dB -0.437659f -#define LVEQNB_Gain_Neg4_dB -0.369043f -#define LVEQNB_Gain_Neg3_dB -0.292054f -#define LVEQNB_Gain_Neg2_dB -0.205672f -#define LVEQNB_Gain_Neg1_dB -0.108749f +#define LVEQNB_Gain_Neg15_dB (-0.822172f) +#define LVEQNB_Gain_Neg14_dB (-0.800474f) +#define LVEQNB_Gain_Neg13_dB (-0.776128f) +#define LVEQNB_Gain_Neg12_dB (-0.748811f) +#define LVEQNB_Gain_Neg11_dB (-0.718162f) +#define LVEQNB_Gain_Neg10_dB (-0.683772f) +#define LVEQNB_Gain_Neg9_dB (-0.645187f) +#define LVEQNB_Gain_Neg8_dB (-0.601893f) +#define LVEQNB_Gain_Neg7_dB (-0.553316f) +#define LVEQNB_Gain_Neg6_dB (-0.498813f) +#define LVEQNB_Gain_Neg5_dB (-0.437659f) +#define LVEQNB_Gain_Neg4_dB (-0.369043f) +#define LVEQNB_Gain_Neg3_dB (-0.292054f) +#define LVEQNB_Gain_Neg2_dB (-0.205672f) +#define LVEQNB_Gain_Neg1_dB (-0.108749f) #define LVEQNB_Gain_0_dB 0.000000f #define LVEQNB_Gain_1_dB 0.122018f #define LVEQNB_Gain_2_dB 0.258925f
diff --git a/media/libeffects/lvm/lib/Eq/src/LVEQNB_Control.c b/media/libeffects/lvm/lib/Eq/src/LVEQNB_Control.c index c290aec..7b0f341 100644 --- a/media/libeffects/lvm/lib/Eq/src/LVEQNB_Control.c +++ b/media/libeffects/lvm/lib/Eq/src/LVEQNB_Control.c
@@ -430,7 +430,15 @@ } - if(bChange){ + // During operating mode transition, there is a race condition where the mode + // is still LVEQNB_ON, but the effect is considered disabled in the upper layers. + // modeChange handles this special race condition. + const int /* bool */ modeChange = pParams->OperatingMode != OperatingModeSave + || (OperatingModeSave == LVEQNB_ON + && pInstance->bInOperatingModeTransition + && LVC_Mixer_GetTarget(&pInstance->BypassMixer.MixerStream[0]) == 0); + + if (bChange || modeChange) { /* * If the sample rate has changed clear the history @@ -462,8 +470,7 @@ LVEQNB_SetCoefficients(pInstance); /* Instance pointer */ } - if(pParams->OperatingMode != OperatingModeSave) - { + if (modeChange) { if(pParams->OperatingMode == LVEQNB_ON) { #ifdef BUILD_FLOAT @@ -479,6 +486,8 @@ else { /* Stay on the ON operating mode until the transition is done */ + // This may introduce a state race condition if the effect is enabled again + // while in transition. This is fixed in the modeChange logic. pInstance->Params.OperatingMode = LVEQNB_ON; #ifdef BUILD_FLOAT LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[0], 0.0f);
diff --git a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Headphone_Coeffs.h b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Headphone_Coeffs.h index 4f5221a..0c2fe53 100644 --- a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Headphone_Coeffs.h +++ b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Headphone_Coeffs.h
@@ -27,127 +27,127 @@ #ifdef BUILD_FLOAT /* Stereo Enhancer coefficients for 8000 Hz sample rate, scaled with 0.161258 */ #define CS_MIDDLE_8000_A0 0.227720 -#define CS_MIDDLE_8000_A1 -0.215125 +#define CS_MIDDLE_8000_A1 (-0.215125) #define CS_MIDDLE_8000_A2 0.000000 -#define CS_MIDDLE_8000_B1 -0.921899 +#define CS_MIDDLE_8000_B1 (-0.921899) #define CS_MIDDLE_8000_B2 0.000000 #define CS_MIDDLE_8000_SCALE 15 #define CS_SIDE_8000_A0 0.611441 -#define CS_SIDE_8000_A1 -0.380344 -#define CS_SIDE_8000_A2 -0.231097 -#define CS_SIDE_8000_B1 -0.622470 -#define CS_SIDE_8000_B2 -0.130759 +#define CS_SIDE_8000_A1 (-0.380344) +#define CS_SIDE_8000_A2 (-0.231097) +#define CS_SIDE_8000_B1 (-0.622470) +#define CS_SIDE_8000_B2 (-0.130759) #define CS_SIDE_8000_SCALE 15 /* Stereo Enhancer coefficients for 11025Hz sample rate, scaled with 0.162943 */ #define CS_MIDDLE_11025_A0 0.230838 -#define CS_MIDDLE_11025_A1 -0.221559 +#define CS_MIDDLE_11025_A1 (-0.221559) #define CS_MIDDLE_11025_A2 0.000000 -#define CS_MIDDLE_11025_B1 -0.943056 +#define CS_MIDDLE_11025_B1 (-0.943056) #define CS_MIDDLE_11025_B2 0.000000 #define CS_MIDDLE_11025_SCALE 15 #define CS_SIDE_11025_A0 0.557372 -#define CS_SIDE_11025_A1 -0.391490 -#define CS_SIDE_11025_A2 -0.165881 -#define CS_SIDE_11025_B1 -0.880608 +#define CS_SIDE_11025_A1 (-0.391490) +#define CS_SIDE_11025_A2 (-0.165881) +#define CS_SIDE_11025_B1 (-0.880608) #define CS_SIDE_11025_B2 0.032397 #define CS_SIDE_11025_SCALE 15 /* Stereo Enhancer coefficients for 12000Hz sample rate, scaled with 0.162191 */ #define CS_MIDDLE_12000_A0 0.229932 -#define CS_MIDDLE_12000_A1 -0.221436 +#define CS_MIDDLE_12000_A1 (-0.221436) #define CS_MIDDLE_12000_A2 0.000000 -#define CS_MIDDLE_12000_B1 -0.947616 +#define CS_MIDDLE_12000_B1 (-0.947616) #define CS_MIDDLE_12000_B2 0.000000 #define CS_MIDDLE_12000_SCALE 15 #define CS_SIDE_12000_A0 0.558398 -#define CS_SIDE_12000_A1 -0.392211 -#define CS_SIDE_12000_A2 -0.166187 -#define CS_SIDE_12000_B1 -0.892550 +#define CS_SIDE_12000_A1 (-0.392211) +#define CS_SIDE_12000_A2 (-0.166187) +#define CS_SIDE_12000_B1 (-0.892550) #define CS_SIDE_12000_B2 0.032856 #define CS_SIDE_12000_SCALE 15 /* Stereo Enhancer coefficients for 16000Hz sample rate, scaled with 0.162371 */ #define CS_MIDDLE_16000_A0 0.230638 -#define CS_MIDDLE_16000_A1 -0.224232 +#define CS_MIDDLE_16000_A1 (-0.224232) #define CS_MIDDLE_16000_A2 0.000000 -#define CS_MIDDLE_16000_B1 -0.960550 +#define CS_MIDDLE_16000_B1 (-0.960550) #define CS_MIDDLE_16000_B2 0.000000 #define CS_MIDDLE_16000_SCALE 15 #define CS_SIDE_16000_A0 0.499695 -#define CS_SIDE_16000_A1 -0.355543 -#define CS_SIDE_16000_A2 -0.144152 -#define CS_SIDE_16000_B1 -1.050788 +#define CS_SIDE_16000_A1 (-0.355543) +#define CS_SIDE_16000_A2 (-0.144152) +#define CS_SIDE_16000_B1 (-1.050788) #define CS_SIDE_16000_B2 0.144104 #define CS_SIDE_16000_SCALE 14 /* Stereo Enhancer coefficients for 22050Hz sample rate, scaled with 0.160781 */ #define CS_MIDDLE_22050_A0 0.228749 -#define CS_MIDDLE_22050_A1 -0.224128 +#define CS_MIDDLE_22050_A1 (-0.224128) #define CS_MIDDLE_22050_A2 0.000000 -#define CS_MIDDLE_22050_B1 -0.971262 +#define CS_MIDDLE_22050_B1 (-0.971262) #define CS_MIDDLE_22050_B2 0.000000 #define CS_MIDDLE_22050_SCALE 15 #define CS_SIDE_22050_A0 0.440112 -#define CS_SIDE_22050_A1 -0.261096 -#define CS_SIDE_22050_A2 -0.179016 -#define CS_SIDE_22050_B1 -1.116786 +#define CS_SIDE_22050_A1 (-0.261096) +#define CS_SIDE_22050_A2 (-0.179016) +#define CS_SIDE_22050_B1 (-1.116786) #define CS_SIDE_22050_B2 0.182507 #define CS_SIDE_22050_SCALE 14 /* Stereo Enhancer coefficients for 24000Hz sample rate, scaled with 0.161882 */ #define CS_MIDDLE_24000_A0 0.230395 -#define CS_MIDDLE_24000_A1 -0.226117 +#define CS_MIDDLE_24000_A1 (-0.226117) #define CS_MIDDLE_24000_A2 0.000000 -#define CS_MIDDLE_24000_B1 -0.973573 +#define CS_MIDDLE_24000_B1 (-0.973573) #define CS_MIDDLE_24000_B2 0.000000 #define CS_MIDDLE_24000_SCALE 15 #define CS_SIDE_24000_A0 0.414770 -#define CS_SIDE_24000_A1 -0.287182 -#define CS_SIDE_24000_A2 -0.127588 -#define CS_SIDE_24000_B1 -1.229648 +#define CS_SIDE_24000_A1 (-0.287182) +#define CS_SIDE_24000_A2 (-0.127588) +#define CS_SIDE_24000_B1 (-1.229648) #define CS_SIDE_24000_B2 0.282177 #define CS_SIDE_24000_SCALE 14 /* Stereo Enhancer coefficients for 32000Hz sample rate, scaled with 0.160322 */ #define CS_MIDDLE_32000_A0 0.228400 -#define CS_MIDDLE_32000_A1 -0.225214 +#define CS_MIDDLE_32000_A1 (-0.225214) #define CS_MIDDLE_32000_A2 0.000000 -#define CS_MIDDLE_32000_B1 -0.980126 +#define CS_MIDDLE_32000_B1 (-0.980126) #define CS_MIDDLE_32000_B2 0.000000 #define CS_MIDDLE_32000_SCALE 15 #define CS_SIDE_32000_A0 0.364579 -#define CS_SIDE_32000_A1 -0.207355 -#define CS_SIDE_32000_A2 -0.157224 -#define CS_SIDE_32000_B1 -1.274231 +#define CS_SIDE_32000_A1 (-0.207355) +#define CS_SIDE_32000_A2 (-0.157224) +#define CS_SIDE_32000_B1 (-1.274231) #define CS_SIDE_32000_B2 0.312495 #define CS_SIDE_32000_SCALE 14 /* Stereo Enhancer coefficients for 44100Hz sample rate, scaled with 0.163834 */ #define CS_MIDDLE_44100_A0 0.233593 -#define CS_MIDDLE_44100_A1 -0.231225 +#define CS_MIDDLE_44100_A1 (-0.231225) #define CS_MIDDLE_44100_A2 0.000000 -#define CS_MIDDLE_44100_B1 -0.985545 +#define CS_MIDDLE_44100_B1 (-0.985545) #define CS_MIDDLE_44100_B2 0.000000 #define CS_MIDDLE_44100_SCALE 15 #define CS_SIDE_44100_A0 0.284573 -#define CS_SIDE_44100_A1 -0.258910 -#define CS_SIDE_44100_A2 -0.025662 -#define CS_SIDE_44100_B1 -1.572248 +#define CS_SIDE_44100_A1 (-0.258910) +#define CS_SIDE_44100_A2 (-0.025662) +#define CS_SIDE_44100_B1 (-1.572248) #define CS_SIDE_44100_B2 0.588399 #define CS_SIDE_44100_SCALE 14 /* Stereo Enhancer coefficients for 48000Hz sample rate, scaled with 0.164402 */ #define CS_MIDDLE_48000_A0 0.234445 -#define CS_MIDDLE_48000_A1 -0.232261 +#define CS_MIDDLE_48000_A1 (-0.232261) #define CS_MIDDLE_48000_A2 0.000000 -#define CS_MIDDLE_48000_B1 -0.986713 +#define CS_MIDDLE_48000_B1 (-0.986713) #define CS_MIDDLE_48000_B2 0.000000 #define CS_MIDDLE_48000_SCALE 15 #define CS_SIDE_48000_A0 0.272606 -#define CS_SIDE_48000_A1 -0.266952 -#define CS_SIDE_48000_A2 -0.005654 -#define CS_SIDE_48000_B1 -1.617141 +#define CS_SIDE_48000_A1 (-0.266952) +#define CS_SIDE_48000_A2 (-0.005654) +#define CS_SIDE_48000_B1 (-1.617141) #define CS_SIDE_48000_B2 0.630405 #define CS_SIDE_48000_SCALE 14 @@ -155,31 +155,31 @@ /* Stereo Enhancer coefficients for 96000Hz sample rate, scaled with 0.165*/ /* high pass filter with cutoff frequency 102.