Media

Overview

A media player is one of the most common building blocks in embedded audio applications, used to play back audio from local storage or over the network. The Media Framework provides a unified MediaPlayer C interface that defines the complete playback lifecycle — create → set data source → prepare → control → release — so that application code no longer needs to care about the underlying container format or decoder.

Compared to calling individual decoder libraries directly, MediaPlayer internally encapsulates multi-protocol data source access, container demuxing, audio decoding, render synchronization, and automatic buffering. It reports state changes, buffering progress, and errors to the application through event callbacks. The framework is modular and Kconfig-driven, so only the formats and third-party decoders actually needed have to be enabled, keeping ROM/RAM footprint under control across SoCs with different capabilities.

Features

Media Framework delivers desktop/mobile-class playback capability on lightweight IoT devices:

  • Unified C API — MediaPlayer hides the differences between container formats and data sources. Whether the input is a prompt WAV on LittleFS, an MP3 ringtone in flash, or an HTTPS AAC stream from the cloud, the application code is virtually identical.

  • Mainstream audio format coverage — Supports demuxing and decoding of WAV, MP3, AAC, M4A, FLAC, AMR, and OGG (Vorbis / Opus); each format can be enabled or disabled independently.

  • Multiple media sources — Local file systems (LittleFS / FAT / USB / SD), HTTP/HTTPS streaming, and application-defined StreamSource.

  • Sync and async preparation — Prepare is convenient for short local files; PrepareAsync parses headers and buffers in the background without blocking the caller, dramatically improving perceived latency on network sources.

  • Complete playback control — Start / Pause / Stop / Seek / Loop / SetVolume / SetSpeed, plus queries such as GetCurrentTime / GetDuration / IsPlaying.

  • Callback-based events — Three channels — OnStateChanged, OnInfo, OnError — report state transitions, buffering information, and unrecoverable errors, making UI updates and retry logic straightforward.

  • Trimmable footprint — Kconfig-based menuconfig lets users enable only the formats and third-party libraries they need.

System Architecture

../../_images/media_architecture.svg

System architecture

Responsibilities by layer:

Layer

Description

Application Layer

User application code that calls the Public C API.

Public API Layer

The four header files under interfaces/media/ — the SDK’s sole outward interface.

Media Framework

Support data acquisition, demuxing, decoding, and render synchronization.

Audio HAL

I2S driver and amplifier control, abstracted behind AudioService.

Realtek Ameba SoC

Hardware layer.

Integration only requires including the headers under interfaces/media/ and linking against Media Framework.

Supported Formats

MediaPlayer supports the following audio formats:

Audio format

Description

File types

AAC

Mono/stereo content with standard sample rates from 8 kHz to 96 kHz.

ADTS raw AAC (.aac)

MP3

Mono/stereo, 8 kbps ~ 320 kbps, constant (CBR) or variable bit-rate (VBR).

MP3 (.mp3)

PCM/WAVE

8-, 16-, 24-bit and float linear PCM, sample rates 8 kHz to 96 kHz.

WAVE (.wav)

FLAC

Mono/stereo (no multichannel), up to 48 kHz. 16-bit recommended; 24-bit supported without dither.

FLAC (.flac)

M4A

Mono/stereo content with standard sample rates from 8 kHz to 96 kHz.

MPEG-4 (.m4a)

OGG-Opus

Low-latency speech codec, 6 ~ 510 kbps, suitable for real-time comms and voice.

Ogg (.ogg)

OGG-Vorbis

Open general-purpose audio format, quality comparable to AAC — music/podcasts.

Ogg (.ogg)

AMR

AMR-NB (narrow-band) / AMR-WB (wide-band) speech codec — voice messages, calls.

AMR (.amr)

Configuration

Media Framework is modular and Kconfig-driven. Audio formats and third-party decoder libraries can be selected as needed to balance features against firmware size.

Basic Configuration

Run ./menuconfig.py from the project root and navigate to:

Audio Config  --->
  [*] Enable Audio Framework        # AUDIO_FWK_MENU
      Select Audio Interfaces  --->
        (X) Mixer                   # Mixer mode recommended
  [*] Enable Media Player           # MEDIA_PLAYER_MENU (auto-selects AUDIO_FWK_MENU)

Selecting MEDIA_PLAYER_MENU automatically pulls in AUDIO_FWK_MENU. The Audio Framework provides the underlying AudioService, which must be initialized (AudioService_Init()) before any MediaPlayer usage.

Audio Format Configuration

Audio Config  --->
  [*] Enable Media Player  --->
      Media Formats  ---> # Select as needed
        [*] WAV
        [*] MP3
        [*] AAC
        [*] M4A
        [*] FLAC
        [*] AMR
        [*] OGG  --->
              OGG Codec  --->
                [*] Vorbis
                [*] Opus

Each format maps to an independent demux + codec module; enabling a format also selects the third-party libraries it depends on. Enabling only the formats you actually use noticeably reduces firmware size.

Third-Party Library Configuration

Kconfig option

Library

Purpose

THIRD_PARTY_HAAC_MENU

FDK-AAC

AAC and M4A decoding

THIRD_PARTY_FLAC_MENU

libFLAC

FLAC lossless decoding

THIRD_PARTY_OPUS_MENU

libopus

Opus encoding/decoding

THIRD_PARTY_TREMOLO_MENU

Tremolo

OGG Vorbis decoding

THIRD_PARTY_GSM_MENU

libgsm

AMR-NB / AMR-WB decoding

Third-party libraries are auto-selected when the corresponding format is enabled. They can also be toggled manually under the Third Party Libraries menu.

Getting Started

This chapter walks through the full MediaPlayer usage flow in the natural order: create → set data source → prepare → control → release.

Creating a Player

Any code using MediaPlayer must first initialize the audio service, then allocate a player instance. AudioService_Init brings up the underlying HAL, launches the render thread, and loads default audio effect settings. Call it once, early in system startup:

#include "media/media_player.h"
#include "audio/audio_service.h"

AudioService_Init();   // Call once per process lifetime.

MediaPlayer *player = MediaPlayer_Create();

The instance returned by MediaPlayer_Create is in the IDLE state. It is strongly recommended to call MediaPlayer_SetCallback() immediately after creation; otherwise early events produced during Prepare / Start (such as PREPARED or BUFFERING_START) will be dropped because there is no listener yet.

Setting the Data Source

MediaPlayer offers two mutually exclusive ways to feed data. For most use cases the URL form is sufficient — the prefix selects the underlying data path:

MediaPlayer_SetDataSource(player, "lfs://res/welcome.mp3");            // LittleFS
MediaPlayer_SetDataSource(player, "fat://music/song.flac");            // FAT / SD
MediaPlayer_SetDataSource(player, "http://example.com/audio.aac");     // HTTP
MediaPlayer_SetDataSource(player, "https://cdn.example.com/song.m4a"); // HTTPS + TLS

When the data does not come from a URL (e.g. Bluetooth audio frames, encrypted containers, application-owned buffers), implement the StreamSource interface and hand it over to the player:

StreamSource *source = MyDataSource_Create(buffer, length);
MediaPlayer_SetStreamSource(player, source);

Regardless of which method is used, the data source must be set while the player is in the IDLE state. To switch tracks, first call MediaPlayer_Reset() to return the state machine to IDLE.

Preparing the Player

Mode

Function

Blocks

Completion notification

Recommended for

Sync

MediaPlayer_Prepare()

Yes

Function returns when done.

Debugging, simple

Async

MediaPlayer_PrepareAsync()

No

OnStateChanged(MEDIA_PLAYER_PREPARED)

Production code

Why PrepareAsync feels faster. Once called, the player enters the PREPARING state immediately, and the SDK completes network connection, format probing, and decoder initialization on an internal thread. The caller thread is not blocked; completion is delivered via callback. The underlying preparation is not actually faster — the win is that blocking is moved off the caller thread to the looper thread. For network streams, preparation may take hundreds of milliseconds to a few seconds (TCP handshake, TLS negotiation, initial buffering). If the UI or business main loop waits synchronously, the user sees an obvious stall. PrepareAsync returns immediately, so the app can keep refreshing the UI, responding to input, or playing a transition animation while preparation runs in the background — dramatically reducing perceived “press-play-to-first-sound” latency. PrepareAsync is strongly recommended for network playback; the synchronous form is fine for short local files where code brevity matters more.

Async preparation example (recommended for network streams):

static volatile int g_prepared = 0;

void OnStateChanged(const MediaPlayerCallback *cb,
                    const MediaPlayer *player, int state)
{
    if (state == MEDIA_PLAYER_PREPARED) {
        g_prepared = 1;
    }
}

MediaPlayer_PrepareAsync(player);
while (!g_prepared) {          /* Use a semaphore instead of polling in real code. */
    rtos_time_delay_ms(20);
}
MediaPlayer_Start(player);

Sync preparation example (recommended for small local files):

if (MediaPlayer_Prepare(player) == AUDIO_OK) {
    MediaPlayer_Start(player);
}

Controlling the Player

../../_images/media_state.svg

Player state machine

API

Valid states

Description

MediaPlayer_Start()

PREPARED / PAUSED / REWIND_COMPLETE

Start or resume playback

MediaPlayer_Pause()

STARTED

Pause; current position preserved

MediaPlayer_Stop()

STARTED / PAUSED / PLAYBACK_COMPLETE

Stop playback

MediaPlayer_Seek(msec)

PREPARED / STARTED / PAUSED

Seek to a specific millisecond

MediaPlayer_SetLooping(1)

Any valid state

Loop playback

MediaPlayer_SetVolume(l, r)

STARTED and beyond

Set L/R channel volume (0.0–1.0)

MediaPlayer_SetSpeed(spd, pitch)

STARTED

Change speed/pitch (1.0 = normal)

MediaPlayer_GetCurrentTime(&ms)

STARTED / PAUSED

Current playback position (ms)

MediaPlayer_GetDuration(&ms)

PREPARED and beyond

Total duration; live streams: -1

MediaPlayer_IsPlaying()

Any

1 = playing

MediaPlayer_Reset()

STOPPED and beyond

Reset to IDLE; can SetDataSource again

Valid state transitions

Current state

Allowed operation

Target state

IDLE

SetDataSource / SetStreamSource

IDLE

IDLE

Prepare

PREPARED

IDLE

PrepareAsync

PREPARING

IDLE

Destroy

—

PREPARING

Prepare complete

PREPARED

PREPARED

Start

STARTED

PREPARED

Reset

IDLE

STARTED

Pause

PAUSED

STARTED

Stop

STOPPED

STARTED

Seek complete

REWIND_COMPLETE

STARTED

Playback complete

PLAYBACK_COMPLETE

REWIND_COMPLETE

Start

STARTED

PAUSED

Start

STARTED

PAUSED

Stop

STOPPED

STOPPED

Reset

IDLE

PLAYBACK_COMPLETE

Reset

IDLE

ERROR

Reset

IDLE

Complete playback flow:

AudioService_Init();

MediaPlayer *player = MediaPlayer_Create();
/* cb must remain valid for the entire lifetime of the player. Do NOT use a stack
   variable. Heap-allocate or use a static/global; free it only after MediaPlayer_Destory. */
MediaPlayerCallback *cb = malloc(sizeof(MediaPlayerCallback));
cb->OnStateChanged = OnStateChanged;
cb->OnInfo = OnInfo;
cb->OnError = OnError;
MediaPlayer_SetCallback(player, cb);

MediaPlayer_SetDataSource(player, "lfs://audio/demo.mp3");
MediaPlayer_PrepareAsync(player);
/* Wait for PREPARED callback ... */

MediaPlayer_Start(player);

int64_t duration = 0;
MediaPlayer_GetDuration(player, &duration);

/* After PLAYBACK_COMPLETE ... */
MediaPlayer_Stop(player);
/* Wait for STOPPED callback ... */

MediaPlayer_Reset(player);
MediaPlayer_Destory(player);
free(cb);

Releasing the Player

The release order must be followed strictly: Stop → (wait for STOPPED) → Reset → Destory.

MediaPlayer_Stop(player);
/* After OnStateChanged(MEDIA_PLAYER_STOPPED) ... */

MediaPlayer_Reset(player);
MediaPlayer_Destory(player);

/* StreamSource must be freed AFTER MediaPlayer_Destory returns. */
if (stream_source) {
    MyStreamSource_Destroy((MyStreamSource *)stream_source);
}
free(cb);

Note

The StreamSource Destory must be called only after MediaPlayer_Destory() returns — the SDK may still be reading data before then.

Player Events

MediaPlayer reports three categories of events to the application via the MediaPlayerCallback struct: state changes, informational updates, and errors. This chapter covers how callbacks are registered, key rules to follow, and the three individual callbacks.

Registering Callbacks

Populate a MediaPlayerCallback struct and call MediaPlayer_SetCallback() before SetDataSource().

/* cb must outlive the player instance. Do NOT use a stack variable.
   Heap-allocate or use a static/global; free it only after MediaPlayer_Destory. */
MediaPlayerCallback *cb = malloc(sizeof(MediaPlayerCallback));
cb->OnStateChanged = my_on_state_changed;
cb->OnInfo         = my_on_info;
cb->OnError        = my_on_error;
MediaPlayer_SetCallback(player, cb);

Callback Usage Notes

Timing

  • Callbacks must be registered before MediaPlayer_Prepare / MediaPlayer_PrepareAsync; otherwise critical states such as MEDIA_PLAYER_PREPARED may be missed.

  • The MediaPlayerCallback struct must remain valid for the entire lifetime of the player. Do not use a stack variable.

Thread safety

  • All three callbacks are dispatched from an internal Player Core thread — not the caller’s thread. Take a lock when accessing shared application state from inside a callback.

  • Callbacks for the same player instance are serialized; they will not fire concurrently.

Memory management

  • The listener and player pointers passed to a callback are owned by the framework and are only guaranteed valid until the callback returns. Do not free them and do not store them in a long-lived container outside the callback.

  • To hand an event off to another thread, copy it into a queue/message — do not save the raw pointer.

Avoiding deadlocks

  • The callback executes on the player’s internal message loop. Do not synchronously call MediaPlayer_Stop / MediaPlayer_Reset / MediaPlayer_Destory from within a callback — doing so deadlocks against the internal state machine.

  • To drive the player based on an event, dispatch the action asynchronously via an application-level task or message.

  • Callbacks should return quickly. Push blocking work (file I/O, network, UI refresh) to your own worker thread.

Player State Changes: OnStateChanged

void OnStateChanged(const MediaPlayerCallback *cb,
                    const MediaPlayer *player, int state)
{
    switch (state) {
    case MEDIA_PLAYER_IDLE:              /* 0 - Initial / after Reset       */ break;
    case MEDIA_PLAYER_PREPARING:         /* 1 - PrepareAsync in progress    */ break;
    case MEDIA_PLAYER_PREPARED:          /* 2 - Preparation complete        */ break;
    case MEDIA_PLAYER_STARTED:           /* 3 - Playing                     */ break;
    case MEDIA_PLAYER_PAUSED:            /* 4 - Paused                      */ break;
    case MEDIA_PLAYER_STOPPED:           /* 5 - Stopped                     */ break;
    case MEDIA_PLAYER_PLAYBACK_COMPLETE: /* 6 - End of stream               */ break;
    case MEDIA_PLAYER_REWIND_COMPLETE:   /* 7 - Seek complete               */ break;
    case MEDIA_PLAYER_ERROR:             /* 8 - Unrecoverable error         */ break;
    }
}

state values (enum MediaPlayerStates):

Value

Meaning

Trigger

MEDIA_PLAYER_IDLE (0)

Idle

After creation / after Reset

MEDIA_PLAYER_PREPARING (1)

Preparing

Emitted right after PrepareAsync

MEDIA_PLAYER_PREPARED (2)

Ready

Prepare complete; Start allowed

MEDIA_PLAYER_STARTED (3)

Playing

Start succeeded

MEDIA_PLAYER_PAUSED (4)

Paused

Pause succeeded

MEDIA_PLAYER_STOPPED (5)

Stopped

Stop succeeded

MEDIA_PLAYER_PLAYBACK_COMPLETE (6)

Ended

Reached EOS, not looping

MEDIA_PLAYER_REWIND_COMPLETE (7)

Seek done

Seek completed

MEDIA_PLAYER_ERROR (8)

Error

Followed by an OnError call

Player Info: OnInfo

void OnInfo(const MediaPlayerCallback *cb,
            const MediaPlayer *player, int info, int extra)
{
    switch (info) {
    case MEDIA_PLAYER_INFO_BUFFERING_START:        /* 0 - Buffering started (playback paused) */ break;
    case MEDIA_PLAYER_INFO_BUFFERING_END:          /* 1 - Buffering ended (playback resumed)  */ break;
    case MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE:  /* 2 - extra = buffered percentage         */ break;
    case MEDIA_PLAYER_INFO_NOT_REWINDABLE:         /* 3 - Live stream, Seek not supported     */ break;
    }
}

info values (enum MediaPlayerInfos):

Value

Meaning

extra

MEDIA_PLAYER_INFO_BUFFERING_START (0)

Buffering started (data underflow)

Reserved

MEDIA_PLAYER_INFO_BUFFERING_END (1)

Buffering ended (playback resumed)

Reserved

MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE (2)

Buffering progress update

Buffered percentage (0–100)

MEDIA_PLAYER_INFO_NOT_REWINDABLE (3)

Data source is not seekable (e.g. live)

Reserved

Applications typically use these events to show a “buffering” indicator or disable the seek bar.

Player Errors: OnError

void OnError(const MediaPlayerCallback *cb,
             const MediaPlayer *player, int error, int extra)
{
    if (error == MEDIA_PLAYER_ERROR_UNKNOWN) {
        /* Log and notify the business layer to retry or prompt the user. */
    }
}

error values (enum MediaPlayerErrors):

Value

Meaning

extra

MEDIA_PLAYER_ERROR_UNKNOWN (0)

Generic unrecoverable error (data source failure, decode fault, state-machine anomaly)

Internal framework error code, usually matching AUDIO_ERR_*. Use for logging; not for branching logic.

Note

Only the generic MEDIA_PLAYER_ERROR_UNKNOWN code is exposed publicly. The specific root cause (network disconnect, corrupted file, decode failure, out-of-memory, …) has to be inferred from the extra field together with the SDK’s serial log.

After OnError fires, the player enters the MEDIA_PLAYER_ERROR state and must be recovered via Reset or Destory before it can be used again.

Media Sources

MediaPlayer supports three families of media sources: local files (LittleFS / FAT / VFS / …), network streams (HTTP/HTTPS), and application-defined StreamSource.

Local Files

A URL prefix selects the underlying storage backend (availability depends on hardware and driver configuration):

Prefix

File system

Example

lfs://

LittleFS (built-in flash)

lfs://audio/prompt.wav

fat://

FAT (SD card / USB mass storage)

fat://music/song.mp3

vfs://

VFS (unified mount for multiple FS)

vfs://usb/song.flac

Note

SD-card and USB storage must be mounted to the corresponding FATFS/VFS path from the SDK side before use. USB audio devices additionally require USB Host + Mass Storage support to be enabled in Kconfig.

URL form:

MediaPlayer_SetDataSource(player, "lfs://audio/alarm.wav");
MediaPlayer_SetDataSource(player, "fat://sdcard/music.flac");

The file source additionally supports playing a sub-range via query parameters (local files only):

MediaPlayer_SetDataSource(player, "lfs://music/song.mp3?offset=1024&size=204800");

HTTP/HTTPS Streaming

The http:// and https:// prefixes are supported. Networking and the HTTP client component must be enabled in the SDK.

MediaPlayer_SetDataSource(player, "http://music.example.com/song.mp3");
MediaPlayer_SetDataSource(player, "https://music.example.com/song.mp3");
MediaPlayer_PrepareAsync(player); /* Async form strongly recommended. */

HTTP Stream Buffering

HTTP stream buffering happens in two independent phases, each governed by its own threshold parameter.

Phase 1: Preparation (Prepare / PrepareAsync)

After calling PrepareAsync(), the SDK opens the HTTP connection in the background and starts receiving data. Once the buffered duration exceeds the buffering_initial_time threshold, the SDK fires OnStateChanged(PREPARED) to signal that playback can begin. During buffering the SDK continuously reports progress through OnInfo(BUFFERING_INFO_UPDATE, percent).

../../_images/media_http_buffering_starting.svg

HTTP buffering — preparation phase

Note

Key parameter: buffering_initial_time (preparation-phase threshold)

  • Default: 5000 ms — PREPARED is emitted after 5 s of buffered audio.

  • Recommended: ≥ 5000 ms for live streams (avoids start-up stalls); a smaller value for on-demand files speeds up first-frame playback.

  • The SDK continuously reports progress through OnInfo(BUFFERING_INFO_UPDATE, percent) during the preparation phase — useful for driving a loading UI.

  • PREPARED is not delivered until the threshold is met. The application must not call Start() before receiving PREPARED.

Phase 2: Playing

If a network stall during playback causes the buffer to underflow, the SDK automatically pauses playback internally and fires BUFFERING_START. Downloading continues; once the buffered duration recovers past the buffering_resume_time threshold, the SDK resumes playback automatically and fires BUFFERING_END. No application intervention is required.

../../_images/media_http_buffering_playing.svg

HTTP buffering — playing phase

Note

Key parameter: buffering_resume_time (playing-phase resume threshold)

  • Default: 2000 ms — playback resumes and BUFFERING_END fires once 2 s of buffer is available.

  • Recommended: ≥ 2000 ms for live streams; smaller for on-demand to resume faster.

  • When the buffer underflows, the SDK internally pauses decoding and reports BUFFERING_START — no polling needed from the application.

  • Weak-network recovery: the SDK has built-in TCP reconnection; read_retry_time_ms controls the retry backoff.

Default parameters:

Parameter

Default

Description

Buffer size

256 KB

Upper bound of the network receive buffer

buffering_initial_time

5000 ms

Preparation threshold (triggers PREPARED)

buffering_resume_time

2000 ms

Playing-phase resume threshold (BUFFERING_END)

Socket connect timeout

500 ms

TCP connect timeout

Send timeout

2000 ms

HTTP request send timeout

Receive timeout

2000 ms

HTTP data receive timeout

Read retry interval

5000 ms

Wait between retries after network interruption

Callback sequence during buffering:

  • OnInfo(MEDIA_PLAYER_INFO_BUFFERING_START) — SDK internally paused (buffer underflow).

  • OnInfo(MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE, percent) — Buffering progress.

  • OnInfo(MEDIA_PLAYER_INFO_BUFFERING_END) — Buffer refilled; playback resumed.

Monitoring Live Playback

  • Use MEDIA_PLAYER_INFO_BUFFERING_START / MEDIA_PLAYER_INFO_BUFFERING_END to drive a “buffering” UI state.

  • Use the extra field of MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE (0–100) to update the buffering progress bar.

  • Live streams have no fixed duration: MediaPlayer_GetDuration() returns -1 and MEDIA_PLAYER_INFO_NOT_REWINDABLE is reported. The application should disable seek operations.

  • Frequent MEDIA_PLAYER_INFO_BUFFERING_START events during playback signal insufficient bandwidth — prompt the user or drop to a lower bitrate.

Live streams (Icecast / HTTP Live) do not support random access. The SDK notifies the application via callback:

void OnInfo(const MediaPlayerCallback *cb,
            const MediaPlayer *player, int info, int extra)
{
    if (info == MEDIA_PLAYER_INFO_NOT_REWINDABLE) {
        g_is_live_stream = 1;  /* Disable seek bar; don't call Seek(). */
    }
    if (info == MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE) {
        printf("Buffering: %d%%\n", extra);
    }
}

Custom Data Source (StreamSource)

StreamSource lets the application supply arbitrary in-memory audio data; the SDK pulls bytes on demand through function pointers.

typedef struct StreamSource StreamSource;
struct StreamSource {
    int32_t (*CheckPrepared)(const StreamSource *source);
    ssize_t (*ReadAt)(const StreamSource *source, off_t offset, void *data, size_t size);
    int32_t (*GetLength)(const StreamSource *source, off_t *size);
};

Implementation Notes

  • CheckPrepared — Confirms during the Prepare phase that the upstream data path is ready. Return AUDIO_OK to allow reads; return AUDIO_ERR_NO_INIT to make the framework report failure.

  • ReadAt — Copy up to size bytes from offset into data and return the actual number of bytes read. Special return values:

    • 0 — End of stream.

    • STREAM_SOURCE_EOF — End of stream (equivalent to 0; either can be used).

    • STREAM_SOURCE_READ_AGAIN — No data yet; ask the framework to retry (typical for real-time pull streams).

    • Any other negative value — Hard error.

  • GetLength — Write the total length into *size:

    • Known length: return AUDIO_OK.

    • Unknown (live): return STREAM_SOURCE_UNKNOWN_LENGTH. The framework will disable seek and notify the app via MEDIA_PLAYER_INFO_NOT_REWINDABLE.

Usage Example

#include "media/stream_source.h"

typedef struct {
    StreamSource base;       /* Must be the first member. */
    const char  *data;
    int          data_length;
    int          all_ready;  /* 1 = all data available */
} MySource;

static int32_t my_check_prepared(const StreamSource *s)
{
    MySource *src = (MySource *)s;
    return src->data ? AUDIO_OK : AUDIO_ERR_NO_INIT;
}

static ssize_t my_read_at(const StreamSource *s, off_t offset,
                          void *buf, size_t size)
{
    MySource *src = (MySource *)s;
    if (offset >= src->data_length) {
        if (!src->all_ready)
            return (ssize_t)STREAM_SOURCE_READ_AGAIN;
        return (ssize_t)STREAM_SOURCE_EOF;
    }
    size_t avail = (size_t)(src->data_length - (int)offset);
    if (size > avail) size = avail;
    memcpy(buf, src->data + offset, size);
    return (ssize_t)size;
}

static int32_t my_get_length(const StreamSource *s, off_t *out)
{
    MySource *src = (MySource *)s;
    *out = src->data_length;
    return src->all_ready ? STREAM_SOURCE_OK : STREAM_SOURCE_UNKNOWN_LENGTH;
}

StreamSource *MySource_Create(const char *data, int len)
{
    MySource *src = calloc(1, sizeof(MySource));
    src->base.CheckPrepared = my_check_prepared;
    src->base.ReadAt        = my_read_at;
    src->base.GetLength     = my_get_length;
    src->data       = data;
    src->data_length = len;
    src->all_ready  = 1;
    return (StreamSource *)src;
}

/* Wire it up: */
StreamSource *src = MySource_Create(audio_buf, audio_buf_len);
MediaPlayer_SetStreamSource(player, src);
MediaPlayer_PrepareAsync(player);

Notes

Item

Description

Random access

The SDK may call ReadAt at arbitrary offsets (seek returns to a prior offset). Random access must be supported.

READ_AGAIN

If data is not ready yet, return STREAM_SOURCE_READ_AGAIN. The SDK will retry rather than hold a lock waiting.

Unknown length

While GetLength returns STREAM_SOURCE_UNKNOWN_LENGTH the SDK treats the source as a stream. Return STREAM_SOURCE_OK once all data has been appended.

Lifetime

The StreamSource object must not be freed until MediaPlayer_Destory() has returned.

Thread safety

ReadAt and GetLength are invoked on the SDK’s internal thread. Lock any shared state accessed inside them.

Player Commands

The player CLI is a command-line debugging tool built on top of the Media SDK. It can be driven from the serial console to test playback end-to-end.

Enable via menuconfig:

Audio Config  --->
  CONFIG AUDIO CMD  --->
    [*] player

Command-line options:

Option

Description

Example

-f <path/url>

File path or HTTP URL to play

-f lfs://audio/test.mp3

-s 0/1

Use StreamSource mode?

-s 1

-v <float>

Initial volume (0.0 ~ 1.0)

-v 0.8

Examples:

player -f http://192.168.1.100/test.mp3
player -f lfs://audio/alarm.wav
player -f buffer -s 1

FAQ

Q: Out of memory when playing a long file?

A: Use streaming playback — the SDK decodes in chunks and never loads the whole file at once. Enable only the formats you actually need (Kconfig-based trimming).


Q: Seek does not work?

A: Some streaming formats (live streams, certain HTTP servers) do not support seeking. Check for MEDIA_PLAYER_INFO_NOT_REWINDABLE — if received, disable seek in the UI. For local files, make sure the file is intact (e.g. MP3 VBR files require a valid Xing/VBRI header).


Q: PrepareAsync times out; PREPARED callback never fires?

A: For HTTP, prepared_timeout_ms defaults to 2000 ms — increase it on weak networks. For StreamSource, make sure CheckPrepared returns AUDIO_OK (data is actually ready).


Q: How to build a play queue (gapless playlist)?

A: When OnStateChanged(MEDIA_PLAYER_PLAYBACK_COMPLETE) fires, signal your business thread via a semaphore and run: Stop → (wait for STOPPED) → Reset → SetDataSource → PrepareAsync → (wait for PREPARED) → Start. Never run these steps directly from the callback thread.

API Reference

This chapter lists the complete signatures, parameters, and return values of the public MediaPlayer API.

Lifecycle

Function

Description

MediaPlayer *MediaPlayer_Create(void)

Create an instance; NULL on error

void MediaPlayer_Destory(MediaPlayer *player)

Destroy and free resources

int32_t MediaPlayer_Reset(MediaPlayer *player)

Reset to the IDLE state

Data Source

MediaPlayer_SetDataSource

int32_t MediaPlayer_SetDataSource(MediaPlayer *player, const char *url);

Parameter

Description

player

MediaPlayer instance

url

File path or HTTP/HTTPS URL

Return value

Meaning

AUDIO_OK

Success

AUDIO_ERR_INVALID_OPERATION

Not in the IDLE state

AUDIO_ERR_UNKNOWN_ERROR

Internal error

MediaPlayer_SetStreamSource

int32_t MediaPlayer_SetStreamSource(MediaPlayer *player, StreamSource *source);

Parameter

Description

player

MediaPlayer instance

source

User-implemented StreamSource pointer

Return values are the same as SetDataSource.

Preparation

Function

Description

int32_t MediaPlayer_Prepare(MediaPlayer *player)

Synchronous prepare; blocks until PREPARED or error

int32_t MediaPlayer_PrepareAsync(MediaPlayer *player)

Asynchronous prepare; returns immediately, result via OnStateChanged

Playback Control

Function

Parameters

Valid states

int32_t MediaPlayer_Start(MediaPlayer*)

—

PREPARED / PAUSED / REWIND_COMPLETE

int32_t MediaPlayer_Pause(MediaPlayer*)

—

STARTED

int32_t MediaPlayer_Stop(MediaPlayer*)

—

STARTED / PAUSED / PLAYBACK_COMPLETE

int32_t MediaPlayer_Seek(MediaPlayer*, int64_t msec)

msec: target position (ms)

PREPARED / STARTED / PAUSED

int32_t MediaPlayer_SetLooping(MediaPlayer*, int8_t loop)

loop: 1=loop, 0=one-shot

Any

int32_t MediaPlayer_SetVolume(MediaPlayer*, float l, float r)

0.0–1.0

STARTED and beyond

int32_t MediaPlayer_SetSpeed(MediaPlayer*, float s, float p)

1.0 = normal

STARTED

Status Query

Function

Description

int32_t MediaPlayer_GetCurrentTime(MediaPlayer*, int64_t *msec)

Current playback position (ms)

int32_t MediaPlayer_GetDuration(MediaPlayer*, int64_t *msec)

Total duration (ms); live streams: -1

int MediaPlayer_IsPlaying(MediaPlayer*)

1 = playing, 0 = not playing

Callbacks

void MediaPlayer_SetCallback(MediaPlayer *player, MediaPlayerCallback *callbacks);

MediaPlayerCallback struct:

struct MediaPlayerCallback {
    void (*OnStateChanged)(const MediaPlayerCallback*, const MediaPlayer*, int state);
    void (*OnInfo)(const MediaPlayerCallback*, const MediaPlayer*, int info, int extra);
    void (*OnError)(const MediaPlayerCallback*, const MediaPlayer*, int error, int extra);
};

StreamSource

struct StreamSource {
    int32_t (*CheckPrepared)(const StreamSource *source);
    ssize_t (*ReadAt)(const StreamSource *source, off_t offset, void *data, size_t size);
    int32_t (*GetLength)(const StreamSource *source, off_t *size);
};

StreamSource return codes:

Constant

Value

Meaning

STREAM_SOURCE_OK

0

Success

STREAM_SOURCE_READ_AGAIN

-1001

Data not available yet; retry later

STREAM_SOURCE_FAIL

-1002

Operation failed

STREAM_SOURCE_EOF

-1003

End of data

STREAM_SOURCE_UNKNOWN_LENGTH

-1004

Total length unknown

Parcel

Function

Description

Parcel *Parcel_Create(void)

Create an empty Parcel

void Parcel_Destroy(Parcel *p)

Destroy and free

Parcel_WriteBool / Parcel_ReadBool

bool read/write

Parcel_WriteInt8 / Parcel_ReadInt8

int8 read/write

Parcel_WriteInt16 / Parcel_ReadInt16

int16 read/write

Parcel_WriteInt32 / Parcel_ReadInt32

int32 read/write

Parcel_WriteInt64 / Parcel_ReadInt64

int64 read/write

Parcel_WriteUint8 / Parcel_ReadUint8

uint8 read/write

Parcel_WriteUint16 / Parcel_ReadUint16

uint16 read/write

Parcel_WriteUint32 / Parcel_ReadUint32

uint32 read/write

Parcel_WriteUint64 / Parcel_ReadUint64

uint64 read/write

Parcel_WriteFloat / Parcel_ReadFloat

float

Parcel_WriteDouble / Parcel_ReadDouble

double

Parcel_WritePointer / Parcel_ReadPointer

Pointer value storage

Parcel_WriteBuffer(p, data, size)

Write an arbitrary byte block

void *Parcel_ReadBuffer(p, length)

Read a byte block (returns internal pointer)

Parcel_WriteCString(p, str)

Write a null-terminated string (with length)

char *Parcel_ReadCString(p)

Read a string (Parcel-owned; do not free)

Note

Read order must match write order. Pointers returned by ReadBuffer / ReadCString are owned by the Parcel and become invalid once the Parcel is destroyed.