Nosmai / docs
Nosmai Effects Nosmai Moderation Nosmai Try-ons coming soon
Docs menu Android
docs / nosmai effects / platform guides / android

Android

Add Nosmai Effects to a native Android app: camera preview, beauty, color, filters, capture and recording.

Overview

The Android SDK provides public APIs for:

  • SDK initialization and license handling
  • real-time processed preview
  • front and back camera input
  • built-in beauty, makeup, and face shaping
  • local and cloud .nosmai filters
  • active-effect state
  • background effects
  • processed video recording
  • processed output for live streaming

The official Android repository contains releases and a complete sample application:

github.com/nosmai/camera-sdk-android

Requirements

RequirementValue
Minimum Android versionAPI 21
Supported device architecturearm64-v8a
Native page size16 KB compatible
Java compatibilityJava 11
Camera APICamera2
Test environmentPhysical Android device

The SDK does not run on an x86 or x86_64 emulator. Use a physical arm64 Android device for camera, face tracking, recording, and performance testing.

Install

1. Download the AAR

Download the current AAR from the Android SDK releases.

For SDK 3.0.1, download both nosmai-sdk-3.0.1.aar and SHA256SUMS, then verify the artifact:

shasum -a 256 -c SHA256SUMS

Place it in the application module:

app/
  libs/
    nosmai-sdk-3.0.1.aar

2. Add the dependency

android {
    compileSdk = 35

    defaultConfig {
        minSdk = 21

        ndk {
            abiFilters += "arm64-v8a"
        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
}

dependencies {
    implementation(files("libs/nosmai-sdk-3.0.1.aar"))
}

If the downloaded release uses a different filename, use that exact filename in the Gradle dependency.

3. Prevent duplicate native libraries

If another dependency includes the same shared C++ runtime, add:

android {
    packaging {
        jniLibs {
            pickFirsts += "lib/arm64-v8a/libc++_shared.so"
        }
    }
}

Only add a pickFirst rule when Gradle reports a duplicate native-library error. Do not add broad packaging exclusions.

Permissions

Add the required permissions:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />

    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <application
        android:name=".NosmaiApplication"
        ...>
    </application>
</manifest>
PermissionRequired when
CAMERAShowing a camera preview
INTERNETLicense verification and cloud filters
RECORD_AUDIORecording or streaming with microphone audio

Request camera and microphone permission at runtime. Do not request microphone permission if the application never records or streams audio.

Initialize

Initialize Nosmai once from the application class.

package com.example.cameraapp;

import android.app.Application;
import com.nosmai.effect.api.NosmaiSDK;

public final class NosmaiApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        NosmaiSDK.initialize(
            this,
            BuildConfig.NOSMAI_LICENSE_KEY
        );
    }
}

Register the class:

<application
    android:name=".NosmaiApplication"
    ...>
</application>

Check the state when needed:

if (!NosmaiSDK.isInitialized()) {
    // Do not open the camera experience yet.
}

Do not initialize from every activity, fragment, or composable. Keep initialization under one application-level owner.

Add the preview

Create a container:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/preview_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Create NosmaiPreviewView after camera permission is granted:

private NosmaiPreviewView previewView;

private void createNosmaiPreview() {
    FrameLayout container = findViewById(R.id.preview_container);

    previewView = new NosmaiPreviewView(this);
    container.addView(
        previewView,
        new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT
        )
    );

    NosmaiSDK.startProcessing(previewView);
}

startProcessing prepares the view for camera frames and effects. It does not grant camera permission.

Connect Camera2

The native Android SDK accepts Camera2 input from the application. The official sample includes a Camera2Helper that handles camera selection, frame size, sensor orientation, camera close, and background-thread ownership.

Use the helper from the official sample or provide equivalent Camera2 code in the host application.

The required application responsibilities are:

  1. create one Camera2 source
  2. connect it to NosmaiPreviewView
  3. tell the SDK whether the front or back camera is active
  4. provide the camera orientation
  5. start the camera after the Nosmai preview is ready
  6. stop the camera before removing the screen

The sample application uses the optimized direct camera connection when the device supports it. The public Nosmai setup is:

previewView.enableOesInput(true);

previewView.addOnOesReadyListener(surfaceTexture -> {
    cameraHelper.setInputMode(Camera2Helper.InputMode.OES);
    cameraHelper.setOesPreviewSurfaceTexture(surfaceTexture);

    cameraHelper.setPreviewSizeCallback((width, height) -> {
        int rotation = calculateFrameRotation();
        previewView.setOesInputFrameInfo(width, height, rotation);
    });

    previewView.setCameraOrientation(
        cameraHelper.isFrontCamera(),
        cameraHelper.getSensorOrientation()
    );

    NosmaiSDK.setCameraFacing(cameraHelper.isFrontCamera());
    cameraHelper.startCamera();
});

Camera2Helper and calculateFrameRotation in this example come from the official sample application, not from the public SDK AAR.

Keep the sample's device fallback behavior. If direct input cannot start on a device, reconnect using the sample's normal Camera2 frame input instead of leaving the preview black.

Front-camera mirroring

Tell the SDK which camera is active:

NosmaiSDK.setCameraFacing(isFrontCamera);

Set the preview mirror preference:

NosmaiSDK.setMirrorX(isFrontCamera);

Back-camera preview should normally not be mirrored.

Do not mirror the Camera2 frame and the Nosmai preview at the same time. Applying the same horizontal flip twice can make the preview look unchanged while face effects use the wrong direction.

Switch camera

Stop or reconfigure the current Camera2 session before opening the other camera.

private boolean isSwitchingCamera;

private void switchCamera() {
    if (isSwitchingCamera) {
        return;
    }

    isSwitchingCamera = true;

    cameraHelper.stopCamera();
    isFrontCamera = !isFrontCamera;

    setupCameraHelper(isFrontCamera);

    NosmaiSDK.setCameraFacing(isFrontCamera);
    NosmaiSDK.setMirrorX(isFrontCamera);

    cameraHelper.startCamera();
    isSwitchingCamera = false;
}

Prevent rapid repeated taps while a switch is already running.

Apply built-in beauty

Use NosmaiBeauty for built-in controls.

import com.nosmai.effect.api.NosmaiBeauty;

NosmaiBeauty.applySkinSmoothing(0.4f);
NosmaiBeauty.applySkinWhitening(0.2f);
NosmaiBeauty.applySharpen(0.15f);
NosmaiBeauty.applyTeethWhitening(0.3f);

Skin smoothing, whitening, sharpening, and teeth whitening use values from 0.0 to 1.0.

Remove built-in beauty and makeup:

NosmaiBeauty.clearAllBeautyFilters();

Apply makeup

NosmaiBeauty.applyLipstickStyle(
    NosmaiBeauty.LIPSTICK_MATTE,
    0.62f,
    0.12f,
    0.18f
);

NosmaiBeauty.setMakeupIntensity(
    NosmaiBeauty.MAKEUP_LIPSTICK,
    0.65f
);

Remove one makeup category:

NosmaiBeauty.removeMakeup(NosmaiBeauty.MAKEUP_LIPSTICK);

Remove all built-in makeup:

NosmaiBeauty.clearBuiltInMakeup();

Apply a local .nosmai package

Use applyEffect for filter, effect, beauty_effect, and background packages:

NosmaiEffects.applyEffect(
    filterPath,
    new NosmaiEffects.EffectCallback() {
        @Override
        public void onSuccess() {
            // Update selected state after success.
        }

        @Override
        public void onError(String errorMessage) {
            // Present or record the error.
        }
    }
);

Applying a package is asynchronous. Do not mark a filter as selected before onSuccess.

Remove an external package:

NosmaiEffects.removeEffect(filterInfo);

Use the typed NosmaiFilterInfo when available so the SDK can remove the correct category.

Adjust effect parameters

Some .nosmai packages expose adjustable numeric or text values.

Set a numeric value:

boolean updated = NosmaiEffects.setEffectParameter(
    "intensity",
    0.7f
);

Read a numeric value:

float intensity =
    NosmaiEffects.getEffectParameterValue("intensity");

Set a text value:

boolean updated = NosmaiEffects.setEffectParameter(
    "headerText",
    "Hello"
);

Use the exact parameter names and types authored in the selected package. Unknown names and incompatible value types return false.

List local filters

Production filters use a .nosmai file, external manifest, and preview image.

List<NosmaiFilterInfo> all = NosmaiEffects.getFilters();

List<NosmaiFilterInfo> effects = NosmaiEffects.getFilters(
    NosmaiFilterInfo.Type.EFFECT
);

Development-only loose packages can be scanned asynchronously:

NosmaiEffects.getDebugFilters(
    NosmaiFilterInfo.Type.FILTER,
    (filters, error) -> {
        if (error != null) {
            return;
        }

        // Show development filters.
    }
);

Do not use debug discovery as the production catalog.

Observe active state

Listen for state changes instead of guessing which item remained active:

NosmaiEffects.PipelineStateListener listener = state -> {
    NosmaiFilterInfo activeFilter = NosmaiEffects.getActiveFilterInfo();
    NosmaiFilterInfo activeEffect = NosmaiEffects.getActiveEffectInfo();

    // Update the application's selected buttons.
};

NosmaiEffects.addPipelineStateListener(listener);

Remove the listener when its screen or controller is destroyed:

NosmaiEffects.removePipelineStateListener(listener);

Background effects

Apply a manual background configuration through NosmaiEffects:

NosmaiBackgroundSegmentationConfig config =
    new NosmaiBackgroundSegmentationConfig();

config.mode = NosmaiBackgroundSegmentationConfig.Mode.BLUR;
config.blurStrength = 55.0f;

NosmaiEffects.setBackgroundSegmentation(config);

Clear it:

NosmaiEffects.clearBackgroundSegmentation();

Use the exact constructors and mode names from the installed SDK version. Background configuration classes can gain new modes between releases.

Cloud filters

Use NosmaiCloud.cachedList() to paint an existing catalog immediately. Run NosmaiCloud.fetch(...) away from the main thread so opening a cloud-filter sheet never blocks the camera interface.

List<NosmaiCloud.Item> cached = NosmaiCloud.cachedList();

NosmaiCloud.FilterQuery query = new NosmaiCloud.FilterQuery();
query.filterType = "effects";
query.page = 1;
query.limit = 20;
query.fetchAllPages = false;
query.cleanupRemoved = false;

cloudExecutor.execute(() -> {
    boolean success = NosmaiCloud.fetch(query);
    List<NosmaiCloud.Item> items = success
            ? NosmaiCloud.list()
            : Collections.emptyList();

    mainHandler.post(() -> {
        // Confirm this screen or request still owns the result.
        // Then render items or show Retry.
    });
});

Download the selected identifier and apply only the returned local path:

NosmaiCloud.download(
    item.id,
    (filterId, success, localPath, error) -> {
        if (success && localPath != null && !localPath.isEmpty()) {
            NosmaiEffects.applyEffect(
                localPath,
                new NosmaiEffects.EffectCallback() {
                    @Override
                    public void onSuccess() {
                        // Confirm selection from the pipeline listener.
                    }

                    @Override
                    public void onError(String message) {
                        // Keep the previous selection and offer Retry.
                    }
                }
            );
        }
    }
);

Deduplicate repeated taps by cloud identifier and ignore UI updates after the owning sheet closes. See Cloud filters for category values, pagination, progress callbacks, cache behavior, and complete edge-case handling.

Record processed video

Create an output path in application storage, then start recording:

File output = new File(
    getExternalFilesDir(null),
    "nosmai_" + System.currentTimeMillis() + ".mp4"
);

NosmaiSDK.startRecording(
    previewView,
    output.getAbsolutePath(),
    new NosmaiSDK.RecordingCallback() {
        @Override
        public void onStarted(boolean success, String error) {
            if (!success) {
                // Recording did not start.
            }
        }
    }
);

Stop and wait for finalization:

NosmaiSDK.stopRecording(new NosmaiSDK.RecordingCallback() {
    @Override
    public void onCompleted(
        String outputPath,
        boolean success,
        String error
    ) {
        if (success) {
            // The MP4 is finalized at outputPath.
        }
    }
});

Do not leave the camera screen while recording is still being finalized.

Native Android photo capture is owned by the application's camera implementation in the current SDK. Flutter exposes a higher-level capturePhoto method through its platform view.

Lifecycle

Use the following ownership:

EventAction
Camera screen opensCreate preview, start Nosmai processing, then start Camera2
App goes to backgroundStop Camera2 and pause the preview view
App returnsResume the preview view and reconnect Camera2
Camera screen closesStop Camera2, stop processing, remove the preview
Application truly finishes SDK useCall full cleanup

Example:

@Override
protected void onResume() {
    super.onResume();

    if (previewView != null) {
        previewView.onResume();
    }

    if (hasCameraPermission()) {
        cameraHelper.startCamera();
    }
}

@Override
protected void onPause() {
    if (cameraHelper != null) {
        cameraHelper.stopCamera();
    }

    if (previewView != null) {
        previewView.onPause();
    }

    super.onPause();
}

@Override
protected void onDestroy() {
    if (cameraHelper != null) {
        cameraHelper.stopCamera();
    }

    NosmaiSDK.stopProcessing();

    if (previewView != null && previewView.getParent() != null) {
        ((ViewGroup) previewView.getParent()).removeView(previewView);
    }

    previewView = null;
    super.onDestroy();
}

Do not call NosmaiSDK.cleanup() every time a camera activity briefly pauses. Full cleanup requires a later initialization before the SDK can be used again.

Release configuration

The AAR includes consumer rules for its public SDK classes. If release-only failures occur, confirm:

  • the current AAR is used
  • consumer ProGuard rules are merged
  • JNI methods have not been renamed or removed
  • the final APK or AAB contains lib/arm64-v8a/libnosmai.so
  • all protected SDK assets are present

Distribute an Android App Bundle when publishing to Google Play. Devices without the supported architecture will not receive an incompatible split.

Common problems

ProblemWhat to check
UnsatisfiedLinkErrorAAR version, Java API version, native library packaging, and ABI
Black previewRuntime permission, camera source, preview readiness, and activity lifecycle
Preview stays black after camera switchStop the old Camera2 session completely before opening the new one
Camera indicator stays onStop Camera2 when leaving the screen
Effect appears reversedCamera-facing value and single mirror owner
Filter apply crashes in release onlyConsumer ProGuard rules and matching AAR classes/native library
Recording does not startPreview readiness, output path, storage, and current recording state
Effect selection UI is wrongUpdate it from apply callbacks and active-state listener
Emulator cannot installUse an arm64 physical device

Production checklist

  • initialize once at application level
  • request camera permission before starting Camera2
  • use one camera source per preview
  • stop the old camera before switching
  • stop Camera2 before removing the screen
  • apply external packages asynchronously
  • remove active-state listeners
  • finalize recording before navigation
  • test front and back camera orientation
  • test background and foreground transitions
  • test repeated camera-screen navigation
  • test rapid filter switching
  • test a low-end and high-end arm64 device
  • test a long recording or streaming session
Nosmai

We make advanced camera and AI technology accessible to every developer. By packaging hard problems into simple

developers
legal
newsletter

Product updates and release notes. No spam.

© 2026 nosmai, inc · all rights reserved