---
title: "Local microphone capture"
canonical_url: https://developer.repebble.com/pebbleos/docs/reference/local-microphone/
last_updated: 2026-10-23
sdk_version: 4.33.1
---
# Local microphone capture

Source: https://developer.repebble.com/pebbleos/docs/reference/local-microphone/

SDK revision 111 adds `mic_capture_start`, `mic_capture_stop` and `mic_capture_is_active`. A watchapp receives signed 16-bit mono PCM at 16 kHz on its app task, in 320-sample (20 ms) callbacks. No audio is sent to the phone by this API. The sample pointer is valid only for the callback. A slow app drops frames once the two-frame kernel queue fills.

```
static void on\_audio(const int16\_t \*samples, size\_t count, void \*context) {// Consume or copy samples here.}static void on\_stopped(MicCaptureStopReason reason, void \*context) {// Capture has ended; a new start is required.}MicCaptureStartResult result = mic\_capture\_start((MicCaptureHandlers) {.data = on\_audio,.stopped = on\_stopped,}, NULL);// Later: mic\_capture\_stop();
```

Declare `manifestVersion: 2` and `usesPermissions: ["Microphone"]` in the `pebble` block of `package.json`. The updated phone app syncs only explicitly allowed Microphone decisions for installed apps while its experimental permission system is enabled. Permission granted automatically by a policy does not authorize local capture: enable the permission on the app’s page. Before the first sync, start returns `MicCaptureStartErrPermissionDenied` and the hardware remains stopped.

The grant is stored on the watch, so capture works after disconnecting the phone or rebooting the watch. An offline watch cannot receive a new grant or revocation until it reconnects. A connected revocation discards queued samples, stops capture and reports `MicCaptureStopReasonPermissionRevoked`. Disabling the permission system on the phone resyncs app metadata with microphone access denied.

Only foreground watchapps may capture. Watchfaces and workers are rejected. The OS displays its Listening banner; focus loss, app exit and dictation preemption stop capture. Permission updates that retain the current app’s grant do not interrupt it. Local capture and phone streaming share the microphone manager, so only one may run at a time: either start returns busy while the other runs, including while a stream waits for the phone.

## Synchronization

Firmware advertises capability bit 27, `app_permissions_support`. CoreApp keeps the watch microphone grant in its existing locker entry and sends it through the normal AppDB (`0x02`) sync, with the same app selection, sync limits and deletion handling. No separate permission database or UUID snapshot exists.

The optional wire extension is a little-endian `uint32_t` appended after the 96-byte app name: bit 0 authorizes the microphone. Only extended metadata (130 bytes) sets or clears the grant; legacy metadata (126 bytes), as an older companion sends, leaves the stored grant unchanged. Apps without a stored grant read as denied. CoreApp sends the extension only to capable firmware. The watch stores the grant in its own AppDB record keyed by the app UUID, keeping the app record at the legacy size that older firmware expects after a downgrade. Permission-only updates, extended metadata whose first 126 bytes match the stored record, preserve the app install ID and cached binary, so toggling the permission does not close or reinstall the app. Any other insert, including a resend of identical metadata, is handled as a reinstall. Metadata deletion revokes the grant.

Streaming and local capture share the same explicit Microphone decision. Automatic approval policies do not grant either API access. Decisions remain in the phone’s existing permission store; the locker field is its watch-sync projection, updated transactionally with the app’s sync hash.

### Trust

The AppDB permissions field is the only consent point for local capture: any extended insert with bit 0 set grants capture to that UUID, with no prompt on the watch beyond the Listening banner while the microphone runs. The watch keeps it out of apps’ reach (BlobDB is private to the system session, PebbleKit sessions cannot reach it, and no app syscall writes the AppDB), but it cannot check what the companion does. A companion must only ever set the field from its own consent UI, never from an API an app can reach, including the app’s own PebbleKit JS.

The grant belongs to the app UUID, which the developer chooses, and survives reinstalls and upgrades. The companion is responsible for asking again when a different binary arrives under a granted UUID, e.g. a sideloaded app reusing it.

The AppDB extension is phone-controlled and not writable by an app syscall. Capture syscalls check task, app UUID and session token, validate app buffers, and copy PCM into the app’s memory. Events contain session tokens, never pointers into kernel memory. A stale event cannot dispatch into a later capture session.

The firmware and SDK must ship together: an older firmware does not contain the revision 111 function table entries. The existing `mic_stream_to_phone_*` API remains unchanged. Frozen SDK platforms expose unavailable stubs: start returns `MicCaptureStartErrUnavailable`, stop is a no-op and is-active returns false.

Generated from PebbleOS [f7970c345](https://github.com/coredevices/pebbleos/commit/f7970c34522cb34294cb980e9a72880a2999d599). This page is maintained in the pebbleos repository: [docs/reference/local-microphone.md](https://github.com/coredevices/pebbleos/blob/f7970c34522cb34294cb980e9a72880a2999d599/docs/reference/local-microphone.md).
