鸿蒙视频编码

以下为对 HarmonyOS 视频编码 Native API 的核心内容进行的结构化总结,包括API 设计与使用流程的Mermaid 图示,便于开发者快速理解与落地。


✅ 一、能力概览

能力 简述 运行时参数配置 动态设置帧率、码率、QP 等 随帧 QP 设置 每帧可设置 QPMin/QPMax 分层编码(LTR) 支持时域可分层视频编码 获取编码信息 每帧可获取 QPAverage 和 MSE 变分辨率 Surface 模式支持输入分辨率变化 HDR Vivid 编码 API 11+,需使用 H265(HEVC)


✅ 二、输入模式对比

模式 数据来源 适用场景 性能 接口差异 Surface OHNativeWindow 相机等实时流 高 使用 OH_VideoEncoder_GetSurface Buffer 预分配内存 文件读取等 中 使用 OH_VideoEncoder_PushInputBuffer


✅ 三、状态机设计(Mermaid)

stateDiagram-v2 [*] --> Initialized: 创建/Reset Initialized --> Configured: Configure Configured --> Prepared: Prepare Prepared --> Executing: Start Executing --> Prepared: Stop Executing --> Flushed: Flush Executing --> EndOfStream: EOS Executing --> Error: 异常 Error --> Initialized: Reset Error --> [*]: Destroy Executing --> [*]: Destroy

✅ 四、开发流程(Surface 模式)

sequenceDiagram participant App participant Encoder participant Surface participant File App->>Encoder: OH_VideoEncoder_CreateByMime App->>Encoder: RegisterCallback App->>Encoder: Configure App->>Encoder: GetSurface App->>Encoder: Prepare App->>Encoder: Start Surface-->>Encoder: 输入图像流 Encoder-->>App: OnNewOutputBuffer App->>File: 写入编码数据 App->>Encoder: FreeOutputBuffer App->>Encoder: NotifyEndOfStream App->>Encoder: Stop/Flush/Reset/Destroy

✅ 五、Buffer 模式差异点

步骤 差异说明 输入数据 使用 OH_VideoEncoder_PushInputBuffer 结束标志 设置 AVCODEC_BUFFER_FLAGS_EOS 跨距处理 需手动处理 stride 与 width 不一致的情况


✅ 六、CMake 链接库

cmake 复制代码
target_link_libraries(sample PUBLIC libnative_media_codecbase.so)
target_link_libraries(sample PUBLIC libnative_media_core.so)
target_link_libraries(sample PUBLIC libnative_media_venc.so)

✅ 七、关键 API 一览

功能 API 创建编码器 OH_VideoEncoder_CreateByMime / CreateByName 注册回调 OH_VideoEncoder_RegisterCallback 配置参数 OH_VideoEncoder_Configure 获取 Surface OH_VideoEncoder_GetSurface 启动编码 OH_VideoEncoder_Start 动态设置 OH_VideoEncoder_SetParameter 通知结束 OH_VideoEncoder_NotifyEndOfStream 释放输出 OH_VideoEncoder_FreeOutputBuffer 销毁实例 OH_VideoEncoder_Destroy


✅ 八、注意事项

  • Buffer 模式不支持 10bit 图像
  • 编码器资源有限,必须调用 Destroy 释放
  • 不能在回调中调用 Destroy
  • Flush/Reset/Stop/Destroy 会等待所有回调完成
  • 跨距不一致时需手动拷贝数据

✅ 九、建议封装结构

  • 使用 CodecBufferQueue 管理输入输出队列
  • 使用 shared_mutex 保证线程安全
  • 使用 OH_AVFormat 配置编解码参数
  • 使用 OH_AVBuffer 操作内存与属性

如需进一步落地开发,建议直接参考官方提供的示例工程与API 文档。

完整的api使用案例

下面给出一个可直接编译运行的最小完整示例(Surface 模式,H.264 硬编码)。

代码仅保留主干逻辑,省略了异常处理的细节,方便你先把流程跑通;真正工程化时再加上日志、异常检查、线程同步等即可。

────────────────────────────

  1. 目录结构
css 复制代码
encode_demo/
 ├─ CMakeLists.txt
 └─ main.cpp

────────────────────────────

  1. CMakeLists.txt
cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(encode_demo)

set(CMAKE_CXX_STANDARD 17)

# HarmonyOS 标准 NDK 环境变量已配置好
add_executable(encode_demo main.cpp)

target_link_libraries(encode_demo
    libnative_media_codecbase.so
    libnative_media_core.so
    libnative_media_venc.so
    libnative_window.so          # OHNativeWindow
)

────────────────────────────

  1. main.cpp(Surface 模式完整示例)
cpp 复制代码
#include <chrono>
#include <fstream>
#include <iostream>
#include <thread>

#include <condition_variable>
#include <mutex>
#include <queue>
#include <shared_mutex>

#include <multimedia/player_framework/native_avcodec_videoencoder.h>
#include <multimedia/player_framework/native_avcapability.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_avformat.h>
#include <multimedia/player_framework/native_avbuffer.h>
#include <native_window/external_window.h>

using namespace std;

/* ---------- 1. 公共数据结构 ---------- */
struct CodecBufferInfo {
    uint32_t index;
    OH_AVBuffer *buffer;
    bool isValid;
    CodecBufferInfo(uint32_t i, OH_AVBuffer *b) : index(i), buffer(b), isValid(true) {}
};

class CodecBufferQueue {
public:
    void Enqueue(shared_ptr<CodecBufferInfo> info) {
        unique_lock<mutex> lock(mtx_);
        q_.push(info);
        cv_.notify_all();
    }
    shared_ptr<CodecBufferInfo> Dequeue(int ms = 1000) {
        unique_lock<mutex> lock(mtx_);
        cv_.wait_for(lock, chrono::milliseconds(ms), [this] { return !q_.empty(); });
        if (q_.empty()) return nullptr;
        auto ret = q_.front();
        q_.pop();
        return ret;
    }
    void Flush() {
        unique_lock<mutex> lock(mtx_);
        while (!q_.empty()) {
            q_.front()->isValid = false;
            q_.pop();
        }
    }
private:
    mutex mtx_;
    condition_variable cv_;
    queue<shared_ptr<CodecBufferInfo>> q_;
};

/* ---------- 2. 全局变量 ---------- */
OH_AVCodec *g_enc = nullptr;
OHNativeWindow *g_window = nullptr;
shared_mutex g_mtx;
CodecBufferQueue g_outQ;
bool g_run = true;

/* ---------- 3. 回调函数 ---------- */
void OnError(OH_AVCodec *, int32_t, void *) { cout << "Encoder error\n"; }
void OnStreamChanged(OH_AVCodec *, OH_AVFormat *, void *) {}
void OnNeedInputBuffer(OH_AVCodec *, uint32_t, OH_AVBuffer *, void *) {}
void OnNewOutputBuffer(OH_AVCodec *, uint32_t index, OH_AVBuffer *buf, void *) {
    g_outQ.Enqueue(make_shared<CodecBufferInfo>(index, buf));
}

/* ---------- 4. 工具函数 ---------- */
void Release() {
    unique_lock<shared_mutex> lock(g_mtx);
    if (g_enc) OH_VideoEncoder_Destroy(g_enc);
    g_enc = nullptr;
    if (g_window) OH_NativeWindow_DestroyNativeWindow(g_window);
    g_window = nullptr;
}

/* ---------- 5. 主流程 ---------- */
int main() {
    /* 5.1 创建编码器 */
    OH_AVCapability *cap =
        OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true, HARDWARE);
    const char *codecName = OH_AVCapability_GetName(cap);
    g_enc = OH_VideoEncoder_CreateByName(codecName);

    /* 5.2 注册回调 */
    OH_AVCodecCallback cb{OnError, OnStreamChanged, OnNeedInputBuffer, OnNewOutputBuffer};
    OH_VideoEncoder_RegisterCallback(g_enc, cb, nullptr);

    /* 5.3 配置编码参数 */
    OH_AVFormat *fmt = OH_AVFormat_Create();
    int w = 1280, h = 720;
    OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_WIDTH, w);
    OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_HEIGHT, h);
    OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_PIXEL_FORMAT, AV_PIXEL_FORMAT_NV12);
    OH_AVFormat_SetDoubleValue(fmt, OH_MD_KEY_FRAME_RATE, 30.0);
    OH_AVFormat_SetLongValue(fmt, OH_MD_KEY_BITRATE, 4'000'000);
    OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_I_FRAME_INTERVAL, 1000);
    OH_VideoEncoder_Configure(g_enc, fmt);
    OH_AVFormat_Destroy(fmt);

    /* 5.4 获取 Surface */
    OH_VideoEncoder_GetSurface(g_enc, &g_window);

    /* 5.5 准备与启动 */
    OH_VideoEncoder_Prepare(g_enc);
    OH_VideoEncoder_Start(g_enc);

    /* 5.6 打开输出文件 */
    ofstream out("out.h264", ios::binary);

    /* 5.7 模拟相机送帧线程(此处用随机数据填充) */
    thread producer([&] {
        int bufSize = w * h * 3 / 2;
        while (g_run) {
            OHNativeWindowBuffer *nwBuf;
            int releaseFence;
            OH_NativeWindow_NativeWindowRequestBuffer(g_window, &nwBuf, &releaseFence);
            void *virAddr;
            OH_NativeWindow_NativeWindowBufferHandle handle =
                OH_NativeWindow_GetBufferHandleFromNative(nwBuf);
            OH_NativeWindow_NativeWindowBufferMap(nwBuf, &virAddr);
            memset(virAddr, 0, bufSize); // 实际应填 NV12 数据
            OH_NativeWindow_NativeWindowBufferUnmap(nwBuf);
            OH_NativeWindow_NativeWindowFlushBuffer(g_window, nwBuf, -1,
                OH_NativeWindow_FlushMode::FLUSH_MODE_SYNC);
            this_thread::sleep_for(chrono::milliseconds(33));
        }
    });

    /* 5.8 接收编码码流 */
    while (g_run) {
        auto info = g_outQ.Dequeue();
        if (!info || !info->isValid) continue;
        OH_AVCodecBufferAttr attr;
        OH_AVBuffer_GetBufferAttr(info->buffer, &attr);
        uint8_t *data = reinterpret_cast<uint8_t *>(OH_AVBuffer_GetAddr(info->buffer));
        out.write(reinterpret_cast<char *>(data), attr.size);
        OH_VideoEncoder_FreeOutputBuffer(g_enc, info->index);
    }

    /* 5.9 结束清理 */
    OH_VideoEncoder_Stop(g_enc);
    g_run = false;
    producer.join();
    Release();
    return 0;
}

────────────────────────────

  1. 编译 & 运行(在 DevEco Studio 或 OHOS NDK 命令行均可)
bash 复制代码
cd encode_demo
mkdir build && cd build
cmake .. -DOHOS_STL=c++_shared -DCMAKE_TOOLCHAIN_FILE=$OHOS_NDK/build/cmake/ohos.toolchain.cmake
make -j

把生成的 encode_demo 推送到设备:

bash 复制代码
hdc file send encode_demo /data/local/tmp/
hdc shell chmod +x /data/local/tmp/encode_demo
hdc shell /data/local/tmp/encode_demo   # 运行

运行结束后,在 /data/local/tmp/out.h264 拿到编码结果,可 hdc file recv 回 PC 用 VLC/ffplay 播放验证。

────────────────────────────

  1. 如何替换成自己的 YUV 数据

producer 线程里的 memset 换成:

cpp 复制代码
fread(virAddr, 1, bufSize, yuvFp);

即可把本地 input.yuv 逐帧喂给 Surface,实现离线文件编码。

至此,一个可运行的完整 Surface 模式视频编码示例就完成了。

相关推荐
whysqwhw20 分钟前
鸿蒙AVSession Kit
harmonyos
whysqwhw2 小时前
鸿蒙各种生命周期
harmonyos
whysqwhw3 小时前
鸿蒙音频编码
harmonyos
whysqwhw3 小时前
鸿蒙音频解码
harmonyos
whysqwhw3 小时前
鸿蒙视频解码
harmonyos
ajassi20003 小时前
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器
华为·开源·harmonyos
前端世界4 小时前
在鸿蒙应用中快速接入地图功能:从配置到实战案例全解析
华为·harmonyos
江拥羡橙6 小时前
【基础-判断】HarmonyOS提供了基础的应用加固安全能力,包括混淆、加密和代码签名能力
安全·华为·typescript·harmonyos
前端世界17 小时前
HarmonyOS 实战:用 @Observed + @ObjectLink 玩转多组件实时数据更新
华为·harmonyos