Merge "audio policy: add ECHO_REFERENCE to virtual audio sources" into rvc-dev am: 74dc21be33 am: d4ce8a5918 am: 504a03abd4 am: 5ce8db69fe
Change-Id: Ia286bd7d2322919184250d87daf73d1133bfd240
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index e63185d..bfd907e 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,10 @@
[Hook Scripts]
mainline_hook = tools/mainline_hook.sh ${PREUPLOAD_COMMIT} "."
+
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+# Only turn on clang-format check for the following subfolders.
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+ media/libmediatranscoding/
diff --git a/camera/ndk/impl/ACameraMetadata.cpp b/camera/ndk/impl/ACameraMetadata.cpp
index bfa60d9..631f6cd 100644
--- a/camera/ndk/impl/ACameraMetadata.cpp
+++ b/camera/ndk/impl/ACameraMetadata.cpp
@@ -527,6 +527,7 @@
case ACAMERA_LENS_OPTICAL_STABILIZATION_MODE:
case ACAMERA_NOISE_REDUCTION_MODE:
case ACAMERA_SCALER_CROP_REGION:
+ case ACAMERA_SCALER_ROTATE_AND_CROP:
case ACAMERA_SENSOR_EXPOSURE_TIME:
case ACAMERA_SENSOR_FRAME_DURATION:
case ACAMERA_SENSOR_SENSITIVITY:
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 16457ac..0808eaa 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -3728,6 +3728,108 @@
ACAMERA_SCALER_AVAILABLE_RECOMMENDED_INPUT_OUTPUT_FORMATS_MAP =
// int32
ACAMERA_SCALER_START + 15,
+ /**
+ * <p>List of rotate-and-crop modes for ACAMERA_SCALER_ROTATE_AND_CROP that are supported by this camera device.</p>
+ *
+ * @see ACAMERA_SCALER_ROTATE_AND_CROP
+ *
+ * <p>Type: byte[n]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * </ul></p>
+ *
+ * <p>This entry lists the valid modes for ACAMERA_SCALER_ROTATE_AND_CROP for this camera device.</p>
+ * <p>Starting with API level 30, all devices will list at least <code>ROTATE_AND_CROP_NONE</code>.
+ * Devices with support for rotate-and-crop will additionally list at least
+ * <code>ROTATE_AND_CROP_AUTO</code> and <code>ROTATE_AND_CROP_90</code>.</p>
+ *
+ * @see ACAMERA_SCALER_ROTATE_AND_CROP
+ */
+ ACAMERA_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES = // byte[n]
+ ACAMERA_SCALER_START + 16,
+ /**
+ * <p>Whether a rotation-and-crop operation is applied to processed
+ * outputs from the camera.</p>
+ *
+ * <p>Type: byte (acamera_metadata_enum_android_scaler_rotate_and_crop_t)</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * <li>ACaptureRequest</li>
+ * </ul></p>
+ *
+ * <p>This control is primarily intended to help camera applications with no support for
+ * multi-window modes to work correctly on devices where multi-window scenarios are
+ * unavoidable, such as foldables or other devices with variable display geometry or more
+ * free-form window placement (such as laptops, which often place portrait-orientation apps
+ * in landscape with pillarboxing).</p>
+ * <p>If supported, the default value is <code>ROTATE_AND_CROP_AUTO</code>, which allows the camera API
+ * to enable backwards-compatibility support for applications that do not support resizing
+ * / multi-window modes, when the device is in fact in a multi-window mode (such as inset
+ * portrait on laptops, or on a foldable device in some fold states). In addition,
+ * <code>ROTATE_AND_CROP_NONE</code> and <code>ROTATE_AND_CROP_90</code> will always be available if this control
+ * is supported by the device. If not supported, devices API level 30 or higher will always
+ * list only <code>ROTATE_AND_CROP_NONE</code>.</p>
+ * <p>When <code>CROP_AUTO</code> is in use, and the camera API activates backward-compatibility mode,
+ * several metadata fields will also be parsed differently to ensure that coordinates are
+ * correctly handled for features like drawing face detection boxes or passing in
+ * tap-to-focus coordinates. The camera API will convert positions in the active array
+ * coordinate system to/from the cropped-and-rotated coordinate system to make the
+ * operation transparent for applications. The following controls are affected:</p>
+ * <ul>
+ * <li>ACAMERA_CONTROL_AE_REGIONS</li>
+ * <li>ACAMERA_CONTROL_AF_REGIONS</li>
+ * <li>ACAMERA_CONTROL_AWB_REGIONS</li>
+ * <li>android.statistics.faces</li>
+ * </ul>
+ * <p>Capture results will contain the actual value selected by the API;
+ * <code>ROTATE_AND_CROP_AUTO</code> will never be seen in a capture result.</p>
+ * <p>Applications can also select their preferred cropping mode, either to opt out of the
+ * backwards-compatibility treatment, or to use the cropping feature themselves as needed.
+ * In this case, no coordinate translation will be done automatically, and all controls
+ * will continue to use the normal active array coordinates.</p>
+ * <p>Cropping and rotating is done after the application of digital zoom (via either
+ * ACAMERA_SCALER_CROP_REGION or ACAMERA_CONTROL_ZOOM_RATIO), but before each individual
+ * output is further cropped and scaled. It only affects processed outputs such as
+ * YUV, PRIVATE, and JPEG. It has no effect on RAW outputs.</p>
+ * <p>When <code>CROP_90</code> or <code>CROP_270</code> are selected, there is a significant loss to the field of
+ * view. For example, with a 4:3 aspect ratio output of 1600x1200, <code>CROP_90</code> will still
+ * produce 1600x1200 output, but these buffers are cropped from a vertical 3:4 slice at the
+ * center of the 4:3 area, then rotated to be 4:3, and then upscaled to 1600x1200. Only
+ * 56.25% of the original FOV is still visible. In general, for an aspect ratio of <code>w:h</code>,
+ * the crop and rotate operation leaves <code>(h/w)^2</code> of the field of view visible. For 16:9,
+ * this is ~31.6%.</p>
+ * <p>As a visual example, the figure below shows the effect of <code>ROTATE_AND_CROP_90</code> on the
+ * outputs for the following parameters:</p>
+ * <ul>
+ * <li>Sensor active array: <code>2000x1500</code></li>
+ * <li>Crop region: top-left: <code>(500, 375)</code>, size: <code>(1000, 750)</code> (4:3 aspect ratio)</li>
+ * <li>Output streams: YUV <code>640x480</code> and YUV <code>1280x720</code></li>
+ * <li><code>ROTATE_AND_CROP_90</code></li>
+ * </ul>
+ * <p><img alt="Effect of ROTATE_AND_CROP_90" src="../images/camera2/metadata/android.scaler.rotateAndCrop/crop-region-rotate-90-43-ratio.png" /></p>
+ * <p>With these settings, the regions of the active array covered by the output streams are:</p>
+ * <ul>
+ * <li>640x480 stream crop: top-left: <code>(219, 375)</code>, size: <code>(562, 750)</code></li>
+ * <li>1280x720 stream crop: top-left: <code>(289, 375)</code>, size: <code>(422, 750)</code></li>
+ * </ul>
+ * <p>Since the buffers are rotated, the buffers as seen by the application are:</p>
+ * <ul>
+ * <li>640x480 stream: top-left: <code>(781, 375)</code> on active array, size: <code>(640, 480)</code>, downscaled 1.17x from sensor pixels</li>
+ * <li>1280x720 stream: top-left: <code>(711, 375)</code> on active array, size: <code>(1280, 720)</code>, upscaled 1.71x from sensor pixels</li>
+ * </ul>
+ *
+ * @see ACAMERA_CONTROL_AE_REGIONS
+ * @see ACAMERA_CONTROL_AF_REGIONS
+ * @see ACAMERA_CONTROL_AWB_REGIONS
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
+ * @see ACAMERA_SCALER_CROP_REGION
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP = // byte (acamera_metadata_enum_android_scaler_rotate_and_crop_t)
+ ACAMERA_SCALER_START + 17,
ACAMERA_SCALER_END,
/**
@@ -8263,6 +8365,51 @@
} acamera_metadata_enum_android_scaler_available_recommended_stream_configurations_t;
+// ACAMERA_SCALER_ROTATE_AND_CROP
+typedef enum acamera_metadata_enum_acamera_scaler_rotate_and_crop {
+ /**
+ * <p>No rotate and crop is applied. Processed outputs are in the sensor orientation.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_NONE = 0,
+
+ /**
+ * <p>Processed images are rotated by 90 degrees clockwise, and then cropped
+ * to the original aspect ratio.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_90 = 1,
+
+ /**
+ * <p>Processed images are rotated by 180 degrees. Since the aspect ratio does not
+ * change, no cropping is performed.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_180 = 2,
+
+ /**
+ * <p>Processed images are rotated by 270 degrees clockwise, and then cropped
+ * to the original aspect ratio.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_270 = 3,
+
+ /**
+ * <p>The camera API automatically selects the best concrete value for
+ * rotate-and-crop based on the application's support for resizability and the current
+ * multi-window mode.</p>
+ * <p>If the application does not support resizing but the display mode for its main
+ * Activity is not in a typical orientation, the camera API will set <code>ROTATE_AND_CROP_90</code>
+ * or some other supported rotation value, depending on device configuration,
+ * to ensure preview and captured images are correctly shown to the user. Otherwise,
+ * <code>ROTATE_AND_CROP_NONE</code> will be selected.</p>
+ * <p>When a value other than NONE is selected, several metadata fields will also be parsed
+ * differently to ensure that coordinates are correctly handled for features like drawing
+ * face detection boxes or passing in tap-to-focus coordinates. The camera API will
+ * convert positions in the active array coordinate system to/from the cropped-and-rotated
+ * coordinate system to make the operation transparent for applications.</p>
+ * <p>No coordinate mapping will be done when the application selects a non-AUTO mode.</p>
+ */
+ ACAMERA_SCALER_ROTATE_AND_CROP_AUTO = 4,
+
+} acamera_metadata_enum_android_scaler_rotate_and_crop_t;
+
// ACAMERA_SENSOR_REFERENCE_ILLUMINANT1
typedef enum acamera_metadata_enum_acamera_sensor_reference_illuminant1 {
diff --git a/camera/ndk/include/camera/NdkCameraWindowType.h b/camera/ndk/include/camera/NdkCameraWindowType.h
index 99f67e9..df977da 100644
--- a/camera/ndk/include/camera/NdkCameraWindowType.h
+++ b/camera/ndk/include/camera/NdkCameraWindowType.h
@@ -44,7 +44,7 @@
*/
#ifdef __ANDROID_VNDK__
#include <cutils/native_handle.h>
-typedef native_handle_t ACameraWindowType;
+typedef const native_handle_t ACameraWindowType;
#else
#include <android/native_window.h>
typedef ANativeWindow ACameraWindowType;
diff --git a/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h b/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h
index e1af8c1..5a1af79 100644
--- a/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h
+++ b/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h
@@ -18,7 +18,7 @@
#include "utils.h"
struct ACaptureSessionOutput {
- explicit ACaptureSessionOutput(native_handle_t* window, bool isShared = false,
+ explicit ACaptureSessionOutput(const native_handle_t* window, bool isShared = false,
const char* physicalCameraId = "") :
mWindow(window), mIsShared(isShared), mPhysicalCameraId(physicalCameraId) {};
diff --git a/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp b/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp
index e511a3f..0fcb700 100644
--- a/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp
+++ b/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp
@@ -355,7 +355,7 @@
std::vector<int32_t> requestStreamIdxList;
std::vector<int32_t> requestSurfaceIdxList;
for (auto outputTarget : request->targets->mOutputs) {
- native_handle_t* anw = outputTarget.mWindow;
+ const native_handle_t* anw = outputTarget.mWindow;
bool found = false;
req->mSurfaceList.push_back(anw);
// lookup stream/surface ID
@@ -434,7 +434,7 @@
}
pRequest->targets = new ACameraOutputTargets();
for (size_t i = 0; i < req->mSurfaceList.size(); i++) {
- native_handle_t* anw = req->mSurfaceList[i];
+ const native_handle_t* anw = req->mSurfaceList[i];
ACameraOutputTarget outputTarget(anw);
pRequest->targets->mOutputs.insert(outputTarget);
}
@@ -611,7 +611,7 @@
std::set<std::pair<native_handle_ptr_wrapper, OutputConfigurationWrapper>> outputSet;
for (auto outConfig : outputs->mOutputs) {
- native_handle_t* anw = outConfig.mWindow;
+ const native_handle_t* anw = outConfig.mWindow;
OutputConfigurationWrapper outConfigInsertW;
OutputConfiguration &outConfigInsert = outConfigInsertW.mOutputConfiguration;
outConfigInsert.rotation = utils::convertToHidl(outConfig.mRotation);
@@ -846,8 +846,7 @@
for (auto streamAndWindowId : request->mCaptureRequest.streamAndWindowIds) {
int32_t windowId = streamAndWindowId.windowId;
if (utils::isWindowNativeHandleEqual(windowHandles[windowId],outHandle)) {
- native_handle_t* anw =
- const_cast<native_handle_t *>(windowHandles[windowId].getNativeHandle());
+ const native_handle_t* anw = windowHandles[windowId].getNativeHandle();
ALOGV("Camera %s Lost output buffer for ANW %p frame %" PRId64,
getId(), anw, frameNumber);
@@ -1244,7 +1243,7 @@
return;
}
- native_handle_t* anw;
+ const native_handle_t* anw;
found = msg->findPointer(kAnwKey, (void**) &anw);
if (!found) {
ALOGE("%s: Cannot find native_handle_t!", __FUNCTION__);
diff --git a/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h b/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h
index ed67615..5715d77 100644
--- a/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h
+++ b/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h
@@ -17,7 +17,7 @@
#include "utils.h"
struct ACameraOutputTarget {
- explicit ACameraOutputTarget(native_handle_t* window) : mWindow(window) {};
+ explicit ACameraOutputTarget(const native_handle_t* window) : mWindow(window) {};
bool operator == (const ACameraOutputTarget& other) const {
return mWindow == other.mWindow;
diff --git a/camera/ndk/ndk_vendor/impl/utils.h b/camera/ndk/ndk_vendor/impl/utils.h
index f389f03..6f5820e 100644
--- a/camera/ndk/ndk_vendor/impl/utils.h
+++ b/camera/ndk/ndk_vendor/impl/utils.h
@@ -42,7 +42,7 @@
// Utility class so that CaptureRequest can be stored by sp<>
struct CaptureRequest : public RefBase {
frameworks::cameraservice::device::V2_0::CaptureRequest mCaptureRequest;
- std::vector<native_handle_t *> mSurfaceList;
+ std::vector<const native_handle_t *> mSurfaceList;
//Physical camera settings metadata is stored here, since the capture request
//might not contain it. That's since, fmq might have consumed it.
hidl_vec<PhysicalCameraSettings> mPhysicalCameraSettings;
@@ -62,13 +62,13 @@
// Utility class so the native_handle_t can be compared with its contents instead
// of just raw pointer comparisons.
struct native_handle_ptr_wrapper {
- native_handle_t *mWindow = nullptr;
+ const native_handle_t *mWindow = nullptr;
- native_handle_ptr_wrapper(native_handle_t *nh) : mWindow(nh) { }
+ native_handle_ptr_wrapper(const native_handle_t *nh) : mWindow(nh) { }
native_handle_ptr_wrapper() = default;
- operator native_handle_t *() const { return mWindow; }
+ operator const native_handle_t *() const { return mWindow; }
bool operator ==(const native_handle_ptr_wrapper other) const {
return isWindowNativeHandleEqual(mWindow, other.mWindow);
diff --git a/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp b/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp
index 938b5f5..ba14c5c 100644
--- a/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp
+++ b/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp
@@ -50,7 +50,7 @@
static constexpr int kTestImageFormat = AIMAGE_FORMAT_YUV_420_888;
using android::hardware::camera::common::V1_0::helper::VendorTagDescriptorCache;
-using ConfiguredWindows = std::set<native_handle_t *>;
+using ConfiguredWindows = std::set<const native_handle_t *>;
class CameraHelper {
public:
@@ -60,11 +60,11 @@
struct PhysicalImgReaderInfo {
const char* physicalCameraId;
- native_handle_t* anw;
+ const native_handle_t* anw;
};
// Retaining the error code in case the caller needs to analyze it.
- std::variant<int, ConfiguredWindows> initCamera(native_handle_t* imgReaderAnw,
+ std::variant<int, ConfiguredWindows> initCamera(const native_handle_t* imgReaderAnw,
const std::vector<PhysicalImgReaderInfo>& physicalImgReaders,
bool usePhysicalSettings) {
ConfiguredWindows configuredWindows;
@@ -257,7 +257,7 @@
ACameraDevice_StateCallbacks mDeviceCb{this, nullptr, nullptr};
ACameraCaptureSession_stateCallbacks mSessionCb{ this, nullptr, nullptr, nullptr};
- native_handle_t* mImgReaderAnw = nullptr; // not owned by us.
+ const native_handle_t* mImgReaderAnw = nullptr; // not owned by us.
// Camera device
ACameraDevice* mDevice = nullptr;
@@ -396,7 +396,7 @@
return 0;
}
- native_handle_t* getNativeWindow() { return mImgReaderAnw; }
+ const native_handle_t* getNativeWindow() { return mImgReaderAnw; }
int getAcquiredImageCount() {
std::lock_guard<std::mutex> lock(mMutex);
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index a6dfb21..206f87f 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -1,32 +1,71 @@
+// for frameworks/av/media
{
- "presubmit": [
- {
- "name": "GtsMediaTestCases",
- "options" : [
+ "presubmit": [
+ // runs whenever we change something in this tree
{
- "include-annotation": "android.platform.test.annotations.Presubmit"
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.EncodeDecodeTest"
+ }
+ ]
},
{
- "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
- }
- ]
- },
- {
- "name": "GtsExoPlayerTestCases",
- "options" : [
- {
- "include-annotation": "android.platform.test.annotations.SocPresubmit"
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.DecodeEditEncodeTest"
+ }
+ ]
},
{
- "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+ "name": "GtsMediaTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
+ }
+ ]
+ },
+ {
+ "name": "GtsExoPlayerTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.SocPresubmit"
+ },
+ {
+ "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+ }
+ ]
}
- ]
- }
- ],
- "imports": [
- {
- "path": "frameworks/av/drm/mediadrm/plugins"
- }
- ]
-}
+ ],
+ "imports": [
+ {
+ "path": "frameworks/av/drm/mediadrm/plugins"
+ }
+ ],
+
+ "staged-platinum-postsubmit": [
+ // runs regularly, independent of changes in this tree.
+ // signals if changes elsewhere break media functionality
+ {
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.EncodeDecodeTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsMediaTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.cts.DecodeEditEncodeTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/media/audioserver/Android.bp b/media/audioserver/Android.bp
new file mode 100644
index 0000000..ca3c81c
--- /dev/null
+++ b/media/audioserver/Android.bp
@@ -0,0 +1,58 @@
+cc_binary {
+ name: "audioserver",
+
+ srcs: [
+ "main_audioserver.cpp",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ header_libs: [
+ "libaudiohal_headers",
+ "libmediametrics_headers",
+ ],
+
+ shared_libs: [
+ "libaaudioservice",
+ "libaudioflinger",
+ "libaudiopolicyservice",
+ "libaudioprocessing",
+ "libbinder",
+ "libcutils",
+ "libhidlbase",
+ "liblog",
+ "libmedia",
+ "libmedialogservice",
+ "libmediautils",
+ "libnbaio",
+ "libnblog",
+ "libpowermanager",
+ "libutils",
+ "libvibrator",
+
+ ],
+
+ // TODO check if we still need all of these include directories
+ include_dirs: [
+ "external/sonic",
+ "frameworks/av/media/libaaudio/include",
+ "frameworks/av/media/libaaudio/src",
+ "frameworks/av/media/libaaudio/src/binding",
+ "frameworks/av/media/libmedia/include",
+ "frameworks/av/services/audioflinger",
+ "frameworks/av/services/audiopolicy",
+ "frameworks/av/services/audiopolicy/common/include",
+ "frameworks/av/services/audiopolicy/common/managerdefinitions/include",
+ "frameworks/av/services/audiopolicy/engine/interface",
+ "frameworks/av/services/audiopolicy/service",
+ "frameworks/av/services/medialog",
+
+ // TODO oboeservice is the old folder name for aaudioservice. It will be changed.
+ "frameworks/av/services/oboeservice",
+ ],
+
+ init_rc: ["audioserver.rc"],
+}
diff --git a/media/audioserver/Android.mk b/media/audioserver/Android.mk
deleted file mode 100644
index cf1c14c..0000000
--- a/media/audioserver/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- main_audioserver.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- libaaudioservice \
- libaudioflinger \
- libaudiopolicyservice \
- libaudioprocessing \
- libbinder \
- libcutils \
- liblog \
- libhidlbase \
- libmedia \
- libmedialogservice \
- libmediautils \
- libnbaio \
- libnblog \
- libutils \
- libvibrator
-
-LOCAL_HEADER_LIBRARIES := \
- libaudiohal_headers \
- libmediametrics_headers \
-
-# TODO oboeservice is the old folder name for aaudioservice. It will be changed.
-LOCAL_C_INCLUDES := \
- frameworks/av/services/audioflinger \
- frameworks/av/services/audiopolicy \
- frameworks/av/services/audiopolicy/common/managerdefinitions/include \
- frameworks/av/services/audiopolicy/common/include \
- frameworks/av/services/audiopolicy/engine/interface \
- frameworks/av/services/audiopolicy/service \
- frameworks/av/services/medialog \
- frameworks/av/services/oboeservice \
- frameworks/av/media/libaaudio/include \
- frameworks/av/media/libaaudio/src \
- frameworks/av/media/libaaudio/src/binding \
- frameworks/av/media/libmedia/include \
- external/sonic \
-
-LOCAL_MODULE := audioserver
-
-LOCAL_INIT_RC := audioserver.rc
-
-LOCAL_CFLAGS := -Werror -Wall
-
-include $(BUILD_EXECUTABLE)
diff --git a/media/audioserver/main_audioserver.cpp b/media/audioserver/main_audioserver.cpp
index f9f4f31..533d330 100644
--- a/media/audioserver/main_audioserver.cpp
+++ b/media/audioserver/main_audioserver.cpp
@@ -29,8 +29,8 @@
#include <mediautils/LimitProcessMemory.h>
#include <utils/Log.h>
-// from LOCAL_C_INCLUDES
-#include "aaudio/AAudioTesting.h"
+// from include_dirs
+#include "aaudio/AAudioTesting.h" // aaudio_policy_t, AAUDIO_PROP_MMAP_POLICY, AAUDIO_POLICY_*
#include "AudioFlinger.h"
#include "AudioPolicyService.h"
#include "AAudioService.h"
diff --git a/media/extractors/TEST_MAPPING b/media/extractors/TEST_MAPPING
new file mode 100644
index 0000000..5c90a70
--- /dev/null
+++ b/media/extractors/TEST_MAPPING
@@ -0,0 +1,7 @@
+// frameworks/av/media/extractors
+{
+ "presubmit": [
+ // b/148094059: unit tests not allowed to download content
+ //{ "name": "ExtractorUnitTest" }
+ ]
+}
diff --git a/media/extractors/tests/Android.bp b/media/extractors/tests/Android.bp
index b3afe2f..0bca6f5 100644
--- a/media/extractors/tests/Android.bp
+++ b/media/extractors/tests/Android.bp
@@ -17,6 +17,7 @@
cc_test {
name: "ExtractorUnitTest",
gtest: true,
+ test_suites: ["device-tests"],
srcs: ["ExtractorUnitTest.cpp"],
diff --git a/media/libaaudio/Doxyfile.orig b/media/libaaudio/Doxyfile.orig
deleted file mode 100644
index 137facb..0000000
--- a/media/libaaudio/Doxyfile.orig
+++ /dev/null
@@ -1,2303 +0,0 @@
-# Doxyfile 1.8.6
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project.
-#
-# All text after a double hash (##) is considered a comment and is placed in
-# front of the TAG it is preceding.
-#
-# All text after a single hash (#) is considered a comment and will be ignored.
-# The format is:
-# TAG = value [value, ...]
-# For lists, items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (\" \").
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all text
-# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
-# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
-# for the list of possible encodings.
-# The default value is: UTF-8.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
-# double-quotes, unless you are using Doxywizard) that should identify the
-# project for which the documentation is generated. This name is used in the
-# title of most generated pages and in a few other places.
-# The default value is: My Project.
-
-PROJECT_NAME = "My Project"
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
-# could be handy for archiving the generated documentation or if some version
-# control system is used.
-
-PROJECT_NUMBER =
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer a
-# quick idea about the purpose of the project. Keep the description short.
-
-PROJECT_BRIEF =
-
-# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
-# the documentation. The maximum height of the logo should not exceed 55 pixels
-# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
-# to the output directory.
-
-PROJECT_LOGO =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
-# into which the generated documentation will be written. If a relative path is
-# entered, it will be relative to the location where doxygen was started. If
-# left blank the current directory will be used.
-
-OUTPUT_DIRECTORY =
-
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
-# directories (in 2 levels) under the output directory of each output format and
-# will distribute the generated files over these directories. Enabling this
-# option can be useful when feeding doxygen a huge amount of source files, where
-# putting all generated files in the same directory would otherwise causes
-# performance problems for the file system.
-# The default value is: NO.
-
-CREATE_SUBDIRS = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
-# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
-# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
-# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
-# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
-# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
-# Ukrainian and Vietnamese.
-# The default value is: English.
-
-OUTPUT_LANGUAGE = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
-# descriptions after the members that are listed in the file and class
-# documentation (similar to Javadoc). Set to NO to disable this.
-# The default value is: YES.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
-# description of a member or function before the detailed description
-#
-# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-# The default value is: YES.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator that is
-# used to form the text in various listings. Each string in this list, if found
-# as the leading text of the brief description, will be stripped from the text
-# and the result, after processing the whole list, is used as the annotated
-# text. Otherwise, the brief description is used as-is. If left blank, the
-# following values are used ($name is automatically replaced with the name of
-# the entity):The $name class, The $name widget, The $name file, is, provides,
-# specifies, contains, represents, a, an and the.
-
-ABBREVIATE_BRIEF =
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# doxygen will generate a detailed section even if there is only a brief
-# description.
-# The default value is: NO.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-# The default value is: NO.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
-# before files name in the file list and in the header files. If set to NO the
-# shortest path that makes the file name unique will be used
-# The default value is: YES.
-
-FULL_PATH_NAMES = YES
-
-# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
-# Stripping is only done if one of the specified strings matches the left-hand
-# part of the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the path to
-# strip.
-#
-# Note that you can specify absolute paths here, but also relative paths, which
-# will be relative from the directory where doxygen is started.
-# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-
-STRIP_FROM_PATH =
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
-# path mentioned in the documentation of a class, which tells the reader which
-# header file to include in order to use a class. If left blank only the name of
-# the header file containing the class definition is used. Otherwise one should
-# specify the list of include paths that are normally passed to the compiler
-# using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
-# less readable) file names. This can be useful is your file systems doesn't
-# support long names like on DOS, Mac, or CD-ROM.
-# The default value is: NO.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
-# first line (until the first dot) of a Javadoc-style comment as the brief
-# description. If set to NO, the Javadoc-style will behave just like regular Qt-
-# style comments (thus requiring an explicit @brief command for a brief
-# description.)
-# The default value is: NO.
-
-JAVADOC_AUTOBRIEF = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
-# line (until the first dot) of a Qt-style comment as the brief description. If
-# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
-# requiring an explicit \brief command for a brief description.)
-# The default value is: NO.
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
-# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
-# a brief description. This used to be the default behavior. The new default is
-# to treat a multi-line C++ comment block as a detailed description. Set this
-# tag to YES if you prefer the old behavior instead.
-#
-# Note that setting this tag to YES also means that rational rose comments are
-# not recognized any more.
-# The default value is: NO.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
-# documentation from any documented member that it re-implements.
-# The default value is: YES.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
-# new page for each member. If set to NO, the documentation of a member will be
-# part of the file/class/namespace that contains it.
-# The default value is: NO.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
-# uses this value to replace tabs by spaces in code fragments.
-# Minimum value: 1, maximum value: 16, default value: 4.
-
-TAB_SIZE = 4
-
-# This tag can be used to specify a number of aliases that act as commands in
-# the documentation. An alias has the form:
-# name=value
-# For example adding
-# "sideeffect=@par Side Effects:\n"
-# will allow you to put the command \sideeffect (or @sideeffect) in the
-# documentation, which will result in a user-defined paragraph with heading
-# "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines.
-
-ALIASES =
-
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
-# only. Doxygen will then generate output that is more tailored for C. For
-# instance, some of the names that are used will be different. The list of all
-# members will be omitted, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_FOR_C = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
-# Python sources only. Doxygen will then generate output that is more tailored
-# for that language. For instance, namespaces will be presented as packages,
-# qualified scopes will look different, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources. Doxygen will then generate output that is tailored for Fortran.
-# The default value is: NO.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for VHDL.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given
-# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make
-# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
-# (default is Fortran), use: inc=Fortran f=C.
-#
-# Note For files without extension you can use no_extension as a placeholder.
-#
-# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
-# the files are not read by doxygen.
-
-EXTENSION_MAPPING =
-
-# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
-# according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you can
-# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
-# case of backward compatibilities issues.
-# The default value is: YES.
-
-MARKDOWN_SUPPORT = YES
-
-# When enabled doxygen tries to link words that correspond to documented
-# classes, or namespaces to their corresponding documentation. Such a link can
-# be prevented in individual cases by by putting a % sign in front of the word
-# or globally by setting AUTOLINK_SUPPORT to NO.
-# The default value is: YES.
-
-AUTOLINK_SUPPORT = YES
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should set this
-# tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string);
-# versus func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-# The default value is: NO.
-
-BUILTIN_STL_SUPPORT = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-# The default value is: NO.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
-# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
-# will parse them like normal C++ but will assume all classes use public instead
-# of private inheritance when no explicit protection keyword is present.
-# The default value is: NO.
-
-SIP_SUPPORT = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate
-# getter and setter methods for a property. Setting this option to YES will make
-# doxygen to replace the get and set methods by a property in the documentation.
-# This will only work if the methods are indeed getting or setting a simple
-# type. If this is not the case, or you want to show the methods anyway, you
-# should set this option to NO.
-# The default value is: YES.
-
-IDL_PROPERTY_SUPPORT = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-# The default value is: NO.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# Set the SUBGROUPING tag to YES to allow class member groups of the same type
-# (for instance a group of public functions) to be put as a subgroup of that
-# type (e.g. under the Public Functions section). Set it to NO to prevent
-# subgrouping. Alternatively, this can be done per class using the
-# \nosubgrouping command.
-# The default value is: YES.
-
-SUBGROUPING = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
-# are shown inside the group in which they are included (e.g. using \ingroup)
-# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
-# and RTF).
-#
-# Note that this feature does not work in combination with
-# SEPARATE_MEMBER_PAGES.
-# The default value is: NO.
-
-INLINE_GROUPED_CLASSES = NO
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
-# with only public data fields or simple typedef fields will be shown inline in
-# the documentation of the scope in which they are defined (i.e. file,
-# namespace, or group documentation), provided this scope is documented. If set
-# to NO, structs, classes, and unions are shown on a separate page (for HTML and
-# Man pages) or section (for LaTeX and RTF).
-# The default value is: NO.
-
-INLINE_SIMPLE_STRUCTS = NO
-
-# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
-# enum is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically be
-# useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-# The default value is: NO.
-
-TYPEDEF_HIDES_STRUCT = NO
-
-# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
-# cache is used to resolve symbols given their name and scope. Since this can be
-# an expensive process and often the same symbol appears multiple times in the
-# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
-# doxygen will become slower. If the cache is too large, memory is wasted. The
-# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
-# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
-# symbols. At the end of a run doxygen will report the cache usage and suggest
-# the optimal cache size from a speed point of view.
-# Minimum value: 0, maximum value: 9, default value: 0.
-
-LOOKUP_CACHE_SIZE = 0
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available. Private
-# class members and static file members will be hidden unless the
-# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
-# Note: This will also disable the warnings about undocumented members that are
-# normally produced when WARNINGS is set to YES.
-# The default value is: NO.
-
-EXTRACT_ALL = NO
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
-# be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PRIVATE = NO
-
-# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
-# scope will be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PACKAGE = NO
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
-# included in the documentation.
-# The default value is: NO.
-
-EXTRACT_STATIC = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
-# locally in source files will be included in the documentation. If set to NO
-# only classes defined in header files are included. Does not have any effect
-# for Java sources.
-# The default value is: YES.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. When set to YES local methods,
-# which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO only methods in the interface are
-# included.
-# The default value is: NO.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base name of
-# the file that contains the anonymous namespace. By default anonymous namespace
-# are hidden.
-# The default value is: NO.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
-# undocumented members inside documented classes or files. If set to NO these
-# members will be included in the various overviews, but no documentation
-# section is generated. This option has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy. If set
-# to NO these classes will be included in the various overviews. This option has
-# no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO these declarations will be
-# included in the documentation.
-# The default value is: NO.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
-# documentation blocks found inside the body of a function. If set to NO these
-# blocks will be appended to the function's detailed documentation block.
-# The default value is: NO.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation that is typed after a
-# \internal command is included. If the tag is set to NO then the documentation
-# will be excluded. Set it to YES to include the internal documentation.
-# The default value is: NO.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-# The default value is: system dependent.
-
-CASE_SENSE_NAMES = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES the
-# scope will be hidden.
-# The default value is: NO.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
-# the files that are included by a file in the documentation of that file.
-# The default value is: YES.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
-# grouped member an include statement to the documentation, telling the reader
-# which file to include in order to use the member.
-# The default value is: NO.
-
-SHOW_GROUPED_MEMB_INC = NO
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
-# files with double quotes in the documentation rather than with sharp brackets.
-# The default value is: NO.
-
-FORCE_LOCAL_INCLUDES = NO
-
-# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
-# documentation for inline members.
-# The default value is: YES.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
-# (detailed) documentation of file and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order.
-# The default value is: YES.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
-# descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order. Note that
-# this will also influence the order of the classes in the class list.
-# The default value is: NO.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
-# (brief and detailed) documentation of class members so that constructors and
-# destructors are listed first. If set to NO the constructors will appear in the
-# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
-# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
-# member documentation.
-# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
-# detailed member documentation.
-# The default value is: NO.
-
-SORT_MEMBERS_CTORS_1ST = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
-# of group names into alphabetical order. If set to NO the group names will
-# appear in their defined order.
-# The default value is: NO.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
-# fully-qualified names, including namespaces. If set to NO, the class list will
-# be sorted only by class name, not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the alphabetical
-# list.
-# The default value is: NO.
-
-SORT_BY_SCOPE_NAME = NO
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
-# type resolution of all parameters of a function it will reject a match between
-# the prototype and the implementation of a member function even if there is
-# only one candidate or it is obvious which candidate to choose by doing a
-# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
-# accept a match between prototype and implementation in such cases.
-# The default value is: NO.
-
-STRICT_PROTO_MATCHING = NO
-
-# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
-# todo list. This list is created by putting \todo commands in the
-# documentation.
-# The default value is: YES.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
-# test list. This list is created by putting \test commands in the
-# documentation.
-# The default value is: YES.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
-# list. This list is created by putting \bug commands in the documentation.
-# The default value is: YES.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
-# the deprecated list. This list is created by putting \deprecated commands in
-# the documentation.
-# The default value is: YES.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional documentation
-# sections, marked by \if <section_label> ... \endif and \cond <section_label>
-# ... \endcond blocks.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
-# initial value of a variable or macro / define can have for it to appear in the
-# documentation. If the initializer consists of more lines than specified here
-# it will be hidden. Use a value of 0 to hide initializers completely. The
-# appearance of the value of individual variables and macros / defines can be
-# controlled using \showinitializer or \hideinitializer command in the
-# documentation regardless of this setting.
-# Minimum value: 0, maximum value: 10000, default value: 30.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
-# the bottom of the documentation of classes and structs. If set to YES the list
-# will mention the files that were used to generate the documentation.
-# The default value is: YES.
-
-SHOW_USED_FILES = YES
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
-# will remove the Files entry from the Quick Index and from the Folder Tree View
-# (if specified).
-# The default value is: YES.
-
-SHOW_FILES = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
-# page. This will remove the Namespaces entry from the Quick Index and from the
-# Folder Tree View (if specified).
-# The default value is: YES.
-
-SHOW_NAMESPACES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command command input-file, where command is the value of the
-# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
-# by doxygen. Whatever the program writes to standard output is used as the file
-# version. For an example see the documentation.
-
-FILE_VERSION_FILTER =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option. You can
-# optionally specify a file name after the option, if omitted DoxygenLayout.xml
-# will be used as the name of the layout file.
-#
-# Note that if you run doxygen from a directory containing a file called
-# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
-# tag is left empty.
-
-LAYOUT_FILE =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
-# the reference definitions. This must be a list of .bib files. The .bib
-# extension is automatically appended if omitted. This requires the bibtex tool
-# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
-# For LaTeX the style of the bibliography can be controlled using
-# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
-# search path. Do not use file names with spaces, bibtex cannot handle them. See
-# also \cite for info how to create references.
-
-CITE_BIB_FILES =
-
-#---------------------------------------------------------------------------
-# Configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated to
-# standard output by doxygen. If QUIET is set to YES this implies that the
-# messages are off.
-# The default value is: NO.
-
-QUIET = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
-# this implies that the warnings are on.
-#
-# Tip: Turn warnings on while writing the documentation.
-# The default value is: YES.
-
-WARNINGS = YES
-
-# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
-# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
-# will automatically be disabled.
-# The default value is: YES.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some parameters
-# in a documented function, or documenting parameters that don't exist or using
-# markup commands wrongly.
-# The default value is: YES.
-
-WARN_IF_DOC_ERROR = YES
-
-# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
-# are documented, but have no documentation for their parameters or return
-# value. If set to NO doxygen will only warn about wrong or incomplete parameter
-# documentation, but not about the absence of documentation.
-# The default value is: NO.
-
-WARN_NO_PARAMDOC = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that doxygen
-# can produce. The string should contain the $file, $line, and $text tags, which
-# will be replaced by the file and line number from which the warning originated
-# and the warning text. Optionally the format may contain $version, which will
-# be replaced by the version of the file (if it could be obtained via
-# FILE_VERSION_FILTER)
-# The default value is: $file:$line: $text.
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning and error
-# messages should be written. If left blank the output is written to standard
-# error (stderr).
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag is used to specify the files and/or directories that contain
-# documented source files. You may enter file names like myfile.cpp or
-# directories like /usr/src/myproject. Separate the files or directories with
-# spaces.
-# Note: If this tag is empty the current directory is searched.
-
-INPUT =
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
-# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: http://www.gnu.org/software/libiconv) for the list of
-# possible encodings.
-# The default value is: UTF-8.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank the
-# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
-# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
-# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
-# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
-# *.qsf, *.as and *.js.
-
-FILE_PATTERNS =
-
-# The RECURSIVE tag can be used to specify whether or not subdirectories should
-# be searched for input files as well.
-# The default value is: NO.
-
-RECURSIVE = NO
-
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-#
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
-EXCLUDE =
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-# The default value is: NO.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories.
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories for example use the pattern */test/*
-
-EXCLUDE_PATTERNS =
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories use the pattern */test/*
-
-EXCLUDE_SYMBOLS =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or directories
-# that contain example code fragments that are included (see the \include
-# command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank all
-# files are included.
-
-EXAMPLE_PATTERNS =
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude commands
-# irrespective of the value of the RECURSIVE tag.
-# The default value is: NO.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or directories
-# that contain images that are to be included in the documentation (see the
-# \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command:
-#
-# <filter> <input-file>
-#
-# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
-# name of an input file. Doxygen will then use the output that the filter
-# program writes to standard output. If FILTER_PATTERNS is specified, this tag
-# will be ignored.
-#
-# Note that the filter must not add or remove lines; it is applied before the
-# code is scanned, but not when the output code is generated. If lines are added
-# or removed, the anchors will not be placed correctly.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form: pattern=filter
-# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
-# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
-# patterns match the file name, INPUT_FILTER is applied.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER ) will also be used to filter the input files that are used for
-# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
-# The default value is: NO.
-
-FILTER_SOURCE_FILES = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
-# it is also possible to disable source filtering for a specific pattern using
-# *.ext= (so without naming a filter).
-# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
-
-FILTER_SOURCE_PATTERNS =
-
-# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page
-# (index.html). This can be useful if you have a project on for instance GitHub
-# and want to reuse the introduction page also for the doxygen output.
-
-USE_MDFILE_AS_MAINPAGE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
-# generated. Documented entities will be cross-referenced with these sources.
-#
-# Note: To get rid of all source code in the generated output, make sure that
-# also VERBATIM_HEADERS is set to NO.
-# The default value is: NO.
-
-SOURCE_BROWSER = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body of functions,
-# classes and enums directly into the documentation.
-# The default value is: NO.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
-# special comment blocks from generated source code fragments. Normal C, C++ and
-# Fortran comments will always remain visible.
-# The default value is: YES.
-
-STRIP_CODE_COMMENTS = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# function all documented functions referencing it will be listed.
-# The default value is: NO.
-
-REFERENCED_BY_RELATION = NO
-
-# If the REFERENCES_RELATION tag is set to YES then for each documented function
-# all documented entities called/used by that function will be listed.
-# The default value is: NO.
-
-REFERENCES_RELATION = NO
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
-# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
-# link to the documentation.
-# The default value is: YES.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
-# source code will show a tooltip with additional information such as prototype,
-# brief description and links to the definition and documentation. Since this
-# will make the HTML file larger and loading of large files a bit slower, you
-# can opt to disable this feature.
-# The default value is: YES.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-SOURCE_TOOLTIPS = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code will
-# point to the HTML generated by the htags(1) tool instead of doxygen built-in
-# source browser. The htags tool is part of GNU's global source tagging system
-# (see http://www.gnu.org/software/global/global.html). You will need version
-# 4.8.6 or higher.
-#
-# To use it do the following:
-# - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
-# - Make sure the INPUT points to the root of the source tree
-# - Run doxygen as normal
-#
-# Doxygen will invoke htags (and that will in turn invoke gtags), so these
-# tools must be available from the command line (i.e. in the search path).
-#
-# The result: instead of the source browser generated by doxygen, the links to
-# source code will now point to the output of htags.
-# The default value is: NO.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
-# verbatim copy of the header file for each class for which an include is
-# specified. Set to NO to disable this.
-# See also: Section \class.
-# The default value is: YES.
-
-VERBATIM_HEADERS = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
-# compounds will be generated. Enable this if the project contains a lot of
-# classes, structs, unions or interfaces.
-# The default value is: YES.
-
-ALPHABETICAL_INDEX = YES
-
-# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
-# which the alphabetical index list will be split.
-# Minimum value: 1, maximum value: 20, default value: 5.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-COLS_IN_ALPHA_INDEX = 5
-
-# In case all classes in a project start with a common prefix, all classes will
-# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
-# can be used to specify a prefix (or a list of prefixes) that should be ignored
-# while generating the index headers.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
-# The default value is: YES.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_OUTPUT = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
-# generated HTML page (for example: .htm, .php, .asp).
-# The default value is: .html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
-# each generated HTML page. If the tag is left blank doxygen will generate a
-# standard header.
-#
-# To get valid HTML the header file that includes any scripts and style sheets
-# that doxygen needs, which is dependent on the configuration options used (e.g.
-# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
-# default header using
-# doxygen -w html new_header.html new_footer.html new_stylesheet.css
-# YourConfigFile
-# and then modify the file new_header.html. See also section "Doxygen usage"
-# for information on how to generate the default header that doxygen normally
-# uses.
-# Note: The header is subject to change so you typically have to regenerate the
-# default header when upgrading to a newer version of doxygen. For a description
-# of the possible markers and block names see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
-# generated HTML page. If the tag is left blank doxygen will generate a standard
-# footer. See HTML_HEADER for more information on how to generate a default
-# footer and what special commands can be used inside the footer. See also
-# section "Doxygen usage" for information on how to generate the default footer
-# that doxygen normally uses.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
-# sheet that is used by each HTML page. It can be used to fine-tune the look of
-# the HTML output. If left blank doxygen will generate a default style sheet.
-# See also section "Doxygen usage" for information on how to generate the style
-# sheet that doxygen normally uses.
-# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
-# it is more robust and this tag (HTML_STYLESHEET) will in the future become
-# obsolete.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_STYLESHEET =
-
-# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
-# defined cascading style sheet that is included after the standard style sheets
-# created by doxygen. Using this option one can overrule certain style aspects.
-# This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefor more robust against future updates.
-# Doxygen will copy the style sheet file to the output directory. For an example
-# see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_STYLESHEET =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
-# files will be copied as-is; there are no commands or markers available.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_FILES =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the stylesheet and background images according to
-# this color. Hue is specified as an angle on a colorwheel, see
-# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
-# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
-# purple, and 360 is red again.
-# Minimum value: 0, maximum value: 359, default value: 220.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_HUE = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
-# in the HTML output. For a value of 0 the output will use grayscales only. A
-# value of 255 will produce the most vivid colors.
-# Minimum value: 0, maximum value: 255, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_SAT = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
-# luminance component of the colors in the HTML output. Values below 100
-# gradually make the output lighter, whereas values above 100 make the output
-# darker. The value divided by 100 is the actual gamma applied, so 80 represents
-# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
-# change the gamma.
-# Minimum value: 40, maximum value: 240, default value: 80.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_GAMMA = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
-# shown in the various tree structured indices initially; the user can expand
-# and collapse entries dynamically later on. Doxygen will expand the tree to
-# such a level that at most the specified number of entries are visible (unless
-# a fully collapsed tree already exceeds this amount). So setting the number of
-# entries 1 will produce a full collapsed tree by default. 0 is a special value
-# representing an infinite number of entries and will result in a full expanded
-# tree by default.
-# Minimum value: 0, maximum value: 9999, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_INDEX_NUM_ENTRIES = 100
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files will be
-# generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: http://developer.apple.com/tools/xcode/), introduced with
-# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
-# Makefile in the HTML output directory. Running make will produce the docset in
-# that directory and running make install will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_DOCSET = NO
-
-# This tag determines the name of the docset feed. A documentation feed provides
-# an umbrella under which multiple documentation sets from a single provider
-# (such as a company or product suite) can be grouped.
-# The default value is: Doxygen generated docs.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# This tag specifies a string that should uniquely identify the documentation
-# set bundle. This should be a reverse domain-name style string, e.g.
-# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_BUNDLE_ID = org.doxygen.Project
-
-# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
-# the documentation publisher. This should be a reverse domain-name style
-# string, e.g. com.mycompany.MyDocSet.documentation.
-# The default value is: org.doxygen.Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_ID = org.doxygen.Publisher
-
-# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
-# The default value is: Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_NAME = Publisher
-
-# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
-# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
-# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
-# Windows.
-#
-# The HTML Help Workshop contains a compiler that can convert all HTML output
-# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
-# files are now used as the Windows 98 help format, and will replace the old
-# Windows help format (.hlp) on all Windows platforms in the future. Compressed
-# HTML files also contain an index, a table of contents, and you can search for
-# words in the documentation. The HTML workshop also contains a viewer for
-# compressed HTML files.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_HTMLHELP = NO
-
-# The CHM_FILE tag can be used to specify the file name of the resulting .chm
-# file. You can add a path in front of the file if the result should not be
-# written to the html output directory.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_FILE =
-
-# The HHC_LOCATION tag can be used to specify the location (absolute path
-# including file name) of the HTML help compiler ( hhc.exe). If non-empty
-# doxygen will try to run the HTML help compiler on the generated index.hhp.
-# The file has to be specified with full path.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-HHC_LOCATION =
-
-# The GENERATE_CHI flag controls if a separate .chi index file is generated (
-# YES) or that it should be included in the master .chm file ( NO).
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-GENERATE_CHI = NO
-
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
-# and project file content.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_INDEX_ENCODING =
-
-# The BINARY_TOC flag controls whether a binary table of contents is generated (
-# YES) or a normal table of contents ( NO) in the .chm file.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members to
-# the table of contents of the HTML help documentation and to the tree view.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-TOC_EXPAND = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
-# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
-# (.qch) of the generated HTML documentation.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_QHP = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
-# the file name of the resulting .qch file. The path specified is relative to
-# the HTML output folder.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QCH_FILE =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
-# Project output. For more information please see Qt Help Project / Namespace
-# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_NAMESPACE = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
-# Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
-# folders).
-# The default value is: doc.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_VIRTUAL_FOLDER = doc
-
-# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
-# filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_NAME =
-
-# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_ATTRS =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's filter section matches. Qt Help Project / Filter Attributes (see:
-# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_SECT_FILTER_ATTRS =
-
-# The QHG_LOCATION tag can be used to specify the location of Qt's
-# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
-# generated .qhp file.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHG_LOCATION =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
-# generated, together with the HTML files, they form an Eclipse help plugin. To
-# install this plugin and make it available under the help contents menu in
-# Eclipse, the contents of the directory containing the HTML and XML files needs
-# to be copied into the plugins directory of eclipse. The name of the directory
-# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
-# After copying Eclipse needs to be restarted before the help appears.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_ECLIPSEHELP = NO
-
-# A unique identifier for the Eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have this
-# name. Each documentation set should have its own identifier.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
-
-ECLIPSE_DOC_ID = org.doxygen.Project
-
-# If you want full control over the layout of the generated HTML pages it might
-# be necessary to disable the index and replace it with your own. The
-# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
-# of each HTML page. A value of NO enables the index and the value YES disables
-# it. Since the tabs in the index contain the same information as the navigation
-# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-DISABLE_INDEX = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information. If the tag
-# value is set to YES, a side panel will be generated containing a tree-like
-# index structure (just like the one that is generated for HTML Help). For this
-# to work a browser that supports JavaScript, DHTML, CSS and frames is required
-# (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
-# further fine-tune the look of the index. As an example, the default style
-# sheet generated by doxygen has an example that shows how to put an image at
-# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
-# the same information as the tab index, you could consider setting
-# DISABLE_INDEX to YES when enabling this option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_TREEVIEW = NO
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
-# doxygen will group on one line in the generated HTML documentation.
-#
-# Note that a value of 0 will completely suppress the enum values from appearing
-# in the overview section.
-# Minimum value: 0, maximum value: 20, default value: 4.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-ENUM_VALUES_PER_LINE = 4
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
-# to set the initial width (in pixels) of the frame in which the tree is shown.
-# Minimum value: 0, maximum value: 1500, default value: 250.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-TREEVIEW_WIDTH = 250
-
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
-# external symbols imported via tag files in a separate window.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-EXT_LINKS_IN_WINDOW = NO
-
-# Use this tag to change the font size of LaTeX formulas included as images in
-# the HTML documentation. When you change the font size after a successful
-# doxygen run you need to manually remove any form_*.png images from the HTML
-# output directory to force them to be regenerated.
-# Minimum value: 8, maximum value: 50, default value: 10.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_FONTSIZE = 10
-
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT = YES
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# http://www.mathjax.org) which uses client side Javascript for the rendering
-# instead of using prerendered bitmaps. Use this if you do not have LaTeX
-# installed or if you want to formulas look prettier in the HTML output. When
-# enabled you may also need to install MathJax separately and configure the path
-# to it using the MATHJAX_RELPATH option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-USE_MATHJAX = NO
-
-# When MathJax is enabled you can set the default output format to be used for
-# the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/latest/output.html) for more details.
-# Possible values are: HTML-CSS (which is slower, but has the best
-# compatibility), NativeMML (i.e. MathML) and SVG.
-# The default value is: HTML-CSS.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_FORMAT = HTML-CSS
-
-# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from http://www.mathjax.org before deployment.
-# The default value is: http://cdn.mathjax.org/mathjax/latest.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
-# extension names that should be enabled during MathJax rendering. For example
-# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_EXTENSIONS =
-
-# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
-# example see the documentation.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_CODEFILE =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
-# the HTML output. The underlying search engine uses javascript and DHTML and
-# should work on any modern browser. Note that when using HTML help
-# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
-# there is already a search function so this one should typically be disabled.
-# For large projects the javascript based search engine can be slow, then
-# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
-# search using the keyboard; to jump to the search box use <access key> + S
-# (what the <access key> is depends on the OS and browser, but it is typically
-# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
-# key> to jump into the search results window, the results can be navigated
-# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
-# the search. The filter options can be selected when the cursor is inside the
-# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
-# to select a filter and <Enter> or <escape> to activate or cancel the filter
-# option.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-SEARCHENGINE = YES
-
-# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
-# are two flavours of web server based searching depending on the
-# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
-# searching and an index file used by the script. When EXTERNAL_SEARCH is
-# enabled the indexing and searching needs to be provided by external tools. See
-# the section "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SERVER_BASED_SEARCH = NO
-
-# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
-# script for searching. Instead the search results are written to an XML file
-# which needs to be processed by an external indexer. Doxygen will invoke an
-# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
-# search results.
-#
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/).
-#
-# See the section "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH = NO
-
-# The SEARCHENGINE_URL should point to a search engine hosted by a web server
-# which will return the search results when EXTERNAL_SEARCH is enabled.
-#
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/). See the section "External Indexing and
-# Searching" for details.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHENGINE_URL =
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
-# search data is written to a file for indexing by an external tool. With the
-# SEARCHDATA_FILE tag the name of this file can be specified.
-# The default file is: searchdata.xml.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHDATA_FILE = searchdata.xml
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
-# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
-# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
-# projects and redirect the results back to the right project.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH_ID =
-
-# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
-# projects other than the one defined by this configuration file, but that are
-# all added to the same external search index. Each project needs to have a
-# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
-# to a relative location where the documentation can be found. The format is:
-# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTRA_SEARCH_MAPPINGS =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
-# The default value is: YES.
-
-GENERATE_LATEX = YES
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_OUTPUT = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked.
-#
-# Note that when enabling USE_PDFLATEX this option is only used for generating
-# bitmaps for formulas in the HTML output, but not in the Makefile that is
-# written to the output directory.
-# The default file is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_CMD_NAME = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
-# index for LaTeX.
-# The default file is: makeindex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-MAKEINDEX_CMD_NAME = makeindex
-
-# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used by the
-# printer.
-# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
-# 14 inches) and executive (7.25 x 10.5 inches).
-# The default value is: a4.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PAPER_TYPE = a4
-
-# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
-# that should be included in the LaTeX output. To get the times font for
-# instance you can specify
-# EXTRA_PACKAGES=times
-# If left blank no extra packages will be included.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
-# generated LaTeX document. The header should contain everything until the first
-# chapter. If it is left blank doxygen will generate a standard header. See
-# section "Doxygen usage" for information on how to let doxygen write the
-# default header to a separate file.
-#
-# Note: Only use a user-defined header if you know what you are doing! The
-# following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will
-# replace them by respectively the title of the page, the current date and time,
-# only the current date, the version number of doxygen, the project name (see
-# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HEADER =
-
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
-# generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer.
-#
-# Note: Only use a user-defined footer if you know what you are doing!
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_FOOTER =
-
-# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the LATEX_OUTPUT output
-# directory. Note that the files will be copied as-is; there are no commands or
-# markers available.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_EXTRA_FILES =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
-# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
-# contain links (just like the HTML output) instead of page references. This
-# makes the output suitable for online browsing using a PDF viewer.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PDF_HYPERLINKS = YES
-
-# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES to get a
-# higher quality PDF documentation.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-USE_PDFLATEX = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
-# command to the generated LaTeX files. This will instruct LaTeX to keep running
-# if errors occur, instead of asking the user for help. This option is also used
-# when generating formulas in HTML.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BATCHMODE = NO
-
-# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
-# index chapters (such as File Index, Compound Index, etc.) in the output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HIDE_INDICES = NO
-
-# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
-# code with syntax highlighting in the LaTeX output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_SOURCE_CODE = NO
-
-# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. See
-# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
-# The default value is: plain.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BIB_STYLE = plain
-
-#---------------------------------------------------------------------------
-# Configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
-# RTF output is optimized for Word 97 and may not look too pretty with other RTF
-# readers/editors.
-# The default value is: NO.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: rtf.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
-# contain hyperlink fields. The RTF file will contain links (just like the HTML
-# output) instead of page references. This makes the output suitable for online
-# browsing using Word or some other Word compatible readers that support those
-# fields.
-#
-# Note: WordPad (write) and others do not support links.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_HYPERLINKS = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's config
-# file, i.e. a series of assignments. You only have to provide replacements,
-# missing definitions are set to their default value.
-#
-# See also section "Doxygen usage" for information on how to generate the
-# default style sheet that doxygen normally uses.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an RTF document. Syntax is
-# similar to doxygen's config file. A template extensions file can be generated
-# using doxygen -e rtf extensionFile.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_EXTENSIONS_FILE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
-# classes and files.
-# The default value is: NO.
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it. A directory man3 will be created inside the directory specified by
-# MAN_OUTPUT.
-# The default directory is: man.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_OUTPUT = man
-
-# The MAN_EXTENSION tag determines the extension that is added to the generated
-# man pages. In case the manual section does not start with a number, the number
-# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
-# optional.
-# The default value is: .3.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_EXTENSION = .3
-
-# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
-# will generate one additional man file for each entity documented in the real
-# man page(s). These additional files only source the real man page, but without
-# them the man command would be unable to find the correct page.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
-# captures the structure of the code including all documentation.
-# The default value is: NO.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: xml.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_OUTPUT = xml
-
-# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
-# validating XML parser to check the syntax of the XML files.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_SCHEMA =
-
-# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
-# validating XML parser to check the syntax of the XML files.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_DTD =
-
-# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
-# listings (including syntax highlighting and cross-referencing information) to
-# the XML output. Note that enabling this will significantly increase the size
-# of the XML output.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the DOCBOOK output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
-# that can be used to generate PDF.
-# The default value is: NO.
-
-GENERATE_DOCBOOK = NO
-
-# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
-# front of it.
-# The default directory is: docbook.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-DOCBOOK_OUTPUT = docbook
-
-#---------------------------------------------------------------------------
-# Configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
-# Definitions (see http://autogen.sf.net) file that captures the structure of
-# the code including all documentation. Note that this feature is still
-# experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
-# file that captures the structure of the code including all documentation.
-#
-# Note that this feature is still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
-# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
-# output from the Perl module output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
-# formatted so it can be parsed by a human reader. This is useful if you want to
-# understand what is going on. On the other hand, if this tag is set to NO the
-# size of the Perl module output will be much smaller and Perl will parse it
-# just the same.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file are
-# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
-# so different doxyrules.make files included by the same Makefile don't
-# overwrite each other's variables.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
-# C-preprocessor directives found in the sources and include files.
-# The default value is: YES.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
-# in the source code. If set to NO only conditional compilation will be
-# performed. Macro expansion can be done in a controlled way by setting
-# EXPAND_ONLY_PREDEF to YES.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-MACRO_EXPANSION = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
-# the macro expansion is limited to the macros specified with the PREDEFINED and
-# EXPAND_AS_DEFINED tags.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_ONLY_PREDEF = NO
-
-# If the SEARCH_INCLUDES tag is set to YES the includes files in the
-# INCLUDE_PATH will be searched if a #include is found.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SEARCH_INCLUDES = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by the
-# preprocessor.
-# This tag requires that the tag SEARCH_INCLUDES is set to YES.
-
-INCLUDE_PATH =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will be
-# used.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-INCLUDE_FILE_PATTERNS =
-
-# The PREDEFINED tag can be used to specify one or more macro names that are
-# defined before the preprocessor is started (similar to the -D option of e.g.
-# gcc). The argument of the tag is a list of macros of the form: name or
-# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
-# is assumed. To prevent a macro definition from being undefined via #undef or
-# recursively expanded use the := operator instead of the = operator.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-PREDEFINED =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
-# tag can be used to specify a list of macro names that should be expanded. The
-# macro definition that is found in the sources will be used. Use the PREDEFINED
-# tag if you want to use a different macro definition that overrules the
-# definition found in the source code.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
-# remove all refrences to function-like macros that are alone on a line, have an
-# all uppercase name, and do not end with a semicolon. Such function macros are
-# typically used for boiler-plate code, and will confuse the parser if not
-# removed.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES tag can be used to specify one or more tag files. For each tag
-# file the location of the external documentation should be added. The format of
-# a tag file without this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where loc1 and loc2 can be relative or absolute paths or URLs. See the
-# section "Linking to external documentation" for more information about the use
-# of tag files.
-# Note: Each tag file must have an unique name (where the name does NOT include
-# the path). If a tag file is not located in the directory in which doxygen is
-# run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
-# tag file that is based on the input files it reads. See section "Linking to
-# external documentation" for more information about the usage of tag files.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
-# class index. If set to NO only the inherited external classes will be listed.
-# The default value is: NO.
-
-ALLEXTERNALS = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
-# the modules index. If set to NO, only the current project's groups will be
-# listed.
-# The default value is: YES.
-
-EXTERNAL_GROUPS = YES
-
-# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
-# the related pages index. If set to NO, only the current project's pages will
-# be listed.
-# The default value is: YES.
-
-EXTERNAL_PAGES = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of 'which perl').
-# The default file (with absolute path) is: /usr/bin/perl.
-
-PERL_PATH = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
-# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
-# NO turns the diagrams off. Note that this option also works with HAVE_DOT
-# disabled, but it is recommended to install and use dot, since it yields more
-# powerful graphs.
-# The default value is: YES.
-
-CLASS_DIAGRAMS = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see:
-# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH =
-
-# You can include diagrams made with dia in doxygen documentation. Doxygen will
-# then run dia to produce the diagram and insert it in the documentation. The
-# DIA_PATH tag allows you to specify the directory where the dia binary resides.
-# If left empty dia is assumed to be found in the default search path.
-
-DIA_PATH =
-
-# If set to YES, the inheritance and collaboration graphs will hide inheritance
-# and usage relations if the target is undocumented or is not a class.
-# The default value is: YES.
-
-HIDE_UNDOC_RELATIONS = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz (see:
-# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
-# Bell Labs. The other options in this section have no effect if this option is
-# set to NO
-# The default value is: NO.
-
-HAVE_DOT = NO
-
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
-# to run in parallel. When set to 0 doxygen will base this on the number of
-# processors available in the system. You can set it explicitly to a value
-# larger than 0 to get control over the balance between CPU load and processing
-# speed.
-# Minimum value: 0, maximum value: 32, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_NUM_THREADS = 0
-
-# When you want a differently looking font n the dot files that doxygen
-# generates you can specify the font name using DOT_FONTNAME. You need to make
-# sure dot is able to find the font, which can be done by putting it in a
-# standard location or by setting the DOTFONTPATH environment variable or by
-# setting DOT_FONTPATH to the directory containing the font.
-# The default value is: Helvetica.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTNAME = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
-# dot graphs.
-# Minimum value: 4, maximum value: 24, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTSIZE = 10
-
-# By default doxygen will tell dot to use the default font as specified with
-# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
-# the path where dot can find it using this tag.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTPATH =
-
-# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
-# each documented class showing the direct and indirect inheritance relations.
-# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
-# graph for each documented class showing the direct and indirect implementation
-# dependencies (inheritance, containment, and class references variables) of the
-# class with other documented classes.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-COLLABORATION_GRAPH = YES
-
-# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
-# groups, showing the direct groups dependencies.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LOOK = NO
-
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
-# class node. If there are many fields or methods and many nodes the graph may
-# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
-# number of items for each type to make the size more manageable. Set this to 0
-# for no limit. Note that the threshold may be exceeded by 50% before the limit
-# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
-# but if the number exceeds 15, the total amount of fields shown is limited to
-# 10.
-# Minimum value: 0, maximum value: 100, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LIMIT_NUM_FIELDS = 10
-
-# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
-# collaboration graphs will show the relations between templates and their
-# instances.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-TEMPLATE_RELATIONS = NO
-
-# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
-# YES then doxygen will generate a graph for each documented file showing the
-# direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDE_GRAPH = YES
-
-# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
-# set to YES then doxygen will generate a graph for each documented file showing
-# the direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable call graphs for selected
-# functions only using the \callgraph command.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALL_GRAPH = NO
-
-# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable caller graphs for selected
-# functions only using the \callergraph command.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALLER_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
-# hierarchy of all classes instead of a textual one.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
-# dependencies a directory has on other directories in a graphical way. The
-# dependency relations are determined by the #include relations between the
-# files in the directories.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot.
-# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
-# to make the SVG files visible in IE 9+ (other browsers do not have this
-# requirement).
-# Possible values are: png, jpg, gif and svg.
-# The default value is: png.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_IMAGE_FORMAT = png
-
-# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
-# enable generation of interactive SVG images that allow zooming and panning.
-#
-# Note that this requires a modern browser other than Internet Explorer. Tested
-# and working are Firefox, Chrome, Safari, and Opera.
-# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
-# the SVG files visible. Older versions of IE do not have SVG support.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INTERACTIVE_SVG = NO
-
-# The DOT_PATH tag can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the \dotfile
-# command).
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOTFILE_DIRS =
-
-# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the \mscfile
-# command).
-
-MSCFILE_DIRS =
-
-# The DIAFILE_DIRS tag can be used to specify one or more directories that
-# contain dia files that are included in the documentation (see the \diafile
-# command).
-
-DIAFILE_DIRS =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
-# that will be shown in the graph. If the number of nodes in a graph becomes
-# larger than this value, doxygen will truncate the graph, which is visualized
-# by representing a node as a red box. Note that doxygen if the number of direct
-# children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
-# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-# Minimum value: 0, maximum value: 10000, default value: 50.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_GRAPH_MAX_NODES = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
-# generated by dot. A depth value of 3 means that only nodes reachable from the
-# root by following a path via at most 3 edges will be shown. Nodes that lay
-# further from the root node will be omitted. Note that setting this option to 1
-# or 2 may greatly reduce the computation time needed for large code bases. Also
-# note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-# Minimum value: 0, maximum value: 1000, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not seem
-# to support this out of the box.
-#
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_TRANSPARENT = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10) support
-# this, this feature is disabled by default.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_MULTI_TARGETS = YES
-
-# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
-# explaining the meaning of the various boxes and arrows in the dot generated
-# graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
-# files that are used to generate the various graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_CLEANUP = YES
diff --git a/media/libeffects/visualizer/Android.bp b/media/libeffects/visualizer/Android.bp
new file mode 100644
index 0000000..f6c585e
--- /dev/null
+++ b/media/libeffects/visualizer/Android.bp
@@ -0,0 +1,32 @@
+// Visualizer library
+cc_library_shared {
+ name: "libvisualizer",
+
+ vendor: true,
+
+ srcs: [
+ "EffectVisualizer.cpp",
+ ],
+
+ cflags: [
+ "-O2",
+ "-fvisibility=hidden",
+
+ "-DBUILD_FLOAT",
+ "-DSUPPORT_MC",
+
+ "-Wall",
+ "-Werror",
+ ],
+
+ shared_libs: [
+ "liblog",
+ ],
+
+ relative_install_path: "soundfx",
+
+ header_libs: [
+ "libaudioeffects",
+ "libaudioutils_headers",
+ ],
+}
diff --git a/media/libeffects/visualizer/Android.mk b/media/libeffects/visualizer/Android.mk
deleted file mode 100644
index 35e2f3d..0000000
--- a/media/libeffects/visualizer/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-# Visualizer library
-include $(CLEAR_VARS)
-
-LOCAL_VENDOR_MODULE := true
-LOCAL_SRC_FILES:= \
- EffectVisualizer.cpp
-
-LOCAL_CFLAGS+= -O2 -fvisibility=hidden
-LOCAL_CFLAGS += -Wall -Werror
-LOCAL_CFLAGS += -DBUILD_FLOAT -DSUPPORT_MC
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- liblog \
- libdl
-
-LOCAL_MODULE_RELATIVE_PATH := soundfx
-LOCAL_MODULE:= libvisualizer
-
-LOCAL_C_INCLUDES := \
- $(call include-path-for, audio-effects) \
- $(call include-path-for, audio-utils)
-
-
-LOCAL_HEADER_LIBRARIES += libhardware_headers
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index 20bc23d..c08d187 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -40,6 +40,7 @@
SET_DATA_SOURCE_FD,
SET_DATA_SOURCE_STREAM,
SET_DATA_SOURCE_CALLBACK,
+ SET_DATA_SOURCE_RTP,
SET_BUFFERING_SETTINGS,
GET_BUFFERING_SETTINGS,
PREPARE_ASYNC,
@@ -161,6 +162,15 @@
return reply.readInt32();
}
+ status_t setDataSource(const String8& rtpParams) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+ data.writeString8(rtpParams);
+ remote()->transact(SET_DATA_SOURCE_RTP, data, &reply);
+
+ return reply.readInt32();
+ }
+
// pass the buffered IGraphicBufferProducer to the media player service
status_t setVideoSurfaceTexture(const sp<IGraphicBufferProducer>& bufferProducer)
{
@@ -685,6 +695,12 @@
}
return NO_ERROR;
}
+ case SET_DATA_SOURCE_RTP: {
+ CHECK_INTERFACE(IMediaPlayer, data, reply);
+ String8 rtpParams = data.readString8();
+ reply->writeInt32(setDataSource(rtpParams));
+ return NO_ERROR;
+ }
case SET_VIDEO_SURFACETEXTURE: {
CHECK_INTERFACE(IMediaPlayer, data, reply);
sp<IGraphicBufferProducer> bufferProducer =
diff --git a/media/libmedia/include/media/IMediaPlayer.h b/media/libmedia/include/media/IMediaPlayer.h
index a4c0ec6..3548a1e 100644
--- a/media/libmedia/include/media/IMediaPlayer.h
+++ b/media/libmedia/include/media/IMediaPlayer.h
@@ -59,6 +59,7 @@
virtual status_t setDataSource(int fd, int64_t offset, int64_t length) = 0;
virtual status_t setDataSource(const sp<IStreamSource>& source) = 0;
virtual status_t setDataSource(const sp<IDataSource>& source) = 0;
+ virtual status_t setDataSource(const String8& rtpParams) = 0;
virtual status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer) = 0;
virtual status_t getBufferingSettings(
diff --git a/media/libmedia/include/media/mediaplayer.h b/media/libmedia/include/media/mediaplayer.h
index 2335c5a..d0a8e38 100644
--- a/media/libmedia/include/media/mediaplayer.h
+++ b/media/libmedia/include/media/mediaplayer.h
@@ -60,6 +60,7 @@
MEDIA_META_DATA = 202,
MEDIA_DRM_INFO = 210,
MEDIA_TIME_DISCONTINUITY = 211,
+ MEDIA_IMS_RX_NOTICE = 300,
MEDIA_AUDIO_ROUTING_CHANGED = 10000,
};
@@ -217,6 +218,7 @@
status_t setDataSource(int fd, int64_t offset, int64_t length);
status_t setDataSource(const sp<IDataSource> &source);
+ status_t setDataSource(const String8& rtpParams);
status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer);
status_t setListener(const sp<MediaPlayerListener>& listener);
diff --git a/media/libmedia/include/media/mediarecorder.h b/media/libmedia/include/media/mediarecorder.h
index 6e2d94d..fbcdb28 100644
--- a/media/libmedia/include/media/mediarecorder.h
+++ b/media/libmedia/include/media/mediarecorder.h
@@ -291,6 +291,8 @@
bool mIsOutputFileSet;
Mutex mLock;
Mutex mNotifyLock;
+
+ output_format mOutputFormat;
};
}; // namespace android
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 1fadc94..1b89fc7 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -195,6 +195,22 @@
return err;
}
+status_t MediaPlayer::setDataSource(const String8& rtpParams)
+{
+ ALOGV("setDataSource(rtpParams)");
+ status_t err = UNKNOWN_ERROR;
+ const sp<IMediaPlayerService> service(getMediaPlayerService());
+ if (service != 0) {
+ sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
+ if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
+ (NO_ERROR != player->setDataSource(rtpParams))) {
+ player.clear();
+ }
+ err = attachNewPlayer(player);
+ }
+ return err;
+}
+
status_t MediaPlayer::invoke(const Parcel& request, Parcel *reply)
{
Mutex::Autolock _l(mLock);
@@ -943,6 +959,9 @@
case MEDIA_META_DATA:
ALOGV("Received timed metadata message");
break;
+ case MEDIA_IMS_RX_NOTICE:
+ ALOGV("Received IMS Rx notice message");
+ break;
default:
ALOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2);
break;
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 70655d5..d9d1f25 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -244,6 +244,7 @@
mCurrentState = MEDIA_RECORDER_ERROR;
return ret;
}
+ mOutputFormat = (output_format)of;
mCurrentState = MEDIA_RECORDER_DATASOURCE_CONFIGURED;
return ret;
}
@@ -479,6 +480,13 @@
(MEDIA_RECORDER_PREPARED |
MEDIA_RECORDER_RECORDING |
MEDIA_RECORDER_ERROR));
+
+ // For RTP video, parameter should be set dynamically.
+ if (isInvalidState) {
+ if (mCurrentState == MEDIA_RECORDER_RECORDING &&
+ mOutputFormat == OUTPUT_FORMAT_RTP_AVP)
+ isInvalidState = false;
+ }
if (isInvalidState) {
ALOGE("setParameters is called in an invalid state: %d", mCurrentState);
return INVALID_OPERATION;
@@ -737,6 +745,7 @@
mIsAudioEncoderSet = false;
mIsVideoEncoderSet = false;
mIsOutputFileSet = false;
+ mOutputFormat = OUTPUT_FORMAT_DEFAULT;
}
// Release should be OK in any state
diff --git a/media/libmediaplayerservice/Android.bp b/media/libmediaplayerservice/Android.bp
index 5301f5c..324f4ae 100644
--- a/media/libmediaplayerservice/Android.bp
+++ b/media/libmediaplayerservice/Android.bp
@@ -16,6 +16,7 @@
"android.hardware.media.c2@1.0",
"android.hardware.media.omx@1.0",
"libbase",
+ "libandroid_net",
"libaudioclient",
"libbinder",
"libcamera_client",
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index c0da0ce..555f459 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1062,6 +1062,17 @@
return mStatus = setDataSource_post(p, p->setDataSource(dataSource));
}
+status_t MediaPlayerService::Client::setDataSource(
+ const String8& rtpParams) {
+ player_type playerType = NU_PLAYER;
+ sp<MediaPlayerBase> p = setDataSource_pre(playerType);
+ if (p == NULL) {
+ return NO_INIT;
+ }
+ // now set data source
+ return mStatus = setDataSource_post(p, p->setDataSource(rtpParams));
+}
+
void MediaPlayerService::Client::disconnectNativeWindow_l() {
if (mConnectedWindow != NULL) {
status_t err = nativeWindowDisconnect(
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 6431ca1..3d596a5 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -368,6 +368,7 @@
virtual status_t setDataSource(const sp<IStreamSource> &source);
virtual status_t setDataSource(const sp<IDataSource> &source);
+ virtual status_t setDataSource(const String8& rtpParams);
sp<MediaPlayerBase> setDataSource_pre(player_type playerType);
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index cf7f423..c6272c8 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -44,6 +44,7 @@
#include <media/stagefright/CameraSourceTimeLapse.h>
#include <media/stagefright/MPEG2TSWriter.h>
#include <media/stagefright/MPEG4Writer.h>
+#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/MediaCodecSource.h>
@@ -117,6 +118,11 @@
mAudioSource((audio_source_t)AUDIO_SOURCE_CNT), // initialize with invalid value
mPrivacySensitive(PRIVACY_SENSITIVE_DEFAULT),
mVideoSource(VIDEO_SOURCE_LIST_END),
+ mRTPCVOExtMap(-1),
+ mRTPCVODegrees(0),
+ mRTPSockDscp(0),
+ mRTPSockNetwork(0),
+ mLastSeqNo(0),
mStarted(false),
mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),
mDeviceCallbackEnabled(false),
@@ -567,6 +573,30 @@
// range that a specific encoder supports. The mismatch between the
// the target and requested bit rate will NOT be treated as an error.
mVideoBitRate = bitRate;
+
+ // A new bitrate(TMMBR) should be applied on runtime as well if OutputFormat is RTP_AVP
+ if (mOutputFormat == OUTPUT_FORMAT_RTP_AVP && mStarted && mPauseStartTimeUs == 0) {
+ // Regular I frames may overload the network so we reduce the bitrate to allow
+ // margins for the I frame overruns.
+ // Still send requested bitrate (TMMBR) in the reply (TMMBN).
+ const float coefficient = 0.8f;
+ mVideoBitRate = (bitRate * coefficient) / 1000 * 1000;
+ mVideoEncoderSource->setEncodingBitrate(mVideoBitRate);
+ ARTPWriter* rtpWriter = static_cast<ARTPWriter*>(mWriter.get());
+ rtpWriter->setTMMBNInfo(mOpponentID, bitRate);
+ }
+
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamVideoBitRateMode(int32_t bitRateMode) {
+ ALOGV("setParamVideoBitRateMode: %d", bitRateMode);
+ // TODO: clarify what bitrate mode of -1 is as these start from 0
+ if (bitRateMode < -1) {
+ ALOGE("Unsupported video bitrate mode: %d", bitRateMode);
+ return BAD_VALUE;
+ }
+ mVideoBitRateMode = bitRateMode;
return OK;
}
@@ -776,6 +806,105 @@
return OK;
}
+status_t StagefrightRecorder::setParamRtpLocalIp(const String8 &localIp) {
+ ALOGV("setParamVideoLocalIp: %s", localIp.string());
+
+ mLocalIp.setTo(localIp.string());
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamRtpLocalPort(int32_t localPort) {
+ ALOGV("setParamVideoLocalPort: %d", localPort);
+
+ mLocalPort = localPort;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamRtpRemoteIp(const String8 &remoteIp) {
+ ALOGV("setParamVideoRemoteIp: %s", remoteIp.string());
+
+ mRemoteIp.setTo(remoteIp.string());
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamRtpRemotePort(int32_t remotePort) {
+ ALOGV("setParamVideoRemotePort: %d", remotePort);
+
+ mRemotePort = remotePort;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamSelfID(int32_t selfID) {
+ ALOGV("setParamSelfID: %x", selfID);
+
+ mSelfID = selfID;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamVideoOpponentID(int32_t opponentID) {
+ mOpponentID = opponentID;
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamPayloadType(int32_t payloadType) {
+ ALOGV("setParamPayloadType: %d", payloadType);
+
+ mPayloadType = payloadType;
+
+ if (mStarted && mOutputFormat == OUTPUT_FORMAT_RTP_AVP) {
+ mWriter->updatePayloadType(mPayloadType);
+ }
+
+ return OK;
+}
+
+status_t StagefrightRecorder::setRTPCVOExtMap(int32_t extmap) {
+ ALOGV("setRtpCvoExtMap: %d", extmap);
+
+ mRTPCVOExtMap = extmap;
+ return OK;
+}
+
+status_t StagefrightRecorder::setRTPCVODegrees(int32_t cvoDegrees) {
+ Mutex::Autolock autolock(mLock);
+ ALOGV("setRtpCvoDegrees: %d", cvoDegrees);
+
+ mRTPCVODegrees = cvoDegrees;
+
+ if (mStarted && mOutputFormat == OUTPUT_FORMAT_RTP_AVP) {
+ mWriter->updateCVODegrees(mRTPCVODegrees);
+ }
+
+ return OK;
+}
+
+status_t StagefrightRecorder::setParamRtpDscp(int32_t dscp) {
+ ALOGV("setParamRtpDscp: %d", dscp);
+
+ mRTPSockDscp = dscp;
+ return OK;
+}
+
+status_t StagefrightRecorder::setSocketNetwork(int64_t networkHandle) {
+ ALOGV("setSocketNetwork: %llu", (unsigned long long) networkHandle);
+
+ mRTPSockNetwork = networkHandle;
+ if (mStarted && mOutputFormat == OUTPUT_FORMAT_RTP_AVP) {
+ mWriter->updateSocketNetwork(mRTPSockNetwork);
+ }
+ return OK;
+}
+
+status_t StagefrightRecorder::requestIDRFrame() {
+ status_t ret = BAD_VALUE;
+ if (mVideoEncoderSource != NULL) {
+ ret = mVideoEncoderSource->requestIDRFrame();
+ } else {
+ ALOGV("requestIDRFrame: Encoder not ready");
+ }
+ return ret;
+}
+
status_t StagefrightRecorder::setParameter(
const String8 &key, const String8 &value) {
ALOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
@@ -844,6 +973,11 @@
if (safe_strtoi32(value.string(), &video_bitrate)) {
return setParamVideoEncodingBitRate(video_bitrate);
}
+ } else if (key == "video-param-bitrate-mode") {
+ int32_t video_bitrate_mode;
+ if (safe_strtoi32(value.string(), &video_bitrate_mode)) {
+ return setParamVideoBitRateMode(video_bitrate_mode);
+ }
} else if (key == "video-param-rotation-angle-degrees") {
int32_t degrees;
if (safe_strtoi32(value.string(), °rees)) {
@@ -884,6 +1018,61 @@
if (safe_strtod(value.string(), &fps)) {
return setParamCaptureFps(fps);
}
+ } else if (key == "rtp-param-local-ip") {
+ return setParamRtpLocalIp(value);
+ } else if (key == "rtp-param-local-port") {
+ int32_t localPort;
+ if (safe_strtoi32(value.string(), &localPort)) {
+ return setParamRtpLocalPort(localPort);
+ }
+ } else if (key == "rtp-param-remote-ip") {
+ return setParamRtpRemoteIp(value);
+ } else if (key == "rtp-param-remote-port") {
+ int32_t remotePort;
+ if (safe_strtoi32(value.string(), &remotePort)) {
+ return setParamRtpRemotePort(remotePort);
+ }
+ } else if (key == "rtp-param-self-id") {
+ int32_t selfID;
+ int64_t temp;
+ if (safe_strtoi64(value.string(), &temp)) {
+ selfID = static_cast<int32_t>(temp);
+ return setParamSelfID(selfID);
+ }
+ } else if (key == "rtp-param-opponent-id") {
+ int32_t opnId;
+ int64_t temp;
+ if (safe_strtoi64(value.string(), &temp)) {
+ opnId = static_cast<int32_t>(temp);
+ return setParamVideoOpponentID(opnId);
+ }
+ } else if (key == "rtp-param-payload-type") {
+ int32_t payloadType;
+ if (safe_strtoi32(value.string(), &payloadType)) {
+ return setParamPayloadType(payloadType);
+ }
+ } else if (key == "rtp-param-ext-cvo-extmap") {
+ int32_t extmap;
+ if (safe_strtoi32(value.string(), &extmap)) {
+ return setRTPCVOExtMap(extmap);
+ }
+ } else if (key == "rtp-param-ext-cvo-degrees") {
+ int32_t degrees;
+ if (safe_strtoi32(value.string(), °rees)) {
+ return setRTPCVODegrees(degrees);
+ }
+ } else if (key == "video-param-request-i-frame") {
+ return requestIDRFrame();
+ } else if (key == "rtp-param-set-socket-dscp") {
+ int32_t dscp;
+ if (safe_strtoi32(value.string(), &dscp)) {
+ return setParamRtpDscp(dscp);
+ }
+ } else if (key == "rtp-param-set-socket-network") {
+ int64_t networkHandle;
+ if (safe_strtoi64(value.string(), &networkHandle)) {
+ return setSocketNetwork(networkHandle);
+ }
} else {
ALOGE("setParameter: failed to find key %s", key.string());
}
@@ -1050,6 +1239,17 @@
sp<MetaData> meta = new MetaData;
int64_t startTimeUs = systemTime() / 1000;
meta->setInt64(kKeyTime, startTimeUs);
+ meta->setInt32(kKeySelfID, mSelfID);
+ meta->setInt32(kKeyPayloadType, mPayloadType);
+ meta->setInt64(kKeySocketNetwork, mRTPSockNetwork);
+ if (mRTPCVOExtMap > 0) {
+ meta->setInt32(kKeyRtpExtMap, mRTPCVOExtMap);
+ meta->setInt32(kKeyRtpCvoDegrees, mRTPCVODegrees);
+ }
+ if (mRTPSockDscp > 0) {
+ meta->setInt32(kKeyRtpDscp, mRTPSockDscp);
+ }
+
status = mWriter->start(meta.get());
break;
}
@@ -1330,7 +1530,7 @@
mVideoEncoderSource = source;
}
- mWriter = new ARTPWriter(mOutputFd);
+ mWriter = new ARTPWriter(mOutputFd, mLocalIp, mLocalPort, mRemoteIp, mRemotePort, mLastSeqNo);
mWriter->addSource(source);
mWriter->setListener(mListener);
@@ -1767,6 +1967,10 @@
format->setInt32("stride", stride);
format->setInt32("slice-height", sliceHeight);
format->setInt32("color-format", colorFormat);
+ if (mOutputFormat == OUTPUT_FORMAT_RTP_AVP) {
+ // This indicates that a raw image provided to encoder needs to be rotated.
+ format->setInt32("rotation-degrees", mRotationDegrees);
+ }
} else {
format->setInt32("width", mVideoWidth);
format->setInt32("height", mVideoHeight);
@@ -1785,6 +1989,7 @@
}
format->setInt32("bitrate", mVideoBitRate);
+ format->setInt32("bitrate-mode", mVideoBitRateMode);
format->setInt32("frame-rate", mFrameRate);
format->setInt32("i-frame-interval", mIFramesIntervalSec);
@@ -2129,6 +2334,7 @@
if (mWriter != NULL) {
err = mWriter->stop();
+ mLastSeqNo = mWriter->getSequenceNum();
mWriter.clear();
}
@@ -2205,6 +2411,8 @@
mVideoHeight = 144;
mFrameRate = -1;
mVideoBitRate = 192000;
+ // Following MediaCodec's default
+ mVideoBitRateMode = BITRATE_MODE_VBR;
mSampleRate = 8000;
mAudioChannels = 1;
mAudioBitRate = 12200;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index a725bee..0362edd 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -119,6 +119,7 @@
int32_t mVideoWidth, mVideoHeight;
int32_t mFrameRate;
int32_t mVideoBitRate;
+ int32_t mVideoBitRateMode;
int32_t mAudioBitRate;
int32_t mAudioChannels;
int32_t mSampleRate;
@@ -138,6 +139,18 @@
int32_t mLongitudex10000;
int32_t mStartTimeOffsetMs;
int32_t mTotalBitRate;
+ String8 mLocalIp;
+ String8 mRemoteIp;
+ int32_t mLocalPort;
+ int32_t mRemotePort;
+ int32_t mSelfID;
+ int32_t mOpponentID;
+ int32_t mPayloadType;
+ int32_t mRTPCVOExtMap;
+ int32_t mRTPCVODegrees;
+ int32_t mRTPSockDscp;
+ int64_t mRTPSockNetwork;
+ uint32_t mLastSeqNo;
int64_t mDurationRecordedUs;
int64_t mStartedRecordingUs;
@@ -205,6 +218,7 @@
status_t setParamCaptureFpsEnable(int32_t timeLapseEnable);
status_t setParamCaptureFps(double fps);
status_t setParamVideoEncodingBitRate(int32_t bitRate);
+ status_t setParamVideoBitRateMode(int32_t bitRateMode);
status_t setParamVideoIFramesInterval(int32_t seconds);
status_t setParamVideoEncoderProfile(int32_t profile);
status_t setParamVideoEncoderLevel(int32_t level);
@@ -219,6 +233,18 @@
status_t setParamMovieTimeScale(int32_t timeScale);
status_t setParamGeoDataLongitude(int64_t longitudex10000);
status_t setParamGeoDataLatitude(int64_t latitudex10000);
+ status_t setParamRtpLocalIp(const String8 &localIp);
+ status_t setParamRtpLocalPort(int32_t localPort);
+ status_t setParamRtpRemoteIp(const String8 &remoteIp);
+ status_t setParamRtpRemotePort(int32_t remotePort);
+ status_t setParamSelfID(int32_t selfID);
+ status_t setParamVideoOpponentID(int32_t opponentID);
+ status_t setParamPayloadType(int32_t payloadType);
+ status_t setRTPCVOExtMap(int32_t extmap);
+ status_t setRTPCVODegrees(int32_t cvoDegrees);
+ status_t setParamRtpDscp(int32_t dscp);
+ status_t setSocketNetwork(int64_t networkHandle);
+ status_t requestIDRFrame();
void clipVideoBitRate();
void clipVideoFrameRate();
void clipVideoFrameWidth();
diff --git a/media/libmediaplayerservice/include/MediaPlayerInterface.h b/media/libmediaplayerservice/include/MediaPlayerInterface.h
index 436cb31..1b5cb4b 100644
--- a/media/libmediaplayerservice/include/MediaPlayerInterface.h
+++ b/media/libmediaplayerservice/include/MediaPlayerInterface.h
@@ -183,6 +183,10 @@
return INVALID_OPERATION;
}
+ virtual status_t setDataSource(const String8& /* rtpParams */) {
+ return INVALID_OPERATION;
+ }
+
// pass the buffered IGraphicBufferProducer to the media player service
virtual status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer) = 0;
diff --git a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
index 684ba2e..7bee002 100644
--- a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
+++ b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
@@ -52,18 +52,19 @@
if (binder == NULL) {
ALOGW("could not get the power manager service");
} else {
- mPowerManager = interface_cast<IPowerManager>(binder);
+ mPowerManager = interface_cast<os::IPowerManager>(binder);
binder->linkToDeath(mDeathRecipient);
}
}
if (mPowerManager != NULL) {
sp<IBinder> binder = new BBinder();
int64_t token = IPCThreadState::self()->clearCallingIdentity();
- status_t status = mPowerManager->acquireWakeLock(
- POWERMANAGER_PARTIAL_WAKE_LOCK,
- binder, String16("AWakeLock"), String16("media"));
+ binder::Status status = mPowerManager->acquireWakeLock(
+ binder, POWERMANAGER_PARTIAL_WAKE_LOCK,
+ String16("AWakeLock"), String16("media"),
+ {} /* workSource */, {} /* historyTag */);
IPCThreadState::self()->restoreCallingIdentity(token);
- if (status == NO_ERROR) {
+ if (status.isOk()) {
mWakeLockToken = binder;
mWakeLockCount++;
return true;
diff --git a/media/libmediaplayerservice/nuplayer/AWakeLock.h b/media/libmediaplayerservice/nuplayer/AWakeLock.h
index 323e7d7..8aa3b41 100644
--- a/media/libmediaplayerservice/nuplayer/AWakeLock.h
+++ b/media/libmediaplayerservice/nuplayer/AWakeLock.h
@@ -18,7 +18,7 @@
#define A_WAKELOCK_H_
#include <media/stagefright/foundation/ABase.h>
-#include <powermanager/IPowerManager.h>
+#include <android/os/IPowerManager.h>
#include <utils/RefBase.h>
namespace android {
@@ -37,7 +37,7 @@
virtual ~AWakeLock();
private:
- sp<IPowerManager> mPowerManager;
+ sp<os::IPowerManager> mPowerManager;
sp<IBinder> mWakeLockToken;
uint32_t mWakeLockCount;
diff --git a/media/libmediaplayerservice/nuplayer/Android.bp b/media/libmediaplayerservice/nuplayer/Android.bp
index 32c97cf..f5e44c7 100644
--- a/media/libmediaplayerservice/nuplayer/Android.bp
+++ b/media/libmediaplayerservice/nuplayer/Android.bp
@@ -14,6 +14,7 @@
"NuPlayerRenderer.cpp",
"NuPlayerStreamListener.cpp",
"RTSPSource.cpp",
+ "RTPSource.cpp",
"StreamingSource.cpp",
],
@@ -30,6 +31,7 @@
"frameworks/av/media/libstagefright/mpeg2ts",
"frameworks/av/media/libstagefright/rtsp",
"frameworks/av/media/libstagefright/timedtext",
+ "frameworks/native/include/android",
],
cflags: [
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index c1c4b55..4e7daa5 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -31,6 +31,7 @@
#include "NuPlayerDriver.h"
#include "NuPlayerRenderer.h"
#include "NuPlayerSource.h"
+#include "RTPSource.h"
#include "RTSPSource.h"
#include "StreamingSource.h"
#include "GenericSource.h"
@@ -368,6 +369,18 @@
return err;
}
+void NuPlayer::setDataSourceAsync(const String8& rtpParams) {
+ ALOGD("setDataSourceAsync for RTP = %s", rtpParams.string());
+ sp<AMessage> msg = new AMessage(kWhatSetDataSource, this);
+
+ sp<AMessage> notify = new AMessage(kWhatSourceNotify, this);
+ sp<Source> source = new RTPSource(notify, rtpParams);
+
+ msg->setObject("source", source);
+ msg->post();
+ mDataSourceType = DATA_SOURCE_TYPE_RTP;
+}
+
void NuPlayer::prepareAsync() {
ALOGV("prepareAsync");
@@ -1915,6 +1928,11 @@
format->setInt32("priority", 0 /* realtime */);
+ if (mDataSourceType == DATA_SOURCE_TYPE_RTP) {
+ ALOGV("instantiateDecoder: set decoder error free on stream corrupt.");
+ format->setInt32("corrupt-free", true);
+ }
+
if (!audio) {
AString mime;
CHECK(format->findString("mime", &mime));
@@ -2715,6 +2733,14 @@
break;
}
+ case Source::kWhatIMSRxNotice:
+ {
+ sp<AMessage> IMSRxNotice;
+ CHECK(msg->findMessage("message", &IMSRxNotice));
+ sendIMSRxNotice(IMSRxNotice);
+ break;
+ }
+
default:
TRESPASS();
}
@@ -2817,11 +2843,53 @@
}
}
+void NuPlayer::sendIMSRxNotice(const sp<AMessage> &msg) {
+ int32_t payloadType;
+
+ CHECK(msg->findInt32("payload-type", &payloadType));
+
+ Parcel in;
+ in.writeInt32(payloadType);
+
+ switch (payloadType) {
+ case NuPlayer::RTPSource::RTCP_TSFB: // RTCP TSFB
+ case NuPlayer::RTPSource::RTCP_PSFB: // RTCP PSFB
+ case NuPlayer::RTPSource::RTP_AUTODOWN:
+ {
+ int32_t feedbackType, id;
+ CHECK(msg->findInt32("feedback-type", &feedbackType));
+ CHECK(msg->findInt32("sender", &id));
+ in.writeInt32(feedbackType);
+ in.writeInt32(id);
+ if (payloadType == NuPlayer::RTPSource::RTCP_TSFB) {
+ int32_t bitrate;
+ CHECK(msg->findInt32("bit-rate", &bitrate));
+ in.writeInt32(bitrate);
+ }
+ break;
+ }
+ case NuPlayer::RTPSource::RTP_CVO:
+ {
+ int32_t cvo;
+ CHECK(msg->findInt32("cvo", &cvo));
+ in.writeInt32(cvo);
+ break;
+ }
+ default:
+ break;
+ }
+
+ notifyListener(MEDIA_IMS_RX_NOTICE, 0, 0, &in);
+}
+
const char *NuPlayer::getDataSourceType() {
switch (mDataSourceType) {
case DATA_SOURCE_TYPE_HTTP_LIVE:
return "HTTPLive";
+ case DATA_SOURCE_TYPE_RTP:
+ return "RTP";
+
case DATA_SOURCE_TYPE_RTSP:
return "RTSP";
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index ef4354c..0105248 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -51,6 +51,8 @@
void setDataSourceAsync(const sp<DataSource> &source);
+ void setDataSourceAsync(const String8& rtpParams);
+
status_t getBufferingSettings(BufferingSettings* buffering /* nonnull */);
status_t setBufferingSettings(const BufferingSettings& buffering);
@@ -117,6 +119,7 @@
struct GenericSource;
struct HTTPLiveSource;
struct Renderer;
+ struct RTPSource;
struct RTSPSource;
struct StreamingSource;
struct Action;
@@ -257,6 +260,7 @@
typedef enum {
DATA_SOURCE_TYPE_NONE,
DATA_SOURCE_TYPE_HTTP_LIVE,
+ DATA_SOURCE_TYPE_RTP,
DATA_SOURCE_TYPE_RTSP,
DATA_SOURCE_TYPE_GENERIC_URL,
DATA_SOURCE_TYPE_GENERIC_FD,
@@ -334,6 +338,7 @@
void sendSubtitleData(const sp<ABuffer> &buffer, int32_t baseIndex);
void sendTimedMetaData(const sp<ABuffer> &buffer);
void sendTimedTextData(const sp<ABuffer> &buffer);
+ void sendIMSRxNotice(const sp<AMessage> &msg);
void writeTrackInfo(Parcel* reply, const sp<AMessage>& format) const;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index f734439..8628edc 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -1050,7 +1050,7 @@
uint32_t flags = 0;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
- int32_t eos, csd;
+ int32_t eos, csd, cvo;
// we do not expect SYNCFRAME for decoder
if (buffer->meta()->findInt32("eos", &eos) && eos) {
flags |= MediaCodec::BUFFER_FLAG_EOS;
@@ -1058,6 +1058,24 @@
flags |= MediaCodec::BUFFER_FLAG_CODECCONFIG;
}
+ if (buffer->meta()->findInt32("cvo", (int32_t*)&cvo)) {
+ ALOGV("[%s] cvo(%d) found at %lld us", mComponentName.c_str(), cvo, (long long)timeUs);
+ switch (cvo) {
+ case 0:
+ codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_0);
+ break;
+ case 1:
+ codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_90);
+ break;
+ case 2:
+ codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_180);
+ break;
+ case 3:
+ codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_270);
+ break;
+ }
+ }
+
// Modular DRM
MediaBufferBase *mediaBuf = NULL;
NuPlayerDrm::CryptoInfo *cryptInfo = NULL;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 24afd43..039a56b 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -215,6 +215,26 @@
return mAsyncResult;
}
+status_t NuPlayerDriver::setDataSource(const String8& rtpParams) {
+ ALOGV("setDataSource(%p) rtp source", this);
+ Mutex::Autolock autoLock(mLock);
+
+ if (mState != STATE_IDLE) {
+ return INVALID_OPERATION;
+ }
+
+ mState = STATE_SET_DATASOURCE_PENDING;
+
+ mPlayer->setDataSourceAsync(rtpParams);
+
+ while (mState == STATE_SET_DATASOURCE_PENDING) {
+ mCondition.wait(mLock);
+ }
+
+ return mAsyncResult;
+}
+
+
status_t NuPlayerDriver::setVideoSurfaceTexture(
const sp<IGraphicBufferProducer> &bufferProducer) {
ALOGV("setVideoSurfaceTexture(%p)", this);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index 7001f4a..6557f0f 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -43,6 +43,8 @@
virtual status_t setDataSource(const sp<DataSource>& dataSource);
+ virtual status_t setDataSource(const String8& rtpParams);
+
virtual status_t setVideoSurfaceTexture(
const sp<IGraphicBufferProducer> &bufferProducer);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
index f137c52..eb39870 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
@@ -58,6 +58,7 @@
kWhatInstantiateSecureDecoders,
// Modular DRM
kWhatDrmInfo,
+ kWhatIMSRxNotice,
};
// The provides message is used to notify the player about various
diff --git a/media/libmediaplayerservice/nuplayer/RTPSource.cpp b/media/libmediaplayerservice/nuplayer/RTPSource.cpp
new file mode 100644
index 0000000..a6601cd
--- /dev/null
+++ b/media/libmediaplayerservice/nuplayer/RTPSource.cpp
@@ -0,0 +1,774 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "RTPSource"
+#include <utils/Log.h>
+
+#include "RTPSource.h"
+
+
+
+
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <string.h>
+
+namespace android {
+
+const int64_t kNearEOSTimeoutUs = 2000000ll; // 2 secs
+static int32_t kMaxAllowedStaleAccessUnits = 20;
+
+NuPlayer::RTPSource::RTPSource(
+ const sp<AMessage> ¬ify,
+ const String8& rtpParams)
+ : Source(notify),
+ mRTPParams(rtpParams),
+ mFlags(0),
+ mState(DISCONNECTED),
+ mFinalResult(OK),
+ mBuffering(false),
+ mInPreparationPhase(true),
+ mRTPConn(new ARTPConnection(ARTPConnection::kViLTEConnection)),
+ mEOSTimeoutAudio(0),
+ mEOSTimeoutVideo(0),
+ mLastCVOUpdated(-1) {
+ ALOGD("RTPSource initialized with rtpParams=%s", rtpParams.string());
+}
+
+NuPlayer::RTPSource::~RTPSource() {
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(id());
+ mLooper->unregisterHandler(mRTPConn->id());
+ mLooper->stop();
+ }
+}
+
+status_t NuPlayer::RTPSource::getBufferingSettings(
+ BufferingSettings* buffering /* nonnull */) {
+ Mutex::Autolock _l(mBufferingSettingsLock);
+ *buffering = mBufferingSettings;
+ return OK;
+}
+
+status_t NuPlayer::RTPSource::setBufferingSettings(const BufferingSettings& buffering) {
+ Mutex::Autolock _l(mBufferingSettingsLock);
+ mBufferingSettings = buffering;
+ return OK;
+}
+
+void NuPlayer::RTPSource::prepareAsync() {
+ if (mLooper == NULL) {
+ mLooper = new ALooper;
+ mLooper->setName("rtp");
+ mLooper->start();
+
+ mLooper->registerHandler(this);
+ mLooper->registerHandler(mRTPConn);
+ }
+
+ CHECK_EQ(mState, (int)DISCONNECTED);
+ mState = CONNECTING;
+
+ setParameters(mRTPParams);
+
+ TrackInfo *info = NULL;
+ unsigned i;
+ for (i = 0; i < mTracks.size(); i++) {
+ info = &mTracks.editItemAt(i);
+
+ if (info == NULL)
+ break;
+
+ AString sdp;
+ ASessionDescription::SDPStringFactory(sdp, info->mLocalIp,
+ info->mIsAudio, info->mLocalPort, info->mPayloadType, info->mAS, info->mCodecName,
+ NULL, info->mWidth, info->mHeight, info->mCVOExtMap);
+ ALOGD("RTPSource SDP =>\n%s", sdp.c_str());
+
+ sp<ASessionDescription> desc = new ASessionDescription;
+ bool isValidSdp = desc->setTo(sdp.c_str(), sdp.size());
+ ALOGV("RTPSource isValidSdp => %d", isValidSdp);
+
+ int sockRtp, sockRtcp;
+ ARTPConnection::MakeRTPSocketPair(&sockRtp, &sockRtcp, info->mLocalIp, info->mRemoteIp,
+ info->mLocalPort, info->mRemotePort, info->mSocketNetwork);
+
+ sp<AMessage> notify = new AMessage('accu', this);
+
+ ALOGV("RTPSource addStream. track-index=%d", i);
+ notify->setSize("trackIndex", i);
+ // index(i) should be started from 1. 0 is reserved for [root]
+ mRTPConn->addStream(sockRtp, sockRtcp, desc, i + 1, notify, false);
+ mRTPConn->setSelfID(info->mSelfID);
+ mRTPConn->setMinMaxBitrate(kMinVideoBitrate, info->mAS * 1000 /* kbps */);
+
+ info->mRTPSocket = sockRtp;
+ info->mRTCPSocket = sockRtcp;
+ info->mFirstSeqNumInSegment = 0;
+ info->mNewSegment = true;
+ info->mAllowedStaleAccessUnits = kMaxAllowedStaleAccessUnits;
+ info->mRTPAnchor = 0;
+ info->mNTPAnchorUs = -1;
+ info->mNormalPlayTimeRTP = 0;
+ info->mNormalPlayTimeUs = 0ll;
+
+ // index(i) should be started from 1. 0 is reserved for [root]
+ info->mPacketSource = new APacketSource(desc, i + 1);
+
+ int32_t timeScale;
+ sp<MetaData> format = getTrackFormat(i, &timeScale);
+ sp<AnotherPacketSource> source = new AnotherPacketSource(format);
+
+ if (info->mIsAudio) {
+ mAudioTrack = source;
+ } else {
+ mVideoTrack = source;
+ }
+
+ info->mSource = source;
+ }
+
+ if (mInPreparationPhase) {
+ mInPreparationPhase = false;
+ notifyPrepared();
+ }
+}
+
+void NuPlayer::RTPSource::start() {
+}
+
+void NuPlayer::RTPSource::pause() {
+ mState = PAUSED;
+}
+
+void NuPlayer::RTPSource::resume() {
+ mState = CONNECTING;
+}
+
+void NuPlayer::RTPSource::stop() {
+ if (mLooper == NULL) {
+ return;
+ }
+ sp<AMessage> msg = new AMessage(kWhatDisconnect, this);
+
+ sp<AMessage> dummy;
+ msg->postAndAwaitResponse(&dummy);
+}
+
+status_t NuPlayer::RTPSource::feedMoreTSData() {
+ Mutex::Autolock _l(mBufferingLock);
+ return mFinalResult;
+}
+
+sp<MetaData> NuPlayer::RTPSource::getFormatMeta(bool audio) {
+ sp<AnotherPacketSource> source = getSource(audio);
+
+ if (source == NULL) {
+ return NULL;
+ }
+
+ return source->getFormat();
+}
+
+bool NuPlayer::RTPSource::haveSufficientDataOnAllTracks() {
+ // We're going to buffer at least 2 secs worth data on all tracks before
+ // starting playback (both at startup and after a seek).
+
+ static const int64_t kMinDurationUs = 2000000ll;
+
+ int64_t mediaDurationUs = 0;
+ getDuration(&mediaDurationUs);
+ if ((mAudioTrack != NULL && mAudioTrack->isFinished(mediaDurationUs))
+ || (mVideoTrack != NULL && mVideoTrack->isFinished(mediaDurationUs))) {
+ return true;
+ }
+
+ status_t err;
+ int64_t durationUs;
+ if (mAudioTrack != NULL
+ && (durationUs = mAudioTrack->getBufferedDurationUs(&err))
+ < kMinDurationUs
+ && err == OK) {
+ ALOGV("audio track doesn't have enough data yet. (%.2f secs buffered)",
+ durationUs / 1E6);
+ return false;
+ }
+
+ if (mVideoTrack != NULL
+ && (durationUs = mVideoTrack->getBufferedDurationUs(&err))
+ < kMinDurationUs
+ && err == OK) {
+ ALOGV("video track doesn't have enough data yet. (%.2f secs buffered)",
+ durationUs / 1E6);
+ return false;
+ }
+
+ return true;
+}
+
+status_t NuPlayer::RTPSource::dequeueAccessUnit(
+ bool audio, sp<ABuffer> *accessUnit) {
+
+ sp<AnotherPacketSource> source = getSource(audio);
+
+ if (mState == PAUSED) {
+ ALOGV("-EWOULDBLOCK");
+ return -EWOULDBLOCK;
+ }
+
+ status_t finalResult;
+ if (!source->hasBufferAvailable(&finalResult)) {
+ if (finalResult == OK) {
+ int64_t mediaDurationUs = 0;
+ getDuration(&mediaDurationUs);
+ sp<AnotherPacketSource> otherSource = getSource(!audio);
+ status_t otherFinalResult;
+
+ // If other source already signaled EOS, this source should also signal EOS
+ if (otherSource != NULL &&
+ !otherSource->hasBufferAvailable(&otherFinalResult) &&
+ otherFinalResult == ERROR_END_OF_STREAM) {
+ source->signalEOS(ERROR_END_OF_STREAM);
+ return ERROR_END_OF_STREAM;
+ }
+
+ // If this source has detected near end, give it some time to retrieve more
+ // data before signaling EOS
+ if (source->isFinished(mediaDurationUs)) {
+ int64_t eosTimeout = audio ? mEOSTimeoutAudio : mEOSTimeoutVideo;
+ if (eosTimeout == 0) {
+ setEOSTimeout(audio, ALooper::GetNowUs());
+ } else if ((ALooper::GetNowUs() - eosTimeout) > kNearEOSTimeoutUs) {
+ setEOSTimeout(audio, 0);
+ source->signalEOS(ERROR_END_OF_STREAM);
+ return ERROR_END_OF_STREAM;
+ }
+ return -EWOULDBLOCK;
+ }
+
+ if (!(otherSource != NULL && otherSource->isFinished(mediaDurationUs))) {
+ // We should not enter buffering mode
+ // if any of the sources already have detected EOS.
+ // TODO: needs to be checked whether below line is needed or not.
+ // startBufferingIfNecessary();
+ }
+
+ return -EWOULDBLOCK;
+ }
+ return finalResult;
+ }
+
+ setEOSTimeout(audio, 0);
+
+ finalResult = source->dequeueAccessUnit(accessUnit);
+ if (finalResult != OK) {
+ return finalResult;
+ }
+
+ int32_t cvo;
+ if ((*accessUnit) != NULL && (*accessUnit)->meta()->findInt32("cvo", &cvo)) {
+ if (cvo != mLastCVOUpdated) {
+ sp<AMessage> msg = new AMessage();
+ msg->setInt32("payload-type", NuPlayer::RTPSource::RTP_CVO);
+ msg->setInt32("cvo", cvo);
+
+ sp<AMessage> notify = dupNotify();
+ notify->setInt32("what", kWhatIMSRxNotice);
+ notify->setMessage("message", msg);
+ notify->post();
+
+ ALOGV("notify cvo updated (%d)->(%d) to upper layer", mLastCVOUpdated, cvo);
+ mLastCVOUpdated = cvo;
+ }
+ }
+
+ return finalResult;
+}
+
+sp<AnotherPacketSource> NuPlayer::RTPSource::getSource(bool audio) {
+ return audio ? mAudioTrack : mVideoTrack;
+}
+
+void NuPlayer::RTPSource::setEOSTimeout(bool audio, int64_t timeout) {
+ if (audio) {
+ mEOSTimeoutAudio = timeout;
+ } else {
+ mEOSTimeoutVideo = timeout;
+ }
+}
+
+status_t NuPlayer::RTPSource::getDuration(int64_t *durationUs) {
+ *durationUs = 0ll;
+
+ int64_t audioDurationUs;
+ if (mAudioTrack != NULL
+ && mAudioTrack->getFormat()->findInt64(
+ kKeyDuration, &audioDurationUs)
+ && audioDurationUs > *durationUs) {
+ *durationUs = audioDurationUs;
+ }
+
+ int64_t videoDurationUs;
+ if (mVideoTrack != NULL
+ && mVideoTrack->getFormat()->findInt64(
+ kKeyDuration, &videoDurationUs)
+ && videoDurationUs > *durationUs) {
+ *durationUs = videoDurationUs;
+ }
+
+ return OK;
+}
+
+status_t NuPlayer::RTPSource::seekTo(int64_t seekTimeUs, MediaPlayerSeekMode mode) {
+ ALOGV("RTPSource::seekTo=%d, mode=%d", (int)seekTimeUs, mode);
+ return OK;
+}
+
+void NuPlayer::RTPSource::schedulePollBuffering() {
+ sp<AMessage> msg = new AMessage(kWhatPollBuffering, this);
+ msg->post(kBufferingPollIntervalUs); // 1 second intervals
+}
+
+void NuPlayer::RTPSource::onPollBuffering() {
+ schedulePollBuffering();
+}
+
+void NuPlayer::RTPSource::onMessageReceived(const sp<AMessage> &msg) {
+ ALOGV("onMessageReceived =%d", msg->what());
+
+ switch (msg->what()) {
+ case kWhatAccessUnitComplete:
+ {
+ if (mState == CONNECTING) {
+ mState = CONNECTED;
+ }
+
+ int32_t timeUpdate;
+ //"time-update" raised from ARTPConnection::parseSR()
+ if (msg->findInt32("time-update", &timeUpdate) && timeUpdate) {
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ uint32_t rtpTime;
+ uint64_t ntpTime;
+ CHECK(msg->findInt32("rtp-time", (int32_t *)&rtpTime));
+ CHECK(msg->findInt64("ntp-time", (int64_t *)&ntpTime));
+
+ onTimeUpdate(trackIndex, rtpTime, ntpTime);
+ break;
+ }
+
+ int32_t firstRTCP;
+ if (msg->findInt32("first-rtcp", &firstRTCP)) {
+ // There won't be an access unit here, it's just a notification
+ // that the data communication worked since we got the first
+ // rtcp packet.
+ ALOGV("first-rtcp");
+ break;
+ }
+
+ int32_t IMSRxNotice;
+ if (msg->findInt32("rtcp-event", &IMSRxNotice)) {
+ int32_t payloadType, feedbackType;
+ CHECK(msg->findInt32("payload-type", &payloadType));
+ CHECK(msg->findInt32("feedback-type", &feedbackType));
+
+ sp<AMessage> notify = dupNotify();
+ notify->setInt32("what", kWhatIMSRxNotice);
+ notify->setMessage("message", msg);
+ notify->post();
+
+ ALOGV("IMSRxNotice \t\t payload : %d feedback : %d",
+ payloadType, feedbackType);
+ break;
+ }
+
+ size_t trackIndex;
+ CHECK(msg->findSize("trackIndex", &trackIndex));
+
+ sp<ABuffer> accessUnit;
+ if (msg->findBuffer("access-unit", &accessUnit) == false) {
+ break;
+ }
+
+ int32_t damaged;
+ if (accessUnit->meta()->findInt32("damaged", &damaged)
+ && damaged) {
+ ALOGD("dropping damaged access unit.");
+ break;
+ }
+
+ // Implicitly assert on valid trackIndex here, which we ensure by
+ // never removing tracks.
+ TrackInfo *info = &mTracks.editItemAt(trackIndex);
+
+ sp<AnotherPacketSource> source = info->mSource;
+ if (source != NULL) {
+ uint32_t rtpTime;
+ CHECK(accessUnit->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ /* AnotherPacketSource make an assertion if there is no ntp provided
+ RTPSource should provide ntpUs all the times.
+ if (!info->mNPTMappingValid) {
+ // This is a live stream, we didn't receive any normal
+ // playtime mapping. We won't map to npt time.
+ source->queueAccessUnit(accessUnit);
+ break;
+ }
+ */
+
+ int64_t nptUs =
+ ((double)rtpTime - (double)info->mRTPTime)
+ / info->mTimeScale
+ * 1000000ll
+ + info->mNormalPlaytimeUs;
+
+ accessUnit->meta()->setInt64("timeUs", nptUs);
+
+ source->queueAccessUnit(accessUnit);
+ }
+
+ break;
+ }
+ case kWhatDisconnect:
+ {
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *info = &mTracks.editItemAt(i);
+
+ if (info->mIsAudio) {
+ mAudioTrack->signalEOS(ERROR_END_OF_STREAM);
+ mAudioTrack = NULL;
+ ALOGV("mAudioTrack disconnected");
+ } else {
+ mVideoTrack->signalEOS(ERROR_END_OF_STREAM);
+ mVideoTrack = NULL;
+ ALOGV("mVideoTrack disconnected");
+ }
+
+ mRTPConn->removeStream(info->mRTPSocket, info->mRTCPSocket);
+ close(info->mRTPSocket);
+ close(info->mRTCPSocket);
+ }
+
+ mTracks.clear();
+ mFirstAccessUnit = true;
+ mAllTracksHaveTime = false;
+ mNTPAnchorUs = -1;
+ mMediaAnchorUs = -1;
+ mLastMediaTimeUs = -1;
+ mNumAccessUnitsReceived = 0;
+ mReceivedFirstRTCPPacket = false;
+ mReceivedFirstRTPPacket = false;
+ mPausing = false;
+ mPauseGeneration = 0;
+
+ (new AMessage)->postReply(replyID);
+
+ break;
+ }
+ case kWhatPollBuffering:
+ break;
+ default:
+ TRESPASS();
+ }
+}
+
+void NuPlayer::RTPSource::onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime) {
+ ALOGV("onTimeUpdate track %d, rtpTime = 0x%08x, ntpTime = %#016llx",
+ trackIndex, rtpTime, (long long)ntpTime);
+
+ // convert ntpTime in Q32 seconds to microseconds. Note: this will not lose precision
+ // because ntpTimeUs is at most 52 bits (double holds 53 bits)
+ int64_t ntpTimeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+
+ TrackInfo *track = &mTracks.editItemAt(trackIndex);
+
+ track->mRTPAnchor = rtpTime;
+ track->mNTPAnchorUs = ntpTimeUs;
+
+ if (mNTPAnchorUs < 0) {
+ mNTPAnchorUs = ntpTimeUs;
+ mMediaAnchorUs = mLastMediaTimeUs;
+ }
+
+ if (!mAllTracksHaveTime) {
+ bool allTracksHaveTime = (mTracks.size() > 0);
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *track = &mTracks.editItemAt(i);
+ if (track->mNTPAnchorUs < 0) {
+ allTracksHaveTime = false;
+ break;
+ }
+ }
+ if (allTracksHaveTime) {
+ mAllTracksHaveTime = true;
+ ALOGI("Time now established for all tracks.");
+ }
+ }
+ if (mAllTracksHaveTime && dataReceivedOnAllChannels()) {
+ // Time is now established, lets start timestamping immediately
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ TrackInfo *trackInfo = &mTracks.editItemAt(i);
+ while (!trackInfo->mPackets.empty()) {
+ sp<ABuffer> accessUnit = *trackInfo->mPackets.begin();
+ trackInfo->mPackets.erase(trackInfo->mPackets.begin());
+
+ if (addMediaTimestamp(i, trackInfo, accessUnit)) {
+ postQueueAccessUnit(i, accessUnit);
+ }
+ }
+ }
+ }
+}
+
+bool NuPlayer::RTPSource::addMediaTimestamp(
+ int32_t trackIndex, const TrackInfo *track,
+ const sp<ABuffer> &accessUnit) {
+
+ uint32_t rtpTime;
+ CHECK(accessUnit->meta()->findInt32(
+ "rtp-time", (int32_t *)&rtpTime));
+
+ int64_t relRtpTimeUs =
+ (((int64_t)rtpTime - (int64_t)track->mRTPAnchor) * 1000000ll)
+ / track->mTimeScale;
+
+ int64_t ntpTimeUs = track->mNTPAnchorUs + relRtpTimeUs;
+
+ int64_t mediaTimeUs = mMediaAnchorUs + ntpTimeUs - mNTPAnchorUs;
+
+ if (mediaTimeUs > mLastMediaTimeUs) {
+ mLastMediaTimeUs = mediaTimeUs;
+ }
+
+ if (mediaTimeUs < 0) {
+ ALOGV("dropping early accessUnit.");
+ return false;
+ }
+
+ ALOGV("track %d rtpTime=%u mediaTimeUs = %lld us (%.2f secs)",
+ trackIndex, rtpTime, (long long)mediaTimeUs, mediaTimeUs / 1E6);
+
+ accessUnit->meta()->setInt64("timeUs", mediaTimeUs);
+
+ return true;
+}
+
+bool NuPlayer::RTPSource::dataReceivedOnAllChannels() {
+ TrackInfo *track;
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ track = &mTracks.editItemAt(i);
+ if (track->mPackets.empty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void NuPlayer::RTPSource::postQueueAccessUnit(
+ size_t trackIndex, const sp<ABuffer> &accessUnit) {
+ sp<AMessage> msg = new AMessage(kWhatAccessUnit, this);
+ msg->setInt32("what", kWhatAccessUnit);
+ msg->setSize("trackIndex", trackIndex);
+ msg->setBuffer("accessUnit", accessUnit);
+ msg->post();
+}
+
+void NuPlayer::RTPSource::postQueueEOS(size_t trackIndex, status_t finalResult) {
+ sp<AMessage> msg = new AMessage(kWhatEOS, this);
+ msg->setInt32("what", kWhatEOS);
+ msg->setSize("trackIndex", trackIndex);
+ msg->setInt32("finalResult", finalResult);
+ msg->post();
+}
+
+sp<MetaData> NuPlayer::RTPSource::getTrackFormat(size_t index, int32_t *timeScale) {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ const TrackInfo &info = mTracks.itemAt(index);
+
+ *timeScale = info.mTimeScale;
+
+ return info.mPacketSource->getFormat();
+}
+
+void NuPlayer::RTPSource::onConnected() {
+ ALOGV("onConnected");
+ mState = CONNECTED;
+}
+
+void NuPlayer::RTPSource::onDisconnected(const sp<AMessage> &msg) {
+ if (mState == DISCONNECTED) {
+ return;
+ }
+
+ status_t err;
+ CHECK(msg->findInt32("result", &err));
+ CHECK_NE(err, (status_t)OK);
+
+// mLooper->unregisterHandler(mHandler->id());
+// mHandler.clear();
+
+ if (mState == CONNECTING) {
+ // We're still in the preparation phase, signal that it
+ // failed.
+ notifyPrepared(err);
+ }
+
+ mState = DISCONNECTED;
+// setError(err);
+
+}
+
+status_t NuPlayer::RTPSource::setParameter(const String8 &key, const String8 &value) {
+ ALOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
+
+ bool isAudioKey = key.contains("audio");
+ TrackInfo *info = NULL;
+ for (unsigned i = 0; i < mTracks.size(); ++i) {
+ info = &mTracks.editItemAt(i);
+ if (info != NULL && info->mIsAudio == isAudioKey) {
+ ALOGV("setParameter: %s track (%d) found", isAudioKey ? "audio" : "video" , i);
+ break;
+ }
+ }
+
+ if (info == NULL) {
+ TrackInfo newTrackInfo;
+ newTrackInfo.mIsAudio = isAudioKey;
+ mTracks.push(newTrackInfo);
+ info = &mTracks.editTop();
+ }
+
+ if (key == "rtp-param-mime-type") {
+ info->mMimeType = value;
+
+ const char *mime = value.string();
+ const char *delimiter = strchr(mime, '/');
+ info->mCodecName = delimiter ? (delimiter + 1) : "<none>";
+
+ ALOGV("rtp-param-mime-type: mMimeType (%s) => mCodecName (%s)",
+ info->mMimeType.string(), info->mCodecName.string());
+ } else if (key == "video-param-decoder-profile") {
+ info->mCodecProfile = atoi(value);
+ } else if (key == "video-param-decoder-level") {
+ info->mCodecLevel = atoi(value);
+ } else if (key == "video-param-width") {
+ info->mWidth = atoi(value);
+ } else if (key == "video-param-height") {
+ info->mHeight = atoi(value);
+ } else if (key == "rtp-param-local-ip") {
+ info->mLocalIp = value;
+ } else if (key == "rtp-param-local-port") {
+ info->mLocalPort = atoi(value);
+ } else if (key == "rtp-param-remote-ip") {
+ info->mRemoteIp = value;
+ } else if (key == "rtp-param-remote-port") {
+ info->mRemotePort = atoi(value);
+ } else if (key == "rtp-param-payload-type") {
+ info->mPayloadType = atoi(value);
+ } else if (key == "rtp-param-as") {
+ //AS means guaranteed bit rate that negotiated from sdp.
+ info->mAS = atoi(value);
+ } else if (key == "rtp-param-rtp-timeout") {
+ } else if (key == "rtp-param-rtcp-timeout") {
+ } else if (key == "rtp-param-time-scale") {
+ } else if (key == "rtp-param-self-id") {
+ info->mSelfID = atoi(value);
+ } else if (key == "rtp-param-ext-cvo-extmap") {
+ info->mCVOExtMap = atoi(value);
+ } else if (key == "rtp-param-set-socket-network") {
+ int64_t networkHandle = atoll(value);
+ setSocketNetwork(networkHandle);
+ }
+
+ return OK;
+}
+
+status_t NuPlayer::RTPSource::setParameters(const String8 ¶ms) {
+ ALOGV("setParameters: %s", params.string());
+ const char *cparams = params.string();
+ const char *key_start = cparams;
+ for (;;) {
+ const char *equal_pos = strchr(key_start, '=');
+ if (equal_pos == NULL) {
+ ALOGE("Parameters %s miss a value", cparams);
+ return BAD_VALUE;
+ }
+ String8 key(key_start, equal_pos - key_start);
+ TrimString(&key);
+ if (key.length() == 0) {
+ ALOGE("Parameters %s contains an empty key", cparams);
+ return BAD_VALUE;
+ }
+ const char *value_start = equal_pos + 1;
+ const char *semicolon_pos = strchr(value_start, ';');
+ String8 value;
+ if (semicolon_pos == NULL) {
+ value.setTo(value_start);
+ } else {
+ value.setTo(value_start, semicolon_pos - value_start);
+ }
+ if (setParameter(key, value) != OK) {
+ return BAD_VALUE;
+ }
+ if (semicolon_pos == NULL) {
+ break; // Reaches the end
+ }
+ key_start = semicolon_pos + 1;
+ }
+ return OK;
+}
+
+void NuPlayer::RTPSource::setSocketNetwork(int64_t networkHandle) {
+ ALOGV("setSocketNetwork: %llu", (unsigned long long)networkHandle);
+
+ TrackInfo *info = NULL;
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ info = &mTracks.editItemAt(i);
+
+ if (info == NULL)
+ break;
+
+ info->mSocketNetwork = networkHandle;
+ }
+}
+
+// Trim both leading and trailing whitespace from the given string.
+//static
+void NuPlayer::RTPSource::TrimString(String8 *s) {
+ size_t num_bytes = s->bytes();
+ const char *data = s->string();
+
+ size_t leading_space = 0;
+ while (leading_space < num_bytes && isspace(data[leading_space])) {
+ ++leading_space;
+ }
+
+ size_t i = num_bytes;
+ while (i > leading_space && isspace(data[i - 1])) {
+ --i;
+ }
+
+ s->setTo(String8(&data[leading_space], i - leading_space));
+}
+
+} // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/RTPSource.h b/media/libmediaplayerservice/nuplayer/RTPSource.h
new file mode 100644
index 0000000..5085a7e
--- /dev/null
+++ b/media/libmediaplayerservice/nuplayer/RTPSource.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RTP_SOURCE_H_
+
+#define RTP_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/Utils.h>
+#include <media/BufferingSettings.h>
+
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+#include <utils/RefBase.h>
+
+#include "AnotherPacketSource.h"
+#include "APacketSource.h"
+#include "ARTPConnection.h"
+#include "ASessionDescription.h"
+#include "NuPlayerSource.h"
+
+
+
+
+
+
+namespace android {
+
+struct ALooper;
+struct AnotherPacketSource;
+
+struct NuPlayer::RTPSource : public NuPlayer::Source {
+ RTPSource(
+ const sp<AMessage> ¬ify,
+ const String8& rtpParams);
+
+ enum {
+ RTCP_TSFB = 205,
+ RTCP_PSFB = 206,
+ RTP_CVO = 300,
+ RTP_AUTODOWN = 400,
+ };
+
+ virtual status_t getBufferingSettings(
+ BufferingSettings* buffering /* nonnull */) override;
+ virtual status_t setBufferingSettings(const BufferingSettings& buffering) override;
+
+ virtual void prepareAsync();
+ virtual void start();
+ virtual void stop();
+ virtual void pause();
+ virtual void resume();
+
+ virtual status_t feedMoreTSData();
+
+ virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+
+ virtual status_t getDuration(int64_t *durationUs);
+ virtual status_t seekTo(
+ int64_t seekTimeUs,
+ MediaPlayerSeekMode mode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC) override;
+
+ void onMessageReceived(const sp<AMessage> &msg);
+
+protected:
+ virtual ~RTPSource();
+
+ virtual sp<MetaData> getFormatMeta(bool audio);
+
+private:
+ enum {
+ kWhatAccessUnit = 'accU',
+ kWhatAccessUnitComplete = 'accu',
+ kWhatDisconnect = 'disc',
+ kWhatEOS = 'eos!',
+ kWhatPollBuffering = 'poll',
+ kWhatSetBufferingSettings = 'sBuS',
+ };
+
+ const int64_t kBufferingPollIntervalUs = 1000000ll;
+ const int32_t kMinVideoBitrate = 192000; /* bps */
+
+ enum State {
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED,
+ PAUSED,
+ };
+
+ struct TrackInfo {
+
+ /* SDP of track */
+ bool mIsAudio;
+ int32_t mPayloadType;
+ String8 mMimeType;
+ String8 mCodecName;
+ int32_t mCodecProfile;
+ int32_t mCodecLevel;
+ int32_t mWidth;
+ int32_t mHeight;
+ String8 mLocalIp;
+ String8 mRemoteIp;
+ int32_t mLocalPort;
+ int32_t mRemotePort;
+ int64_t mSocketNetwork;
+ int32_t mTimeScale;
+ int32_t mAS;
+
+ /* Unique ID indicates itself */
+ uint32_t mSelfID;
+ /* extmap:<value> for CVO will be set to here */
+ int32_t mCVOExtMap;
+
+ /* a copy of TrackInfo in RTSPSource */
+ sp<AnotherPacketSource> mSource;
+ uint32_t mRTPTime;
+ int64_t mNormalPlaytimeUs;
+ bool mNPTMappingValid;
+
+ /* a copy of TrackInfo in MyHandler.h */
+ int mRTPSocket;
+ int mRTCPSocket;
+ uint32_t mFirstSeqNumInSegment;
+ bool mNewSegment;
+ int32_t mAllowedStaleAccessUnits;
+ uint32_t mRTPAnchor;
+ int64_t mNTPAnchorUs;
+ bool mEOSReceived;
+ uint32_t mNormalPlayTimeRTP;
+ int64_t mNormalPlayTimeUs;
+ sp<APacketSource> mPacketSource;
+ List<sp<ABuffer>> mPackets;
+ };
+
+ const String8 mRTPParams;
+ uint32_t mFlags;
+ State mState;
+ status_t mFinalResult;
+
+ // below 3 parameters need to be checked whether it needed or not.
+ Mutex mBufferingLock;
+ bool mBuffering;
+ bool mInPreparationPhase;
+ Mutex mBufferingSettingsLock;
+ BufferingSettings mBufferingSettings;
+
+ sp<ALooper> mLooper;
+
+ sp<ARTPConnection> mRTPConn;
+
+ Vector<TrackInfo> mTracks;
+ sp<AnotherPacketSource> mAudioTrack;
+ sp<AnotherPacketSource> mVideoTrack;
+
+ int64_t mEOSTimeoutAudio;
+ int64_t mEOSTimeoutVideo;
+
+ /* MyHandler.h */
+ bool mFirstAccessUnit;
+ bool mAllTracksHaveTime;
+ int64_t mNTPAnchorUs;
+ int64_t mMediaAnchorUs;
+ int64_t mLastMediaTimeUs;
+ int64_t mNumAccessUnitsReceived;
+ int32_t mLastCVOUpdated;
+ bool mReceivedFirstRTCPPacket;
+ bool mReceivedFirstRTPPacket;
+ bool mPausing;
+ int32_t mPauseGeneration;
+
+ sp<AnotherPacketSource> getSource(bool audio);
+
+ /* MyHandler.h */
+ void onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime);
+ bool addMediaTimestamp(int32_t trackIndex, const TrackInfo *track,
+ const sp<ABuffer> &accessUnit);
+ bool dataReceivedOnAllChannels();
+ void postQueueAccessUnit(size_t trackIndex, const sp<ABuffer> &accessUnit);
+ void postQueueEOS(size_t trackIndex, status_t finalResult);
+ sp<MetaData> getTrackFormat(size_t index, int32_t *timeScale);
+ void onConnected();
+ void onDisconnected(const sp<AMessage> &msg);
+
+ void schedulePollBuffering();
+ void onPollBuffering();
+
+ bool haveSufficientDataOnAllTracks();
+
+ void setEOSTimeout(bool audio, int64_t timeout);
+
+ status_t setParameters(const String8 ¶ms);
+ status_t setParameter(const String8 &key, const String8 &value);
+ void setSocketNetwork(int64_t networkHandle);
+ static void TrimString(String8 *s);
+
+ DISALLOW_EVIL_CONSTRUCTORS(RTPSource);
+};
+
+} // namespace android
+
+#endif // RTP_SOURCE_H_
diff --git a/media/libmediatranscoding/.clang-format b/media/libmediatranscoding/.clang-format
index 3198d00..f23b842 100644
--- a/media/libmediatranscoding/.clang-format
+++ b/media/libmediatranscoding/.clang-format
@@ -26,4 +26,26 @@
DerivePointerAlignment: false
IndentWidth: 4
PointerAlignment: Left
-TabWidth: 4
\ No newline at end of file
+TabWidth: 4
+
+# Deviations from the above file:
+# "Don't indent the section label"
+AccessModifierOffset: -4
+# "Each line of text in your code should be at most 100 columns long."
+ColumnLimit: 100
+# "Constructor initializer lists can be all on one line or with subsequent
+# lines indented eight spaces.". clang-format does not support having the colon
+# on the same line as the constructor function name, so this is the best
+# approximation of that rule, which makes all entries in the list (except the
+# first one) have an eight space indentation.
+ConstructorInitializerIndentWidth: 6
+# There is nothing in go/droidcppstyle about case labels, but there seems to be
+# more code that does not indent the case labels in frameworks/base.
+IndentCaseLabels: false
+# There have been some bugs in which subsequent formatting operations introduce
+# weird comment jumps.
+ReflowComments: false
+# Android supports C++17 now, but it seems only Cpp11 will work now.
+# "Cpp11 is a deprecated alias for Latest" according to
+# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+Standard: Cpp11
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index f948bd8..2753db4 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -21,7 +21,8 @@
local_include_dir: "aidl",
srcs: [
"aidl/android/media/IMediaTranscodingService.aidl",
- "aidl/android/media/ITranscodingServiceClient.aidl",
+ "aidl/android/media/ITranscodingClient.aidl",
+ "aidl/android/media/ITranscodingClientCallback.aidl",
"aidl/android/media/TranscodingErrorCode.aidl",
"aidl/android/media/TranscodingJobPriority.aidl",
"aidl/android/media/TranscodingType.aidl",
@@ -36,7 +37,8 @@
name: "libmediatranscoding",
srcs: [
- "TranscodingClientManager.cpp"
+ "TranscodingClientManager.cpp",
+ "TranscodingJobScheduler.cpp",
],
shared_libs: [
@@ -44,6 +46,7 @@
"libcutils",
"liblog",
"libutils",
+ "libmediatranscoder",
],
export_include_dirs: ["include"],
@@ -53,9 +56,13 @@
],
cflags: [
- "-Werror",
- "-Wno-error=deprecated-declarations",
"-Wall",
+ "-Werror",
+ "-Wformat",
+ "-Wno-error=deprecated-declarations",
+ "-Wthread-safety",
+ "-Wunused",
+ "-Wunreachable-code",
],
sanitize: {
diff --git a/media/libmediatranscoding/OWNERS b/media/libmediatranscoding/OWNERS
index 02287cb..b08d573 100644
--- a/media/libmediatranscoding/OWNERS
+++ b/media/libmediatranscoding/OWNERS
@@ -1,3 +1,5 @@
-akersten@google.com
+chz@google.com
+gokrishnan@google.com
hkuang@google.com
lnilsson@google.com
+pawin@google.com
diff --git a/media/libmediatranscoding/TranscodingClientManager.cpp b/media/libmediatranscoding/TranscodingClientManager.cpp
index 7252437..6bc8613 100644
--- a/media/libmediatranscoding/TranscodingClientManager.cpp
+++ b/media/libmediatranscoding/TranscodingClientManager.cpp
@@ -17,31 +17,138 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "TranscodingClientManager"
+#include <aidl/android/media/BnTranscodingClient.h>
+#include <android/binder_ibinder.h>
#include <inttypes.h>
#include <media/TranscodingClientManager.h>
+#include <media/TranscodingRequest.h>
#include <utils/Log.h>
namespace android {
+using ::aidl::android::media::BnTranscodingClient;
+using ::aidl::android::media::TranscodingJobParcel;
+using ::aidl::android::media::TranscodingRequestParcel;
using Status = ::ndk::ScopedAStatus;
+using ::ndk::SpAIBinder;
-// static
-TranscodingClientManager& TranscodingClientManager::getInstance() {
- static TranscodingClientManager gInstance{};
- return gInstance;
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * ClientImpl implements a single client and contains all its information.
+ */
+struct TranscodingClientManager::ClientImpl : public BnTranscodingClient {
+ /* The remote client callback that this ClientInfo is associated with.
+ * Once the ClientInfo is created, we hold an SpAIBinder so that the binder
+ * object doesn't get created again, otherwise the binder object pointer
+ * may not be unique.
+ */
+ SpAIBinder mClientCallback;
+ /* A unique id assigned to the client by the service. This number is used
+ * by the service for indexing. Here we use the binder object's pointer
+ * (casted to int64t_t) as the client id.
+ */
+ ClientIdType mClientId;
+ pid_t mClientPid;
+ uid_t mClientUid;
+ std::string mClientName;
+ std::string mClientOpPackageName;
+
+ // Next jobId to assign
+ std::atomic<std::int32_t> mNextJobId;
+ // Pointer to the client manager for this client
+ TranscodingClientManager* mOwner;
+
+ ClientImpl(const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
+ const std::string& clientName, const std::string& opPackageName,
+ TranscodingClientManager* owner);
+
+ Status submitRequest(const TranscodingRequestParcel& /*in_request*/,
+ TranscodingJobParcel* /*out_job*/, bool* /*_aidl_return*/) override;
+
+ Status cancelJob(int32_t /*in_jobId*/, bool* /*_aidl_return*/) override;
+
+ Status getJobWithId(int32_t /*in_jobId*/, TranscodingJobParcel* /*out_job*/,
+ bool* /*_aidl_return*/) override;
+
+ Status unregister() override;
+};
+
+TranscodingClientManager::ClientImpl::ClientImpl(
+ const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
+ const std::string& clientName, const std::string& opPackageName,
+ TranscodingClientManager* owner)
+ : mClientCallback((callback != nullptr) ? callback->asBinder() : nullptr),
+ mClientId((int64_t)mClientCallback.get()),
+ mClientPid(pid),
+ mClientUid(uid),
+ mClientName(clientName),
+ mClientOpPackageName(opPackageName),
+ mNextJobId(0),
+ mOwner(owner) {}
+
+Status TranscodingClientManager::ClientImpl::submitRequest(
+ const TranscodingRequestParcel& in_request, TranscodingJobParcel* out_job,
+ bool* _aidl_return) {
+ if (in_request.fileName.empty()) {
+ // This is the only error we check for now.
+ *_aidl_return = false;
+ return Status::ok();
+ }
+
+ int32_t jobId = mNextJobId.fetch_add(1);
+
+ *_aidl_return =
+ mOwner->mJobScheduler->submit(mClientId, jobId, mClientUid, in_request,
+ ITranscodingClientCallback::fromBinder(mClientCallback));
+
+ if (*_aidl_return) {
+ out_job->jobId = jobId;
+
+ // TODO(chz): is some of this coming from JobScheduler?
+ *(TranscodingRequest*)&out_job->request = in_request;
+ out_job->awaitNumberOfJobs = 0;
+ }
+ return Status::ok();
}
+Status TranscodingClientManager::ClientImpl::cancelJob(int32_t in_jobId, bool* _aidl_return) {
+ *_aidl_return = mOwner->mJobScheduler->cancel(mClientId, in_jobId);
+ return Status::ok();
+}
+
+Status TranscodingClientManager::ClientImpl::getJobWithId(int32_t in_jobId,
+ TranscodingJobParcel* out_job,
+ bool* _aidl_return) {
+ *_aidl_return = mOwner->mJobScheduler->getJob(mClientId, in_jobId, &out_job->request);
+
+ if (*_aidl_return) {
+ out_job->jobId = in_jobId;
+ out_job->awaitNumberOfJobs = 0;
+ }
+ return Status::ok();
+}
+
+Status TranscodingClientManager::ClientImpl::unregister() {
+ // TODO(chz): Decide what to do about this client's jobs.
+ // If app crashed, it could be relaunched later. Do we want to keep the
+ // jobs around for that?
+ mOwner->removeClient(mClientId);
+ return Status::ok();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
// static
void TranscodingClientManager::BinderDiedCallback(void* cookie) {
- int32_t clientId = static_cast<int32_t>(reinterpret_cast<intptr_t>(cookie));
- ALOGD("Client %" PRId32 " is dead", clientId);
- // Don't check for pid validity since we know it's already dead.
- TranscodingClientManager& manager = TranscodingClientManager::getInstance();
- manager.removeClient(clientId);
+ ClientImpl* client = static_cast<ClientImpl*>(cookie);
+ ALOGD("Client %lld is dead", (long long)client->mClientId);
+ client->unregister();
}
-TranscodingClientManager::TranscodingClientManager()
- : mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)) {
+TranscodingClientManager::TranscodingClientManager(
+ const std::shared_ptr<SchedulerClientInterface>& scheduler)
+ : mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)), mJobScheduler(scheduler) {
ALOGD("TranscodingClientManager started");
}
@@ -49,97 +156,94 @@
ALOGD("TranscodingClientManager exited");
}
-bool TranscodingClientManager::isClientIdRegistered(int32_t clientId) const {
- std::scoped_lock lock{mLock};
- return mClientIdToClientInfoMap.find(clientId) != mClientIdToClientInfoMap.end();
-}
-
void TranscodingClientManager::dumpAllClients(int fd, const Vector<String16>& args __unused) {
String8 result;
const size_t SIZE = 256;
char buffer[SIZE];
+ std::scoped_lock lock{mLock};
- snprintf(buffer, SIZE, " Total num of Clients: %zu\n", mClientIdToClientInfoMap.size());
+ snprintf(buffer, SIZE, " Total num of Clients: %zu\n", mClientIdToClientMap.size());
result.append(buffer);
- if (mClientIdToClientInfoMap.size() > 0) {
+ if (mClientIdToClientMap.size() > 0) {
snprintf(buffer, SIZE, "========== Dumping all clients =========\n");
result.append(buffer);
}
- for (const auto& iter : mClientIdToClientInfoMap) {
- const std::shared_ptr<ITranscodingServiceClient> client = iter.second->mClient;
- std::string clientName;
- Status status = client->getName(&clientName);
- if (!status.isOk()) {
- ALOGE("Failed to get client: %d information", iter.first);
- continue;
- }
- snprintf(buffer, SIZE, " -- Clients: %d name: %s\n", iter.first, clientName.c_str());
+ for (const auto& iter : mClientIdToClientMap) {
+ snprintf(buffer, SIZE, " -- Client id: %lld name: %s\n", (long long)iter.first,
+ iter.second->mClientName.c_str());
result.append(buffer);
}
write(fd, result.string(), result.size());
}
-status_t TranscodingClientManager::addClient(std::unique_ptr<ClientInfo> client) {
+status_t TranscodingClientManager::addClient(
+ const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
+ const std::string& clientName, const std::string& opPackageName,
+ std::shared_ptr<ITranscodingClient>* outClient) {
// Validate the client.
- if (client == nullptr || client->mClientId < 0 || client->mClientPid < 0 ||
- client->mClientUid < 0 || client->mClientOpPackageName.empty() ||
- client->mClientOpPackageName == "") {
+ if (callback == nullptr || pid < 0 || clientName.empty() || opPackageName.empty()) {
ALOGE("Invalid client");
return BAD_VALUE;
}
+ // Creates the client and uses its process id as client id.
+ std::shared_ptr<ClientImpl> client = ::ndk::SharedRefBase::make<ClientImpl>(
+ callback, pid, uid, clientName, opPackageName, this);
+
std::scoped_lock lock{mLock};
- // Check if the client already exists.
- if (mClientIdToClientInfoMap.count(client->mClientId) != 0) {
- ALOGW("Client already exists.");
+ // Checks if the client already registers.
+ if (mClientIdToClientMap.find(client->mClientId) != mClientIdToClientMap.end()) {
return ALREADY_EXISTS;
}
- ALOGD("Adding client id %d pid: %d uid: %d %s", client->mClientId, client->mClientPid,
- client->mClientUid, client->mClientOpPackageName.c_str());
+ ALOGD("Adding client id %lld, pid %d, uid %d, name %s, package %s",
+ (long long)client->mClientId, client->mClientPid, client->mClientUid,
+ client->mClientName.c_str(), client->mClientOpPackageName.c_str());
- AIBinder_linkToDeath(client->mClient->asBinder().get(), mDeathRecipient.get(),
- reinterpret_cast<void*>(client->mClientId));
+ AIBinder_linkToDeath(client->mClientCallback.get(), mDeathRecipient.get(),
+ reinterpret_cast<void*>(client.get()));
// Adds the new client to the map.
- mClientIdToClientInfoMap[client->mClientId] = std::move(client);
+ mClientIdToClientMap[client->mClientId] = client;
+
+ *outClient = client;
return OK;
}
-status_t TranscodingClientManager::removeClient(int32_t clientId) {
- ALOGD("Removing client id %d", clientId);
+status_t TranscodingClientManager::removeClient(ClientIdType clientId) {
+ ALOGD("Removing client id %lld", (long long)clientId);
std::scoped_lock lock{mLock};
// Checks if the client is valid.
- auto it = mClientIdToClientInfoMap.find(clientId);
- if (it == mClientIdToClientInfoMap.end()) {
- ALOGE("Client id %d does not exist", clientId);
+ auto it = mClientIdToClientMap.find(clientId);
+ if (it == mClientIdToClientMap.end()) {
+ ALOGE("Client id %lld does not exist", (long long)clientId);
return INVALID_OPERATION;
}
- std::shared_ptr<ITranscodingServiceClient> client = it->second->mClient;
+ SpAIBinder callback = it->second->mClientCallback;
// Check if the client still live. If alive, unlink the death.
- if (client) {
- AIBinder_unlinkToDeath(client->asBinder().get(), mDeathRecipient.get(),
- reinterpret_cast<void*>(clientId));
+ if (callback.get() != nullptr) {
+ AIBinder_unlinkToDeath(callback.get(), mDeathRecipient.get(),
+ reinterpret_cast<void*>(it->second.get()));
}
// Erase the entry.
- mClientIdToClientInfoMap.erase(it);
+ mClientIdToClientMap.erase(it);
return OK;
}
size_t TranscodingClientManager::getNumOfClients() const {
std::scoped_lock lock{mLock};
- return mClientIdToClientInfoMap.size();
+ return mClientIdToClientMap.size();
}
} // namespace android
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
new file mode 100644
index 0000000..6e235c6
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "TranscodingJobScheduler"
+
+#define VALIDATE_STATE 1
+
+#include <inttypes.h>
+#include <media/TranscodingJobScheduler.h>
+#include <utils/Log.h>
+
+#include <utility>
+
+namespace android {
+
+constexpr static uid_t OFFLINE_UID = -1;
+
+//static
+String8 TranscodingJobScheduler::jobToString(const JobKeyType& jobKey) {
+ return String8::format("{client:%lld, job:%d}", (long long)jobKey.first, jobKey.second);
+}
+
+TranscodingJobScheduler::TranscodingJobScheduler(
+ const std::shared_ptr<TranscoderInterface>& transcoder,
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy)
+ : mTranscoder(transcoder), mUidPolicy(uidPolicy), mCurrentJob(nullptr), mResourceLost(false) {
+ // Only push empty offline queue initially. Realtime queues are added when requests come in.
+ mUidSortedList.push_back(OFFLINE_UID);
+ mOfflineUidIterator = mUidSortedList.begin();
+ mJobQueues.emplace(OFFLINE_UID, JobQueueType());
+}
+
+TranscodingJobScheduler::~TranscodingJobScheduler() {}
+
+TranscodingJobScheduler::Job* TranscodingJobScheduler::getTopJob_l() {
+ if (mJobMap.empty()) {
+ return nullptr;
+ }
+ uid_t topUid = *mUidSortedList.begin();
+ JobKeyType topJobKey = *mJobQueues[topUid].begin();
+ return &mJobMap[topJobKey];
+}
+
+void TranscodingJobScheduler::updateCurrentJob_l() {
+ Job* topJob = getTopJob_l();
+ Job* curJob = mCurrentJob;
+ ALOGV("updateCurrentJob: topJob is %s, curJob is %s",
+ topJob == nullptr ? "null" : jobToString(topJob->key).c_str(),
+ curJob == nullptr ? "null" : jobToString(curJob->key).c_str());
+
+ // If we found a topJob that should be run, and it's not already running,
+ // take some actions to ensure it's running.
+ if (topJob != nullptr && (topJob != curJob || topJob->state != Job::RUNNING)) {
+ // If another job is currently running, pause it first.
+ if (curJob != nullptr && curJob->state == Job::RUNNING) {
+ mTranscoder->pause(curJob->key.first, curJob->key.second);
+ curJob->state = Job::PAUSED;
+ }
+ // If we are not experiencing resource loss, we can start or resume
+ // the topJob now.
+ if (!mResourceLost) {
+ if (topJob->state == Job::NOT_STARTED) {
+ mTranscoder->start(topJob->key.first, topJob->key.second);
+ } else if (topJob->state == Job::PAUSED) {
+ mTranscoder->resume(topJob->key.first, topJob->key.second);
+ }
+ topJob->state = Job::RUNNING;
+ }
+ }
+ mCurrentJob = topJob;
+}
+
+void TranscodingJobScheduler::removeJob_l(const JobKeyType& jobKey) {
+ ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+ return;
+ }
+
+ // Remove job from uid's queue.
+ const uid_t uid = mJobMap[jobKey].uid;
+ JobQueueType& jobQueue = mJobQueues[uid];
+ auto it = std::find(jobQueue.begin(), jobQueue.end(), jobKey);
+ if (it == jobQueue.end()) {
+ ALOGE("couldn't find job %s in queue for uid %d", jobToString(jobKey).c_str(), uid);
+ return;
+ }
+ jobQueue.erase(it);
+
+ // If this is the last job in a real-time queue, remove this uid's queue.
+ if (uid != OFFLINE_UID && jobQueue.empty()) {
+ mUidSortedList.remove(uid);
+ mJobQueues.erase(uid);
+ }
+
+ // Clear current job.
+ if (mCurrentJob == &mJobMap[jobKey]) {
+ mCurrentJob = nullptr;
+ }
+
+ // Remove job from job map.
+ mJobMap.erase(jobKey);
+}
+
+bool TranscodingJobScheduler::submit(ClientIdType clientId, int32_t jobId, uid_t uid,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& callback) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ ALOGV("%s: job %s, uid %d, prioirty %d", __FUNCTION__, jobToString(jobKey).c_str(), uid,
+ (int32_t)request.priority);
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) > 0) {
+ ALOGE("job %s already exists", jobToString(jobKey).c_str());
+ return false;
+ }
+
+ // TODO(chz): only support offline vs real-time for now. All kUnspecified jobs
+ // go to offline queue.
+ if (request.priority == TranscodingJobPriority::kUnspecified) {
+ uid = OFFLINE_UID;
+ }
+
+ // Add job to job map.
+ mJobMap[jobKey].key = jobKey;
+ mJobMap[jobKey].uid = uid;
+ mJobMap[jobKey].state = Job::NOT_STARTED;
+ mJobMap[jobKey].request = request;
+ mJobMap[jobKey].callback = callback;
+
+ // If it's an offline job, the queue was already added in constructor.
+ // If it's a real-time jobs, check if a queue is already present for the uid,
+ // and add a new queue if needed.
+ if (uid != OFFLINE_UID) {
+ if (mJobQueues.count(uid) == 0) {
+ if (mUidPolicy->isUidOnTop(uid)) {
+ mUidSortedList.push_front(uid);
+ } else {
+ // Shouldn't be submitting real-time requests from non-top app,
+ // put it in front of the offline queue.
+ mUidSortedList.insert(mOfflineUidIterator, uid);
+ }
+ } else if (uid != *mUidSortedList.begin()) {
+ if (mUidPolicy->isUidOnTop(uid)) {
+ mUidSortedList.remove(uid);
+ mUidSortedList.push_front(uid);
+ }
+ }
+ }
+ // Append this job to the uid's queue.
+ mJobQueues[uid].push_back(jobKey);
+
+ updateCurrentJob_l();
+
+ validateState_l();
+ return true;
+}
+
+bool TranscodingJobScheduler::cancel(ClientIdType clientId, int32_t jobId) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+ return false;
+ }
+ // If the job is running, pause it first.
+ if (mJobMap[jobKey].state == Job::RUNNING) {
+ mTranscoder->pause(clientId, jobId);
+ }
+
+ // Remove the job.
+ removeJob_l(jobKey);
+
+ // Start next job.
+ updateCurrentJob_l();
+
+ validateState_l();
+ return true;
+}
+
+bool TranscodingJobScheduler::getJob(ClientIdType clientId, int32_t jobId,
+ TranscodingRequestParcel* request) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+ return false;
+ }
+
+ *(TranscodingRequest*)request = mJobMap[jobKey].request;
+ return true;
+}
+
+void TranscodingJobScheduler::onFinish(ClientIdType clientId, int32_t jobId) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGW("ignoring abort for non-existent job");
+ return;
+ }
+
+ // Only ignore if job was never started. In particular, propagate the status
+ // to client if the job is paused. Transcoder could have posted finish when
+ // we're pausing it, and the finish arrived after we changed current job.
+ if (mJobMap[jobKey].state == Job::NOT_STARTED) {
+ ALOGW("ignoring abort for job that was never started");
+ return;
+ }
+
+ {
+ auto clientCallback = mJobMap[jobKey].callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFinished(jobId, TranscodingResultParcel({jobId, 0}));
+ }
+ }
+
+ // Remove the job.
+ removeJob_l(jobKey);
+
+ // Start next job.
+ updateCurrentJob_l();
+
+ validateState_l();
+}
+
+void TranscodingJobScheduler::onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ ALOGV("%s: job %s, err %d", __FUNCTION__, jobToString(jobKey).c_str(), (int32_t)err);
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGW("ignoring abort for non-existent job");
+ return;
+ }
+
+ // Only ignore if job was never started. In particular, propagate the status
+ // to client if the job is paused. Transcoder could have posted finish when
+ // we're pausing it, and the finish arrived after we changed current job.
+ if (mJobMap[jobKey].state == Job::NOT_STARTED) {
+ ALOGW("ignoring abort for job that was never started");
+ return;
+ }
+
+ {
+ auto clientCallback = mJobMap[jobKey].callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFailed(jobId, err);
+ }
+ }
+
+ // Remove the job.
+ removeJob_l(jobKey);
+
+ // Start next job.
+ updateCurrentJob_l();
+
+ validateState_l();
+}
+
+void TranscodingJobScheduler::onResourceLost() {
+ ALOGV("%s", __FUNCTION__);
+
+ std::scoped_lock lock{mLock};
+
+ // If we receive a resource loss event, the TranscoderLibrary already paused
+ // the transcoding, so we don't need to call onPaused to notify it to pause.
+ // Only need to update the job state here.
+ if (mCurrentJob != nullptr && mCurrentJob->state == Job::RUNNING) {
+ mCurrentJob->state = Job::PAUSED;
+ }
+ mResourceLost = true;
+
+ validateState_l();
+}
+
+void TranscodingJobScheduler::onTopUidChanged(uid_t uid) {
+ ALOGV("%s: uid %d", __FUNCTION__, uid);
+
+ std::scoped_lock lock{mLock};
+
+ if (uid == OFFLINE_UID) {
+ ALOGW("%s: ignoring invalid uid %d", __FUNCTION__, uid);
+ return;
+ }
+ // If this uid doesn't have any jobs, we don't care about it.
+ if (mJobQueues.count(uid) == 0) {
+ ALOGW("%s: ignoring uid %d without any jobs", __FUNCTION__, uid);
+ return;
+ }
+ // If this uid is already top, don't do anything.
+ if (uid == *mUidSortedList.begin()) {
+ ALOGW("%s: uid %d is already top", __FUNCTION__, uid);
+ return;
+ }
+
+ mUidSortedList.remove(uid);
+ mUidSortedList.push_front(uid);
+
+ updateCurrentJob_l();
+
+ validateState_l();
+}
+
+void TranscodingJobScheduler::onResourceAvailable() {
+ ALOGV("%s", __FUNCTION__);
+
+ std::scoped_lock lock{mLock};
+
+ mResourceLost = false;
+ updateCurrentJob_l();
+
+ validateState_l();
+}
+
+void TranscodingJobScheduler::validateState_l() {
+#ifdef VALIDATE_STATE
+ LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_UID) != 1,
+ "mJobQueues offline queue number is not 1");
+ LOG_ALWAYS_FATAL_IF(*mOfflineUidIterator != OFFLINE_UID,
+ "mOfflineUidIterator not pointing to offline uid");
+ LOG_ALWAYS_FATAL_IF(mUidSortedList.size() != mJobQueues.size(),
+ "mUidList and mJobQueues size mismatch");
+
+ int32_t totalJobs = 0;
+ for (auto uidIt = mUidSortedList.begin(); uidIt != mUidSortedList.end(); uidIt++) {
+ LOG_ALWAYS_FATAL_IF(mJobQueues.count(*uidIt) != 1, "mJobQueues count for uid %d is not 1",
+ *uidIt);
+ for (auto jobIt = mJobQueues[*uidIt].begin(); jobIt != mJobQueues[*uidIt].end(); jobIt++) {
+ LOG_ALWAYS_FATAL_IF(mJobMap.count(*jobIt) != 1, "mJobs count for job %s is not 1",
+ jobToString(*jobIt).c_str());
+ }
+
+ totalJobs += mJobQueues[*uidIt].size();
+ }
+ LOG_ALWAYS_FATAL_IF(mJobMap.size() != totalJobs,
+ "mJobs size doesn't match total jobs counted from uid queues");
+#endif // VALIDATE_STATE
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl b/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
index 07b6c1a..40ca2c2 100644
--- a/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
+++ b/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
@@ -16,9 +16,10 @@
package android.media;
+import android.media.ITranscodingClient;
+import android.media.ITranscodingClientCallback;
import android.media.TranscodingJobParcel;
import android.media.TranscodingRequestParcel;
-import android.media.ITranscodingServiceClient;
/**
* Binder interface for MediaTranscodingService.
@@ -48,64 +49,29 @@
/**
* Register the client with the MediaTranscodingService.
*
- * Client must call this function to register itself with the service in order to perform
- * transcoding. This function will return a unique positive Id assigned by the service.
- * Client should save this Id and use it for all the transaction with the service.
+ * Client must call this function to register itself with the service in
+ * order to perform transcoding tasks. This function will return an
+ * ITranscodingClient interface object. The client should save and use it
+ * for all future transactions with the service.
*
- * @param client interface for the MediaTranscodingService to call the client.
+ * @param callback client interface for the MediaTranscodingService to call
+ * the client.
+ * @param clientName name of the client.
* @param opPackageName op package name of the client.
* @param clientUid user id of the client.
* @param clientPid process id of the client.
- * @return a unique positive Id assigned to the client by the service, -1 means failed to
- * register.
+ * @return an ITranscodingClient interface object, with nullptr indicating
+ * failure to register.
*/
- int registerClient(in ITranscodingServiceClient client,
- in String opPackageName,
- in int clientUid,
- in int clientPid);
-
- /**
- * Unregister the client with the MediaTranscodingService.
- *
- * Client will not be able to perform any more transcoding after unregister.
- *
- * @param clientId assigned Id of the client.
- * @return true if succeeds, false otherwise.
- */
- boolean unregisterClient(in int clientId);
+ ITranscodingClient registerClient(
+ in ITranscodingClientCallback callback,
+ in String clientName,
+ in String opPackageName,
+ in int clientUid,
+ in int clientPid);
/**
* Returns the number of clients. This is used for debugging.
*/
int getNumOfClients();
-
- /**
- * Submits a transcoding request to MediaTranscodingService.
- *
- * @param clientId assigned Id of the client.
- * @param request a TranscodingRequest contains transcoding configuration.
- * @param job(output variable) a TranscodingJob generated by the MediaTranscodingService.
- * @return a unique positive jobId generated by the MediaTranscodingService, -1 means failure.
- */
- int submitRequest(in int clientId,
- in TranscodingRequestParcel request,
- out TranscodingJobParcel job);
-
- /**
- * Cancels a transcoding job.
- *
- * @param clientId assigned id of the client.
- * @param jobId a TranscodingJob generated by the MediaTranscodingService.
- * @return true if succeeds, false otherwise.
- */
- boolean cancelJob(in int clientId, in int jobId);
-
- /**
- * Queries the job detail associated with a jobId.
- *
- * @param jobId a TranscodingJob generated by the MediaTranscodingService.
- * @param job(output variable) the TranscodingJob associated with the jobId.
- * @return true if succeeds, false otherwise.
- */
- boolean getJobWithId(in int jobId, out TranscodingJobParcel job);
}
diff --git a/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl b/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl
new file mode 100644
index 0000000..37b5147
--- /dev/null
+++ b/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.TranscodingJobParcel;
+import android.media.TranscodingRequestParcel;
+
+/**
+ * ITranscodingClient
+ *
+ * Interface for a client to communicate with MediaTranscodingService.
+ *
+ * {@hide}
+ */
+interface ITranscodingClient {
+ /**
+ * Submits a transcoding request to MediaTranscodingService.
+ *
+ * @param request a TranscodingRequest contains transcoding configuration.
+ * @param job(output variable) a TranscodingJob generated by the MediaTranscodingService.
+ * @return true if success, false otherwise.
+ */
+ boolean submitRequest(in TranscodingRequestParcel request,
+ out TranscodingJobParcel job);
+
+ /**
+ * Cancels a transcoding job.
+ *
+ * @param jobId a TranscodingJob generated by the MediaTranscodingService.
+ * @return true if succeeds, false otherwise.
+ */
+ boolean cancelJob(in int jobId);
+
+ /**
+ * Queries the job detail associated with a jobId.
+ *
+ * @param jobId a TranscodingJob generated by the MediaTranscodingService.
+ * @param job(output variable) the TranscodingJob associated with the jobId.
+ * @return true if succeeds, false otherwise.
+ */
+ boolean getJobWithId(in int jobId, out TranscodingJobParcel job);
+
+ /**
+ * Unregister the client with the MediaTranscodingService.
+ *
+ * Client will not be able to perform any more transcoding after unregister.
+ */
+ void unregister();
+}
diff --git a/media/libmediatranscoding/aidl/android/media/ITranscodingServiceClient.aidl b/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
similarity index 90%
rename from media/libmediatranscoding/aidl/android/media/ITranscodingServiceClient.aidl
rename to media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
index e23c833..e810f1e 100644
--- a/media/libmediatranscoding/aidl/android/media/ITranscodingServiceClient.aidl
+++ b/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
@@ -21,17 +21,13 @@
import android.media.TranscodingResultParcel;
/**
- * ITranscodingServiceClient interface for the MediaTranscodingervice to communicate with the
- * client.
+ * ITranscodingClientCallback
+ *
+ * Interface for the MediaTranscodingService to communicate with the client.
*
* {@hide}
*/
-//TODO(hkuang): Implement the interface.
-interface ITranscodingServiceClient {
- /**
- * Retrieves the name of the client.
- */
- @utf8InCpp String getName();
+interface ITranscodingClientCallback {
/**
* Called when the transcoding associated with the jobId finished.
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
index 7b7986d..5857482 100644
--- a/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
@@ -39,12 +39,12 @@
/**
* Input source file descriptor.
*/
- ParcelFileDescriptor inFd;
+ @nullable ParcelFileDescriptor inFd;
/**
* Output transcoded file descriptor.
*/
- ParcelFileDescriptor outFd;
+ @nullable ParcelFileDescriptor outFd;
/**
* Priority of this transcoding. Service will schedule the transcoding based on the priority.
diff --git a/media/libmediatranscoding/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/build_and_run_all_unit_tests.sh
new file mode 100755
index 0000000..388e2ea
--- /dev/null
+++ b/media/libmediatranscoding/build_and_run_all_unit_tests.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# Script to run all transcoding related tests from subfolders.
+# Run script from this folder.
+#
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+fi
+
+# ensure we have mm
+. $ANDROID_BUILD_TOP/build/envsetup.sh
+
+mm
+
+echo "waiting for device"
+
+adb root && adb wait-for-device remount && adb sync
+SYNC_FINISHED=true
+
+# Run the transcoding service tests.
+pushd tests
+. build_and_run_all_unit_tests.sh
+popd
+
+# Run the transcoder tests.
+pushd transcoder/tests/
+. build_and_run_all_unit_tests.sh
+popd
+
diff --git a/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h b/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
index 0e8dcfd..9ca2ee9 100644
--- a/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
+++ b/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
@@ -38,7 +38,7 @@
*/
template <class T, class Comparator = std::less<T>>
class AdjustableMaxPriorityQueue {
- public:
+public:
typedef typename std::vector<T>::iterator iterator;
typedef typename std::vector<T>::const_iterator const_iterator;
@@ -104,7 +104,7 @@
/* Return the backbone storage of this PriorityQueue. Mainly used for debugging. */
const std::vector<T>& getStorage() const { return mHeap; };
- private:
+private:
std::vector<T> mHeap;
/* Implementation shared by both public push() methods. */
diff --git a/media/libmediatranscoding/include/media/SchedulerClientInterface.h b/media/libmediatranscoding/include/media/SchedulerClientInterface.h
new file mode 100644
index 0000000..6ccf117
--- /dev/null
+++ b/media/libmediatranscoding/include/media/SchedulerClientInterface.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_SCHEDULER_CLIENT_INTERFACE_H
+#define ANDROID_MEDIA_SCHEDULER_CLIENT_INTERFACE_H
+
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
+
+namespace android {
+
+using ::aidl::android::media::ITranscodingClientCallback;
+using ::aidl::android::media::TranscodingRequestParcel;
+
+using ClientIdType = int64_t;
+
+// Interface for a client to call the scheduler to schedule or retrieve
+// the status of a job.
+class SchedulerClientInterface {
+public:
+ virtual bool submit(ClientIdType clientId, int32_t jobId, uid_t uid,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& clientCallback) = 0;
+
+ virtual bool cancel(ClientIdType clientId, int32_t jobId) = 0;
+
+ virtual bool getJob(ClientIdType clientId, int32_t jobId,
+ TranscodingRequestParcel* request) = 0;
+
+protected:
+ virtual ~SchedulerClientInterface() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SCHEDULER_CLIENT_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscoderInterface.h b/media/libmediatranscoding/include/media/TranscoderInterface.h
new file mode 100644
index 0000000..d74135a
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscoderInterface.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODER_INTERFACE_H
+#define ANDROID_MEDIA_TRANSCODER_INTERFACE_H
+
+#include <aidl/android/media/TranscodingErrorCode.h>
+
+namespace android {
+
+using ::aidl::android::media::TranscodingErrorCode;
+
+// Interface for the scheduler to call the transcoder to take actions.
+class TranscoderInterface {
+public:
+ // TODO(chz): determine what parameters are needed here.
+ // For now, always pass in clientId&jobId.
+ virtual void start(int64_t clientId, int32_t jobId) = 0;
+ virtual void pause(int64_t clientId, int32_t jobId) = 0;
+ virtual void resume(int64_t clientId, int32_t jobId) = 0;
+
+protected:
+ virtual ~TranscoderInterface() = default;
+};
+
+// Interface for the transcoder to notify the scheduler of the status of
+// the currently running job, or temporary loss of transcoding resources.
+class TranscoderCallbackInterface {
+public:
+ // TODO(chz): determine what parameters are needed here.
+ virtual void onFinish(int64_t clientId, int32_t jobId) = 0;
+ virtual void onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) = 0;
+
+ // Called when transcoding becomes temporarily inaccessible due to loss of resource.
+ // If there is any job currently running, it will be paused. When resource contention
+ // is solved, the scheduler should call TranscoderInterface's to either start a new job,
+ // or resume a paused job.
+ virtual void onResourceLost() = 0;
+
+protected:
+ virtual ~TranscoderCallbackInterface() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODER_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscodingClientManager.h b/media/libmediatranscoding/include/media/TranscodingClientManager.h
index eec120a..dba669a 100644
--- a/media/libmediatranscoding/include/media/TranscodingClientManager.h
+++ b/media/libmediatranscoding/include/media/TranscodingClientManager.h
@@ -17,22 +17,22 @@
#ifndef ANDROID_MEDIA_TRANSCODING_CLIENT_MANAGER_H
#define ANDROID_MEDIA_TRANSCODING_CLIENT_MANAGER_H
-#include <aidl/android/media/BnTranscodingServiceClient.h>
-#include <android/binder_ibinder.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
#include <sys/types.h>
#include <utils/Condition.h>
-#include <utils/RefBase.h>
#include <utils/String8.h>
#include <utils/Vector.h>
#include <mutex>
#include <unordered_map>
+#include "SchedulerClientInterface.h"
+
namespace android {
-using ::aidl::android::media::ITranscodingServiceClient;
-
-class MediaTranscodingService;
+using ::aidl::android::media::ITranscodingClient;
+using ::aidl::android::media::ITranscodingClientCallback;
/*
* TranscodingClientManager manages all the transcoding clients across different processes.
@@ -46,44 +46,46 @@
* TODO(hkuang): Hook up with MediaMetrics to log all the transactions.
*/
class TranscodingClientManager {
- public:
+public:
virtual ~TranscodingClientManager();
/**
- * ClientInfo contains a single client's information.
- */
- struct ClientInfo {
- /* The remote client that this ClientInfo is associated with. */
- std::shared_ptr<ITranscodingServiceClient> mClient;
- /* A unique positive Id assigned to the client by the service. */
- int32_t mClientId;
- /* Process id of the client */
- int32_t mClientPid;
- /* User id of the client. */
- int32_t mClientUid;
- /* Package name of the client. */
- std::string mClientOpPackageName;
-
- ClientInfo(const std::shared_ptr<ITranscodingServiceClient>& client, int64_t clientId,
- int32_t pid, int32_t uid, const std::string& opPackageName)
- : mClient(client),
- mClientId(clientId),
- mClientPid(pid),
- mClientUid(uid),
- mClientOpPackageName(opPackageName) {}
- };
-
- /**
* Adds a new client to the manager.
*
- * The client must have valid clientId, pid, uid and opPackageName, otherwise, this will return
- * a non-zero errorcode. If the client has already been added, it will also return non-zero
- * errorcode.
+ * The client must have valid callback, pid, uid, clientName and opPackageName.
+ * Otherwise, this will return a non-zero errorcode. If the client callback has
+ * already been added, it will also return non-zero errorcode.
*
- * @param client to be added to the manager.
+ * @param callback client callback for the service to call this client.
+ * @param pid client's process id.
+ * @param uid client's user id.
+ * @param clientName client's name.
+ * @param opPackageName client's package name.
+ * @param client output holding the ITranscodingClient interface for the client
+ * to use for subsequent communications with the service.
* @return 0 if client is added successfully, non-zero errorcode otherwise.
*/
- status_t addClient(std::unique_ptr<ClientInfo> client);
+ status_t addClient(const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid,
+ uid_t uid, const std::string& clientName, const std::string& opPackageName,
+ std::shared_ptr<ITranscodingClient>* client);
+
+ /**
+ * Gets the number of clients.
+ */
+ size_t getNumOfClients() const;
+
+ /**
+ * Dump all the client information to the fd.
+ */
+ void dumpAllClients(int fd, const Vector<String16>& args);
+
+private:
+ friend class MediaTranscodingService;
+ friend class TranscodingClientManagerTest;
+ struct ClientImpl;
+
+ // Only allow MediaTranscodingService and unit tests to instantiate.
+ TranscodingClientManager(const std::shared_ptr<SchedulerClientInterface>& scheduler);
/**
* Removes an existing client from the manager.
@@ -93,39 +95,17 @@
* @param clientId id of the client to be removed..
* @return 0 if client is removed successfully, non-zero errorcode otherwise.
*/
- status_t removeClient(int32_t clientId);
-
- /**
- * Gets the number of clients.
- */
- size_t getNumOfClients() const;
-
- /**
- * Checks if a client with clientId is already registered.
- */
- bool isClientIdRegistered(int32_t clientId) const;
-
- /**
- * Dump all the client information to the fd.
- */
- void dumpAllClients(int fd, const Vector<String16>& args);
-
- private:
- friend class MediaTranscodingService;
- friend class TranscodingClientManagerTest;
-
- /** Get the singleton instance of the TranscodingClientManager. */
- static TranscodingClientManager& getInstance();
-
- TranscodingClientManager();
+ status_t removeClient(ClientIdType clientId);
static void BinderDiedCallback(void* cookie);
mutable std::mutex mLock;
- std::unordered_map<int32_t, std::unique_ptr<ClientInfo>> mClientIdToClientInfoMap
+ std::unordered_map<ClientIdType, std::shared_ptr<ClientImpl>> mClientIdToClientMap
GUARDED_BY(mLock);
::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+
+ std::shared_ptr<SchedulerClientInterface> mJobScheduler;
};
} // namespace android
diff --git a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
new file mode 100644
index 0000000..77f6404
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
+#define ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
+
+#include <aidl/android/media/TranscodingJobPriority.h>
+#include <media/SchedulerClientInterface.h>
+#include <media/TranscoderInterface.h>
+#include <media/TranscodingRequest.h>
+#include <media/UidPolicyInterface.h>
+#include <utils/String8.h>
+
+#include <list>
+#include <map>
+#include <mutex>
+
+namespace android {
+using ::aidl::android::media::TranscodingJobPriority;
+using ::aidl::android::media::TranscodingResultParcel;
+
+class TranscodingJobScheduler : public UidPolicyCallbackInterface,
+ public SchedulerClientInterface,
+ public TranscoderCallbackInterface {
+public:
+ virtual ~TranscodingJobScheduler();
+
+ // SchedulerClientInterface
+ bool submit(ClientIdType clientId, int32_t jobId, uid_t uid,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override;
+ bool cancel(ClientIdType clientId, int32_t jobId) override;
+ bool getJob(ClientIdType clientId, int32_t jobId, TranscodingRequestParcel* request) override;
+ // ~SchedulerClientInterface
+
+ // TranscoderCallbackInterface
+ void onFinish(ClientIdType clientId, int32_t jobId) override;
+ void onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) override;
+ void onResourceLost() override;
+ // ~TranscoderCallbackInterface
+
+ // UidPolicyCallbackInterface
+ void onTopUidChanged(uid_t uid) override;
+ void onResourceAvailable() override;
+ // ~UidPolicyCallbackInterface
+
+private:
+ friend class MediaTranscodingService;
+ friend class TranscodingJobSchedulerTest;
+
+ using JobKeyType = std::pair<ClientIdType, int32_t /*jobId*/>;
+ using JobQueueType = std::list<JobKeyType>;
+
+ struct Job {
+ JobKeyType key;
+ uid_t uid;
+ enum JobState {
+ NOT_STARTED,
+ RUNNING,
+ PAUSED,
+ } state;
+ TranscodingRequest request;
+ std::weak_ptr<ITranscodingClientCallback> callback;
+ };
+
+ // TODO(chz): call transcoder without global lock.
+ // Use mLock for all entrypoints for now.
+ mutable std::mutex mLock;
+
+ std::map<JobKeyType, Job> mJobMap;
+
+ // uid->JobQueue map (uid == -1: offline queue)
+ std::map<uid_t, JobQueueType> mJobQueues;
+
+ // uids, with the head being the most-recently-top app, 2nd item is the
+ // previous top app, etc.
+ std::list<uid_t> mUidSortedList;
+ std::list<uid_t>::iterator mOfflineUidIterator;
+
+ std::shared_ptr<TranscoderInterface> mTranscoder;
+ std::shared_ptr<UidPolicyInterface> mUidPolicy;
+
+ Job* mCurrentJob;
+ bool mResourceLost;
+
+ // Only allow MediaTranscodingService and unit tests to instantiate.
+ TranscodingJobScheduler(const std::shared_ptr<TranscoderInterface>& transcoder,
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy);
+
+ Job* getTopJob_l();
+ void updateCurrentJob_l();
+ void removeJob_l(const JobKeyType& jobKey);
+
+ // Internal state verifier (debug only)
+ void validateState_l();
+
+ static String8 jobToString(const JobKeyType& jobKey);
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
diff --git a/media/libmediatranscoding/include/media/TranscodingRequest.h b/media/libmediatranscoding/include/media/TranscodingRequest.h
new file mode 100644
index 0000000..1337af3
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingRequest.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODING_REQUEST_H
+#define ANDROID_MEDIA_TRANSCODING_REQUEST_H
+
+#include <aidl/android/media/TranscodingRequestParcel.h>
+
+namespace android {
+
+using ::aidl::android::media::TranscodingRequestParcel;
+
+// Helper class for duplicating a TranscodingRequestParcel
+class TranscodingRequest : public TranscodingRequestParcel {
+public:
+ TranscodingRequest() = default;
+ TranscodingRequest(const TranscodingRequestParcel& parcel) { setTo(parcel); }
+ TranscodingRequest& operator=(const TranscodingRequest& request) {
+ setTo(request);
+ return *this;
+ }
+
+private:
+ void setTo(const TranscodingRequestParcel& parcel) {
+ fileName = parcel.fileName;
+ transcodingType = parcel.transcodingType;
+ // TODO: determine if the fds need dup
+ inFd.set(dup(parcel.inFd.get()));
+ outFd.set(dup(parcel.outFd.get()));
+ priority = parcel.priority;
+ requestUpdate = parcel.requestUpdate;
+ }
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODING_REQUEST_H
diff --git a/media/libmediatranscoding/include/media/UidPolicyInterface.h b/media/libmediatranscoding/include/media/UidPolicyInterface.h
new file mode 100644
index 0000000..e9a9da4
--- /dev/null
+++ b/media/libmediatranscoding/include/media/UidPolicyInterface.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_UID_POLICY_INTERFACE_H
+#define ANDROID_MEDIA_UID_POLICY_INTERFACE_H
+
+namespace android {
+
+// Interface for the scheduler to query a uid's info.
+class UidPolicyInterface {
+public:
+ // Determines if a uid is currently running as top.
+ // TODO(chz): this should probably be replaced by a query that determines
+ // which uid has the highest priority among a given set of uids.
+ virtual bool isUidOnTop(uid_t uid) = 0;
+
+protected:
+ virtual ~UidPolicyInterface() = default;
+};
+
+// Interface for notifying the scheduler of a change in a uid's state or
+// transcoding resource availability.
+class UidPolicyCallbackInterface {
+public:
+ // Called when a uid is brought to top.
+ // TODO(chz): this should probably be replace by a callback when the uid
+ // that was previously identified being the highest priority as in
+ // UidPolicyInterface::isUidOnTop() has changed in priority.
+ virtual void onTopUidChanged(uid_t uid) = 0;
+
+ // Called when resources become available for transcoding use. The scheduler
+ // may use this as a signal to attempt restart transcoding activity that
+ // were previously paused due to temporary resource loss.
+ virtual void onResourceAvailable() = 0;
+
+protected:
+ virtual ~UidPolicyCallbackInterface() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_UID_POLICY_INTERFACE_H
diff --git a/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp b/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
index d58af4e..2e49f32 100644
--- a/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
+++ b/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
@@ -36,7 +36,7 @@
namespace android {
class IntUniquePtrComp {
- public:
+public:
bool operator()(const std::unique_ptr<int>& lhs, const std::unique_ptr<int>& rhs) const {
return *lhs < *rhs;
}
@@ -233,7 +233,7 @@
// The job is arranging according to priority with highest priority comes first.
// For the job with the same priority, the job with early createTime will come first.
class TranscodingJobComp {
- public:
+ public:
bool operator()(const std::unique_ptr<TranscodingJob>& lhs,
const std::unique_ptr<TranscodingJob>& rhs) const {
if (lhs->priority != rhs->priority) {
diff --git a/media/libmediatranscoding/tests/Android.bp b/media/libmediatranscoding/tests/Android.bp
index 8191b00..904cf9b 100644
--- a/media/libmediatranscoding/tests/Android.bp
+++ b/media/libmediatranscoding/tests/Android.bp
@@ -37,6 +37,13 @@
srcs: ["TranscodingClientManager_tests.cpp"],
}
+cc_test {
+ name: "TranscodingJobScheduler_tests",
+ defaults: ["libmediatranscoding_test_defaults"],
+
+ srcs: ["TranscodingJobScheduler_tests.cpp"],
+}
+
//
// AdjustableMaxPriorityQueue unit test
//
diff --git a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
index 5d2419d..7e5ae61 100644
--- a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
@@ -19,52 +19,52 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "TranscodingClientManagerTest"
-#include <aidl/android/media/BnTranscodingServiceClient.h>
+#include <aidl/android/media/BnTranscodingClientCallback.h>
#include <aidl/android/media/IMediaTranscodingService.h>
-#include <aidl/android/media/ITranscodingServiceClient.h>
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <gtest/gtest.h>
+#include <media/SchedulerClientInterface.h>
#include <media/TranscodingClientManager.h>
+#include <media/TranscodingRequest.h>
#include <utils/Log.h>
+#include <list>
+
namespace android {
using Status = ::ndk::ScopedAStatus;
-using aidl::android::media::BnTranscodingServiceClient;
-using aidl::android::media::IMediaTranscodingService;
-using aidl::android::media::ITranscodingServiceClient;
+using ::aidl::android::media::BnTranscodingClientCallback;
+using ::aidl::android::media::IMediaTranscodingService;
+using ::aidl::android::media::TranscodingErrorCode;
+using ::aidl::android::media::TranscodingJobParcel;
+using ::aidl::android::media::TranscodingRequestParcel;
+using ::aidl::android::media::TranscodingResultParcel;
-constexpr int32_t kInvalidClientId = -1;
-constexpr int32_t kInvalidClientPid = -1;
-constexpr int32_t kInvalidClientUid = -1;
-constexpr const char* kInvalidClientOpPackageName = "";
+constexpr pid_t kInvalidClientPid = -1;
+constexpr const char* kInvalidClientName = "";
+constexpr const char* kInvalidClientPackage = "";
-constexpr int32_t kClientId = 1;
-constexpr int32_t kClientPid = 2;
-constexpr int32_t kClientUid = 3;
-constexpr const char* kClientOpPackageName = "TestClient";
+constexpr pid_t kClientPid = 2;
+constexpr uid_t kClientUid = 3;
+constexpr const char* kClientName = "TestClientName";
+constexpr const char* kClientPackage = "TestClientPackage";
-struct TestClient : public BnTranscodingServiceClient {
- TestClient(const std::shared_ptr<IMediaTranscodingService>& service) : mService(service) {
- ALOGD("TestClient Created");
- }
+struct TestClientCallback : public BnTranscodingClientCallback {
+ TestClientCallback() { ALOGI("TestClientCallback Created"); }
- Status getName(std::string* _aidl_return) override {
- *_aidl_return = "test_client";
+ virtual ~TestClientCallback() { ALOGI("TestClientCallback destroyed"); };
+
+ Status onTranscodingFinished(int32_t in_jobId,
+ const TranscodingResultParcel& in_result) override {
+ EXPECT_EQ(in_jobId, in_result.jobId);
+ mEventQueue.push_back(Finished(in_jobId));
return Status::ok();
}
- Status onTranscodingFinished(
- int32_t /* in_jobId */,
- const ::aidl::android::media::TranscodingResultParcel& /* in_result */) override {
- return Status::ok();
- }
-
- Status onTranscodingFailed(
- int32_t /* in_jobId */,
- ::aidl::android::media::TranscodingErrorCode /*in_errorCode */) override {
+ Status onTranscodingFailed(int32_t in_jobId, TranscodingErrorCode /*in_errorCode */) override {
+ mEventQueue.push_back(Failed(in_jobId));
return Status::ok();
}
@@ -77,204 +77,371 @@
return Status::ok();
}
- virtual ~TestClient() { ALOGI("TestClient destroyed"); };
+ struct Event {
+ enum {
+ NoEvent,
+ Finished,
+ Failed,
+ } type;
+ int32_t jobId;
+ };
- private:
- std::shared_ptr<IMediaTranscodingService> mService;
- TestClient(const TestClient&) = delete;
- TestClient& operator=(const TestClient&) = delete;
+ static constexpr Event NoEvent = {Event::NoEvent, 0};
+#define DECLARE_EVENT(action) \
+ static Event action(int32_t jobId) { return {Event::action, jobId}; }
+
+ DECLARE_EVENT(Finished);
+ DECLARE_EVENT(Failed);
+
+ const Event& popEvent() {
+ if (mEventQueue.empty()) {
+ mPoppedEvent = NoEvent;
+ } else {
+ mPoppedEvent = *mEventQueue.begin();
+ mEventQueue.pop_front();
+ }
+ return mPoppedEvent;
+ }
+
+private:
+ Event mPoppedEvent;
+ std::list<Event> mEventQueue;
+
+ TestClientCallback(const TestClientCallback&) = delete;
+ TestClientCallback& operator=(const TestClientCallback&) = delete;
+};
+
+bool operator==(const TestClientCallback::Event& lhs, const TestClientCallback::Event& rhs) {
+ return lhs.type == rhs.type && lhs.jobId == rhs.jobId;
+}
+
+struct TestScheduler : public SchedulerClientInterface {
+ TestScheduler() { ALOGI("TestScheduler Created"); }
+
+ virtual ~TestScheduler() { ALOGI("TestScheduler Destroyed"); }
+
+ bool submit(int64_t clientId, int32_t jobId, uid_t /*uid*/,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+ if (mJobs.count(jobKey) > 0) {
+ return false;
+ }
+
+ // This is the secret name we'll check, to test error propagation from
+ // the scheduler back to client.
+ if (request.fileName == "bad_file") {
+ return false;
+ }
+
+ mJobs[jobKey].request = request;
+ mJobs[jobKey].callback = clientCallback;
+
+ mLastJob = jobKey;
+ return true;
+ }
+
+ bool cancel(int64_t clientId, int32_t jobId) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ if (mJobs.count(jobKey) == 0) {
+ return false;
+ }
+ mJobs.erase(jobKey);
+ return true;
+ }
+
+ bool getJob(int64_t clientId, int32_t jobId, TranscodingRequestParcel* request) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+ if (mJobs.count(jobKey) == 0) {
+ return false;
+ }
+
+ *(TranscodingRequest*)request = mJobs[jobKey].request;
+ return true;
+ }
+
+ void finishLastJob() {
+ auto it = mJobs.find(mLastJob);
+ if (it == mJobs.end()) {
+ return;
+ }
+ {
+ auto clientCallback = it->second.callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFinished(
+ mLastJob.second, TranscodingResultParcel({mLastJob.second, 0}));
+ }
+ }
+ mJobs.erase(it);
+ }
+
+ void abortLastJob() {
+ auto it = mJobs.find(mLastJob);
+ if (it == mJobs.end()) {
+ return;
+ }
+ {
+ auto clientCallback = it->second.callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFailed(mLastJob.second,
+ TranscodingErrorCode::kUnknown);
+ }
+ }
+ mJobs.erase(it);
+ }
+
+ struct Job {
+ TranscodingRequest request;
+ std::weak_ptr<ITranscodingClientCallback> callback;
+ };
+
+ typedef std::pair<int64_t, int32_t> JobKeyType;
+ std::map<JobKeyType, Job> mJobs;
+ JobKeyType mLastJob;
};
class TranscodingClientManagerTest : public ::testing::Test {
- public:
- TranscodingClientManagerTest() : mClientManager(TranscodingClientManager::getInstance()) {
+public:
+ TranscodingClientManagerTest()
+ : mScheduler(new TestScheduler()),
+ mClientManager(new TranscodingClientManager(mScheduler)) {
ALOGD("TranscodingClientManagerTest created");
}
void SetUp() override {
- ::ndk::SpAIBinder binder(AServiceManager_getService("media.transcoding"));
- mService = IMediaTranscodingService::fromBinder(binder);
- if (mService == nullptr) {
- ALOGE("Failed to connect to the media.trascoding service.");
- return;
- }
-
- mTestClient = ::ndk::SharedRefBase::make<TestClient>(mService);
+ mClientCallback1 = ::ndk::SharedRefBase::make<TestClientCallback>();
+ mClientCallback2 = ::ndk::SharedRefBase::make<TestClientCallback>();
+ mClientCallback3 = ::ndk::SharedRefBase::make<TestClientCallback>();
}
- void TearDown() override {
- ALOGI("TranscodingClientManagerTest tear down");
- mService = nullptr;
- }
+ void TearDown() override { ALOGI("TranscodingClientManagerTest tear down"); }
~TranscodingClientManagerTest() { ALOGD("TranscodingClientManagerTest destroyed"); }
- TranscodingClientManager& mClientManager;
- std::shared_ptr<ITranscodingServiceClient> mTestClient = nullptr;
- std::shared_ptr<IMediaTranscodingService> mService = nullptr;
+ void addMultipleClients() {
+ EXPECT_EQ(mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient1),
+ OK);
+ EXPECT_NE(mClient1, nullptr);
+
+ EXPECT_EQ(mClientManager->addClient(mClientCallback2, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient2),
+ OK);
+ EXPECT_NE(mClient2, nullptr);
+
+ EXPECT_EQ(mClientManager->addClient(mClientCallback3, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient3),
+ OK);
+ EXPECT_NE(mClient3, nullptr);
+
+ EXPECT_EQ(mClientManager->getNumOfClients(), 3);
+ }
+
+ void unregisterMultipleClients() {
+ EXPECT_TRUE(mClient1->unregister().isOk());
+ EXPECT_TRUE(mClient2->unregister().isOk());
+ EXPECT_TRUE(mClient3->unregister().isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
+ }
+
+ std::shared_ptr<TestScheduler> mScheduler;
+ std::shared_ptr<TranscodingClientManager> mClientManager;
+ std::shared_ptr<ITranscodingClient> mClient1;
+ std::shared_ptr<ITranscodingClient> mClient2;
+ std::shared_ptr<ITranscodingClient> mClient3;
+ std::shared_ptr<TestClientCallback> mClientCallback1;
+ std::shared_ptr<TestClientCallback> mClientCallback2;
+ std::shared_ptr<TestClientCallback> mClientCallback3;
};
-TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientId) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid client id.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kInvalidClientId, kClientPid, kClientUid, kClientOpPackageName);
-
- // Add the client to the manager and expect failure.
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientCallback) {
+ // Add a client with null callback and expect failure.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(nullptr, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
+ EXPECT_EQ(err, BAD_VALUE);
}
TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientPid) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid Pid.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kClientId, kInvalidClientPid, kClientUid, kClientOpPackageName);
-
- // Add the client to the manager and expect failure.
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+ // Add a client with invalid Pid and expect failure.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kInvalidClientPid, kClientUid,
+ kClientName, kClientPackage, &client);
+ EXPECT_EQ(err, BAD_VALUE);
}
-TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientUid) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid Uid.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kClientId, kClientPid, kInvalidClientUid, kClientOpPackageName);
-
- // Add the client to the manager and expect failure.
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientName) {
+ // Add a client with invalid name and expect failure.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid,
+ kInvalidClientName, kClientPackage, &client);
+ EXPECT_EQ(err, BAD_VALUE);
}
TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientPackageName) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid packagename.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kClientId, kClientPid, kClientUid, kInvalidClientOpPackageName);
-
- // Add the client to the manager and expect failure.
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+ // Add a client with invalid packagename and expect failure.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kInvalidClientPackage, &client);
+ EXPECT_EQ(err, BAD_VALUE);
}
TEST_F(TranscodingClientManagerTest, TestAddingValidClient) {
- std::shared_ptr<ITranscodingServiceClient> client1 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
+ // Add a valid client, should succeed.
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
+ EXPECT_EQ(err, OK);
+ EXPECT_NE(client.get(), nullptr);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client1, kClientId, kClientPid, kClientUid, kClientOpPackageName);
-
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err == OK);
-
- size_t numOfClients = mClientManager.getNumOfClients();
- EXPECT_EQ(numOfClients, 1);
-
- err = mClientManager.removeClient(kClientId);
- EXPECT_TRUE(err == OK);
+ // Unregister client, should succeed.
+ Status status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
}
TEST_F(TranscodingClientManagerTest, TestAddingDupliacteClient) {
- std::shared_ptr<ITranscodingServiceClient> client1 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
+ std::shared_ptr<ITranscodingClient> client;
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
+ EXPECT_EQ(err, OK);
+ EXPECT_NE(client.get(), nullptr);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client1, kClientId, kClientPid, kClientUid, kClientOpPackageName);
+ std::shared_ptr<ITranscodingClient> dupClient;
+ err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, "dupClient",
+ "dupPackage", &dupClient);
+ EXPECT_EQ(err, ALREADY_EXISTS);
+ EXPECT_EQ(dupClient.get(), nullptr);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err == OK);
+ Status status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
- err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err != OK);
+ err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, "dupClient",
+ "dupPackage", &dupClient);
+ EXPECT_EQ(err, OK);
+ EXPECT_NE(dupClient.get(), nullptr);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- err = mClientManager.removeClient(kClientId);
- EXPECT_TRUE(err == OK);
+ status = dupClient->unregister();
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
}
TEST_F(TranscodingClientManagerTest, TestAddingMultipleClient) {
- std::shared_ptr<ITranscodingServiceClient> client1 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo1 =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client1, kClientId, kClientPid, kClientUid, kClientOpPackageName);
-
- status_t err = mClientManager.addClient(std::move(clientInfo1));
- EXPECT_TRUE(err == OK);
-
- std::shared_ptr<ITranscodingServiceClient> client2 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo2 =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client2, kClientId + 1, kClientPid, kClientUid, kClientOpPackageName);
-
- err = mClientManager.addClient(std::move(clientInfo2));
- EXPECT_TRUE(err == OK);
-
- std::shared_ptr<ITranscodingServiceClient> client3 =
- ::ndk::SharedRefBase::make<TestClient>(mService);
-
- // Create a client with invalid packagename.
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo3 =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client3, kClientId + 2, kClientPid, kClientUid, kClientOpPackageName);
-
- err = mClientManager.addClient(std::move(clientInfo3));
- EXPECT_TRUE(err == OK);
-
- size_t numOfClients = mClientManager.getNumOfClients();
- EXPECT_EQ(numOfClients, 3);
-
- err = mClientManager.removeClient(kClientId);
- EXPECT_TRUE(err == OK);
-
- err = mClientManager.removeClient(kClientId + 1);
- EXPECT_TRUE(err == OK);
-
- err = mClientManager.removeClient(kClientId + 2);
- EXPECT_TRUE(err == OK);
+ addMultipleClients();
+ unregisterMultipleClients();
}
-TEST_F(TranscodingClientManagerTest, TestRemovingNonExistClient) {
- status_t err = mClientManager.removeClient(kInvalidClientId);
- EXPECT_TRUE(err != OK);
+TEST_F(TranscodingClientManagerTest, TestSubmitCancelGetJobs) {
+ addMultipleClients();
- err = mClientManager.removeClient(1000 /* clientId */);
- EXPECT_TRUE(err != OK);
+ // Test jobId assignment.
+ TranscodingRequestParcel request;
+ request.fileName = "test_file_0";
+ TranscodingJobParcel job;
+ bool result;
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
+
+ request.fileName = "test_file_1";
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 1);
+
+ request.fileName = "test_file_2";
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 2);
+
+ // Test submit bad request (no valid fileName) fails.
+ TranscodingRequestParcel badRequest;
+ badRequest.fileName = "bad_file";
+ EXPECT_TRUE(mClient1->submitRequest(badRequest, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test get jobs by id.
+ EXPECT_TRUE(mClient1->getJobWithId(2, &job, &result).isOk());
+ EXPECT_EQ(job.jobId, 2);
+ EXPECT_EQ(job.request.fileName, "test_file_2");
+ EXPECT_TRUE(result);
+
+ // Test get jobs by invalid id fails.
+ EXPECT_TRUE(mClient1->getJobWithId(100, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test cancel non-existent job fail.
+ EXPECT_TRUE(mClient2->cancelJob(100, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test cancel valid jobId in arbitrary order.
+ EXPECT_TRUE(mClient1->cancelJob(2, &result).isOk());
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(mClient1->cancelJob(0, &result).isOk());
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(mClient1->cancelJob(1, &result).isOk());
+ EXPECT_TRUE(result);
+
+ // Test cancel job again fails.
+ EXPECT_TRUE(mClient1->cancelJob(1, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test get job after cancel fails.
+ EXPECT_TRUE(mClient1->getJobWithId(2, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test jobId independence for each client.
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
+
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 1);
+
+ unregisterMultipleClients();
}
-TEST_F(TranscodingClientManagerTest, TestCheckClientWithClientId) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
+TEST_F(TranscodingClientManagerTest, TestClientCallback) {
+ addMultipleClients();
- std::unique_ptr<TranscodingClientManager::ClientInfo> clientInfo =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- client, kClientId, kClientPid, kClientUid, kClientOpPackageName);
+ TranscodingRequestParcel request;
+ request.fileName = "test_file_name";
+ TranscodingJobParcel job;
+ bool result;
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
- status_t err = mClientManager.addClient(std::move(clientInfo));
- EXPECT_TRUE(err == OK);
+ mScheduler->finishLastJob();
+ EXPECT_EQ(mClientCallback1->popEvent(), TestClientCallback::Finished(job.jobId));
- bool res = mClientManager.isClientIdRegistered(kClientId);
- EXPECT_TRUE(res);
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 1);
- res = mClientManager.isClientIdRegistered(kInvalidClientId);
- EXPECT_FALSE(res);
+ mScheduler->abortLastJob();
+ EXPECT_EQ(mClientCallback1->popEvent(), TestClientCallback::Failed(job.jobId));
+
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 2);
+
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
+
+ mScheduler->finishLastJob();
+ EXPECT_EQ(mClientCallback2->popEvent(), TestClientCallback::Finished(job.jobId));
+
+ unregisterMultipleClients();
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
new file mode 100644
index 0000000..6bc9e20
--- /dev/null
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Unit Test for TranscodingJobScheduler
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "TranscodingJobSchedulerTest"
+
+#include <aidl/android/media/BnTranscodingClientCallback.h>
+#include <aidl/android/media/IMediaTranscodingService.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include <media/TranscodingClientManager.h>
+#include <media/TranscodingJobScheduler.h>
+#include <utils/Log.h>
+
+namespace android {
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::media::BnTranscodingClientCallback;
+using aidl::android::media::IMediaTranscodingService;
+using aidl::android::media::ITranscodingClient;
+
+constexpr int64_t kClientId = 1000;
+constexpr int32_t kClientJobId = 0;
+constexpr uid_t kClientUid = 5000;
+constexpr uid_t kInvalidUid = (uid_t)-1;
+
+#define CLIENT(n) (kClientId + (n))
+#define JOB(n) (kClientJobId + (n))
+#define UID(n) (kClientUid + (n))
+
+class TestCallback : public TranscoderInterface, public UidPolicyInterface {
+public:
+ TestCallback() : mTopUid(kInvalidUid), mLastError(TranscodingErrorCode::kUnknown) {}
+ virtual ~TestCallback() {}
+
+ // TranscoderInterface
+ void start(int64_t clientId, int32_t jobId) override {
+ mEventQueue.push_back(Start(clientId, jobId));
+ }
+ void pause(int64_t clientId, int32_t jobId) override {
+ mEventQueue.push_back(Pause(clientId, jobId));
+ }
+ void resume(int64_t clientId, int32_t jobId) override {
+ mEventQueue.push_back(Resume(clientId, jobId));
+ }
+
+ // UidPolicyInterface
+ bool isUidOnTop(uid_t uid) override { return uid == mTopUid; }
+
+ void onFinished(int64_t clientId, int32_t jobId) {
+ mEventQueue.push_back(Finished(clientId, jobId));
+ }
+
+ void onFailed(int64_t clientId, int32_t jobId, TranscodingErrorCode err) {
+ mLastError = err;
+ mEventQueue.push_back(Failed(clientId, jobId));
+ }
+
+ void setTop(uid_t uid) { mTopUid = uid; }
+
+ TranscodingErrorCode getLastError() {
+ TranscodingErrorCode result = mLastError;
+ mLastError = TranscodingErrorCode::kUnknown;
+ return result;
+ }
+
+ struct Event {
+ enum { NoEvent, Start, Pause, Resume, Finished, Failed } type;
+ int64_t clientId;
+ int32_t jobId;
+ };
+
+ static constexpr Event NoEvent = {Event::NoEvent, 0, 0};
+
+#define DECLARE_EVENT(action) \
+ static Event action(int64_t clientId, int32_t jobId) { \
+ return {Event::action, clientId, jobId}; \
+ }
+
+ DECLARE_EVENT(Start);
+ DECLARE_EVENT(Pause);
+ DECLARE_EVENT(Resume);
+ DECLARE_EVENT(Finished);
+ DECLARE_EVENT(Failed);
+
+ const Event& popEvent() {
+ if (mEventQueue.empty()) {
+ mPoppedEvent = NoEvent;
+ } else {
+ mPoppedEvent = *mEventQueue.begin();
+ mEventQueue.pop_front();
+ }
+ return mPoppedEvent;
+ }
+
+private:
+ Event mPoppedEvent;
+ std::list<Event> mEventQueue;
+ uid_t mTopUid;
+ TranscodingErrorCode mLastError;
+};
+
+bool operator==(const TestCallback::Event& lhs, const TestCallback::Event& rhs) {
+ return lhs.type == rhs.type && lhs.clientId == rhs.clientId && lhs.jobId == rhs.jobId;
+}
+
+struct TestClientCallback : public BnTranscodingClientCallback {
+ TestClientCallback(TestCallback* owner, int64_t clientId) : mOwner(owner), mClientId(clientId) {
+ ALOGD("TestClient Created");
+ }
+
+ Status onTranscodingFinished(int32_t in_jobId,
+ const TranscodingResultParcel& in_result) override {
+ EXPECT_EQ(in_jobId, in_result.jobId);
+ mOwner->onFinished(mClientId, in_jobId);
+ return Status::ok();
+ }
+
+ Status onTranscodingFailed(int32_t in_jobId, TranscodingErrorCode in_errorCode) override {
+ mOwner->onFailed(mClientId, in_jobId, in_errorCode);
+ return Status::ok();
+ }
+
+ Status onAwaitNumberOfJobsChanged(int32_t /* in_jobId */, int32_t /* in_oldAwaitNumber */,
+ int32_t /* in_newAwaitNumber */) override {
+ return Status::ok();
+ }
+
+ Status onProgressUpdate(int32_t /* in_jobId */, int32_t /* in_progress */) override {
+ return Status::ok();
+ }
+
+ virtual ~TestClientCallback() { ALOGI("TestClient destroyed"); };
+
+private:
+ TestCallback* mOwner;
+ int64_t mClientId;
+ TestClientCallback(const TestClientCallback&) = delete;
+ TestClientCallback& operator=(const TestClientCallback&) = delete;
+};
+
+class TranscodingJobSchedulerTest : public ::testing::Test {
+public:
+ TranscodingJobSchedulerTest() { ALOGI("TranscodingJobSchedulerTest created"); }
+
+ void SetUp() override {
+ ALOGI("TranscodingJobSchedulerTest set up");
+ mCallback.reset(new TestCallback());
+ mScheduler.reset(new TranscodingJobScheduler(mCallback, mCallback));
+
+ // Set priority only, ignore other fields for now.
+ mOfflineRequest.priority = TranscodingJobPriority::kUnspecified;
+ mRealtimeRequest.priority = TranscodingJobPriority::kHigh;
+ mClientCallback0 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(0));
+ mClientCallback1 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(1));
+ mClientCallback2 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(2));
+ mClientCallback3 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(3));
+ }
+
+ void TearDown() override { ALOGI("TranscodingJobSchedulerTest tear down"); }
+
+ ~TranscodingJobSchedulerTest() { ALOGD("TranscodingJobSchedulerTest destroyed"); }
+
+ std::shared_ptr<TestCallback> mCallback;
+ std::shared_ptr<TranscodingJobScheduler> mScheduler;
+ TranscodingRequestParcel mOfflineRequest;
+ TranscodingRequestParcel mRealtimeRequest;
+ std::shared_ptr<TestClientCallback> mClientCallback0;
+ std::shared_ptr<TestClientCallback> mClientCallback1;
+ std::shared_ptr<TestClientCallback> mClientCallback2;
+ std::shared_ptr<TestClientCallback> mClientCallback3;
+};
+
+TEST_F(TranscodingJobSchedulerTest, TestSubmitJob) {
+ ALOGD("TestSubmitJob");
+
+ // Start with UID(1) on top.
+ mCallback->setTop(UID(1));
+
+ // Submit offline job to CLIENT(0) in UID(0).
+ // Should start immediately (because this is the only job).
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), 0));
+
+ // Submit real-time job to CLIENT(0).
+ // Should pause offline job and start new job, even if UID(0) is not on top.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job to CLIENT(0), should be queued after the previous job.
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit real-time job to CLIENT(1) in same uid, should be queued after the previous job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit real-time job to CLIENT(2) in UID(1).
+ // Should pause previous job and start new job, because UID(1) is top.
+ mCallback->setTop(UID(1));
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+
+ // Submit offline job, shouldn't generate any event.
+ mScheduler->submit(CLIENT(2), JOB(1), UID(1), mOfflineRequest, mClientCallback2);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ mCallback->setTop(UID(0));
+ // Submit real-time job to CLIENT(1) in UID(0).
+ // Should pause current job, and resume last job in UID(0).
+ mScheduler->submit(CLIENT(1), JOB(1), UID(0), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(1)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestCancelJob) {
+ ALOGD("TestCancelJob");
+
+ // Submit real-time job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should not start.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit offline job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Cancel queued real-time job.
+ // Cancel real-time job JOB(1), should be cancelled.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(1)));
+
+ // Cancel queued offline job.
+ // Cancel offline job JOB(2), should be cancelled.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(2)));
+
+ // Submit offline job JOB(3), shouldn't cause any event.
+ mScheduler->submit(CLIENT(0), JOB(3), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Cancel running real-time job JOB(0).
+ // - Should be paused first then cancelled.
+ // - Should also start offline job JOB(2) because real-time queue is empty.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(3)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestFinishJob) {
+ ALOGD("TestFinishJob");
+
+ // Fail without any jobs submitted, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit offline job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should pause offline job and start immediately.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Fail when the job never started, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(2));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // UID(1) moves to top.
+ mCallback->setTop(UID(1));
+ // Submit real-time job to CLIENT(1) in UID(1), should pause previous job and start new job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(1), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+
+ // Simulate Fail that arrived late, after pause issued by scheduler.
+ // Should still be propagated to client, but shouldn't trigger any new start.
+ mScheduler->onFinish(CLIENT(0), JOB(1));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(1)));
+
+ // Fail running real-time job, should start next real-time job in queue.
+ mScheduler->onFinish(CLIENT(1), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(2)));
+
+ // Fail running real-time job, should resume next job (offline job) in queue.
+ mScheduler->onFinish(CLIENT(0), JOB(2));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+ // Fail running offline job.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
+
+ // Duplicate fail for last job, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestFailJob) {
+ ALOGD("TestFailJob");
+
+ // Fail without any jobs submitted, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit offline job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should pause offline job and start immediately.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Fail when the job never started, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // UID(1) moves to top.
+ mCallback->setTop(UID(1));
+ // Submit real-time job to CLIENT(1) in UID(1), should pause previous job and start new job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(1), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+
+ // Simulate Fail that arrived late, after pause issued by scheduler.
+ // Should still be propagated to client, but shouldn't trigger any new start.
+ mScheduler->onError(CLIENT(0), JOB(1), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(1)));
+
+ // Fail running real-time job, should start next real-time job in queue.
+ mScheduler->onError(CLIENT(1), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(2)));
+
+ // Fail running real-time job, should resume next job (offline job) in queue.
+ mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+ // Fail running offline job, and test error code propagation.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kInvalidBitstream);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->getLastError(), TranscodingErrorCode::kInvalidBitstream);
+
+ // Duplicate fail for last job, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestTopUidChanged) {
+ ALOGD("TestTopUidChanged");
+
+ // Submit real-time job to CLIENT(0), job should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit offline job to CLIENT(0), should not start.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Move UID(1) to top.
+ mCallback->setTop(UID(1));
+ // Submit real-time job to CLIENT(2) in different uid UID(1).
+ // Should pause previous job and start new job.
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+
+ // Bring UID(0) back to top.
+ mCallback->setTop(UID(0));
+ mScheduler->onTopUidChanged(UID(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+ // Bring invalid uid to top.
+ mScheduler->onTopUidChanged(kInvalidUid);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Finish job, next real-time job should resume.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(2), JOB(0)));
+
+ // Finish job, offline job should start.
+ mScheduler->onFinish(CLIENT(2), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestResourceLost) {
+ ALOGD("TestResourceLost");
+
+ // Submit real-time job to CLIENT(0), job should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit offline job to CLIENT(0), should not start.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Move UID(1) to top.
+ mCallback->setTop(UID(1));
+
+ // Submit real-time job to CLIENT(2) in different uid UID(1).
+ // Should pause previous job and start new job.
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+
+ // Test 1: No queue change during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Signal resource available, CLIENT(2) should resume.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(2), JOB(0)));
+
+ // Test 2: Change of queue order during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Move UID(0) back to top, should have no resume due to no resource.
+ mScheduler->onTopUidChanged(UID(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Signal resource available, CLIENT(0) should resume.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+ // Test 3: Adding new queue during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Move UID(2) to top.
+ mCallback->setTop(UID(2));
+
+ // Submit real-time job to CLIENT(3) in UID(2), job shouldn't start due to no resource.
+ mScheduler->submit(CLIENT(3), JOB(0), UID(2), mRealtimeRequest, mClientCallback3);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Signal resource available, CLIENT(3)'s job should start.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(3), JOB(0)));
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
index d8e4830..ff6df2c 100644
--- a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -3,24 +3,32 @@
# Run tests in this directory.
#
-if [ -z "$ANDROID_BUILD_TOP" ]; then
- echo "Android build environment not set"
- exit -1
+if [ "$SYNC_FINISHED" != true ]; then
+ if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+ fi
+
+ # ensure we have mm
+ . $ANDROID_BUILD_TOP/build/envsetup.sh
+
+ mm
+
+ echo "waiting for device"
+
+ adb root && adb wait-for-device remount && adb sync
fi
-# ensure we have mm
-. $ANDROID_BUILD_TOP/build/envsetup.sh
-
-mm
-
-echo "waiting for device"
-
-adb root && adb wait-for-device remount && adb sync
-
echo "========================================"
echo "testing TranscodingClientManager"
-adb shell /data/nativetest64/TranscodingClientManager_tests/TranscodingClientManager_tests
+#adb shell /data/nativetest64/TranscodingClientManager_tests/TranscodingClientManager_tests
+adb shell /data/nativetest/TranscodingClientManager_tests/TranscodingClientManager_tests
echo "testing AdjustableMaxPriorityQueue"
-adb shell /data/nativetest64/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
+#adb shell /data/nativetest64/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
+adb shell /data/nativetest/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
+
+echo "testing TranscodingJobScheduler"
+#adb shell /data/nativetest64/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
+adb shell /data/nativetest/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
new file mode 100644
index 0000000..e352245
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_library_shared {
+ name: "libmediatranscoder",
+
+ srcs: [
+ "MediaSampleQueue.cpp",
+ "MediaSampleReaderNDK.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "libmediandk",
+ "libutils",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wformat",
+ "-Wno-error=deprecated-declarations",
+ "-Wthread-safety",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ },
+}
diff --git a/media/libmediatranscoding/transcoder/MediaSampleQueue.cpp b/media/libmediatranscoding/transcoder/MediaSampleQueue.cpp
new file mode 100644
index 0000000..691ee1c
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaSampleQueue.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaSampleQueue"
+
+#include <android-base/logging.h>
+#include <media/MediaSampleQueue.h>
+
+namespace android {
+
+bool MediaSampleQueue::enqueue(const std::shared_ptr<MediaSample>& sample) {
+ std::scoped_lock<std::mutex> lock(mMutex);
+ if (!mAborted) {
+ mSampleQueue.push(sample);
+ mCondition.notify_one();
+ }
+ return mAborted;
+}
+
+// Unfortunately std::unique_lock is incompatible with -Wthread-safety
+bool MediaSampleQueue::dequeue(std::shared_ptr<MediaSample>* sample) NO_THREAD_SAFETY_ANALYSIS {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (mSampleQueue.empty() && !mAborted) {
+ mCondition.wait(lock);
+ }
+
+ if (!mAborted) {
+ if (sample != nullptr) {
+ *sample = mSampleQueue.front();
+ }
+ mSampleQueue.pop();
+ }
+ return mAborted;
+}
+
+void MediaSampleQueue::abort() {
+ std::scoped_lock<std::mutex> lock(mMutex);
+ // Clear the queue and notify consumers.
+ std::queue<std::shared_ptr<MediaSample>> empty = {};
+ std::swap(mSampleQueue, empty);
+ mAborted = true;
+ mCondition.notify_all();
+}
+} // namespace android
\ No newline at end of file
diff --git a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
new file mode 100644
index 0000000..d0f117d
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaSampleReader"
+
+#include <android-base/logging.h>
+#include <media/MediaSampleReaderNDK.h>
+
+#include <algorithm>
+#include <vector>
+
+namespace android {
+
+// static
+std::shared_ptr<MediaSampleReader> MediaSampleReaderNDK::createFromFd(int fd, size_t offset,
+ size_t size) {
+ AMediaExtractor* extractor = AMediaExtractor_new();
+ if (extractor == nullptr) {
+ LOG(ERROR) << "Unable to allocate AMediaExtractor";
+ return nullptr;
+ }
+
+ media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, offset, size);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "AMediaExtractor_setDataSourceFd returned error: " << status;
+ AMediaExtractor_delete(extractor);
+ return nullptr;
+ }
+
+ auto sampleReader = std::shared_ptr<MediaSampleReaderNDK>(new MediaSampleReaderNDK(extractor));
+ status = sampleReader->init();
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "MediaSampleReaderNDK::init returned error: " << status;
+ return nullptr;
+ }
+
+ return sampleReader;
+}
+
+MediaSampleReaderNDK::MediaSampleReaderNDK(AMediaExtractor* extractor)
+ : mExtractor(extractor), mTrackCount(AMediaExtractor_getTrackCount(mExtractor)) {
+ if (mTrackCount > 0) {
+ mTrackCursors.resize(mTrackCount);
+ mTrackCursors.resize(mTrackCount);
+ }
+}
+
+media_status_t MediaSampleReaderNDK::init() {
+ for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ media_status_t status = AMediaExtractor_selectTrack(mExtractor, trackIndex);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "AMediaExtractor_selectTrack returned error: " << status;
+ return status;
+ }
+ }
+
+ mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ if (mExtractorTrackIndex >= 0) {
+ mTrackCursors[mExtractorTrackIndex].current.set(mExtractorSampleIndex,
+ AMediaExtractor_getSampleTime(mExtractor));
+ } else if (mTrackCount > 0) {
+ // The extractor track index is only allowed to be invalid if there are no tracks.
+ LOG(ERROR) << "Track index " << mExtractorTrackIndex << " is invalid for track count "
+ << mTrackCount;
+ return AMEDIA_ERROR_MALFORMED;
+ }
+
+ return AMEDIA_OK;
+}
+
+MediaSampleReaderNDK::~MediaSampleReaderNDK() {
+ if (mExtractor != nullptr) {
+ AMediaExtractor_delete(mExtractor);
+ }
+}
+
+bool MediaSampleReaderNDK::advanceExtractor_l() {
+ // Reset the "next" sample time whenever the extractor advances past a sample that is current,
+ // to ensure that "next" is appropriately updated when the extractor advances over the next
+ // sample of that track.
+ if (mTrackCursors[mExtractorTrackIndex].current.isSet &&
+ mTrackCursors[mExtractorTrackIndex].current.index == mExtractorSampleIndex) {
+ mTrackCursors[mExtractorTrackIndex].next.reset();
+ }
+
+ if (!AMediaExtractor_advance(mExtractor)) {
+ return false;
+ }
+
+ mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ mExtractorSampleIndex++;
+
+ SampleCursor& cursor = mTrackCursors[mExtractorTrackIndex];
+ if (mExtractorSampleIndex > cursor.previous.index) {
+ if (!cursor.current.isSet) {
+ cursor.current.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
+ } else if (!cursor.next.isSet && mExtractorSampleIndex > cursor.current.index) {
+ cursor.next.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
+ }
+ }
+ return true;
+}
+
+media_status_t MediaSampleReaderNDK::seekExtractorBackwards_l(int64_t targetTimeUs,
+ int targetTrackIndex,
+ uint64_t targetSampleIndex) {
+ if (targetSampleIndex > mExtractorSampleIndex) {
+ LOG(ERROR) << "Error: Forward seek is not supported";
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ // AMediaExtractor supports reading negative timestamps but does not support seeking to them.
+ const int64_t seekToTimeUs = std::max(targetTimeUs, (int64_t)0);
+ media_status_t status =
+ AMediaExtractor_seekTo(mExtractor, seekToTimeUs, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to seek to " << seekToTimeUs << ", target " << targetTimeUs;
+ return status;
+ }
+ mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ int64_t sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
+
+ while (sampleTimeUs != targetTimeUs || mExtractorTrackIndex != targetTrackIndex) {
+ if (!AMediaExtractor_advance(mExtractor)) {
+ return AMEDIA_ERROR_END_OF_STREAM;
+ }
+ mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
+ }
+ mExtractorSampleIndex = targetSampleIndex;
+ return AMEDIA_OK;
+}
+
+void MediaSampleReaderNDK::advanceTrack(int trackIndex) {
+ std::scoped_lock lock(mExtractorMutex);
+
+ if (trackIndex < 0 || trackIndex >= mTrackCount) {
+ LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
+ return;
+ }
+
+ // Note: Positioning the extractor before advancing the track is needed for two reasons:
+ // 1. To enable multiple advances without explicitly letting the extractor catch up.
+ // 2. To prevent the extractor from being farther than "next".
+ (void)positionExtractorForTrack_l(trackIndex);
+
+ SampleCursor& cursor = mTrackCursors[trackIndex];
+ cursor.previous = cursor.current;
+ cursor.current = cursor.next;
+ cursor.next.reset();
+}
+
+media_status_t MediaSampleReaderNDK::positionExtractorForTrack_l(int trackIndex) {
+ media_status_t status = AMEDIA_OK;
+ const SampleCursor& cursor = mTrackCursors[trackIndex];
+
+ // Seek backwards if the extractor is ahead of the current time.
+ if (cursor.current.isSet && mExtractorSampleIndex > cursor.current.index) {
+ status = seekExtractorBackwards_l(cursor.current.timeStampUs, trackIndex,
+ cursor.current.index);
+ if (status != AMEDIA_OK) return status;
+ }
+
+ // Advance until extractor points to the current sample.
+ while (!(cursor.current.isSet && cursor.current.index == mExtractorSampleIndex)) {
+ if (!advanceExtractor_l()) {
+ return AMEDIA_ERROR_END_OF_STREAM;
+ }
+ }
+
+ return AMEDIA_OK;
+}
+
+media_status_t MediaSampleReaderNDK::getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) {
+ std::scoped_lock lock(mExtractorMutex);
+
+ if (trackIndex < 0 || trackIndex >= mTrackCount) {
+ LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ } else if (info == nullptr) {
+ LOG(ERROR) << "MediaSampleInfo pointer is NULL.";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ media_status_t status = positionExtractorForTrack_l(trackIndex);
+ if (status == AMEDIA_OK) {
+ info->presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
+ info->flags = AMediaExtractor_getSampleFlags(mExtractor);
+ info->size = AMediaExtractor_getSampleSize(mExtractor);
+ } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
+ info->presentationTimeUs = 0;
+ info->flags = SAMPLE_FLAG_END_OF_STREAM;
+ info->size = 0;
+ }
+
+ return status;
+}
+
+media_status_t MediaSampleReaderNDK::readSampleDataForTrack(int trackIndex, uint8_t* buffer,
+ size_t bufferSize) {
+ std::scoped_lock lock(mExtractorMutex);
+
+ if (trackIndex < 0 || trackIndex >= mTrackCount) {
+ LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ } else if (buffer == nullptr) {
+ LOG(ERROR) << "buffer pointer is NULL";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ media_status_t status = positionExtractorForTrack_l(trackIndex);
+ if (status != AMEDIA_OK) return status;
+
+ ssize_t sampleSize = AMediaExtractor_getSampleSize(mExtractor);
+ if (bufferSize < sampleSize) {
+ LOG(ERROR) << "Buffer is too small for sample, " << bufferSize << " vs " << sampleSize;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ ssize_t bytesRead = AMediaExtractor_readSampleData(mExtractor, buffer, bufferSize);
+ if (bytesRead < sampleSize) {
+ LOG(ERROR) << "Unable to read full sample, " << bytesRead << " vs " << sampleSize;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ return AMEDIA_OK;
+}
+
+AMediaFormat* MediaSampleReaderNDK::getFileFormat() {
+ return AMediaExtractor_getFileFormat(mExtractor);
+}
+
+size_t MediaSampleReaderNDK::getTrackCount() const {
+ return mTrackCount;
+}
+
+AMediaFormat* MediaSampleReaderNDK::getTrackFormat(int trackIndex) {
+ if (trackIndex < 0 || trackIndex >= mTrackCount) {
+ LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
+ return AMediaFormat_new();
+ }
+
+ return AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSample.h b/media/libmediatranscoding/transcoder/include/media/MediaSample.h
new file mode 100644
index 0000000..e206a3e
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSample.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_SAMPLE_H
+#define ANDROID_MEDIA_SAMPLE_H
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+
+namespace android {
+
+/**
+ * Media sample flags.
+ * These flags purposely match the media NDK's buffer and extractor flags with one exception. The
+ * NDK extractor's flag for encrypted samples (AMEDIAEXTRACTOR_SAMPLE_FLAG_ENCRYPTED) is equal to 2,
+ * i.e. the same as SAMPLE_FLAG_CODEC_CONFIG below and NDK's AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG.
+ * Sample producers based on the NDK's extractor is responsible for catching those values.
+ * Note that currently the media transcoder does not support encrypted samples.
+ */
+enum : uint32_t {
+ SAMPLE_FLAG_SYNC_SAMPLE = 1,
+ SAMPLE_FLAG_CODEC_CONFIG = 2,
+ SAMPLE_FLAG_END_OF_STREAM = 4,
+ SAMPLE_FLAG_PARTIAL_FRAME = 8,
+};
+
+// Check that the sample flags have the expected NDK meaning.
+namespace {
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaExtractor.h>
+
+static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
+ "Sample flag mismatch: SYNC_SAMPLE");
+static_assert(SAMPLE_FLAG_CODEC_CONFIG == AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG,
+ "Sample flag mismatch: CODEC_CONFIG");
+static_assert(SAMPLE_FLAG_END_OF_STREAM == AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM,
+ "Sample flag mismatch: END_OF_STREAM");
+static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
+ "Sample flag mismatch: PARTIAL_FRAME");
+} // anonymous namespace
+
+/**
+ * MediaSampleInfo is an object that carries information about a compressed media sample without
+ * holding any sample data.
+ */
+struct MediaSampleInfo {
+ /** The sample's presentation timestamp in microseconds. */
+ int64_t presentationTimeUs = 0;
+
+ /** The size of the compressed sample data in bytes. */
+ size_t size = 0;
+
+ /** Sample flags. */
+ uint32_t flags = 0;
+};
+
+/**
+ * MediaSample holds a compressed media sample in memory.
+ */
+struct MediaSample {
+ /**
+ * Callback to notify that a media sample is about to be released, giving the creator a chance
+ * to reclaim the data buffer backing the sample. Once this callback returns, the media sample
+ * instance *will* be released so it cannot be used outside of the callback. To enable the
+ * callback, create the media sample with {@link #createWithReleaseCallback}.
+ * @param sample The sample to be released.
+ */
+ using OnSampleReleasedCallback = std::function<void(MediaSample* sample)>;
+
+ /**
+ * Creates a new media sample instance with a registered release callback. The release callback
+ * will get called right before the media sample is released giving the creator a chance to
+ * reclaim the buffer.
+ * @param buffer Byte buffer containing the sample's compressed data.
+ * @param dataOffset Offset, in bytes, to the sample's compressed data inside the buffer.
+ * @param bufferId Buffer identifier that can be used to identify the buffer on release.
+ * @param releaseCallback The sample release callback.
+ * @return A new media sample instance.
+ */
+ static std::shared_ptr<MediaSample> createWithReleaseCallback(
+ uint8_t* buffer, size_t dataOffset, uint32_t bufferId,
+ OnSampleReleasedCallback releaseCallback) {
+ MediaSample* sample = new MediaSample(buffer, dataOffset, bufferId, releaseCallback);
+ return std::shared_ptr<MediaSample>(
+ sample, std::bind(&MediaSample::releaseSample, std::placeholders::_1));
+ }
+
+ /**
+ * Byte buffer containing the sample's compressed data. The media sample instance does not take
+ * ownership of the buffer and will not automatically release the memory, but the caller can
+ * register a release callback by creating the media sample with
+ * {@link #createWithReleaseCallback}.
+ */
+ const uint8_t* buffer = nullptr;
+
+ /** Offset, in bytes, to the sample's compressed data inside the buffer. */
+ size_t dataOffset = 0;
+
+ /**
+ * Buffer identifier. This identifier is likely only meaningful to the sample data producer and
+ * can be used for reclaiming the buffer once a consumer is done processing it.
+ */
+ uint32_t bufferId = 0xBAADF00D;
+
+ /** Media sample information. */
+ MediaSampleInfo info;
+
+private:
+ MediaSample(uint8_t* buffer, size_t dataOffset, uint32_t bufferId,
+ OnSampleReleasedCallback releaseCallback)
+ : buffer(buffer),
+ dataOffset(dataOffset),
+ bufferId(bufferId),
+ mReleaseCallback(releaseCallback){};
+
+ static void releaseSample(MediaSample* sample) {
+ if (sample->mReleaseCallback != nullptr) {
+ sample->mReleaseCallback(sample);
+ }
+ delete sample;
+ }
+
+ // Do not allow copying to prevent dangling pointers in the copied object after the original is
+ // released.
+ MediaSample(const MediaSample&) = delete;
+ MediaSample& operator=(const MediaSample&) = delete;
+
+ const OnSampleReleasedCallback mReleaseCallback = nullptr;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleQueue.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleQueue.h
new file mode 100644
index 0000000..dc22423
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleQueue.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_SAMPLE_QUEUE_H
+#define ANDROID_MEDIA_SAMPLE_QUEUE_H
+
+#include <media/MediaSample.h>
+#include <utils/Mutex.h>
+
+#include <memory>
+#include <mutex>
+#include <queue>
+
+namespace android {
+
+/**
+ * MediaSampleQueue asynchronously connects a producer and a consumer of media samples.
+ * Media samples flows through the queue in FIFO order. If the queue is empty the consumer will be
+ * blocked until a new media sample is added or until the producer aborts the queue operation.
+ */
+class MediaSampleQueue {
+public:
+ /**
+ * Enqueues a media sample at the end of the queue and notifies potentially waiting consumers.
+ * If the queue has previously been aborted this method does nothing.
+ * @param sample The media sample to enqueue.
+ * @return True if the queue has been aborted.
+ */
+ bool enqueue(const std::shared_ptr<MediaSample>& sample);
+
+ /**
+ * Removes the next media sample from the queue and returns it. If the queue has previously been
+ * aborted this method returns null. Note that this method will block while the queue is empty.
+ * @param[out] sample The next media sample in the queue.
+ * @return True if the queue has been aborted.
+ */
+ bool dequeue(std::shared_ptr<MediaSample>* sample /* nonnull */);
+
+ /**
+ * Aborts the queue operation. This clears the queue and notifies waiting consumers. After the
+ * has been aborted it is not possible to enqueue more samples, and dequeue will return null.
+ */
+ void abort();
+
+private:
+ std::queue<std::shared_ptr<MediaSample>> mSampleQueue GUARDED_BY(mMutex);
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ bool mAborted GUARDED_BY(mMutex) = false;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_QUEUE_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleReader.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleReader.h
new file mode 100644
index 0000000..acebac2
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleReader.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_SAMPLE_READER_H
+#define ANDROID_MEDIA_SAMPLE_READER_H
+
+#include <media/MediaSample.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+
+namespace android {
+
+/**
+ * MediaSampleReader is an interface for reading media samples from a container.
+ * MediaSampleReader allows for reading samples from multiple tracks independently of each other
+ * while preserving the order of samples within each individual track.
+ * MediaSampleReader implementations are thread safe and can be used by multiple threads
+ * concurrently. But note that MediaSampleReader only maintains one state per track so concurrent
+ * usage of the same track from multiple threads has no benefit.
+ */
+class MediaSampleReader {
+public:
+ /**
+ * Returns the file format of the media container as a AMediaFormat.
+ * The caller is responsible for releasing the format when finished with it using
+ * AMediaFormat_delete().
+ * @return The file media format.
+ */
+ virtual AMediaFormat* getFileFormat() = 0;
+
+ /**
+ * Returns the number of tracks in the media container.
+ * @return The number of tracks.
+ */
+ virtual size_t getTrackCount() const = 0;
+
+ /**
+ * Returns the media format of a specific track as a AMediaFormat.
+ * The caller is responsible for releasing the format when finished with it using
+ * AMediaFormat_delete().
+ * @param trackIndex The track index (zero-based).
+ * @return The track media format.
+ */
+ virtual AMediaFormat* getTrackFormat(int trackIndex) = 0;
+
+ /**
+ * Returns the sample information for the current sample in the specified track.
+ * @param trackIndex The track index (zero-based).
+ * @param info Pointer to a MediaSampleInfo object where the sample information is written.
+ * @return AMEDIA_OK on success, AMEDIA_ERROR_END_OF_STREAM if there are no more samples to read
+ * from the track and AMEDIA_ERROR_INVALID_PARAMETER if trackIndex is out of bounds or the
+ * info pointer is NULL. Other AMEDIA_ERROR_* return values may not be recoverable.
+ */
+ virtual media_status_t getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) = 0;
+
+ /**
+ * Reads the current sample's data into the supplied buffer.
+ * @param trackIndex The track index (zero-based).
+ * @param buffer The buffer to write the sample's data to.
+ * @param bufferSize The size of the supplied buffer.
+ * @return AMEDIA_OK on success, AMEDIA_ERROR_END_OF_STREAM if there are no more samples to read
+ * from the track and AMEDIA_ERROR_INVALID_PARAMETER if trackIndex is out of bounds, if the
+ * buffer pointer is NULL or if bufferSize is too small for the sample. Other AMEDIA_ERROR_*
+ * return values may not be recoverable.
+ */
+ virtual media_status_t readSampleDataForTrack(int trackIndex, uint8_t* buffer,
+ size_t bufferSize) = 0;
+
+ /**
+ * Advance the specified track to the next sample.
+ * @param trackIndex The track index (zero-based).
+ */
+ virtual void advanceTrack(int trackIndex) = 0;
+
+ /** Destructor. */
+ virtual ~MediaSampleReader() = default;
+
+ /** Constructor. */
+ MediaSampleReader() = default;
+
+private:
+ MediaSampleReader(const MediaSampleReader&) = delete;
+ MediaSampleReader& operator=(const MediaSampleReader&) = delete;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_READER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleReaderNDK.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleReaderNDK.h
new file mode 100644
index 0000000..2dc9029
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleReaderNDK.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_SAMPLE_READER_NDK_H
+#define ANDROID_MEDIA_SAMPLE_READER_NDK_H
+
+#include <media/MediaSampleReader.h>
+#include <media/NdkMediaExtractor.h>
+
+#include <memory>
+#include <mutex>
+#include <vector>
+
+namespace android {
+
+/**
+ * MediaSampleReaderNDK is a concrete implementation of the MediaSampleReader interface based on the
+ * media NDK extractor.
+ */
+class MediaSampleReaderNDK : public MediaSampleReader {
+public:
+ /**
+ * Creates a new MediaSampleReaderNDK instance wrapped in a shared pointer.
+ * @param fd Source file descriptor. The caller is responsible for closing the fd and it is safe
+ * to do so when this method returns.
+ * @param offset Source data offset.
+ * @param size Source data size.
+ * @return A shared pointer referencing the new MediaSampleReaderNDK instance on success, or an
+ * empty shared pointer if an error occurred.
+ */
+ static std::shared_ptr<MediaSampleReader> createFromFd(int fd, size_t offset, size_t size);
+
+ AMediaFormat* getFileFormat() override;
+ size_t getTrackCount() const override;
+ AMediaFormat* getTrackFormat(int trackIndex) override;
+ media_status_t getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) override;
+ media_status_t readSampleDataForTrack(int trackIndex, uint8_t* buffer,
+ size_t bufferSize) override;
+ void advanceTrack(int trackIndex) override;
+
+ virtual ~MediaSampleReaderNDK() override;
+
+private:
+ /**
+ * Creates a new MediaSampleReaderNDK object from an AMediaExtractor. The extractor needs to be
+ * initialized with a valid data source before attempting to create a MediaSampleReaderNDK.
+ * @param extractor The initialized media extractor.
+ */
+ MediaSampleReaderNDK(AMediaExtractor* extractor);
+ media_status_t init();
+
+ AMediaExtractor* mExtractor = nullptr;
+ std::mutex mExtractorMutex;
+ const size_t mTrackCount;
+
+ int mExtractorTrackIndex = -1;
+ uint64_t mExtractorSampleIndex = 0;
+
+ /**
+ * SamplePosition describes the position of a single sample in the media file using its
+ * timestamp and index in the file.
+ */
+ struct SamplePosition {
+ uint64_t index = 0;
+ int64_t timeStampUs = 0;
+ bool isSet = false;
+
+ void set(uint64_t sampleIndex, int64_t sampleTimeUs) {
+ index = sampleIndex;
+ timeStampUs = sampleTimeUs;
+ isSet = true;
+ }
+
+ void reset() { isSet = false; }
+ };
+
+ /**
+ * SampleCursor keeps track of the sample position for a specific track. When the track is
+ * advanced, previous is set to current, current to next and next is reset. As the extractor
+ * advances over the combined timeline of tracks, it updates current and next for the track it
+ * points to if they are not already set.
+ */
+ struct SampleCursor {
+ SamplePosition previous;
+ SamplePosition current;
+ SamplePosition next;
+ };
+
+ /** Samples cursor for each track in the file. */
+ std::vector<SampleCursor> mTrackCursors;
+
+ bool advanceExtractor_l();
+ media_status_t positionExtractorForTrack_l(int trackIndex);
+ media_status_t seekExtractorBackwards_l(int64_t targetTimeUs, int targetTrackIndex,
+ uint64_t targetSampleIndex);
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_SAMPLE_READER_NDK_H
diff --git a/media/libmediatranscoding/transcoder/tests/Android.bp b/media/libmediatranscoding/transcoder/tests/Android.bp
new file mode 100644
index 0000000..a9937d7
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/Android.bp
@@ -0,0 +1,46 @@
+// Unit tests for libmediatranscoder.
+
+filegroup {
+ name: "test_assets",
+ srcs: ["assets/*"],
+}
+
+cc_defaults {
+ name: "testdefaults",
+
+ header_libs: [
+ "libbase_headers",
+ "libmedia_headers",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "libmediandk",
+ "libmediatranscoder",
+ "libutils",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ data: [":test_assets"],
+ test_config_template: "AndroidTestTemplate.xml",
+ test_suites: ["device-tests", "TranscoderTests"],
+}
+
+// MediaSampleReaderNDK unit test
+cc_test {
+ name: "MediaSampleReaderNDKTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaSampleReaderNDKTests.cpp"],
+}
+
+// MediaSampleQueue unit test
+cc_test {
+ name: "MediaSampleQueueTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaSampleQueueTests.cpp"],
+}
diff --git a/media/libmediatranscoding/transcoder/tests/AndroidTestTemplate.xml b/media/libmediatranscoding/transcoder/tests/AndroidTestTemplate.xml
new file mode 100644
index 0000000..23d1bab
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/AndroidTestTemplate.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Unit test configuration for {MODULE}">
+ <option name="test-suite-tag" value="TranscoderTests" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="false" />
+ <option name="push-file"
+ key="assets"
+ value="/data/local/tmp/TranscoderTestAssets" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
+
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleQueueTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleQueueTests.cpp
new file mode 100644
index 0000000..2046ca0
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleQueueTests.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Unit Test for MediaSampleQueue
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaSampleQueueTests"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleQueue.h>
+
+#include <thread>
+
+namespace android {
+
+/** Duration to use when delaying threads to order operations. */
+static constexpr int64_t kThreadDelayDurationMs = 100;
+
+class MediaSampleQueueTests : public ::testing::Test {
+public:
+ MediaSampleQueueTests() { LOG(DEBUG) << "MediaSampleQueueTests created"; }
+ ~MediaSampleQueueTests() { LOG(DEBUG) << "MediaSampleQueueTests destroyed"; }
+};
+
+static std::shared_ptr<MediaSample> newSample(uint32_t id) {
+ return MediaSample::createWithReleaseCallback(nullptr /* buffer */, 0 /* offset */, id,
+ nullptr /* callback */);
+}
+
+TEST_F(MediaSampleQueueTests, TestSequentialDequeueOrder) {
+ LOG(DEBUG) << "TestSequentialDequeueOrder Starts";
+
+ static constexpr int kNumSamples = 4;
+ MediaSampleQueue sampleQueue;
+
+ // Enqueue loop.
+ for (int i = 0; i < kNumSamples; ++i) {
+ sampleQueue.enqueue(newSample(i));
+ }
+
+ // Dequeue loop.
+ for (int i = 0; i < kNumSamples; ++i) {
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_NE(sample, nullptr);
+ EXPECT_EQ(sample->bufferId, i);
+ EXPECT_FALSE(aborted);
+ }
+}
+
+TEST_F(MediaSampleQueueTests, TestInterleavedDequeueOrder) {
+ LOG(DEBUG) << "TestInterleavedDequeueOrder Starts";
+
+ static constexpr int kNumSamples = 4;
+ MediaSampleQueue sampleQueue;
+
+ // Enqueue and dequeue.
+ for (int i = 0; i < kNumSamples; ++i) {
+ sampleQueue.enqueue(newSample(i));
+
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_NE(sample, nullptr);
+ EXPECT_EQ(sample->bufferId, i);
+ EXPECT_FALSE(aborted);
+ }
+}
+
+TEST_F(MediaSampleQueueTests, TestBlockingDequeue) {
+ LOG(DEBUG) << "TestBlockingDequeue Starts";
+
+ MediaSampleQueue sampleQueue;
+
+ std::thread enqueueThread([&sampleQueue] {
+ // Note: This implementation is a bit racy. Any amount of sleep will not guarantee that the
+ // main thread will be blocked on the sample queue by the time this thread calls enqueue.
+ // But we can say with high confidence that it will and the test will not fail regardless.
+ std::this_thread::sleep_for(std::chrono::milliseconds(kThreadDelayDurationMs));
+ sampleQueue.enqueue(newSample(1));
+ });
+
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_NE(sample, nullptr);
+ EXPECT_EQ(sample->bufferId, 1);
+ EXPECT_FALSE(aborted);
+
+ enqueueThread.join();
+}
+
+TEST_F(MediaSampleQueueTests, TestDequeueBufferRelease) {
+ LOG(DEBUG) << "TestDequeueBufferRelease Starts";
+
+ static constexpr int kNumSamples = 4;
+ std::vector<bool> bufferReleased(kNumSamples, false);
+
+ MediaSample::OnSampleReleasedCallback callback = [&bufferReleased](MediaSample* sample) {
+ bufferReleased[sample->bufferId] = true;
+ };
+
+ MediaSampleQueue sampleQueue;
+ for (int i = 0; i < kNumSamples; ++i) {
+ bool aborted = sampleQueue.enqueue(
+ MediaSample::createWithReleaseCallback(nullptr, 0, i, callback));
+ EXPECT_FALSE(aborted);
+ }
+
+ for (int i = 0; i < kNumSamples; ++i) {
+ EXPECT_FALSE(bufferReleased[i]);
+ }
+
+ for (int i = 0; i < kNumSamples; ++i) {
+ {
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_NE(sample, nullptr);
+ EXPECT_EQ(sample->bufferId, i);
+ EXPECT_FALSE(bufferReleased[i]);
+ EXPECT_FALSE(aborted);
+ }
+
+ for (int j = 0; j < kNumSamples; ++j) {
+ EXPECT_EQ(bufferReleased[j], j <= i);
+ }
+ }
+}
+
+TEST_F(MediaSampleQueueTests, TestAbortBufferRelease) {
+ LOG(DEBUG) << "TestAbortBufferRelease Starts";
+
+ static constexpr int kNumSamples = 4;
+ std::vector<bool> bufferReleased(kNumSamples, false);
+
+ MediaSample::OnSampleReleasedCallback callback = [&bufferReleased](MediaSample* sample) {
+ bufferReleased[sample->bufferId] = true;
+ };
+
+ MediaSampleQueue sampleQueue;
+ for (int i = 0; i < kNumSamples; ++i) {
+ bool aborted = sampleQueue.enqueue(
+ MediaSample::createWithReleaseCallback(nullptr, 0, i, callback));
+ EXPECT_FALSE(aborted);
+ }
+
+ for (int i = 0; i < kNumSamples; ++i) {
+ EXPECT_FALSE(bufferReleased[i]);
+ }
+
+ sampleQueue.abort();
+
+ for (int i = 0; i < kNumSamples; ++i) {
+ EXPECT_TRUE(bufferReleased[i]);
+ }
+}
+
+TEST_F(MediaSampleQueueTests, TestNonEmptyAbort) {
+ LOG(DEBUG) << "TestNonEmptyAbort Starts";
+
+ MediaSampleQueue sampleQueue;
+ bool aborted = sampleQueue.enqueue(newSample(1));
+ EXPECT_FALSE(aborted);
+
+ sampleQueue.abort();
+
+ std::shared_ptr<MediaSample> sample;
+ aborted = sampleQueue.dequeue(&sample);
+ EXPECT_TRUE(aborted);
+ EXPECT_EQ(sample, nullptr);
+
+ aborted = sampleQueue.enqueue(sample);
+ EXPECT_TRUE(aborted);
+}
+
+TEST_F(MediaSampleQueueTests, TestEmptyAbort) {
+ LOG(DEBUG) << "TestEmptyAbort Starts";
+
+ MediaSampleQueue sampleQueue;
+ sampleQueue.abort();
+
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_TRUE(aborted);
+ EXPECT_EQ(sample, nullptr);
+
+ aborted = sampleQueue.enqueue(sample);
+ EXPECT_TRUE(aborted);
+}
+
+TEST_F(MediaSampleQueueTests, TestBlockingAbort) {
+ LOG(DEBUG) << "TestBlockingAbort Starts";
+
+ MediaSampleQueue sampleQueue;
+
+ std::thread abortingThread([&sampleQueue] {
+ // Note: This implementation is a bit racy. Any amount of sleep will not guarantee that the
+ // main thread will be blocked on the sample queue by the time this thread calls abort.
+ // But we can say with high confidence that it will and the test will not fail regardless.
+ std::this_thread::sleep_for(std::chrono::milliseconds(kThreadDelayDurationMs));
+ sampleQueue.abort();
+ });
+
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = sampleQueue.dequeue(&sample);
+ EXPECT_TRUE(aborted);
+ EXPECT_EQ(sample, nullptr);
+
+ abortingThread.join();
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp
new file mode 100644
index 0000000..858fcb3
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Unit Test for MediaSampleReaderNDK
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaSampleReaderNDKTests"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <utils/Timers.h>
+
+// TODO(b/153453392): Test more asset types and validate sample data from readSampleDataForTrack.
+
+namespace android {
+
+#define SEC_TO_USEC(s) ((s)*1000 * 1000)
+
+class MediaSampleReaderNDKTests : public ::testing::Test {
+public:
+ MediaSampleReaderNDKTests() { LOG(DEBUG) << "MediaSampleReaderNDKTests created"; }
+
+ void SetUp() override {
+ LOG(DEBUG) << "MediaSampleReaderNDKTests set up";
+ const char* sourcePath =
+ "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+ mExtractor = AMediaExtractor_new();
+ ASSERT_NE(mExtractor, nullptr);
+
+ mSourceFd = open(sourcePath, O_RDONLY);
+ ASSERT_GT(mSourceFd, 0);
+
+ mFileSize = lseek(mSourceFd, 0, SEEK_END);
+ lseek(mSourceFd, 0, SEEK_SET);
+
+ media_status_t status =
+ AMediaExtractor_setDataSourceFd(mExtractor, mSourceFd, 0, mFileSize);
+ ASSERT_EQ(status, AMEDIA_OK);
+
+ mTrackCount = AMediaExtractor_getTrackCount(mExtractor);
+ for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ AMediaExtractor_selectTrack(mExtractor, trackIndex);
+ }
+ }
+
+ void initExtractorTimestamps() {
+ // Save all sample timestamps, per track, as reported by the extractor.
+ mExtractorTimestamps.resize(mTrackCount);
+ do {
+ const int trackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
+ const int64_t sampleTime = AMediaExtractor_getSampleTime(mExtractor);
+
+ mExtractorTimestamps[trackIndex].push_back(sampleTime);
+ } while (AMediaExtractor_advance(mExtractor));
+ }
+
+ void TearDown() override {
+ LOG(DEBUG) << "MediaSampleReaderNDKTests tear down";
+ AMediaExtractor_delete(mExtractor);
+ close(mSourceFd);
+ }
+
+ ~MediaSampleReaderNDKTests() { LOG(DEBUG) << "MediaSampleReaderNDKTests destroyed"; }
+
+ AMediaExtractor* mExtractor = nullptr;
+ size_t mTrackCount;
+ int mSourceFd;
+ size_t mFileSize;
+ std::vector<std::vector<int64_t>> mExtractorTimestamps;
+};
+
+TEST_F(MediaSampleReaderNDKTests, TestSampleTimes) {
+ LOG(DEBUG) << "TestSampleTimes Starts";
+
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(mSourceFd, 0, mFileSize);
+ ASSERT_TRUE(sampleReader);
+
+ MediaSampleInfo info;
+ int trackEosCount = 0;
+ std::vector<bool> trackReachedEos(mTrackCount, false);
+ std::vector<std::vector<int64_t>> readerTimestamps(mTrackCount);
+
+ // Initialize the extractor timestamps.
+ initExtractorTimestamps();
+
+ // Read 5s of each track at a time.
+ const int64_t chunkDurationUs = SEC_TO_USEC(5);
+ int64_t chunkEndTimeUs = chunkDurationUs;
+
+ // Loop until all tracks have reached End Of Stream.
+ while (trackEosCount < mTrackCount) {
+ for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ if (trackReachedEos[trackIndex]) continue;
+
+ // Advance current track to next chunk end time.
+ do {
+ media_status_t status = sampleReader->getSampleInfoForTrack(trackIndex, &info);
+ if (status != AMEDIA_OK) {
+ ASSERT_EQ(status, AMEDIA_ERROR_END_OF_STREAM);
+ ASSERT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) != 0);
+ trackReachedEos[trackIndex] = true;
+ trackEosCount++;
+ break;
+ }
+ ASSERT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+ readerTimestamps[trackIndex].push_back(info.presentationTimeUs);
+ sampleReader->advanceTrack(trackIndex);
+ } while (info.presentationTimeUs < chunkEndTimeUs);
+ }
+ chunkEndTimeUs += chunkDurationUs;
+ }
+
+ for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+ LOG(DEBUG) << "Track " << trackIndex << ", comparing "
+ << readerTimestamps[trackIndex].size() << " samples.";
+ ASSERT_EQ(readerTimestamps[trackIndex].size(), mExtractorTimestamps[trackIndex].size());
+ for (size_t sampleIndex = 0; sampleIndex < readerTimestamps[trackIndex].size();
+ sampleIndex++) {
+ ASSERT_EQ(readerTimestamps[trackIndex][sampleIndex],
+ mExtractorTimestamps[trackIndex][sampleIndex]);
+ }
+ }
+}
+
+TEST_F(MediaSampleReaderNDKTests, TestInvalidFd) {
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(0, 0, mFileSize);
+ ASSERT_TRUE(sampleReader == nullptr);
+
+ sampleReader = MediaSampleReaderNDK::createFromFd(-1, 0, mFileSize);
+ ASSERT_TRUE(sampleReader == nullptr);
+}
+
+TEST_F(MediaSampleReaderNDKTests, TestZeroSize) {
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(mSourceFd, 0, 0);
+ ASSERT_TRUE(sampleReader == nullptr);
+}
+
+TEST_F(MediaSampleReaderNDKTests, TestInvalidOffset) {
+ std::shared_ptr<MediaSampleReader> sampleReader =
+ MediaSampleReaderNDK::createFromFd(mSourceFd, mFileSize, mFileSize);
+ ASSERT_TRUE(sampleReader == nullptr);
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/README.md b/media/libmediatranscoding/transcoder/tests/README.md
new file mode 100644
index 0000000..59417b0
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/README.md
@@ -0,0 +1,14 @@
+## Transcoder Testing ##
+---
+#### Transcoder unit tests :
+To run all transcoder unit tests, run the supplied script from this folder:
+
+```
+./build_and_run_all_unit_tests.sh
+```
+
+To run individual unit test modules, use atest:
+
+```
+atest MediaSampleReaderNDK
+```
diff --git a/media/libmediatranscoding/transcoder/tests/assets/cubicle_avc_480x240_aac_24KHz.mp4 b/media/libmediatranscoding/transcoder/tests/assets/cubicle_avc_480x240_aac_24KHz.mp4
new file mode 100644
index 0000000..ef7e1b7
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/assets/cubicle_avc_480x240_aac_24KHz.mp4
Binary files differ
diff --git a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
new file mode 100755
index 0000000..dbee604
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# Run tests in this directory.
+#
+
+if [ "$SYNC_FINISHED" != true ]; then
+ if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+ fi
+
+ # ensure we have mm
+ . $ANDROID_BUILD_TOP/build/envsetup.sh
+
+ mm
+
+ echo "waiting for device"
+
+ adb root && adb wait-for-device remount && adb sync
+fi
+adb push assets /data/local/tmp/TranscoderTestAssets
+
+echo "========================================"
+
+echo "testing MediaSampleReaderNDK"
+adb shell /data/nativetest64/MediaSampleReaderNDKTests/MediaSampleReaderNDKTests
+
+echo "testing MediaSampleQueue"
+adb shell /data/nativetest64/MediaSampleQueueTests/MediaSampleQueueTests
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 63ab654..6941198 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -555,6 +555,7 @@
mShutdownInProgress(false),
mExplicitShutdown(false),
mIsLegacyVP9Decoder(false),
+ mIsStreamCorruptFree(false),
mEncoderDelay(0),
mEncoderPadding(0),
mRotationDegrees(0),
@@ -2323,6 +2324,12 @@
mChannelMaskPresent = false;
}
+ int32_t isCorruptFree = 0;
+ if (msg->findInt32("corrupt-free", &isCorruptFree)) {
+ mIsStreamCorruptFree = isCorruptFree == 1 ? true : false;
+ ALOGV("corrupt-free=[%d]", mIsStreamCorruptFree);
+ }
+
int32_t maxInputSize;
if (msg->findInt32("max-input-size", &maxInputSize)) {
err = setMinBufferSize(kPortIndexInput, (size_t)maxInputSize);
@@ -4127,6 +4134,29 @@
ALOGI("setupVideoEncoder succeeded");
}
+ // Video should be encoded as stand straight because RTP protocol
+ // can provide rotation information only if CVO is supported.
+ // This needs to be added to support non-CVO case for video streaming scenario.
+ int32_t rotation = 0;
+ if (msg->findInt32("rotation-degrees", &rotation)) {
+ OMX_CONFIG_ROTATIONTYPE config;
+ InitOMXParams(&config);
+ config.nPortIndex = kPortIndexOutput;
+ status_t err = mOMXNode->getConfig(
+ (OMX_INDEXTYPE)OMX_IndexConfigCommonRotate, &config, sizeof(config));
+ if (err != OK) {
+ ALOGW("Failed to getConfig of OMX_IndexConfigCommonRotate(err %d)", err);
+ }
+ config.nRotation = rotation;
+ err = mOMXNode->setConfig(
+ (OMX_INDEXTYPE)OMX_IndexConfigCommonRotate, &config, sizeof(config));
+
+ ALOGD("Applying encoder-rotation=[%d] to video encoder.", config.nRotation);
+ if (err != OK) {
+ ALOGW("Failed to setConfig of OMX_IndexConfigCommonRotate(err %d)", err);
+ }
+ }
+
return err;
}
@@ -5974,6 +6004,12 @@
return false;
}
+ if (mCodec->mIsStreamCorruptFree && data1 == (OMX_U32)OMX_ErrorStreamCorrupt) {
+ ALOGV("[%s] handle OMX_ErrorStreamCorrupt as a normal operation",
+ mCodec->mComponentName.c_str());
+ return true;
+ }
+
ALOGE("[%s] ERROR(0x%08x)", mCodec->mComponentName.c_str(), data1);
// verify OMX component sends back an error we expect.
@@ -6081,6 +6117,13 @@
return;
}
+ int32_t cvo;
+ if (mCodec->mNativeWindow != NULL && buffer != NULL &&
+ buffer->meta()->findInt32("cvo", &cvo)) {
+ ALOGV("cvo(%d) found in buffer #%u", cvo, bufferID);
+ setNativeWindowRotation(mCodec->mNativeWindow.get(), cvo);
+ }
+
info->mStatus = BufferInfo::OWNED_BY_US;
info->mData = buffer;
diff --git a/media/libstagefright/FrameDecoder.cpp b/media/libstagefright/FrameDecoder.cpp
index 734f5bb..965b6dd 100644
--- a/media/libstagefright/FrameDecoder.cpp
+++ b/media/libstagefright/FrameDecoder.cpp
@@ -19,6 +19,7 @@
#include "include/FrameDecoder.h"
#include "include/FrameCaptureLayer.h"
+#include "include/HevcUtils.h"
#include <binder/MemoryBase.h>
#include <binder/MemoryHeapBase.h>
#include <gui/Surface.h>
@@ -456,7 +457,8 @@
const sp<IMediaSource> &source)
: FrameDecoder(componentName, trackMeta, source),
mFrame(NULL),
- mIsAvcOrHevc(false),
+ mIsAvc(false),
+ mIsHevc(false),
mSeekMode(MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC),
mTargetTimeUs(-1LL),
mDefaultSampleDurationUs(0) {
@@ -479,8 +481,8 @@
return NULL;
}
- mIsAvcOrHevc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)
- || !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC);
+ mIsAvc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+ mIsHevc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC);
if (frameTimeUs < 0) {
int64_t thumbNailTime = -1ll;
@@ -543,8 +545,10 @@
ALOGV("Seeking closest: targetTimeUs=%lld", (long long)mTargetTimeUs);
}
- if (mIsAvcOrHevc && !isSeekingClosest
- && IsIDR(codecBuffer->data(), codecBuffer->size())) {
+ if (!isSeekingClosest
+ && ((mIsAvc && IsIDR(codecBuffer->data(), codecBuffer->size()))
+ || (mIsHevc && IsIDR(
+ codecBuffer->data(), codecBuffer->size())))) {
// Only need to decode one IDR frame, unless we're seeking with CLOSEST
// option, in which case we need to actually decode to targetTimeUs.
*flags |= MediaCodec::BUFFER_FLAG_EOS;
diff --git a/media/libstagefright/HevcUtils.cpp b/media/libstagefright/HevcUtils.cpp
index 482a1a7..9faa28c 100644
--- a/media/libstagefright/HevcUtils.cpp
+++ b/media/libstagefright/HevcUtils.cpp
@@ -32,7 +32,10 @@
namespace android {
-static const uint8_t kHevcNalUnitTypes[5] = {
+static const uint8_t kHevcNalUnitTypes[8] = {
+ kHevcNalUnitTypeCodedSliceIdr,
+ kHevcNalUnitTypeCodedSliceIdrNoLP,
+ kHevcNalUnitTypeCodedSliceCra,
kHevcNalUnitTypeVps,
kHevcNalUnitTypeSps,
kHevcNalUnitTypePps,
@@ -488,4 +491,26 @@
return OK;
}
+bool HevcParameterSets::IsHevcIDR(const uint8_t *data, size_t size) {
+ bool foundIDR = false;
+ const uint8_t *nalStart;
+ size_t nalSize;
+ while (!foundIDR && getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
+ if (nalSize == 0) {
+ ALOGE("Encountered zero-length HEVC NAL");
+ return false;
+ }
+
+ uint8_t nalType = (nalStart[0] & 0x7E) >> 1;
+ switch(nalType) {
+ case kHevcNalUnitTypeCodedSliceIdr:
+ case kHevcNalUnitTypeCodedSliceIdrNoLP:
+ case kHevcNalUnitTypeCodedSliceCra:
+ foundIDR = true;
+ break;
+ }
+ }
+
+ return foundIDR;
+}
} // namespace android
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 11f2f38..3ba87d6 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -56,6 +56,7 @@
#include <media/stagefright/CCodec.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecList.h>
+#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaFilter.h>
@@ -2305,7 +2306,7 @@
if (mSurface != nullptr && !mAllowFrameDroppingBySurface) {
// signal frame dropping mode in the input format as this may also be
// meaningful and confusing for an encoder in a transcoder scenario
- mInputFormat->setInt32("allow-frame-drop", mAllowFrameDroppingBySurface);
+ mInputFormat->setInt32(KEY_ALLOW_FRAME_DROP, mAllowFrameDroppingBySurface);
}
ALOGV("[%s] configured as input format: %s, output format: %s",
mComponentName.c_str(),
@@ -2764,7 +2765,7 @@
}
if (obj != NULL) {
- if (!format->findInt32("allow-frame-drop", &mAllowFrameDroppingBySurface)) {
+ if (!format->findInt32(KEY_ALLOW_FRAME_DROP, &mAllowFrameDroppingBySurface)) {
// allow frame dropping by surface by default
mAllowFrameDroppingBySurface = true;
}
diff --git a/media/libstagefright/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index 1395c27..bc656a2 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -435,6 +435,30 @@
buffer->release();
}
+status_t MediaCodecSource::setEncodingBitrate(int32_t bitRate) {
+ ALOGV("setEncodingBitrate (%d)", bitRate);
+
+ if (mEncoder == NULL) {
+ ALOGW("setEncodingBitrate (%d) : mEncoder is null", bitRate);
+ return BAD_VALUE;
+ }
+
+ sp<AMessage> params = new AMessage;
+ params->setInt32("video-bitrate", bitRate);
+
+ return mEncoder->setParameters(params);
+}
+
+status_t MediaCodecSource::requestIDRFrame() {
+ if (mEncoder == NULL) {
+ ALOGW("requestIDRFrame : mEncoder is null");
+ return BAD_VALUE;
+ } else {
+ mEncoder->requestIDRFrame();
+ return OK;
+ }
+}
+
MediaCodecSource::MediaCodecSource(
const sp<ALooper> &looper,
const sp<AMessage> &outputFormat,
diff --git a/media/libstagefright/SurfaceUtils.cpp b/media/libstagefright/SurfaceUtils.cpp
index 4c94baa..85ff474 100644
--- a/media/libstagefright/SurfaceUtils.cpp
+++ b/media/libstagefright/SurfaceUtils.cpp
@@ -165,6 +165,22 @@
ALOGW_IF(err != 0, "failed to set cta861_3 metadata on surface (%d)", err);
}
+status_t setNativeWindowRotation(
+ ANativeWindow *nativeWindow /* nonnull */, int rotation) {
+
+ int transform = 0;
+ if ((rotation % 90) == 0) {
+ switch ((rotation / 90) & 3) {
+ case 1: transform = HAL_TRANSFORM_ROT_90; break;
+ case 2: transform = HAL_TRANSFORM_ROT_180; break;
+ case 3: transform = HAL_TRANSFORM_ROT_270; break;
+ default: transform = 0; break;
+ }
+ }
+
+ return native_window_set_buffers_transform(nativeWindow, transform);
+}
+
status_t pushBlankBuffersToNativeWindow(ANativeWindow *nativeWindow /* nonnull */) {
status_t err = NO_ERROR;
ANativeWindowBuffer* anb = NULL;
diff --git a/media/libstagefright/foundation/TEST_MAPPING b/media/libstagefright/foundation/TEST_MAPPING
index 3301c4b..1bd56a6 100644
--- a/media/libstagefright/foundation/TEST_MAPPING
+++ b/media/libstagefright/foundation/TEST_MAPPING
@@ -1,5 +1,10 @@
+// mappings for frameworks/av/media/libstagefright/foundation
{
"presubmit": [
- { "name": "sf_foundation_test" }
+ // b/148094059: unit tests not allowed to download content
+ //{ "name": "OpusHeaderTest" },
+
+ { "name": "sf_foundation_test" },
+ { "name": "MetaDataBaseUnitTest"}
]
}
diff --git a/media/libstagefright/foundation/tests/Android.bp b/media/libstagefright/foundation/tests/Android.bp
index 45e81e8..9e67189 100644
--- a/media/libstagefright/foundation/tests/Android.bp
+++ b/media/libstagefright/foundation/tests/Android.bp
@@ -28,6 +28,7 @@
cc_test {
name: "MetaDataBaseUnitTest",
+ test_suites: ["device-tests"],
gtest: true,
srcs: [
diff --git a/media/libstagefright/include/FrameDecoder.h b/media/libstagefright/include/FrameDecoder.h
index 19ae0e3..bca7f01 100644
--- a/media/libstagefright/include/FrameDecoder.h
+++ b/media/libstagefright/include/FrameDecoder.h
@@ -135,7 +135,8 @@
private:
sp<FrameCaptureLayer> mCaptureLayer;
VideoFrame *mFrame;
- bool mIsAvcOrHevc;
+ bool mIsAvc;
+ bool mIsHevc;
MediaSource::ReadOptions::SeekMode mSeekMode;
int64_t mTargetTimeUs;
List<int64_t> mSampleDurations;
diff --git a/media/libstagefright/include/HevcUtils.h b/media/libstagefright/include/HevcUtils.h
index 0f59631..d2a86eb 100644
--- a/media/libstagefright/include/HevcUtils.h
+++ b/media/libstagefright/include/HevcUtils.h
@@ -30,6 +30,10 @@
namespace android {
enum {
+ kHevcNalUnitTypeCodedSliceIdr = 19,
+ kHevcNalUnitTypeCodedSliceIdrNoLP = 20,
+ kHevcNalUnitTypeCodedSliceCra = 21,
+
kHevcNalUnitTypeVps = 32,
kHevcNalUnitTypeSps = 33,
kHevcNalUnitTypePps = 34,
@@ -92,6 +96,7 @@
status_t makeHvcc(uint8_t *hvcc, size_t *hvccSize, size_t nalSizeLength);
Info getInfo() const { return mInfo; }
+ static bool IsHevcIDR(const uint8_t *data, size_t size);
private:
status_t parseVps(const uint8_t* data, size_t size);
diff --git a/media/libstagefright/include/media/stagefright/ACodec.h b/media/libstagefright/include/media/stagefright/ACodec.h
index 83e92b9..cc40f76 100644
--- a/media/libstagefright/include/media/stagefright/ACodec.h
+++ b/media/libstagefright/include/media/stagefright/ACodec.h
@@ -272,6 +272,7 @@
bool mShutdownInProgress;
bool mExplicitShutdown;
bool mIsLegacyVP9Decoder;
+ bool mIsStreamCorruptFree;
// If "mKeepComponentAllocated" we only transition back to Loaded state
// and do not release the component instance.
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 63a9dad..c6b6639 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -81,6 +81,13 @@
BUFFER_FLAG_MUXER_DATA = 16,
};
+ enum CVODegree {
+ CVO_DEGREE_0 = 0,
+ CVO_DEGREE_90 = 90,
+ CVO_DEGREE_180 = 180,
+ CVO_DEGREE_270 = 270,
+ };
+
enum {
CB_INPUT_AVAILABLE = 1,
CB_OUTPUT_AVAILABLE = 2,
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
index 178d334..9c67338 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
@@ -744,6 +744,7 @@
constexpr char KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT[] = "aac-max-output-channel_count";
constexpr char KEY_AAC_PROFILE[] = "aac-profile";
constexpr char KEY_AAC_SBR_MODE[] = "aac-sbr-mode";
+constexpr char KEY_ALLOW_FRAME_DROP[] = "allow-frame-drop";
constexpr char KEY_AUDIO_SESSION_ID[] = "audio-session-id";
constexpr char KEY_BIT_RATE[] = "bitrate";
constexpr char KEY_BITRATE_MODE[] = "bitrate-mode";
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecSource.h b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
index 2f98af1..0f7b535 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecSource.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
@@ -64,12 +64,15 @@
// MediaBufferObserver
virtual void signalBufferReturned(MediaBufferBase *buffer);
+ virtual status_t setEncodingBitrate(int32_t bitRate /* bps */);
// for AHandlerReflector
void onMessageReceived(const sp<AMessage> &msg);
+ status_t requestIDRFrame();
+
protected:
virtual ~MediaCodecSource();
diff --git a/media/libstagefright/include/media/stagefright/MediaWriter.h b/media/libstagefright/include/media/stagefright/MediaWriter.h
index 08e54b3..fd2c171 100644
--- a/media/libstagefright/include/media/stagefright/MediaWriter.h
+++ b/media/libstagefright/include/media/stagefright/MediaWriter.h
@@ -55,6 +55,10 @@
virtual void setStartTimeOffsetMs(int /*ms*/) {}
virtual int32_t getStartTimeOffsetMs() const { return 0; }
virtual status_t setNextFd(int /*fd*/) { return INVALID_OPERATION; }
+ virtual void updateCVODegrees(int32_t /*cvoDegrees*/) {}
+ virtual void updatePayloadType(int32_t /*payloadType*/) {}
+ virtual void updateSocketNetwork(int64_t /*socketNetwork*/) {}
+ virtual uint32_t getSequenceNum() { return 0; }
protected:
virtual ~MediaWriter() {}
diff --git a/media/libstagefright/include/media/stagefright/MetaDataBase.h b/media/libstagefright/include/media/stagefright/MetaDataBase.h
index 3b701f6..847093d 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataBase.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataBase.h
@@ -241,6 +241,15 @@
// Treat empty track as malformed for MediaRecorder.
kKeyEmptyTrackMalFormed = 'nemt', // bool (int32_t)
+
+ kKeySps = 'sSps', // int32_t, indicates that a buffer is sps (value ignored).
+ kKeyPps = 'sPps', // int32_t, indicates that a buffer is pps (value ignored).
+ kKeySelfID = 'sfid', // int32_t, source ID to identify itself on RTP protocol.
+ kKeyPayloadType = 'pTyp', // int32_t, SDP negotiated payload type.
+ kKeyRtpExtMap = 'extm', // int32_t, rtp extension ID for cvo on RTP protocol.
+ kKeyRtpCvoDegrees = 'cvod', // int32_t, rtp cvo degrees as per 3GPP 26.114.
+ kKeyRtpDscp = 'dscp', // int32_t, DSCP(Differentiated services codepoint) of RFC 2474.
+ kKeySocketNetwork = 'sNet', // int64_t, socket will be bound to network handle.
};
enum {
diff --git a/media/libstagefright/include/media/stagefright/RemoteDataSource.h b/media/libstagefright/include/media/stagefright/RemoteDataSource.h
index d82be8a..d605cda 100644
--- a/media/libstagefright/include/media/stagefright/RemoteDataSource.h
+++ b/media/libstagefright/include/media/stagefright/RemoteDataSource.h
@@ -41,6 +41,11 @@
close();
}
virtual sp<IMemory> getIMemory() {
+ Mutex::Autolock lock(mLock);
+ if (mMemory.get() == nullptr) {
+ ALOGE("getIMemory() failed, mMemory is nullptr");
+ return nullptr;
+ }
return mMemory;
}
virtual ssize_t readAt(off64_t offset, size_t size) {
@@ -48,19 +53,35 @@
if (size > kBufferSize) {
size = kBufferSize;
}
+
+ Mutex::Autolock lock(mLock);
+ if (mSource.get() == nullptr) {
+ ALOGE("readAt() failed, mSource is nullptr");
+ return 0;
+ }
return mSource->readAt(offset, mMemory->unsecurePointer(), size);
}
virtual status_t getSize(off64_t *size) {
+ Mutex::Autolock lock(mLock);
+ if (mSource.get() == nullptr) {
+ ALOGE("getSize() failed, mSource is nullptr");
+ return INVALID_OPERATION;
+ }
return mSource->getSize(size);
}
virtual void close() {
// Protect strong pointer assignments. This also can be called from the binder
// clean-up procedure which is running on a separate thread.
- Mutex::Autolock lock(mCloseLock);
+ Mutex::Autolock lock(mLock);
mSource = nullptr;
mMemory = nullptr;
}
virtual uint32_t getFlags() {
+ Mutex::Autolock lock(mLock);
+ if (mSource.get() == nullptr) {
+ ALOGE("getSize() failed, mSource is nullptr");
+ return 0;
+ }
return mSource->flags();
}
virtual String8 toString() {
@@ -75,9 +96,10 @@
sp<IMemory> mMemory;
sp<DataSource> mSource;
String8 mName;
- Mutex mCloseLock;
+ Mutex mLock;
explicit RemoteDataSource(const sp<DataSource> &source) {
+ Mutex::Autolock lock(mLock);
mSource = source;
sp<MemoryDealer> memoryDealer = new MemoryDealer(kBufferSize, "RemoteDataSource");
mMemory = memoryDealer->allocate(kBufferSize);
diff --git a/media/libstagefright/include/media/stagefright/SurfaceUtils.h b/media/libstagefright/include/media/stagefright/SurfaceUtils.h
index ae55c65..35b3fa2 100644
--- a/media/libstagefright/include/media/stagefright/SurfaceUtils.h
+++ b/media/libstagefright/include/media/stagefright/SurfaceUtils.h
@@ -38,6 +38,8 @@
int width, int height, int format, int rotation, int usage, bool reconnect);
void setNativeWindowHdrMetadata(
ANativeWindow *nativeWindow /* nonnull */, HDRStaticInfo *info /* nonnull */);
+status_t setNativeWindowRotation(
+ ANativeWindow *nativeWindow /* nonnull */, int rotation);
status_t pushBlankBuffersToNativeWindow(ANativeWindow *nativeWindow /* nonnull */);
status_t nativeWindowConnect(ANativeWindow *surface, const char *reason);
status_t nativeWindowDisconnect(ANativeWindow *surface, const char *reason);
diff --git a/media/libstagefright/rtsp/AAVCAssembler.cpp b/media/libstagefright/rtsp/AAVCAssembler.cpp
index 4bc67e8..0164040 100644
--- a/media/libstagefright/rtsp/AAVCAssembler.cpp
+++ b/media/libstagefright/rtsp/AAVCAssembler.cpp
@@ -51,7 +51,66 @@
return NOT_ENOUGH_DATA;
}
+ sp<ABuffer> buffer = *queue->begin();
+ int32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", &rtpTime));
+ int64_t startTime = source->mFirstSysTime / 1000;
+ int64_t nowTime = ALooper::GetNowUs() / 1000;
+ int64_t playedTime = nowTime - startTime;
+ int32_t playedTimeRtp = source->mFirstRtpTime +
+ (((uint32_t)playedTime) * (source->mClockRate / 1000));
+ const int32_t jitterTime = source->mClockRate / 5; // 200ms
+ int32_t expiredTimeInJb = rtpTime + jitterTime;
+ bool isExpired = expiredTimeInJb <= (playedTimeRtp);
+ bool isTooLate200 = expiredTimeInJb < (playedTimeRtp - jitterTime);
+ bool isTooLate300 = expiredTimeInJb < (playedTimeRtp - (jitterTime * 3 / 2));
+
+ if (mShowQueue && mShowQueueCnt < 20) {
+ showCurrentQueue(queue);
+ ALOGD("start=%lld, now=%lld, played=%lld", (long long)startTime,
+ (long long)nowTime, (long long)playedTime);
+ ALOGD("rtp-time(JB)=%d, played-rtp-time(JB)=%d, expired-rtp-time(JB)=%d isExpired=%d",
+ rtpTime, playedTimeRtp, expiredTimeInJb, isExpired);
+ mShowQueueCnt++;
+ }
+
+ ALOGV("start=%lld, now=%lld, played=%lld", (long long)startTime,
+ (long long)nowTime, (long long)playedTime);
+ ALOGV("rtp-time(JB)=%d, played-rtp-time(JB)=%d, expired-rtp-time(JB)=%d isExpired=%d",
+ rtpTime, playedTimeRtp, expiredTimeInJb, isExpired);
+
+ if (!isExpired) {
+ ALOGV("buffering in jitter buffer.");
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (isTooLate200)
+ ALOGW("=== WARNING === buffer arrived 200ms late. === WARNING === ");
+
+ if (isTooLate300) {
+ ALOGW("buffer arrived too late. 300ms..");
+ ALOGW("start=%lld, now=%lld, played=%lld", (long long)startTime,
+ (long long)nowTime, (long long)playedTime);
+ ALOGW("rtp-time(JB)=%d, plyed-rtp-time(JB)=%d, exp-rtp-time(JB)=%d diff=%lld isExpired=%d",
+ rtpTime, playedTimeRtp, expiredTimeInJb,
+ ((long long)playedTimeRtp) - expiredTimeInJb, isExpired);
+ ALOGW("expected Seq. NO =%d", buffer->int32Data());
+
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ CHECK((*it)->meta()->findInt32("rtp-time", &rtpTime));
+ if (rtpTime + jitterTime >= playedTimeRtp) {
+ mNextExpectedSeqNo = (*it)->int32Data();
+ break;
+ }
+ it++;
+ }
+ source->noticeAbandonBuffer();
+ }
+
if (mNextExpectedSeqNoValid) {
+ int32_t size = queue->size();
+ int32_t cnt = 0;
List<sp<ABuffer> >::iterator it = queue->begin();
while (it != queue->end()) {
if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
@@ -59,14 +118,19 @@
}
it = queue->erase(it);
+ cnt++;
}
+ if (cnt > 0) {
+ source->noticeAbandonBuffer(cnt);
+ ALOGW("delete %d of %d buffers", cnt, size);
+ }
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
}
- sp<ABuffer> buffer = *queue->begin();
+ buffer = *queue->begin();
if (!mNextExpectedSeqNoValid) {
mNextExpectedSeqNoValid = true;
@@ -290,6 +354,7 @@
unit->data()[0] = (nri << 5) | nalType;
size_t offset = 1;
+ int32_t cvo = -1;
List<sp<ABuffer> >::iterator it = queue->begin();
for (size_t i = 0; i < totalCount; ++i) {
const sp<ABuffer> &buffer = *it;
@@ -300,6 +365,8 @@
#endif
memcpy(unit->data() + offset, buffer->data() + 2, buffer->size() - 2);
+
+ buffer->meta()->findInt32("cvo", &cvo);
offset += buffer->size() - 2;
it = queue->erase(it);
@@ -307,6 +374,10 @@
unit->setRange(0, totalSize);
+ if (cvo >= 0) {
+ unit->meta()->setInt32("cvo", cvo);
+ }
+
addSingleNALUnit(unit);
ALOGV("successfully assembled a NAL unit from fragments.");
@@ -327,6 +398,7 @@
sp<ABuffer> accessUnit = new ABuffer(totalSize);
size_t offset = 0;
+ int32_t cvo = -1;
for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
it != mNALUnits.end(); ++it) {
memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4);
@@ -335,6 +407,8 @@
sp<ABuffer> nal = *it;
memcpy(accessUnit->data() + offset, nal->data(), nal->size());
offset += nal->size();
+
+ nal->meta()->findInt32("cvo", &cvo);
}
CopyTimes(accessUnit, *mNALUnits.begin());
@@ -343,6 +417,9 @@
printf(mAccessUnitDamaged ? "X" : ".");
fflush(stdout);
#endif
+ if (cvo >= 0) {
+ accessUnit->meta()->setInt32("cvo", cvo);
+ }
if (mAccessUnitDamaged) {
accessUnit->meta()->setInt32("damaged", true);
diff --git a/media/libstagefright/rtsp/AHEVCAssembler.cpp b/media/libstagefright/rtsp/AHEVCAssembler.cpp
new file mode 100644
index 0000000..93869fb
--- /dev/null
+++ b/media/libstagefright/rtsp/AHEVCAssembler.cpp
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "AHEVCAssembler"
+#include <utils/Log.h>
+
+#include "AHEVCAssembler.h"
+
+#include "ARTPSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+#include <stdint.h>
+
+#define H265_NALU_MASK 0x3F
+#define H265_NALU_VPS 0x20
+#define H265_NALU_SPS 0x21
+#define H265_NALU_PPS 0x22
+#define H265_NALU_AP 0x30
+#define H265_NALU_FU 0x31
+#define H265_NALU_PACI 0x32
+
+
+namespace android {
+
+// static
+AHEVCAssembler::AHEVCAssembler(const sp<AMessage> ¬ify)
+ : mNotifyMsg(notify),
+ mAccessUnitRTPTime(0),
+ mNextExpectedSeqNoValid(false),
+ mNextExpectedSeqNo(0),
+ mAccessUnitDamaged(false) {
+
+ ALOGV("Constructor");
+}
+
+AHEVCAssembler::~AHEVCAssembler() {
+}
+
+ARTPAssembler::AssemblyStatus AHEVCAssembler::addNALUnit(
+ const sp<ARTPSource> &source) {
+ List<sp<ABuffer> > *queue = source->queue();
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ sp<ABuffer> buffer = *queue->begin();
+ int32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", &rtpTime));
+ int64_t startTime = source->mFirstSysTime / 1000;
+ int64_t nowTime = ALooper::GetNowUs() / 1000;
+ int64_t playedTime = nowTime - startTime;
+ int32_t playedTimeRtp = source->mFirstRtpTime +
+ (((uint32_t)playedTime) * (source->mClockRate / 1000));
+ int32_t expiredTimeInJb = rtpTime + (source->mClockRate / 5);
+ bool isExpired = expiredTimeInJb <= (playedTimeRtp);
+ ALOGV("start=%lld, now=%lld, played=%lld", (long long)startTime,
+ (long long)nowTime, (long long)playedTime);
+ ALOGV("rtp-time(JB)=%d, played-rtp-time(JB)=%d, expired-rtp-time(JB)=%d isExpired=%d",
+ rtpTime, playedTimeRtp, expiredTimeInJb, isExpired);
+
+ if (!isExpired) {
+ ALOGV("buffering in jitter buffer.");
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mNextExpectedSeqNoValid) {
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
+ break;
+ }
+
+ it = queue->erase(it);
+ }
+
+ if (queue->empty()) {
+ return NOT_ENOUGH_DATA;
+ }
+ }
+
+ buffer = *queue->begin();
+
+ if (!mNextExpectedSeqNoValid) {
+ mNextExpectedSeqNoValid = true;
+ mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
+ } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
+ ALOGV("Not the sequence number I expected");
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if (size < 1 || (data[0] & 0x80)) {
+ // Corrupt.
+
+ ALOGV("Ignoring corrupt buffer.");
+ queue->erase(queue->begin());
+
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ unsigned nalType = (data[0] >> 1) & H265_NALU_MASK;
+ if (nalType > 0 && nalType < H265_NALU_AP) {
+ addSingleNALUnit(buffer);
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return OK;
+ } else if (nalType == H265_NALU_FU) {
+ // FU-A
+ return addFragmentedNALUnit(queue);
+ } else if (nalType == H265_NALU_AP) {
+ // STAP-A
+ bool success = addSingleTimeAggregationPacket(buffer);
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return success ? OK : MALFORMED_PACKET;
+ } else if (nalType == 0) {
+ ALOGV("Ignoring undefined nal type.");
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return OK;
+ } else {
+ ALOGV("Ignoring unsupported buffer (nalType=%d)", nalType);
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+
+ return MALFORMED_PACKET;
+ }
+}
+
+void AHEVCAssembler::addSingleNALUnit(const sp<ABuffer> &buffer) {
+ ALOGV("addSingleNALUnit of size %zu", buffer->size());
+#if !LOG_NDEBUG
+ hexdump(buffer->data(), buffer->size());
+#endif
+
+ uint32_t rtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
+
+ if (!mNALUnits.empty() && rtpTime != mAccessUnitRTPTime) {
+ submitAccessUnit();
+ }
+ mAccessUnitRTPTime = rtpTime;
+
+ mNALUnits.push_back(buffer);
+}
+
+bool AHEVCAssembler::addSingleTimeAggregationPacket(const sp<ABuffer> &buffer) {
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if (size < 3) {
+ ALOGV("Discarding too small STAP-A packet.");
+ return false;
+ }
+
+ ++data;
+ --size;
+ while (size >= 2) {
+ size_t nalSize = (data[0] << 8) | data[1];
+
+ if (size < nalSize + 2) {
+ ALOGV("Discarding malformed STAP-A packet.");
+ return false;
+ }
+
+ sp<ABuffer> unit = new ABuffer(nalSize);
+ memcpy(unit->data(), &data[2], nalSize);
+
+ CopyTimes(unit, buffer);
+
+ addSingleNALUnit(unit);
+
+ data += 2 + nalSize;
+ size -= 2 + nalSize;
+ }
+
+ if (size != 0) {
+ ALOGV("Unexpected padding at end of STAP-A packet.");
+ }
+
+ return true;
+}
+
+ARTPAssembler::AssemblyStatus AHEVCAssembler::addFragmentedNALUnit(
+ List<sp<ABuffer> > *queue) {
+ CHECK(!queue->empty());
+
+ sp<ABuffer> buffer = *queue->begin();
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ CHECK(size > 0);
+ /* H265 payload header is 16 bit
+ 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |F| Type | Layer ID | TID |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ unsigned indicator = (data[0] >> 1);
+
+ CHECK((indicator & H265_NALU_MASK) == H265_NALU_FU);
+
+ if (size < 3) {
+ ALOGV("Ignoring malformed FU buffer (size = %zu)", size);
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ if (!(data[2] & 0x80)) {
+ // Start bit not set on the first buffer.
+
+ ALOGV("Start bit not set on first buffer");
+
+ queue->erase(queue->begin());
+ ++mNextExpectedSeqNo;
+ return MALFORMED_PACKET;
+ }
+
+ /* FU INDICATOR HDR
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+
+ |S|E| Type |
+ +-+-+-+-+-+-+-+-+
+ */
+ uint32_t nalType = data[2] & H265_NALU_MASK;
+ uint32_t tid = data[1] & 0x7;
+ ALOGV("nalType =%u, tid =%u", nalType, tid);
+
+ uint32_t expectedSeqNo = (uint32_t)buffer->int32Data() + 1;
+ size_t totalSize = size - 3;
+ size_t totalCount = 1;
+ bool complete = false;
+
+ if (data[2] & 0x40) {
+ // Huh? End bit also set on the first buffer.
+
+ ALOGV("Grrr. This isn't fragmented at all.");
+
+ complete = true;
+ } else {
+ List<sp<ABuffer> >::iterator it = ++queue->begin();
+ while (it != queue->end()) {
+ ALOGV("sequence length %zu", totalCount);
+
+ const sp<ABuffer> &buffer = *it;
+
+ const uint8_t *data = buffer->data();
+ size_t size = buffer->size();
+
+ if ((uint32_t)buffer->int32Data() != expectedSeqNo) {
+ ALOGV("sequence not complete, expected seqNo %d, got %d",
+ expectedSeqNo, (uint32_t)buffer->int32Data());
+
+ return WRONG_SEQUENCE_NUMBER;
+ }
+
+ if (size < 3
+ || ((data[0] >> 1) & H265_NALU_MASK) != indicator
+ || (data[2] & H265_NALU_MASK) != nalType
+ || (data[2] & 0x80)) {
+ ALOGV("Ignoring malformed FU buffer.");
+
+ // Delete the whole start of the FU.
+
+ it = queue->begin();
+ for (size_t i = 0; i <= totalCount; ++i) {
+ it = queue->erase(it);
+ }
+
+ mNextExpectedSeqNo = expectedSeqNo + 1;
+
+ return MALFORMED_PACKET;
+ }
+
+ totalSize += size - 3;
+ ++totalCount;
+
+ expectedSeqNo = expectedSeqNo + 1;
+
+ if (data[2] & 0x40) {
+ // This is the last fragment.
+ complete = true;
+ break;
+ }
+
+ ++it;
+ }
+ }
+
+ if (!complete) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ mNextExpectedSeqNo = expectedSeqNo;
+
+ // We found all the fragments that make up the complete NAL unit.
+
+ // Leave room for the header. So far totalSize did not include the
+ // header byte.
+ totalSize += 2;
+
+ sp<ABuffer> unit = new ABuffer(totalSize);
+ CopyTimes(unit, *queue->begin());
+
+ unit->data()[0] = (nalType << 1);
+ unit->data()[1] = tid;
+
+ size_t offset = 2;
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ for (size_t i = 0; i < totalCount; ++i) {
+ const sp<ABuffer> &buffer = *it;
+
+ ALOGV("piece #%zu/%zu", i + 1, totalCount);
+#if !LOG_NDEBUG
+ hexdump(buffer->data(), buffer->size());
+#endif
+
+ memcpy(unit->data() + offset, buffer->data() + 3, buffer->size() - 3);
+ offset += buffer->size() - 3;
+
+ it = queue->erase(it);
+ }
+
+ unit->setRange(0, totalSize);
+
+ addSingleNALUnit(unit);
+
+ ALOGV("successfully assembled a NAL unit from fragments.");
+
+ return OK;
+}
+
+void AHEVCAssembler::submitAccessUnit() {
+ CHECK(!mNALUnits.empty());
+
+ ALOGV("Access unit complete (%zu nal units)", mNALUnits.size());
+
+ size_t totalSize = 0;
+ for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
+ it != mNALUnits.end(); ++it) {
+ totalSize += 4 + (*it)->size();
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(totalSize);
+ size_t offset = 0;
+ for (List<sp<ABuffer> >::iterator it = mNALUnits.begin();
+ it != mNALUnits.end(); ++it) {
+ memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4);
+ offset += 4;
+
+ sp<ABuffer> nal = *it;
+ memcpy(accessUnit->data() + offset, nal->data(), nal->size());
+ offset += nal->size();
+ }
+
+ CopyTimes(accessUnit, *mNALUnits.begin());
+
+#if 0
+ printf(mAccessUnitDamaged ? "X" : ".");
+ fflush(stdout);
+#endif
+
+ if (mAccessUnitDamaged) {
+ accessUnit->meta()->setInt32("damaged", true);
+ }
+
+ mNALUnits.clear();
+ mAccessUnitDamaged = false;
+
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setBuffer("access-unit", accessUnit);
+ msg->post();
+}
+
+ARTPAssembler::AssemblyStatus AHEVCAssembler::assembleMore(
+ const sp<ARTPSource> &source) {
+ AssemblyStatus status = addNALUnit(source);
+ if (status == MALFORMED_PACKET) {
+ mAccessUnitDamaged = true;
+ }
+ return status;
+}
+
+void AHEVCAssembler::packetLost() {
+ CHECK(mNextExpectedSeqNoValid);
+ ALOGV("packetLost (expected %d)", mNextExpectedSeqNo);
+
+ ++mNextExpectedSeqNo;
+
+ mAccessUnitDamaged = true;
+}
+
+void AHEVCAssembler::onByeReceived() {
+ sp<AMessage> msg = mNotifyMsg->dup();
+ msg->setInt32("eos", true);
+ msg->post();
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/AHEVCAssembler.h b/media/libstagefright/rtsp/AHEVCAssembler.h
new file mode 100644
index 0000000..cc20622
--- /dev/null
+++ b/media/libstagefright/rtsp/AHEVCAssembler.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef A_HEVC_ASSEMBLER_H_
+
+#define A_HEVC_ASSEMBLER_H_
+
+#include "ARTPAssembler.h"
+
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+struct AHEVCAssembler : public ARTPAssembler {
+ AHEVCAssembler(const sp<AMessage> ¬ify);
+
+protected:
+ virtual ~AHEVCAssembler();
+
+ virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source);
+ virtual void onByeReceived();
+ virtual void packetLost();
+
+private:
+ sp<AMessage> mNotifyMsg;
+
+ uint32_t mAccessUnitRTPTime;
+ bool mNextExpectedSeqNoValid;
+ uint32_t mNextExpectedSeqNo;
+ bool mAccessUnitDamaged;
+ List<sp<ABuffer> > mNALUnits;
+
+ AssemblyStatus addNALUnit(const sp<ARTPSource> &source);
+ void addSingleNALUnit(const sp<ABuffer> &buffer);
+ AssemblyStatus addFragmentedNALUnit(List<sp<ABuffer> > *queue);
+ bool addSingleTimeAggregationPacket(const sp<ABuffer> &buffer);
+
+ void submitAccessUnit();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AHEVCAssembler);
+};
+
+} // namespace android
+
+#endif // A_HEVC_ASSEMBLER_H_
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
index 574bd7a..8f4df8e 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -454,6 +454,17 @@
mFormat->setInt32(kKeyWidth, width);
mFormat->setInt32(kKeyHeight, height);
+ } else if (!strncmp(desc.c_str(), "H265/", 5)) {
+ mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+
+ int32_t width, height;
+ if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
+ width = -1;
+ height = -1;
+ }
+
+ mFormat->setInt32(kKeyWidth, width);
+ mFormat->setInt32(kKeyHeight, height);
} else if (!strncmp(desc.c_str(), "H263-2000/", 10)
|| !strncmp(desc.c_str(), "H263-1998/", 10)) {
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
diff --git a/media/libstagefright/rtsp/ARTPAssembler.cpp b/media/libstagefright/rtsp/ARTPAssembler.cpp
index befc226..52aa3a0 100644
--- a/media/libstagefright/rtsp/ARTPAssembler.cpp
+++ b/media/libstagefright/rtsp/ARTPAssembler.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#define LOG_TAG "ARTPAssembler"
#include "ARTPAssembler.h"
#include <media/stagefright/foundation/ABuffer.h>
@@ -21,12 +22,16 @@
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AMessage.h>
+#include <android-base/properties.h>
+
#include <stdint.h>
namespace android {
ARTPAssembler::ARTPAssembler()
- : mFirstFailureTimeUs(-1) {
+ : mShowQueueCnt(0),
+ mFirstFailureTimeUs(-1) {
+ mShowQueue = android::base::GetBoolProperty("debug.stagefright.rtp", false);
}
void ARTPAssembler::onPacketReceived(const sp<ARTPSource> &source) {
@@ -141,4 +146,15 @@
return accessUnit;
}
+void ARTPAssembler::showCurrentQueue(List<sp<ABuffer> > *queue) {
+ AString temp("Queue elem size : ");
+ List<sp<ABuffer> >::iterator it = queue->begin();
+ while (it != queue->end()) {
+ temp.append((*it)->size());
+ temp.append(" \t");
+ it++;
+ }
+ ALOGD("%s",temp.c_str());
+};
+
} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPAssembler.h b/media/libstagefright/rtsp/ARTPAssembler.h
index 4082d4c..191f08e 100644
--- a/media/libstagefright/rtsp/ARTPAssembler.h
+++ b/media/libstagefright/rtsp/ARTPAssembler.h
@@ -56,6 +56,11 @@
static sp<ABuffer> MakeCompoundFromPackets(
const List<sp<ABuffer> > &frames);
+ void showCurrentQueue(List<sp<ABuffer> > *queue);
+
+ bool mShowQueue;
+ int32_t mShowQueueCnt;
+
private:
int64_t mFirstFailureTimeUs;
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
index 6a4706d..1346c9a 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -30,6 +30,8 @@
#include <media/stagefright/foundation/AString.h>
#include <media/stagefright/foundation/hexdump.h>
+#include <android/multinetwork.h>
+
#include <arpa/inet.h>
#include <sys/socket.h>
@@ -53,6 +55,7 @@
const int64_t ARTPConnection::kSelectTimeoutUs = 1000LL;
struct ARTPConnection::StreamInfo {
+ bool isIPv6;
int mRTPSocket;
int mRTCPSocket;
sp<ASessionDescription> mSessionDesc;
@@ -63,14 +66,19 @@
int64_t mNumRTCPPacketsReceived;
int64_t mNumRTPPacketsReceived;
struct sockaddr_in mRemoteRTCPAddr;
+ struct sockaddr_in6 mRemoteRTCPAddr6;
bool mIsInjected;
+
+ // RTCP Extension for CVO
+ int mCVOExtMap; // will be set to 0 if cvo is not negotiated in sdp
};
ARTPConnection::ARTPConnection(uint32_t flags)
: mFlags(flags),
mPollEventPending(false),
- mLastReceiverReportTimeUs(-1) {
+ mLastReceiverReportTimeUs(-1),
+ mLastBitrateReportTimeUs(-1) {
}
ARTPConnection::~ARTPConnection() {
@@ -145,6 +153,117 @@
TRESPASS();
}
+// static
+void ARTPConnection::MakeRTPSocketPair(
+ int *rtpSocket, int *rtcpSocket, const char *localIp, const char *remoteIp,
+ unsigned localPort, unsigned remotePort, int64_t socketNetwork) {
+ bool isIPv6 = false;
+ if (strchr(localIp, ':') != NULL)
+ isIPv6 = true;
+
+ *rtpSocket = socket(isIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(*rtpSocket, 0);
+
+ bumpSocketBufferSize(*rtpSocket);
+
+ *rtcpSocket = socket(isIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(*rtcpSocket, 0);
+
+ if (socketNetwork != 0) {
+ ALOGD("trying to bind rtp socket(%d) to network(%llu).",
+ *rtpSocket, (unsigned long long)socketNetwork);
+
+ int result = android_setsocknetwork((net_handle_t)socketNetwork, *rtpSocket);
+ if (result != 0) {
+ ALOGW("failed(%d) to bind rtp socket(%d) to network(%llu)",
+ result, *rtpSocket, (unsigned long long)socketNetwork);
+ }
+ result = android_setsocknetwork((net_handle_t)socketNetwork, *rtcpSocket);
+ if (result != 0) {
+ ALOGW("failed(%d) to bind rtcp socket(%d) to network(%llu)",
+ result, *rtcpSocket, (unsigned long long)socketNetwork);
+ }
+ }
+
+ bumpSocketBufferSize(*rtcpSocket);
+
+ struct sockaddr *addr;
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+
+ if (isIPv6) {
+ addr = (struct sockaddr *)&addr6;
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, localIp, &addr6.sin6_addr);
+ addr6.sin6_port = htons((uint16_t)localPort);
+ } else {
+ addr = (struct sockaddr *)&addr4;
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = AF_INET;
+ addr4.sin_addr.s_addr = inet_addr(localIp);
+ addr4.sin_port = htons((uint16_t)localPort);
+ }
+
+ int sockopt = 1;
+ setsockopt(*rtpSocket, SOL_SOCKET, SO_REUSEADDR, (int *)&sockopt, sizeof(sockopt));
+ setsockopt(*rtcpSocket, SOL_SOCKET, SO_REUSEADDR, (int *)&sockopt, sizeof(sockopt));
+
+ int sizeSockSt = isIPv6 ? sizeof(addr6) : sizeof(addr4);
+
+ if (bind(*rtpSocket, addr, sizeSockSt) == 0) {
+ ALOGI("rtp socket successfully binded. addr=%s:%d", localIp, localPort);
+ } else {
+ ALOGE("failed to bind rtp socket addr=%s:%d err=%s", localIp, localPort, strerror(errno));
+ return;
+ }
+
+ if (isIPv6)
+ addr6.sin6_port = htons(localPort + 1);
+ else
+ addr4.sin_port = htons(localPort + 1);
+
+ if (bind(*rtcpSocket, addr, sizeSockSt) == 0) {
+ ALOGI("rtcp socket successfully binded. addr=%s:%d", localIp, localPort + 1);
+ } else {
+ ALOGE("failed to bind rtcp socket addr=%s:%d err=%s", localIp,
+ localPort + 1, strerror(errno));
+ }
+
+ // Re uses addr variable as remote addr.
+ if (isIPv6) {
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, remoteIp, &addr6.sin6_addr);
+ addr6.sin6_port = htons((uint16_t)remotePort);
+ } else {
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = AF_INET;
+ addr4.sin_addr.s_addr = inet_addr(remoteIp);
+ addr4.sin_port = htons((uint16_t)remotePort);
+ }
+ if (connect(*rtpSocket, addr, sizeSockSt) == 0) {
+ ALOGI("rtp socket successfully connected to remote=%s:%d", remoteIp, remotePort);
+ } else {
+ ALOGE("failed to connect rtp socket to remote addr=%s:%d err=%s", remoteIp,
+ remotePort, strerror(errno));
+ return;
+ }
+
+ if (isIPv6)
+ addr6.sin6_port = htons(remotePort + 1);
+ else
+ addr4.sin_port = htons(remotePort + 1);
+
+ if (connect(*rtcpSocket, addr, sizeSockSt) == 0) {
+ ALOGI("rtcp socket successfully connected to remote=%s:%d", remoteIp, remotePort + 1);
+ } else {
+ ALOGE("failed to connect rtcp socket addr=%s:%d err=%s", remoteIp,
+ remotePort + 1, strerror(errno));
+ return;
+ }
+}
+
void ARTPConnection::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatAddStream:
@@ -204,6 +323,19 @@
info->mNumRTCPPacketsReceived = 0;
info->mNumRTPPacketsReceived = 0;
memset(&info->mRemoteRTCPAddr, 0, sizeof(info->mRemoteRTCPAddr));
+ memset(&info->mRemoteRTCPAddr6, 0, sizeof(info->mRemoteRTCPAddr6));
+
+ sp<ASessionDescription> sessionDesc = info->mSessionDesc;
+ info->mCVOExtMap = 0;
+ for (size_t i = 1; i < sessionDesc->countTracks(); ++i) {
+ int32_t cvoExtMap;
+ if (sessionDesc->getCvoExtMap(i, &cvoExtMap)) {
+ info->mCVOExtMap = cvoExtMap;
+ ALOGI("urn:3gpp:video-orientation(cvo) found as extmap:%d", info->mCVOExtMap);
+ } else {
+ ALOGI("urn:3gpp:video-orientation(cvo) not found :%d", info->mCVOExtMap);
+ }
+ }
if (!injected) {
postPollEvent();
@@ -295,6 +427,12 @@
if (err == -ECONNRESET) {
// socket failure, this stream is dead, Jim.
+ sp<AMessage> notify = it->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 400);
+ notify->setInt32("feedback-type", 1);
+ notify->setInt32("sender", it->mSources.valueAt(0)->getSelfID());
+ notify->post();
ALOGW("failed to receive RTP/RTCP datagram.");
it = mStreams.erase(it);
@@ -306,6 +444,8 @@
}
int64_t nowUs = ALooper::GetNowUs();
+ checkRxBitrate(nowUs);
+
if (mLastReceiverReportTimeUs <= 0
|| mLastReceiverReportTimeUs + 5000000LL <= nowUs) {
sp<ABuffer> buffer = new ABuffer(kMaxUDPSize);
@@ -340,13 +480,7 @@
if (buffer->size() > 0) {
ALOGV("Sending RR...");
- ssize_t n;
- do {
- n = sendto(
- s->mRTCPSocket, buffer->data(), buffer->size(), 0,
- (const struct sockaddr *)&s->mRemoteRTCPAddr,
- sizeof(s->mRemoteRTCPAddr));
- } while (n < 0 && errno == EINTR);
+ ssize_t n = send(s, buffer);
if (n <= 0) {
ALOGW("failed to send RTCP receiver report (%s).",
@@ -377,9 +511,21 @@
sp<ABuffer> buffer = new ABuffer(65536);
+ struct sockaddr *pRemoteRTCPAddr;
+ int sizeSockSt;
+ if (s->isIPv6) {
+ pRemoteRTCPAddr = (struct sockaddr *)&s->mRemoteRTCPAddr6;
+ sizeSockSt = sizeof(struct sockaddr_in6);
+ } else {
+ pRemoteRTCPAddr = (struct sockaddr *)&s->mRemoteRTCPAddr;
+ sizeSockSt = sizeof(struct sockaddr_in);
+ }
socklen_t remoteAddrLen =
(!receiveRTP && s->mNumRTCPPacketsReceived == 0)
- ? sizeof(s->mRemoteRTCPAddr) : 0;
+ ? sizeSockSt : 0;
+
+ if (mFlags & kViLTEConnection)
+ remoteAddrLen = 0;
ssize_t nbytes;
do {
@@ -388,8 +534,9 @@
buffer->data(),
buffer->capacity(),
0,
- remoteAddrLen > 0 ? (struct sockaddr *)&s->mRemoteRTCPAddr : NULL,
+ remoteAddrLen > 0 ? pRemoteRTCPAddr : NULL,
remoteAddrLen > 0 ? &remoteAddrLen : NULL);
+ mCumulativeBytes += nbytes;
} while (nbytes < 0 && errno == EINTR);
if (nbytes <= 0) {
@@ -410,6 +557,36 @@
return err;
}
+ssize_t ARTPConnection::send(const StreamInfo *info, const sp<ABuffer> buffer) {
+ struct sockaddr* pRemoteRTCPAddr;
+ int sizeSockSt;
+
+ /* It seems this isIPv6 variable is useless.
+ * We should remove it to prevent confusion */
+ if (info->isIPv6) {
+ pRemoteRTCPAddr = (struct sockaddr *)&info->mRemoteRTCPAddr6;
+ sizeSockSt = sizeof(struct sockaddr_in6);
+ } else {
+ pRemoteRTCPAddr = (struct sockaddr *)&info->mRemoteRTCPAddr;
+ sizeSockSt = sizeof(struct sockaddr_in);
+ }
+
+ if (mFlags & kViLTEConnection) {
+ ALOGV("ViLTE RTCP");
+ pRemoteRTCPAddr = NULL;
+ sizeSockSt = 0;
+ }
+
+ ssize_t n;
+ do {
+ n = sendto(
+ info->mRTCPSocket, buffer->data(), buffer->size(), 0,
+ pRemoteRTCPAddr, sizeSockSt);
+ } while (n < 0 && errno == EINTR);
+
+ return n;
+}
+
status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) {
if (s->mNumRTPPacketsReceived++ == 0) {
sp<AMessage> notify = s->mNotifyMsg->dup();
@@ -431,6 +608,11 @@
return -1;
}
+ if ((data[1] & 0x7f) == 20 /* decimal */) {
+ // Unassigned payload type
+ return -1;
+ }
+
if (data[0] & 0x20) {
// Padding present.
@@ -454,6 +636,7 @@
return -1;
}
+ int32_t cvoDegrees = -1;
if (data[0] & 0x10) {
// Header eXtension present.
@@ -473,6 +656,7 @@
return -1;
}
+ parseRTPExt(s, (const uint8_t *)extensionData, extensionLength, &cvoDegrees);
payloadOffset += 4 + extensionLength;
}
@@ -487,6 +671,8 @@
meta->setInt32("rtp-time", rtpTime);
meta->setInt32("PT", data[1] & 0x7f);
meta->setInt32("M", data[1] >> 7);
+ if (cvoDegrees >= 0)
+ meta->setInt32("cvo", cvoDegrees);
buffer->setInt32Data(u16at(&data[2]));
buffer->setRange(payloadOffset, size - payloadOffset);
@@ -496,11 +682,65 @@
return OK;
}
+status_t ARTPConnection::parseRTPExt(StreamInfo *s,
+ const uint8_t *extHeader, size_t extLen, int32_t *cvoDegrees) {
+ if (extLen < 4)
+ return -1;
+
+ uint16_t header = (extHeader[0] << 8) | (extHeader[1]);
+ bool isOnebyteHeader = false;
+
+ if (header == 0xBEDE) {
+ isOnebyteHeader = true;
+ } else if (header == 0x1000) {
+ ALOGW("parseRTPExt: two-byte header is not implemented yet");
+ return -1;
+ } else {
+ ALOGW("parseRTPExt: can not recognize header");
+ return -1;
+ }
+
+ const uint8_t *extPayload = extHeader + 4;
+ extLen -= 4;
+ size_t offset = 0; //start from first payload of rtp extension.
+ // one-byte header parser
+ while (isOnebyteHeader && offset < extLen) {
+ uint8_t extmapId = extPayload[offset] >> 4;
+ uint8_t length = (extPayload[offset] & 0xF) + 1;
+ offset++;
+
+ // padding case
+ if (extmapId == 0)
+ continue;
+
+ uint8_t data[16]; // maximum length value
+ for (uint8_t j = 0; offset + j <= extLen && j < length; j++) {
+ data[j] = extPayload[offset + j];
+ }
+
+ offset += length;
+
+ if (extmapId == s->mCVOExtMap) {
+ *cvoDegrees = (int32_t)data[0];
+ return OK;
+ }
+ }
+
+ return BAD_VALUE;
+}
+
status_t ARTPConnection::parseRTCP(StreamInfo *s, const sp<ABuffer> &buffer) {
if (s->mNumRTCPPacketsReceived++ == 0) {
sp<AMessage> notify = s->mNotifyMsg->dup();
notify->setInt32("first-rtcp", true);
notify->post();
+
+ ALOGI("send first-rtcp event to upper layer as ImsRxNotice");
+ sp<AMessage> imsNotify = s->mNotifyMsg->dup();
+ imsNotify->setInt32("rtcp-event", 1);
+ imsNotify->setInt32("payload-type", 101);
+ imsNotify->setInt32("feedback-type", 0);
+ imsNotify->post();
}
const uint8_t *data = buffer->data();
@@ -551,8 +791,12 @@
break;
case 205: // TSFB (transport layer specific feedback)
+ parseTSFB(s, data, headerLength);
+ break;
case 206: // PSFB (payload specific feedback)
// hexdump(data, headerLength);
+ parsePSFB(s, data, headerLength);
+ ALOGI("RTCP packet type %u of size %zu", (unsigned)data[1], headerLength);
break;
case 203:
@@ -621,6 +865,144 @@
return 0;
}
+status_t ARTPConnection::parseTSFB(
+ StreamInfo *s, const uint8_t *data, size_t size) {
+ if (size < 12) {
+ // broken packet
+ return -1;
+ }
+
+ uint8_t msgType = data[0] & 0x1f;
+ uint32_t id = u32at(&data[4]);
+
+ const uint8_t *ptr = &data[12];
+ size -= 12;
+
+ using namespace std;
+ size_t FCISize;
+ switch(msgType) {
+ case 1: // Generic NACK
+ {
+ FCISize = 4;
+ while (size >= FCISize) {
+ uint16_t PID = u16at(&ptr[0]); // lost packet RTP number
+ uint16_t BLP = u16at(&ptr[2]); // Bitmask of following Lost Packets
+
+ size -= FCISize;
+ ptr += FCISize;
+
+ AString list_of_losts;
+ list_of_losts.append(PID);
+ for (int i=0 ; i<16 ; i++) {
+ bool is_lost = BLP & (0x1 << i);
+ if (is_lost) {
+ list_of_losts.append(", ");
+ list_of_losts.append(PID + i);
+ }
+ }
+ ALOGI("Opponent losts packet of RTP %s", list_of_losts.c_str());
+ }
+ break;
+ }
+ case 3: // TMMBR
+ case 4: // TMMBN
+ {
+ FCISize = 8;
+ while (size >= FCISize) {
+ uint32_t MxTBR = u32at(&ptr[4]);
+ uint32_t MxTBRExp = MxTBR >> 26;
+ uint32_t MxTBRMantissa = (MxTBR >> 9) & 0x01FFFF;
+ uint32_t overhead = MxTBR & 0x01FF;
+
+ size -= FCISize;
+ ptr += FCISize;
+
+ uint32_t bitRate = (1 << MxTBRExp) * MxTBRMantissa;
+
+ if (msgType == 3)
+ ALOGI("Op -> UE Req Tx bitrate : %d X 2^%d = %d",
+ MxTBRMantissa, MxTBRExp, bitRate);
+ else if (msgType == 4)
+ ALOGI("OP -> UE Noti Rx bitrate : %d X 2^%d = %d",
+ MxTBRMantissa, MxTBRExp, bitRate);
+
+ sp<AMessage> notify = s->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 205);
+ notify->setInt32("feedback-type", msgType);
+ notify->setInt32("sender", id);
+ notify->setInt32("bit-rate", bitRate);
+ notify->post();
+ ALOGI("overhead : %d", overhead);
+ }
+ break;
+ }
+ default:
+ {
+ ALOGI("Not supported TSFB type %d", msgType);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+status_t ARTPConnection::parsePSFB(
+ StreamInfo *s, const uint8_t *data, size_t size) {
+ if (size < 12) {
+ // broken packet
+ return -1;
+ }
+
+ uint8_t msgType = data[0] & 0x1f;
+ uint32_t id = u32at(&data[4]);
+
+ const uint8_t *ptr = &data[12];
+ size -= 12;
+
+ using namespace std;
+ switch(msgType) {
+ case 1: // Picture Loss Indication (PLI)
+ {
+ if (size > 0) {
+ // PLI does not need parameters
+ break;
+ };
+ sp<AMessage> notify = s->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 206);
+ notify->setInt32("feedback-type", msgType);
+ notify->setInt32("sender", id);
+ notify->post();
+ ALOGI("PLI detected.");
+ break;
+ }
+ case 4: // Full Intra Request (FIR)
+ {
+ if (size < 4) {
+ break;
+ }
+ uint32_t requestedId = u32at(&ptr[0]);
+ if (requestedId == (uint32_t)mSelfID) {
+ sp<AMessage> notify = s->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 206);
+ notify->setInt32("feedback-type", msgType);
+ notify->setInt32("sender", id);
+ notify->post();
+ ALOGI("FIR detected.");
+ }
+ break;
+ }
+ default:
+ {
+ ALOGI("Not supported PSFB type %d", msgType);
+ break;
+ }
+ }
+
+ return 0;
+}
sp<ARTPSource> ARTPConnection::findSource(StreamInfo *info, uint32_t srcId) {
sp<ARTPSource> source;
ssize_t index = info->mSources.indexOfKey(srcId);
@@ -630,6 +1012,8 @@
source = new ARTPSource(
srcId, info->mSessionDesc, info->mIndex, info->mNotifyMsg);
+ source->setSelfID(mSelfID);
+ source->setMinMaxBitrate(mMinBitrate, mMaxBitrate);
info->mSources.add(srcId, source);
} else {
source = info->mSources.valueAt(index);
@@ -645,6 +1029,78 @@
msg->post();
}
+void ARTPConnection::setSelfID(const uint32_t selfID) {
+ mSelfID = selfID;
+}
+
+void ARTPConnection::setMinMaxBitrate(int32_t min, int32_t max) {
+ mMinBitrate = min;
+ mMaxBitrate = max;
+}
+
+void ARTPConnection::checkRxBitrate(int64_t nowUs) {
+ if (mLastBitrateReportTimeUs <= 0) {
+ mCumulativeBytes = 0;
+ mLastBitrateReportTimeUs = nowUs;
+ }
+ else if (mLastBitrateReportTimeUs + 1000000ll <= nowUs) {
+ int32_t timeDiff = (nowUs - mLastBitrateReportTimeUs) / 1000000ll;
+ int32_t bitrate = mCumulativeBytes * 8 / timeDiff;
+ ALOGI("Actual Rx bitrate : %d bits/sec", bitrate);
+
+ sp<ABuffer> buffer = new ABuffer(kMaxUDPSize);
+ List<StreamInfo>::iterator it = mStreams.begin();
+ while (it != mStreams.end()) {
+ StreamInfo *s = &*it;
+ if (s->mIsInjected) {
+ ++it;
+ continue;
+ }
+
+ if (s->mNumRTCPPacketsReceived == 0) {
+ // We have never received any RTCP packets on this stream,
+ // we don't even know where to send a report.
+ ++it;
+ continue;
+ }
+
+ buffer->setRange(0, 0);
+
+ for (size_t i = 0; i < s->mSources.size(); ++i) {
+ sp<ARTPSource> source = s->mSources.valueAt(i);
+ source->setBitrateData(bitrate, nowUs);
+ source->setTargetBitrate();
+ source->addTMMBR(buffer);
+ if (source->isNeedToDowngrade()) {
+ sp<AMessage> notify = s->mNotifyMsg->dup();
+ notify->setInt32("rtcp-event", 1);
+ notify->setInt32("payload-type", 400);
+ notify->setInt32("feedback-type", 1);
+ notify->setInt32("sender", source->getSelfID());
+ notify->post();
+ }
+ }
+ if (buffer->size() > 0) {
+ ALOGV("Sending TMMBR...");
+
+ ssize_t n = send(s, buffer);
+
+ if (n <= 0) {
+ ALOGW("failed to send RTCP TMMBR (%s).",
+ n == 0 ? "connection gone" : strerror(errno));
+
+ it = mStreams.erase(it);
+ continue;
+ }
+
+ CHECK_EQ(n, (ssize_t)buffer->size());
+ }
+ ++it;
+ }
+ mCumulativeBytes = 0;
+ mLastBitrateReportTimeUs = nowUs;
+ }
+}
void ARTPConnection::onInjectPacket(const sp<AMessage> &msg) {
int32_t index;
CHECK(msg->findInt32("index", &index));
@@ -672,4 +1128,3 @@
}
} // namespace android
-
diff --git a/media/libstagefright/rtsp/ARTPConnection.h b/media/libstagefright/rtsp/ARTPConnection.h
index d5f7c2e..712eec5 100644
--- a/media/libstagefright/rtsp/ARTPConnection.h
+++ b/media/libstagefright/rtsp/ARTPConnection.h
@@ -30,6 +30,7 @@
struct ARTPConnection : public AHandler {
enum Flags {
kRegularlyRequestFIR = 2,
+ kViLTEConnection = 4,
};
explicit ARTPConnection(uint32_t flags = 0);
@@ -44,11 +45,21 @@
void injectPacket(int index, const sp<ABuffer> &buffer);
+ void setSelfID(const uint32_t selfID);
+ void setMinMaxBitrate(int32_t min, int32_t max);
+
// Creates a pair of UDP datagram sockets bound to adjacent ports
// (the rtpSocket is bound to an even port, the rtcpSocket to the
// next higher port).
static void MakePortPair(
int *rtpSocket, int *rtcpSocket, unsigned *rtpPort);
+ // Creates a pair of UDP datagram sockets bound to assigned ip and
+ // ports (the rtpSocket is bound to an even port, the rtcpSocket
+ // to the next higher port).
+ static void MakeRTPSocketPair(
+ int *rtpSocket, int *rtcpSocket,
+ const char *localIp, const char *remoteIp,
+ unsigned localPort, unsigned remotePort, int64_t socketNetwork = 0);
protected:
virtual ~ARTPConnection();
@@ -71,18 +82,30 @@
bool mPollEventPending;
int64_t mLastReceiverReportTimeUs;
+ int64_t mLastBitrateReportTimeUs;
+
+ int32_t mSelfID;
+
+ int32_t mMinBitrate;
+ int32_t mMaxBitrate;
+ int32_t mCumulativeBytes;
void onAddStream(const sp<AMessage> &msg);
void onRemoveStream(const sp<AMessage> &msg);
void onPollStreams();
void onInjectPacket(const sp<AMessage> &msg);
void onSendReceiverReports();
+ void checkRxBitrate(int64_t nowUs);
status_t receive(StreamInfo *info, bool receiveRTP);
+ ssize_t send(const StreamInfo *info, const sp<ABuffer> buffer);
status_t parseRTP(StreamInfo *info, const sp<ABuffer> &buffer);
+ status_t parseRTPExt(StreamInfo *s, const uint8_t *extData, size_t extLen, int32_t *cvoDegrees);
status_t parseRTCP(StreamInfo *info, const sp<ABuffer> &buffer);
status_t parseSR(StreamInfo *info, const uint8_t *data, size_t size);
+ status_t parseTSFB(StreamInfo *info, const uint8_t *data, size_t size);
+ status_t parsePSFB(StreamInfo *info, const uint8_t *data, size_t size);
status_t parseBYE(StreamInfo *info, const uint8_t *data, size_t size);
sp<ARTPSource> findSource(StreamInfo *info, uint32_t id);
diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp
index f5f8128..bbe9d94 100644
--- a/media/libstagefright/rtsp/ARTPSource.cpp
+++ b/media/libstagefright/rtsp/ARTPSource.cpp
@@ -22,6 +22,7 @@
#include "AAMRAssembler.h"
#include "AAVCAssembler.h"
+#include "AHEVCAssembler.h"
#include "AH263Assembler.h"
#include "AMPEG2TSAssembler.h"
#include "AMPEG4AudioAssembler.h"
@@ -35,13 +36,17 @@
namespace android {
-static const uint32_t kSourceID = 0xdeadbeef;
+static uint32_t kSourceID = 0xdeadbeef;
ARTPSource::ARTPSource(
uint32_t id,
const sp<ASessionDescription> &sessionDesc, size_t index,
const sp<AMessage> ¬ify)
- : mID(id),
+ : mFirstSeqNumber(0),
+ mFirstRtpTime(0),
+ mFirstSysTime(0),
+ mClockRate(0),
+ mID(id),
mHighestSeqNumber(0),
mPrevExpected(0),
mBaseSeqNumber(0),
@@ -61,6 +66,9 @@
if (!strncmp(desc.c_str(), "H264/", 5)) {
mAssembler = new AAVCAssembler(notify);
mIssueFIRRequests = true;
+ } else if (!strncmp(desc.c_str(), "H265/", 5)) {
+ mAssembler = new AHEVCAssembler(notify);
+ mIssueFIRRequests = true;
} else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) {
mAssembler = new AMPEG4AudioAssembler(notify, params);
} else if (!strncmp(desc.c_str(), "H263-1998/", 10)
@@ -112,9 +120,16 @@
bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) {
uint32_t seqNum = (uint32_t)buffer->int32Data();
- if (mNumBuffersReceived++ == 0) {
+ if (mNumBuffersReceived++ == 0 && mFirstSysTime == 0) {
+ int32_t firstRtpTime;
+ CHECK(buffer->meta()->findInt32("rtp-time", &firstRtpTime));
+ mFirstSysTime = ALooper::GetNowUs();
mHighestSeqNumber = seqNum;
mBaseSeqNumber = seqNum;
+ mFirstRtpTime = firstRtpTime;
+ ALOGV("first-rtp arrived: first-rtp-time=%d, sys-time=%lld, seq-num=%u",
+ mFirstRtpTime, (long long)mFirstSysTime, mHighestSeqNumber);
+ mClockRate = 90000;
mQueue.push_back(buffer);
return true;
}
@@ -306,6 +321,97 @@
buffer->setRange(buffer->offset(), buffer->size() + 32);
}
+void ARTPSource::addTMMBR(const sp<ABuffer> &buffer) {
+ if (buffer->size() + 20 > buffer->capacity()) {
+ ALOGW("RTCP buffer too small to accommodate RR.");
+ return;
+ }
+
+ int32_t targetBitrate = mQualManager.getTargetBitrate();
+ if (targetBitrate <= 0)
+ return;
+
+ uint8_t *data = buffer->data() + buffer->size();
+
+ data[0] = 0x80 | 3; // TMMBR
+ data[1] = 205; // TSFB
+ data[2] = 0;
+ data[3] = 4; // total (4+1) * sizeof(int32_t) = 20 bytes
+ data[4] = kSourceID >> 24;
+ data[5] = (kSourceID >> 16) & 0xff;
+ data[6] = (kSourceID >> 8) & 0xff;
+ data[7] = kSourceID & 0xff;
+
+ *(int32_t*)(&data[8]) = 0; // 4 bytes blank
+
+ data[12] = mID >> 24;
+ data[13] = (mID >> 16) & 0xff;
+ data[14] = (mID >> 8) & 0xff;
+ data[15] = mID & 0xff;
+
+ int32_t exp, mantissa;
+
+ // Round off to the nearest 2^4th
+ ALOGI("UE -> Op Req Rx bitrate : %d ", targetBitrate & 0xfffffff0);
+ for (exp=4 ; exp < 32 ; exp++)
+ if (((targetBitrate >> exp) & 0x01) != 0)
+ break;
+ mantissa = targetBitrate >> exp;
+
+ data[16] = ((exp << 2) & 0xfc) | ((mantissa & 0x18000) >> 15);
+ data[17] = (mantissa & 0x07f80) >> 7;
+ data[18] = (mantissa & 0x0007f) << 1;
+ data[19] = 40; // 40 bytes overhead;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 20);
+}
+
+uint32_t ARTPSource::getSelfID() {
+ return kSourceID;
+}
+void ARTPSource::setSelfID(const uint32_t selfID) {
+ kSourceID = selfID;
+}
+
+void ARTPSource::setMinMaxBitrate(int32_t min, int32_t max) {
+ mQualManager.setMinMaxBitrate(min, max);
+}
+
+void ARTPSource::setBitrateData(int32_t bitrate, int64_t time) {
+ mQualManager.setBitrateData(bitrate, time);
+}
+
+void ARTPSource::setTargetBitrate() {
+ uint8_t fraction = 0;
+
+ // According to appendix A.3 in RFC 3550
+ uint32_t expected = mHighestSeqNumber - mBaseSeqNumber + 1;
+ int64_t intervalExpected = expected - mPrevExpected;
+ int64_t intervalReceived = mNumBuffersReceived - mPrevNumBuffersReceived;
+ int64_t intervalPacketLost = intervalExpected - intervalReceived;
+
+ ALOGI("UID %p expectedPkts %lld lostPkts %lld", this, (long long)intervalExpected, (long long)intervalPacketLost);
+
+ if (intervalPacketLost < 0 || intervalExpected == 0)
+ fraction = 0;
+ else if (intervalExpected <= intervalPacketLost)
+ fraction = 255;
+ else
+ fraction = (intervalPacketLost << 8) / intervalExpected;
+
+ mQualManager.setTargetBitrate(fraction, ALooper::GetNowUs(), intervalExpected < 5);
+}
+
+bool ARTPSource::isNeedToReport() {
+ int64_t intervalReceived = mNumBuffersReceived - mPrevNumBuffersReceived;
+ return (intervalReceived > 0) ? true : false;
+}
+
+bool ARTPSource::isNeedToDowngrade() {
+ return mQualManager.isNeedToDowngrade();
+}
+
+void ARTPSource::noticeAbandonBuffer(int cnt) {
+ mNumBuffersReceived -= cnt;
+}
} // namespace android
-
-
diff --git a/media/libstagefright/rtsp/ARTPSource.h b/media/libstagefright/rtsp/ARTPSource.h
index f44e83f..652e753 100644
--- a/media/libstagefright/rtsp/ARTPSource.h
+++ b/media/libstagefright/rtsp/ARTPSource.h
@@ -23,6 +23,7 @@
#include <media/stagefright/foundation/ABase.h>
#include <utils/List.h>
#include <utils/RefBase.h>
+#include <QualManager.h>
namespace android {
@@ -45,8 +46,25 @@
void addReceiverReport(const sp<ABuffer> &buffer);
void addFIR(const sp<ABuffer> &buffer);
+ void addTMMBR(const sp<ABuffer> &buffer);
+ uint32_t getSelfID();
+ void setSelfID(const uint32_t selfID);
+ void setMinMaxBitrate(int32_t min, int32_t max);
+ void setBitrateData(int32_t bitrate, int64_t time);
+ void setTargetBitrate();
+
+ bool isNeedToReport();
+ bool isNeedToDowngrade();
+
+ void noticeAbandonBuffer(int cnt=1);
+
+ int32_t mFirstSeqNumber;
+ int32_t mFirstRtpTime;
+ int64_t mFirstSysTime;
+ int32_t mClockRate;
private:
+
uint32_t mID;
uint32_t mHighestSeqNumber;
uint32_t mPrevExpected;
@@ -66,6 +84,8 @@
sp<AMessage> mNotify;
+ QualManager mQualManager;
+
bool queuePacket(const sp<ABuffer> &buffer);
DISALLOW_EVIL_CONSTRUCTORS(ARTPSource);
diff --git a/media/libstagefright/rtsp/ARTPWriter.cpp b/media/libstagefright/rtsp/ARTPWriter.cpp
index 58d6086..70d34de 100644
--- a/media/libstagefright/rtsp/ARTPWriter.cpp
+++ b/media/libstagefright/rtsp/ARTPWriter.cpp
@@ -35,10 +35,29 @@
#define PT 97
#define PT_STR "97"
+#define H264_NALU_MASK 0x1F
+#define H264_NALU_SPS 0x7
+#define H264_NALU_PPS 0x8
+#define H264_NALU_IFRAME 0x5
+#define H264_NALU_PFRAME 0x1
+
+#define H265_NALU_MASK 0x3F
+#define H265_NALU_VPS 0x40
+#define H265_NALU_SPS 0x42
+#define H265_NALU_PPS 0x44
+
+#define UDP_HEADER_SIZE 8
+#define RTP_HEADER_SIZE 12
+#define RTP_HEADER_EXT_SIZE 1
+#define RTP_FU_HEADER_SIZE 2
+#define RTP_PAYLOAD_ROOM_SIZE 140
+
+
namespace android {
// static const size_t kMaxPacketSize = 65507; // maximum payload in UDP over IP
static const size_t kMaxPacketSize = 1500;
+static char kCNAME[255] = "someone@somewhere";
static int UniformRand(int limit) {
return ((double)rand() * limit) / RAND_MAX;
@@ -50,13 +69,16 @@
mLooper(new ALooper),
mReflector(new AHandlerReflector<ARTPWriter>(this)) {
CHECK_GE(fd, 0);
+ mIsIPv6 = false;
mLooper->setName("rtp writer");
mLooper->registerHandler(mReflector);
mLooper->start();
- mSocket = socket(AF_INET, SOCK_DGRAM, 0);
- CHECK_GE(mSocket, 0);
+ mRTPSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mRTPSocket, 0);
+ mRTCPSocket = socket(AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mRTCPSocket, 0);
memset(mRTPAddr.sin_zero, 0, sizeof(mRTPAddr.sin_zero));
mRTPAddr.sin_family = AF_INET;
@@ -72,6 +94,42 @@
mRTCPAddr = mRTPAddr;
mRTCPAddr.sin_port = htons(ntohs(mRTPAddr.sin_port) | 1);
+ mSPSBuf = NULL;
+ mPPSBuf = NULL;
+
+#if LOG_TO_FILES
+ mRTPFd = open(
+ "/data/misc/rtpout.bin",
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0644);
+ CHECK_GE(mRTPFd, 0);
+
+ mRTCPFd = open(
+ "/data/misc/rtcpout.bin",
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0644);
+ CHECK_GE(mRTCPFd, 0);
+#endif
+}
+
+ARTPWriter::ARTPWriter(int fd, String8& localIp, int localPort, String8& remoteIp,
+ int remotePort, uint32_t seqNo)
+ : mFlags(0),
+ mFd(dup(fd)),
+ mLooper(new ALooper),
+ mReflector(new AHandlerReflector<ARTPWriter>(this)) {
+ CHECK_GE(fd, 0);
+ mIsIPv6 = false;
+
+ mLooper->setName("rtp writer");
+ mLooper->registerHandler(mReflector);
+ mLooper->start();
+
+ makeSocketPairAndBind(localIp, localPort, remoteIp , remotePort);
+ mSPSBuf = NULL;
+ mPPSBuf = NULL;
+
+ mSeqNo = seqNo;
#if LOG_TO_FILES
mRTPFd = open(
@@ -89,6 +147,16 @@
}
ARTPWriter::~ARTPWriter() {
+ if (mSPSBuf != NULL) {
+ mSPSBuf->release();
+ mSPSBuf = NULL;
+ }
+
+ if (mPPSBuf != NULL) {
+ mPPSBuf->release();
+ mPPSBuf = NULL;
+ }
+
#if LOG_TO_FILES
close(mRTCPFd);
mRTCPFd = -1;
@@ -97,8 +165,11 @@
mRTPFd = -1;
#endif
- close(mSocket);
- mSocket = -1;
+ close(mRTPSocket);
+ mRTPSocket = -1;
+
+ close(mRTCPSocket);
+ mRTCPSocket = -1;
close(mFd);
mFd = -1;
@@ -114,28 +185,61 @@
return (mFlags & kFlagEOS) != 0;
}
-status_t ARTPWriter::start(MetaData * /* params */) {
+status_t ARTPWriter::start(MetaData * params) {
Mutex::Autolock autoLock(mLock);
if (mFlags & kFlagStarted) {
return INVALID_OPERATION;
}
mFlags &= ~kFlagEOS;
- mSourceID = rand();
- mSeqNo = UniformRand(65536);
- mRTPTimeBase = rand();
+ if (mSourceID == 0)
+ mSourceID = rand();
+ if (mSeqNo == 0)
+ mSeqNo = UniformRand(65536);
+ mRTPTimeBase = 0;
mNumRTPSent = 0;
mNumRTPOctetsSent = 0;
mLastRTPTime = 0;
mLastNTPTime = 0;
+ mOpponentID = 0;
+ mBitrate = 192000;
mNumSRsSent = 0;
+ mRTPCVOExtMap = -1;
+ mRTPCVODegrees = 0;
+ mRTPSockNetwork = 0;
const char *mime;
CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+ int32_t selfID = 0;
+ if (params->findInt32(kKeySelfID, &selfID))
+ mSourceID = selfID;
+
+ int32_t payloadType = 0;
+ if (params->findInt32(kKeyPayloadType, &payloadType))
+ mPayloadType = payloadType;
+
+ int32_t rtpExtMap = 0;
+ if (params->findInt32(kKeyRtpExtMap, &rtpExtMap))
+ mRTPCVOExtMap = rtpExtMap;
+
+ int32_t rtpCVODegrees = 0;
+ if (params->findInt32(kKeyRtpCvoDegrees, &rtpCVODegrees))
+ mRTPCVODegrees = rtpCVODegrees;
+
+ int32_t dscp = 0;
+ if (params->findInt32(kKeyRtpDscp, &dscp))
+ updateSocketDscp(dscp);
+
+ int64_t sockNetwork = 0;
+ if (params->findInt64(kKeySocketNetwork, &sockNetwork))
+ updateSocketNetwork(sockNetwork);
+
mMode = INVALID;
if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
mMode = H264;
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) {
+ mMode = H265;
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_H263)) {
mMode = H263;
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
@@ -173,9 +277,12 @@
return OK;
}
-static void StripStartcode(MediaBufferBase *buffer) {
+// return size of SPS if there is more NAL unit found following to SPS.
+static uint32_t StripStartcode(MediaBufferBase *buffer) {
+ uint32_t nalSize = 0;
+
if (buffer->range_length() < 4) {
- return;
+ return 0;
}
const uint8_t *ptr =
@@ -185,13 +292,65 @@
buffer->set_range(
buffer->range_offset() + 4, buffer->range_length() - 4);
}
+
+ ptr = (const uint8_t *)buffer->data() + buffer->range_offset();
+
+ if (buffer->range_length() > 0 && (*ptr & H264_NALU_MASK) == H264_NALU_SPS) {
+ for (uint32_t i = 1; i + 4 <= buffer->range_length(); i++) {
+
+ if (!memcmp(ptr + i, "\x00\x00\x00\x01", 4)) {
+ // Now, we found one more NAL unit in the media buffer.
+ // Mostly, it will be a PPS.
+ nalSize = i;
+ ALOGV("SPS found. size=%d", nalSize);
+ }
+ }
+ }
+
+ return nalSize;
+}
+
+static void SpsPpsParser(MediaBufferBase *mediaBuffer,
+ MediaBufferBase **spsBuffer, MediaBufferBase **ppsBuffer, uint32_t spsSize) {
+
+ if (mediaBuffer == NULL || mediaBuffer->range_length() < 4)
+ return;
+
+ if ((*spsBuffer) != NULL) {
+ (*spsBuffer)->release();
+ (*spsBuffer) = NULL;
+ }
+
+ if ((*ppsBuffer) != NULL) {
+ (*ppsBuffer)->release();
+ (*ppsBuffer) = NULL;
+ }
+
+ // we got sps/pps but startcode of sps is striped.
+ (*spsBuffer) = MediaBufferBase::Create(spsSize);
+ memcpy((*spsBuffer)->data(),
+ (const uint8_t *)mediaBuffer->data() + mediaBuffer->range_offset(),
+ spsSize);
+
+ int32_t ppsSize = mediaBuffer->range_length() - spsSize - 4 /*startcode*/;
+ if (ppsSize > 0) {
+ (*ppsBuffer) = MediaBufferBase::Create(ppsSize);
+ ALOGV("PPS found. size=%d", (int)ppsSize);
+ mediaBuffer->set_range(mediaBuffer->range_offset() + spsSize + 4 /*startcode*/,
+ mediaBuffer->range_length() - spsSize - 4 /*startcode*/);
+ memcpy((*ppsBuffer)->data(),
+ (const uint8_t *)mediaBuffer->data() + mediaBuffer->range_offset(),
+ ppsSize);
+ }
}
void ARTPWriter::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatStart:
{
- CHECK_EQ(mSource->start(), (status_t)OK);
+ sp<MetaData> meta = new MetaData();
+ meta->setInt64(kKeyTime, 10ll);
+ CHECK_EQ(mSource->start(meta.get()), (status_t)OK);
#if 0
if (mMode == H264) {
@@ -264,6 +423,18 @@
}
}
+void ARTPWriter::setTMMBNInfo(uint32_t opponentID, uint32_t bitrate) {
+ mOpponentID = opponentID;
+ mBitrate = bitrate;
+
+ sp<ABuffer> buffer = new ABuffer(65536);
+ buffer->setRange(0, 0);
+
+ addTMMBN(buffer);
+
+ send(buffer, true /* isRTCP */);
+}
+
void ARTPWriter::onRead(const sp<AMessage> &msg) {
MediaBufferBase *mediaBuf;
status_t err = mSource->read(&mediaBuf);
@@ -280,8 +451,15 @@
ALOGV("read buffer of size %zu", mediaBuf->range_length());
if (mMode == H264) {
+ uint32_t spsSize = 0;
+ if ((spsSize = StripStartcode(mediaBuf)) > 0) {
+ SpsPpsParser(mediaBuf, &mSPSBuf, &mPPSBuf, spsSize);
+ } else {
+ sendAVCData(mediaBuf);
+ }
+ } else if (mMode == H265) {
StripStartcode(mediaBuf);
- sendAVCData(mediaBuf);
+ sendHEVCData(mediaBuf);
} else if (mMode == H263) {
sendH263Data(mediaBuf);
} else if (mMode == AMR_NB || mMode == AMR_WB) {
@@ -309,12 +487,29 @@
}
void ARTPWriter::send(const sp<ABuffer> &buffer, bool isRTCP) {
- ssize_t n = sendto(
- mSocket, buffer->data(), buffer->size(), 0,
- (const struct sockaddr *)(isRTCP ? &mRTCPAddr : &mRTPAddr),
- sizeof(mRTCPAddr));
+ int sizeSockSt;
+ struct sockaddr *remAddr;
- CHECK_EQ(n, (ssize_t)buffer->size());
+ if (mIsIPv6) {
+ sizeSockSt = sizeof(struct sockaddr_in6);
+ if (isRTCP)
+ remAddr = (struct sockaddr *)&mRTCPAddr6;
+ else
+ remAddr = (struct sockaddr *)&mRTPAddr6;
+ } else {
+ sizeSockSt = sizeof(struct sockaddr_in);
+ if (isRTCP)
+ remAddr = (struct sockaddr *)&mRTCPAddr;
+ else
+ remAddr = (struct sockaddr *)&mRTPAddr;
+ }
+
+ ssize_t n = sendto(isRTCP ? mRTCPSocket : mRTPSocket,
+ buffer->data(), buffer->size(), 0, remAddr, sizeSockSt);
+
+ if (n != (ssize_t)buffer->size()) {
+ ALOGW("packets can not be sent. ret=%d, buf=%d", (int)n, (int)buffer->size());
+ }
#if LOG_TO_FILES
int fd = isRTCP ? mRTCPFd : mRTPFd;
@@ -379,7 +574,6 @@
data[offset++] = 1; // CNAME
- static const char *kCNAME = "someone@somewhere";
data[offset++] = strlen(kCNAME);
memcpy(&data[offset], kCNAME, strlen(kCNAME));
@@ -416,9 +610,52 @@
buffer->setRange(buffer->offset(), buffer->size() + offset);
}
+void ARTPWriter::addTMMBN(const sp<ABuffer> &buffer) {
+ if (buffer->size() + 20 > buffer->capacity()) {
+ ALOGW("RTCP buffer too small to accommodate SR.");
+ return;
+ }
+ if (mOpponentID == 0)
+ return;
+
+ uint8_t *data = buffer->data() + buffer->size();
+
+ data[0] = 0x80 | 4; // TMMBN
+ data[1] = 205; // TSFB
+ data[2] = 0;
+ data[3] = 4; // total (4+1) * sizeof(int32_t) = 20 bytes
+ data[4] = mSourceID >> 24;
+ data[5] = (mSourceID >> 16) & 0xff;
+ data[6] = (mSourceID >> 8) & 0xff;
+ data[7] = mSourceID & 0xff;
+
+ *(int32_t*)(&data[8]) = 0; // 4 bytes blank
+
+ data[12] = mOpponentID >> 24;
+ data[13] = (mOpponentID >> 16) & 0xff;
+ data[14] = (mOpponentID >> 8) & 0xff;
+ data[15] = mOpponentID & 0xff;
+
+ int32_t exp, mantissa;
+
+ // Round off to the nearest 2^4th
+ ALOGI("UE -> Op Noti Tx bitrate : %d ", mBitrate & 0xfffffff0);
+ for (exp=4 ; exp < 32 ; exp++)
+ if (((mBitrate >> exp) & 0x01) != 0)
+ break;
+ mantissa = mBitrate >> exp;
+
+ data[16] = ((exp << 2) & 0xfc) | ((mantissa & 0x18000) >> 15);
+ data[17] = (mantissa & 0x07f80) >> 7;
+ data[18] = (mantissa & 0x0007f) << 1;
+ data[19] = 40; // 40 bytes overhead;
+
+ buffer->setRange(buffer->offset(), buffer->size() + 20);
+}
+
// static
uint64_t ARTPWriter::GetNowNTP() {
- uint64_t nowUs = ALooper::GetNowUs();
+ uint64_t nowUs = systemTime(SYSTEM_TIME_REALTIME) / 1000ll;
nowUs += ((70LL * 365 + 17) * 24) * 60 * 60 * 1000000LL;
@@ -463,7 +700,7 @@
sdp.append("m=audio ");
}
- sdp.append(AStringPrintf("%d", ntohs(mRTPAddr.sin_port)));
+ sdp.append(AStringPrintf("%d", mIsIPv6 ? ntohs(mRTPAddr6.sin6_port) : ntohs(mRTPAddr.sin_port)));
sdp.append(
" RTP/AVP " PT_STR "\r\n"
"b=AS 320000\r\n"
@@ -569,24 +806,49 @@
send(buffer, true /* isRTCP */);
}
-void ARTPWriter::sendAVCData(MediaBufferBase *mediaBuf) {
+void ARTPWriter::sendSPSPPSIfIFrame(MediaBufferBase *mediaBuf, int64_t timeUs) {
+ const uint8_t *mediaData =
+ (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+ if (mediaBuf->range_length() == 0
+ || (mediaData[0] & H264_NALU_MASK) != H264_NALU_IFRAME)
+ return;
+
+ if (mSPSBuf != NULL) {
+ mSPSBuf->meta_data().setInt64(kKeyTime, timeUs);
+ mSPSBuf->meta_data().setInt32(kKeySps, 1);
+ sendAVCData(mSPSBuf);
+ }
+
+ if (mPPSBuf != NULL) {
+ mPPSBuf->meta_data().setInt64(kKeyTime, timeUs);
+ mPPSBuf->meta_data().setInt32(kKeyPps, 1);
+ sendAVCData(mPPSBuf);
+ }
+}
+
+void ARTPWriter::sendHEVCData(MediaBufferBase *mediaBuf) {
// 12 bytes RTP header + 2 bytes for the FU-indicator and FU-header.
CHECK_GE(kMaxPacketSize, 12u + 2u);
int64_t timeUs;
CHECK(mediaBuf->meta_data().findInt64(kKeyTime, &timeUs));
- uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100LL);
+ sendSPSPPSIfIFrame(mediaBuf, timeUs);
+
+ uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll);
const uint8_t *mediaData =
(const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
- if (mediaBuf->range_length() + 12 <= buffer->capacity()) {
+
+ if (mediaBuf->range_length() + UDP_HEADER_SIZE + RTP_HEADER_SIZE + RTP_PAYLOAD_ROOM_SIZE
+ <= buffer->capacity()) {
// The data fits into a single packet
uint8_t *data = buffer->data();
data[0] = 0x80;
- data[1] = (1 << 7) | PT; // M-bit
+ data[1] = (1 << 7) | mPayloadType; // M-bit
data[2] = (mSeqNo >> 8) & 0xff;
data[3] = mSeqNo & 0xff;
data[4] = rtpTime >> 24;
@@ -611,21 +873,24 @@
} else {
// FU-A
- unsigned nalType = mediaData[0];
- size_t offset = 1;
+ unsigned nalType = (mediaData[0] >> 1) & H265_NALU_MASK;
+ ALOGV("H265 nalType 0x%x, data[0]=0x%x", nalType, mediaData[0]);
+ size_t offset = 2; //H265 payload header is 16 bit.
bool firstPacket = true;
while (offset < mediaBuf->range_length()) {
size_t size = mediaBuf->range_length() - offset;
bool lastPacket = true;
- if (size + 12 + 2 > buffer->capacity()) {
+ if (size + UDP_HEADER_SIZE + RTP_HEADER_SIZE + RTP_FU_HEADER_SIZE +
+ RTP_PAYLOAD_ROOM_SIZE > buffer->capacity()) {
lastPacket = false;
- size = buffer->capacity() - 12 - 2;
+ size = buffer->capacity() - UDP_HEADER_SIZE - RTP_HEADER_SIZE -
+ RTP_FU_HEADER_SIZE - RTP_PAYLOAD_ROOM_SIZE;
}
uint8_t *data = buffer->data();
data[0] = 0x80;
- data[1] = (lastPacket ? (1 << 7) : 0x00) | PT; // M-bit
+ data[1] = (lastPacket ? (1 << 7) : 0x00) | mPayloadType; // M-bit
data[2] = (mSeqNo >> 8) & 0xff;
data[3] = mSeqNo & 0xff;
data[4] = rtpTime >> 24;
@@ -637,18 +902,39 @@
data[10] = (mSourceID >> 8) & 0xff;
data[11] = mSourceID & 0xff;
- data[12] = 28 | (nalType & 0xe0);
+ /* H265 payload header is 16 bit
+ 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |F| Type | Layer ID | TID |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ ALOGV("H265 payload header 0x%x %x", mediaData[0], mediaData[1]);
+ // excludes Type from 1st byte of H265 payload header.
+ data[12] = mediaData[0] & 0x81;
+ // fills Type as FU (49 == 0x31)
+ data[12] = data[12] | (0x31 << 1);
+ data[13] = mediaData[1];
+
+ ALOGV("H265 FU header 0x%x %x", data[12], data[13]);
CHECK(!firstPacket || !lastPacket);
+ /*
+ FU INDICATOR HDR
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+
+ |S|E| Type |
+ +-+-+-+-+-+-+-+
+ */
- data[13] =
+ data[14] =
(firstPacket ? 0x80 : 0x00)
| (lastPacket ? 0x40 : 0x00)
- | (nalType & 0x1f);
+ | (nalType & H265_NALU_MASK);
+ ALOGV("H265 FU indicator 0x%x", data[14]);
- memcpy(&data[14], &mediaData[offset], size);
+ memcpy(&data[15], &mediaData[offset], size);
- buffer->setRange(0, 14 + size);
+ buffer->setRange(0, 15 + size);
send(buffer, false /* isRTCP */);
@@ -663,6 +949,170 @@
mLastRTPTime = rtpTime;
mLastNTPTime = GetNowNTP();
+
+}
+
+void ARTPWriter::sendAVCData(MediaBufferBase *mediaBuf) {
+ // 12 bytes RTP header + 2 bytes for the FU-indicator and FU-header.
+ CHECK_GE(kMaxPacketSize, 12u + 2u);
+
+ int64_t timeUs;
+ CHECK(mediaBuf->meta_data().findInt64(kKeyTime, &timeUs));
+
+ sendSPSPPSIfIFrame(mediaBuf, timeUs);
+
+ uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100LL);
+
+ const uint8_t *mediaData =
+ (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset();
+
+ int32_t sps, pps;
+ bool isSpsPps = false;
+ if (mediaBuf->meta_data().findInt32(kKeySps, &sps) ||
+ mediaBuf->meta_data().findInt32(kKeyPps, &pps)) {
+ isSpsPps = true;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(kMaxPacketSize);
+ if (mediaBuf->range_length() + UDP_HEADER_SIZE + RTP_HEADER_SIZE + RTP_PAYLOAD_ROOM_SIZE
+ <= buffer->capacity()) {
+ // The data fits into a single packet
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ if (mRTPCVOExtMap > 0)
+ data[0] |= 0x10;
+ if (isSpsPps)
+ data[1] = mPayloadType; // Marker bit should not be set in case of sps/pps
+ else
+ data[1] = (1 << 7) | mPayloadType;
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+ int rtpExtIndex = 0;
+ if (mRTPCVOExtMap > 0) {
+ /*
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 0xBE | 0xDE | length=3 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ID | L=0 | data | ID | L=1 | data...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ...data | 0 (pad) | 0 (pad) | ID | L=3 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | data |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ In the one-byte header form of extensions, the 16-bit value required
+ by the RTP specification for a header extension, labeled in the RTP
+ specification as "defined by profile", takes the fixed bit pattern
+ 0xBEDE (the first version of this specification was written on the
+ feast day of the Venerable Bede).
+ */
+ data[12] = 0xBE;
+ data[13] = 0xDE;
+ // put a length of RTP Extension.
+ data[14] = 0x00;
+ data[15] = 0x01;
+ // put extmap of RTP assigned for CVO.
+ data[16] = (mRTPCVOExtMap << 4) | 0x0;
+ // put image degrees as per CVO specification.
+ data[17] = mRTPCVODegrees;
+ data[18] = 0x0;
+ data[19] = 0x0;
+ rtpExtIndex = 8;
+ }
+
+ memcpy(&data[12 + rtpExtIndex],
+ mediaData, mediaBuf->range_length());
+
+ buffer->setRange(0, mediaBuf->range_length() + (12 + rtpExtIndex));
+
+ send(buffer, false /* isRTCP */);
+
+ ++mSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - (12 + rtpExtIndex);
+ } else {
+ // FU-A
+
+ unsigned nalType = mediaData[0];
+ size_t offset = 1;
+
+ bool firstPacket = true;
+ while (offset < mediaBuf->range_length()) {
+ size_t size = mediaBuf->range_length() - offset;
+ bool lastPacket = true;
+ if (size + UDP_HEADER_SIZE + RTP_HEADER_SIZE + RTP_FU_HEADER_SIZE +
+ RTP_PAYLOAD_ROOM_SIZE > buffer->capacity()) {
+ lastPacket = false;
+ size = buffer->capacity() - UDP_HEADER_SIZE - RTP_HEADER_SIZE -
+ RTP_FU_HEADER_SIZE - RTP_PAYLOAD_ROOM_SIZE;
+ }
+
+ uint8_t *data = buffer->data();
+ data[0] = 0x80;
+ if (lastPacket && mRTPCVOExtMap > 0)
+ data[0] |= 0x10;
+ data[1] = (lastPacket ? (1 << 7) : 0x00) | mPayloadType; // M-bit
+ data[2] = (mSeqNo >> 8) & 0xff;
+ data[3] = mSeqNo & 0xff;
+ data[4] = rtpTime >> 24;
+ data[5] = (rtpTime >> 16) & 0xff;
+ data[6] = (rtpTime >> 8) & 0xff;
+ data[7] = rtpTime & 0xff;
+ data[8] = mSourceID >> 24;
+ data[9] = (mSourceID >> 16) & 0xff;
+ data[10] = (mSourceID >> 8) & 0xff;
+ data[11] = mSourceID & 0xff;
+
+ int rtpExtIndex = 0;
+ if (lastPacket && mRTPCVOExtMap > 0) {
+ data[12] = 0xBE;
+ data[13] = 0xDE;
+ data[14] = 0x00;
+ data[15] = 0x01;
+ data[16] = (mRTPCVOExtMap << 4) | 0x0;
+ data[17] = mRTPCVODegrees;
+ data[18] = 0x0;
+ data[19] = 0x0;
+ rtpExtIndex = 8;
+ }
+
+ data[12 + rtpExtIndex] = 28 | (nalType & 0xe0);
+
+ CHECK(!firstPacket || !lastPacket);
+
+ data[13 + rtpExtIndex] =
+ (firstPacket ? 0x80 : 0x00)
+ | (lastPacket ? 0x40 : 0x00)
+ | (nalType & 0x1f);
+
+ memcpy(&data[14 + rtpExtIndex], &mediaData[offset], size);
+
+ buffer->setRange(0, 14 + rtpExtIndex + size);
+
+ send(buffer, false /* isRTCP */);
+
+ ++mSeqNo;
+ ++mNumRTPSent;
+ mNumRTPOctetsSent += buffer->size() - (12 + rtpExtIndex);
+
+ firstPacket = false;
+ offset += size;
+ }
+ }
+
+ mLastRTPTime = rtpTime;
+ mLastNTPTime = GetNowNTP();
}
void ARTPWriter::sendH263Data(MediaBufferBase *mediaBuf) {
@@ -696,7 +1146,7 @@
uint8_t *data = buffer->data();
data[0] = 0x80;
- data[1] = (lastPacket ? 0x80 : 0x00) | PT; // M-bit
+ data[1] = (lastPacket ? 0x80 : 0x00) | mPayloadType; // M-bit
data[2] = (mSeqNo >> 8) & 0xff;
data[3] = mSeqNo & 0xff;
data[4] = rtpTime >> 24;
@@ -727,6 +1177,54 @@
mLastNTPTime = GetNowNTP();
}
+void ARTPWriter::updateCVODegrees(int32_t cvoDegrees) {
+ Mutex::Autolock autoLock(mLock);
+ mRTPCVODegrees = cvoDegrees;
+}
+
+void ARTPWriter::updatePayloadType(int32_t payloadType) {
+ Mutex::Autolock autoLock(mLock);
+ mPayloadType = payloadType;
+}
+
+void ARTPWriter::updateSocketDscp(int32_t dscp) {
+ mRtpLayer3Dscp = dscp << 2;
+
+ /* mRtpLayer3Dscp will be mapped to WMM(Wifi) as per operator's requirement */
+ if (setsockopt(mRTPSocket, IPPROTO_IP, IP_TOS,
+ (int *)&mRtpLayer3Dscp, sizeof(mRtpLayer3Dscp)) < 0) {
+ ALOGE("failed to set dscp on rtpsock. err=%s", strerror(errno));
+ } else {
+ ALOGD("successfully set dscp on rtpsock. opt=%d", mRtpLayer3Dscp);
+ setsockopt(mRTCPSocket, IPPROTO_IP, IP_TOS,
+ (int *)&mRtpLayer3Dscp, sizeof(mRtpLayer3Dscp));
+ ALOGD("successfully set dscp on rtcpsock. opt=%d", mRtpLayer3Dscp);
+ }
+}
+
+void ARTPWriter::updateSocketNetwork(int64_t socketNetwork) {
+ mRTPSockNetwork = (net_handle_t)socketNetwork;
+ ALOGI("trying to bind rtp socket(%d) to network(%llu).",
+ mRTPSocket, (unsigned long long)mRTPSockNetwork);
+
+ int result = android_setsocknetwork(mRTPSockNetwork, mRTPSocket);
+ if (result != 0) {
+ ALOGW("failed(%d) to bind rtp socket(%d) to network(%llu)",
+ result, mRTPSocket, (unsigned long long)mRTPSockNetwork);
+ }
+ result = android_setsocknetwork(mRTPSockNetwork, mRTCPSocket);
+ if (result != 0) {
+ ALOGW("failed(%d) to bind rtcp socket(%d) to network(%llu)",
+ result, mRTCPSocket, (unsigned long long)mRTPSockNetwork);
+ }
+ ALOGI("done. bind rtp socket(%d) to network(%llu)",
+ mRTPSocket, (unsigned long long)mRTPSockNetwork);
+}
+
+uint32_t ARTPWriter::getSequenceNum() {
+ return mSeqNo;
+}
+
static size_t getFrameSize(bool isWide, unsigned FT) {
static const size_t kFrameSizeNB[8] = {
95, 103, 118, 134, 148, 159, 204, 244
@@ -778,7 +1276,7 @@
// The data fits into a single packet
uint8_t *data = buffer->data();
data[0] = 0x80;
- data[1] = PT;
+ data[1] = mPayloadType;
if (mNumRTPSent == 0) {
// Signal start of talk-spurt.
data[1] |= 0x80; // M-bit
@@ -834,5 +1332,80 @@
mLastNTPTime = GetNowNTP();
}
-} // namespace android
+void ARTPWriter::makeSocketPairAndBind(String8& localIp, int localPort,
+ String8& remoteIp, int remotePort) {
+ static char kSomeone[16] = "someone@";
+ int nameLength = strlen(kSomeone);
+ memcpy(kCNAME, kSomeone, nameLength);
+ memcpy(kCNAME + nameLength, localIp.c_str(), localIp.length() + 1);
+ if (localIp.contains(":"))
+ mIsIPv6 = true;
+ else
+ mIsIPv6 = false;
+
+ mRTPSocket = socket(mIsIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mRTPSocket, 0);
+ mRTCPSocket = socket(mIsIPv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+ CHECK_GE(mRTCPSocket, 0);
+
+ int sockopt = 1;
+ setsockopt(mRTPSocket, SOL_SOCKET, SO_REUSEADDR, (int *)&sockopt, sizeof(sockopt));
+ setsockopt(mRTCPSocket, SOL_SOCKET, SO_REUSEADDR, (int *)&sockopt, sizeof(sockopt));
+
+ if (mIsIPv6) {
+ memset(&mLocalAddr6, 0, sizeof(mLocalAddr6));
+ memset(&mRTPAddr6, 0, sizeof(mRTPAddr6));
+ memset(&mRTCPAddr6, 0, sizeof(mRTCPAddr6));
+
+ mLocalAddr6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, localIp.string(), &mLocalAddr6.sin6_addr);
+ mLocalAddr6.sin6_port = htons((uint16_t)localPort);
+
+ mRTPAddr6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, remoteIp.string(), &mRTPAddr6.sin6_addr);
+ mRTPAddr6.sin6_port = htons((uint16_t)remotePort);
+
+ mRTCPAddr6 = mRTPAddr6;
+ mRTCPAddr6.sin6_port = htons((uint16_t)(remotePort + 1));
+ } else {
+ memset(&mLocalAddr, 0, sizeof(mLocalAddr));
+ memset(&mRTPAddr, 0, sizeof(mRTPAddr));
+ memset(&mRTCPAddr, 0, sizeof(mRTCPAddr));
+
+ mLocalAddr.sin_family = AF_INET;
+ mLocalAddr.sin_addr.s_addr = inet_addr(localIp.string());
+ mLocalAddr.sin_port = htons((uint16_t)localPort);
+
+ mRTPAddr.sin_family = AF_INET;
+ mRTPAddr.sin_addr.s_addr = inet_addr(remoteIp.string());
+ mRTPAddr.sin_port = htons((uint16_t)remotePort);
+
+ mRTCPAddr = mRTPAddr;
+ mRTCPAddr.sin_port = htons((uint16_t)(remotePort + 1));
+ }
+
+ struct sockaddr *localAddr = mIsIPv6 ?
+ (struct sockaddr*)&mLocalAddr6 : (struct sockaddr*)&mLocalAddr;
+
+ int sizeSockSt = mIsIPv6 ? sizeof(mLocalAddr6) : sizeof(mLocalAddr);
+
+ if (bind(mRTPSocket, localAddr, sizeSockSt) == -1) {
+ ALOGE("failed to bind rtp %s:%d err=%s", localIp.string(), localPort, strerror(errno));
+ } else {
+ ALOGD("succeed to bind rtp %s:%d", localIp.string(), localPort);
+ }
+
+ if (mIsIPv6)
+ mLocalAddr6.sin6_port = htons((uint16_t)(localPort + 1));
+ else
+ mLocalAddr.sin_port = htons((uint16_t)(localPort + 1));
+
+ if (bind(mRTCPSocket, localAddr, sizeSockSt) == -1) {
+ ALOGE("failed to bind rtcp %s:%d err=%s", localIp.string(), localPort + 1, strerror(errno));
+ } else {
+ ALOGD("succeed to bind rtcp %s:%d", localIp.string(), localPort + 1);
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/rtsp/ARTPWriter.h b/media/libstagefright/rtsp/ARTPWriter.h
index 2f13486..f7e2204 100644
--- a/media/libstagefright/rtsp/ARTPWriter.h
+++ b/media/libstagefright/rtsp/ARTPWriter.h
@@ -27,6 +27,8 @@
#include <arpa/inet.h>
#include <sys/socket.h>
+#include <android/multinetwork.h>
+
#define LOG_TO_FILES 0
namespace android {
@@ -36,14 +38,23 @@
struct ARTPWriter : public MediaWriter {
explicit ARTPWriter(int fd);
+ explicit ARTPWriter(int fd, String8& localIp, int localPort,
+ String8& remoteIp, int remotePort,
+ uint32_t seqNo);
virtual status_t addSource(const sp<MediaSource> &source);
virtual bool reachedEOS();
virtual status_t start(MetaData *params);
virtual status_t stop();
virtual status_t pause();
+ void updateCVODegrees(int32_t cvoDegrees);
+ void updatePayloadType(int32_t payloadType);
+ void updateSocketDscp(int32_t dscp);
+ void updateSocketNetwork(int64_t socketNetwork);
+ uint32_t getSequenceNum();
virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual void setTMMBNInfo(uint32_t opponentID, uint32_t bitrate);
protected:
virtual ~ARTPWriter();
@@ -76,15 +87,26 @@
sp<ALooper> mLooper;
sp<AHandlerReflector<ARTPWriter> > mReflector;
- int mSocket;
+ bool mIsIPv6;
+ int mRTPSocket, mRTCPSocket;
+ struct sockaddr_in mLocalAddr;
struct sockaddr_in mRTPAddr;
struct sockaddr_in mRTCPAddr;
+ struct sockaddr_in6 mLocalAddr6;
+ struct sockaddr_in6 mRTPAddr6;
+ struct sockaddr_in6 mRTCPAddr6;
+ int32_t mRtpLayer3Dscp;
+ net_handle_t mRTPSockNetwork;
AString mProfileLevel;
AString mSeqParamSet;
AString mPicParamSet;
+ MediaBufferBase *mSPSBuf;
+ MediaBufferBase *mPPSBuf;
+
uint32_t mSourceID;
+ uint32_t mPayloadType;
uint32_t mSeqNo;
uint32_t mRTPTimeBase;
uint32_t mNumRTPSent;
@@ -92,10 +114,16 @@
uint32_t mLastRTPTime;
uint64_t mLastNTPTime;
+ uint32_t mOpponentID;
+ uint32_t mBitrate;
+
int32_t mNumSRsSent;
+ int32_t mRTPCVOExtMap;
+ int32_t mRTPCVODegrees;
enum {
INVALID,
+ H265,
H264,
H263,
AMR_NB,
@@ -109,16 +137,20 @@
void addSR(const sp<ABuffer> &buffer);
void addSDES(const sp<ABuffer> &buffer);
+ void addTMMBN(const sp<ABuffer> &buffer);
void makeH264SPropParamSets(MediaBufferBase *buffer);
void dumpSessionDesc();
void sendBye();
+ void sendSPSPPSIfIFrame(MediaBufferBase *mediaBuf, int64_t timeUs);
+ void sendHEVCData(MediaBufferBase *mediaBuf);
void sendAVCData(MediaBufferBase *mediaBuf);
void sendH263Data(MediaBufferBase *mediaBuf);
void sendAMRData(MediaBufferBase *mediaBuf);
void send(const sp<ABuffer> &buffer, bool isRTCP);
+ void makeSocketPairAndBind(String8& localIp, int localPort, String8& remoteIp, int remotePort);
DISALLOW_EVIL_CONSTRUCTORS(ARTPWriter);
};
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
index 2b42040..5b5b4b1 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -27,6 +27,8 @@
namespace android {
+constexpr unsigned kDefaultAs = 960; // kbps?
+
ASessionDescription::ASessionDescription()
: mIsValid(false) {
}
@@ -103,7 +105,7 @@
key.setTo(line, 0, colonPos);
if (key == "a=fmtp" || key == "a=rtpmap"
- || key == "a=framesize") {
+ || key == "a=framesize" || key == "a=extmap") {
ssize_t spacePos = line.find(" ", colonPos + 1);
if (spacePos < 0) {
return false;
@@ -201,6 +203,33 @@
return true;
}
+bool ASessionDescription::getCvoExtMap(
+ size_t index, int32_t *cvoExtMap) const {
+ CHECK_GE(index, 0u);
+ CHECK_LT(index, mTracks.size());
+
+ AString key, value;
+ *cvoExtMap = 0;
+
+ const Attribs &track = mTracks.itemAt(index);
+ for (size_t i = 0; i < track.size(); i++) {
+ value = track.valueAt(i);
+ if (value.size() > 0 && strcmp(value.c_str(), "urn:3gpp:video-orientation") == 0) {
+ key = track.keyAt(i);
+ break;
+ }
+ }
+
+ if (key.size() > 0) {
+ const char *colonPos = strrchr(key.c_str(), ':');
+ colonPos++;
+ *cvoExtMap = atoi(colonPos);
+ return true;
+ }
+
+ return false;
+}
+
void ASessionDescription::getFormatType(
size_t index, unsigned long *PT,
AString *desc, AString *params) const {
@@ -345,5 +374,74 @@
return *npt2 > *npt1;
}
+// static
+void ASessionDescription::SDPStringFactory(AString &sdp,
+ const char *ip, bool isAudio, unsigned port, unsigned payloadType,
+ unsigned as, const char *codec, const char *fmtp,
+ int32_t width, int32_t height, int32_t cvoExtMap)
+{
+ bool isIPv4 = (AString(ip).find("::") == -1) ? true : false;
+ sdp.clear();
+ sdp.append("v=0\r\n");
+
+ sdp.append("a=range:npt=now-\r\n");
+
+ sdp.append("m=");
+ sdp.append(isAudio ? "audio " : "video ");
+ sdp.append(port);
+ sdp.append(" RTP/AVP ");
+ sdp.append(payloadType);
+ sdp.append("\r\n");
+
+ sdp.append("c= IN IP");
+ if (isIPv4) {
+ sdp.append("4 ");
+ } else {
+ sdp.append("6 ");
+ }
+ sdp.append(ip);
+ sdp.append("\r\n");
+
+ sdp.append("b=AS:");
+ sdp.append(as > 0 ? as : kDefaultAs);
+ sdp.append("\r\n");
+
+ sdp.append("a=rtpmap:");
+ sdp.append(payloadType);
+ sdp.append(" ");
+ sdp.append(codec);
+ sdp.append("/");
+ sdp.append(isAudio ? "8000" : "90000");
+ sdp.append("\r\n");
+
+ if (fmtp != NULL) {
+ sdp.append("a=fmtp:");
+ sdp.append(payloadType);
+ sdp.append(" ");
+ sdp.append(fmtp);
+ sdp.append("\r\n");
+ }
+
+ if (!isAudio && width > 0 && height > 0) {
+ sdp.append("a=framesize:");
+ sdp.append(payloadType);
+ sdp.append(" ");
+ sdp.append(width);
+ sdp.append("-");
+ sdp.append(height);
+ sdp.append("\r\n");
+ }
+
+ if (cvoExtMap > 0) {
+ sdp.append("a=extmap:");
+ sdp.append(cvoExtMap);
+ sdp.append(" ");
+ sdp.append("urn:3gpp:video-orientation");
+ sdp.append("\r\n");
+ }
+
+ ALOGV("SDPStringFactory => %s", sdp.c_str());
+}
+
} // namespace android
diff --git a/media/libstagefright/rtsp/ASessionDescription.h b/media/libstagefright/rtsp/ASessionDescription.h
index b462983..91f5442 100644
--- a/media/libstagefright/rtsp/ASessionDescription.h
+++ b/media/libstagefright/rtsp/ASessionDescription.h
@@ -40,6 +40,8 @@
size_t countTracks() const;
void getFormat(size_t index, AString *value) const;
+ bool getCvoExtMap(size_t index, int32_t *cvoExtMap) const;
+
void getFormatType(
size_t index, unsigned long *PT,
AString *desc, AString *params) const;
@@ -63,6 +65,9 @@
// i.e. we have a fixed duration, otherwise this is live streaming.
static bool parseNTPRange(const char *s, float *npt1, float *npt2);
+ static void SDPStringFactory(AString &sdp, const char *ip, bool isAudio, unsigned port,
+ unsigned payloadType, unsigned as, const char *codec, const char *fmtp = NULL,
+ int32_t width = 0, int32_t height = 0, int32_t cvoExtMap = 0);
protected:
virtual ~ASessionDescription();
diff --git a/media/libstagefright/rtsp/Android.bp b/media/libstagefright/rtsp/Android.bp
index a5a895e..6179142 100644
--- a/media/libstagefright/rtsp/Android.bp
+++ b/media/libstagefright/rtsp/Android.bp
@@ -4,6 +4,7 @@
srcs: [
"AAMRAssembler.cpp",
"AAVCAssembler.cpp",
+ "AHEVCAssembler.cpp",
"AH263Assembler.cpp",
"AMPEG2TSAssembler.cpp",
"AMPEG4AudioAssembler.cpp",
@@ -17,9 +18,11 @@
"ARTSPConnection.cpp",
"ASessionDescription.cpp",
"SDPLoader.cpp",
+ "QualManager.cpp",
],
shared_libs: [
+ "libandroid_net",
"libcrypto",
"libdatasource",
"libmedia",
@@ -28,6 +31,7 @@
include_dirs: [
"frameworks/av/media/libstagefright",
"frameworks/native/include/media/openmax",
+ "frameworks/native/include/android",
],
arch: {
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 7f025a5..0fdf431 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -1032,6 +1032,11 @@
break;
}
+ int32_t rtcpEvent;
+ if (msg->findInt32("rtcp-event", &rtcpEvent)) {
+ break;
+ }
+
++mNumAccessUnitsReceived;
postAccessUnitTimeoutCheck();
diff --git a/media/libstagefright/rtsp/QualManager.cpp b/media/libstagefright/rtsp/QualManager.cpp
new file mode 100644
index 0000000..37aa326
--- /dev/null
+++ b/media/libstagefright/rtsp/QualManager.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "QualManager"
+
+#include <algorithm>
+
+#include <sys/prctl.h>
+#include <utils/Log.h>
+
+#include "QualManager.h"
+
+namespace android {
+
+QualManager::Watcher::Watcher(int32_t timeLimit)
+ : Thread(false), mWatching(false), mSwitch(false),
+ mTimeLimit(timeLimit * 1000000LL) // timeLimit ms
+{
+}
+
+bool QualManager::Watcher::isExpired() const
+{
+ return mSwitch;
+}
+
+void QualManager::Watcher::setup() {
+ AutoMutex _l(mMyLock);
+ if (mWatching == false) {
+ mWatching = true;
+ mMyCond.signal();
+ }
+}
+
+void QualManager::Watcher::release() {
+ AutoMutex _l(mMyLock);
+ if (mSwitch) {
+ ALOGW("%s DISARMED", name);
+ mSwitch = false;
+ }
+ if (mWatching == true) {
+ ALOGW("%s DISARMED", name);
+ mWatching = false;
+ mMyCond.signal();
+ }
+}
+
+void QualManager::Watcher::exit() {
+ AutoMutex _l(mMyLock);
+ // The order is important to avoid dead lock.
+ Thread::requestExit();
+ mMyCond.signal();
+}
+
+QualManager::Watcher::~Watcher() {
+ ALOGI("%s thread dead", name);
+}
+
+bool QualManager::Watcher::threadLoop() {
+ AutoMutex _l(mMyLock);
+#if defined(__linux__)
+ prctl(PR_GET_NAME, name, 0, 0, 0);
+#endif
+ while (!exitPending()) {
+ ALOGW("%s Timer init", name);
+ mMyCond.wait(mMyLock); // waits as non-watching state
+ if (exitPending())
+ return false;
+ ALOGW("%s timer BOOM after %d msec", name, (int)(mTimeLimit / 1000000LL));
+ mMyCond.waitRelative(mMyLock, mTimeLimit); // waits as watching satte
+ if (mWatching == true) {
+ mSwitch = true;
+ ALOGW("%s BOOM!!!!", name);
+ }
+ mWatching = false;
+ }
+ return false;
+}
+
+
+QualManager::QualManager()
+ : mMinBitrate(-1), mMaxBitrate(-1),
+ mTargetBitrate(512000), mLastTargetBitrate(-1),
+ mLastSetBitrateTime(0), mIsNewTargetBitrate(false)
+{
+ VFPWatcher = new Watcher(3000); //Very Few Packet Watcher
+ VFPWatcher->run("VeryFewPtk");
+ LBRWatcher = new Watcher(10000); //Low Bit Rate Watcher
+ LBRWatcher->run("LowBitRate");
+}
+
+QualManager::~QualManager() {
+ VFPWatcher->exit();
+ LBRWatcher->exit();
+}
+
+int32_t QualManager::getTargetBitrate() {
+ if (mIsNewTargetBitrate) {
+ mIsNewTargetBitrate = false;
+ mLastTargetBitrate = clampingBitrate(mTargetBitrate);
+ mTargetBitrate = mLastTargetBitrate;
+ return mTargetBitrate;
+ } else {
+ return -1;
+ }
+}
+
+bool QualManager::isNeedToDowngrade() {
+ return LBRWatcher->isExpired();
+}
+
+void QualManager::setTargetBitrate(uint8_t fraction, int64_t nowUs, bool isTooLowPkts) {
+ /* Too Low Packet. Maybe opponent is switching camera.
+ * If this condition goes longer, we should down bitrate.
+ */
+ if (isTooLowPkts) {
+ VFPWatcher->setup();
+ } else {
+ VFPWatcher->release();
+ }
+
+ if ((fraction > (256 * 5 / 100) && !isTooLowPkts) || VFPWatcher->isExpired()) {
+ // loss more than 5% or VFPWatcher BOOMED
+ mTargetBitrate -= mBitrateStep * 3;
+ } else if (fraction <= (256 * 2 /100)) {
+ // loss less than 2%
+ mTargetBitrate += mBitrateStep;
+ }
+
+ if (mTargetBitrate > mMaxBitrate) {
+ mTargetBitrate = mMaxBitrate + mBitrateStep;
+ } else if (mTargetBitrate < mMinBitrate) {
+ LBRWatcher->setup();
+ mTargetBitrate = mMinBitrate - mBitrateStep;
+ }
+
+ if (mLastTargetBitrate != clampingBitrate(mTargetBitrate) ||
+ nowUs - mLastSetBitrateTime > 5000000ll) {
+ mIsNewTargetBitrate = true;
+ mLastSetBitrateTime = nowUs;
+ }
+}
+
+void QualManager::setMinMaxBitrate(int32_t min, int32_t max) {
+ mMinBitrate = min;
+ mMaxBitrate = max;
+ mBitrateStep = (max - min) / 8;
+}
+
+void QualManager::setBitrateData(int32_t bitrate, int64_t /*now*/) {
+ // A bitrate that is considered packetloss also should be good.
+ if (bitrate >= mMinBitrate && mTargetBitrate >= mMinBitrate) {
+ LBRWatcher->release();
+ } else if (bitrate < mMinBitrate){
+ LBRWatcher->setup();
+ }
+}
+
+int32_t QualManager::clampingBitrate(int32_t bitrate) {
+ return std::min(std::max(mMinBitrate, bitrate), mMaxBitrate);
+}
+} // namespace android
diff --git a/media/libstagefright/rtsp/QualManager.h b/media/libstagefright/rtsp/QualManager.h
new file mode 100644
index 0000000..a7dc921
--- /dev/null
+++ b/media/libstagefright/rtsp/QualManager.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef QUAL_MANAGER_H_
+
+#define QUAL_MANAGER_H_
+
+#include <stdint.h>
+#include <utils/Thread.h>
+
+namespace android {
+class QualManager {
+public:
+ QualManager();
+ ~QualManager();
+
+ int32_t getTargetBitrate();
+ bool isNeedToDowngrade();
+
+ void setTargetBitrate(uint8_t fraction, int64_t nowUs, bool isTooLowPkts);
+ void setMinMaxBitrate(int32_t min, int32_t max);
+ void setBitrateData(int32_t bitrate, int64_t now);
+private:
+ class Watcher : public Thread
+ {
+ public:
+ Watcher(int32_t timeLimit);
+
+ void setup();
+ void release();
+ void exit();
+ bool isExpired() const;
+ private:
+ virtual ~Watcher();
+ virtual bool threadLoop();
+
+ char name[32] = {0,};
+
+ Condition mMyCond;
+ Mutex mMyLock;
+
+ bool mWatching;
+ bool mSwitch;
+ const nsecs_t mTimeLimit;
+ };
+ sp<Watcher> VFPWatcher;
+ sp<Watcher> LBRWatcher;
+ int32_t mMinBitrate;
+ int32_t mMaxBitrate;
+ int32_t mBitrateStep;
+
+ int32_t mTargetBitrate;
+ int32_t mLastTargetBitrate;
+ int64_t mLastSetBitrateTime;
+
+ bool mIsNewTargetBitrate;
+
+ int32_t clampingBitrate(int32_t bitrate);
+};
+} //namespace android
+
+#endif // QUAL_MANAGER_H_
diff --git a/media/libstagefright/timedtext/TEST_MAPPING b/media/libstagefright/timedtext/TEST_MAPPING
new file mode 100644
index 0000000..4528b32
--- /dev/null
+++ b/media/libstagefright/timedtext/TEST_MAPPING
@@ -0,0 +1,7 @@
+// mappings for frameworks/av/media/libstagefright/timedtext
+{
+ "presubmit": [
+ // b/148094059: unit tests not allowed to download content
+ //{ "name": "TimedTextUnitTest" }
+ ]
+}
diff --git a/media/libstagefright/xmlparser/TEST_MAPPING b/media/libstagefright/xmlparser/TEST_MAPPING
new file mode 100644
index 0000000..8626d72
--- /dev/null
+++ b/media/libstagefright/xmlparser/TEST_MAPPING
@@ -0,0 +1,6 @@
+// test mapping for frameworks/av/media/libstagefright/xmlparser
+{
+ "presubmit": [
+ { "name": "XMLParserTest" }
+ ]
+}
diff --git a/media/libstagefright/xmlparser/test/Android.bp b/media/libstagefright/xmlparser/test/Android.bp
index 6d97c96..ba02f84 100644
--- a/media/libstagefright/xmlparser/test/Android.bp
+++ b/media/libstagefright/xmlparser/test/Android.bp
@@ -16,6 +16,7 @@
cc_test {
name: "XMLParserTest",
+ test_suites: ["device-tests"],
gtest: true,
srcs: [
diff --git a/media/ndk/NdkImagePriv.h b/media/ndk/NdkImagePriv.h
index 0e8cbcb..b019448 100644
--- a/media/ndk/NdkImagePriv.h
+++ b/media/ndk/NdkImagePriv.h
@@ -30,6 +30,18 @@
using namespace android;
+// Formats not listed in the public API, but still available to AImageReader
+enum AIMAGE_PRIVATE_FORMATS {
+ /**
+ * Unprocessed implementation-dependent raw
+ * depth measurements, opaque with 16 bit
+ * samples.
+ *
+ */
+
+ AIMAGE_FORMAT_RAW_DEPTH = 0x1002,
+};
+
// TODO: this only supports ImageReader
struct AImage {
AImage(AImageReader* reader, int32_t format, uint64_t usage, BufferItem* buffer,
diff --git a/media/ndk/NdkImageReader.cpp b/media/ndk/NdkImageReader.cpp
index c0ceb3d..5d8f0b8 100644
--- a/media/ndk/NdkImageReader.cpp
+++ b/media/ndk/NdkImageReader.cpp
@@ -21,7 +21,6 @@
#include "NdkImagePriv.h"
#include "NdkImageReaderPriv.h"
-#include <private/media/NdkImage.h>
#include <cutils/atomic.h>
#include <utils/Log.h>
diff --git a/media/ndk/include/private/media/NdkImage.h b/media/ndk/include/private/media/NdkImage.h
deleted file mode 100644
index 4368a56..0000000
--- a/media/ndk/include/private/media/NdkImage.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _PRIVATE_MEDIA_NDKIMAGE_H_
-#define _PRIVATE_MEDIA_NDKIMAGE_H_
-// Formats not listed in the public API, but still available to AImageReader
-enum AIMAGE_PRIVATE_FORMATS {
- /**
- * Unprocessed implementation-dependent raw
- * depth measurements, opaque with 16 bit
- * samples.
- *
- */
-
- AIMAGE_FORMAT_RAW_DEPTH = 0x1002,
-};
-#endif // _PRIVATE_MEDIA_NDKIMAGE
diff --git a/media/ndk/tests/AImageReaderWindowHandleTest.cpp b/media/ndk/tests/AImageReaderWindowHandleTest.cpp
index 5b65064..27864c2 100644
--- a/media/ndk/tests/AImageReaderWindowHandleTest.cpp
+++ b/media/ndk/tests/AImageReaderWindowHandleTest.cpp
@@ -17,10 +17,10 @@
#include <gtest/gtest.h>
#include <media/NdkImageReader.h>
#include <media/NdkImage.h>
-#include <private/media/NdkImage.h>
#include <mediautils/AImageReaderUtils.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
+#include <NdkImagePriv.h>
#include <NdkImageReaderPriv.h>
#include <vndk/hardware_buffer.h>
#include <memory>
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 6afbd4f..3fd55d4 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -89,7 +89,7 @@
#include "AudioHwDevice.h"
#include "NBAIO_Tee.h"
-#include <powermanager/IPowerManager.h>
+#include <android/os/IPowerManager.h>
#include <media/nblog/NBLog.h>
#include <private/media/AudioEffectShared.h>
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 5d9c35a..2b618ab 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -983,15 +983,16 @@
if (mPowerManager != 0) {
sp<IBinder> binder = new BBinder();
// Uses AID_AUDIOSERVER for wakelock. updateWakeLockUids_l() updates with client uids.
- status_t status = mPowerManager->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK,
- binder,
+ binder::Status status = mPowerManager->acquireWakeLockAsync(binder,
+ POWERMANAGER_PARTIAL_WAKE_LOCK,
getWakeLockTag(),
String16("audioserver"),
- true /* FIXME force oneway contrary to .aidl */);
- if (status == NO_ERROR) {
+ {} /* workSource */,
+ {} /* historyTag */);
+ if (status.isOk()) {
mWakeLockToken = binder;
}
- ALOGV("acquireWakeLock_l() %s status %d", mThreadName, status);
+ ALOGV("acquireWakeLock_l() %s status %d", mThreadName, status.exceptionCode());
}
gBoottime.acquire(mWakeLockToken);
@@ -1011,8 +1012,7 @@
if (mWakeLockToken != 0) {
ALOGV("releaseWakeLock_l() %s", mThreadName);
if (mPowerManager != 0) {
- mPowerManager->releaseWakeLock(mWakeLockToken, 0,
- true /* FIXME force oneway contrary to .aidl */);
+ mPowerManager->releaseWakeLockAsync(mWakeLockToken, 0);
}
mWakeLockToken.clear();
}
@@ -1026,7 +1026,7 @@
if (binder == 0) {
ALOGW("Thread %s cannot connect to the power manager service", mThreadName);
} else {
- mPowerManager = interface_cast<IPowerManager>(binder);
+ mPowerManager = interface_cast<os::IPowerManager>(binder);
binder->linkToDeath(mDeathRecipient);
}
}
@@ -1053,10 +1053,9 @@
}
if (mPowerManager != 0) {
std::vector<int> uidsAsInt(uids.begin(), uids.end()); // powermanager expects uids as ints
- status_t status = mPowerManager->updateWakeLockUids(
- mWakeLockToken, uidsAsInt.size(), uidsAsInt.data(),
- true /* FIXME force oneway contrary to .aidl */);
- ALOGV("updateWakeLockUids_l() %s status %d", mThreadName, status);
+ binder::Status status = mPowerManager->updateWakeLockUidsAsync(
+ mWakeLockToken, uidsAsInt);
+ ALOGV("updateWakeLockUids_l() %s status %d", mThreadName, status.exceptionCode());
}
}
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index e5a6196..b56eabc 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -562,7 +562,7 @@
static const int kThreadNameLength = 16; // prctl(PR_SET_NAME) limit
char mThreadName[kThreadNameLength]; // guaranteed NUL-terminated
- sp<IPowerManager> mPowerManager;
+ sp<os::IPowerManager> mPowerManager;
sp<IBinder> mWakeLockToken;
const sp<PMDeathRecipient> mDeathRecipient;
// list of suspended effects per session and per type. The first (outer) vector is
diff --git a/services/audiopolicy/engine/common/src/VolumeCurve.cpp b/services/audiopolicy/engine/common/src/VolumeCurve.cpp
index c352578..8aa4b08 100644
--- a/services/audiopolicy/engine/common/src/VolumeCurve.cpp
+++ b/services/audiopolicy/engine/common/src/VolumeCurve.cpp
@@ -43,10 +43,24 @@
indexInUi = volIndexMax;
}
+ // Calculate the new volume index
size_t nbCurvePoints = mCurvePoints.size();
- // the volume index in the UI is relative to the min and max volume indices for this stream
- int nbSteps = 1 + mCurvePoints[nbCurvePoints - 1].mIndex - mCurvePoints[0].mIndex;
- int volIdx = (nbSteps * (indexInUi - volIndexMin)) / (volIndexMax - volIndexMin);
+
+ int volIdx;
+ if (volIndexMin == volIndexMax) {
+ if (indexInUi == volIndexMin) {
+ volIdx = volIndexMin;
+ } else {
+ // This would result in a divide-by-zero below
+ ALOG_ASSERT(volIndexmin != volIndexMax, "Invalid volume index range & value: 0");
+ return NAN;
+ }
+ } else {
+ // interpolaate
+ // the volume index in the UI is relative to the min and max volume indices for this stream
+ int nbSteps = 1 + mCurvePoints[nbCurvePoints - 1].mIndex - mCurvePoints[0].mIndex;
+ volIdx = (nbSteps * (indexInUi - volIndexMin)) / (volIndexMax - volIndexMin);
+ }
// Where would this volume index been inserted in the curve point
size_t indexInUiPosition = mCurvePoints.orderOf(CurvePoint(volIdx, 0));
diff --git a/services/audiopolicy/manager/Android.bp b/services/audiopolicy/manager/Android.bp
new file mode 100644
index 0000000..5bb432f
--- /dev/null
+++ b/services/audiopolicy/manager/Android.bp
@@ -0,0 +1,32 @@
+cc_library_shared {
+ name: "libaudiopolicymanager",
+
+ srcs: [
+ "AudioPolicyFactory.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/services/audioflinger"
+ ],
+
+ shared_libs: [
+ "libaudiopolicymanagerdefault",
+ ],
+
+ static_libs: [
+ "libaudiopolicycomponents",
+ ],
+
+ header_libs: [
+ "libaudiopolicycommon",
+ "libaudiopolicyengine_interface_headers",
+ "libaudiopolicymanager_interface_headers",
+ "libaudioutils_headers",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+}
diff --git a/services/audiopolicy/manager/Android.mk b/services/audiopolicy/manager/Android.mk
deleted file mode 100644
index cae6cfa..0000000
--- a/services/audiopolicy/manager/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-ifneq ($(USE_CUSTOM_AUDIO_POLICY), 1)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- AudioPolicyFactory.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libaudiopolicymanagerdefault
-
-LOCAL_STATIC_LIBRARIES := \
- libaudiopolicycomponents
-
-LOCAL_C_INCLUDES += \
- $(call include-path-for, audio-utils)
-
-LOCAL_HEADER_LIBRARIES := \
- libaudiopolicycommon \
- libaudiopolicyengine_interface_headers \
- libaudiopolicymanager_interface_headers
-
-LOCAL_CFLAGS := -Wall -Werror
-
-LOCAL_MODULE:= libaudiopolicymanager
-
-include $(BUILD_SHARED_LIBRARY)
-
-endif #ifneq ($(USE_CUSTOM_AUDIO_POLICY), 1)
diff --git a/services/audiopolicy/service/Android.bp b/services/audiopolicy/service/Android.bp
new file mode 100644
index 0000000..6ee573f
--- /dev/null
+++ b/services/audiopolicy/service/Android.bp
@@ -0,0 +1,54 @@
+cc_library_shared {
+ name: "libaudiopolicyservice",
+
+ srcs: [
+ "AudioPolicyClientImpl.cpp",
+ "AudioPolicyEffects.cpp",
+ "AudioPolicyInterfaceImpl.cpp",
+ "AudioPolicyService.cpp",
+ "CaptureStateNotifier.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/services/audioflinger"
+ ],
+
+ shared_libs: [
+ "libaudioclient",
+ "libaudiofoundation",
+ "libaudiopolicymanager",
+ "libaudioutils",
+ "libbinder",
+ "libcutils",
+ "libeffectsconfig",
+ "libhardware_legacy",
+ "liblog",
+ "libmedia_helper",
+ "libmediametrics",
+ "libmediautils",
+ "libsensorprivacy",
+ "libutils",
+ "capture_state_listener-aidl-cpp",
+ ],
+
+ static_libs: [
+ "libaudiopolicycomponents",
+ ],
+
+ header_libs: [
+ "libaudiopolicycommon",
+ "libaudiopolicyengine_interface_headers",
+ "libaudiopolicymanager_interface_headers",
+ "libaudioutils_headers",
+ ],
+
+ cflags: [
+ "-fvisibility=hidden",
+ "-Werror",
+ "-Wall",
+ ],
+
+ export_shared_lib_headers: [
+ "libsensorprivacy",
+ ],
+}
diff --git a/services/audiopolicy/service/Android.mk b/services/audiopolicy/service/Android.mk
deleted file mode 100644
index 94e4811..0000000
--- a/services/audiopolicy/service/Android.mk
+++ /dev/null
@@ -1,50 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- AudioPolicyService.cpp \
- AudioPolicyEffects.cpp \
- AudioPolicyInterfaceImpl.cpp \
- AudioPolicyClientImpl.cpp \
- CaptureStateNotifier.cpp
-
-LOCAL_C_INCLUDES := \
- frameworks/av/services/audioflinger \
- $(call include-path-for, audio-utils)
-
-LOCAL_HEADER_LIBRARIES := \
- libaudiopolicycommon \
- libaudiopolicyengine_interface_headers \
- libaudiopolicymanager_interface_headers
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- libutils \
- liblog \
- libbinder \
- libaudioclient \
- libaudioutils \
- libaudiofoundation \
- libhardware_legacy \
- libaudiopolicymanager \
- libmedia_helper \
- libmediametrics \
- libmediautils \
- libeffectsconfig \
- libsensorprivacy \
- capture_state_listener-aidl-cpp
-
-LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := \
- libsensorprivacy
-
-LOCAL_STATIC_LIBRARIES := \
- libaudiopolicycomponents
-
-LOCAL_MODULE:= libaudiopolicyservice
-
-LOCAL_CFLAGS += -fvisibility=hidden
-LOCAL_CFLAGS += -Wall -Werror
-
-include $(BUILD_SHARED_LIBRARY)
-
diff --git a/services/mediatranscoding/MediaTranscodingService.cpp b/services/mediatranscoding/MediaTranscodingService.cpp
index 82d4161..a13bec0 100644
--- a/services/mediatranscoding/MediaTranscodingService.cpp
+++ b/services/mediatranscoding/MediaTranscodingService.cpp
@@ -16,9 +16,12 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaTranscodingService"
-#include <MediaTranscodingService.h>
+#include "MediaTranscodingService.h"
+
#include <android/binder_manager.h>
#include <android/binder_process.h>
+#include <media/TranscodingClientManager.h>
+#include <media/TranscodingJobScheduler.h>
#include <private/android_filesystem_config.h>
#include <utils/Log.h>
#include <utils/Vector.h>
@@ -45,8 +48,41 @@
}
}
+// DummyTranscoder and DummyUidPolicy are currently used to instantiate
+// MediaTranscodingService on service side for testing, so that we could
+// actually test the IPC calls of MediaTranscodingService to expose some
+// issues that's observable only over IPC.
+class DummyTranscoder : public TranscoderInterface {
+ void start(int64_t clientId, int32_t jobId) override {
+ (void)clientId;
+ (void)jobId;
+ }
+ void pause(int64_t clientId, int32_t jobId) override {
+ (void)clientId;
+ (void)jobId;
+ }
+ void resume(int64_t clientId, int32_t jobId) override {
+ (void)clientId;
+ (void)jobId;
+ }
+};
+
+class DummyUidPolicy : public UidPolicyInterface {
+ bool isUidOnTop(uid_t uid) override {
+ (void)uid;
+ return true;
+ }
+};
+
MediaTranscodingService::MediaTranscodingService()
- : mTranscodingClientManager(TranscodingClientManager::getInstance()) {
+ : MediaTranscodingService(std::make_shared<DummyTranscoder>(),
+ std::make_shared<DummyUidPolicy>()) {}
+
+MediaTranscodingService::MediaTranscodingService(
+ const std::shared_ptr<TranscoderInterface>& transcoder,
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy)
+ : mJobScheduler(new TranscodingJobScheduler(transcoder, uidPolicy)),
+ mClientManager(new TranscodingClientManager(mJobScheduler)) {
ALOGV("MediaTranscodingService is created");
}
@@ -64,7 +100,7 @@
write(fd, result.string(), result.size());
Vector<String16> args;
- mTranscodingClientManager.dumpAllClients(fd, args);
+ mClientManager->dumpAllClients(fd, args);
return OK;
}
@@ -80,19 +116,20 @@
}
Status MediaTranscodingService::registerClient(
- const std::shared_ptr<ITranscodingServiceClient>& in_client,
- const std::string& in_opPackageName, int32_t in_clientUid, int32_t in_clientPid,
- int32_t* _aidl_return) {
- if (in_client == nullptr) {
- ALOGE("Client can not be null");
- *_aidl_return = kInvalidJobId;
+ const std::shared_ptr<ITranscodingClientCallback>& in_callback,
+ const std::string& in_clientName, const std::string& in_opPackageName, int32_t in_clientUid,
+ int32_t in_clientPid, std::shared_ptr<ITranscodingClient>* _aidl_return) {
+ if (in_callback == nullptr) {
+ ALOGE("Client callback can not be null");
+ *_aidl_return = nullptr;
return Status::fromServiceSpecificError(ERROR_ILLEGAL_ARGUMENT);
}
int32_t callingPid = AIBinder_getCallingPid();
int32_t callingUid = AIBinder_getCallingUid();
- // Check if we can trust clientUid. Only privilege caller could forward the uid on app client's behalf.
+ // Check if we can trust clientUid. Only privilege caller could forward the
+ // uid on app client's behalf.
if (in_clientUid == USE_CALLING_UID) {
in_clientUid = callingUid;
} else if (!isTrustedCallingUid(callingUid)) {
@@ -106,7 +143,8 @@
in_clientPid, in_clientUid);
}
- // Check if we can trust clientPid. Only privilege caller could forward the pid on app client's behalf.
+ // Check if we can trust clientPid. Only privilege caller could forward the
+ // pid on app client's behalf.
if (in_clientPid == USE_CALLING_PID) {
in_clientPid = callingPid;
} else if (!isTrustedCallingUid(callingUid)) {
@@ -120,78 +158,23 @@
in_clientPid, in_clientUid);
}
- // We know the clientId must be equal to its pid as we assigned client's pid as its clientId.
- int32_t clientId = in_clientPid;
-
- // Checks if the client already registers.
- if (mTranscodingClientManager.isClientIdRegistered(clientId)) {
- return Status::fromServiceSpecificError(ERROR_ALREADY_EXISTS);
- }
-
// Creates the client and uses its process id as client id.
- std::unique_ptr<TranscodingClientManager::ClientInfo> newClient =
- std::make_unique<TranscodingClientManager::ClientInfo>(
- in_client, clientId, in_clientPid, in_clientUid, in_opPackageName);
- status_t err = mTranscodingClientManager.addClient(std::move(newClient));
+ std::shared_ptr<ITranscodingClient> newClient;
+
+ status_t err = mClientManager->addClient(in_callback, in_clientPid, in_clientUid, in_clientName,
+ in_opPackageName, &newClient);
if (err != OK) {
- *_aidl_return = kInvalidClientId;
+ *_aidl_return = nullptr;
return STATUS_ERROR_FMT(err, "Failed to add client to TranscodingClientManager");
}
- ALOGD("Assign client: %s pid: %d, uid: %d with id: %d", in_opPackageName.c_str(), in_clientPid,
- in_clientUid, clientId);
-
- *_aidl_return = clientId;
- return Status::ok();
-}
-
-Status MediaTranscodingService::unregisterClient(int32_t clientId, bool* _aidl_return) {
- ALOGD("unregisterClient id: %d", clientId);
- int32_t callingUid = AIBinder_getCallingUid();
- int32_t callingPid = AIBinder_getCallingPid();
-
- // Only the client with clientId or the trusted caller could unregister the client.
- if (callingPid != clientId) {
- if (!isTrustedCallingUid(callingUid)) {
- ALOGE("Untrusted caller (calling PID %d, UID %d) trying to "
- "unregister client with id: %d",
- callingUid, callingPid, clientId);
- *_aidl_return = true;
- return STATUS_ERROR_FMT(ERROR_PERMISSION_DENIED,
- "Untrusted caller (calling PID %d, UID %d) trying to "
- "unregister client with id: %d",
- callingUid, callingPid, clientId);
- }
- }
-
- *_aidl_return = (mTranscodingClientManager.removeClient(clientId) == OK);
+ *_aidl_return = newClient;
return Status::ok();
}
Status MediaTranscodingService::getNumOfClients(int32_t* _aidl_return) {
ALOGD("MediaTranscodingService::getNumOfClients");
- *_aidl_return = mTranscodingClientManager.getNumOfClients();
- return Status::ok();
-}
-
-Status MediaTranscodingService::submitRequest(int32_t /*clientId*/,
- const TranscodingRequestParcel& /*request*/,
- TranscodingJobParcel* /*job*/,
- int32_t* /*_aidl_return*/) {
- // TODO(hkuang): Add implementation.
- return Status::ok();
-}
-
-Status MediaTranscodingService::cancelJob(int32_t /*in_clientId*/, int32_t /*in_jobId*/,
- bool* /*_aidl_return*/) {
- // TODO(hkuang): Add implementation.
- return Status::ok();
-}
-
-Status MediaTranscodingService::getJobWithId(int32_t /*in_jobId*/,
- TranscodingJobParcel* /*out_job*/,
- bool* /*_aidl_return*/) {
- // TODO(hkuang): Add implementation.
+ *_aidl_return = mClientManager->getNumOfClients();
return Status::ok();
}
diff --git a/services/mediatranscoding/MediaTranscodingService.h b/services/mediatranscoding/MediaTranscodingService.h
index cc69727..f7ac336 100644
--- a/services/mediatranscoding/MediaTranscodingService.h
+++ b/services/mediatranscoding/MediaTranscodingService.h
@@ -19,15 +19,19 @@
#include <aidl/android/media/BnMediaTranscodingService.h>
#include <binder/IServiceManager.h>
-#include <media/TranscodingClientManager.h>
namespace android {
using Status = ::ndk::ScopedAStatus;
using ::aidl::android::media::BnMediaTranscodingService;
-using ::aidl::android::media::ITranscodingServiceClient;
+using ::aidl::android::media::ITranscodingClient;
+using ::aidl::android::media::ITranscodingClientCallback;
using ::aidl::android::media::TranscodingJobParcel;
using ::aidl::android::media::TranscodingRequestParcel;
+class TranscodingClientManager;
+class TranscodingJobScheduler;
+class TranscoderInterface;
+class UidPolicyInterface;
class MediaTranscodingService : public BnMediaTranscodingService {
public:
@@ -35,28 +39,21 @@
static constexpr int32_t kInvalidClientId = -1;
MediaTranscodingService();
+ MediaTranscodingService(const std::shared_ptr<TranscoderInterface>& transcoder,
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy);
virtual ~MediaTranscodingService();
static void instantiate();
static const char* getServiceName() { return "media.transcoding"; }
- Status registerClient(const std::shared_ptr<ITranscodingServiceClient>& in_client,
- const std::string& in_opPackageName, int32_t in_clientUid,
- int32_t in_clientPid, int32_t* _aidl_return) override;
-
- Status unregisterClient(int32_t clientId, bool* _aidl_return) override;
+ Status registerClient(const std::shared_ptr<ITranscodingClientCallback>& in_callback,
+ const std::string& in_clientName, const std::string& in_opPackageName,
+ int32_t in_clientUid, int32_t in_clientPid,
+ std::shared_ptr<ITranscodingClient>* _aidl_return) override;
Status getNumOfClients(int32_t* _aidl_return) override;
- Status submitRequest(int32_t in_clientId, const TranscodingRequestParcel& in_request,
- TranscodingJobParcel* out_job, int32_t* _aidl_return) override;
-
- Status cancelJob(int32_t in_clientId, int32_t in_jobId, bool* _aidl_return) override;
-
- Status getJobWithId(int32_t in_jobId, TranscodingJobParcel* out_job,
- bool* _aidl_return) override;
-
virtual inline binder_status_t dump(int /*fd*/, const char** /*args*/, uint32_t /*numArgs*/);
private:
@@ -64,7 +61,8 @@
mutable std::mutex mServiceLock;
- TranscodingClientManager& mTranscodingClientManager;
+ std::shared_ptr<TranscodingJobScheduler> mJobScheduler;
+ std::shared_ptr<TranscodingClientManager> mClientManager;
};
} // namespace android
diff --git a/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh b/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
index bcdc7f7..ce017d7 100644
--- a/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -20,4 +20,5 @@
echo "========================================"
echo "testing mediatranscodingservice"
-adb shell /data/nativetest64/mediatranscodingservice_tests/mediatranscodingservice_tests
+#adb shell /data/nativetest64/mediatranscodingservice_tests/mediatranscodingservice_tests
+adb shell /data/nativetest/mediatranscodingservice_tests/mediatranscodingservice_tests
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp
index 5a791fe..351f830 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-// Unit Test for MediaTranscoding Service.
+// Unit Test for MediaTranscodingService.
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaTranscodingServiceTest"
-#include <aidl/android/media/BnTranscodingServiceClient.h>
+#include <aidl/android/media/BnTranscodingClientCallback.h>
#include <aidl/android/media/IMediaTranscodingService.h>
-#include <aidl/android/media/ITranscodingServiceClient.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <aidl/android/media/TranscodingJobParcel.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <android/binder_ibinder_jni.h>
@@ -38,49 +41,28 @@
namespace media {
using Status = ::ndk::ScopedAStatus;
-using aidl::android::media::BnTranscodingServiceClient;
+using aidl::android::media::BnTranscodingClientCallback;
using aidl::android::media::IMediaTranscodingService;
-using aidl::android::media::ITranscodingServiceClient;
-
-constexpr int32_t kInvalidClientId = -5;
+using aidl::android::media::ITranscodingClient;
+using aidl::android::media::ITranscodingClientCallback;
+using aidl::android::media::TranscodingJobParcel;
+using aidl::android::media::TranscodingRequestParcel;
// Note that -1 is valid and means using calling pid/uid for the service. But only privilege caller could
// use them. This test is not a privilege caller.
constexpr int32_t kInvalidClientPid = -5;
-constexpr int32_t kInvalidClientUid = -5;
+constexpr const char* kInvalidClientName = "";
constexpr const char* kInvalidClientOpPackageName = "";
-constexpr int32_t kClientUseCallingPid = -1;
-constexpr int32_t kClientUseCallingUid = -1;
-constexpr const char* kClientOpPackageName = "TestClient";
+constexpr int32_t kClientUseCallingPid = IMediaTranscodingService::USE_CALLING_PID;
+constexpr int32_t kClientUseCallingUid = IMediaTranscodingService::USE_CALLING_UID;
+constexpr const char* kClientName = "TestClient";
+constexpr const char* kClientOpPackageName = "TestClientPackage";
-class MediaTranscodingServiceTest : public ::testing::Test {
-public:
- MediaTranscodingServiceTest() { ALOGD("MediaTranscodingServiceTest created"); }
+struct TestClient : public BnTranscodingClientCallback {
+ TestClient() { ALOGD("TestClient Created"); }
- void SetUp() override {
- ::ndk::SpAIBinder binder(AServiceManager_getService("media.transcoding"));
- mService = IMediaTranscodingService::fromBinder(binder);
- if (mService == nullptr) {
- ALOGE("Failed to connect to the media.trascoding service.");
- return;
- }
- }
-
- ~MediaTranscodingServiceTest() { ALOGD("MediaTranscodingingServiceTest destroyed"); }
-
- std::shared_ptr<IMediaTranscodingService> mService = nullptr;
-};
-
-struct TestClient : public BnTranscodingServiceClient {
- TestClient(const std::shared_ptr<IMediaTranscodingService>& service) : mService(service) {
- ALOGD("TestClient Created");
- }
-
- Status getName(std::string* _aidl_return) override {
- *_aidl_return = "test_client";
- return Status::ok();
- }
+ virtual ~TestClient() { ALOGI("TestClient destroyed"); }
Status onTranscodingFinished(
int32_t /* in_jobId */,
@@ -102,71 +84,125 @@
Status onProgressUpdate(int32_t /* in_jobId */, int32_t /* in_progress */) override {
return Status::ok();
}
+};
- virtual ~TestClient() { ALOGI("TestClient destroyed"); };
+class MediaTranscodingServiceTest : public ::testing::Test {
+public:
+ MediaTranscodingServiceTest() { ALOGD("MediaTranscodingServiceTest created"); }
-private:
+ ~MediaTranscodingServiceTest() { ALOGD("MediaTranscodingingServiceTest destroyed"); }
+
+ void SetUp() override {
+ ::ndk::SpAIBinder binder(AServiceManager_getService("media.transcoding"));
+ mService = IMediaTranscodingService::fromBinder(binder);
+ if (mService == nullptr) {
+ ALOGE("Failed to connect to the media.trascoding service.");
+ return;
+ }
+ mClientCallback = ::ndk::SharedRefBase::make<TestClient>();
+ mClientCallback2 = ::ndk::SharedRefBase::make<TestClient>();
+ mClientCallback3 = ::ndk::SharedRefBase::make<TestClient>();
+ }
+
+ void registerMultipleClients() {
+ // Register 3 clients.
+ Status status =
+ mService->registerClient(mClientCallback, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &mClient1);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_TRUE(mClient1 != nullptr);
+
+ status = mService->registerClient(mClientCallback2, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &mClient2);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_TRUE(mClient2 != nullptr);
+
+ status = mService->registerClient(mClientCallback3, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &mClient3);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_TRUE(mClient3 != nullptr);
+
+ // Check the number of clients.
+ int32_t numOfClients;
+ status = mService->getNumOfClients(&numOfClients);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(3, numOfClients);
+ }
+
+ void unregisterMultipleClients() {
+ // Unregister the clients.
+ Status status = mClient1->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ status = mClient2->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ status = mClient3->unregister();
+ EXPECT_TRUE(status.isOk());
+
+ // Check the number of clients.
+ int32_t numOfClients;
+ status = mService->getNumOfClients(&numOfClients);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(0, numOfClients);
+ }
+
std::shared_ptr<IMediaTranscodingService> mService;
+ std::shared_ptr<ITranscodingClientCallback> mClientCallback;
+ std::shared_ptr<ITranscodingClientCallback> mClientCallback2;
+ std::shared_ptr<ITranscodingClientCallback> mClientCallback3;
+ std::shared_ptr<ITranscodingClient> mClient1;
+ std::shared_ptr<ITranscodingClient> mClient2;
+ std::shared_ptr<ITranscodingClient> mClient3;
};
TEST_F(MediaTranscodingServiceTest, TestRegisterNullClient) {
- std::shared_ptr<ITranscodingServiceClient> client = nullptr;
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kClientUseCallingPid, &clientId);
+ std::shared_ptr<ITranscodingClient> client;
+
+ // Register the client with null callback.
+ Status status = mService->registerClient(nullptr, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
EXPECT_FALSE(status.isOk());
}
TEST_F(MediaTranscodingServiceTest, TestRegisterClientWithInvalidClientPid) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
+ std::shared_ptr<ITranscodingClient> client;
// Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kInvalidClientPid, &clientId);
+ Status status = mService->registerClient(mClientCallback, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kInvalidClientPid, &client);
EXPECT_FALSE(status.isOk());
}
-TEST_F(MediaTranscodingServiceTest, TestRegisterClientWithInvalidClientUid) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
+TEST_F(MediaTranscodingServiceTest, TestRegisterClientWithInvalidClientName) {
+ std::shared_ptr<ITranscodingClient> client;
// Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kInvalidClientUid,
- kClientUseCallingPid, &clientId);
+ Status status = mService->registerClient(mClientCallback, kInvalidClientName,
+ kInvalidClientOpPackageName, kClientUseCallingUid,
+ kClientUseCallingPid, &client);
EXPECT_FALSE(status.isOk());
}
TEST_F(MediaTranscodingServiceTest, TestRegisterClientWithInvalidClientPackageName) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
+ std::shared_ptr<ITranscodingClient> client;
// Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kInvalidClientOpPackageName,
- kClientUseCallingUid, kClientUseCallingPid, &clientId);
+ Status status =
+ mService->registerClient(mClientCallback, kClientName, kInvalidClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
EXPECT_FALSE(status.isOk());
}
TEST_F(MediaTranscodingServiceTest, TestRegisterOneClient) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
+ std::shared_ptr<ITranscodingClient> client;
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingPid,
- kClientUseCallingUid, &clientId);
- ALOGD("client id is %d", clientId);
+ Status status = mService->registerClient(mClientCallback, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
EXPECT_TRUE(status.isOk());
- // Validate the clientId.
- EXPECT_TRUE(clientId > 0);
+ // Validate the client.
+ EXPECT_TRUE(client != nullptr);
// Check the number of Clients.
int32_t numOfClients;
@@ -175,64 +211,110 @@
EXPECT_EQ(1, numOfClients);
// Unregister the client.
- bool res;
- status = mService->unregisterClient(clientId, &res);
+ status = client->unregister();
EXPECT_TRUE(status.isOk());
- EXPECT_TRUE(res);
-}
-
-TEST_F(MediaTranscodingServiceTest, TestUnRegisterClientWithInvalidClientId) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
-
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kClientUseCallingPid, &clientId);
- ALOGD("client id is %d", clientId);
- EXPECT_TRUE(status.isOk());
-
- // Validate the clientId.
- EXPECT_TRUE(clientId > 0);
// Check the number of Clients.
- int32_t numOfClients;
status = mService->getNumOfClients(&numOfClients);
EXPECT_TRUE(status.isOk());
- EXPECT_EQ(1, numOfClients);
-
- // Unregister the client with invalid ID
- bool res;
- mService->unregisterClient(kInvalidClientId, &res);
- EXPECT_FALSE(res);
-
- // Unregister the valid client.
- mService->unregisterClient(clientId, &res);
+ EXPECT_EQ(0, numOfClients);
}
TEST_F(MediaTranscodingServiceTest, TestRegisterClientTwice) {
- std::shared_ptr<ITranscodingServiceClient> client =
- ::ndk::SharedRefBase::make<TestClient>(mService);
- EXPECT_TRUE(client != nullptr);
+ std::shared_ptr<ITranscodingClient> client;
- // Register the client with the service.
- int32_t clientId = 0;
- Status status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kClientUseCallingPid, &clientId);
+ Status status = mService->registerClient(mClientCallback, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client);
EXPECT_TRUE(status.isOk());
- // Validate the clientId.
- EXPECT_TRUE(clientId > 0);
+ // Validate the client.
+ EXPECT_TRUE(client != nullptr);
// Register the client again and expects failure.
- status = mService->registerClient(client, kClientOpPackageName, kClientUseCallingUid,
- kClientUseCallingPid, &clientId);
+ std::shared_ptr<ITranscodingClient> client1;
+ status = mService->registerClient(mClientCallback, kClientName, kClientOpPackageName,
+ kClientUseCallingUid, kClientUseCallingPid, &client1);
EXPECT_FALSE(status.isOk());
- // Unregister the valid client.
- bool res;
- mService->unregisterClient(clientId, &res);
+ // Unregister the client.
+ status = client->unregister();
+ EXPECT_TRUE(status.isOk());
+}
+
+TEST_F(MediaTranscodingServiceTest, TestRegisterMultipleClients) {
+ registerMultipleClients();
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceTest, TestSubmitCancelGetJobs) {
+ registerMultipleClients();
+
+ // Test jobId assignment.
+ TranscodingRequestParcel request;
+ request.fileName = "test_file_0";
+ TranscodingJobParcel job;
+ bool result;
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
+
+ request.fileName = "test_file_1";
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 1);
+
+ request.fileName = "test_file_2";
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 2);
+
+ // Test submit bad request (no valid fileName) fails.
+ TranscodingRequestParcel badRequest;
+ EXPECT_TRUE(mClient1->submitRequest(badRequest, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test get jobs by id.
+ EXPECT_TRUE(mClient1->getJobWithId(2, &job, &result).isOk());
+ EXPECT_EQ(job.jobId, 2);
+ EXPECT_EQ(job.request.fileName, "test_file_2");
+ EXPECT_TRUE(result);
+
+ // Test get jobs by invalid id fails.
+ EXPECT_TRUE(mClient1->getJobWithId(100, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test cancel non-existent job fail.
+ EXPECT_TRUE(mClient2->cancelJob(100, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test cancel valid jobId in arbitrary order.
+ EXPECT_TRUE(mClient1->cancelJob(2, &result).isOk());
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(mClient1->cancelJob(0, &result).isOk());
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(mClient1->cancelJob(1, &result).isOk());
+ EXPECT_TRUE(result);
+
+ // Test cancel job again fails.
+ EXPECT_TRUE(mClient1->cancelJob(1, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test get job after cancel fails.
+ EXPECT_TRUE(mClient1->getJobWithId(2, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test jobId independence for each client.
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
+
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 1);
+
+ unregisterMultipleClients();
}
} // namespace media