鸿蒙视频编码

以下为对 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 模式视频编码示例就完成了。

相关推荐
Georgewu2 小时前
【HarmonyOS 6】 The target can not be empty. check the build.profile,json5 file of
harmonyos
Georgewu2 小时前
【HarmonyOS 6】Install Failed: error: failed to install bundle.code:9568322
harmonyos
爱笑的眼睛114 小时前
HarmonyOS 应用开发新范式:深入剖析 Stage 模型与 ArkTS 状态管理
华为·harmonyos
爱笑的眼睛115 小时前
深入浅出 HarmonyOS ArkUI 3.0:基于声明式开发范式与高级状态管理构建高性能应用
华为·harmonyos
程序员潘Sir8 小时前
鸿蒙应用开发从入门到实战(一):鸿蒙应用开发概述
harmonyos
敲代码的鱼哇11 小时前
跳转原生系统设置插件 支持安卓/iOS/鸿蒙UTS组件
android·ios·harmonyos
在下历飞雨12 小时前
Kuikly基础之状态管理与数据绑定:让“孤寡”计数器动起来
ios·harmonyos
在下历飞雨12 小时前
Kuikly基础之Kuikly DSL基础组件实战:构建青蛙主界面
ios·harmonyos
HarmonyOS小助手14 小时前
HEIF:更高质量、更小体积,开启 HarmonyOS 图像新体验
harmonyos·鸿蒙·鸿蒙生态
self_myth15 小时前
[特殊字符] 深入理解操作系统核心特性:从并发到分布式,从单核到多核的全面解析
windows·macos·wpf·harmonyos