HarmonyOS 6.0 AVCodec Kit Deep Dive: Engineering Patterns for Passing userData Through OH_AVDataSource Callbacks

HarmonyOS 6.0 AVCodec Kit adds userData passthrough for custom data sources, allowing OH_AVDataSource callbacks to access business context directly. This solves key challenges in multi-instance media processing, thread isolation, and C/C++ bridging. Keywords: HarmonyOS 6.0, AVCodec Kit, userData passthrough.

Technical Specification Snapshot

Parameter Description
Platform HarmonyOS 6.0
API Version API 20
Language C / C++
Core Modules AVCodec Kit, AVSource
Key API OH_AVSource_CreateWithDataSourceExt
Key Structures OH_AVDataSource, OH_AVDataSourceExt
Callback Capability readAt supports receiving userData
Applicable Scenarios Encrypted media, memory cache, database BLOB, multi-stream pipelines
Original Post Engagement 94 views, 5 likes, 6 bookmarks
Core Dependencies native_avsource.h, native_avcodec_base.h

Illustration AI Visual Insight: This image is the article cover screenshot. Its primary purpose is to identify the topic and page layout. It does not expose technical details such as interface structures, call chains, or memory layouts, so its visual value is mainly for content orientation rather than implementation guidance.

HarmonyOS 6.0 closes the context-passing gap in custom data sources

In earlier versions of AVCodec Kit, developers could provide media read capabilities through OH_AVDataSource, but the readAt callback could not naturally carry business context. Once you entered scenarios involving multi-instance processing, concurrent demuxing, or object-oriented encapsulation, this limitation became a serious problem.

Common workarounds included global variables, static members, or external mapping tables. These approaches can work, but they break encapsulation boundaries and increase the risk of thread contention and instance cross-talk. They are especially problematic in complex media applications such as players, editors, and VR multi-stream systems.

The legacy data source abstraction was lightweight but not extensible enough

typedef struct OH_AVDataSource {
    int64_t size;              // Total length of the data source
    OH_AVDataSourceReadAt readAt; // Callback for reading data at an offset
} OH_AVDataSource;

This structure shows that the legacy API only focused on “how large the data is” and “how to read it,” but not on “who is reading” or “what context the read operation depends on.”

The new extension API turns userData into a standard passthrough channel

HarmonyOS 6.0 introduces OH_AVSource_CreateWithDataSourceExt and OH_AVDataSourceExt. The core value is not simply “one more parameter,” but that media callbacks now have a standardized context entry point for the first time.

When creating an OH_AVSource, developers can pass a void *userData, and the system returns it in subsequent callbacks. This allows the callback function to access instance-level state, object handles, cache pools, decryptors, or log tags directly.

The new API design preserves compatibility during API evolution

OH_AVSource *OH_AVSource_CreateWithDataSourceExt(
    OH_AVDataSourceExt *dataSource,
    void *userData
);

It exists alongside the legacy API, which means HarmonyOS preserves backward compatibility while placing enhanced capabilities in an extended version. This is a stable and practical way to evolve Native APIs.

The userData mechanism directly solves three engineering pain points

The first pain point is multi-instance isolation. Each media source can bind its own independent context, so you no longer need to infer which instance a callback belongs to from global state.

The second pain point is C-to-C++ bridging. Although AVCodec Kit exposes a C API, userData can carry a C++ object pointer and restore object-oriented semantics inside the callback.

The third pain point is state aggregation. Keys, caches, connection handles, track metadata, and other runtime state can be placed into a single context structure, which reduces scattered parameters and lowers maintenance cost.

Define an aggregated context structure first

#include <multimedia/player_framework/native_avsource.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <stdint.h>

typedef struct {
    uint8_t *data;             // Start address of the media buffer
    int64_t size;              // Current data source length
    const char *sourceName;    // Used for logging and instance identification
    void *userContext;         // Reserved extension field, such as a decryptor or database handle
} CustomDataSourceContext;

The goal of this structure is to aggregate all state required for reading into a single object, making callback passthrough and resource management easier.

The readAt callback should prioritize boundary checks and context validation

Once userData is supported, the first step inside readAt should no longer be calling memcpy directly. Instead, validate the context, offset, and buffer first. Since all instance state enters through this path, a single error can lead to an out-of-bounds read or a crash.

int32_t CustomDataSource_ReadAt(void *userData, int64_t offset,
    uint8_t *buffer, int32_t length)
{
    CustomDataSourceContext *ctx = (CustomDataSourceContext *)userData; // Restore the instance context
    if (ctx == NULL || buffer == NULL || offset < 0 || length <= 0) {
        return -1; // Invalid arguments, return an error immediately
    }

    if (offset >= ctx->size) {
        return 0; // Reached EOF
    }

    int64_t remain = ctx->size - offset;
    int32_t bytesToRead = (length < remain) ? length : (int32_t)remain;
    memcpy(buffer, ctx->data + offset, bytesToRead); // Copy the target segment from the custom data source
    return bytesToRead;
}

This code demonstrates the minimum viable loop for a custom data source: restore the context, perform boundary checks, and return readable data at the requested offset.

You must design the lifecycle clearly when creating an AVSource

The real source of bugs is usually not the callback signature, but the resource release order. While OH_AVSource is alive, the system may enter readAt again at any time. Therefore, the object pointed to by userData must never be released too early.

int CreateMediaSource(CustomDataSourceContext *ctx)
{
    OH_AVDataSourceExt dataSource;
    dataSource.size = ctx->size;              // Bind the total data length
    dataSource.readAt = CustomDataSource_ReadAt; // Bind the read callback

    OH_AVSource *source = OH_AVSource_CreateWithDataSourceExt(&dataSource, ctx);
    if (source == NULL) {
        return -1; // Creation failed
    }

    // ... Connect to Demuxer / Player later

    OH_AVSource_Destroy(source); // Destroy source first to ensure no further callbacks occur
    return 0;
}

The key point in this flow is not the syntax, but the order: destroy source first, then release ctx and its internal resources.

Lifecycle rules must remain aligned with OH_AVSource

  1. The lifetime of userData must cover the entire lifetime of OH_AVSource.
  2. The buffers, handles, and key objects referenced by readAt must not be released early.
  3. If asynchronous chains exist, make sure upper-layer playback or demuxing has stopped before destruction.
  4. Do not pass temporary stack objects directly as userData.

Multi-instance scenarios show the value of the new API most clearly

Player list previews, video editing timelines, picture-in-picture, and multi-track composition all create multiple media sources at the same time. In these cases, sharing one callback implementation is ideal, but only if each callback can identify its owning instance.

typedef struct {
    OH_AVSource *source;
    CustomDataSourceContext *ctx;
} MediaInstance;

// Each instance holds its own ctx
// When the callback runs, userData automatically maps it to the correct instance

This design avoids repetitive implementations such as “one callback per source,” and it also removes the need for reverse lookups through a global hash table. The resulting structure is clearer, and the threading model is easier to reason about.

This capability is especially well suited to four types of media systems

Encrypted media playback can carry decryption context directly into the read stage

When media segments are stored in encrypted form, userData can carry keys, decryptor instances, and block mapping information. The callback can read ciphertext by offset, decrypt it, and write it into the output buffer on demand, which reduces intermediate copies.

Memory-cached playback can encapsulate cache state and preload strategy in the context

Network players typically maintain a ring buffer, downloaded ranges, and prefetch thresholds. By placing this state in userData, readAt can decide whether to return data immediately or trigger refill logic based on cache hit status.

Database-backed media can carry record location metadata directly

If media is stored in a database BLOB, userData can store the database connection, record ID, and offset mapping. This avoids writing temporary files and works well for albums, chat histories, and offline media containers.

Multi-stream merge scenarios make track-level distinction easier

In VR, multi-camera, or composite-track applications, different tracks may come from different sources. userData can include a track ID, time base, or routing tag, allowing a shared callback to become track-aware.

This API update fundamentally improves the engineering usability of AVCodec Kit

From an interface perspective, the change simply turns context passing into a standard capability. From an engineering perspective, it makes HarmonyOS Native media development feel much closer to the design patterns used in mature system libraries. Developers no longer need to build patch-style architecture around callback limitations.

For team collaboration, this also means cleaner encapsulation boundaries. The data reading layer depends only on the context structure, does not pollute the global namespace, and does not leak instance state externally. That makes it a better fit for large-scale project maintenance.

FAQ

Q1: What is the best thing to pass through userData?

The best choice is an instance-level context structure pointer, such as a buffer address, length, log tag, decryptor handle, database connection, or C++ object pointer. Do not pass the address of a temporary local variable.

Q2: How should I choose between OH_AVDataSourceExt and the legacy OH_AVDataSource?

If your scenario only reads a static file and has no instance-state dependency, the legacy API is still acceptable. If your design involves multiple instances, custom caching, decryption, databases, or object bridging, prefer the extended API.

Q3: What is the most common pitfall?

The biggest risk is incorrect lifecycle management: releasing userData, the underlying buffer, or related handles before OH_AVSource is destroyed. The second common issue is insufficient boundary checking in the callback, which can cause out-of-bounds access or incorrect EOF handling.

AI Readability Summary

This article explains the new OH_AVSource_CreateWithDataSourceExt and OH_AVDataSourceExt capabilities introduced in HarmonyOS 6.0 AVCodec Kit. It shows how to safely pass userData through custom media data source callbacks to eliminate reliance on global variables, improve multi-instance isolation, and manage lifecycle constraints correctly.