This chapter explains terminal audio hardware integration and which playback sources the APP can share on Android and iOS. Use matching SDK 1.0.0 / package revision 27 / ABI 8 headers and archives. Complete first connection first.
Choose a supported audio path
| Path | Model capability | Terminal implementation | Audio source |
|---|---|---|---|
| Bluetooth playback | audio.a2dp.sink, bit15 |
A working A2DP Sink module, drivers and output | The phone’s selected system Bluetooth media route |
| Wi-Fi playback | audio.wifi, bit16, with network.wifi enabled |
A separate TLS receiver, PCM sink and actual speaker output | Playback that the user authorizes and the system permits capturing |
| Calls | calls with a real HFP/call driver |
Call-audio hardware and drivers | See call integration |
Classic Bluetooth does not guarantee A2DP Sink support. Selecting Classic Bluetooth alone must not declare audio playback. network.bluetooth.classic selects the business transport; audio.a2dp.sink describes audio receiver hardware. BLE business control can coexist with a separate Classic Bluetooth A2DP module.
The SDK intersects actual hardware, cloud authorization and capabilities understood by the client. PROFILE key36 reports hardware, authorized and negotiated audio paths. It neither indicates active playback nor changes the phone’s system route. Declare only integrated hardware; audio cannot be marked simulated. A capability checkbox, authenticated Bluetooth business session or received packet is not evidence of audible output.
Wi-Fi format and capture sources
Wi-Fi uses a separate TLS channel on TCP 5006: 16 kHz mono PCM S16LE, 160 samples / 10 ms / 320 bytes per frame. This is a voice-oriented format, not a high-fidelity music channel or an HFP replacement. Audio does not enter the video UDP queue.
One home-screen projection entry attaches Wi-Fi audio according to the selected vehicle:
| Condition | APP behavior |
|---|---|
| Model has no Wi-Fi audio authorization | Video-only projection with no audio permission prompt; vehicle management hides audio connection and separate sharing controls |
| Authorized and runtime-ready | Attach audio when Wi-Fi hardware / authorized / negotiated in PROFILE36 are valid, primary wifiConnected=true and PROFILE37 audioRuntime=true |
| Capture permission or source policy blocks samples | Retain available video and report the actual capture state |
Android 10+ uses system playback capture. iOS uses ReplayKit audioApp and ignores microphone audio. System permission, the audio source and its capture policy determine sample availability.
The APP does not embed TTS or generate replacement navigation speech. AMap’s public guidance callback supplies text, not original PCM; it cannot provide original navigation audio over Wi-Fi. Built-in navigation currently uses the phone’s system / A2DP route and is mutually exclusive with screen mirroring. When permitted samples are unavailable, show a source restriction or silence rather than claiming playback.
Managed: configure the sink before start
The SDK task owns audio TLS, authorization and cleanup. Implement real drivers and fill moto_audio_port from moto/audio.h. Call this after successful create and before start:
// terminal is created; all four sink callbacks use real audio drivers.
moto_result rc = moto_terminal_set_audio(terminal, &sink, NULL);
if (rc == MOTO_OK) {
rc = moto_terminal_start(terminal);
}
// Check rc; on failure, do not enable input. Follow lifecycle cleanup rules.
A NULL third argument reuses config.network. With advanced external primary transport, explicitly supply a complete moto_network_port for the separate audio receiver. The function copies port structures; sink and network contexts must remain valid through terminal destruction.
Serialize setup on the lifecycle-owner task. Calling while running returns MOTO_BUSY; a successful stop/start retains configuration. Do not attach audio at runtime through moto_terminal_post. For independent iOS mirroring, also call moto_terminal_set_mirror(terminal, true) before starting; see video projection for prerequisites.
Implement all four sink callbacks
| Callback | Driver responsibility |
|---|---|
begin(user, token) |
Prepare this stream; return an error if hardware is unavailable |
write(user, token, pcm) |
Accept this stream’s PCM; copy into a bounded queue before returning for asynchronous playback |
stop(user, token) |
Immediately invalidate queued data for this token so old audio cannot enter the next stream |
status(user, token, &played, &queued) |
Report samples submitted to the driver and samples still queued |
All four callbacks are required. They run on the SDK owner thread and must not block or reenter the SDK. PCM is borrowed until callback return; do not retain its pointer for a playback task. Identify streams by token and invalidate pending driver buffers when stopping.
MOTO_FULL drops the current live frame; MOTO_IO ends the stream. Do not retransmit stale audio or hide a slow driver with an unbounded queue. played counts samples submitted to the driver, not proven speaker output. Measure latency and quality on target hardware.
Core: reuse the shared audio receiver
Core applications call moto_audio_attach(sdk, &sink) after moto_create and before connecting. It copies the port without creating tasks or sockets; keep the sink context alive through SDK destruction.
Use moto_audio_server_create/poll/stop/destroy from moto/audio_server.h and link the package’s moto_tls component. Configuration borrows the primary SDK, network port and secure random source; these objects and callback contexts must outlive the receiver. Do not continue audio startup if creation fails.
Call moto_audio_server_poll(server, now_ms) every 10–20 ms on the same SDK owner thread using a monotonic clock. The receiver listens on 5006 only during a valid grant and owns one active connection, TLS, parsing and backpressure. Customers do not need to implement another audio parser. Stop all callers and destroy the receiver before destroying the primary SDK. For custom transport integration, follow the delivered moto_audio_wifi_* contract; never let both receivers own the same stream.
Teardown and acceptance
Stopping sharing, authorization revocation, owner changes, network loss and lease expiry must stop the old stream. After unexpected primary TCP loss, only an already delegated, still-valid mirror lease may continue under its lifecycle rules. This does not permit playback after loss of the entire network. Temporary audio credentials belong only to the current owner, session and token; never log or persist them.
On Android and iOS separately, validate permitted and blocked sources, audible speaker output, system A2DP switching, stop/restart sharing, network loss and old-buffer removal. Verify that Wi-Fi audio stops after switching to A2DP to avoid duplicate playback. Without A2DP hardware, keep route/listening acceptance pending rather than substituting negotiation results. Builds, received packets and played counters do not establish audible output. See troubleshooting.
