> ## Documentation Index
> Fetch the complete documentation index at: https://imsdkdocs.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Reference Applications

> Reference C++ App Builder pipeline examples

## Multimedia Pipeline

Before running any of the applications below, create the sample directory tree on the target device:

```bash theme={null}
# ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{media,models,labels}"
```

This is required even for apps that don't download any media, model, or label files (such as the camera capture and recording apps), since they still write their output under this path.

### USB Camera Preview

USB (UVC) cameras are handled by the `v4l2src` node. Each USB camera comes with its own format and frame constraints. Because of this, we always place `qtivcomposer` right after the USB camera to ensure that the rest of the pipeline receives a hardware-friendly NV12 (Semi-planar YUV420) video format. However, `qtivcomposer` will automatically operate in passthrough mode if the USB camera already supports NV12, so you don't need to worry about any performance overhead.

<Note>
  Some USB webcams (e.g. Logitech Brio 4K) may render a green/garbled frame at 30fps depending on platform/build. If this happens, lower the pipeline's framerate (e.g. to 15fps) as a workaround.
</Note>

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/usb_cam_src.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=2eb0a04e674fbfb4ee468483a076bad3" alt="Introduction" width="2147" height="510" data-path="app-builder/images/usb_cam_src.png" />

<Accordion title="Try me">
  <Tabs>
    <Tab title="Explicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_usb_camera`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_usb_camera)

        Pre-built application on device: `/usr/bin/qimsdk_ref_usb_camera`
      </Info>

      ```cpp theme={null}
      #include <iostream>
      #include <qti/qimsdk.h>
      using namespace qti;
      //  Example pipeline:
      //
      //    source → transform → [videofilter] → display
      //
      //  The pipeline reads frames from a USB (V4L2) camera, rotates them, restricts
      //  the stream to NV12/1080p/30fps, and displays the result through Wayland.
      void create_and_execute_pipeline(const std::string &device) {
      // Captures frames from the USB camera source.
      Element source("v4l2src", "source");
      source.set("device", device);

      // Applies geometric transforms to video frames.
      Element transform("qtivtransform", "transform");

      // Stream filters used in branch links.
      // They define specific stream characteristics from the supported options.
      auto videofilter = VideoFilter().format("NV12").resolution(1920, 1080).framerate(30);

      // Render video stream on display.
      //
      // sync=false disables strict rendering synchronization to the pipeline clock.
      // fullscreen=true renders the output fullscreen on the target display.
      Element display("waylandsink", "display");
      display.set("sync", false);
      display.set("fullscreen", true);

      // Creates the pipeline, adds elements, links them explicitly, and executes it.
      Pipeline pipeline("usb-cam-pipeline");
      pipeline.add(source)
      .add(transform)
      .add_stream_filter("videofilter", videofilter)
      .add(display)
      .link("source", "transform", "videofilter", "display")
      .execute();
      }
      int main(int argc, char **argv) {
      // Route GStreamer logs through the QIM SDK logger and enable debug output.
      qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
      qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
      // V4L2 device node to capture from, defaults to /dev/video2. Pass -d/--device to override.
      std::string device = "/dev/video2";
      for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        if ((arg == "-d" || arg == "--device") && i + 1 < argc) {
          device = argv[++i];
        }
      }
      try {
      create_and_execute_pipeline(device);
      } catch (const std::exception &ex) {
      std::cerr << "Exception: " << ex.what() << std::endl;
      return 1;
      }
      return 0;
      }
      ```

      <Steps>
        <Step title="Connect a USB camera">
          Connect a USB (UVC) camera to the target device and verify it is exposed as `/dev/video2` (update the pipeline's `device` property if your camera is exposed on a different node).
        </Step>

        <Step title="Run the application">
          ```bash theme={null}
          /usr/bin/qimsdk_ref_usb_camera --input-config /dev/video2
          ```

          The application connects to the USB camera, restricts the stream to NV12/1080p/30fps, and renders it fullscreen on the display.
        </Step>

        <Step title="Expected Output">
          The live USB camera feed is rendered fullscreen on the display.

          <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/usb-camera-preview.jpeg?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=2ed060f58a450ceb48de22da1d5cf295" alt="Expected Output" width="1108" height="774" data-path="app-builder/images/usb-camera-preview.jpeg" />

          To stop the application, press **CTRL + C**.
        </Step>
      </Steps>
    </Tab>

    <Tab title="Implicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_usb_camera_impl`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_usb_camera_impl)

        Pre-built application on device: `/usr/bin/qimsdk_ref_usb_camera_impl`
      </Info>

      ```cpp theme={null}
      #include <iostream>
      #include <qti/qimsdk.h>
      using namespace qti;
      //  Example pipeline:
      //
      //    source → transform → [videofilter] → display
      //
      //  The pipeline reads frames from a USB (V4L2) camera, rotates them, restricts
      //  the stream to NV12/1080p/30fps, and displays the result through Wayland.
      void create_and_execute_pipeline(const std::string &device) {
      // Creates the pipeline, adds and links elements, and executes it.
      //
      // Elements are created on the fly as they are added, and linking is
      // implicit, following the order in which elements are added.
      //
      // sync=false disables strict rendering synchronization to the pipeline clock.
      // fullscreen=true renders the output fullscreen on the target display.
      Pipeline pipeline("usb-cam-pipeline");
      pipeline.add("v4l2src", "source", "device", device)
      .add("qtivtransform", "transform")
      .add_stream_filter("videofilter", VideoFilter().format("NV12").resolution(1920, 1080).framerate(30))
      .add("waylandsink", "display", "sync", false, "fullscreen", true)
      .execute();
      }
      int main(int argc, char **argv) {
      // Route GStreamer logs through the QIM SDK logger and enable debug output.
      qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
      qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
      // V4L2 device node to capture from, defaults to /dev/video2. Pass -d/--device to override.
      std::string device = "/dev/video2";
      for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        if ((arg == "-d" || arg == "--device") && i + 1 < argc) {
          device = argv[++i];
        }
      }
      try {
      create_and_execute_pipeline(device);
      } catch (const std::exception &ex) {
      std::cerr << "Exception: " << ex.what() << std::endl;
      return 1;
      }
      return 0;
      }
      ```

      <Steps>
        <Step title="Connect a USB camera">
          Connect a USB (UVC) camera to the target device and verify it is exposed as `/dev/video2` (update the pipeline's `device` property if your camera is exposed on a different node).
        </Step>

        <Step title="Run the application">
          ```bash theme={null}
          /usr/bin/qimsdk_ref_usb_camera_impl --input-config /dev/video2
          ```

          The application connects to the USB camera, restricts the stream to NV12/1080p/30fps, and renders it fullscreen on the display.
        </Step>

        <Step title="Expected Output">
          The live USB camera feed is rendered fullscreen on the display.

          <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/usb-camera-preview.jpeg?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=2ed060f58a450ceb48de22da1d5cf295" alt="Expected Output" width="1108" height="774" data-path="app-builder/images/usb-camera-preview.jpeg" />

          To stop the application, press **CTRL + C**.
        </Step>
      </Steps>
    </Tab>

    <Tab title="YAML config style">
      <Info>
        See the full YAML configuration here: [qimsdk\_ref\_usb\_camera.yaml](https://github.com/qualcomm/qimsdk/blob/main/tools/appbuilders-configs/qimsdk_ref_usb_camera.yaml)

        Check application source code on GitHub: [qimsdk\_ref\_yml](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yml)

        Pre-built application on device: `/usr/bin/qimsdk_ref_yml`

        YAML configuration saved on device: `/etc/qimsdk/qimsdk_ref_usb_camera.yaml`
      </Info>

      ```yaml theme={null}
      pipeline:
        elements:
          - type: v4l2src
            name: source
            device: /dev/video2

          - type: qtivtransform
            name: transform

          - type: filter
            name: videostream
            video:
              format: NV12
              width: 1920
              height: 1080
              framerate: 30

          - type: waylandsink
            name: display
            sync: false
            fullscreen: true
      ```

      <Steps>
        <Step title="Connect a USB camera">
          Connect a USB (UVC) camera to the target device and verify it is exposed as `/dev/video2` (update the pipeline's `device` property if your camera is exposed on a different node).
        </Step>

        <Step title="Run the application">
          ```bash theme={null}
          qimsdk_ref_yml /etc/qimsdk/qimsdk_ref_usb_camera.yaml
          ```

          The application connects to the USB camera, restricts the stream to NV12/1080p/30fps, and renders it fullscreen on the display.
        </Step>

        <Step title="Expected Output">
          The live USB camera feed is rendered fullscreen on the display.

          <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/usb-camera-preview.jpeg?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=2ed060f58a450ceb48de22da1d5cf295" alt="Expected Output" width="1108" height="774" data-path="app-builder/images/usb-camera-preview.jpeg" />

          To stop the application, press **CTRL + C**.
        </Step>
      </Steps>
    </Tab>
  </Tabs>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_usb\_camera](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_usb_camera)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Built-in Camera Preview

This pipeline includes a built-in camera node, a stream filter, and a display node. If the device has more than one built-in camera, you can specify which one to use by providing the camera's unique ID. The stream filter defines the camera's output format, resolution, and framerate. You can also configure the display settings, such as enabling fullscreen rendering.

<Note>
  IQ9 has no onboard ISP camera. You can attach an external IMX camera (e.g. from RB3 Gen2) instead — see [ISP Camera (Config #2 / qticamsrc)](/advanced/debugging#isp-camera-config-2--qticamsrc) for the procedure to switch from `libcamera` to `qticamsrc`.
</Note>

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/built_in_cam_src.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=e777745704666adec46efa79f2a7b461" alt="Introduction" width="1093" height="252" data-path="app-builder/images/built_in_cam_src.png" />

<Accordion title="Try me">
  <Tabs>
    <Tab title="Explicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_camera`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera)

        Pre-built application on device: `/usr/bin/qimsdk_ref_camera`
      </Info>

      ```cpp theme={null}
      #include <iostream>
      #include <qti/qimsdk.h>
      using namespace qti;
      //  Example pipeline:
      //
      //    source → [videostream] → display
      //
      //  The pipeline reads camera frames, runs ML inference and postprocessing,
      //  and displays the result through Wayland.
      void create_and_execute_pipeline() {
      // Captures frames from the camera source.
      Element source("qtiqmmfsrc", "source");

      // Render video stream on display.
      //
      // async=false enforce state transition to ensure the buffers are returned on time.
      // sync=false disables strict rendering synchronization to the pipeline clock.
      // fullscreen=true renders the output fullscreen on the target display.
      Element display("waylandsink", "display");
      display.set("sync", false);
      display.set("fullscreen", true);

      // Stream filters used in branch links.
      // They define specific stream characteristics from the supported options.
      auto videostream = VideoFilter().format("NV12").resolution(1920, 1080).framerate(30);

      // Creates the pipeline, adds and links elements, and executes it.
      //
      // Linking is implicit and follows the order in which elements are added.
      Pipeline pipeline("cam-pipeline");
      pipeline.add(source)
      .add_stream_filter("videostream", videostream)
      .add(display)
      .execute();
      }
      int main() {
      // Route GStreamer logs through the QIMSDK logger and enable debug output.
      qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
      qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
      try {
      create_and_execute_pipeline();
      } catch (const std::exception &ex) {
      std::cerr << "Exception: " << ex.what() << std::endl;
      return 1;
      }
      return 0;
      }
      ```
    </Tab>

    <Tab title="Implicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_camera_impl`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_impl)

        Pre-built application on device: `/usr/bin/qimsdk_ref_camera_impl`
      </Info>

      ```cpp theme={null}
      #include <iostream>
      #include <qti/qimsdk.h>
      using namespace qti;
      void create_and_execute_pipeline() {
      Pipeline pipeline("cam-pipeline");
      pipeline.add("qtiqmmfsrc", "source")
      .add_stream_filter("videostream", VideoFilter().format("NV12").resolution(1920, 1080).framerate(30))
      .add("waylandsink", "display", "sync", false, "fullscreen", true)
      .execute();
      }
      int main() {
      qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
      qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
      try {
      create_and_execute_pipeline();
      } catch (const std::exception &ex) {
      std::cerr << "Exception: " << ex.what() << std::endl;
      return 1;
      }
      return 0;
      }
      ```
    </Tab>
  </Tabs>

  <Steps>
    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_camera
      ```

      The application captures live camera frames and renders them fullscreen on the display.
    </Step>

    <Step title="Expected Output">
      A live preview of the camera feed is rendered fullscreen on the display.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_camera](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Built-in Camera Preview with Still Capture

<Note>
  The application captures a still image a few seconds after starting and then stops automatically — this is expected behavior, not a hang.
</Note>

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/img_cap_isp.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=82ba5616eae28f1ac42df27be89a9b1b" alt="Introduction" width="1086" height="439" data-path="app-builder/images/img_cap_isp.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_camera_and_capture`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_and_capture)

    Pre-built application on device: `/usr/bin/qimsdk_ref_camera_and_capture`
  </Info>

  ```cpp theme={null}
  #include <iostream>
  #include <thread>
  #include <cstdlib>
  #include <qti/qimsdk.h>
  using namespace qti;
  static const std::string home_path =
  std::getenv("HOME") ? std::getenv("HOME") : "";
  //  Example pipeline:
  //
  //    source → [vf] → display → [if] → imagesink
  //
  //  The pipeline reads camera frames, runs ML inference and postprocessing,
  //  and displays the result through Wayland.
  void create_and_execute_pipeline() {
  // Captures frames from the camera source.
  Element source("qtiqmmfsrc", "source");

  // Render video stream on display.
  //
  // async=false enforce state transition to ensure the buffers are returned on time.
  // sync=false disables strict rendering synchronization to the pipeline clock.
  // fullscreen=true renders the output fullscreen on the target display.
  Element display("waylandsink", "display");
  display.set("async", false);
  display.set("sync", false);
  display.set("fullscreen", true);

  // Writes output buffers to files.
  Element imagesink("multifilesink", "imagesink");
  imagesink.set("enable-last-sample", false);
  imagesink.set("location", home_path + "/Downloads/qimsdk_samples/media/image_%d.jpeg");

  // Stream filters used in branch links.
  // They define specific stream characteristics from the supported options.
  auto vf = VideoFilter().format("NV12").resolution(1920, 1080).framerate(30);
  auto image_filter = ImageFilter().format("jpeg").resolution(3840, 2160);

  // Creates the pipeline, adds and links elements, and executes it.
  //
  // Explicit linking is applied
  Pipeline pipeline("cam-pipeline");
  pipeline.add(source)
  .add_stream_filter("vf", vf)
  .add(display)
  .add_stream_filter("if", image_filter)
  .add(imagesink)
  .link("source", "vf", "display")
  .link("source", "if", "imagesink")
  .start();

  std::this_thread::sleep_for(std::chrono::seconds(2));

  auto cam = pipeline.get<CamSrc>("source");
  cam.image_capture();

  std::this_thread::sleep_for(std::chrono::seconds(2));

  pipeline.stop();
  }
  int main() {
  if (home_path.empty()) {
  std::cerr << "Error: HOME environment variable is not set." << std::endl;
  return 1;
  }
  // Route GStreamer logs through the QIMSDK logger and enable debug output.
  qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
  qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
  try {
  create_and_execute_pipeline();
  } catch (const std::exception &ex) {
  std::cerr << "Exception: " << ex.what() << std::endl;
  return 1;
  }
  return 0;
  }
  ```

  <Steps>
    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_camera_and_capture
      ```

      The application displays a live preview and captures a still JPEG image a few seconds after starting, saving it under `~/media/image_%d.jpeg`.
    </Step>

    <Step title="Expected Output">
      A live preview is rendered, followed by a still JPEG image saved to `~/Downloads/qimsdk_samples/media/image_%d.jpeg`. The application then stops automatically.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_camera\_and\_capture](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_and_capture)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Camera Recording to MP4

This pipeline captures frames from the built-in camera, encodes them into an H.264 stream using the hardware encoder, parses the bitstream, muxes it into an MP4 container, and writes the resulting file to disk.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/camera-encoder.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=3facf40df2e33b24c695a56c41e08223" alt="Introduction" width="2471" height="453" data-path="app-builder/images/camera-encoder.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_camera_encoder`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_encoder)

    Pre-built application on device: `/usr/bin/qimsdk_ref_camera_encoder`
  </Info>

  ```cpp theme={null}
  #include <iostream>
  #include <cstdlib>
  #include <qti/qimsdk.h>
  using namespace qti;
  static const std::string home_path =
  std::getenv("HOME") ? std::getenv("HOME") : "";
  //  Example pipeline:
  //
  //    source -> [vf] -> encoder -> parser -> muxer -> sink
  //
  //  The pipeline reads camera frames, encodes them with H.264, muxes into MP4,
  //  and writes the output to disk.
  void create_and_execute_pipeline() {
  // Captures frames from the camera source.
  Element source("qtiqmmfsrc", "source");
  // Encodes raw video frames into H.264 stream.
  Element encoder("v4l2h264enc", "encoder");
  encoder.set("output-io-mode", "dmabuf-import");
  encoder.set("capture-io-mode", "dmabuf");
  // Parses H.264 bitstream for downstream muxing.
  Element parser("h264parse", "parser");
  // Muxes encoded stream into MP4 container.
  Element muxer("mp4mux", "muxer");
  // Writes output stream to a file.
  Element sink("filesink", "sink");
  sink.set("location", home_path + "/Downloads/qimsdk_samples/media/encoder_output.mp4");
  // Stream filters used in branch links.
  // They define specific stream characteristics from the supported options.
  auto vf = VideoFilter().format("NV12").resolution(1920, 1080).framerate(30);
  // Creates the pipeline, adds and links elements, and executes it.
  //
  // Explicit linking is applied.
  Pipeline pipeline("cam-encoder-pipeline");
  pipeline.add(source)
  .add_stream_filter("vf", vf)
  .add(encoder)
  .add(parser)
  .add(muxer)
  .add(sink)
  .eos(true)
  .execute();
  }
  int main() {
  if (home_path.empty()) {
  std::cerr << "Error: HOME environment variable is not set." << std::endl;
  return 1;
  }
  // Route GStreamer logs through the QIMSDK logger and enable debug output.
  qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
  qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
  try {
  create_and_execute_pipeline();
  } catch (const std::exception &ex) {
  std::cerr << "Exception: " << ex.what() << std::endl;
  return 1;
  }
  return 0;
  }
  ```

  <Steps>
    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_camera_encoder
      ```

      The application records camera frames encoded as H.264 into an MP4 file saved to `~/Downloads/qimsdk_samples/media/encoder_output.mp4`.
    </Step>

    <Step title="Expected Output">
      The recorded MP4 file is written to `~/Downloads/qimsdk_samples/media/encoder_output.mp4` and can be played back to verify the camera recording.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_camera\_encoder](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_encoder)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### MP4 File Playback

This pipeline ingests an MP4 file, demultiplexes it to extract the video track, decodes the compressed stream (e.g., H.264/H.265) into raw frames, converts (or directly negotiates) the output to NV12 — a hardware-friendly YUV 4:2:0 semi-planar format — and then renders the frames to the display using a sink that supports zero-copy or hardware buffers.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/offline_video_src.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=22f1d4b65ef60c1a7e1561843da9a5ee" alt="Introduction" width="2139" height="326" data-path="app-builder/images/offline_video_src.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_qtdemux_decode_display`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_qtdemux_decode_display)

    Pre-built application on device: `/usr/bin/qimsdk_ref_qtdemux_decode_display`
  </Info>

  ```cpp theme={null}
  #include <iostream>
  #include <cstdlib>
  #include <qti/qimsdk.h>
  using namespace qti;
  static const std::string home_path =
  std::getenv("HOME") ? std::getenv("HOME") : "";
  //  Example pipeline:
  //
  //    src → demux → parse → decoder → [videofilter] → display
  //
  //  The pipeline reads an MP4/H.264 file, decodes it through the hardware decoder,
  //  overlays detected objects, and displays the result through Wayland.
  void create_and_execute_pipeline() {
  // Reads the input media file as raw bytes.
  Element src("filesrc", "src");
  src.set("location", home_path + "/Downloads/qimsdk_samples/media/ai_demo_sample.mp4");

  // Extracts elementary streams from the MP4 container.
  Element demux("qtdemux", "demux");

  // Prepares the H.264 bitstream for the decoder.
  Element parse("h264parse", "parse");

  // Decodes the compressed H.264 stream into raw video frames.
  //
  // The I/O mode is configured to enforce DMA buffer usage,
  // avoiding unnecessary buffer copies.
  Element decoder("v4l2h264dec", "decoder");
  decoder.set("output-io-mode", 4);
  decoder.set("capture-io-mode", 4);

  // Render video stream on display.
  //
  // async=false enforce state transition to ensure the buffers are returned on time.
  // sync=true keeps rendering synchronized to the pipeline clock.
  // fullscreen=true renders the output fullscreen on the target display.
  Element display("waylandsink", "display");
  display.set("fullscreen", true);

  // Stream filters used in branch links.
  // They define specific stream characteristics from the supported options.
  auto videofilter = VideoFilter().format("NV12");

  // Creates the pipeline, adds and links elements, and executes it.
  //
  // Linking is implicit and follows the order in which elements are added.
  Pipeline pipeline("video-pipeline");
  pipeline.add(src)
  .add(demux)
  .add(parse)
  .add(decoder)
  .add_stream_filter("videofilter", videofilter)
  .add(display);
  pipeline.start().wait().stop();
  }
  int main() {
  if (home_path.empty()) {
  std::cerr << "Error: HOME environment variable is not set." << std::endl;
  return 1;
  }
  // Route GStreamer logs through the QIMSDK logger and enable debug output.
  qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
  qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
  try {
  create_and_execute_pipeline();
  } catch (const std::exception &ex) {
  std::cerr << "Exception: " << ex.what() << std::endl;
  return 1;
  }
  return 0;
  }
  ```

  <Steps>
    <Step title="Download Required Files">
      | File         | Download                                                                                                                                                     | Save as              |
      | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- |
      | Sample video | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | `ai_demo_sample.mp4` |

      You can also use your own MP4 (H.264) file, or update the pipeline's `location` property to point to it.
    </Step>

    <Step title="Copy the video file to the device">
      ```bash theme={null}
      # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

      ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/media"
      scp ai_demo_sample.mp4 <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
      ```
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_qtdemux_decode_display
      ```

      The application decodes the video file and renders it fullscreen on the display.
    </Step>

    <Step title="Expected Output">
      The MP4 video plays back fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/video-play-back.png?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=3e0d2e8fcff29d3d3ed1778114b86df7" alt="Expected Output" width="2260" height="1267" data-path="app-builder/images/video-play-back.png" />

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_qtdemux\_decode\_display](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_qtdemux_decode_display)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Transport Stream (.ts) File Playback

This pipeline is similar to the Offline Video Source pipeline, but ingests an MPEG transport stream (`.ts`) file instead of an MP4 container. It demultiplexes the transport stream using `tsdemux` to extract the video track, decodes the compressed H.264 stream into raw frames, converts (or directly negotiates) the output to NV12, and renders the frames to the display.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/transport-stream-video-source.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=d82a97ad8232ad92d41ae13f30ea397e" alt="Introduction" width="2135" height="538" data-path="app-builder/images/transport-stream-video-source.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_tsdemux_decode_display`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_tsdemux_decode_display)

    Pre-built application on device: `/usr/bin/qimsdk_ref_tsdemux_decode_display`
  </Info>

  ```cpp theme={null}
  #include <iostream>
  #include <cstdlib>
  #include <qti/qimsdk.h>
  using namespace qti;
  static const std::string home_path =
  std::getenv("HOME") ? std::getenv("HOME") : "";
  //  Example pipeline:
  //
  //    src → demux → [vf1] → parse → decoder → [vf2] → display
  //
  //  The pipeline reads an MPEG transport stream (.ts) file, demultiplexes it,
  //  decodes the H.264 stream through the hardware decoder, and displays the
  //  result through Wayland.
  void create_and_execute_pipeline() {
  // Reads a sequence of input files as stream data.
  Element src("multifilesrc", "src");
  src.set("location", home_path + "/Downloads/qimsdk_samples/media/ai_demo_sample.ts");
  src.set("stop-index", 0);
  // Extracts elementary streams from the transport stream.
  Element demux("tsdemux", "demux");
  // Prepares the H.264 bitstream for the decoder.
  Element parse("h264parse", "parse");
  // Decodes the compressed H.264 stream into raw video frames.
  //
  // The I/O mode is configured to enforce DMA buffer usage,
  // avoiding unnecessary buffer copies.
  Element decoder("v4l2h264dec", "decoder");
  decoder.set("output-io-mode", 4);
  decoder.set("capture-io-mode", 4);
  // Render video stream on display.
  //
  // async=false enforce state transition to ensure the buffers are returned on time.
  // sync=true keeps rendering synchronized to the pipeline clock.
  // fullscreen=true renders the output fullscreen on the target display.
  Element display("waylandsink", "display");
  display.set("fullscreen", true);
  auto vf1 = H264Filter().framerate(30);
  // Stream filters used in branch links.
  // They define specific stream characteristics from the supported options.
  auto vf2 = VideoFilter().format("NV12");
  // Creates the pipeline, adds and links elements, and executes it.
  //
  // Linking is implicit and follows the order in which elements are added.
  Pipeline pipeline("video-pipeline");
  pipeline.add(src)
  .add(demux)
  .add_stream_filter("vf1", vf1)
  .add(parse)
  .add(decoder)
  .add_stream_filter("vf2", vf2)
  .add(display);
  pipeline.start().wait().stop();
  }
  int main() {
  if (home_path.empty()) {
  std::cerr << "Error: HOME environment variable is not set." << std::endl;
  return 1;
  }
  // Route GStreamer logs through the QIMSDK logger and enable debug output.
  qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
  qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
  try {
  create_and_execute_pipeline();
  } catch (const std::exception &ex) {
  std::cerr << "Exception: " << ex.what() << std::endl;
  return 1;
  }
  return 0;
  }
  ```

  <Steps>
    <Step title="Download Required Files">
      | File                    | Download                                                                                                                                                             | Save as             |
      | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
      | Sample transport stream | [ai\_demo\_sample.ts](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/raw/refs/heads/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.ts) | `ai_demo_sample.ts` |
    </Step>

    <Step title="Copy the transport stream file to the device">
      ```bash theme={null}
      # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

      ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/media"
      scp ai_demo_sample.ts <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
      ```
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_tsdemux_decode_display
      ```

      The application decodes the transport stream file and renders it fullscreen on the display.
    </Step>

    <Step title="Expected Output">
      The transport stream video plays back fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/video-play-back.png?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=3e0d2e8fcff29d3d3ed1778114b86df7" alt="Expected Output" width="2260" height="1267" data-path="app-builder/images/video-play-back.png" />

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_tsdemux\_decode\_display](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_tsdemux_decode_display)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Synthetic Test Pattern (VideoFilter API)

This pipeline generates synthetic test video frames using `videotestsrc` (a bouncing ball pattern), instead of reading from a camera or file. The frames are restricted to NV12/1080p/30fps, rotated 180 degrees by [`qtivtransform`](/plugin-reference/qtivtransform), downscaled to 1280x720 by a second stream filter, and rendered to the display. This is a useful pipeline for testing display and transform stages without any camera or media file dependency.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/video-test-source-with-video-filter.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=d61e04032b475dd2ee014d28ed1a7fa8" alt="Introduction" width="2193" height="499" data-path="app-builder/images/video-test-source-with-video-filter.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_videotestsrc_videofilter_display`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_videotestsrc_videofilter_display)

    Pre-built application on device: `/usr/bin/qimsdk_ref_videotestsrc_videofilter_display`
  </Info>

  ```cpp theme={null}
  #include <iostream>
  #include <qti/qimsdk.h>
  using namespace qti;
  //  Example pipeline:
  //
  //    src → [vf1] → transform → [vf2] → sink
  //
  //  The pipeline generates synthetic test video frames, rotates them, restricts
  //  the stream to NV12/1080p/30fps, downscales to 720p, and displays the result
  //  through Wayland.
  void create_and_execute_pipeline() {
  // Generates synthetic test video frames.
  Element src("videotestsrc", "src");
  src.set("pattern", "ball");
  // Applies geometric transforms to video frames.
  Element transform("qtivtransform", "transform");
  transform.set("rotate", "180");
  // Render video stream on display.
  Element sink("waylandsink", "sink");
  sink.set("fullscreen", true);
  // Stream filters used in branch links.
  // They define specific stream characteristics from the supported options.
  auto vf1 = qti::VideoFilter().format("NV12").resolution(1920, 1080).framerate(30);
  auto vf2 = qti::VideoFilter().resolution(1280, 720);
  // Creates the pipeline, adds and links elements, and executes it.
  //
  // Linking is implicit and follows the order in which elements are added.
  Pipeline pipeline("video-pipeline");
  pipeline.add(src)
  .add_stream_filter("vf1", vf1)
  .add(transform)
  .add_stream_filter("vf2", vf2)
  .add(sink)
  .execute();
  }
  int main() {
  // Route GStreamer logs through the QIMSDK logger and enable debug output.
  qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
  qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
  try {
  create_and_execute_pipeline();
  } catch (const std::exception &ex) {
  std::cerr << "Exception: " << ex.what() << std::endl;
  return 1;
  }
  return 0;
  }
  ```

  <Steps>
    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_videotestsrc_videofilter_display
      ```

      The application generates a synthetic bouncing-ball test pattern, rotates and rescales it, and renders it fullscreen on the display.
    </Step>

    <Step title="Expected Output">
      A rotated, rescaled bouncing-ball test pattern is rendered fullscreen on the display.

      <video src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/Synthetic.mp4?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=214760d31f05f15713e9a5be9f8238eb" autoPlay muted loop playsInline style={{ width: "100%", height: "auto", display: "block" }} data-path="app-builder/images/Synthetic.mp4" />

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_videotestsrc\_videofilter\_display](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_videotestsrc_videofilter_display)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Synthetic Test Pattern (Raw Caps StreamFilter)

This pipeline is functionally identical to the previous example, but it defines its stream constraints using raw GStreamer caps strings via `StreamFilter` (e.g. `"video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1"`), instead of the structured `VideoFilter` builder API. This is useful when you need to express filter capabilities that aren't covered by the structured builder, or when porting caps strings directly from existing GStreamer pipelines.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/video-test-source-with-stream-filter.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=94bcfdb72df4216c99f806f236ed2c84" alt="Introduction" width="2134" height="479" data-path="app-builder/images/video-test-source-with-stream-filter.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_videotestsrc_streamfilter_display`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_videotestsrc_streamfilter_display)

    Pre-built application on device: `/usr/bin/qimsdk_ref_videotestsrc_streamfilter_display`
  </Info>

  ```cpp theme={null}
  #include <iostream>
  #include <qti/qimsdk.h>
  using namespace qti;
  //  Example pipeline:
  //
  //    src → [vf1] → transform → [vf2] → sink
  //
  //  The pipeline generates synthetic test video frames, rotates them, restricts
  //  the stream using raw caps strings, and displays the result through Wayland.
  void create_and_execute_pipeline() {
  // Generates synthetic test video frames.
  Element src("videotestsrc", "src");
  src.set("pattern", "ball");
  // Applies geometric transforms to video frames.
  Element transform("qtivtransform", "transform");
  transform.set("rotate", "180");
  // Render video stream on display.
  Element sink("waylandsink", "sink");
  sink.set("fullscreen", true);
  // Stream filters expressed as raw GStreamer caps strings, used in branch links.
  qti::StreamFilter vf1(
  "video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1");
  qti::StreamFilter vf2("video/x-raw,width=1280,height=720");
  // Creates the pipeline, adds and links elements, and executes it.
  //
  // Linking is implicit and follows the order in which elements are added.
  Pipeline pipeline("video-pipeline-streamfilter-string");
  pipeline.add(src)
  .add_stream_filter("vf1", vf1)
  .add(transform)
  .add_stream_filter("vf2", vf2)
  .add(sink)
  .execute();
  }
  int main() {
  // Route GStreamer logs through the QIMSDK logger and enable debug output.
  qti::SetImsdkGstLogMode(qti::ImsdkGstLogMode::ImsdkLog);
  qti::SetImsdkLogLevel(qti::ImsdkLogLevel::Debug);
  try {
  create_and_execute_pipeline();
  } catch (const std::exception &ex) {
  std::cerr << "Exception: " << ex.what() << std::endl;
  return 1;
  }
  return 0;
  }
  ```

  <Steps>
    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_videotestsrc_streamfilter_display
      ```

      The application generates a synthetic bouncing-ball test pattern, rotates and rescales it, and renders it fullscreen on the display.
    </Step>

    <Step title="Expected Output">
      A rotated, rescaled bouncing-ball test pattern is rendered fullscreen on the display.

      <video src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/Synthetic.mp4?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=214760d31f05f15713e9a5be9f8238eb" autoPlay muted loop playsInline style={{ width: "100%", height: "auto", display: "block" }} data-path="app-builder/images/Synthetic.mp4" />

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_videotestsrc\_streamfilter\_display](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_videotestsrc_streamfilter_display)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### RTSP Camera Stream Playback

This pipeline connects to a network RTSP camera stream instead of a local (USB/built-in) camera or file. `rtspsrc` establishes the RTSP session and negotiates the H.264 RTP stream, a caps filter restricts it to H.264 video, `rtph264depay` extracts the H.264 payload from the RTP packets, `h264parse` prepares the bitstream for the hardware decoder, and the decoded frames are converted to NV12 and rendered to the display.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/rtsp-camera-source.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=6dd772d9868d25bcda54b763ea2909cf" alt="Introduction" width="2121" height="429" data-path="app-builder/images/rtsp-camera-source.png" />

<Accordion title="Try me">
  <Info>
    See the full YAML configuration here: [qimsdk\_ref\_rtsp\_camera.yaml](https://github.com/qualcomm/qimsdk/blob/main/tools/appbuilders-configs/qimsdk_ref_rtsp_camera.yaml)

    Check application source code on GitHub: [qimsdk\_ref\_yml](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yml)

    Pre-built application on device: `/usr/bin/qimsdk_ref_yml`

    YAML configuration saved on device: `/etc/qimsdk/qimsdk_ref_rtsp_camera.yaml`
  </Info>

  ```yaml theme={null}
  pipeline:
    elements:
      - type: rtspsrc
        name: source
        location: rtsp://username:password@192.168.1.188:554/Streaming/Channels/101
        latency: 0

      - type: capsfilter
        name: rtp_caps
        caps: application/x-rtp,media=video,encoding-name=H264

      - type: rtph264depay
        name: depay

      - type: h264parse
        name: parse

      - type: v4l2h264dec
        name: decoder
        capture-io-mode: 4
        output-io-mode: 4

      - type: filter
        name: videostream
        video:
          format: NV12

      - type: waylandsink
        name: display
        sync: false
        fullscreen: true
  ```

  <Steps>
    <Step title="Update the RTSP camera credentials">
      On the device, edit `/etc/qimsdk/qimsdk_ref_rtsp_camera.yaml` and update the `location` property with your RTSP camera's username, password, IP address, and stream path.
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      qimsdk_ref_yml /etc/qimsdk/qimsdk_ref_rtsp_camera.yaml
      ```

      The application connects to the RTSP camera, decodes the H.264 stream, and renders it fullscreen on the display.
    </Step>

    <Step title="Expected Output">
      The live RTSP camera stream plays back fullscreen on the display.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## AI Pipeline

### Video-to-Tensor Dump (Offline Preprocessing)

This pipeline ingests an MP4 file, demultiplexes it to extract the video track, decodes the compressed stream (e.g., H.264/H.265) into raw frames, converts (or directly negotiates) the output to NV12 — a hardware-friendly YUV 4:2:0 semi-planar format — and then goes through [`qtimlvconverter`](/plugin-reference/qtimlvconverter) (ML preprocessing). The `TensorFilter` stream filter describes the tensor format and shape as UINT8 with dimensions \[1, 520, 520, 3] (batch, height, width, channels). Finally, `multifilesink` writes the preprocessed tensors out as sequential `.rgb` files to `~/Downloads/qimsdk_samples/media/tensor_520_520_%d.rgb`, rolling after every 10 files.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/ai_tensors.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=2f0621ae6c6dedfc7bbadf1b1dee16d1" alt="Introduction" width="2131" height="311" data-path="app-builder/images/ai_tensors.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_tensor_dump`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_tensor_dump)

    Pre-built application on device: `/usr/bin/qimsdk_ref_tensor_dump`
  </Info>

  <Steps>
    <Step title="Download Required Files">
      | File         | Download                                                                                                                                                     | Save as              |
      | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- |
      | Sample video | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | `ai_demo_sample.mp4` |

      You can also use your own MP4 (H.264) file, or update the pipeline's `location` property to point to it.
    </Step>

    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/media"
        scp ai_demo_sample.mp4 <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_tensor_dump
      ```

      The application writes preprocessed tensors to `~/Downloads/qimsdk_samples/media/tensor_520_520_%d.rgb`.
    </Step>

    <Step title="Expected Output">
      Sequential `.rgb` tensor files accumulate under `~/Downloads/qimsdk_samples/media/`, rolling after every 10 files.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_tensor\_dump](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_tensor_dump)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Live Camera YOLOv8 Detection (Manual Chain)

This pipeline captures live video from the on-device camera, then drives a full detection-and-visualization loop in real time: the raw NV12 stream is normalized and preprocessed into one or more tensors, fed to a TFLite inference stage (running on the external QNN delegate for efficient hardware execution), and the resulting tensors are postprocessed into semantic detections (classes, boxes, confidences). Those results are converted into QIM SDK-friendly metadata and attached back to the original video timeline, so the visual stream and its ML annotations stay synchronized. A parallel branch from the early tee ensures the original frames are available to a metadata muxer, which merges the detections with the video. Finally, the overlay stage renders the boxes/labels directly on the live frames, and the composed output is presented fullscreen to the display. In short, the graph turns a live camera feed into actionable on-screen insights by (1) creating a tensor view of the stream, (2) running detection, (3) translating inference outputs into QIM SDK metadata, (4) fusing metadata with the original frames, and (5) drawing the results on top of the video for low-latency, on-screen visualization.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/ai_pipeline.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=46292a8fe9ebbf84232c13388e105d40" alt="Introduction" width="1805" height="319" data-path="app-builder/images/ai_pipeline.png" />

<Accordion title="Try me">
  <Tabs>
    <Tab title="Explicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_camera_yolov8`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_yolov8)

        Pre-built application on device: `/usr/bin/qimsdk_ref_camera_yolov8`
      </Info>

      #### Download Required Files

      | File                                                                   | Save as                       |
      | ---------------------------------------------------------------------- | ----------------------------- |
      | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)              | yolov8\_det\_quantized.tflite |
      | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a> | yolov8.json                   |

      <Note>
        If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

        Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
      </Note>

      <Steps>
        <Step title="Copy Files to Device">
          <CodeGroup>
            ```bash SCP (SSH) theme={null}
            # Replace <user> and <device-ip> with your device credentials.
            # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

            ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels}"
            scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
            scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
            ```
          </CodeGroup>
        </Step>

        <Step title="Run the application">
          ```bash theme={null}
          /usr/bin/qimsdk_ref_camera_yolov8
          ```

          The application overlays detected bounding boxes and labels on the live camera feed and renders it fullscreen on the display.
        </Step>

        <Step title="Expected Output">
          The live camera feed is rendered fullscreen with detected objects outlined by bounding boxes and labeled with class names.

          <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/object-det-live.jpeg?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=9e1969d7993f5be0c0e9e8b6dacb2095" alt="Expected Output" width="987" height="585" data-path="app-builder/images/object-det-live.jpeg" />

          To stop the application, press **CTRL + C**.
        </Step>
      </Steps>
    </Tab>

    <Tab title="YAML config style">
      <Info>
        See the full YAML configuration here: [qimsdk\_ref\_camera\_yolov8.yaml](https://github.com/qualcomm/qimsdk/blob/main/tools/appbuilders-configs/qimsdk_ref_camera_yolov8.yaml)

        Check application source code on GitHub: [qimsdk\_ref\_yml](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yml)

        Pre-built application on device: `/usr/bin/qimsdk_ref_yml`

        YAML configuration saved on device: `/etc/qimsdk/qimsdk_ref_camera_yolov8.yaml`
      </Info>

      ```yaml theme={null}
      pipeline:
        elements:
          - type: qtiqmmfsrc
            name: source
            camera: 0

          - type: filter
            name: videostream
            video:
              format: NV12
              width: 1920
              height: 1080
              framerate: 30

          - type: tee
            name: split

          - type: queue
            name: q1

          - type: qtimlvconverter
            name: preprocessing

          - type: queue
            name: q3

          - type: qtimltflite
            name: inferencing
            delegate: external
            external-delegate-path: libQnnTFLiteDelegate.so
            external-delegate-options: "QNNExternalDelegate,backend_type=htp;"
            model: ~/Downloads/qimsdk_samples/models/yolov8_det_quantized.tflite

          - type: queue
            name: q4

          - type: qtimlpostprocess
            name: postprocessing
            results: 5
            module: yolov8
            labels: ~/Downloads/qimsdk_samples/labels/yolov8.json
            settings: '{"confidence": 70.0}'

          - type: filter
            name: mlf
            text: {}

          - type: qtimetamux
            name: mlmuxer

          - type: queue
            name: q5

          - type: qtivoverlay
            name: overlay

          - type: waylandsink
            name: display
            sync: false
            fullscreen: true

        links:
          - [split, mlmuxer]
          - [source, videostream, split, q1, preprocessing, q3, inferencing, q4, postprocessing, mlf, mlmuxer, q5, overlay, display]
      ```

      #### Download Required Files

      | File                                                                   | Save as                       |
      | ---------------------------------------------------------------------- | ----------------------------- |
      | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)              | yolov8\_det\_quantized.tflite |
      | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a> | yolov8.json                   |

      <Note>
        If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

        Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
      </Note>

      <Steps>
        <Step title="Copy Files to Device">
          <CodeGroup>
            ```bash SCP (SSH) theme={null}
            # Replace <user> and <device-ip> with your device credentials.
            # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

            ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels}"
            scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
            scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
            ```
          </CodeGroup>
        </Step>

        <Step title="Run the application">
          ```bash theme={null}
          qimsdk_ref_yml /etc/qimsdk/qimsdk_ref_camera_yolov8.yaml
          ```

          The application overlays detected bounding boxes and labels on the live camera feed and renders it fullscreen on the display.
        </Step>

        <Step title="Expected Output">
          The live camera feed is rendered fullscreen with detected objects outlined by bounding boxes and labeled with class names.

          <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/object-det-live.jpeg?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=9e1969d7993f5be0c0e9e8b6dacb2095" alt="Expected Output" width="987" height="585" data-path="app-builder/images/object-det-live.jpeg" />

          To stop the application, press **CTRL + C**.
        </Step>
      </Steps>
    </Tab>
  </Tabs>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_camera\_yolov8](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_yolov8)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Live Camera YOLOv8 Detection (ML Bin)

This solution is identical to the previous one. The only difference is that here we use `qtimlvideotflitebin` instead of manually linking the preprocessing, inference, postprocessing, and ML muxer components, which simplifies the pipeline.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/inference_bin_alt.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=f7e11d08c205cab98d6e4a7da7004aca" alt="Introduction" width="1384" height="812" data-path="app-builder/images/inference_bin_alt.png" />

<Accordion title="Try me">
  <Tabs>
    <Tab title="Explicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_camera_yolov8_mlbin`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_yolov8_mlbin)

        Pre-built application on device: `/usr/bin/qimsdk_ref_camera_yolov8_mlbin`
      </Info>

      #### Download Required Files

      | File                                                                   | Save as                       |
      | ---------------------------------------------------------------------- | ----------------------------- |
      | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)              | yolov8\_det\_quantized.tflite |
      | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a> | yolov8.json                   |

      <Note>
        If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

        Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
      </Note>

      <Steps>
        <Step title="Copy Files to Device">
          <CodeGroup>
            ```bash SCP (SSH) theme={null}
            # Replace <user> and <device-ip> with your device credentials.
            # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

            ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels}"
            scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
            scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
            ```
          </CodeGroup>
        </Step>

        <Step title="Run the application">
          ```bash theme={null}
          /usr/bin/qimsdk_ref_camera_yolov8_mlbin
          ```

          The application overlays detected bounding boxes and labels on the live camera feed and renders it fullscreen on the display.
        </Step>

        <Step title="Expected Output">
          The live camera feed is rendered fullscreen with detected objects outlined by bounding boxes and labeled with class names.

          <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/object-det-live.jpeg?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=9e1969d7993f5be0c0e9e8b6dacb2095" alt="Expected Output" width="987" height="585" data-path="app-builder/images/object-det-live.jpeg" />

          To stop the application, press **CTRL + C**.
        </Step>
      </Steps>
    </Tab>

    <Tab title="YAML config style">
      <Info>
        See the full YAML configuration here: [qimsdk\_ref\_mlbin\_yolov8.yaml](https://github.com/qualcomm/qimsdk/blob/main/tools/appbuilders-configs/qimsdk_ref_mlbin_yolov8.yaml)

        Check application source code on GitHub: [qimsdk\_ref\_yml](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yml)

        Pre-built application on device: `/usr/bin/qimsdk_ref_yml`

        YAML configuration saved on device: `/etc/qimsdk/qimsdk_ref_mlbin_yolov8.yaml`
      </Info>

      ```yaml theme={null}
      pipeline:
        elements:
          - type: filesrc
            name: src
            location: ~/Downloads/qimsdk_samples/media/ai_demo_sample.mp4

          - type: qtdemux
            name: demux

          - type: h264parse
            name: parse

          - type: v4l2h264dec
            name: decoder
            output-io-mode: 4
            capture-io-mode: 4

          - type: filter
            name: videofilter
            video:
              format: NV12

          - type: qtimlvideotflitebin
            name: mlbin
            inference-delegate: "external"
            inference-external-delegate-path: "libQnnTFLiteDelegate.so"
            inference-external-delegate-options: "QNNExternalDelegate,backend_type=htp;"
            inference-model: "~/Downloads/qimsdk_samples/models/yolov8_det_quantized.tflite"
            postprocess-module: "yolov8"
            postprocess-labels: "~/Downloads/qimsdk_samples/labels/yolov8.json"

          - type: qtivoverlay
            name: overlay

          - type: waylandsink
            name: display
            fullscreen: true
      ```

      #### Download Required Files

      | File                                                                   | Save as                       |
      | ---------------------------------------------------------------------- | ----------------------------- |
      | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)              | yolov8\_det\_quantized.tflite |
      | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a> | yolov8.json                   |

      <Note>
        If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

        Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
      </Note>

      <Steps>
        <Step title="Copy Files to Device">
          <CodeGroup>
            ```bash SCP (SSH) theme={null}
            # Replace <user> and <device-ip> with your device credentials.
            # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

            ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels}"
            scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
            scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
            ```
          </CodeGroup>
        </Step>

        <Step title="Run the application">
          ```bash theme={null}
          qimsdk_ref_yml /etc/qimsdk/qimsdk_ref_mlbin_yolov8.yaml
          ```

          The application overlays detected bounding boxes and labels on the live camera feed and renders it fullscreen on the display.
        </Step>

        <Step title="Expected Output">
          The live camera feed is rendered fullscreen with detected objects outlined by bounding boxes and labeled with class names.

          <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/object-det-live.jpeg?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=9e1969d7993f5be0c0e9e8b6dacb2095" alt="Expected Output" width="987" height="585" data-path="app-builder/images/object-det-live.jpeg" />

          To stop the application, press **CTRL + C**.
        </Step>
      </Steps>
    </Tab>
  </Tabs>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_camera\_yolov8\_mlbin](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_camera_yolov8_mlbin)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Offline Video YOLOv8 Detection (Manual Chain, Metamux Overlay)

This pipeline is the offline-video counterpart to Live Camera YOLOv8 Detection (Manual Chain): instead of a live camera feed, an MP4 file is demuxed and decoded, then split by a `tee` into a display branch and an ML branch. The ML branch runs preprocessing, inference, and postprocessing manually, and the resulting detections are attached back onto the original frame via [`qtimetamux`](/plugin-reference/qtimetamux) before being overlaid and rendered. This Metamux-based approach is more flexible than the video composer approach (it allows chaining additional ML models or streaming metadata separately), whereas the composer overlay approach used in the next example offers better performance.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/ai-pipeline-metamux-overlay.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=66abd8f995edef08bcc5ce39dfdec1e6" alt="Introduction" width="2461" height="466" data-path="app-builder/images/ai-pipeline-metamux-overlay.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_yolov8_metamux_overlay`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yolov8_metamux_overlay)

    Pre-built application on device: `/usr/bin/qimsdk_ref_yolov8_metamux_overlay`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                       |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)                                                                                                    | yolov8\_det\_quantized.tflite |
  | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a>                                                                                       | yolov8.json                   |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4          |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4          <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_yolov8_metamux_overlay
      ```

      The application decodes the video file, runs YOLOv8 detection, attaches the detections to the original frame via [`qtimetamux`](/plugin-reference/qtimetamux), and overlays the results, rendering them fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back with YOLOv8 bounding boxes and class labels overlaid on each detected object, rendered fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/WywR0vqVkL-AO2Rd/app-builder/images/expected-output-object-detect.png?fit=max&auto=format&n=WywR0vqVkL-AO2Rd&q=85&s=0765f511db9b649cbac010ebc9861230" alt="Expected Output" width="1262" height="709" data-path="app-builder/images/expected-output-object-detect.png" />

      To stop the application, press **CTRL + C**.
    </Step>

    <Step title="Customize application">
      Refer to the [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application) section to customize this application.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_yolov8\_metamux\_overlay](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yolov8_metamux_overlay)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Offline Video YOLOv8 Detection (ML Bin, Composer Overlay)

The AI output can be overlaid directly on top of the frame instead of attaching AI metadata to the main frame. This approach provides better performance, whereas the Metamux solution is more flexible and can be used for chaining AI models or streaming AI metadata separately.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/ai_pipeline_with_video.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=d989ba65177fa980e91592c335eb1747" alt="Introduction" width="1798" height="328" data-path="app-builder/images/ai_pipeline_with_video.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_yolov8_composer_overlay`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yolov8_composer_overlay)

    Pre-built application on device: `/usr/bin/qimsdk_ref_yolov8_composer_overlay`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                       |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)                                                                                                    | yolov8\_det\_quantized.tflite |
  | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a>                                                                                       | yolov8.json                   |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4          |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4          <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_yolov8_composer_overlay
      ```

      The application composes the AI detection overlay directly on top of the decoded video frames and displays it fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back with YOLOv8 bounding boxes and class labels composited directly onto each frame, rendered fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/WywR0vqVkL-AO2Rd/app-builder/images/expected-output-object-detect.png?fit=max&auto=format&n=WywR0vqVkL-AO2Rd&q=85&s=0765f511db9b649cbac010ebc9861230" alt="Expected Output" width="1262" height="709" data-path="app-builder/images/expected-output-object-detect.png" />

      To stop the application, press **CTRL + C**.
    </Step>

    <Step title="Customize application">
      Refer to the [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application) section to customize this application.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_yolov8\_composer\_overlay](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yolov8_composer_overlay)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Gesture Recognition

This pipeline demonstrates a daisy-chained, multi-model gesture recognition graph built entirely from standard SDK elements ([`qtimlvconverter`](/plugin-reference/qtimlvconverter), [`qtimltflite`](/plugin-reference/qtimltflite), [`qtimlpostprocess`](/plugin-reference/qtimlpostprocess), [`qtimetamux`](/plugin-reference/qtimetamux), `qtimetatransform`) rather than a custom postprocessing callback. Palm detection locates the hand region in the live camera frame; that region is transformed and fed into hand landmark detection; the resulting landmarks are passed through a gesture embedder followed by a canned gesture classifier to decode the recognized gesture. Two parallel [`qtimetamux`](/plugin-reference/qtimetamux) merge points combine metadata from each stage back onto the original frame before the final overlay is rendered to the display.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/gesture-recognition.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=60a6ec94a3cfd6fc1120e2ddbbb7f3e2" alt="Introduction" width="2132" height="956" data-path="app-builder/images/gesture-recognition.png" />

<Accordion title="Try me">
  <Tabs>
    <Tab title="Explicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_gesture_recognition`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_gesture_recognition)

        Pre-built application on device: `/usr/bin/qimsdk_ref_gesture_recognition`
      </Info>
    </Tab>

    <Tab title="YAML config style">
      <Info>
        See the full YAML configuration here: [qimsdk\_ref\_gesture\_recognition.yaml](https://github.com/qualcomm/qimsdk/blob/main/tools/appbuilders-configs/qimsdk_ref_gesture_recognition.yaml)

        Check application source code on GitHub: [qimsdk\_ref\_yml](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yml)

        Pre-built application on device: `/usr/bin/qimsdk_ref_yml`

        YAML configuration saved on device: `/etc/qimsdk/qimsdk_ref_gesture_recognition.yaml`
      </Info>
    </Tab>
  </Tabs>

  #### Download Required Files

  Download the gesture recognizer task bundle from Google MediaPipe to obtain the palm detection, hand landmark, gesture embedder, and canned gesture classifier models:

  ```bash theme={null}
  # Download the gesture recognizer task bundle
  wget https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/latest/gesture_recognizer.task

  # Extract the top-level task
  unzip gesture_recognizer.task

  # Extract hand landmarker models
  unzip hand_landmarker.task
  # → hand_detector.tflite, hand_landmarks_detector.tflite

  # Extract gesture recognizer models
  unzip hand_gesture_recognizer.task
  # → gesture_embedder.tflite, canned_gesture_classifier.tflite
  ```

  | File                                                                                                        | Save as                            |
  | ----------------------------------------------------------------------------------------------------------- | ---------------------------------- |
  | hand\_detector.tflite (see steps above)                                                                     | palm\_detection\_full.tflite       |
  | hand\_landmarks\_detector.tflite (see steps above)                                                          | hand\_landmark\_full.tflite        |
  | gesture\_embedder.tflite (see steps above)                                                                  | gesture\_embedder.tflite           |
  | canned\_gesture\_classifier.tflite (see steps above)                                                        | canned\_gesture\_classifier.tflite |
  | <a href="../labels/palmd_labels.json" download="palmd_labels.json">palmd\_labels.json</a>                   | palmd\_labels.json                 |
  | <a href="../labels/palmd_settings.json" download="palmd_settings.json">palmd\_settings.json</a>             | palmd\_settings.json               |
  | <a href="../labels/hlandmarks.json" download="hlandmarks.json">hlandmarks.json</a>                          | hlandmarks.json                    |
  | <a href="../labels/hlandmark_settings.json" download="hlandmark_settings.json">hlandmark\_settings.json</a> | hlandmark\_settings.json           |
  | <a href="../labels/gesture_rec.json" download="gesture_rec.json">gesture\_rec.json</a>                      | gesture\_rec.json                  |

  <Note>
    If a downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      This pipeline's source is the on-device camera, so no video file needs to be copied.

      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels}"
        scp palm_detection_full.tflite       <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp hand_landmark_full.tflite        <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp gesture_embedder.tflite          <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp canned_gesture_classifier.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp palmd_labels.json                <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp palmd_settings.json              <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp hlandmarks.json                  <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp hlandmark_settings.json          <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp gesture_rec.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_gesture_recognition
      ```

      The application captures live video from the on-device camera, runs the daisy-chained palm detection → hand landmark → gesture embedder → canned gesture classifier models, and overlays the recognized gesture on the display fullscreen.
    </Step>

    <Step title="Expected Output">
      The live camera feed is rendered fullscreen with hand landmarks and the recognized gesture label overlaid.

      <video src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/gesture_title.mp4?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=e7ef0f72f564669496d916446a3da328" autoPlay muted loop playsInline style={{ width: "100%", height: "auto", display: "block" }} data-path="app-builder/images/gesture_title.mp4" />

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_gesture\_recognition](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_gesture_recognition)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### PPE & Person/Foot Detection (ML Bin)

This pipeline demonstrates a two-model ML bin pipeline for personal protective equipment (PPE) and person/foot detection: `foot_track_net` detects persons and their feet in the frame, while `gear_guard_net` detects PPE gear. Both models run through `qtimlvideotflitebin`, which internally handles preprocessing, inference, and postprocessing, simplifying the pipeline compared to manually chaining each stage.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/ml-bin-ppe-detection.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=b5b28104f64f675be8652ba80211ef02" alt="Introduction" width="2134" height="535" data-path="app-builder/images/ml-bin-ppe-detection.png" />

<Accordion title="Try me">
  <Tabs>
    <Tab title="Explicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_mlbin_ppe`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_mlbin_ppe)

        Pre-built application on device: `/usr/bin/qimsdk_ref_mlbin_ppe`
      </Info>
    </Tab>

    <Tab title="YAML config style">
      <Info>
        See the full YAML configuration here: [qimsdk\_ref\_mlbin\_ppe.yaml](https://github.com/qualcomm/qimsdk/blob/main/tools/appbuilders-configs/qimsdk_ref_mlbin_ppe.yaml)

        Check application source code on GitHub: [qimsdk\_ref\_yml](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yml)

        Pre-built application on device: `/usr/bin/qimsdk_ref_yml`

        YAML configuration saved on device: `/etc/qimsdk/qimsdk_ref_mlbin_ppe.yaml`
      </Info>
    </Tab>
  </Tabs>

  #### Download Required Files

  | File                                                                                                                                                           | Save as                                            |
  | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
  | [Model](https://aihub.qualcomm.com/models/gear_guard_net?searchTerm=gear)                                                                                      | gear\_guard\_net-ppe-detection-w8a8.tflite         |
  | [Model](https://aihub.qualcomm.com/models/foot_track_net?searchTerm=foot)                                                                                      | foot\_track\_net-person-foot-detection-w8a8.tflite |
  | <a href="../labels/gear_guard_net.json" download="gear_guard_net.json">gear\_guard\_net.json</a>                                                               | gear\_guard\_net.json                              |
  | <a href="../labels/foot_track_net.json" download="foot_track_net.json">foot\_track\_net.json</a>                                                               | foot\_track\_net.json                              |
  | <a href="../labels/foot_track_net_settings.json" download="foot_track_net_settings.json">foot\_track\_net\_settings.json</a>                                   | foot\_track\_net\_settings.json                    |
  | [ppe\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/refs/heads/main/qualcomm-linux/artifacts/videos/demo_samples/ppe_sample.mp4) | ppe\_sample.mp4                                    |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp gear_guard_net-ppe-detection-w8a8.tflite      <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp foot_track_net-person-foot-detection-w8a8.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp gear_guard_net.json                           <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp foot_track_net.json                           <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp foot_track_net_settings.json                  <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ppe_sample.mp4                                <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_mlbin_ppe
      ```

      The application runs inference on the video file, detecting persons/feet with `foot_track_net` and PPE gear with `gear_guard_net`, overlaying the resulting detections on the video and rendering it fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back fullscreen with detected persons, feet, and PPE gear outlined by bounding boxes and labeled with class names.

      <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/expected-output-ppe.png?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=bfb956c2dd1ca5ef3d68f175ab090c83" alt="Expected Output" width="1869" height="1050" data-path="app-builder/images/expected-output-ppe.png" />

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_mlbin\_ppe](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_mlbin_ppe)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### YOLOv8 Detection (ML Bin)

This pipeline reads an MP4/H.264 file, decodes it through the hardware decoder, and runs YOLOv8 object detection using `qtimlvideotflitebin`, which internally handles preprocessing, inference, and postprocessing. The detected objects are then overlaid on the video and displayed.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/mlbin-yolov8.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=ccef4a20f03a1f3ff992463da7dcad14" alt="Introduction" width="2137" height="544" data-path="app-builder/images/mlbin-yolov8.png" />

<Accordion title="Try me">
  <Tabs>
    <Tab title="Explicit coding style">
      <Info>
        Check application source code on GitHub: [`qimsdk_ref_mlbin_yolov8`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_mlbin_yolov8)

        Pre-built application on device: `/usr/bin/qimsdk_ref_mlbin_yolov8`
      </Info>
    </Tab>

    <Tab title="YAML config style">
      <Info>
        See the full YAML configuration here: [qimsdk\_ref\_mlbin\_yolov8.yaml](https://github.com/qualcomm/qimsdk/blob/main/tools/appbuilders-configs/qimsdk_ref_mlbin_yolov8.yaml)

        Check application source code on GitHub: [qimsdk\_ref\_yml](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_yml)

        Pre-built application on device: `/usr/bin/qimsdk_ref_yml`

        YAML configuration saved on device: `/etc/qimsdk/qimsdk_ref_mlbin_yolov8.yaml`
      </Info>
    </Tab>
  </Tabs>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                       |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)                                                                                                    | yolov8\_det\_quantized.tflite |
  | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a>                                                                                       | yolov8.json                   |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4          |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4          <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_mlbin_yolov8
      ```

      The application decodes the video file, runs YOLOv8 detection through `qtimlvideotflitebin`, and overlays the detected objects on the video, rendering it fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back with YOLOv8 bounding boxes and class labels overlaid on each detected object, rendered fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/WywR0vqVkL-AO2Rd/app-builder/images/expected-output-object-detect.png?fit=max&auto=format&n=WywR0vqVkL-AO2Rd&q=85&s=0765f511db9b649cbac010ebc9861230" alt="Expected Output" width="1262" height="709" data-path="app-builder/images/expected-output-object-detect.png" />

      To stop the application, press **CTRL + C**.
    </Step>

    <Step title="Customize application">
      Refer to the [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application) section to customize this application.
    </Step>
  </Steps>
</Accordion>

## AppSink and AppSrc

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/appsink_appsrc.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=47c0b2e7747d4b442cb670ef1f016216" alt="Introduction" width="1161" height="382" data-path="app-builder/images/appsink_appsrc.png" />

AppSink and AppSrc are special elements because they expose buffers directly to and from the application. Because of this, we create and configure these elements separately, using their dedicated wrapper classes. Once configured, we insert them into the pipeline as fully constructed element objects.

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_appsrc_and_appsink`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_appsrc_and_appsink)

    Pre-built application on device: `/usr/bin/qimsdk_ref_appsrc_and_appsink`
  </Info>

  <Steps>
    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_appsrc_and_appsink
      ```

      The application generates synthetic test frames, pushes them through an `AppSink`/`AppSrc` bridge, and renders the resulting stream fullscreen on the display.
    </Step>

    <Step title="Expected Output">
      The synthetic test frames generated in application code are rendered fullscreen on the display after passing through the `AppSink`/`AppSrc` bridge.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_mlbin\_yolov8](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_mlbin_yolov8)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

## Custom Pre and Post Processing

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom_pre_and_post.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=fc618d60540500aa789ca1a4ba13343c" alt="Introduction" width="1807" height="506" data-path="app-builder/images/custom_pre_and_post.png" />

### Custom Preprocessing for YOLOv8 (Detection)

QIM SDK plugin support different backend: GLES, OpenCV, etc. This level of interface could be exposed to application. This API takes all input and output at once in case of batching or daisy for example. It can be simplified by calling application implementation for every input. Advantage of this approach is that all complexity of batching, depth, daisy chain support remains hidden in the plugin and application will do only pre-processing on a single frame/tensor at once.

<img src="https://mintcdn.com/qimsdk/7x6Bz5GZLUK00UVx/app-builder/images/custom_pre.png?fit=max&auto=format&n=7x6Bz5GZLUK00UVx&q=85&s=030b35ce1ed3d39d3efdb1af6d132836" alt="Introduction" width="2451" height="973" data-path="app-builder/images/custom_pre.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_preprocess_detection_yolov8`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_preprocess_detection_yolov8)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_preprocess_detection_yolov8`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                       |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)                                                                                                    | yolov8\_det\_quantized.tflite |
  | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a>                                                                                       | yolov8.json                   |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4          |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4          <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_preprocess_detection_yolov8
      ```
    </Step>

    <Step title="Expected Output">
      The video plays back with YOLOv8 bounding boxes and class labels overlaid on each detected object, rendered fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/WywR0vqVkL-AO2Rd/app-builder/images/expected-output-object-detect.png?fit=max&auto=format&n=WywR0vqVkL-AO2Rd&q=85&s=0765f511db9b649cbac010ebc9861230" alt="Expected Output" width="1262" height="709" data-path="app-builder/images/expected-output-object-detect.png" />

      To stop the application, press **CTRL + C**.
    </Step>

    <Step title="Customize application">
      Refer to the [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application) section to customize this application.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_preprocess\_detection\_yolov8](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_preprocess_detection_yolov8)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Preprocessing for YOLOv8 (ML Bin variant)

This example demonstrates external preprocessing support inside `qtimlvideotflitebin`. Setting `preprocess-engine` to `none` disables the bin's internal preprocessing path and hands frame-to-tensor conversion off to an application-defined callback registered via `MLVideoTFLiteBin::set_preprocess_handler(...)`. The callback receives the decoded NV12 blit(s) and writes directly into the bin's tensor buffer, converting each pixel to the quantized int8 NHWC layout expected by the YOLOv8 model, with letterbox padding for any area the blit doesn't cover. Inference and postprocessing then run inside the same bin, and the detected objects are overlaid on the video and displayed.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-preproc-mlbin.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=0604bf7ca6c8c3858d077373c9157dd1" alt="Introduction" width="2136" height="740" data-path="app-builder/images/custom-preproc-mlbin.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_preprocess_detection_mlbin_yolov8`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_preprocess_detection_mlbin_yolov8)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_preprocess_detection_mlbin_yolov8`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                       |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)                                                                                                    | yolov8\_det\_quantized.tflite |
  | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a>                                                                                       | yolov8.json                   |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4          |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4          <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_preprocess_detection_mlbin_yolov8
      ```

      The application decodes the video file, runs the custom, application-defined preprocessing callback inside `mlbin` to convert NV12 frames into the quantized int8 tensor expected by YOLOv8, then runs inference and postprocessing, overlaying the detected objects and rendering the result fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back with YOLOv8 bounding boxes and class labels overlaid on each detected object, rendered fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/WywR0vqVkL-AO2Rd/app-builder/images/expected-output-object-detect.png?fit=max&auto=format&n=WywR0vqVkL-AO2Rd&q=85&s=0765f511db9b649cbac010ebc9861230" alt="Expected Output" width="1262" height="709" data-path="app-builder/images/expected-output-object-detect.png" />

      To stop the application, press **CTRL + C**.
    </Step>

    <Step title="Customize application">
      Refer to the [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application) section to customize this application.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_preprocess\_detection\_mlbin\_yolov8](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_preprocess_detection_mlbin_yolov8)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Preprocessing with Lightweight Face Detection

This example demonstrates external preprocessing with [`qtimlvconverter`](/plugin-reference/qtimlvconverter) (rather than `qtimlvideotflitebin`), so the pipeline links preprocessing, inference, and postprocessing manually through explicit queues, and merges the detections back onto the original frame via [`qtimetamux`](/plugin-reference/qtimetamux) before overlaying. Setting `engine` to `none` on the [`MLVConverter`](/plugin-reference/qtimlvconverter) element disables its internal preprocessing path and hands frame-to-tensor conversion off to an application-defined callback registered via [`MLVConverter::set_handler(...)`](/plugin-reference/qtimlvconverter). Unlike the YOLOv8 example, the `face_det_lite_w8a8.tflite` model consumes raw uint8 grayscale, so the callback resizes only the NV12 luma (Y) plane into a single-channel `[1, H, W, 1]` tensor, skipping color conversion and normalization entirely and leaving the chroma plane unread. A `tee` right after decoding splits the stream into a display branch and an ML branch, and a queue on each branch (including the display one) keeps the two branches from stalling each other on a shared thread.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-preproc-lightweight-face-det.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=ef654987a5b442fa9fe6c657fa5d8e77" alt="Introduction" width="2142" height="849" data-path="app-builder/images/custom-preproc-lightweight-face-det.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_preprocess_lightweight_face_detect`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_preprocess_lightweight_face_detect)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_preprocess_lightweight_face_detect`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                      |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/face_det_lite)                                                                                                 | face\_det\_lite\_w8a8.tflite |
  | <a href="../labels/qfd-labels.json" download="qfd-labels.json">qfd-labels.json</a>                                                                           | qfd-labels.json              |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | face\_sample.mp4             |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp face_det_lite_w8a8.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp qfd-labels.json            <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp face_sample.mp4            <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_preprocess_lightweight_face_detect
      ```

      The application decodes the video file, runs the custom, application-defined preprocessing callback to convert the NV12 luma plane into the grayscale tensor expected by the lightweight face detector, then runs inference and postprocessing, merging the detected faces onto the original frame via [`qtimetamux`](/plugin-reference/qtimetamux), overlaying them, and rendering the result fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back fullscreen with detected faces outlined by bounding boxes.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_preprocess\_lightweight\_face\_detect](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_preprocess_lightweight_face_detect)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Preprocessing with Palm Detection

This example is the live-camera counterpart to the lightweight face detection pipeline: [`qtiqmmfsrc`](/plugin-reference/qticamsrc) captures frames directly instead of decoding a file, but the branching structure is otherwise identical — a `tee` right after the stream filter splits the pipeline into a display branch and an ML branch, each guarded by its own queue, and [`qtimetamux`](/plugin-reference/qtimetamux) merges the ML branch's detections back onto the original frame before overlay. Setting `engine` to `none` on the [`MLVConverter`](/plugin-reference/qtimlvconverter) element disables its internal preprocessing path and hands frame-to-tensor conversion off to an application-defined callback registered via [`MLVConverter::set_handler(...)`](/plugin-reference/qtimlvconverter). Unlike the grayscale face-detection example, `palm_detection_full.tflite` expects a `[1, H, W, 3]` float32 RGB tensor normalized to `[0, 1]`, so the callback converts the full NV12 image (both luma and chroma planes) to RGB and scales each channel into that range, with letterbox padding for any area the blit doesn't cover.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-preproc-palm-det.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=ba5ce463436ebd4c1fbd45e8e94f4512" alt="Introduction" width="2146" height="849" data-path="app-builder/images/custom-preproc-palm-det.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_preprocess_palm_detect`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_preprocess_palm_detect)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_preprocess_palm_detect`
  </Info>

  #### Download Required Files

  Download the gesture recognizer task bundle from Google MediaPipe to obtain the palm detection model:

  ```bash theme={null}
  # Download the gesture recognizer task bundle
  wget https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/latest/gesture_recognizer.task

  # Extract the top-level task
  unzip gesture_recognizer.task

  # Extract hand landmarker models
  unzip hand_landmarker.task
  # → hand_detector.tflite, hand_landmarks_detector.tflite
  ```

  | File                                                                                            | Save as                      |
  | ----------------------------------------------------------------------------------------------- | ---------------------------- |
  | hand\_detector.tflite (see steps above)                                                         | palm\_detection\_full.tflite |
  | <a href="../labels/palmd_labels.json" download="palmd_labels.json">palmd\_labels.json</a>       | palmd\_labels.json           |
  | <a href="../labels/palmd_settings.json" download="palmd_settings.json">palmd\_settings.json</a> | palmd\_settings.json         |

  <Note>
    If a downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      This pipeline's source is the on-device camera, so no video file needs to be copied.

      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels}"
        scp palm_detection_full.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp palmd_labels.json          <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp palmd_settings.json        <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_preprocess_palm_detect
      ```

      The application captures live video from the on-device camera, runs the custom, application-defined preprocessing callback to convert NV12 frames into the normalized float32 RGB tensor expected by the palm detector, then runs inference and postprocessing, merging the detected palms onto the original frame via [`qtimetamux`](/plugin-reference/qtimetamux), overlaying them, and rendering the result fullscreen.
    </Step>

    <Step title="Expected Output">
      The live camera feed is rendered fullscreen with detected palms outlined by bounding boxes.

      <img src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/palm-det.png?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=1c137d0b332b5cd8e9f092a78adaa971" alt="Expected Output" width="1718" height="968" data-path="app-builder/images/palm-det.png" />

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_preprocess\_palm\_detect](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_preprocess_palm_detect)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Postprocessing with YOLOv5 (Detection)

This example demonstrates the object detection postprocessing callback (`ObjectDetectionPostprocessCallback`) using a YOLOv5 model. Preprocessing and inference are handled by the SDK, and the raw output tensors are decoded into bounding boxes, class names, and confidence scores by an application-defined callback, which are then overlaid on the video and displayed.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-postproc-yolov5.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=a4849ac981cf84d197cca6ce350cab82" alt="Introduction" width="2124" height="838" data-path="app-builder/images/custom-postproc-yolov5.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_postprocess_detection_yolov5`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_detection_yolov5)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_postprocess_detection_yolov5`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                     |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
  | [Model](https://aihub.qualcomm.com/models/yolov5?searchTerm=yolov5)                                                                                          | yolov5m-320x320-int8.tflite |
  | <a href="../labels/yolov5m.json" download="yolov5m.json">yolov5m.json</a>                                                                                    | yolov5m.json                |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4        |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp yolov5m-320x320-int8.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp yolov5m.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4           <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_postprocess_detection_yolov5
      ```

      The application runs inference on the video file and uses the custom, application-defined postprocessing callback to decode detections, which are then overlaid and displayed fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back with YOLOv5 bounding boxes and class labels decoded by the custom postprocessing callback, overlaid and rendered fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/WywR0vqVkL-AO2Rd/app-builder/images/expected-output-object-detect.png?fit=max&auto=format&n=WywR0vqVkL-AO2Rd&q=85&s=0765f511db9b649cbac010ebc9861230" alt="Expected Output" width="1262" height="709" data-path="app-builder/images/expected-output-object-detect.png" />

      To stop the application, press **CTRL + C**.
    </Step>

    <Step title="Customize application">
      Refer to the [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application) section to customize this application.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_postprocess\_detection\_yolov5](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_detection_yolov5)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Post processing with ML Bin

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom_post_mlbin.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=ffe92c6996071713466d89b9e4edb79b" alt="Introduction" width="1084" height="819" data-path="app-builder/images/custom_post_mlbin.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_postprocess_mlbin_yolov8`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_mlbin_yolov8)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_postprocess_mlbin_yolov8`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                       |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/yolov8_det)                                                                                                    | yolov8\_det\_quantized.tflite |
  | <a href="../labels/yolov8.json" download="yolov8.json">yolov8.json</a>                                                                                       | yolov8.json                   |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4          |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp yolov8_det_quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp yolov8.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4          <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_postprocess_mlbin_yolov8
      ```

      The application runs preprocessing, inference, and the custom, application-defined postprocessing callback all inside `mlbin`, overlaying the resulting detections on the video and rendering it fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back with YOLOv8 bounding boxes and class labels decoded by the custom postprocessing callback, overlaid and rendered fullscreen on the display.

      <img src="https://mintcdn.com/qimsdk/WywR0vqVkL-AO2Rd/app-builder/images/expected-output-object-detect.png?fit=max&auto=format&n=WywR0vqVkL-AO2Rd&q=85&s=0765f511db9b649cbac010ebc9861230" alt="Expected Output" width="1262" height="709" data-path="app-builder/images/expected-output-object-detect.png" />

      To stop the application, press **CTRL + C**.
    </Step>

    <Step title="Customize application">
      Refer to the [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application) section to customize this application.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_postprocess\_mlbin\_yolov8](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_mlbin_yolov8)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Postprocessing with ResNet101 (Classification)

This example demonstrates the classification postprocessing callback (`ClassificationPostprocessCallback`). Preprocessing and inference are handled by the SDK, and the raw output tensors from a ResNet101 model are decoded into labeled classification results (name and confidence) by an application-defined callback.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-postproc-resnet101.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=6905707d7db248600ebd218a8c58ec92" alt="Introduction" width="2126" height="842" data-path="app-builder/images/custom-postproc-resnet101.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_postprocess_classification_resnet101`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_classification_resnet101)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_postprocess_classification_resnet101`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                     |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/resnet101)                                                                                                     | Resnet101\_Quantized.tflite |
  | <a href="../labels/resnet101.json" download="resnet101.json">resnet101.json</a>                                                                              | resnet101.json              |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | classification\_sample.mp4  |

  <Note>
    No ideal sample video ships with the SDK for this use case. Supply your own content for best results.
  </Note>

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp Resnet101_Quantized.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp resnet101.json             <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp classification_sample.mp4  <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_postprocess_classification_resnet101
      ```

      The application runs inference on the ResNet101 model and uses the custom, application-defined postprocessing callback to decode classification results, which are then displayed.
    </Step>

    <Step title="Expected Output">
      The video plays back with the predicted classification label and confidence score displayed on screen for each frame.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_postprocess\_classification\_resnet101](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_classification_resnet101)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Postprocessing with MiDaS v2 (Depth Estimation)

This example demonstrates the depth estimation postprocessing callback (`DepthEstimationPostprocessCallback`). Preprocessing and inference are handled by the SDK, and the raw output tensors from a MiDaS v2 model are decoded by an application-defined callback into a per-pixel depth map (values and pseudo-color) for downstream visualization.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-postproc-midasv2.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=7c9df81994d076f5406285a7430b34ff" alt="Introduction" width="2129" height="842" data-path="app-builder/images/custom-postproc-midasv2.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_postprocess_depth_estimation_midasv2`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_depth_estimation_midasv2)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_postprocess_depth_estimation_midasv2`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                  |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ |
  | [Model](https://aihub.qualcomm.com/models/midas)                                                                                                             | midas-tflite-w8a8.tflite |
  | <a href="../labels/midas-v2-labels.json" download="midas-v2-labels.json">midas-v2-labels.json</a>                                                            | midas-v2-labels.json     |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4     |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp midas-tflite-w8a8.tflite  <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp midas-v2-labels.json      <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4        <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_postprocess_depth_estimation_midasv2
      ```

      The application runs inference on the MiDaS v2 model and uses the custom, application-defined postprocessing callback to decode the depth map, which is then rendered.
    </Step>

    <Step title="Expected Output">
      The video plays back with a pseudo-colored depth map rendered fullscreen, where color represents estimated per-pixel depth.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_postprocess\_depth\_estimation\_midasv2](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_depth_estimation_midasv2)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Postprocessing with Gesture Recognition

This example demonstrates gesture recognition using a daisy-chained, multi-model pipeline: palm detection locates the hand region, hand landmark detection extracts keypoints from that region, and a gesture embedder followed by a canned gesture classifier decode the landmarks into a recognized gesture. Preprocessing and inference are handled by the SDK, and the raw output tensors are decoded by an application-defined postprocessing callback. The pipeline uses the on-device camera as its source.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-postproc-gesture-recog.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=2066e61d52a0e755886c6e8b53ae14f6" alt="Introduction" width="2132" height="957" data-path="app-builder/images/custom-postproc-gesture-recog.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_postprocess_gesture_recognition`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_gesture_recognition)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_postprocess_gesture_recognition`
  </Info>

  #### Download Required Files

  Download the gesture recognizer task bundle from Google MediaPipe to obtain the palm detection, hand landmark, gesture embedder, and canned gesture classifier models:

  ```bash theme={null}
  # Download the gesture recognizer task bundle
  wget https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/latest/gesture_recognizer.task

  # Extract the top-level task
  unzip gesture_recognizer.task

  # Extract hand landmarker models
  unzip hand_landmarker.task
  # → hand_detector.tflite, hand_landmarks_detector.tflite

  # Extract gesture recognizer models
  unzip hand_gesture_recognizer.task
  # → gesture_embedder.tflite, canned_gesture_classifier.tflite
  ```

  | File                                                                                      | Save as                            |
  | ----------------------------------------------------------------------------------------- | ---------------------------------- |
  | hand\_detector.tflite (see steps above)                                                   | palm\_detection\_full.tflite       |
  | hand\_landmarks\_detector.tflite (see steps above)                                        | hand\_landmark\_full.tflite        |
  | gesture\_embedder.tflite (see steps above)                                                | gesture\_embedder.tflite           |
  | canned\_gesture\_classifier.tflite (see steps above)                                      | canned\_gesture\_classifier.tflite |
  | <a href="../labels/palmd_labels.json" download="palmd_labels.json">palmd\_labels.json</a> | palmd\_labels.json                 |
  | <a href="../labels/hlandmarks.json" download="hlandmarks.json">hlandmarks.json</a>        | hlandmarks.json                    |
  | <a href="../labels/gesture_rec.json" download="gesture_rec.json">gesture\_rec.json</a>    | gesture\_rec.json                  |

  <Note>
    If a downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels}"
        scp palm_detection_full.tflite       <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp hand_landmark_full.tflite        <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp gesture_embedder.tflite          <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp canned_gesture_classifier.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp palmd_labels.json                <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp hlandmarks.json                  <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp gesture_rec.json                 <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_postprocess_gesture_recognition
      ```

      The application captures live video from the on-device camera, runs the daisy-chained palm detection → hand landmark → gesture embedder → canned gesture classifier models, and uses the custom, application-defined postprocessing callback to decode the recognized gesture, which is then overlaid and displayed fullscreen.
    </Step>

    <Step title="Expected Output">
      The live camera feed is rendered fullscreen with the recognized gesture label overlaid.

      <video src="https://mintcdn.com/qimsdk/vM1cbKMfLRcPD7xl/app-builder/images/gesture_title.mp4?fit=max&auto=format&n=vM1cbKMfLRcPD7xl&q=85&s=e7ef0f72f564669496d916446a3da328" autoPlay muted loop playsInline style={{ width: "100%", height: "auto", display: "block" }} data-path="app-builder/images/gesture_title.mp4" />

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_postprocess\_gesture\_recognition](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_gesture_recognition)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Postprocessing with HRNet (Pose Estimation)

This example demonstrates the pose estimation postprocessing callback (`PoseEstimationPostprocessCallback`) using a two-stage pipeline: a YOLOv5 model detects persons in the frame, and an HRNet model estimates keypoints for each detected person. Preprocessing and inference are handled by the SDK, and the raw output tensors are decoded into keypoints and skeleton links by an application-defined callback, which are then overlaid on the video and displayed.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-postproc-hrnet.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=a8b9d4578bb1ab5d31c8a265da5e625a" alt="Introduction" width="2283" height="836" data-path="app-builder/images/custom-postproc-hrnet.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_postprocess_pose_estimation_hrnet`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_pose_estimation_hrnet)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_postprocess_pose_estimation_hrnet`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                     |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
  | [Model](https://aihub.qualcomm.com/models/yolov5?searchTerm=yolov5)                                                                                          | yolov5m-320x320-int8.tflite |
  | [Model](https://aihub.qualcomm.com/models/hrnet_pose?searchTerm=hr)                                                                                          | hrnet\_pose\_w8a8.tflite    |
  | <a href="../labels/yolov5m.json" download="yolov5m.json">yolov5m.json</a>                                                                                    | yolov5m.json                |
  | <a href="../labels/hrnet.json" download="hrnet.json">hrnet.json</a>                                                                                          | hrnet.json                  |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | pose\_sample.mp4            |

  <Note>
    No ideal sample video ships with the SDK for this use case. Supply your own content for best results.
  </Note>

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp yolov5m-320x320-int8.tflite <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp hrnet_pose_w8a8.tflite      <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp yolov5m.json                <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp hrnet.json                  <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp pose_sample.mp4             <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_postprocess_pose_estimation_hrnet
      ```

      The application runs inference on the video file, first detecting persons with YOLOv5 and then estimating pose keypoints with HRNet, using the custom, application-defined postprocessing callback to decode the results, which are then overlaid and displayed fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back fullscreen with detected persons' pose keypoints and skeleton links overlaid.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_postprocess\_pose\_estimation\_hrnet](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_pose_estimation_hrnet)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)

### Custom Postprocessing with DeepLabv3 (Segmentation)

This example demonstrates the segmentation postprocessing callback (`SegmentationPostprocessCallback`) using a DeepLabv3 model. Preprocessing and inference are handled by the SDK, and the raw output tensors are decoded into per-pixel semantic labels and colors by an application-defined callback, which are then overlaid on the video and displayed.

<img src="https://mintcdn.com/qimsdk/QarSxH4rrv0vwi-l/app-builder/images/custom-postproc-deeplab.png?fit=max&auto=format&n=QarSxH4rrv0vwi-l&q=85&s=06fd1049578d6466691761dfeb64706a" alt="Introduction" width="2133" height="840" data-path="app-builder/images/custom-postproc-deeplab.png" />

<Accordion title="Try me">
  <Info>
    Check application source code on GitHub: [`qimsdk_ref_external_postprocess_segmentation_deeplab`](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_segmentation_deeplab)

    Pre-built application on device: `/usr/bin/qimsdk_ref_external_postprocess_segmentation_deeplab`
  </Info>

  #### Download Required Files

  | File                                                                                                                                                         | Save as                   |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- |
  | [Model](https://aihub.qualcomm.com/iot/models/deeplabv3_plus_mobilenet)                                                                                      | dv3\_argmax\_int32.tflite |
  | <a href="../labels/dv3-argmax-labels.json" download="dv3-argmax-labels.json">dv3-argmax-labels.json</a>                                                      | dv3-argmax-labels.json    |
  | [ai\_demo\_sample.mp4](https://github.com/qualcomm/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/videos/demo_samples/ai_demo_sample.mp4) | ai\_demo\_sample.mp4      |

  <Note>
    If the downloaded model file is a `.zip` archive, extract it on your host machine before copying: `unzip filename.zip`

    Some AI Hub models require running the AI Hub export/optimize step for your target runtime before the downloaded file is usable — download alone isn't always sufficient.
  </Note>

  <Steps>
    <Step title="Copy Files to Device">
      <CodeGroup>
        ```bash SCP (SSH) theme={null}
        # Replace <user> and <device-ip> with your device credentials.
        # ~ resolves automatically to the correct home directory on the device (/root on QLI, /home/ubuntu on Ubuntu).

        ssh <user>@<device-ip> "mkdir -p ~/Downloads/qimsdk_samples/{models,labels,media}"
        scp dv3_argmax_int32.tflite    <user>@<device-ip>:~/Downloads/qimsdk_samples/models/
        scp dv3-argmax-labels.json     <user>@<device-ip>:~/Downloads/qimsdk_samples/labels/
        scp ai_demo_sample.mp4         <user>@<device-ip>:~/Downloads/qimsdk_samples/media/
        ```
      </CodeGroup>
    </Step>

    <Step title="Run the application">
      ```bash theme={null}
      /usr/bin/qimsdk_ref_external_postprocess_segmentation_deeplab
      ```

      The application runs inference on the video file, segmenting the frame with DeepLabv3, and uses the custom, application-defined postprocessing callback to decode the per-pixel segmentation results, which are then overlaid and displayed fullscreen.
    </Step>

    <Step title="Expected Output">
      The video plays back fullscreen with a color-coded segmentation mask overlaid, where each color represents a detected semantic class.

      To stop the application, press **CTRL + C**.
    </Step>
  </Steps>
</Accordion>

## Build the Application

* **Source code:** [qimsdk\_ref\_external\_postprocess\_segmentation\_deeplab](https://github.com/qualcomm/qimsdk/tree/main/cpp/examples/reference-apps/qimsdk_ref_external_postprocess_segmentation_deeplab)
* **Build instructions:** [Steps to build custom application](/advanced/yocto-build#steps-to-build-custom-application)
