All examples can be found in the project's OrbbecSdkExamples directory.

| Name | Language | Description |
| --- | --- | --- |
| HelloOrbbec | Java | Demonstrate connect to device to get SDK version and device information |
| DepthViewer | Java | Open the specified configuration depth stream through the Pipeline and render it |
| ColorViewer | Java | Turn on the specified configuration color stream through the Pipeline and render it |
| InfraredViewer | Java | Open the specified configuration infrared stream through Pipeline and render it |
| DoubleIRViewer | Java | Open the specified configuration IR_LEFT & IR_RIGHT stream through Pipeline and render it |
| SyncAlignViewer | Java | Demonstrate operations on sensor data stream alignment |
| HotPlugin | Java | Demonstrate the settings of the device plug and unplug callback, and get the stream processed after plugging and unplugging |
| IMUReader | Java | Get IMU data and output display |
| MultiDevice | Java | Demonstrate operation on multiple devices |
| PointCloud | Java | Demonstrate the generation of depth point cloud or RGBD point cloud and save it as ply format file |
| SaveToDisk | Java | Get color storage in png format, depth storage in raw format and format conversion through filter |
| SensorControl | Java | Demonstrate manipulation of device and sensor control commands |
| Record and playback example | Java | Connect the device to open the stream, record the current video stream to a file, and load the video file for playback. |
# Android
## Common--BaseActivity
### OrbbecSdkExamples’ Activity inheritance relationship
```txt
                                 BaseActivity
        ______________________________|____________________________________
        |                      |                     |                    |
HelloOrbbecActivity   ColorViewerActivity   ......(XXXActivity)   DepthViewerActivity
```
HelloOrbbecActvity and so on are inherited from BaseActivity. BaseActivity mainly provides the following functions:
1. Initialization and release of OBContext;
2. Resolution acquisition;

### Initialization and release of OBContext
OBContext is the entrance to Orbbec SDK, which configures log information and manages devices. An application can only have one OBContext instance. In OrbbecSdkExamples, each Activity inherits from BaseActivity. It is actually initialized at BaseActivity#onStart() and released at BaseActivity#onStop(). One OBContext is always maintained.
Note: If the application software has multiple OBContext instances at the same time, an error may occur.

BaseActivity.java demonstrates the initialization and release implementation of OBContext
```java
/**
 * OBContext is entry of OrbbecSDK, and support only one instance.
 */
protected OBContext mOBContext;

protected abstract DeviceChangedCallback getDeviceChangedCallback();

protected void initSDK() {
    try {
        if (BuildConfig.DEBUG) {
            // set debug level in code
            OBContext.setLoggerSeverity(LogSeverity.DEBUG);
        }

        DeviceChangedCallback deviceChangedCallback = getDeviceChangedCallback();

        // 1.Initialize the SDK Context and listen device changes
        String configFilePath = getXmlConfigFile();
        if (!TextUtils.isEmpty(configFilePath)) {
            mOBContext = new OBContext(getApplicationContext(), configFilePath, deviceChangedCallback);
        } else {
            mOBContext = new OBContext(getApplicationContext(), deviceChangedCallback);
        }
    } catch (OBException e) {
        e.printStackTrace();
    }
}

protected void releaseSDK() {
    try {
        // Release SDK Context
        if (null != mOBContext) {
            mOBContext.close();
        }
    } catch (OBException e) {
        e.printStackTrace();
    }
}
```

Execute the initialization and release of OBContext, taking HelloOrbbecActivity.java as an example
```java
@Override
protected void onStart() {
    super.onStart();
    initSDK();
}

@Override
protected void onStop() {
    releaseSDK();
    super.onStop();
}
```
Because the samples of OrbbecSdkExamples are implemented in independent forms, Orbbec SDK is initialized every time Actiivty#onStart() and Orbbec SDK is released every time Activity#onStop().
Note: This call in the subclass Activity is for developers to know the initialization and release sequence of the SDK when reading the code. In actual application development, an application must have only one OBContext instance.

### Device management
Reading BaseActivity of OrbbecSdkExamples, you can notice that BaseActivity is an abstract class and the abstract method is
```java
protected abstract DeviceChangedCallback getDeviceChangedCallback();
```
The subclass implements the interface DeviceChangedCallback, and returns the corresponding callback object in the implemented abstract method getDeviceChangedCallback();
DeviceChangedCallback is a device change callback interface. When the device connected to the host computer (Host) changes, Orbbec SDK will notify the application layer through the DeviceChangedCallback callback.

OBContext provides two constructors, one can pass a custom configuration file, and the other does not need to pass a custom configuration file.
```java
public OBContext(Context context, DeviceChangedCallback callback);
public OBContext(Context context, String configPath, DeviceChangedCallback callback);
```

#### DeviceChangedCallback Implementation
Manage device changes in DeviceChangedCallback,
```java
// Listen for device changes
private DeviceChangedCallback mDeviceChangedCallback = new DeviceChangedCallback() {
    @Override
    public void onDeviceAttach(DeviceList deviceList) {
        // deviceList needs close to release resources
        try {
            int deviceCount = deviceList.getDeviceCount();
            for (int i = 0; i < deviceCount; ++i) {
                if (null == mDevice) {
                    // Cautious: Same index inside deviceList can call DeviceList.getDevice(index) only once.
                    mDevice = deviceList.getDevice(i); // Remember to close when mDevice no more use
                    mDeviceInfo = mDevice.getInfo(); // Remember to close when mDeviceInfo no more use

                    // use device to do something
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // always release deviceList
            try {
              deviceList.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onDeviceDetach(DeviceList deviceList) {
        // deviceList needs close to release resources
        try {
            int deviceCount = deviceList.getDeviceCount();
            for (int i = 0; i < deviceCount; ++i) {
                // Get the uid of the disconnected device
                String uid = deviceList.getUid(i);
                // Check is current device disconnection
                if (null != mDevice && mDeviceInfo.getUid().equals(uid)) {
                    // stop stream if application has start stream
                    // close pipelines if has pipleline create from mDevice

                    // release device info
                    mDeviceInfo.close();
                    mDeviceInfo = null;
                    // release device
                    mDevice.close();
                    mDevice = null;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // always release deviceList
            try {
              deviceList.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
};

// Subclasses of BaseActivity should return callback to satisfy OBContext initialization
protected DeviceChangedCallback getDeviceChangedCallback() {
    return mDeviceChangedCallback;
}
```

### Resolution acquisition (single camera)
The OrbbecSdkExamples instance has many functions that need to obtain the camera resolution and open the camera; since OrbbecSdkExamples is a simple instance and only supports rendering some resolution formats, unsupported resolution formats are filtered out when obtaining the resolution.
Get resolution through Pipeline and SensorType
```java
/**
 * Get best VideoStreamProfile of pipeline support by OrbbecSdkExamples.
 * Note: OrbbecSdkExamples just sample code to render and save frame, it support limit VideoStreamProfile Format.
 * @param pipeline Pipeline
 * @param sensorType Target Sensor Type
 * @return If success return a VideoStreamProfile, otherwise return null.
 */
protected final VideoStreamProfile getStreamProfile(Pipeline pipeline, SensorType sensorType) {
    // Select prefer Format
    Format format;
    if (sensorType == SensorType.COLOR) {
        format = Format.RGB888;
    } else if (sensorType == SensorType.IR
            || sensorType == SensorType.IR_LEFT
            || sensorType == SensorType.IR_RIGHT) {
        format = Format.Y8;
    } else if (sensorType == SensorType.DEPTH) {
        format = Format.Y16;
    } else {
        Log.w(TAG, "getStreamProfile not support sensorType: " + sensorType);
        return null;
    }

    try {
        // Get StreamProfileList from sensor
        StreamProfileList profileList = pipeline.getStreamProfileList(sensorType);
        List<VideoStreamProfile> profiles = new ArrayList<>();
        for (int i = 0, N = profileList.getStreamProfileCount(); i < N; i++) {
            // Get StreamProfile by index and convert it to VideoStreamProfile
            VideoStreamProfile profile = profileList.getStreamProfile(i).as(StreamType.VIDEO);
            // Match target with and format.
            // Note: width >= 640 && width <= 1280 is consider best render for OrbbecSdkExamples
            if ((profile.getWidth() >= 640 && profile.getWidth() <= 1280) && profile.getFormat() == format) {
                profiles.add(profile);
            } else {
                profile.close();
            }
        }
        // If not match StreamProfile with prefer format, Try other.
        // Note: OrbbecSdkExamples not support render Format of MJPEG and RVL
        if (profiles.isEmpty() && profileList.getStreamProfileCount() > 0) {
            for (int i = 0, N = profileList.getStreamProfileCount(); i < N; i++) {
                VideoStreamProfile profile = profileList.getStreamProfile(i).as(StreamType.VIDEO);
                if ((profile.getWidth() >= 640 && profile.getWidth() <= 1280)
                        && (profile.getFormat() != Format.MJPG && profile.getFormat() != Format.RVL)) {
                    profiles.add(profile);
                } else {
                    profile.close();
                }
            }
        }
        // Release StreamProfileList
        profileList.close();

        // Sort VideoStreamProfile list and let recommend profile at first
        // Priority:
        // 1. high fps at first.
        // 2. large width at first
        // 3. large height at first
        Collections.sort(profiles, new Comparator<VideoStreamProfile>() {
            @Override
            public int compare(VideoStreamProfile o1, VideoStreamProfile o2) {
                if (o1.getFps() != o2.getFps()) {
                    return o2.getFps() - o1.getFps();
                }
                if (o1.getWidth() != o2.getWidth()) {
                    return o2.getWidth() - o1.getWidth();
                }
                return o2.getHeight() - o1.getHeight();
            }
        });
        for (VideoStreamProfile p : profiles) {
            Log.d(TAG, "getStreamProfile " + p.getWidth() + "x" + p.getHeight() + "--" + p.getFps());
        }

        if (profiles.isEmpty()) {
            return null;
        }

        // Get first stream profile which is the best for OrbbecSdkExamples.
        VideoStreamProfile retProfile = profiles.get(0);
        // Release other stream profile
        for (int i = 1; i < profiles.size(); i++) {
            profiles.get(i).close();
        }
        return retProfile;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
```

### Get D2C resolution
When d2c is aligned, you need to check whether the color and depth match, otherwise it will cause a D2C exception. The following example shows how to obtain the matching D2C resolution.

For convenience, a D2C Java bean is defined to hold the matching color and depth resolutions.
```java
/**
 * Data bean bundle VideoStreamProfile of depth and color
 */
protected static class D2CStreamProfile implements AutoCloseable {
    // color stream profile
    private VideoStreamProfile colorProfile;
    // depth stream profile
    private VideoStreamProfile depthProfile;

    // getter && setter
}
```

The following code shows how to obtain a set of D2C resolutions. Since OrbbecSdkExamples supports limited rendering resolution formats, the resolution format and width and height are also filtered during the acquisition process.
```java
/**
 * Get D2CStreamProfile which contain color and depth
 * @param pipeline
 * @param alignMode
 * @return Success: D2CStreamProfile which contain color and depth. Failure: null.
 */
protected D2CStreamProfile genD2CStreamProfile(Pipeline pipeline, AlignMode alignMode) {
    // Config color profile
    VideoStreamProfile colorProfile = null;
    List<VideoStreamProfile> colorProfiles = getAvailableColorProfiles(pipeline, alignMode);
    if (colorProfiles.isEmpty()) {
        Log.w(TAG, "genConfig failed. colorProfiles is empty");
        return null;
    }
    for (VideoStreamProfile profile : colorProfiles) {
        if (profile.getWidth() >= 640 && profile.getWidth() <= 1280 && profile.getFormat() == Format.RGB888) {
            colorProfile = profile;
            break;
        }
    }
    if (null == colorProfile) {
        if (colorProfiles.size() > 0) {
            colorProfile = colorProfiles.get(0);
        } else {
            Log.w(TAG, "genConfig failed. not match color profile width >= 640 and width <= 1280");
            return null;
        }
    }
    // Release colorProfiles resource
    for (VideoStreamProfile profile : colorProfiles) {
        if (profile != colorProfile) {
            profile.close();
        }
    }
    colorProfiles.clear();

    // Config depth profile
    VideoStreamProfile depthProfile = null;
    List<VideoStreamProfile> depthProfiles = getAvailableDepthProfiles(pipeline, colorProfile, alignMode);
    for (VideoStreamProfile profile : depthProfiles) {
        if (profile.getWidth() >= 640 && profile.getWidth() <= 1280 && profile.getFormat() == Format.Y16) {
            depthProfile = profile;
            break;
        }
    }
    if (null == depthProfile) {
        if (depthProfiles.size() > 0) {
            depthProfile = depthProfiles.get(0);
        } else {
            Log.w(TAG, "genConfig failed. not match depth profile width >= 640 and width <= 1280");
            colorProfile.close();
            colorProfile = null;
            return null;
        }
    }
    // Release depthProfiles resource
    for (VideoStreamProfile profile : depthProfiles) {
        if (depthProfile != profile) {
            profile.close();
        }
    }
    depthProfiles.clear();

    D2CStreamProfile d2CStreamProfile = new D2CStreamProfile();
    d2CStreamProfile.colorProfile = colorProfile;
    d2CStreamProfile.depthProfile = depthProfile;
    return d2CStreamProfile;
}
```

Enumeration can support D2C color resolution VideoStreamProfile
```java
/**
 * Get available color profiles with AlignMode. If alignMode is ALIGN_D2C_HW_ENABLE or ALIGN_D2C_SW_ENABLE
 *     Not all color stream profile has match depth stream profile list, This function will filter the color stream profile
 *     when it match any depth stream profile under target alignMode.
 * @param pipeline
 * @param alignMode
 * @return Color stream profile list that has supported depth stream profiles.
 */
private List<VideoStreamProfile> getAvailableColorProfiles(Pipeline pipeline, AlignMode alignMode) {
    List<VideoStreamProfile> colorProfiles = new ArrayList<>();
    StreamProfileList depthProfileList = null;
    try (StreamProfileList colorProfileList = pipeline.getStreamProfileList(SensorType.COLOR)) {
        final int profileCount = colorProfileList.getStreamProfileCount();
        for (int i = 0; i < profileCount; i++) {
            colorProfiles.add(colorProfileList.getStreamProfile(i).as(StreamType.VIDEO));
        }
        sortVideoStreamProfiles(colorProfiles);

        // All depth profile are available when D2C is disalbe
        if (alignMode == AlignMode.ALIGN_D2C_DISABLE) {
            return colorProfiles;
        }

        // Filter color profile which unsupported depth profile
        for (int i = colorProfiles.size() - 1; i >= 0; i--) {
            VideoStreamProfile colorProfile = colorProfiles.get(i);
            depthProfileList = pipeline.getD2CDepthProfileList(colorProfile, alignMode);
            if (null == depthProfileList || depthProfileList.getStreamProfileCount() == 0) {
                colorProfiles.remove(i);
                colorProfile.close();
            }
            // Release depthProfileList
            depthProfileList.close();
            depthProfileList = null;
        }
        return colorProfiles;
    } catch (OBException e) {
        e.printStackTrace();
    } finally {
        // Release depthProfileList when encounter OBException
        if (null != depthProfileList) {
            depthProfileList.close();
            depthProfileList = null;
        }
    }
    return colorProfiles;
}
```

Check whether the color resolution has a supported depth resolution. If there is a supported depth resolution VideoStreamProfile, then return the corresponding resolution list; if there is no supported depth resolution VideoStreamProfile, then return an empty list;
```java
/**
    * Get target depth stream profile list with target color stream profile and alignMode
    * @param pipeline Pipeline
    * @param colorProfile Target color stream profile
    * @param alignMode Target alignMode
    * @return Depth stream profile list associate with target color stream profile.
    *     Success: depth stream profile list has elements. Failure: depth stream profile list is empty.
    */
private List<VideoStreamProfile> getAvailableDepthProfiles(Pipeline pipeline, VideoStreamProfile colorProfile, AlignMode alignMode) {
    List<VideoStreamProfile> depthProfiles = new ArrayList<>();
    try (StreamProfileList depthProfileList = pipeline.getD2CDepthProfileList(colorProfile, alignMode)) {
        final int profileCount = depthProfileList.getStreamProfileCount();
        for (int i = 0; i < profileCount; i++) {
            depthProfiles.add(depthProfileList.getStreamProfile(i).as(StreamType.VIDEO));
        }
        sortVideoStreamProfiles(depthProfiles);
    } catch (OBException e) {
        e.printStackTrace();
    }
    return depthProfiles;
}
```

D2C resolution VideoStreamProfile list sorting rules, the same resolution formats are put together, width is arranged in ascending order (the smallest is placed at the front), height is arranged in descending order (the largest is placed at the front), and frame rate is arranged in descending order (the highest frame rate is placed at the front)
```java
private DeviceChangedCallback mDeviceChangedCallback = new DeviceChangedCallback() {
    @Override
    public void onDeviceAttach(DeviceList deviceList) {
        Log.d(TAG, "onDeviceAttach");
        try {
            deviceList.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        dumpDevices();
    }

    @Override
    public void onDeviceDetach(DeviceList deviceList) {
        Log.d(TAG, "onDeviceDetach");
        try {
            deviceList.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        dumpDevices();
    }
};

private void sortVideoStreamProfiles(List<VideoStreamProfile> profiles) {
    Collections.sort(profiles, new Comparator<VideoStreamProfile>() {
        @Override
        public int compare(VideoStreamProfile o1, VideoStreamProfile o2) {
            if (o1.getFormat() != o2.getFormat()) {
                return o1.getFormat().value() - o2.getFormat().value();
            }
            if (o1.getWidth() != o2.getWidth()) {
                // Little first
                return o1.getWidth() - o2.getWidth();
            }
            if (o1.getHeight() != o2.getHeight()) {
                // Large first
                return o2.getHeight() - o1.getHeight();
            }
            // Large first
            return o2.getFps() - o1.getFps();
        }
    });
}
```


## HelloOrbbec
Function description: Used to demonstrate SDK initialization, obtain SDK version, obtain device model, obtain device serial number, obtain firmware version number, and SDK release resources.
```java
// Get device information
DeviceInfo info = mDevice.getInfo();
builder.append("Name: " + info.getName() + "\n");
builder.append("Vid: " + LocalUtils.formatHex04(info.getVid()) + "\n");
builder.append("Pid: " + LocalUtils.formatHex04(info.getPid()) + "\n");
builder.append("Uid: " + info.getUid() + "\n");
builder.append("SN: " + info.getSerialNumber() + "\n");
builder.append("connectType: " + info.getConnectionType() + "\n");
String firmwareVersion = info.getFirmwareVersion();
builder.append("FirmwareVersion: " + firmwareVersion + "\n");
builder.append(dumpExtensionInfo(info.getExtensionInfo()));

// Iterate through the sensors of the current device
for (Sensor sensor : device.querySensors()) {
    // 8.Query sensor type
    builder.append("Sensor:    " + sensor.getType() + "\n");
}

// Release device information
info.close();
```

## ColorViewer
Function description: This example mainly demonstrates the initialization of the SDK, device creation, Pipeline initialization and configuration, and opening and rendering of the specified configuration color stream through the Pipeline.

Build Pipeline from mDevice and open the stream
```java
// check sensor Color exists
Sensor colorSensor = mDevice.getSensor(SensorType.COLOR);
if (null == colorSensor) {
    mDevice.close();
    mDevice = null;
    return;
}
// Create Device and initialize Pipeline through Device
mPipeline = new Pipeline(mDevice);

// Get stream profile BaseActivity#getStreamProfile(Pipeline, SensorType)
VideoStreamProfile streamProfile = getStreamProfile(mPipeline, SensorType.COLOR);

// Create Pipeline configuration
Config config = new Config();

// 6.Enable color StreamProfile
if (null != streamProfile) {
    printStreamProfile(streamProfile.as(StreamType.VIDEO));
    config.enableStream(streamProfile);
    streamProfile.close();
} else {
    config.close();
    Log.w(TAG, "No target stream profile!");
    return;
}

// Start sensor stream
mPipeline.start(config);

// Release config
config.close();
```

Read Color image data from mPipeline in a child thread and render it
```java
private Runnable mStreamRunnable = () -> {
    ByteBuffer buffer = null;
    while (mIsStreamRunning) {
        try {
            // Obtain the data set in blocking mode. If it cannot be obtained after waiting for 100ms, it will time out.
            FrameSet frameSet = mPipeline.waitForFrameSet(100);

            Log.d(TAG, "frameSet=" + frameSet);
            if (null == frameSet) {
                continue;
            }

            // Get color flow data
            ColorFrame colorFrame = frameSet.getColorFrame();
            if (null != buffer) {
                buffer.clear();
            }

            Log.d(TAG, "frameSet=" + frameSet + ", colorFrame=" + colorFrame);
            if (null != colorFrame) {
                Log.d(TAG, "color frame: " + colorFrame.getSystemTimeStamp());
                // Initialize buffer
                int dataSize = colorFrame.getDataSize();
                if (null == buffer || buffer.capacity() != dataSize) {
                    buffer = ByteBuffer.allocateDirect(dataSize);
                }
                // Get data and render
                colorFrame.getData(buffer);
                mColorView.update(colorFrame.getWidth(), colorFrame.getHeight(), StreamType.COLOR, colorFrame.getFormat(), buffer, 1.0f);

                // Release color data frame
                colorFrame.close();
            }
            // Release FrameSet
            frameSet.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};
```

Stop stream
```java
try {
    // Stop the Pipeline and release
    if (null != mPipeline) {
        mPipeline.stop();
        mPipeline.close();
        mPipeline = null;
    }
} catch (Exception e) {
    e.printStackTrace();
}
```

## DepthViewer
Function description: This example mainly demonstrates the initialization of the SDK, device creation, Pipeline initialization and configuration, and opening and rendering of the specified configuration depth stream through the Pipeline.
Build Pipeline from mDevice and open the stream
```java
// Check sensor Depth exists
Sensor depthSensor = mDevice.getSensor(SensorType.DEPTH);
if (null == depthSensor) {
    mDevice.close();
    mDevice = null;
    return;
}
// Create Device and initialize Pipeline through Device
mPipeline = new Pipeline(mDevice);

// Get stream profile BaseActivity#getStreamProfile(Pipeline, SensorType)
VideoStreamProfile streamProfile = getStreamProfile(mPipeline, SensorType.DEPTH);

// Create Pipeline configuration
Config config = new Config();

// 6.Enable depth StreamProfile
if (null != streamProfile) {
    printStreamProfile(streamProfile.as(StreamType.VIDEO));
    config.enableStream(streamProfile);
    streamProfile.close();
} else {
    config.close();
    Log.w(TAG, "No target stream profile!");
    return;
}

// Start sensor stream
mPipeline.start(config);

// Release config
config.close();
```

Read depth image data from mPipeline in a child thread and render it
```java
private Runnable mStreamRunnable = () -> {
    ByteBuffer buffer = null;
    while (mIsStreamRunning) {
        try {
            // Obtain the data set in blocking mode. If it cannot be obtained after waiting for 100ms, it will time out.
            FrameSet frameSet = mPipeline.waitForFrameSet(100);

            Log.d(TAG, "frameSet=" + frameSet);
            if (null == frameSet) {
                continue;
            }

            // Get depth flow data
            DepthFrame depthFrame = frameSet.getDepthFrame();
            if (null != buffer) {
                buffer.clear();
            }

            Log.d(TAG, "frameSet=" + frameSet + ", depthFrame=" + depthFrame);
            if (null != depthFrame) {
                Log.d(TAG, "depth frame: " + depthFrame.getSystemTimeStamp());
                // Initialize buffer
                int dataSize = depthFrame.getDataSize();
                if (null == buffer || buffer.capacity() != dataSize) {
                    buffer = ByteBuffer.allocateDirect(dataSize);
                }
                // Get data and render
                depthFrame.getData(buffer);
                mDepthView.update(frame.getWidth(), frame.getHeight(), StreamType.DEPTH, frame.getFormat(), frameData, frame.getValueScale());

                // Release depth data frame
                depthFrame.close();
            }
            // Release FrameSet
            frameSet.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};
```

Stop stream
```java
try {
    // Stop the Pipeline and release
    if (null != mPipeline) {
        mPipeline.stop();
        mPipeline.close();
        mPipeline = null;
    }
} catch (Exception e) {
    e.printStackTrace();
}
```


## InfraredViewer
Function description: This example mainly demonstrates the initialization of the SDK, device creation, Pipeline initialization and configuration, and opening and rendering of the specified configuration infrared stream through the Pipeline.

Build Pipeline from mDevice and open the stream
```java
// Check sensor IR exists
Sensor irSensor = mDevice.getSensor(SensorType.IR);
if (null == irSensor) {
    mDevice.close();
    mDevice = null;
    return;
}
// Create Device and initialize Pipeline through Device
mPipeline = new Pipeline(mDevice);

// Get stream profile BaseActivity#getStreamProfile(Pipeline, SensorType)
VideoStreamProfile streamProfile = getStreamProfile(mPipeline, SensorType.IR);

// Create Pipeline configuration
Config config = new Config();

// 6.Enable IR StreamProfile
if (null != streamProfile) {
    printStreamProfile(streamProfile.as(StreamType.VIDEO));
    config.enableStream(streamProfile);
    streamProfile.close();
} else {
    config.close();
    Log.w(TAG, "No target stream profile!");
    return;
}

// Start sensor stream
mPipeline.start(config);

// Release config
config.close();
```

Read IR image data from mPipeline in a child thread and render it
```java
private Runnable mStreamRunnable = () -> {
    while (mIsStreamRunning) {
        try {
            // Obtain the data set in blocking mode. If it cannot be obtained after waiting for 100ms, it will time out.
            FrameSet frameSet = mPipeline.waitForFrameSet(100);

            if (null == frameSet) {
                continue;
            }

            // Get Infrared flow data
            IRFrame frame = frameSet.getIrFrame();

            if (frame != null) {
                // Get infrared data and render it
                byte[] frameData = new byte[frame.getDataSize()];
                frame.getData(frameData);
                mIrView.update(frame.getWidth(), frame.getHeight(), StreamType.IR, frame.getFormat(), frameData, 1.0f);

                // Release infrared data frame
                frame.close();
            }

            // Release FrameSet
            frameSet.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};
```

Stop stream
```java
try {
    // Stop the Pipeline and release
    if (null != mPipeline) {
        mPipeline.stop();
        mPipeline.close();
        mPipeline = null;
    }
} catch (Exception e) {
    e.printStackTrace();
}
```

## DoubleIRViewer
Dual IR example shows how to open the left and right IR at the same time, only some products support it.

Create mPipeline configuration to open left and right IR streams
```java
Sensor irLeftSensor = mDevice.getSensor(SensorType.IR_LEFT);
if (null == irLeftSensor) {
    showToast(getString(R.string.device_not_support_ir));
    mDevice.close();
    mDevice = null;
    return;
}

Sensor irRightSensor = mDevice.getSensor(SensorType.IR_RIGHT);
if (null == irRightSensor) {
    showToast(getString(R.string.device_not_support_ir_right));
    mDevice.close();
    mDevice = null;
    return;
}

mPipeline = new Pipeline(mDevice);

// 3.Initialize stream profile
Config config = initStreamProfile(mPipeline);
if (null == config) {
    showToast(getString(R.string.init_stream_profile_failed));
    mPipeline.close();
    mPipeline = null;
    mDevice.close();
    mDevice = null;
    config.close();
    return;
}

// 4.Start sensor stream
mPipeline.start(config);

// 5.Release config
config.close();

// 6.Create a thread to obtain Pipeline data
start();
```

initStreamProfile(Pipeline) Implementation
```java
private Config initStreamProfile(Pipeline pipeline) {
    // 1.Create Pipeline configuration
    Config config = new Config();

    SensorType sensorTypes[] = {SensorType.IR_LEFT, SensorType.IR_RIGHT};
    for (SensorType sensorType : sensorTypes) {
        // Obtain the stream profile and configure it to Config, where the matching
        // is performed according to the width and frame rate, and the matching satisfies
        // the configuration with a width of 640 and a frame rate of 30fps
        VideoStreamProfile irStreamProfile = getStreamProfile(pipeline, sensorType);
        // Enable ir left StreamProfile
        if (null != irStreamProfile) {
            Log.d(TAG, "irStreamProfile: " + sensorType);
            printStreamProfile(irStreamProfile.as(StreamType.VIDEO));
            config.enableStream(irStreamProfile);
            irStreamProfile.close();
        } else {
            Log.w(TAG, ": No target stream profile! ir left stream profile is null");
            config.close();
            return null;
        }
    }
    return config;
}
```

Get left and right IR image data and render
```java
    private Runnable mStreamRunnable = () -> {
        FrameType frameTypes[] = {FrameType.IR_LEFT, FrameType.IR_RIGHT};
        while (mIsStreamRunning) {
            try {
                // Obtain the data set in blocking mode. If it cannot be obtained after waiting for 100ms, it will time out.
                FrameSet frameSet = mPipeline.waitForFrameSet(100);

                if (null == frameSet) {
                    continue;
                }

                // Get Infrared flow data
                for (int i = 0; i < frameTypes.length; i++) {
                    IRFrame frame = frameSet.getFrame(frameTypes[i]);

                    if (frame != null) {
                        // Get infrared data and render it
                        byte[] frameData = new byte[frame.getDataSize()];
                        frame.getData(frameData);

                        OBGLView glView = frameTypes[i] == FrameType.IR_LEFT ? mIrLeftView : mIrRightView;
                        glView.update(frame.getWidth(), frame.getHeight(), StreamType.IR, frame.getFormat(), frameData, 1.0f);

                        // Release infrared data frame
                        frame.close();
                    }
                }

                // Release FrameSet
                frameSet.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
```

Stop stream
```java
try {
    // Stop the Pipeline and release
    if (null != mPipeline) {
        mPipeline.stop();
        mPipeline.close();
        mPipeline = null;
    }
} catch (Exception e) {
    e.printStackTrace();
}
```

## SyncAlignViewer
Function description: This example mainly demonstrates the operation of data flow control alignment. This example may cause the depth image and color image mirroring status to be inconsistent due to the depth or color sensor not supporting mirroring.
As a result, the images displayed by the depth map and the color map are opposite. If this happens, just keep the two mirror states consistent by setting the mirror interface.
In addition, there may be cases where the resolution obtained by some devices does not support the D2C function, so the D2C function is subject to the actual supported D2C resolution.
For example: The resolution of D2C supported by DaBai DCW is 640x360, but the actual resolution obtained by this example may be 640x480. In this case, users can modify it to a resolution of 640x360.

In the basic module BaseActivity, we introduced genD2CStreamProfile(). Stream alignment requires configuring both the depth and color resolutions, as well as the alignment type. The implementation method is as follows:
```java
private Config genD2CConfig(Pipeline pipeline, AlignMode alignMode) {
    // Get D2CStreamProfile BaseActivity#genD2CStreamProfile(Pipeline, AlignMode)
    D2CStreamProfile d2CStreamProfile = genD2CStreamProfile(pipeline, alignMode);
    if (null == d2CStreamProfile) {
        return null;
    }

    // Update color information to UI
    VideoStreamProfile colorProfile = d2CStreamProfile.getColorProfile();
    // Update depth information to UI
    VideoStreamProfile depthProfile = d2CStreamProfile.getDepthProfile();

    Config config = new Config();
    // set D2C AlignMode
    config.setAlignMode(alignMode);
    config.enableStream(d2CStreamProfile.getColorProfile());
    config.enableStream(d2CStreamProfile.getDepthProfile());
    d2CStreamProfile.close();
    return config;
}
```

Build the Pipeline and open the stream
```java
if (null == mDevice.getSensor(SensorType.DEPTH)) {
    mDevice.close();
    mDevice = null;
    showToast(getString(R.string.device_not_support_depth));
    return;
}

if (null == mDevice.getSensor(SensorType.COLOR)) {
    mDevice.close();
    mDevice = null;
    showToast(getString(R.string.device_not_support_color));
    return;
}

mPipeline = new Pipeline(mDevice);

// Create Pipeline configuration
mConfig = genD2CConfig(mPipeline, AlignMode.ALIGN_D2C_DISABLE);
if (null == mConfig) {
    mPipeline.close();
    mPipeline = null;
    mDevice.close();
    mDevice = null;
    Log.w(TAG, "onDeviceAttach: No target depth and color stream profile!");
    showToast(getString(R.string.init_stream_profile_failed));
    return;
}

// Open the stream through config
mPipeline.start(mConfig);

// Create a thread to obtain Pipeline data
start();
```

## Get data and render
```java
private Runnable mStreamRunnable = () -> {
    while (mIsStreamRunning) {
        try {
            // If it cannot be obtained after waiting for 100ms, it will time out.
            FrameSet frameSet = mPipeline.waitForFrameSet(100);

            if (null == frameSet) {
                continue;
            }

            // Get depth streaming data
            DepthFrame depthFrame = frameSet.getDepthFrame();

            // Get color stream data
            ColorFrame colorFrame = frameSet.getColorFrame();

            // Depth and color overlay rendering
            depthOverlayColorProcess(depthFrame, colorFrame);

            // Release depth frame
            if (null != depthFrame) {
                depthFrame.close();
            }

            // Release color frames
            if (null != colorFrame) {
                colorFrame.close();
            }

            // Release frameSet
            frameSet.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};
```
The color and depth data obtained by depthOverlayColorProcess() are decoded and rendered. For the specific code, please see the OrbbecSdkExamples source code SyncAlignViewerActivity.java

Stop stream
```java
try {
    // Stop the Pipeline and release
    if (null != mPipeline) {
        mPipeline.stop();
        mPipeline.close();
        mPipeline = null;
    }
} catch (Exception e) {
    e.printStackTrace();
}
```


## HotPlugin
Function description: This example mainly demonstrates the setting of the callback for device unplugging and the processing of the obtained stream after unplugging.

Define Color's FrameCallback
```java
private FrameCallback mColorFrameCallback = frame -> {
	printFrameInfo(frame.as(FrameType.COLOR), mColorFps);

	// Release frame
	frame.close();
};

```
FrameCallback that defines Depth
```java
private FrameCallback mDepthFrameCallback = frame -> {
    printFrameInfo(frame.as(FrameType.DEPTH), mDepthFps);

    // Release frame
    frame.close();
};
```
Define IR's FrameCallback
```java
private FrameCallback mIrFrameCallback = frame -> {
    printFrameInfo(frame.as(FrameType.IR), mIrFps);

    // Release frame
    frame.close();
};
```
Print data frame information
```java
private void printFrameInfo(VideoFrame frame, int fps) {
	try {
		String frameInfo = "FrameType:" + frame.getStreamType()
				+ ", index:" + frame.getFrameIndex()
				+ ", width:" + frame.getWidth()
				+ ", height:" + frame.getHeight()
				+ ", format:" + frame.getFormat()
				+ ", fps:" + fps
				+ ", timeStampUs:" + frame.getTimeStampUs();
		if (frame.getStreamType() == FrameType.DEPTH) {
			frameInfo += ", middlePixelValue:" + getMiddlePixelValue(frame);
		}
		Log.i(TAG, frameInfo);
	} catch (Exception e) {
		e.printStackTrace();
	}
}
```
Listen for device changes
```java
// Implement monitoring device changes
private DeviceChangedCallback mDeviceChangedCallback new DeviceChangedCallback() {
    @Override
    public void onDeviceAttach(DeviceList deviceList) {
        try {
            if (deviceList == null || deviceList.getDeviceCount() <= 0) {
                setText(mNameTv, "No device connected !");
            }
            // Create a device and get the device name
            mDevice = deviceList.getDevice(0);
            DeviceInfo devInfo = mDevice.getInfo();
            String deviceName = devInfo.getName();
            setText(mNameTv, deviceName);
            devInfo.close();
            // Get depth sensor
            mDepthSensor = mDevice.getSensor(SensorType.DEPTH);
            // Open the depth stream, and pass null to profile, which means using the parameters configured in the configuration file to open the stream.
            // If the configuration does not exist in the device, or the configuration file does not exist, it means using the first configuration in the Profile list.
            if (null != mDepthSensor) {
                mDepthSensor.start(null, mDepthFrameCallback);
            } else {
                Log.w(TAG, "onDeviceAttach: depth sensor is unsupported!");
            }
            // Get color sensor
            mColorSensor = mDevice.getSensor(SensorType.COLOR);
            // Open the color stream, and pass null to profile, which means using the parameters configured in the configuration file to open the stream.
            // If the configuration does not exist in the device, or the configuration file does not exist, it means using the first configuration in the Profile list.
            if (null != mColorSensor) {
                mColorSensor.start(null, mColorFrameCallback);
            } else {
                Log.w(TAG, "onDeviceAttach: color sensor is unsupported!");
            }
            // Get IR sensor
            mIrSensor = mDevice.getSensor(SensorType.IR);
            // Open the infrared stream, and pass null to profile, which means using the parameters configured in the configuration file to open the stream.
            // If the configuration does not exist in the device, or the configuration file does not exist, it means using the first configuration in the Profile list.
            if (null != mIrSensor) {
                mIrSensor.start(null, mIrFrameCallback);
            } else {
                Log.w(TAG, "onDeviceAttach: ir sensor is unsupported!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Update open stream profile information
            setText(mProfileInfoTv, formatProfileInfo());
            // Release deviceList resources
            deviceList.close();
        }
    }
    @Override
    public void onDeviceDetach(DeviceList deviceList) {
        try {
            setText(mNameTv, "No device connected !");
            setText(mProfileInfoTv, "");
            mDepthFps = 0;
            mColorFps = 0;
            mIrFps = 0;
            // Stop depth flow
            if (null != mDepthSensor) {
                mDepthSensor.stop();
            }
            // Stop color flow
            if (null != mColorSensor) {
                mColorSensor.stop();
            }
            // Stop infrared flow
            if (null != mIrSensor) {
                mIrSensor.stop();
            }
            // Release mDevice
            if (null != mDevice) {
                mDevice.close();
                mDevice = null;
            }
            // Release deviceList
            deviceList.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};

// implementation BaseActivity#getDeviceChangedCallback()
@Override
protected DeviceChangedCallback getDeviceChangedCallback() {
    return mDeviceChangedCallback;
}
```

Resource release
```java
try {
	// Stop depth flow
	if (null != mDepthSensor) {
		mDepthSensor.stop();
	}
	// Stop color flow
	if (null != mColorSensor) {
		mColorSensor.stop();
	}
	// Stop infrared flow
	if (null != mIrSensor) {
		mIrSensor.stop();
	}
	// Release mDevice
	if (null != mDevice) {
		mDevice.close();
		mDevice = null;
	}
} catch (Exception e) {
    e.printStackTrace();
}
```

## IMU
Function description: This example mainly demonstrates using the SDK to obtain IMU data and output it for display.

Define IMU related sensors
```java
// accelerometer sensor
private AccelFrame mAccelFrame;
// Gyro sensor
private GyroFrame mGyroFrame;
```
Listen for device changes
```java
// Implement monitoring device changes
private DeviceChangedCallback mDeviceChangedCallback = new DeviceChangedCallback() {
    @Override
    public void onDeviceAttach(DeviceList deviceList) {
	    try {
			if (deviceList == null || deviceList.getDeviceCount() == 0) {
				showToast(getString(R.string.please_access_device));
			} else {
				// 2.Create Device
				mDevice = deviceList.getDevice(0);

				// 3.Get Acceleration Sensor through Device
				mSensorAccel = mDevice.getSensor(SensorType.ACCEL);

				// 4.Get Gyroscope Sensor through Device
				mSensorGyro = mDevice.getSensor(SensorType.GYRO);

				if (mSensorAccel == null || mSensorGyro == null) {
					showToast(getString(R.string.device_not_support_imu));
					return;
				}

				if (mSensorAccel != null && mSensorGyro != null) {
					// 5.Get accelerometer StreamProfile List
					StreamProfileList accelProfileList = mSensorAccel.getStreamProfileList();
					if (null != accelProfileList) {
						mAccelStreamProfile = accelProfileList.getStreamProfile(0).as(StreamType.ACCEL);
						accelProfileList.close();
					}

					// 6.Get gyroscope StreamProfile List
					StreamProfileList gyroProfileList = mSensorGyro.getStreamProfileList();
					if (null != gyroProfileList) {
						mGyroStreamProfile = gyroProfileList.getStreamProfile(0).as(StreamType.GYRO);
						gyroProfileList.close();
					}

					// 7. start IMU
					if (mIsActivityStarted) {
						startIMU();
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 8.Release DeviceList
			deviceList.close();
		}
    }
    @Override
    public void onDeviceDetach(DeviceList deviceList) {
        try {
            showToast("Please access the device");
            deviceList.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};

// implementation BaseActivity#getDeviceChangedCallback()
@Override
protected DeviceChangedCallback getDeviceChangedCallback() {
    return mDeviceChangedCallback;
}
```

Open the stream through specified configuration
```java
private void startIMU() {
	// 7.1.Start gyroscope sampling
	startGyroStream();

	// 7.2.Start accelerometer sampling
	startAccelStream();
}
```
Turn on accelerometer sampling
```java
private void startAccelStream() {
	try {
		// Turn on accelerometer sampling
		if (null != mAccelStreamProfile) {
			mSensorAccel.start(mAccelStreamProfile, new FrameCallback() {
				@Override
				public void onFrame(Frame frame) {
					AccelFrame accelFrame = frame.as(FrameType.ACCEL);

					Log.d(TAG, "AccelFrame onFrame");
					synchronized (mAccelLock) {
						if (null != mAccelFrame) {
							mAccelFrame.close();
							mAccelFrame = null;
						}
						mAccelFrame = accelFrame;
					}
				}
			});
			mIsAccelStarted = true;
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}
```
Turn on gyroscope sampling
```java
private void startGyroStream() {
	try {
		// Turn on gyroscope sampling
		if (null != mGyroStreamProfile) {
			mSensorGyro.start(mGyroStreamProfile, new FrameCallback() {
				@Override
				public void onFrame(Frame frame) {
					GyroFrame gyroFrame = frame.as(FrameType.GYRO);

					Log.d(TAG, "GyroFrame onFrame");
					synchronized (mGyroLock) {
						if (null != mGyroFrame) {
							mGyroFrame.close();
							mGyroFrame = null;
						}
						mGyroFrame = gyroFrame;
					}
				}
			});
			mIsGyroStarted = true;
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}
```
Create AccelFrame and GyroFrame data drawing methods and draw them in real time on the data refresh thread
```java
private void drawImuInfo() {
    if (mIsAccelStarted || mIsGyroStarted) {
        synchronized (mAccelLock) {
            if (null != mAccelFrame) {
                mAccelTimeStampView.setText("AccelTimestamp:" + mAccelFrame.getTimeStamp());
                mAccelTemperatureView.setText(String.format("AccelTemperature:%.2f°C", mAccelFrame.getTemperature()));
                mAccelXView.setText(String.format("Accel.x: %.6fm/s^2", mAccelFrame.getAccelData()[0]));
                mAccelYView.setText(String.format("Accel.x:%.6fm/s^2", mAccelFrame.getAccelData()[1]));
                mAccelZView.setText(String.format("Accel.x: %.6fm/s^2", mAccelFrame.getAccelData()[2]));
            } else {
                mAccelTimeStampView.setText("AccelTimestamp: null");
                mAccelTemperatureView.setText("AccelTemperature: null");
                mAccelXView.setText("Accel.x: null");
                mAccelYView.setText("Accel.x: null");
                mAccelZView.setText("Accel.x: null");
            }
        }

        synchronized (mGyroLock) {
            if (null != mGyroFrame) {
                mGyroTimeStampView.setText("GyroTimestamp:" + mGyroFrame.getTimeStamp());
                mGyroTemperatureView.setText(String.format("GyroTemperature:%.2f°C", mGyroFrame.getTemperature()));
                mGyroXView.setText(String.format("Gyro.x: %.6frad/s", mGyroFrame.getGyroData()[0]));
                mGyroYView.setText(String.format("Gyro.x:%.6frad/s", mGyroFrame.getGyroData()[1]));
                mGyroZView.setText(String.format("Gyro.x: %.6frad/s", mGyroFrame.getGyroData()[2]));
            } else {
                mGyroTimeStampView.setText("GyroTimestamp: null");
                mGyroTemperatureView.setText("GyroTemperature: null");
                mGyroXView.setText("Gyro.x: null");
                mGyroYView.setText("Gyro.x: null");
                mGyroZView.setText("Gyro.x: null");
            }
        }
    } // if accel or gyro started
}
```

Stop IMU
```java
private void stopIMU() {
	try {
		// Stop accelerometer sampling
		if (null != mSensorAccel) {
			mSensorAccel.stop();
		}
		mIsAccelStarted = false;

		// Stop gyroscope sampling
		if (null != mSensorGyro) {
			mSensorGyro.stop();
		}
		mIsGyroStarted = false;
	} catch (Exception e) {
		e.printStackTrace();
	}
}
```

Resource release
```java
try {
	// Release accelerometer StreamProfile
	if (null != mAccelStreamProfile) {
		mAccelStreamProfile.close();
		mAccelStreamProfile = null;
	}

	// Release gyroscope StreamProfile
	if (null != mGyroStreamProfile) {
		mGyroStreamProfile.close();
		mGyroStreamProfile = null;
	}

	// Release Device
	if (null != mDevice) {
		mDevice.close();
		mDevice = null;
	}
} catch (Exception e) {
    e.printStackTrace();
}
```

## MultiDevice
Function description: This example mainly demonstrates the operation of multiple devices.

Initialize the SDK and monitor device changes
```java
// Initialize SDK
mOBContext = new OBContext(getApplicationContext(), new DeviceChangedCallback() {
    @Override
    public void onDeviceAttach(DeviceList deviceList) {
        try {
            int count = deviceList.getDeviceCount();
            for (int i = 0; i < count; i++) {
                // Create device
                Device device = deviceList.getDevice(i);
                // Get DeviceInfo
                DeviceInfo devInfo = device.getInfo();
                // Get device name
                String name = devInfo.getName();
                // Get device uid
                String uid = devInfo.getUid();
                // Get device usb interface type
                String connectionType = devInfo.getConnectionType();
                // Release DeviceInfo resources
                devInfo.close();
                runOnUiThread(() -> {
                    mDeviceControllerAdapter.addItem(new DeviceBean(name, uid, connectionType, device));
                });
                
            }
            
            // Release device list resources
            deviceList.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void onDeviceDetach(DeviceList deviceList) {
        try {
            for (DeviceBean deviceBean : mDeviceBeanList) {
                //Judging offline devices by uid
                if (deviceBean.getDeviceUid().equals(deviceList.getUid(0))) {
                    // Release offline device resources
                    deviceBean.getDevice().close();
                    runOnUiThread(() -> {
                        mDeviceControllerAdapter.deleteItem(deviceBean);
                    });
                }
            }
            
            // Release device list resources
            deviceList.close();
        } catch (Exception e) {
            Log.w(TAG, "onDeviceDetach: " + e.getMessage());
        }
    }
});
```
Create a device list in the device connection callback method
```java
int count = deviceList.getDeviceCount();
for (int i = 0; i < count; i++) {
    // Create device
    Device device = deviceList.getDevice(i);
    // Get DeviceInfo
    DeviceInfo devInfo = device.getInfo();
    // Get device name
    String name = devInfo.getName();
    // Get device uid
    String uid = devInfo.getUid();
    // Get device usb interface type
    String connectionType = devInfo.getConnectionType();
    // Release DeviceInfo resources
    devInfo.close();
    runOnUiThread(() -> {
        mDeviceControllerAdapter.addItem(new DeviceBean(name, uid, connectionType, device));
    });
}
```
Select the corresponding device to open the stream
```java
private void startStream(Sensor sensor, GLView glView) {
	try {
		// Get a list of stream profiles for a sensor
		StreamProfileList profileList = sensor.getStreamProfileList();
		if (null == profileList) {
			Log.w(TAG, "start stream failed, profileList is null !");
			return;
		}
		switch (sensor.getType()) {
			case DEPTH:
				GLView depthGLView = glView;
				// Get the open stream profile through StreamProfileList
				StreamProfile depthProfile = profileList.getVideoStreamProfile(640, 480, Format.Y16, 0);
				if (null != depthProfile) {
					// Open current by specifying StreamProfile
					sensor.start(depthProfile, frame -> {
						DepthFrame depthFrame = frame.as(FrameType.DEPTH);
						byte[] bytes = new byte[depthFrame.getDataSize()];
						depthFrame.getData(bytes);
						depthGLView.update(depthFrame.getWidth(), depthFrame.getHeight(),
                                    StreamType.DEPTH, depthFrame.getFormat(), bytes, depthFrame.getValueScale());
						// Release frame resources
						frame.close();
					});
					// Release profile resources
					depthProfile.close();
				} else {
					Log.w(TAG, "start depth stream failed, depthProfile is null!");
				}
				break;
			case COLOR:
				GLView colorGLView = glView;
				// Get the open stream profile through StreamProfileList
				StreamProfile colorProfile = profileList.getVideoStreamProfile(640, 480, Format.RGB888, 0);

				if (null != colorProfile) {
					// Open current by specifying StreamProfile
					sensor.start(colorProfile, frame -> {
						ColorFrame colorFrame = frame.as(FrameType.COLOR);
						byte[] bytes = new byte[colorFrame.getDataSize()];
						// Get frame data
						colorFrame.getData(bytes);
						// Render data
						colorGLView.update(colorFrame.getWidth(), colorFrame.getHeight(), StreamType.COLOR, colorFrame.getFormat(), bytes, 1.0f);
						// Release frame resources
						frame.close();
					});
					// Release profile resources
					colorProfile.close();
				} else {
					Log.w(TAG, "start color stream failed, colorProfile is null!");
				}
				break;
			case IR:
				GLView irGLView = glView;
				// Get the open stream profile through StreamProfileList
				StreamProfile irProfile = profileList.getVideoStreamProfile(640, 480, Format.Y16, 0);
				if (null != irProfile) {
					// Open current by specifying StreamProfile
					sensor.start(irProfile, frame -> {
						IRFrame irFrame = frame.as(FrameType.IR);
						byte[] bytes = new byte[irFrame.getDataSize()];
						// Get frame data
						irFrame.getData(bytes);
						// Render data
						irGLView.update(irFrame.getWidth(), irFrame.getHeight(),
                                    StreamType.IR, irFrame.getFormat(), bytes, 1.0f);
						// Frame resource
						frame.close();
					});
					// Release profile resources
					irProfile.close();
				} else {
					Log.w(TAG, "start ir stream failed, irProfile is null!");
				}
				break;
		}

		// Release profileList resources
		profileList.close();
	} catch (Exception e) {
		Log.w(TAG, "startStream: " + e.getMessage());
	}
}
```
Shut down the specified sensor stream to the specified device
```java
private void stopStream(Sensor sensor) {
    try {
        sensor.stop();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
```
Do the corresponding resource release in the device disconnect callback and refresh the device list
```java
try {
    for (DeviceBean deviceBean : mDeviceBeanList) {
        // Judging offline devices by uid
        if (deviceBean.getDeviceUid().equals(deviceList.getUid(0))) {
            // Release offline device resources
            deviceBean.getDevice().close();
            runOnUiThread(() -> {
                mDeviceControllerAdapter.deleteItem(deviceBean);
            });
        }
    }
} catch (Exception e) {
    Log.w(TAG, "onDeviceDetach: " + e.getMessage());
}
```
Resource release
```java
try {
    // Release resources
    for (DeviceBean deviceBean : mDeviceBeanList) {
        try {
            // Release device resources
            deviceBean.getDevice().close();
        } catch (Exception e) {
            Log.w(TAG, "onDestroy: " + e.getMessage());
        }
    }
    mDeviceBeanList.clear();
} catch (Exception e) {
    e.printStackTrace();
}
```  
  
## PointCloud
Function description: This example mainly demonstrates connecting the device to open the flow, generating a depth point cloud or RGBD point cloud and saving it into a ply format file.

In the basic module BaseActivity, we introduced genD2CStreamProfile(). Stream alignment requires configuring both the depth and color resolutions, as well as the alignment type. The implementation method is as follows:
```java
private Config genD2CConfig(Pipeline pipeline, AlignMode alignMode) {
    // Get D2CStreamProfile BaseActivity#genD2CStreamProfile(Pipeline, AlignMode)
    D2CStreamProfile d2CStreamProfile = genD2CStreamProfile(pipeline, alignMode);
    if (null == d2CStreamProfile) {
        return null;
    }

    // Update color information to UI
    VideoStreamProfile colorProfile = d2CStreamProfile.getColorProfile();
    // Update depth information to UI
    VideoStreamProfile depthProfile = d2CStreamProfile.getDepthProfile();

    Config config = new Config();
    // set D2C AlignMode
    config.setAlignMode(alignMode);
    config.enableStream(d2CStreamProfile.getColorProfile());
    config.enableStream(d2CStreamProfile.getDepthProfile());
    d2CStreamProfile.close();
    return config;
}
```

Listen for device changes
```java
// Implement monitoring device changes
private DeviceChangedCallback mDeviceChangedCallback = new DeviceChangedCallback() {
    @Override
    public void onDeviceAttach(DeviceList deviceList) {
        try {
            if (null == mPipeline) {
                // 2.Create Device and initialize Pipeline through Device
                mDevice = deviceList.getDevice(0);

                if (null == mDevice.getSensor(SensorType.COLOR)) {
                    mDevice.close();
                    mDevice = null;
                    showToast(getString(R.string.device_not_support_color));
                    runOnUiThread(() -> {
                        mSaveColorPointsBtn.setEnabled(false);
                    });
                }

                if (null == mDevice.getSensor(SensorType.DEPTH)) {
                    mDevice.close();
                    mDevice = null;
                    showToast(getString(R.string.device_not_support_depth));
                    return;
                }

                // 3.Create Device and initialize Pipeline through Device
                mPipeline = new Pipeline(mDevice);

                // 4.Create Config to configure pipeline opening sensors
                Config config = genD2CConfig(mPipeline, AlignMode.ALIGN_D2C_HW_ENABLE);
                if (null == config) {
                    mPipeline.close();
                    mPipeline = null;
                    mDevice.close();
                    mDevice = null;
                    Log.w(TAG, "onDeviceAttach: No target depth and color stream profile!");
                    showToast(getString(R.string.init_stream_profile_failed));
                    return;
                }

                // 5.Start sensors stream
                mPipeline.start(config, mPointCloudFrameSetCallback);

                // 6.Start the point cloud asynchronous processing thread
                start();

                // 7.Create point cloud filter
                mPointCloudFilter = new PointCloudFilter();

                // 8.Set the format of the point cloud filter
                mPointCloudFilter.setPointFormat(mPointFormat);

                // 9.Obtain camera intrinsic parameters and set parameters to point cloud filter
                CameraParam cameraParam = mPipeline.getCameraParam();
                mPointCloudFilter.setCameraParam(cameraParam);
                Log.i(TAG, "onDeviceAttach: cameraParam:" + cameraParam);

                // 10.Release config resources
                config.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Release device list resources
            deviceList.close();
        }
    }
    @Override
    public void onDeviceDetach(DeviceList deviceList) {
        try {
            deviceList.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};
```

Create a callback method for Pipeline opening flow
```java
// Pipeline open flow callback
private FrameSetCallback mPointCloudFrameSetCallback = frameSet -> {
    if (null != frameSet) {
        if (mIsPointCloudRunning) {
            if (null == mPointFrameSet) {
                mPointFrameSet = frameSet;
                return;
            }
        }
        frameSet.close();
    }
};
```

Point cloud filter thread data processing
```java
while (mIsPointCloudRunning) {
     try {
         if (null != mPointFrameSet) {
             Frame frame = null;
             if (mPointFormat == Format.POINT) {
                 //Set the save format to depth point cloud
                 mPointCloudFilter.setPointFormat(Format.POINT);
             } else {
                 //Set the save format to color point cloud
                 mPointCloudFilter.setPointFormat(Format.RGB_POINT);
             }
             // Pay attention to the depth progress unit when saving point clouds
             DepthFrame depthFrame = mPointFrameSet.getDepthFrame();
             if (null != depthFrame) {
                 //Update the depth progress unit of the point cloud
                 mPointCloudFilter.setPositionDataScale(depthFrame.getValueScale());
                 depthFrame.close();
             }
             //Point cloud filter processing generates corresponding point cloud data
             frame = mPointCloudFilter.process(mPointFrameSet);
             if (null != frame) {
                 // Get point cloud frame
                 PointFrame pointFrame = frame.as(FrameType.POINTS);
                 if (mIsSavePoints) {
                     if (mPointFormat == Format.POINT) {
                         // Get the depth point cloud data and save it. The data size of the depth point cloud is w * h * 3
                         float[] depthPoints = new float[pointFrame.getDataSize() / Float.BYTES];
                         pointFrame.getPointCloudData(depthPoints);
                         String depthPointsPath = mSdcardDir.toString() + "/Orbbec/point.ply";
                         FileUtils.savePointCloud(depthPointsPath, depthPoints);
                         runOnUiThread(new Runnable() {
                             @Override
                             public void run() {
                                 mInfoTv.append("Save Path:" + depthPointsPath + "\n");
                             }
                         });
                     } else {
                         // Get the color point cloud data and save it. The data size of the color point cloud is w * h * 6
                         float[] colorPoints = new float[pointFrame.getDataSize() / Float.BYTES];
                         pointFrame.getPointCloudData(colorPoints);
                         String colorPointsPath = mSdcardDir.toString() + "/Orbbec/point_rgb.ply";
                         FileUtils.saveRGBPointCloud(colorPointsPath, colorPoints);
                         runOnUiThread(new Runnable() {
                             @Override
                             public void run() {
                                 mInfoTv.append("Save Path:" + colorPointsPath + "\n");
                             }
                         });
                     }
                     mIsSavePoints = false;
                 }
                 // Release the newly generated frame
                 frame.close();
             }
             // Release the original data frameSet
             mPointFrameSet.close();
             mPointFrameSet = null;
         }
     } catch (Exception e) {
         e.printStackTrace();
     }
}
```
To save depth point cloud data, please refer to OrbbecSdkExamples#PointCloudActivity.java
```java
public static void savePointCloud(String fileName, float[] data)
```
To save color point cloud data, please refer to OrbbecSdkExamples#PointCloudActivity.java
```java
public static void saveRGBPointCloud(String fileName, float[] data)
```

Resource release
```java
try {
     // Stop the Pipeline and close it
     if (null != mPipeline) {
         mPipeline.stop();
         mPipeline.close();
         mPipeline = null;
     }
     // Release point cloud filter
     if (null != mPointCloudFilter) {
         try {
             mPointCloudFilter.close();
         } catch (Exception e) {
         }
         mPointCloudFilter = null;
     }
     // Release Device
     if (mDevice != null) {
         mDevice.close();
         mDevice = null;
     }
} catch (Exception e) {
     e.printStackTrace();
}
```

## SaveToDisk
Function description: This example is used to demonstrate the connection device to open the stream, obtain the color and store it in png format, store the depth in raw format and perform format conversion through filter.

First, you need to create a Context, which is used to obtain the device information list and create devices.
```java
private OBContext mOBContext;
```
Listen for device changes
```java
// Implement monitoring device changes
private DeviceChangedCallback mDeviceChangedCallback = new DeviceChangedCallback() {
    @Override
    public void onDeviceAttach(DeviceList deviceList) {
        try {
            if (null == mPipeline) {
                // 2.Create Device and initialize Pipeline through Device
                mDevice = deviceList.getDevice(0);

                if (null == mDevice.getSensor(SensorType.DEPTH)) {
                    depthCount = 5;
                    showToast(getString(R.string.device_not_support_depth));
                }
                if (null == mDevice.getSensor(SensorType.COLOR)) {
                    colorCount = 5;
                    showToast(getString(R.string.device_not_support_color));
                }

                mPipeline = new Pipeline(mDevice);

                // 3.Initialize the format conversion filter
                if (null != mFormatConvertFilter) {
                    mFormatConvertFilter = new FormatConvertFilter();
                }

                // 4.Create Pipeline configuration
                Config config = new Config();

                // 5.Get the color Sensor VideoStreamProfile and configure it to Config
                try {
                    VideoStreamProfile colorStreamProfile = getStreamProfile(mPipeline, SensorType.COLOR);

                    // 6.Enable color sensor through the obtained color sensor StreamProfile
                    if (null != colorStreamProfile) {
                        printStreamProfile(colorStreamProfile.as(StreamType.VIDEO));
                        config.enableStream(colorStreamProfile);
                        colorStreamProfile.close();
                    } else {
                        Log.w(TAG, "onDeviceAttach: No target color stream profile!");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                // 7.Get the depth sensor StreamProfile and configure it to Config
                try {
                    VideoStreamProfile depthStreamProfile = getStreamProfile(mPipeline, SensorType.DEPTH);

                    // 8.Enable depth sensor through the obtained depth sensor StreamProfile
                    if (null != depthStreamProfile) {
                        printStreamProfile(depthStreamProfile.as(StreamType.VIDEO));
                        config.enableStream(depthStreamProfile);
                        depthStreamProfile.close();
                    } else {
                        Log.w(TAG, "onDeviceAttach: No target depth stream profile!");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                initSaveImageDir();

                // 9.Open Pipeline with Config
                mPipeline.start(config);

                // 10.Release config resources
                config.close();

                // 11.Create a pipeline data acquisition thread and a picture saving thread
                start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 12.Release DeviceList
            deviceList.close();
        }
    }

    @Override
    public void onDeviceDetach(DeviceList deviceList) {
        try {
            if (mDevice != null) {
                for (int i = 0, N = deviceList.getDeviceCount(); i < N; i++) {
                    String uid = deviceList.getUid(i);
                    DeviceInfo deviceInfo = mDevice.getInfo();
                    if (null != deviceInfo && TextUtils.equals(uid, deviceInfo.getUid())) {
                        stop();
                        mPipeline.stop();
                        mPipeline.close();
                        mPipeline = null;
                        mDevice.close();
                        mDevice = null;
                    }
                    deviceInfo.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                deviceList.close();
            } catch (Exception ignore) {
            }
        }
    }
};
```

Create a thread to obtain Pipeline data and a thread to save images
```java
private void start() {
    colorCount = 0;
    depthCount = 0;
    mIsStreamRunning = true;
    mIsPicSavingRunning = true;
    if (null == mStreamThread) {
        mStreamThread = new Thread(mStreamRunnable);
        mStreamThread.start();
    }
    if (null == mPicSavingThread) {
        mPicSavingThread = new Thread(mPicSavingRunnable);
        mPicSavingThread.start();
    }
}
```
Data stream processing
```java
int count = 0;
ByteBuffer colorSrcBuf = null;
ByteBuffer colorDstBuf = null;
while (mIsStreamRunning) {
     try {
         // If it cannot be obtained after waiting for 100ms, it will time out.
         FrameSet frameSet = mPipeline.waitForFrameSet(100);
         if (null == frameSet) {
             continue;
         }
         if (count < 5) {
             frameSet.close();
             count++;
             continue;
         }
         // Get color stream data
         ColorFrame colorFrame = frameSet.getColorFrame();
         if (null != colorFrame) {
             Frame rgbFrame = null;
             switch (colorFrame.getFormat()) {
                 case MJPG:
                     mFormatConvertFilter.setFormatType(FormatConvertType.FORMAT_MJPEG_TO_RGB888);
                     rgbFrame = mFormatConvertFilter.process(colorFrame);
                     break;
                 case YUYV:
                     mFormatConvertFilter.setFormatType(FormatConvertType.FORMAT_YUYV_TO_RGB888);
                     rgbFrame = mFormatConvertFilter.process(colorFrame);
                     break;
                 case UYVY:
                     FrameCopy frameCopy = copyToFrameT(colorFrame);
                     if (null == colorSrcBuf || colorSrcBuf.capacity() != colorFrame.getDataSize()) {
                         colorSrcBuf = ByteBuffer.allocateDirect(colorFrame.getDataSize());
                     }
                     colorSrcBuf.clear();
                     colorFrame.getData(colorSrcBuf);
                     int colorDstSize = colorFrame.getWidth() * colorFrame.getHeight() * 3;
                     if (null == colorDstBuf || colorDstBuf.capacity() != colorDstSize) {
                         colorDstBuf = ByteBuffer.allocateDirect(colorDstSize);
                     }
                     colorDstBuf.clear();
                     ImageUtils.uyvyToRgb(colorSrcBuf, colorDstBuf, colorFrame.getWidth(), colorFrame.getHeight());
                     frameCopy.data = new byte[colorDstSize];
                     colorDstBuf.get(frameCopy.data);
                     mFrameSaveQueue.offer(frameCopy);
                     break;
                 default:
                     Log.w(TAG, "Unsupported color format!");
                     break;
             }
             if (null != rgbFrame) {
                 FrameCopy frameCopy = copyToFrameT(rgbFrame.as(FrameType.VIDEO));
                 mFrameSaveQueue.offer(frameCopy);
                 rgbFrame.close();
             }
             colorFrame.close();
         }
         // Get depth stream data
         DepthFrame depthFrame = frameSet.getDepthFrame();
         if (null != depthFrame) {
             FrameCopy frameT = copyToFrameT(depthFrame);
             mFrameSaveQueue.offer(frameT);
             depthFrame.close();
         }
         // Release the data set
         frameSet.close();
     } catch (Exception e) {
         e.printStackTrace();
     }
}
```
Data saving
```java
while (mIsPicSavingRunning) {
    try {
        FrameCopy frameT = mFrameSaveQueue.poll(300, TimeUnit.MILLISECONDS);
        if (null != frameT) {
            Log.d(TAG, "colorCount :" + colorCount);
            if (frameT.getStreamType() == FrameType.COLOR && colorCount < 5) {
                FileUtils.saveImage(frameT);
                colorCount++;
            }
            Log.d(TAG, "depthCount :" + depthCount);
            if (frameT.getStreamType() == FrameType.DEPTH && depthCount < 5) {
                FileUtils.saveImage(frameT);
                depthCount++;
            }
        }
    } catch (Exception e) {
    }
    if (colorCount == 5 && depthCount == 5) {
        mIsPicSavingRunning = false;
        break;
    }
}

mFrameSaveQueue.clear();
```
Exit the data processing thread and image storage thread
```java
private void stop() {
     mIsStreamRunning = false;
     mIsPicSavingRunning = false;
     if (null != mStreamThread) {
         try {
             mStreamThread.join(1000);
         } catch (InterruptedException e) {
         }
         mStreamThread = null;
     }
     if (null != mPicSavingThread) {
         try {
             mPicSavingThread.join(1000);
         } catch (InterruptedException e) {
         }
         mPicSavingThread = null;
     }
}
```
Resource release
```java
try {
     // Release filter resources
     if (null != mFormatConvertFilter) {
         try {
             mFormatConvertFilter.close();
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
     // Stop the Pipeline and close it
     if (null != mPipeline) {
         mPipeline.stop();
         mPipeline.close();
         mPipeline = null;
     }
     // Release Device resources
     if (null != mDevice) {
         mDevice.close();
         mDevice = null;
     }
} catch (Exception e) {
     e.printStackTrace();
}
```


## Sensor control example-SensorControl
Function description: This example mainly demonstrates the operation of device control commands and the operation of Sensor control commands.

For attribute operations, please read the OrbbecSdkExamples code SensorControlActivity.java

## Recording and playback example-Recorder & Playback
Function description: : Connect the device to start streaming, record the current video stream to a file, and load the video file for playback.

Listen for device changes
```java
private DeviceChangedCallback mDeviceChangedCallback = new DeviceChangedCallback() {
    @Override
    public void onDeviceAttach(DeviceList deviceList) {
        try {
            if (null == mPipeline) {
                // 2.Create Device and initialize Pipeline through Device
                mDevice = deviceList.getDevice(0);

                if (null == mDevice.getSensor(SensorType.DEPTH)) {
                    showToast(getString(R.string.device_not_support_depth));
                    return;
                }

                mPipeline = new Pipeline(mDevice);

                // 3.Update device information view
                updateDeviceInfoView(false);

                // 4.Create Pipeline configuration
                mConfig = new Config();

                // 5.Get the depth stream profile and configure it to Config
                VideoStreamProfile streamProfile = getStreamProfile(mPipeline, SensorType.DEPTH);
                // 6.Enable deep sensor StreamProfile
                if (null != streamProfile) {
                    printStreamProfile(streamProfile.as(StreamType.VIDEO));
                    mConfig.enableStream(streamProfile);
                    streamProfile.close();
                } else {
                    mDevice.close();
                    mDevice = null;
                    mPipeline.close();
                    mPipeline = null;
                    mConfig.close();
                    mConfig = null;
                    Log.w(TAG, "onDeviceAttach: No target stream profile!");
                    showToast(getString(R.string.init_stream_profile_failed));
                    return;
                }

                // 7.Start pipeline
                mPipeline.start(mConfig);

                // 8.Create a thread to obtain Pipeline data
                start();

                runOnUiThread(() -> {
                    mStartRecordBtn.setEnabled(true);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 9.Release DeviceList
            deviceList.close();
        }
    }

    @Override
    public void onDeviceDetach(DeviceList deviceList) {
        try {
            release();

            runOnUiThread(() -> {
                mStartRecordBtn.setEnabled(false);
                mStopRecordBtn.setEnabled(false);
                mStartPlaybackBtn.setEnabled(isPlayFileValid());
                mStopPlaybackBtn.setEnabled(false);
            });
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            deviceList.close();
        }
    }
};
```

Recording implementation, start recording and stop recording
```java
/**
 * Start recording
 */
private void startRecord() {
    try {
        if (!mIsRecording) {
            if (null != mPipeline) {
                // Start recording
                mPipeline.startRecord(mRecordFilePath);
                mIsRecording = true;
            } else {
                Log.w(TAG, "mPipeline is null !");
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * stop recording
 */
private void stopRecord() {
    try {
        if (mIsRecording) {
            mIsRecording = false;
            if (null != mPipeline) {
                // stop recording
                mPipeline.stopRecord();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
```

Playback implementation, start playback and stop playback
```java
/**
 * Start playback
 */
private void startPlayback() {
    try {
        if (!FileUtils.isFileExists(mRecordFilePath)) {
            Toast.makeText(RecordPlaybackActivity.this, "File not found!", Toast.LENGTH_LONG).show();
            return;
        }
        if (!mIsPlaying) {
            mIsPlaying = true;

            // Release Playback resources
            if (null != mPlayback) {
                mPlayback.close();
                mPlayback = null;
            }

            // Create a playback pipeline
            if (null != mPlaybackPipe) {
                mPlaybackPipe.close();
                mPlaybackPipe = null;
            }
            // Create Playback Pipeline
            mPlaybackPipe = new Pipeline(mRecordFilePath);

            // Get the Playback from Pipeline
            mPlayback = mPlaybackPipe.getPlayback();

            // Set playback status callback
            mPlayback.setMediaStateCallback(mMediaStateCallback);

            // start playback
            mPlaybackPipe.start(null);

            // Create a playback thread
            if (null == mPlaybackThread) {
                mPlaybackThread = new Thread(mPlaybackRunnable);
                mPlaybackThread.start();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * stop playback
 */
private void stopPlayback() {
    try {
        if (mIsPlaying) {
            mIsPlaying = false;
            // stop playback thread
            if (null != mPlaybackThread) {
                try {
                    mPlaybackThread.join(300);
                } catch (InterruptedException e) {
                }
                mPlaybackThread = null;
            }

            // stop playback
            if (null != mPlaybackPipe) {
                mPlaybackPipe.stop();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
```

Get recorded image data
```java
// playback thread
private Runnable mPlaybackRunnable = () -> {
    while (mIsPlaying) {
        try {
            // If it cannot be obtained after waiting for 100ms, it will time out
            FrameSet frameSet = mPlaybackPipe.waitForFrameSet(100);
            if (null == frameSet) {
                continue;
            }

            // Get depth stream data
            DepthFrame depthFrame = frameSet.getDepthFrame();
            if (null != depthFrame) {
                // Get depth data and render
                byte[] frameData = new byte[depthFrame.getDataSize()];
                depthFrame.getData(frameData);
                synchronized (mSync) {
                    mDepthGLView.update(depthFrame.getWidth(), depthFrame.getHeight(), StreamType.DEPTH, depthFrame.getFormat(), frameData, depthFrame.getValueScale());
                }

                // Release the depth data frame
                depthFrame.close();
            }

            // Release FrameSet
            frameSet.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};
```


Release resources
```java
try {
    // Stop getting Pipeline data
    stop();

    // stop playback thread
    if (mIsPlaying) {
        mIsPlaying = false;
        if (null != mPlaybackThread) {
            try {
                mPlaybackThread.join(300);
            } catch (InterruptedException e) {
            }
            mPlaybackThread = null;
        }
    }

    // release playback
    if (null != mPlayback) {
        mPlayback.close();
        mPlayback = null;
    }

    // Release playback pipeline
    if (null != mPlaybackPipe) {
        mPlaybackPipe.close();
        mPlaybackPipe = null;
    }

    // release config
    if (null != mConfig) {
        mConfig.close();
        mConfig = null;
    }

    // Release preview pipeline
    if (null != mPipeline) {
        mPipeline.close();
        mPipeline = null;
    }

    // Release Device
    if (null != mDevice) {
        mDevice.close();
        mDevice = null;
    }
} catch (Exception e) {
    e.printStackTrace();
}
```
