SkillAgentSearch skills...

Ndk

Idiomatic Go bindings for 34 Android NDK modules — Camera2, AAudio, OpenGL ES, Vulkan, sensors, MediaCodec, and more.

Install / Use

/learn @AndroidGoLab/Ndk

README

ndk

Go Reference Go Report Card License: MIT Go Version Ask AI

Idiomatic Go bindings for the Android NDK, auto-generated from C headers to ensure full coverage and easy maintenance.

Android Interfaces for Go

This project is part of a family of three Go libraries that cover the major Android interface surfaces. Each wraps a different layer of the Android platform:

graph TD
    subgraph "Go application"
        GO["Go code"]
    end

    subgraph "Interface libraries"
        NDK["<b>ndk</b><br/>C API bindings via cgo"]
        JNI["<b>jni</b><br/>Java API bindings via JNI+cgo"]
        AIDL["<b>binder</b><br/>Binder IPC, pure Go"]
    end

    subgraph "Android platform"
        CAPI["NDK C libraries<br/>(libcamera2ndk, libaaudio,<br/>libEGL, libvulkan, ...)"]
        JAVA["Java SDK<br/>(android.bluetooth,<br/>android.location, ...)"]
        BINDER["/dev/binder<br/>kernel driver"]
        SYSSVCS["System services<br/>(ActivityManager,<br/>PowerManager, ...)"]
    end

    GO --> NDK
    GO --> JNI
    GO --> AIDL

    NDK -- "cgo / #include" --> CAPI
    JNI -- "cgo / JNIEnv*" --> JAVA
    AIDL -- "ioctl syscalls" --> BINDER
    BINDER --> SYSSVCS
    JAVA -. "internally uses" .-> BINDER
    CAPI -. "some use" .-> BINDER

| Library | Interface | Requires | Best for | | ------------------------------------------------------------- | ---------------------------- | ------------------- | --------------------------------------------------------------------------------------------------- | | ndk (this project) | Android NDK C APIs | cgo + NDK toolchain | High-performance hardware access: camera, audio, sensors, OpenGL/Vulkan, media codecs | | jni | Java Android SDK via JNI | cgo + JNI + JVM/ART | Java-only APIs with no NDK equivalent: Bluetooth, WiFi, NFC, location, telephony, content providers | | binder | Binder IPC (system services) | pure Go (no cgo) | Direct system service calls without Java: works on non-Android Linux with binder, minimal footprint |

When to use which

  • Start with ndk when the NDK provides a C API for what you need (camera, audio, sensors, EGL/Vulkan, media codecs). These are the lowest-latency, lowest-overhead bindings since they go straight from Go to the C library via cgo.

  • Use jni when you need a Java Android SDK API that the NDK does not expose. Examples: Bluetooth discovery, WiFi P2P, NFC tag reading, location services, telephony, content providers, notifications. JNI is also the right choice when you need to interact with Java components (Activities, Services, BroadcastReceivers) or when you need the gRPC remote-access layer.

  • Use binder when you want pure-Go access to Android system services without any cgo dependency. This is ideal for lightweight tools, CLI programs, or scenarios where you want to talk to the binder driver from a non-Android Linux system. AIDL covers the same system services that Java SDK wraps (ActivityManager, PowerManager, etc.) but at the wire-protocol level.

  • Combine them when your application needs multiple layers. For example, a streaming app might use ndk for camera capture and audio encoding, jni for Bluetooth controller discovery, and binder for querying battery status from a companion daemon.

How they relate to each other

All three libraries talk to the same Android system services, but through different paths:

  • The NDK C APIs are provided by Google as stable C interfaces to Android platform features. Some (camera, sensors, audio) internally use binder IPC to talk to system services; others (EGL, Vulkan, OpenGL) talk directly to kernel drivers. The ndk library wraps these C APIs via cgo.
  • The Java SDK uses binder IPC internally for system service access (BluetoothManager, LocationManager, etc.), routing calls through the Android Runtime (ART/Dalvik). The jni library calls into these Java APIs via the JNI C interface and cgo.
  • The AIDL binder protocol is the underlying IPC mechanism that system-facing NDK and Java SDK APIs use. The binder library implements this protocol directly in pure Go, bypassing both C and Java layers entirely.

Requirements

  • Android NDK r28 (28.0.13004108) or later
  • API level 35 (Android 15) target

Idiomatic vs capi/ Packages

Always import the idiomatic top-level packages (github.com/AndroidGoLab/ndk/{module}) in your application code. These provide Go-friendly types with proper lifecycle management (Close(), defer), typed error handling, and method receivers.

The capi/ packages (github.com/AndroidGoLab/ndk/capi/{module}) are the raw CGo bindings generated in Stage 2 of the pipeline. They mirror the C API directly — C-style function names, unsafe.Pointer parameters, raw integer return codes. They are intended for power users who need access to NDK functions not yet wrapped by the idiomatic layer. All commonly used functions — including hwbuf.Allocate, buf.Lock, codec.DequeueInputBuffer, and codec.DequeueOutputBuffer — are available in the idiomatic layer.

import "github.com/AndroidGoLab/ndk/hwbuf"

// Allocate a hardware buffer using the idiomatic API
desc := hwbuf.Desc{
    Width: 1920, Height: 1080, Layers: 1,
    Format: uint32(hwbuf.R8g8b8a8Unorm),
    Usage:  uint64(hwbuf.CpuWriteOften | hwbuf.GpuSampledImage),
}
buf, err := hwbuf.Allocate(&desc)
if err != nil {
    log.Fatal(err)
}
defer buf.Close()

Examples

All types implement idempotent, nil-safe Close() error. Error types wrap NDK status codes and work with errors.Is.

<details> <summary>Audio playback (AAudio)</summary>
package main

import (
	"log"
	"unsafe"

	"github.com/AndroidGoLab/ndk/audio"
)

func main() {
	builder, err := audio.NewStreamBuilder()
	if err != nil {
		log.Fatal(err)
	}
	defer builder.Close()

	builder.
		SetDirection(audio.Output).
		SetSampleRate(44100).
		SetChannelCount(2).
		SetFormat(audio.PcmFloat).
		SetPerformanceMode(audio.LowLatency).
		SetSharingMode(audio.Shared)

	stream, err := builder.Open()
	if err != nil {
		log.Fatal(err)
	}
	defer stream.Close()

	log.Printf("opened: %d Hz, %d ch, burst=%d",
		stream.SampleRate(), stream.ChannelCount(), stream.FramesPerBurst())

	if err := stream.Start(); err != nil {
		log.Fatal(err)
	}
	defer stream.Stop()

	buf := make([]float32, int(stream.FramesPerBurst())*2)
	stream.Write(unsafe.Pointer(&buf[0]), stream.FramesPerBurst(), 1_000_000_000)
}
</details> <details> <summary>Camera discovery</summary>
package main

import (
	"log"

	"github.com/AndroidGoLab/ndk/camera"
)

func main() {
	mgr := camera.NewManager()
	defer mgr.Close()

	ids, err := mgr.CameraIdList()
	if err != nil {
		log.Fatal(err) // camera.ErrPermissionDenied if CAMERA not granted
	}

	for _, id := range ids {
		meta, _ := mgr.GetCameraCharacteristics(id)
		orientation := meta.I32At(uint32(camera.SensorOrientation), 0)
		log.Printf("camera %s: orientation=%d°", id, orientation)
	}
}
</details> <details> <summary>Sensor querying</summary>
package main

import (
	"fmt"

	"github.com/AndroidGoLab/ndk/sensor"
)

func main() {
	mgr := sensor.GetInstance()
	accel := mgr.DefaultSensor(sensor.Accelerometer)

	fmt.Printf("Sensor: %s (%s)\n", accel.Name(), accel.Vendor())
	fmt.Printf("Resolution: %g, min delay: %d µs\n",
		accel.Resolution(), accel.MinDelay())
}
</details> <details> <summary>Event loop (ALooper)</summary>
package main

import (
	"log"
	"runtime"
	"time"
	"unsafe"

	"github.com/AndroidGoLab/ndk/looper"
)

func main() {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	lp := looper.Prepare(int32(looper.ALOOPER_PREPARE_ALLOW_NON_CALLBACKS))
	defer func() { _ = lp.Close() }()

	lp.Acquire()
	go func() {
		time.Sleep(100 * time.Millisecond)
		lp.Wake()
	}()

	var fd, events int32
	var data unsafe.Pointer
	result := looper.LOOPER_POLL(looper.PollOnce(-1, &fd, &events, &data))

	switch result {
	case looper.ALOOPER_POLL_WAKE:
		log.Println("woke up")
	case looper.ALOOPER_POLL_TIMEOUT:
		log.Println("timed out")
	}
}
</details> <details> <summary>Camera preview (full pipeline)</summary>

A complete camera-to-screen example using NativeActivity, EGL, and OpenGL ES. See examples/camera/display/ for the full working application. Build it with make apk-displaycamera.

// Sketch of the camera pipeline (requires NativeActivity context)

mgr := camera.NewManager()
defer mgr.Close()

device, err := mgr.OpenCamera(cameraID, camera.DeviceStateCallbacks{
	OnDisconnected: func() { log.Println("disconnected") },
	OnError:        func(code int) { log.Printf("error: %d", code) },
})
defer device.Close()

request, _ := device.CreateCaptureRequest(camera.Preview)
defer request.Close()

target, _ := camera.NewOutputTarget(nativeWindow)
request.AddTarget(target)

container, _ := camera.NewSessionOutputContainer()
output, _ := camera.NewSessionOutput(nativeWindow)
container.Add(output)

session, _ := device.CreateCaptureSession(container,
	camera.SessionStateCallbacks{
		OnReady:  func() { log.Println("ready") },
		OnActive: func() { log.Println("active") },
	})
session.Set
View on GitHub
GitHub Stars6
CategoryDevelopment
Updated8d ago
Forks0

Languages

Go

Security Score

90/100

Audited on Mar 23, 2026

No findings