# Process external frames on Android

> Send direct I420 planes through the Nosmai external-frame pipeline and deliver the processed result to your own consumer.

> For AI agents: the complete documentation index is at https://nosmai.com/llms/effects.txt

Product: Nosmai Effects
Group: guide
Source: https://nosmai.com/docs/effects/guides/streaming/render-frames-off-screen/android/

## Prerequisites

- A working [Android quickstart](/docs/effects/quickstart/android/)
- An app-owned I420 source with stable dimensions and direct buffers

## When to use this guide

Use external-frame processing when your application already owns the camera or
video source. If Nosmai owns the camera and you only need processed output, use
`DUAL_OUTPUT` and a frame callback instead.

## Prepare the pipeline

Initialize Nosmai first, then configure the stable input dimensions:

```java
boolean ready = NosmaiSDK.initializeExternalFramePipeline(width, height);
if (!ready) {
    throw new IllegalStateException("External processing is unavailable");
}

NosmaiSDK.setExternalFrameMode(true);
NosmaiSDK.setExternalFrameThreadMode(NosmaiSDK.ThreadMode.GL_THREAD);
```

Reinitialize the pipeline if the input dimensions change.

## Process I420 planes

```java
NosmaiSDK.ProcessedI420Frame output =
    NosmaiSDK.processExternalI420Sync(
        yBuffer, uBuffer, vBuffer,
        width, height,
        yStride, uStride, vStride,
        rotation, mirror
    );

if (output != null) {
    consumer.onFrame(output);
}
```

The three inputs must be direct `ByteBuffer` instances with correct strides.
Consume or copy the returned native buffers before submitting the next frame.

## Process writable planes in place

```java
boolean processed = NosmaiSDK.processExternalI420InPlace(
    yBuffer, uBuffer, vBuffer,
    width, height,
    yStride, uStride, vStride,
    rotation, mirror
);
```

Use this path only when the source planes are writable and large enough for
their declared strides. The call blocks until processing finishes.

## Stop safely

Stop the source before disabling the pipeline:

```java
NosmaiSDK.clearAllEffects();
NosmaiSDK.setFrameCallback(null);
NosmaiSDK.setExternalFrameMode(false);
```

## Verify it worked

Use a visible effect and confirm output dimensions, orientation, timestamp
order, and color on a physical arm64 device. Track dropped or late frames in
the host consumer.

## Common errors

| Symptom | Cause | Fix |
| --- | --- | --- |
| Processing returns `null` or `false` | Buffer type, strides, dimensions, or pipeline state are invalid | Use direct buffers and reinitialize after an input-size change |
| Colors or orientation are wrong | Plane order, stride, rotation, or mirror metadata is incorrect | Validate the source contract before submitting the frame |

## Next steps

- [Understand off-screen rendering](/docs/effects/concepts/off-screen-rendering/)
- [Integrate Agora on Android](/docs/effects/integrations/agora/android/)

## References

- [Off-screen rendering](/docs/effects/concepts/off-screen-rendering/)
- [Android platform guide](/docs/effects/platforms/android/)
- [Limits and performance](/docs/effects/limits-and-performance/)
