ndk

module
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 16, 2026 License: MIT

README

ndk

Go Reference Go Report Card Go Version

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

Examples

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

Audio playback (AAudio)
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)
}
Camera discovery
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)
	}
}
Sensor querying
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())
}
Event loop (ALooper)
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")
	}
}
Camera preview (full pipeline)

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.SetRepeatingRequest(request)
Asset loading
package main

import (
	"fmt"
	"io"
	"unsafe"

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

func main() {
	// mgr obtained from activity.AssetManager in a real NativeActivity app.
	// This example documents the API pattern.
	var mgr *asset.Manager // = activity.AssetManager(nativeActivity)

	a := mgr.Open("textures/wood.png", asset.Streaming)
	defer a.Close()

	size := a.Length()
	buf := make([]byte, size)
	_, _ = io.ReadFull(unsafe.NewReader(a), buf)
	fmt.Printf("read %d bytes\n", len(buf))
}
How to record from the microphone
package main

import (
	"log"
	"math"
	"time"
	"unsafe"

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

func main() {
	builder, err := audio.NewStreamBuilder()
	if err != nil {
		log.Fatalf("create stream builder: %v", err)
	}
	defer builder.Close()

	builder.
		SetDirection(audio.Input).
		SetSampleRate(48000).
		SetChannelCount(1).
		SetFormat(audio.PcmI16).
		SetPerformanceMode(audio.LowLatency).
		SetSharingMode(audio.Shared)

	stream, err := builder.Open()
	if err != nil {
		log.Fatalf("open stream: %v", err)
	}
	defer func() {
		if err := stream.Close(); err != nil {
			log.Printf("close stream: %v", err)
		}
	}()

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

	if err := stream.Start(); err != nil {
		log.Fatalf("start stream: %v", err)
	}

	// Read approximately 1 second of audio.
	totalFrames := rate
	buf := make([]int16, 1024)
	bufBytes := unsafe.Slice((*byte)(unsafe.Pointer(&buf[0])), len(buf)*int(unsafe.Sizeof(buf[0])))
	var captured []int16

	for int32(len(captured)) < totalFrames {
		framesToRead := int32(len(buf))
		if remaining := totalFrames - int32(len(captured)); remaining < framesToRead {
			framesToRead = remaining
		}
		n, err := stream.Read(bufBytes, framesToRead, time.Second)
		if err != nil {
			log.Fatalf("read: %v", err)
		}
		captured = append(captured, buf[:n]...)
	}

	if err := stream.Stop(); err != nil {
		log.Fatalf("stop stream: %v", err)
	}

	// Compute peak amplitude.
	var peak int16
	for _, s := range captured {
		if s < 0 {
			s = -s
		}
		if s > peak {
			peak = s
		}
	}

	log.Printf("captured %d frames", len(captured))
	log.Printf("peak amplitude: %d (%.1f dBFS)", peak, 20*math.Log10(float64(peak)/32767.0))
	log.Println("recording example finished")
}
How to take a picture from the camera
package main

import (
	"log"
	"os"
	"time"
	"unsafe"

	"github.com/AndroidGoLab/ndk/camera"
	capimedia "github.com/AndroidGoLab/ndk/capi/media"
	"github.com/AndroidGoLab/ndk/media"
)

func main() {
	// 1. Create an ImageReader (640x480 JPEG, up to 2 images).
	//    The idiomatic media.NewImageReader uses an output parameter.
	var reader *media.ImageReader
	if status := media.NewImageReader(640, 480, capimedia.AIMAGE_FORMAT_JPEG, 2, &reader); status != 0 {
		log.Fatalf("create image reader: status %d", status)
	}
	defer reader.Close()

	// 2. Get the ANativeWindow from the ImageReader.
	//    The idiomatic Window() method has a broken signature (no output),
	//    so we call the capi function directly.
	readerPtr := (*capimedia.AImageReader)(reader.Pointer())
	var capiWindow *capimedia.ANativeWindow
	if status := capimedia.AImageReader_getWindow(readerPtr, &capiWindow); status != 0 {
		log.Fatalf("get window: status %d", status)
	}
	// Convert capi/media.ANativeWindow to capi/camera.ANativeWindow via unsafe.
	camWindow := (*camera.ANativeWindow)(unsafe.Pointer(capiWindow))

	// 3. Create camera Manager and list cameras.
	mgr := camera.NewManager()
	defer mgr.Close()

	ids, err := mgr.CameraIDList()
	if err != nil {
		log.Fatalf("list cameras: %v", err)
	}
	if len(ids) == 0 {
		log.Fatal("no cameras available")
	}
	log.Printf("cameras: %v (using %s)", ids, ids[0])

	// 4. Open the first camera.
	dev, err := mgr.OpenCamera(ids[0], camera.DeviceStateCallbacks{
		OnDisconnected: func() { log.Println("camera disconnected") },
		OnError:        func(code int) { log.Printf("camera error: %d", code) },
	})
	if err != nil {
		log.Fatalf("open camera: %v", err)
	}
	defer dev.Close()

	// 5. Create OutputTarget and SessionOutput from the window.
	target, err := camera.NewOutputTarget(camWindow)
	if err != nil {
		log.Fatalf("create output target: %v", err)
	}
	defer target.Close()

	sessOutput, err := camera.NewSessionOutput(camWindow)
	if err != nil {
		log.Fatalf("create session output: %v", err)
	}
	defer sessOutput.Close()

	container, err := camera.NewSessionOutputContainer()
	if err != nil {
		log.Fatalf("create session output container: %v", err)
	}
	defer container.Close()

	if err := container.Add(sessOutput); err != nil {
		log.Fatalf("add session output: %v", err)
	}

	// 6. Create a CaptureRequest (StillCapture template) and add the target.
	req, err := dev.CreateCaptureRequest(camera.StillCapture)
	if err != nil {
		log.Fatalf("create capture request: %v", err)
	}
	defer req.Close()
	req.AddTarget(target)

	// 7. Create a CaptureSession and set a repeating request.
	ready := make(chan struct{}, 1)
	session, err := dev.CreateCaptureSession(container, camera.SessionStateCallbacks{
		OnReady:  func() { select { case ready <- struct{}{}: default: } },
		OnActive: func() { log.Println("session active") },
		OnClosed: func() { log.Println("session closed") },
	})
	if err != nil {
		log.Fatalf("create capture session: %v", err)
	}
	defer session.Close()

	if err := session.SetRepeatingRequest(req); err != nil {
		log.Fatalf("set repeating request: %v", err)
	}

	// Wait for at least one frame to arrive.
	select {
	case <-ready:
	case <-time.After(5 * time.Second):
		log.Println("warning: timed out waiting for session ready")
	}
	time.Sleep(500 * time.Millisecond)

	// 8. Acquire an image from the ImageReader and save it.
	var capiImage *capimedia.AImage
	if status := capimedia.AImageReader_acquireLatestImage(readerPtr, &capiImage); status != 0 {
		log.Fatalf("acquire image: status %d", status)
	}
	img := media.NewImageFromPointer(unsafe.Pointer(capiImage))
	defer img.Close()

	var numPlanes int32
	if err := img.NumberOfPlanes(&numPlanes); err != nil {
		log.Fatalf("get number of planes: %v", err)
	}

	// For JPEG there is exactly one plane; get its data.
	var dataPtr *uint8
	var dataLen int32
	if status := capimedia.AImage_getPlaneData(
		(*capimedia.AImage)(img.Pointer()), 0, &dataPtr, &dataLen,
	); status != 0 {
		log.Fatalf("get plane data: status %d", status)
	}

	data := unsafe.Slice(dataPtr, dataLen)
	if err := os.WriteFile("/sdcard/capture.jpg", data, 0644); err != nil {
		log.Fatalf("write file: %v", err)
	}

	if err := session.StopRepeating(); err != nil {
		log.Printf("stop repeating: %v", err)
	}

	log.Printf("saved %d bytes to /sdcard/capture.jpg", dataLen)
}
How to list available sensors
package main

import (
	"fmt"

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

// printSensor queries and prints sensor properties. It returns false
// if the sensor's underlying C pointer is NULL (the device lacks this
// sensor type), recovering from the resulting panic.
func printSensor(mgr *sensor.Manager, label string, sensorType sensor.Type) (ok bool) {
	defer func() {
		if r := recover(); r != nil {
			ok = false
		}
	}()

	s := mgr.DefaultSensor(sensorType)

	// Trigger a method call; if the internal pointer is NULL the NDK
	// dereferences a null pointer and Go's signal handler turns it
	// into a panic that we recover above.
	name := s.Name()
	vendor := s.Vendor()
	if name == "" || vendor == "" {
		return false
	}

	fmt.Printf("  %s:\n", label)
	fmt.Printf("    Name:       %s\n", name)
	fmt.Printf("    Vendor:     %s\n", vendor)
	fmt.Printf("    Type:       %s (%d)\n", sensorType, int32(sensorType))
	fmt.Printf("    Resolution: %g\n", s.Resolution())
	fmt.Printf("    Min delay:  %d us\n", s.MinDelay())
	fmt.Println()
	return true
}

func main() {
	mgr := sensor.GetInstance()

	type sensorInfo struct {
		label      string
		sensorType sensor.Type
	}

	sensors := []sensorInfo{
		{"Accelerometer", sensor.Accelerometer},
		{"Gyroscope", sensor.Gyroscope},
		{"Light", sensor.Light},
		{"Proximity", sensor.Proximity},
		{"Magnetic Field", sensor.MagneticField},
	}

	fmt.Println("Default sensors on this device:")
	fmt.Println()

	found := 0
	for _, info := range sensors {
		if printSensor(mgr, info.label, info.sensorType) {
			found++
		} else {
			fmt.Printf("  %-16s  not available\n", info.label+":")
		}
	}

	if found == 0 {
		fmt.Println("  No default sensors found on this device.")
	}
}
How to check device thermal status
package main

import (
	"fmt"

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

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

	status := mgr.CurrentStatus()
	fmt.Printf("Thermal status: %s (%d)\n", status, int32(status))

	switch status {
	case thermal.StatusNone:
		fmt.Println("Device is cool.")
	case thermal.StatusLight, thermal.StatusModerate:
		fmt.Println("Device is warm; consider reducing workload.")
	case thermal.StatusSevere, thermal.StatusCritical:
		fmt.Println("Device is hot; throttling likely.")
	case thermal.StatusEmergency, thermal.StatusShutdown:
		fmt.Println("Device is critically hot; shutdown imminent.")
	default:
		fmt.Println("Unable to determine thermal status.")
	}
}
How to query GPU capabilities
package main

import (
	"fmt"
	"log"
	"unsafe"

	"github.com/AndroidGoLab/ndk/egl"
	"github.com/AndroidGoLab/ndk/gles2"
)

// goString converts a *gles2.GLubyte (C string) to a Go string.
func goString(p *gles2.GLubyte) string {
	if p == nil {
		return "<nil>"
	}
	// Walk the null-terminated byte sequence.
	var buf []byte
	for ptr := (*byte)(unsafe.Pointer(p)); *ptr != 0; ptr = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + 1)) {
		buf = append(buf, *ptr)
	}
	return string(buf)
}

func main() {
	// 1. Get the default EGL display and initialize it.
	dpy := egl.GetDisplay(egl.EGLNativeDisplayType(0))
	if dpy == nil {
		log.Fatal("eglGetDisplay failed")
	}

	var major, minor egl.Int
	if egl.Initialize(dpy, &major, &minor) == egl.False {
		log.Fatalf("eglInitialize failed: 0x%x", egl.GetError())
	}
	defer egl.Terminate(dpy)

	fmt.Printf("EGL %d.%d\n", major, minor)
	fmt.Printf("  Vendor:     %s\n", egl.QueryString(dpy, egl.EGL_VENDOR))
	fmt.Printf("  Version:    %s\n", egl.QueryString(dpy, egl.EGL_VERSION))
	fmt.Printf("  Client APIs: %s\n", egl.QueryString(dpy, egl.EGL_CLIENT_APIS))
	fmt.Printf("  Extensions: %s\n", egl.QueryString(dpy, egl.EGL_EXTENSIONS))

	// 2. Choose a config with ES2 support and pbuffer surface type.
	attribs := []egl.Int{
		egl.RenderableType, egl.OpenglEs2Bit,
		egl.SurfaceType, egl.PbufferBit,
		egl.RedSize, 8,
		egl.GreenSize, 8,
		egl.BlueSize, 8,
		egl.None,
	}
	var cfg egl.EGLConfig
	var numCfg egl.Int
	if egl.ChooseConfig(dpy, &attribs[0], &cfg, 1, &numCfg) == egl.False || numCfg == 0 {
		log.Fatal("eglChooseConfig failed")
	}

	// 3. Create a 1x1 pbuffer surface and an ES2 context.
	pbufAttribs := []egl.Int{egl.Width, 1, egl.Height, 1, egl.None}
	surface := egl.CreatePbufferSurface(dpy, cfg, &pbufAttribs[0])

	ctxAttribs := []egl.Int{egl.ContextClientVersion, 2, egl.None}
	ctx := egl.CreateContext(dpy, cfg, nil, &ctxAttribs[0])
	if ctx == nil {
		log.Fatal("eglCreateContext failed")
	}
	defer egl.DestroyContext(dpy, ctx)
	defer egl.DestroySurface(dpy, surface)

	egl.MakeCurrent(dpy, surface, surface, ctx)

	// 4. Query OpenGL ES strings.
	fmt.Println()
	fmt.Printf("GL Vendor:     %s\n", goString(gles2.GetString(gles2.GL_VENDOR)))
	fmt.Printf("GL Renderer:   %s\n", goString(gles2.GetString(gles2.GL_RENDERER)))
	fmt.Printf("GL Version:    %s\n", goString(gles2.GetString(gles2.GL_VERSION)))
	fmt.Printf("GL Extensions: %s\n", goString(gles2.GetString(gles2.GL_EXTENSIONS)))

	egl.MakeCurrent(dpy, nil, nil, nil)
}
How to probe available media codecs
package main

import (
	"fmt"

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

func main() {
	codecs := []struct {
		mime string
		desc string
	}{
		{"video/avc", "H.264 / AVC"},
		{"video/hevc", "H.265 / HEVC"},
		{"video/x-vnd.on2.vp8", "VP8"},
		{"video/x-vnd.on2.vp9", "VP9"},
		{"video/av01", "AV1"},
		{"audio/mp4a-latm", "AAC"},
		{"audio/opus", "Opus"},
		{"audio/flac", "FLAC"},
	}

	fmt.Printf("%-28s %-10s %-10s\n", "MIME Type", "Encoder", "Decoder")
	fmt.Printf("%-28s %-10s %-10s\n", "---", "---", "---")

	for _, c := range codecs {
		encOK := "no"
		enc := media.NewEncoder(c.mime)
		if enc != nil && enc.Pointer() != nil {
			encOK = "yes"
			enc.Close()
		}

		decOK := "no"
		dec := media.NewDecoder(c.mime)
		if dec != nil && dec.Pointer() != nil {
			decOK = "yes"
			dec.Close()
		}

		fmt.Printf("%-28s %-10s %-10s  (%s)\n", c.mime, encOK, decOK, c.desc)
	}
}
How to read device configuration
package main

import (
	"fmt"

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

func main() {
	cfg := config.NewConfig()
	defer cfg.Close()

	fmt.Println("Device configuration:")
	fmt.Printf("  Density:         %d dpi\n", cfg.Density())
	fmt.Printf("  Orientation:     %d\n", cfg.Orientation())
	fmt.Printf("  Screen size:     %d\n", cfg.ScreenSize())
	fmt.Printf("  Screen width:    %d dp\n", cfg.ScreenWidthDp())
	fmt.Printf("  Screen height:   %d dp\n", cfg.ScreenHeightDp())
	fmt.Printf("  SDK version:     %d\n", cfg.SdkVersion())

	switch config.Orientation(cfg.Orientation()) {
	case config.OrientationPort:
		fmt.Println("  (portrait)")
	case config.OrientationLand:
		fmt.Println("  (landscape)")
	case config.OrientationSquare:
		fmt.Println("  (square)")
	default:
		fmt.Println("  (any/unknown)")
	}
}
How to decode an image file
package main

import (
	"fmt"
	"log"
	"syscall"
	"unsafe"

	capidec "github.com/AndroidGoLab/ndk/capi/imagedecoder"
	"github.com/AndroidGoLab/ndk/image"
)

func main() {
	// 1. Open the image file via POSIX fd.
	fd, err := syscall.Open("/sdcard/photo.jpg", syscall.O_RDONLY, 0)
	if err != nil {
		log.Fatalf("open file: %v", err)
	}
	defer syscall.Close(fd)

	// 2. Create a Decoder from the fd.
	//    The factory function is not yet in the high-level image package,
	//    so we call the capi function and wrap the result.
	var decPtr *capidec.AImageDecoder
	if rc := capidec.AImageDecoder_createFromFd(int32(fd), &decPtr); rc != 0 {
		log.Fatalf("create decoder: error %d", rc)
	}
	decoder := image.NewDecoderFromPointer(unsafe.Pointer(decPtr))
	defer decoder.Close()

	// 3. Query image dimensions from the header.
	headerPtr := capidec.AImageDecoder_getHeaderInfo(decPtr)
	width := capidec.AImageDecoderHeaderInfo_getWidth(headerPtr)
	height := capidec.AImageDecoderHeaderInfo_getHeight(headerPtr)
	fmt.Printf("Image: %d x %d\n", width, height)

	// 4. Query the stride and allocate the pixel buffer.
	stride := decoder.MinimumStride()
	bufSize := stride * uint64(height)
	pixels := make([]byte, bufSize)
	fmt.Printf("Stride: %d bytes, buffer: %d bytes\n", stride, bufSize)

	// 5. Decode the image into the buffer.
	if err := decoder.Decode(unsafe.Pointer(&pixels[0]), stride, bufSize); err != nil {
		log.Fatalf("decode: %v", err)
	}

	fmt.Printf("Decoded %d bytes of RGBA pixel data.\n", bufSize)
}

For more examples, see examples/.

ndkcli

ndkcli is a CLI tool that exposes the full Android NDK surface from the command line. Supports 273 commands across 33 modules — query cameras, record audio, probe GPU capabilities, check sensors, and more.

Install

Option A: Download pre-built binary from GitHub Releases:

# Download for your device architecture
wget https://github.com/AndroidGoLab/ndk/releases/latest/download/ndkcli-android-arm64
adb push ndkcli-android-arm64 /data/local/tmp/ndkcli
adb shell chmod 755 /data/local/tmp/ndkcli

Option B: Build from source (requires Android NDK):

make ndkcli                                # → build/ndkcli (arm64)
adb push build/ndkcli /data/local/tmp/
adb shell chmod 755 /data/local/tmp/ndkcli
Quick start
# List cameras
adb shell /data/local/tmp/ndkcli camera list-details

# Record 5 seconds of audio
adb shell /data/local/tmp/ndkcli audio record --output /data/local/tmp/rec.pcm --duration 5s

# Check thermal status
adb shell /data/local/tmp/ndkcli thermal manager current-status

# Query GPU
adb shell /data/local/tmp/ndkcli gles2 info

# List sensors
adb shell /data/local/tmp/ndkcli sensor read --type 1
List all commands
# From source (no Android needed):
make ndkcli-commands

# On device:
adb shell /data/local/tmp/ndkcli --help
adb shell /data/local/tmp/ndkcli camera --help
Examples
List available cameras and their characteristics
# List camera IDs
adb shell /data/local/tmp/ndkcli camera manager camera-id-list

# Show full details (lens facing, orientation, hardware level) for all cameras
adb shell /data/local/tmp/ndkcli camera list-details

# Query characteristics for a specific camera
adb shell /data/local/tmp/ndkcli camera manager get-camera-characteristics --camera-id 0
Capture raw frames from the camera
# Capture 10 frames from camera 0 at 640x480 in RGBA format, save to file
adb shell /data/local/tmp/ndkcli camera capture \
    --id 0 --width 640 --height 480 --format 1 --count 10 \
    --output /data/local/tmp/frames.raw
Record audio from the microphone
# Record 5 seconds of mono 44.1kHz PCM16 audio
adb shell /data/local/tmp/ndkcli audio record \
    --output /data/local/tmp/recording.pcm \
    --duration 5s --sample-rate 44100 --channels 1

# Record 10 seconds of stereo 48kHz audio
adb shell /data/local/tmp/ndkcli audio record \
    --output /data/local/tmp/stereo.pcm \
    --duration 10s --sample-rate 48000 --channels 2
Play back recorded audio
# Play a previously recorded PCM file
adb shell /data/local/tmp/ndkcli audio play \
    --input /data/local/tmp/recording.pcm \
    --sample-rate 44100 --channels 1
Query audio system capabilities
# Open a probe stream and print audio properties
adb shell /data/local/tmp/ndkcli audio stream-builder new
adb shell /data/local/tmp/ndkcli audio stream channel-count
adb shell /data/local/tmp/ndkcli audio stream sample-rate
adb shell /data/local/tmp/ndkcli audio stream frames-per-burst
List sensors and read sensor data
# List all available sensors (probes known types)
adb shell /data/local/tmp/ndkcli sensor read --type 1   # Accelerometer
adb shell /data/local/tmp/ndkcli sensor read --type 4   # Gyroscope
adb shell /data/local/tmp/ndkcli sensor read --type 5   # Light

# Query a specific sensor by type number
adb shell /data/local/tmp/ndkcli sensor manager default-sensor --value 1
adb shell /data/local/tmp/ndkcli sensor sensor name
adb shell /data/local/tmp/ndkcli sensor sensor vendor
adb shell /data/local/tmp/ndkcli sensor sensor resolution
Check thermal status
# One-shot thermal status
adb shell /data/local/tmp/ndkcli thermal manager current-status

# Monitor thermal status every 2 seconds for 30 seconds
adb shell /data/local/tmp/ndkcli thermal monitor --interval 2s --duration 30s
Query EGL and GPU capabilities
# EGL display information (vendor, version, extensions)
adb shell /data/local/tmp/ndkcli egl info

# List EGL configurations
adb shell /data/local/tmp/ndkcli egl configs

# OpenGL ES 2.0 info (creates pbuffer context, queries GL strings)
adb shell /data/local/tmp/ndkcli gles2 info

# OpenGL ES 3.0 info
adb shell /data/local/tmp/ndkcli gles3 info
Probe available media codecs
# Check which codecs are available (H.264, H.265, VP8/9, AV1, AAC, etc.)
adb shell /data/local/tmp/ndkcli media codecs

# Create specific encoder/decoder
adb shell /data/local/tmp/ndkcli media new-encoder --mime_type video/avc
adb shell /data/local/tmp/ndkcli media new-decoder --mime_type audio/mp4a-latm

# Probe a media file
adb shell /data/local/tmp/ndkcli media probe --file /sdcard/video.mp4
Read device configuration
# Show all configuration values (density, orientation, screen, SDK version)
adb shell /data/local/tmp/ndkcli config show

# Individual queries
adb shell /data/local/tmp/ndkcli config config density
adb shell /data/local/tmp/ndkcli config config sdk-version
adb shell /data/local/tmp/ndkcli config config screen-width-dp
adb shell /data/local/tmp/ndkcli config config orientation
Decode an image file
# Decode a JPEG/PNG and print dimensions, stride, format
adb shell /data/local/tmp/ndkcli image decode --file /sdcard/photo.jpg

# Decode with target size (downscale)
adb shell /data/local/tmp/ndkcli image decode --file /sdcard/photo.jpg --width 320 --height 240
Match system fonts
# Find a matching font by family name and weight
adb shell /data/local/tmp/ndkcli font match --family sans-serif --weight 400
adb shell /data/local/tmp/ndkcli font match --family serif --weight 700 --italic
Check permissions
# Check if a permission is granted for a PID/UID
adb shell /data/local/tmp/ndkcli permission check \
    --name android.permission.CAMERA --pid 1000 --uid 1000
Trace and logging
# Check if tracing is enabled
adb shell /data/local/tmp/ndkcli trace is-enabled

# Add a trace marker
adb shell /data/local/tmp/ndkcli trace begin-section --section-name "my_operation"
adb shell /data/local/tmp/ndkcli trace end-section

# Set a trace counter
adb shell /data/local/tmp/ndkcli trace set-counter --counter-name "frames" --counter-value 42

# Write to Android log
adb shell /data/local/tmp/ndkcli log write --tag myapp --text "hello from ndkcli" --prio 4
NNAPI (Neural Networks) probe
# Check if NNAPI is available
adb shell /data/local/tmp/ndkcli nnapi probe

# Create and inspect a model
adb shell /data/local/tmp/ndkcli nnapi model new
Storage and OBB
# Check OBB mount status
adb shell /data/local/tmp/ndkcli storage obb --file /sdcard/main.obb
adb shell /data/local/tmp/ndkcli storage manager is-obb-mounted --filename /sdcard/main.obb
Looper and window utilities
# Test looper functionality (prepare, wake, poll)
adb shell /data/local/tmp/ndkcli looper test

# Query window properties via ImageReader-backed window
adb shell /data/local/tmp/ndkcli window query

Supported Modules

NDK Module Go Package Import Path
Graphics & Rendering
egl egl github.com/AndroidGoLab/ndk/egl
gles2 gles2 github.com/AndroidGoLab/ndk/gles2
gles3 gles3 github.com/AndroidGoLab/ndk/gles3
vulkan vulkan github.com/AndroidGoLab/ndk/vulkan
surfacecontrol surfacecontrol github.com/AndroidGoLab/ndk/surfacecontrol
surfacetexture surfacetexture github.com/AndroidGoLab/ndk/surfacetexture
hwbuf hwbuf github.com/AndroidGoLab/ndk/hwbuf
window window github.com/AndroidGoLab/ndk/window
bitmap bitmap github.com/AndroidGoLab/ndk/bitmap
Camera & Imaging
camera camera github.com/AndroidGoLab/ndk/camera
image image github.com/AndroidGoLab/ndk/image
Audio & Media
audio audio github.com/AndroidGoLab/ndk/audio
media media github.com/AndroidGoLab/ndk/media
midi midi github.com/AndroidGoLab/ndk/midi
Sensors & Input
sensor sensor github.com/AndroidGoLab/ndk/sensor
input input github.com/AndroidGoLab/ndk/input
choreographer choreographer github.com/AndroidGoLab/ndk/choreographer
Activity & Lifecycle
activity activity github.com/AndroidGoLab/ndk/activity
config config github.com/AndroidGoLab/ndk/config
thermal thermal github.com/AndroidGoLab/ndk/thermal
hint hint github.com/AndroidGoLab/ndk/hint
permission permission github.com/AndroidGoLab/ndk/permission
Storage & Assets
asset asset github.com/AndroidGoLab/ndk/asset
storage storage github.com/AndroidGoLab/ndk/storage
System & IPC
binder binder github.com/AndroidGoLab/ndk/binder
persistablebundle persistablebundle github.com/AndroidGoLab/ndk/persistablebundle
looper looper github.com/AndroidGoLab/ndk/looper
log log github.com/AndroidGoLab/ndk/log
sharedmem sharedmem github.com/AndroidGoLab/ndk/sharedmem
sync sync github.com/AndroidGoLab/ndk/sync
net net github.com/AndroidGoLab/ndk/net
Machine Learning
nnapi nnapi github.com/AndroidGoLab/ndk/nnapi
Debugging & Fonts
trace trace github.com/AndroidGoLab/ndk/trace
font font github.com/AndroidGoLab/ndk/font

Architecture

The project converts Android NDK C headers into safe, idiomatic Go packages through three code generation stages. Each stage has a dedicated tool and its own set of input/output artifacts:

flowchart TD
    NDK["Android NDK C Headers"]
    MAN["capi/manifests/*.yaml"]
    SPEC["spec/generated/{module}.yaml"]
    CAPI["capi/{module}/*.go"]
    OVER["spec/overlays/{module}.yaml"]
    TMPL["templates/*.tmpl"]
    IDOM["{module}/*.go"]

    NDK -->|parsed by| S1
    MAN -->|configures| S1

    subgraph S1["Stage 1: specgen + c2ffi"]
        direction LR
        S1D["Extracts structured API spec"]
    end

    S1 --> SPEC

    SPEC -->|read by| S2
    MAN -->|configures| S2

    subgraph S2["Stage 2: capigen"]
        direction LR
        S2D["Generates raw CGo bindings"]
    end

    S2 --> CAPI

    SPEC --> S3
    OVER -->|semantic annotations| S3
    TMPL -->|code templates| S3

    subgraph S3["Stage 3: idiomgen"]
        direction LR
        S3D["Generates idiomatic Go packages"]
    end

    S3 --> IDOM

    style NDK fill:#e0e0e0,color:#000
    style MAN fill:#fff3cd,color:#000
    style OVER fill:#fff3cd,color:#000
    style TMPL fill:#fff3cd,color:#000
    style SPEC fill:#d4edda,color:#000
    style CAPI fill:#d4edda,color:#000
    style IDOM fill:#cce5ff,color:#000

Legend: Yellow = hand-written inputs, Green = generated intermediates, Blue = final output.

Stage 1: Spec Extraction (make specs)

Tool: tools/cmd/specgen + c2ffi (external)

Parses NDK C headers via c2ffi and extracts a structured YAML specification containing types, enums, functions, callbacks, and structs.

Input: capi/manifests/{module}.yaml + NDK sysroot headers

Output: spec/generated/{module}.yaml

The spec captures:

  • Types: opaque pointers, typedefs, pointer handles
  • Enums: constant groups with resolved values
  • Functions: signatures with parameter names and types
  • Callbacks: function pointer type signatures
  • Structs: field definitions

Example manifest (capi/manifests/looper.yaml):

GENERATOR:
  PackageName: looper
  Includes: ["android/looper.h"]
  FlagGroups:
    - { name: LDFLAGS, flags: [-landroid] }

Example output (spec/generated/looper.yaml, abridged):

module: looper
source_package: github.com/AndroidGoLab/ndk/capi/looper
types:
  ALooper:
    kind: opaque_ptr
    c_type: ALooper
    go_type: "*C.ALooper"
enums:
  Looper_event_t:
    - { name: ALOOPER_EVENT_INPUT, value: 1 }
    - { name: ALOOPER_EVENT_OUTPUT, value: 2 }
functions:
  ALooper_prepare:
    c_name: ALooper_prepare
    params: [{ name: opts, type: int32 }]
    returns: "*ALooper"
Stage 2: Raw CGo Bindings (make capi)

Tool: tools/cmd/capigen (in-repo)

Reads the generated spec YAML and manifest YAML, then produces raw CGo wrapper packages with type aliases, function wrappers, callback proxies, and enum constants.

Input: spec/generated/{module}.yaml + capi/manifests/{module}.yaml

Output: capi/{module}/ -- a package with:

  • doc.go -- package declaration and doc comment
  • types.go -- Go type aliases (type ALooper C.ALooper)
  • const.go -- Enum constants
  • {module}.go -- Go functions that call through CGo
  • cgo_helpers.h / cgo_helpers.go -- Callback proxy declarations and implementations
Stage 3: Idiomatic Go Generation (make idiomatic)

Tool: tools/cmd/idiomgen (in-repo)

Merges the generated spec with a hand-written overlay and renders Go templates to produce the final user-facing packages.

Input:

  • spec/generated/{module}.yaml -- structured API spec
  • spec/overlays/{module}.yaml -- hand-written semantic annotations
  • templates/*.tmpl -- Go text templates

Output: {module}/*.go (e.g., looper/looper.go, looper/enums.go, ...)

flowchart LR
    SPEC["spec/generated/{module}.yaml"]
    OVER["spec/overlays/{module}.yaml"]

    subgraph MERGE["Merge"]
        direction TB
        M1["Resolve type names"]
        M2["Assign methods to receivers"]
        M3["Classify enums (error vs value)"]
        M4["Configure constructors/destructors"]
    end

    SPEC --> MERGE
    OVER --> MERGE

    MERGE --> MERGED["MergedSpec"]

    TMPL["templates/*.tmpl"]

    MERGED --> RENDER["Template Rendering"]
    TMPL --> RENDER

    RENDER --> PKG["package.go"]
    RENDER --> TYPES["types.go"]
    RENDER --> ENUMS["enums.go"]
    RENDER --> ERRS["errors.go"]
    RENDER --> FUNCS["functions.go"]
    RENDER --> TF["<type>.go (per opaque type)"]
    RENDER --> BRIDGE["capi/{module}/bridge_*.go"]
Overlay Files

Overlays provide semantic annotations that transform raw C APIs into idiomatic Go. They are the primary mechanism for customization and are entirely hand-written.

Example overlay (spec/overlays/looper.yaml):

module: looper
package:
  go_name: looper
  go_import: github.com/AndroidGoLab/ndk/looper
  doc: "Package looper provides Go bindings for Android ALooper."
types:
  ALooper:
    go_name: Looper # ALooper -> Looper
    destructor: ALooper_release # generates Close() method
    pattern: ref_counted
  Looper_event_t:
    go_name: Event
    strip_prefix: ALOOPER_ # ALOOPER_EVENT_INPUT -> EventInput
functions:
  ALooper_prepare:
    go_name: Prepare
    returns_new: Looper # returns *Looper, acts as constructor
  ALooper_wake:
    receiver: Looper # becomes method: (*Looper).Wake()
    go_name: Wake
  ALooper_release:
    skip: true # handled by destructor, don't expose

Key overlay capabilities:

Feature Effect
go_name Rename C symbol to idiomatic Go
go_error: true Enum type implements error interface
constructor / destructor Generate New*() and Close()
receiver Turn free function into method
returns_new Mark function as factory returning new handle
strip_prefix Remove C naming prefix from enum constants
pattern Lifecycle pattern: builder, ref_counted, singleton
callback_structs Generate CGo callback bridges with registry
struct_accessors Wrap C struct arrays as Go accessors
skip: true Exclude function from generation
chain: true Method returns receiver for chaining
Templates

13 Go text templates in templates/ control the shape of generated code:

Template Generates
package.go.tmpl Package declaration and doc comment
type_alias_file.go.tmpl Type aliases for basic typedefs
value_enum_file.go.tmpl Type-safe enum definitions with String()
errors.go.tmpl Error type implementing error interface
functions.go.tmpl Package-level free functions
callback_file.go.tmpl Callback type placeholders
type_file.go.tmpl Per-opaque-type struct, constructor, destructor, methods
bridge_registry.go.tmpl Callback registration via sync.Map
bridge_c.go.tmpl C inline functions for callback dispatch
bridge_export.go.tmpl //export Go functions callable from C
lifecycle.go.tmpl NativeActivity lifecycle API
bridge_lifecycle.go.tmpl Lifecycle callback C bridges
bridge_lifecycle_export.go.tmpl Lifecycle callback exports

End-to-End Example: looper

The transformation from C header to Go package:

flowchart TD
    subgraph C["C Header (android/looper.h)"]
        C1["ALooper* ALooper_prepare(int opts)"]
        C2["void ALooper_wake(ALooper* looper)"]
        C3["void ALooper_release(ALooper* looper)"]
    end

    subgraph SPEC["spec/generated/looper.yaml (specgen + c2ffi)"]
        S1["types: ALooper: {kind: opaque_ptr}"]
        S2["functions: ALooper_prepare: ..."]
    end

    subgraph CAPI["capi/looper/looper.go (capigen)"]
        R1["type ALooper C.ALooper"]
        R2["func ALooper_prepare(opts int32) *ALooper"]
        R3["func ALooper_wake(looper *ALooper)"]
        R4["func ALooper_release(looper *ALooper)"]
    end

    subgraph GO["looper/looper.go (idiomgen)"]
        G1["type Looper struct { ptr *capi.ALooper }"]
        G2["func Prepare(opts int32) *Looper"]
        G3["func (h *Looper) Wake()"]
        G4["func (h *Looper) Close() error"]
    end

    C --> SPEC
    SPEC --> CAPI
    SPEC --> GO

E2E Verification

Run make e2e-examples-test to test ndkcli commands on a connected device or emulator.

Verified platforms (click to expand)
Type Device Android API ABI Build Date Passed Total
Phone Pixel 8a 16 36 arm64-v8a BP4A.260205.001 2026-03-15 17 17
Emulator sdk_gphone64_x86_64 15 35 x86_64 Pixel_7_API_35 2026-03-15 17 17

Project Layout

.
├── Makefile                      # Build orchestration
├── tools/
│   ├── cmd/
│   │   ├── specgen/              # Stage 1: spec extraction via c2ffi
│   │   ├── capigen/              # Stage 2: CGo wrapper generator
│   │   ├── idiomgen/             # Stage 3: idiomatic Go generator
│   │   ├── headerspec/           # Standalone header spec extraction tool
│   │   └── ndk-build/            # APK packaging tool
│   └── pkg/
│       ├── c2ffi/                # c2ffi invocation and JSON→spec conversion
│       ├── capigen/              # CGo code generation from spec + manifest
│       ├── headerspec/           # Header spec extraction logic
│       ├── specgen/              # Spec writing and legacy Go AST parsing
│       ├── specmodel/            # Spec data model (types, enums, functions)
│       ├── idiomgen/             # Merge, resolve, template rendering
│       └── overlaymodel/         # Overlay data model (annotations)
├── capi/
│   ├── manifests/                # capigen config per module (hand-written)
│   └── {module}/                 # Generated raw CGo packages
├── spec/
│   ├── generated/                # Generated YAML specs (intermediate)
│   └── overlays/                 # Hand-written semantic overlays
├── templates/                    # Go text templates for code generation
├── {module}/                     # Generated idiomatic Go packages (final output)
├── jni/                          # Hand-written JNI helpers (activity, permissions, etc.)
├── e2e/                          # End-to-end tests (Android emulator)
└── examples/                     # Usage examples per module

Make Targets

Target Description Requires
make all Run all three stages NDK + c2ffi
make specs Stage 1: extract specs from C headers NDK + c2ffi
make capi Stage 2: generate raw CGo bindings specs + manifests
make idiomatic Stage 3: generate idiomatic packages specs + overlays
make regen Clean and regenerate everything NDK + c2ffi
make fixtures Generate from testdata (no NDK needed) --
make test Run unit tests --
make e2e Run E2E tests on Android emulator NDK + SDK + KVM
make apk-displaycamera Build camera example APK NDK + SDK
make clean Remove capi/*/ and spec/generated/ --

Adding a New Module

  1. Create capi/manifests/{module}.yaml -- configure which NDK headers to parse and which symbols to accept.
  2. Run make specs to extract the structured spec from C headers via c2ffi.
  3. Run make capi to generate the raw CGo binding package.
  4. Create spec/overlays/{module}.yaml -- annotate types with Go names, assign functions as methods, configure constructors/destructors, error types, etc.
  5. Run make idiomatic to generate the final Go package.

Comparison with Similar Projects

Several other projects provide Go (or Rust) bindings for the Android NDK. The table below highlights how they differ in scope, API style, and maintenance status.

ndk (this project) gomobile android-go gooid rust-mobile/ndk
Language Go Go Go Go Rust
NDK modules 34 (graphics, camera, audio, media, sensors, ML, binder, …) ~6 (app, gl, asset, sensor, audio, font — mostly in exp/) ~7 (android core, EGL, GLES 1/2/3/3.1, NativeActivity) Core NDK + camera, sensor, audio via hand-written wrappers ~18 (asset, audio, bitmap, config, font, hardware buffer, looper, media, native window, surface texture, trace, …)
API style Idiomatic Go: methods on types, builders, Close(), error interface, chaining Cross-platform abstraction; hides NDK behind portable APIs Thin wrappers over C; raw NDK types with some hand-written helpers Hand-written Go wrappers; Chinese documentation Safe Rust abstractions over raw FFI (ndk-sys)
Code generation 3-stage pipeline (c2ffi → spec YAML → CGo → idiomatic Go) Hand-written c-for-go auto-generation from NDK headers Manual bindgen for ndk-sys; hand-written safe layer
Target NDK Current (configurable via sysroot) Tied to gomobile toolchain android-23 Unspecified (older) Current (configurable)
Camera2 NDK Yes No No Yes (partial, hand-written) No
AAudio Yes No (exp/audio uses OpenSL ES) No Yes (partial) Yes
MediaCodec Yes No No No Yes (partial)
Vulkan Yes No No No No
NNAPI Yes No No No No
Binder IPC Yes No No No No
Callback bridging Auto-generated C↔Go bridges with registry N/A (event loop abstraction) Manual via CGo Manual Rust closures / trait objects
Lifecycle mgmt Idempotent nil-safe Close() error on all types Managed by framework Manual Manual Drop trait
Cross-platform Android only Android + iOS Android only Android only Android only
Maintenance Active (2025) Active (official Go project) Inactive (last commit 2022) Inactive (last commit 2019) Active (2025)
Stars ~6 100 ~1 100 ~60 ~1 300
Key Differences
  • gomobile is the official Go mobile toolkit. It prioritizes cross-platform portability (Android + iOS) over NDK coverage and exposes a small set of portable APIs (OpenGL ES, sensors, audio). It does not provide direct access to NDK-specific modules like Camera2, AAudio, MediaCodec, Vulkan, or Binder. If you need to call Android-specific NDK APIs, gomobile alone is not sufficient.

  • android-go (xlab) was a pioneering project that used c-for-go to auto-generate bindings from NDK headers. It covers EGL and OpenGL ES well but does not wrap higher-level NDK modules (camera, audio, media, sensors). The generated API exposes raw C types without idiomatic Go transformations. The project has not been updated since 2022.

  • gooid provides hand-written Go wrappers for a few NDK modules including camera and sensors, with example apps. Documentation is primarily in Chinese. The project has been inactive since 2019.

  • rust-mobile/ndk is the closest analog in the Rust ecosystem. It follows a similar two-layer approach (raw FFI in ndk-sys, safe abstractions in ndk) and covers ~18 NDK modules. It does not yet wrap Camera2, Vulkan, or NNAPI. If you are writing Rust rather than Go, this is the standard choice.

  • This project (AndroidGoLab/ndk) combines auto-generation with hand-written semantic overlays to produce idiomatic Go APIs across 34 NDK modules — the broadest coverage of any Go NDK binding library. The pipeline ensures that new NDK headers can be incorporated by updating manifests and overlays rather than rewriting bindings by hand.

FAQ

Q: Why NativeActivity instead of GameActivity?

This project wraps NDK C headers via a three-stage generation pipeline. GameActivity is a Jetpack Java library (AAR in the AGDK), not an NDK C API. NativeActivity is the only activity model exposed by NDK headers themselves. GameActivity requires bundling a Java AAR + JNI bridging — fundamentally different from the C→Go pipeline. rust-mobile/ndk faces the identical constraint and also centers NativeActivity. A companion gameactivity/ package is planned for wrapping the AGDK GameActivity C library.

Q: What about permissions, text input, and other Java-only APIs?

Android runtime permissions are Activity-driven Java APIs; the NDK APermissionManager_checkPermission only checks, it cannot request. The jni/ package provides HasPermission() and RequestPermission() via JNI as the escape hatch. examples/camera/display/ demonstrates the full permission request flow. This is inherent to Android's platform design — all native-first frameworks (Rust ndk, C++ NativeActivity) hit the same boundary. See Platform Integration Guide for details.

Q: Why do examples use unsafe.Pointer and runtime.LockOSThread()?

unsafe.Pointer in audio I/O provides zero-copy buffer access — adding a copying wrapper would be a performance regression for the primary use case. runtime.LockOSThread() is the standard Go pattern for thread-affine APIs (EGL contexts, ALooper, AInputQueue) — it is not a leaky abstraction, it is how Go correctly interoperates with thread-local native APIs. The idiomatic layer hides unsafe.Pointer for all handle types behind typed Go structs with constructors and Close() methods. See Thread Safety Guide.

Q: Why make targets instead of Android Studio / Gradle?

There is no Go↔Gradle integration in the wider Go ecosystem; gomobile also uses a custom build tool. The provided Makefile produces complete, signed APKs ready for deployment. A standalone go-ndk-build CLI tool is planned to streamline APK packaging.

Q: Is the API stable?

The project is pre-release (v0.x). APIs may change as the overlay system evolves. Generated APIs track NDK header changes — running the pipeline with a new NDK version may change signatures. Semantic versioning will be adopted once the overlay format stabilizes.

Guides

Directories

Path Synopsis
Package activity provides Go bindings for Android NativeActivity.
Package activity provides Go bindings for Android NativeActivity.
Package asset provides Go bindings for Android Asset Manager.
Package asset provides Go bindings for Android Asset Manager.
Package audio provides Go bindings for Android AAudio.
Package audio provides Go bindings for Android AAudio.
Package binder provides Go bindings for Android Binder IPC.
Package binder provides Go bindings for Android Binder IPC.
Package binderprocess provides access to ABinderProcess_* functions that are not part of the public NDK headers but available at runtime via dlsym in libbinder_ndk.so.
Package binderprocess provides access to ABinderProcess_* functions that are not part of the public NDK headers but available at runtime via dlsym in libbinder_ndk.so.
Package bitmap provides Go bindings for Android Bitmap (jnigraphics).
Package bitmap provides Go bindings for Android Bitmap (jnigraphics).
Package camera provides Go bindings for Android Camera2 NDK.
Package camera provides Go bindings for Android Camera2 NDK.
capi
aaudio
Raw CGo bindings for Android AAudio
Raw CGo bindings for Android AAudio
asset
Raw CGo bindings for Android asset manager
Raw CGo bindings for Android asset manager
binder
Raw CGo bindings for Android Binder NDK
Raw CGo bindings for Android Binder NDK
bitmap
Raw CGo bindings for Android Bitmap
Raw CGo bindings for Android Bitmap
camera
Raw CGo bindings for Android Camera2 NDK
Raw CGo bindings for Android Camera2 NDK
choreographer
Raw CGo bindings for Android choreographer
Raw CGo bindings for Android choreographer
configuration
Raw CGo bindings for Android configuration
Raw CGo bindings for Android configuration
egl
Raw CGo bindings for EGL
Raw CGo bindings for EGL
font
Raw CGo bindings for Android Font
Raw CGo bindings for Android Font
gles2
Raw CGo bindings for OpenGL ES 2.0
Raw CGo bindings for OpenGL ES 2.0
gles3
Raw CGo bindings for OpenGL ES 3.0
Raw CGo bindings for OpenGL ES 3.0
hardwarebuffer
Raw CGo bindings for Android hardware buffer
Raw CGo bindings for Android hardware buffer
imagedecoder
Raw CGo bindings for Android Image Decoder
Raw CGo bindings for Android Image Decoder
input
Raw CGo bindings for Android input
Raw CGo bindings for Android input
logging
Raw CGo bindings for Android logging
Raw CGo bindings for Android logging
looper
Raw CGo bindings for Android looper
Raw CGo bindings for Android looper
media
Raw CGo bindings for Android Media NDK
Raw CGo bindings for Android Media NDK
midi
Raw CGo bindings for Android MIDI
Raw CGo bindings for Android MIDI
multinetwork
Raw CGo bindings for Android Multi-Network
Raw CGo bindings for Android Multi-Network
nativeactivity
Raw CGo bindings for Android NativeActivity
Raw CGo bindings for Android NativeActivity
nativewindow
Raw CGo bindings for Android native window
Raw CGo bindings for Android native window
neuralnetworks
Raw CGo bindings for Android Neural Networks
Raw CGo bindings for Android Neural Networks
performancehint
Raw CGo bindings for Android Performance Hint
Raw CGo bindings for Android Performance Hint
permission
Raw CGo bindings for Android Permission Manager
Raw CGo bindings for Android Permission Manager
persistablebundle
Raw CGo bindings for Android PersistableBundle NDK
Raw CGo bindings for Android PersistableBundle NDK
sensor
Raw CGo bindings for Android sensor
Raw CGo bindings for Android sensor
sharedmem
Raw CGo bindings for Android Shared Memory
Raw CGo bindings for Android Shared Memory
storage
Raw CGo bindings for Android Storage Manager
Raw CGo bindings for Android Storage Manager
surfacecontrol
Raw CGo bindings for Android SurfaceControl
Raw CGo bindings for Android SurfaceControl
surfacetexture
Raw CGo bindings for Android SurfaceTexture
Raw CGo bindings for Android SurfaceTexture
sync
Raw CGo bindings for Android Sync
Raw CGo bindings for Android Sync
thermal
Raw CGo bindings for Android thermal
Raw CGo bindings for Android thermal
trace
Raw CGo bindings for Android trace
Raw CGo bindings for Android trace
vulkan
Raw CGo bindings for Vulkan
Raw CGo bindings for Vulkan
Package choreographer provides Go bindings for Android AChoreographer.
Package choreographer provides Go bindings for Android AChoreographer.
cmd
ndkcli command
Package config provides Go bindings for Android AConfiguration.
Package config provides Go bindings for Android AConfiguration.
Package egl provides Go bindings for EGL.
Package egl provides Go bindings for EGL.
examples
activity/overview command
NativeActivity lifecycle overview.
NativeActivity lifecycle overview.
asset/overview command
Example: AAssetManager API overview and directory iteration.
Example: AAssetManager API overview and directory iteration.
audio/basic-playback command
Basic AAudio playback example.
Basic AAudio playback example.
audio/recording command
Audio recording (capture) example.
Audio recording (capture) example.
audio/state-machine command
AAudio stream state machine example.
AAudio stream state machine example.
audio/stream-info command
Stream information query example.
Stream information query example.
binder/overview command
Binder IPC API overview.
Binder IPC API overview.
bitmap/formats command
Example: Android bitmap format constants and error codes.
Example: Android bitmap format constants and error codes.
camera/capture-session command
Camera capture session lifecycle example.
Camera capture session lifecycle example.
camera/display command
Camera preview display using NativeActivity with OpenGL ES rendering.
Camera preview display using NativeActivity with OpenGL ES rendering.
camera/manager command
Camera manager lifecycle example.
Camera manager lifecycle example.
camera/overview command
Camera2 pipeline overview.
Camera2 pipeline overview.
choreographer/vsync command
Example: obtain the Android Choreographer singleton.
Example: obtain the Android Choreographer singleton.
config/device-config command
Example: query device configuration properties.
Example: query device configuration properties.
egl/config-query command
Example: EGL configuration attribute query.
Example: EGL configuration attribute query.
egl/initialize command
Example: EGL initialization and display query.
Example: EGL initialization and display query.
egl/offscreen-render command
EGL offscreen rendering with pbuffer and OpenGL ES 2.
EGL offscreen rendering with pbuffer and OpenGL ES 2.
egl/pbuffer-context command
Example: offscreen pbuffer surface with an OpenGL ES 2 context.
Example: offscreen pbuffer surface with an OpenGL ES 2 context.
gles2/clear-screen command
Simplest GLES2 example: clear the framebuffer to a known color and verify.
Simplest GLES2 example: clear the framebuffer to a known color and verify.
gles2/framebuffer command
Render-to-texture via an offscreen framebuffer object (FBO).
Render-to-texture via an offscreen framebuffer object (FBO).
gles2/texture command
Textured quad: upload pixel data to a texture and render it on a quad.
Textured quad: upload pixel data to a texture and render it on a quad.
gles2/triangle command
Hello Triangle: the classic first OpenGL ES 2.0 program.
Hello Triangle: the classic first OpenGL ES 2.0 program.
gles3/instanced-drawing command
Instanced drawing example using OpenGL ES 3.0.
Instanced drawing example using OpenGL ES 3.0.
gles3/sync-fences command
Sync fence example using OpenGL ES 3.0.
Sync fence example using OpenGL ES 3.0.
gles3/transform-feedback command
Transform feedback example using OpenGL ES 3.0.
Transform feedback example using OpenGL ES 3.0.
gles3/vertex-array-objects command
Vertex Array Object (VAO) example using OpenGL ES 3.0.
Vertex Array Object (VAO) example using OpenGL ES 3.0.
hint/overview command
Performance Hint API overview.
Performance Hint API overview.
hwbuf/allocate-lock command
Hardware buffer allocation and CPU-lock lifecycle.
Hardware buffer allocation and CPU-lock lifecycle.
hwbuf/formats-usage command
Hardware buffer formats and usage flags overview.
Hardware buffer formats and usage flags overview.
image/overview command
AImageDecoder API overview.
AImageDecoder API overview.
input/event-types command
Example: input event type system and processing pattern.
Example: input event type system and processing pattern.
input/touch-handling command
Touch and key input event handling example.
Touch and key input event handling example.
log/priorities command
Example: Android log priority levels and their string representations.
Example: Android log priority levels and their string representations.
looper/basic-loop command
Basic ALooper event loop example.
Basic ALooper event loop example.
looper/timeout-poll command
Timeout-based ALooper polling example.
Timeout-based ALooper polling example.
media/codec-lifecycle command
MediaCodec full lifecycle example.
MediaCodec full lifecycle example.
media/decoder-setup command
Video decoder setup example.
Video decoder setup example.
media/format-create command
Media format creation and property access example.
Media format creation and property access example.
media/muxer-tracks command
Muxer track format preparation example.
Muxer track format preparation example.
midi/overview command
MIDI API overview and reference example.
MIDI API overview and reference example.
permission/check-permissions command
Check common Android permissions for the current process.
Check common Android permissions for the current process.
persistablebundle/overview command
PersistableBundle API overview.
PersistableBundle API overview.
sensor/accelerometer command
Example: accelerometer sensor detail and event-queue setup.
Example: accelerometer sensor detail and event-queue setup.
sensor/event-loop command
Sensor event loop pattern combining sensor and looper packages.
Sensor event loop pattern combining sensor and looper packages.
sensor/list-sensors command
Example: list default sensors and their properties.
Example: list default sensors and their properties.
sharedmem/overview command
Example: sharedmem package overview
Example: sharedmem package overview
storage/obb-manager command
OBB storage manager lifecycle example.
OBB storage manager lifecycle example.
surfacecontrol/transaction command
Transaction lifecycle and chaining pattern example.
Transaction lifecycle and chaining pattern example.
surfacetexture/gl-rendering command
SurfaceTexture GL rendering pipeline example.
SurfaceTexture GL rendering pipeline example.
surfacetexture/overview command
ASurfaceTexture API overview.
ASurfaceTexture API overview.
sync/overview command
Example: sync package overview
Example: sync package overview
thermal/monitor command
Example: thermal status monitoring.
Example: thermal status monitoring.
thermal/status-polling command
Thermal status polling example.
Thermal status polling example.
trace/basic-tracing command
Basic systrace example: synchronous sections, asynchronous sections, and counters.
Basic systrace example: synchronous sections, asynchronous sections, and counters.
trace/function-tracing command
Function-level tracing with defer.
Function-level tracing with defer.
trace/nested-sections command
Nested trace sections and counters example.
Nested trace sections and counters example.
window/formats command
Example: ANativeWindow pixel format constants and API overview.
Example: ANativeWindow pixel format constants and API overview.
window/software-render command
Software rendering to an ANativeWindow.
Software rendering to an ANativeWindow.
Package font provides Go bindings for Android Font.
Package font provides Go bindings for Android Font.
Package gameactivity provides Go bindings for the AGDK GameActivity C library.
Package gameactivity provides Go bindings for the AGDK GameActivity C library.
Package gles2 provides Go bindings for OpenGL ES 2.0.
Package gles2 provides Go bindings for OpenGL ES 2.0.
Package gles3 provides Go bindings for OpenGL ES 3.0.
Package gles3 provides Go bindings for OpenGL ES 3.0.
Package hint provides Go bindings for Android performance hint management.
Package hint provides Go bindings for Android performance hint management.
Package hwbuf provides Go bindings for Android HardwareBuffer.
Package hwbuf provides Go bindings for Android HardwareBuffer.
Package image provides Go bindings for Android ImageDecoder.
Package image provides Go bindings for Android ImageDecoder.
Package input provides Go bindings for Android Input events.
Package input provides Go bindings for Android Input events.
internal
Package jni provides JNI helper functions for Android NativeActivity operations that have no NDK C API equivalent (permission dialogs, Toast messages, SurfaceTexture creation from Java).
Package jni provides JNI helper functions for Android NativeActivity operations that have no NDK C API equivalent (permission dialogs, Toast messages, SurfaceTexture creation from Java).
Package log provides Go bindings for Android logging.
Package log provides Go bindings for Android logging.
Package looper provides Go bindings for Android ALooper.
Package looper provides Go bindings for Android ALooper.
Package media provides Go bindings for Android Media NDK.
Package media provides Go bindings for Android Media NDK.
Package midi provides Go bindings for Android MIDI.
Package midi provides Go bindings for Android MIDI.
Package net provides Go bindings for Android multinetwork APIs.
Package net provides Go bindings for Android multinetwork APIs.
Package nnapi provides Go bindings for Android Neural Networks API (NNAPI).
Package nnapi provides Go bindings for Android Neural Networks API (NNAPI).
Package permission provides Go bindings for Android Permission Manager.
Package permission provides Go bindings for Android Permission Manager.
Package persistablebundle provides Go bindings for Android PersistableBundle.
Package persistablebundle provides Go bindings for Android PersistableBundle.
Package sensor provides Go bindings for Android Sensor NDK.
Package sensor provides Go bindings for Android Sensor NDK.
Package sharedmem provides Go bindings for Android shared memory.
Package sharedmem provides Go bindings for Android shared memory.
Package storage provides Go bindings for Android Storage Manager.
Package storage provides Go bindings for Android Storage Manager.
Package surfacecontrol provides Go bindings for Android SurfaceControl.
Package surfacecontrol provides Go bindings for Android SurfaceControl.
Package surfacetexture provides Go bindings for Android SurfaceTexture.
Package surfacetexture provides Go bindings for Android SurfaceTexture.
Package sync provides Go bindings for Android sync primitives.
Package sync provides Go bindings for Android sync primitives.
tests
e2e command
E2E test for ndk idiomatic Go packages on Android.
E2E test for ndk idiomatic Go packages on Android.
e2e/audio-inject command
Audio injector: streams a sine wave to the Android emulator's virtual microphone via the gRPC injectAudio API.
Audio injector: streams a sine wave to the Android emulator's virtual microphone via the gRPC injectAudio API.
e2e/audio-recording-e2e command
E2E test: exercises the full AAudio input stream lifecycle at 48 kHz.
E2E test: exercises the full AAudio input stream lifecycle at 48 kHz.
Package thermal provides Go bindings for Android thermal management.
Package thermal provides Go bindings for Android thermal management.
tools
cmd/capigen command
Command capigen generates raw CGo binding packages from spec YAML and manifest configuration.
Command capigen generates raw CGo binding packages from spec YAML and manifest configuration.
cmd/cligen command
Command cligen generates cobra CLI subcommands for all exported functions and methods in the NDK idiomatic packages.
Command cligen generates cobra CLI subcommands for all exported functions and methods in the NDK idiomatic packages.
cmd/headerspec command
Command headerspec generates spec YAML and capi/ Go packages from NDK C headers using clang AST.
Command headerspec generates spec YAML and capi/ Go packages from NDK C headers using clang AST.
cmd/idiomgen command
Command idiomgen generates idiomatic Go packages from specs and overlays.
Command idiomgen generates idiomatic Go packages from specs and overlays.
cmd/ndk-build command
Command ndk-build produces a signed Android APK from a Go package using the Android SDK/NDK toolchain.
Command ndk-build produces a signed Android APK from a Go package using the Android SDK/NDK toolchain.
cmd/ndkcli-commands command
Command ndkcli-commands parses cmd/ndkcli/ source files and prints the full cobra command tree without compiling the binary (which requires Android NDK libraries).
Command ndkcli-commands parses cmd/ndkcli/ source files and prints the full cobra command tree without compiling the binary (which requires Android NDK libraries).
cmd/specgen command
Command specgen extracts structured YAML specs from c2ffi JSON output (or legacy Go AST parsing for test fixtures).
Command specgen extracts structured YAML specs from c2ffi JSON output (or legacy Go AST parsing for test fixtures).
pkg/apkbuild
Package apkbuild provides functionality for building signed Android APKs from Go packages using the Android SDK/NDK toolchain.
Package apkbuild provides functionality for building signed Android APKs from Go packages using the Android SDK/NDK toolchain.
pkg/c2ffi
Package c2ffi parses c2ffi JSON output and converts it to specmodel.Spec.
Package c2ffi parses c2ffi JSON output and converts it to specmodel.Spec.
pkg/capigen
Package capigen generates raw CGo binding packages from spec YAML and manifest configuration.
Package capigen generates raw CGo binding packages from spec YAML and manifest configuration.
pkg/headerspec
Package headerspec parses clang JSON AST output and generates spec YAML.
Package headerspec parses clang JSON AST output and generates spec YAML.
pkg/overlaymodel
Package overlaymodel defines the YAML overlay schema for hand-written semantic annotations.
Package overlaymodel defines the YAML overlay schema for hand-written semantic annotations.
pkg/specgen
Package specgen extracts structured API specs from Go source files produced by c-for-go.
Package specgen extracts structured API specs from Go source files produced by c-for-go.
pkg/specmodel
Package specmodel defines the YAML spec schema produced by specgen and consumed by idiomgen.
Package specmodel defines the YAML spec schema produced by specgen and consumed by idiomgen.
Package trace provides Go bindings for Android native tracing.
Package trace provides Go bindings for Android native tracing.
Package vulkan provides Go bindings for Vulkan on Android.
Package vulkan provides Go bindings for Vulkan on Android.
Package window provides Go bindings for Android NativeWindow.
Package window provides Go bindings for Android NativeWindow.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL