普通视图

发现新文章,点击刷新页面。
昨天以前首页

鸿蒙模块间资源引用

作者 风冷
2025年8月24日 12:03

CrossModuleResourceAccess项目

跨模块资源访问-程序包结构-应用框架 - 华为HarmonyOS开发者

根据官方文档和项目实践,以下是关于跨模块资源访问的总结:

1. 跨模块资源访问的核心目标

  • 资源共享:通过 HAR(Harmony Archive)和 HSP(Harmony Shared Package)模块,实现资源(如文本、图片、样式等)的复用,减少冗余定义。
  • 模块化开发:支持功能模块的独立开发和维护,提升开发效率和代码可维护性。

2. 资源访问方式

  • 直接引用
    • 使用 $r('app.type.name')$rawfile('name') 访问当前模块资源。
    • 使用 $r('[hsp].type.name')$rawfile('[hsp].name') 访问 HSP 模块资源。
  • 动态 API 访问
    • 通过 resourceManager 接口(如 getStringSyncgetMediaContentSync)动态获取资源。
    • 使用 createModuleContext 创建其他模块的上下文,获取其 resourceManager 对象。

3. 资源优先级规则

  • 优先级从高到低
    1. 当前模块(HAP/HSP):自身模块的资源优先级最高。
    2. 依赖的 HAR/HSP 模块
      • 如果多个依赖模块中存在同名资源,按照依赖顺序覆盖(依赖顺序靠前的优先级更高)。

4. 官方文档补充

  • 资源隔离与访问控制
    • 类似腾讯云 CAM(访问管理)的权限设计,HarmonyOS 通过模块化设计实现资源的逻辑隔离。
    • 开发者可以通过显式依赖和资源命名规范避免冲突。
  • 跨模块通信
    • 除了资源访问,还可以通过模块间接口调用实现功能共享。

5. 最佳实践

  • 命名规范:为资源文件添加模块前缀(如 hsp1_icon.png),避免命名冲突。
  • 依赖管理:在 oh-package.json5 中明确模块依赖顺序,确保资源优先级符合预期。
  • 动态加载:对于插件化场景,优先使用 resourceManager 动态加载资源。

6. 适用场景

  • 多模块共享通用资源(如主题、图标、多语言文本)。
  • 动态加载不同模块的资源(如插件化设计)。

如果需要进一步分析具体实现或优化建议,请告诉我!

鸿蒙Flex与Row/Column对比

作者 风冷
2025年8月24日 11:59

在鸿蒙(HarmonyOS)应用开发中,Flex布局与Row/Column布局是两种核心的容器组件,它们在功能、性能及适用场景上存在显著差异。以下从五个维度进行详细对比:


📊 1. 核心差异对比

特性 Flex布局 Row/Column布局
布局机制 动态弹性计算,支持二次布局(重新分配空间) 单次线性排列,无二次布局
方向控制 支持水平(Row)、垂直(Column)及反向排列 Row仅水平,Column仅垂直
换行能力 支持自动换行(FlexWrap.Wrap 不支持换行,子组件溢出时被截断或压缩
子组件控制 支持flexGrowflexShrinkflexBasis动态分配空间 仅支持layoutWeight按比例分配空间
性能表现 较低(二次布局增加计算开销) 较高(单次布局完成)

⚠️ 二次布局问题:当子组件总尺寸与容器不匹配时,Flex需通过拉伸/压缩重新计算布局,导致性能损耗。


🔧 2. Flex布局的核心特点与场景

  • 核心优势

    • 多方向布局:通过direction自由切换主轴方向(水平/垂直)。

    • 复杂对齐:组合justifyContent(主轴)和alignItems(交叉轴)实现精准对齐。

    • 动态空间分配

      • flexGrow:按比例分配剩余空间(如搜索框占满剩余宽度)。
      • flexShrink:空间不足时按比例压缩子组件(需配合minWidth避免过度压缩)。
  • 必用场景

    • 多行排列:标签组、商品网格布局(需设置wrap: FlexWrap.Wrap)。
    • 响应式适配:跨设备屏幕(如手机/车机动态调整列数)。

📐 3. Row/Column布局的核心特点与场景

  • 核心优势

    • 轻量高效:线性排列无弹性计算,渲染性能更高。

    • 简洁属性

      • space:控制子组件间距(如导航栏按钮间隔)。
      • layoutWeight:一次遍历完成空间分配(性能优于flexGrow)。
  • 推荐场景

    • 单向排列

      • Row:水平导航栏、头像+文字组合。
      • Column:垂直表单、卡片内容堆叠。
    • 固定尺寸布局:子组件尺寸明确时(如按钮宽度固定)。


4. 性能差异与优化建议

  • Flex性能瓶颈

    • 二次布局触发条件:子组件总尺寸 ≠ 容器尺寸、优先级冲突(如displayPriority分组计算)。
    • 后果:嵌套过深或动态数据下易引发界面卡顿。
  • 优化策略

    • 替代方案:简单布局优先用Row/Column,避免Flex嵌套超过3层。

    • 属性优化

      • 固定尺寸组件设置flexShrink(0)禁止压缩。
      • 等分布局用layoutWeight替代flexGrow(如Row中占比1:2)。
    • 预设尺寸:尽量让子组件总尺寸接近容器尺寸,减少拉伸需求。


🛠️ 5. 选择策略与工程实践

  • 何时选择Flex?

    ✅ 需换行(如标签云)、复杂弹性对齐(如交叉轴居中)、动态网格布局。

    ❌ 避免在简单列表、表单等场景使用,优先Row/Column。

  • 何时选择Row/Column?

    ✅ 单向排列(水平/垂直)、子组件尺寸固定或比例明确(如30%+70%)。

    ✅ 高频场景:导航栏(Row)、表单(Column)、图文混排(Row+垂直居中)。

  • 工程最佳实践

    • 多端适配:通过DeviceType动态调整参数(如车机增大点击区域)。
    • 调试工具:用DevEco Studio布局分析器监测二次布局次数。
    • 混合布局:Flex内嵌套Row/Column(如Flex容器中的商品项用Column)。

💎 总结

  • Flex:强大但“重”,适合复杂弹性多行响应式布局,需警惕二次布局问题。

  • Row/Column:轻量高效,是单向排列场景的首选,性能优势明显。

  • 决策关键

    简单布局看方向(水平用Row,垂直用Column),

    复杂需求看弹性(换行/动态分配用Flex)。

通过合理选择组件并优化属性配置,可显著提升鸿蒙应用的渲染效率与用户体验。

鸿蒙AVSession Kit

作者 风冷
2025年8月17日 15:28

—— ArkTS 与 C/C++ 双语言、Provider 与 Controller 全角色

适用版本:HarmonyOS API 12+

覆盖:本地会话提供方(Provider)、会话控制方(Controller)、后台播放


一、设计总览(Mermaid)

graph TD
    subgraph 系统
        PC[播控中心/耳机/车机]
    end
    subgraph 应用进程
        A[AVSessionProvider<br>播放器] -->|setAVMetadata<br>setAVPlaybackState| S[AVSession]
        C[AVSessionController<br>控制逻辑] -->|sendControlCommand| S
    end
    PC <-->|控制命令| S
  • 任何进程都能 读 会话(获取元数据/状态)
  • 任何进程都能 写 会话(发命令)——只要拿到对应 AVSessionController

二、ArkTS 完整落地

  1. 依赖
import { avSession } from '@kit.AVSessionKit';
import { BackgroundTasks } from '@kit.BackgroundTasksKit';
  1. 会话提供方(Provider)
class MyProvider {
  private session: avSession.AVSession | null = null;

  async init(ctx: Context) {
    /* 1. 长时任务 */
    await BackgroundTasks.startBackgroundRunning(ctx, 'AUDIO_PLAYBACK', 'music');

    /* 2. 创建会话 */
    this.session = await avSession.createAVSession(ctx, 'MusicSession', 'audio');

    /* 3. 元数据 */
    await this.session.setAVMetadata({
      assetId: '10086',
      title: 'Bohemian Rhapsody',
      artist: 'Queen',
      duration: 355000,
      mediaImage: 'https://xx/cover.jpg',
      previousAssetId: '10085',
      nextAssetId: '10087'
    });

    /* 4. 播放状态 */
    await this.session.setAVPlaybackState({
      state: avSession.PlaybackState.PLAYBACK_STATE_PLAYING,
      position: { elapsedTime: 60000, updateTime: Date.now() },
      speed: 1.0,
      loopMode: avSession.LoopMode.LOOP_MODE_LIST
    });

    /* 5. 注册命令(被动) */
    this.session.on('play',     () => this.player.play());
    this.session.on('pause',    () => this.player.pause());
    this.session.on('seek',     (pos: number) => this.player.seek(pos));
    this.session.on('playNext', () => this.player.next());

    /* 6. 激活 */
    await this.session.activate();
  }

  private player = { play:()=>{}, pause:()=>{}, seek:()=>{}, next:()=>{} };

  async destroy() {
    this.session?.destroy();
    BackgroundTasks.stopBackgroundRunning(getContext(this));
  }
}
  1. 会话控制方(Controller)
class MyController {
  private ctrl: avSession.AVSessionController | null = null;

  async attach(sessionId: string) {
    this.ctrl = await avSession.createController(sessionId);
  }

  async play()  { await this.ctrl?.sendControlCommand({ command: 'play' }); }
  async pause() { await this.ctrl?.sendControlCommand({ command: 'pause' }); }
  async seek(ms: number) {
    await this.ctrl?.sendControlCommand({ command: 'seek', parameters: { position: ms } });
  }
}
  1. 枚举会话
const sessions = await avSession.getAllActiveSessions();
console.log('当前活跃会话', sessions.map(s => s.sessionTag));

三、C/C++(NDK)完整落地

  1. CMake
find_library(avsession-lib libohavsession.so)
target_link_libraries(your_target ${avsession-lib})
  1. 会话提供方(Provider)
// 1. 创建
OH_AVSession* session;
OH_AVSession_Create("MusicSess", SESSION_TYPE_AUDIO,
                    "com.demo", "MainAbility", &session);

// 2. 元数据
OH_AVMetadataBuilder* mb = OH_AVMetadataBuilder_Create();
OH_AVMetadataBuilder_SetAssetId(mb, "10086");
OH_AVMetadataBuilder_SetTitle(mb, "Rhapsody");
OH_AVMetadata* meta = OH_AVMetadataBuilder_Build(mb);
OH_AVSession_SetAVMetadata(session, meta);

// 3. 播放状态
OH_AVPlaybackState* ps = OH_AVPlaybackState_Create();
OH_AVPlaybackState_SetState(ps, PLAYBACK_STATE_PLAYING);
OH_AVSession_SetAVPlaybackState(session, ps);

// 4. 注册命令
OH_AVSession_RegisterCommandCallback(
    session, COMMAND_PLAY,
    [](void*){ /* real play */ }, nullptr);

OH_AVSession_Activate(session);
  1. 会话控制方(Controller)
// 枚举
AVSession_SessionInfo* infos;
size_t cnt;
OH_AVSessionManager_GetAllActiveSessions(&infos, &cnt);

// 创建 Controller
OH_AVSessionController* ctrl;
OH_AVSessionController_Create(infos[0].sessionId, &ctrl);

// 发命令
OH_AVControllerCommand cmd = { .command = COMMAND_SEEK, .parameters.position = 30000 };
OH_AVSessionController_SendCommand(ctrl, &cmd);
  1. 清理
OH_AVSessionController_Destroy(ctrl);
OH_AVSession_Destroy(session);

四、命令全集(两语言通用)

命令ArkTS 参数C++ 枚举 play{ command:'play' }COMMAND_PLAY pause{ command:'pause' }COMMAND_PAUSE stop{ command:'stop' }COMMAND_STOP seek{ command:'seek', parameters:{ position } }COMMAND_SEEK setSpeed{ command:'setSpeed', parameters:{ speed } }COMMAND_SET_SPEED setLoopMode{ command:'setLoopMode', parameters:{ loopMode } }COMMAND_SET_LOOP_MODE toggleFavorite{ command:'toggleFavorite' }COMMAND_TOGGLE_FAVORITE playNext / playPrevious{ command:'playNext'/'playPrevious' }COMMAND_PLAY_NEXT / PREVIOUS skipToQueueItem{ command:'skipToQueueItem', parameters:{ itemId } }COMMAND_SKIP_TO_QUEUE_ITEM commonCommand{ command:'commonCommand', parameters:{ command, extras } }COMMAND_COMMON


五、后台播放 checklist(上架必过)

  • createAVSession 的 type 与音频流类型匹配(audio / video / voice_call)
  • 激活前已 setAVMetadata + setAVPlaybackState + 注册命令
  • 已申请 AUDIO_PLAYBACK 长时任务且 pause/stop 时主动取消
  • 退出业务时 destroy 会话
  • 若支持歌单/冷启动续播,已注册 PlayMusicList / PlayAudio 意图

至此,ArkTS 与 C/C++ 双语言、Provider 与 Controller 全角色一次讲透。

鸿蒙各种生命周期

作者 风冷
2025年8月17日 13:35

说真的记不住,用的时候自然会

层级 对象 主要回调 触发时机一句话
UI-组件 @Component aboutToAppear → build → onDidBuild → onReady → (onWillDestroy) → onDestroy → aboutToDisappear 创建到销毁完整链路
UI-页面 @Entry onPageShow / onPageHide / onBackPress 页面可见/隐藏/返回键
UI-窗口 WindowStage onWindowStageCreate / Destroy / Show / Hide 多窗口/悬浮窗
UI-复用 @Reusable aboutToReuse / aboutToRecycle 列表滚动复用池
Ability UIAbility onCreate → onWindowStageCreate → onForeground → onBackground → onWindowStageDestroy → onDestroy Stage 模型
Ability ServiceAbility onStart / onCommand / onConnect / onDisconnect / onStop 启动/绑定/停止
Ability DataAbility onInitialize + CRUD 跨应用数据共享
Ability FormAbility onCreateForm / onUpdateForm / onDeleteForm… 桌面卡片
扩展点 ExtensionAbility onCreate → 业务回调 → onDestroy 输入法/分享/打印/VoIP…
模块级 AbilityStage onCreate → onAcceptWant → onConfigurationUpdated → onMemoryLevel → onDestroy 每个 HAP 一个实例
并发 Worker onCreate → onMessage → onError → onDestroy 耗时计算
并发 TaskDispatcher onStart / onProgress / onComplete / onFail 轻量并发
硬件 Camera onCreate → onCaptureStarted/Ended → onRelease 相机
硬件 AudioRenderer onStateChange / onMarkReach / onPeriodReach 音频播放
硬件 Sensor onSensorChanged / onAccuracyChanged 传感器
图形 XComponent onSurfaceCreated / Changed / Destroyed Native 渲染
分布式 Continuation onStartContinuation → onSaveData → onRestoreData → onCompleteContinuation 跨设备迁移
分布式 RemoteAbility ServiceAbility(但跑在远端) 分布式后台
Native NativeWindow Create → … → Destroy C++ 层图形
Native NativeAudioRenderer Create → … → Release C++ 层音频
系统事件 netAvailable / lowBattery / orientationChange 全局广播
调试 HotReload / HiTrace 热重载/性能追踪

鸿蒙音频编码

作者 风冷
2025年8月17日 12:32

【HarmonyOS 音频编码开发速览】

文档地址:developer.huawei.com/consumer/cn…

  1. 能力总览

    • 将任意来源的 PCM 数据编码为 AAC、FLAC、MP3、G.711μ、OPUS、AMR-NB/WB 等格式。

    • 典型使用场景:录音后封装、PCM 编辑后导出文件。

    • AAC 默认使用 VBR,码率可能与设定值存在偏差。

  2. 核心流程(12 步)

    1. 引入头文件。

    2. 创建编码器(OH_AVCodec):可按 MIME 或编解码器名称创建。

    3. 注册回调:

      • onError、onNeedInputBuffer、onNewOutputBuffer(onStreamChanged 暂不支持)。
    4. 配置参数:

      必须项:采样率、声道数、采样格式;

      可/必须项:码率、声道布局、ADTS、Profile 等,各格式差异见表。

    5. Prepare → 6) Start → 7) 逐帧输入 PCM(需按格式计算单次样点数) →

    6. 取出码流 → 9) FreeOutputBuffer →

    10)(可选)Flush / Reset → 11) Stop → 12) Destroy。

  3. 关键参数表

    • 采样率/声道数范围:AAC-LC 8–96 kHz、1–8 ch;FLAC 8–96 kHz、1–8 ch;MP3 8–48 kHz、1–2 ch;OPUS 8–48 kHz、1–2 ch;AMR-NB 8 kHz/1 ch,AMR-WB 16 kHz/1 ch。

    • 单次输入样点数:AAC-LC=1024;HE-AAC=2048;FLAC 按采样率查表(如 44.1 kHz→4608)。

  4. 开发注意

    • 必须按顺序调用 API,否则可能异常。

    • 最后一帧需置 AVCODEC_BUFFER_FLAGS_EOS。

    • 调用 Flush/Reset/Stop 后需重新 Start 并可能重新输入数据。

以下 Mermaid 流程图对应「最开始回答」中的 12 步开发顺序,可直接复制到 Mermaid Live Editor 渲染。

图中:

  • 实线框为「必选步骤」
  • 虚线框为「可选步骤」
  • 箭头旁文字为调用接口名
%% HarmonyOS 音频编码 12 步调用流程(对应官方文档顺序)
flowchart TD
    A([开始]) --> B[1. 添加头文件\n#include]
    B --> C[2. 创建编码器\nOH_AudioCodec_CreateByMime / CreateByName]
    C --> D[3. 注册回调\nOH_AudioCodec_RegisterCallback]
    D --> E[4. 配置参数\nOH_AudioCodec_Configure]
    E --> F[5. 就绪\nOH_AudioCodec_Prepare]
    F --> G[6. 启动\nOH_AudioCodec_Start]
    G --> H[7. 循环输入 PCM\nOH_AudioCodec_PushInputBuffer]
    H --> I[8. 取出码流\nOH_AudioCodec_FreeOutputBuffer]

    I --> J{是否继续?}
    J -->|是| H
    J -->|否| K[11. 停止\nOH_AudioCodec_Stop]

    %% 可选分支
    H -.->|9. 刷新队列| L[OH_AudioCodec_Flush]
    L -.-> G
    K -.->|10. 重置| M[OH_AudioCodec_Reset]
    M -.-> E
    K --> N[12. 销毁\nOH_AudioCodec_Destroy]

    N --> Z([结束])

下面给出

  1. 用 Mermaid 绘制的「音频编码状态/调用流程图」
  2. 一份可直接在 DevEco Studio 4.0(API 10+)里跑的 C++ 最小完整示例(CMake 工程),演示把 44.1 kHz/16-bit/立体声 PCM 编码为 AAC-LC(ADTS)并写入本地文件。

  1. Mermaid 图
%% 音频编码器状态机 + 调用时序(HarmonyOS AVCodec)
stateDiagram-v2
    [*] --> Init: OH_AudioCodec_CreateByMime(...)
    Init --> Configured: Configure(params)
    Configured --> Prepared: Prepare()
    Prepared --> Running: Start()

    Running --> Running: PushInputBuffer() ➜ FreeOutputBuffer()
    Running --> Flushed: Flush()        %% 可选
    Flushed --> Running: Start()

    Running --> Stopped: Stop()
    Stopped --> Prepared: Start()      %% 可再次启动
    Stopped --> Reset: Reset()         %% 可选
    Reset --> Configured: Configure()

    Running --> [*]: Destroy()
    Stopped --> [*]: Destroy()
    Reset --> [*]: Destroy()

  1. 最小可编译示例

目录结构

AudioEncoderDemo/
 ├─ entry/
 │   ├─ src/main/cpp/
 │   │   ├─ native_audio_encoder.cpp (下面代码)
 │   │   └─ CMakeLists.txt
 │   └─ src/main/resources/rawfile/
 │       └─ test_44k_16bit_2ch.pcm   (原始 PCM, 任意长度)

2.1 CMakeLists.txt

cmake_minimum_required(VERSION 3.16)
project(audio_encoder_demo)

set(CMAKE_CXX_STANDARD 17)

# HarmonyOS NDK
find_library(hilog-lib hilog_ndk.z)
find_library(native-buffer-lib native_buffer)
find_library(avcodec-lib libavcodec_base.z)

add_library(entry SHARED native_audio_encoder.cpp)
target_link_libraries(entry
        ${hilog-lib}
        ${native-buffer-lib}
        ${avcodec-lib}
        ohaudio
)

2.2 native_audio_encoder.cpp

#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>

#include "napi/native_api.h"
#include "multimedia/audio_codec/audio_codec_api.h"
#include "hilog/log.h"

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x0001
#define LOG_TAG "AudioEncoder"

static const int SAMPLE_RATE   = 44100;
static const int CHANNEL_COUNT = 2;
static const int BIT_RATE      = 128000; // 128 kbps
static const int PCM_FRAME_SAMPLES = 1024; // AAC-LC 每帧 1024 样点
static const int PCM_FRAME_BYTES =
        PCM_FRAME_SAMPLES * CHANNEL_COUNT * sizeof(int16_t);

static OH_AVCodec *g_encoder = nullptr;
static int32_t g_fd_out = -1;          // 输出 ADTS 文件描述符
static bool g_input_done = false;

/* ---------- 工具 ---------- */
static void write_adts_header(uint8_t *buf, int frameLen) {
    const int profile = 2;     // AAC-LC
    const int freqIdx = 4;     // 44.1 kHz
    const int chanCfg = 2;     // 2 ch

    int fullLen = frameLen + 7;
    buf[0] = 0xFF;
    buf[1] = 0xF1;
    buf[2] = (profile - 1) << 6 | (freqIdx << 2) | (chanCfg >> 2);
    buf[3] = ((chanCfg & 3) << 6) | (fullLen >> 11);
    buf[4] = (fullLen >> 3) & 0xFF;
    buf[5] = ((fullLen & 7) << 5) | 0x1F;
    buf[6] = 0xFC;
}

/* ---------- 回调 ---------- */
static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData) {
    OH_LOG_ERROR(LOG_APP, "Encoder error %{public}d", errorCode);
}

static void OnOutputFormatChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) {
    // AAC 暂未支持
}

static void OnNeedInputBuffer(OH_AVCodec *codec,
                              uint32_t index,
                              OH_AVBuffer *buffer,
                              void *userData) {
    if (g_input_done) return;

    uint8_t *addr = OH_AVBuffer_GetAddr(buffer);
    int32_t capacity = OH_AVBuffer_GetCapacity(buffer);
    ssize_t bytes = read(0, addr, capacity); // 从 stdin 读 PCM
    if (bytes <= 0) {
        OH_AudioCodec_Stop(codec);
        g_input_done = true;
        return;
    }
    OH_AVCodec_PushInputBuffer(codec, index);
}

static void OnNewOutputBuffer(OH_AVCodec *codec,
                              uint32_t index,
                              OH_AVBuffer *buffer,
                              OH_AVFormat *attr,
                              void *userData) {
    uint8_t *data = OH_AVBuffer_GetAddr(buffer);
    int32_t size  = OH_AVBuffer_GetSize(buffer);

    uint8_t adts[7];
    write_adts_header(adts, size);
    write(g_fd_out, adts, 7);
    write(g_fd_out, data, size);

    OH_AudioCodec_FreeOutputBuffer(codec, index);
}

static OH_AVCodecCallback g_callback = {
        .onError = OnError,
        .onStreamChanged = OnOutputFormatChanged,
        .onNeedInputBuffer = OnNeedInputBuffer,
        .onNewOutputBuffer = OnNewOutputBuffer,
};

/* ---------- NAPI 接口 ---------- */
static napi_value EncodeFile(napi_env env, napi_callback_info info) {
    g_encoder = OH_AudioCodec_CreateByMime(OH_AVCODEC_MIMETYPE_AUDIO_AAC, true);
    if (!g_encoder) {
        OH_LOG_ERROR(LOG_APP, "Create encoder failed");
        return nullptr;
    }

    OH_AVFormat *fmt = OH_AVFormat_Create();
    OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_AUD_SAMPLE_RATE, SAMPLE_RATE);
    OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_AUD_CHANNEL_COUNT, CHANNEL_COUNT);
    OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_S16LE);
    OH_AVFormat_SetLongValue(fmt, OH_MD_KEY_BITRATE, BIT_RATE);
    OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_AAC_IS_ADTS, 1); // 输出 ADTS

    OH_AudioCodec_RegisterCallback(g_encoder, &g_callback, nullptr);
    OH_AudioCodec_Configure(g_encoder, fmt);
    OH_AVFormat_Destroy(fmt);

    OH_AudioCodec_Prepare(g_encoder);

    g_fd_out = open("/data/storage/el2/base/haps/entry/files/out.aac",
                    O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (g_fd_out < 0) {
        OH_LOG_ERROR(LOG_APP, "open output failed");
        return nullptr;
    }

    OH_AudioCodec_Start(g_encoder);
    while (!g_input_done) {
        usleep(10 * 1000); // 简单阻塞等待
    }

    OH_AudioCodec_Stop(g_encoder);
    OH_AudioCodec_Destroy(g_encoder);
    close(g_fd_out);
    OH_LOG_INFO(LOG_APP, "encode done");
    return nullptr;
}

/* ---------- 注册 NAPI ---------- */
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"encodeFile", nullptr, EncodeFile, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = nullptr,
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule() {
    napi_module_register(&demoModule);
}

2.3 使用方式

  1. test_44k_16bit_2ch.pcm 推送到 /data/storage/el2/base/haps/entry/files/in.pcm
  2. ArkTS 侧调用:
import entry from '@ohos.entry';
entry.encodeFile();
  1. 运行后可在同目录得到 out.aac(ADTS 封装,可直接播放验证)。

至此,完整的 Mermaid 流程图 + 可跑代码实现已给出,可直接集成到 HarmonyOS 工程中。

鸿蒙音频解码

作者 风冷
2025年8月17日 12:32

以下是对鸿蒙音频解码模块的总结,包含核心流程、API设计与使用要点,以及Mermaid示意图:

一、鸿蒙音频解码核心流程

以下内容基于官方文档,用一张 Mermaid 时序图 把「鸿蒙音频解码(AVCodec Kit)」的核心流程、API 设计与使用要点一次性梳理出来。可直接复制到 mermaid.live 预览。

仅保留开发者最常调用的接口与顺序,省略了可选项与异常分支,方便快速上手。

%% 鸿蒙音频解码(AVCodec Kit)API 调用时序
sequenceDiagram
    participant App as 应用层
    participant Codec as OH_AVCodec* 解码器实例
    participant PCM as 音频输出(PCM)

    Note over App,PCM: 1. 准备阶段
    App ->> App: 1.1 添加头文件 & CMake 链接 libnative_media_codecbase.so
    App ->> Codec: 1.2 OH_AudioCodec_CreateByMime("audio/mp4a-latm") 或通过 codecName
    App ->> Codec: 1.3 OH_AudioCodec_RegisterCallback(cb) 注册 4 个回调
    App ->> Codec: 1.4 OH_AudioCodec_Configure(cfg) 配置采样率/声道/格式
    App ->> Codec: 1.5 OH_AudioCodec_Prepare()
    App ->> Codec: 1.6 OH_AudioCodec_Start()

    Note over App,PCM: 2. 运行阶段
    loop 解码循环
        Codec -->> App: OH_AVCodecOnNeedInputBuffer(idx, buf)
        App ->> Codec: OH_AudioCodec_PushInputBuffer(idx, buf, size, flags)
        Codec -->> App: OH_AVCodecOnNewOutputBuffer(idx, info, buf)
        App ->> PCM: 取走 PCM 数据
        App ->> Codec: OH_AudioCodec_FreeOutputBuffer(idx)
    end

    Note over App,PCM: 3. 结束阶段
    App ->> Codec: OH_AudioCodec_Stop()
    App ->> Codec: OH_AudioCodec_Destroy()
  1. 支持的输入格式

    AAC、FLAC、MP3、Vorbis、G711、AMR、APE、Opus、Audio Vivid 等,具体采样率/声道范围见文档表格。

  2. PCM 输出格式

    通过 OH_MD_KEY_AUDIO_SAMPLE_FORMAT 可选 SAMPLE_S16LESAMPLE_F32LE,默认 S16LE。

  3. 线程模型

    所有回调都在内部工作线程,请勿阻塞;应用需要保证线程安全。

  4. DRM 解密

    若内容加密,需在 Prepare 前调用 OH_AudioCodec_SetDecryptionConfig,并在 PushInputBuffer 时把 cencInfo 通过 OH_AVCencInfo_SetAVBuffer 写入。

  5. EOS 处理

    输入最后一包数据时把 flags 设为 AVCODEC_BUFFER_FLAGS_EOS,解码器会在回调中同样给出 EOS,应用即可进入停止流程。

  6. CMake 链接

   target_link_libraries(xxx
       native_media_codecbase.so
   )

一句话总结
「鸿蒙音频解码」遵循 创建→配置→启动→循环喂数据→取 PCM→停止销毁 的极简五步模型;所有细节都围绕 OH_AVCodec* 句柄与 4 个回调完成,无需额外线程或同步,直接嵌入现有播放 / 编辑管线即可。

audio-decode.png

二、API设计与使用要点

1. 核心API组件

组件类型 关键API 作用说明
解码器实例 OH_AudioCodec_CreateByMime() 通过MIME类型创建解码器
OH_AudioCodec_CreateByName() 通过编解码器名称创建
回调注册 OH_AudioCodec_RegisterCallback() 注册错误/数据流/缓冲区回调
参数配置 OH_AudioCodec_Configure() 设置采样率/声道数等参数
运行时控制 OH_AudioCodec_Start() 启动解码器
OH_AudioCodec_Flush() 刷新缓冲区(可选)
OH_AudioCodec_Reset() 重置解码器(可选)
数据操作 OH_AudioCodec_PushInputBuffer() 送入压缩数据
OH_AudioCodec_FreeOutputBuffer() 释放PCM输出数据
资源管理 OH_AudioCodec_Destroy() 销毁解码器实例

2. 关键开发步骤

  1. 创建实例

    // 通过MIME创建
    OH_AVCodec *decoder = OH_AudioCodec_CreateByMime(OH_AVCODEC_MIMETYPE_AUDIO_AAC, false);
    
    // 或通过名称创建
    const char *name = OH_AVCapability_GetName(capability);
    OH_AVCodec *decoder = OH_AudioCodec_CreateByName(name);
    
  2. 注册回调

    OH_AVCodecCallback cb = {OnError, OnOutputFormatChanged, 
                            OnInputBufferAvailable, OnOutputBufferAvailable};
    OH_AudioCodec_RegisterCallback(decoder, cb, userData);
    
  3. 配置参数

    OH_AVFormat *format = OH_AVFormat_Create();
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, 44100);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, 2);
    OH_AudioCodec_Configure(decoder, format);
    
  4. **数据流处理

    • 输入:在OnInputBufferAvailable回调中填充压缩数据

      OH_AVBuffer_SetBufferAttr(buffer, &attr); // 设置PTS/flag等属性
      OH_AudioCodec_PushInputBuffer(decoder, index);
      
    • 输出:在OnOutputBufferAvailable中获取PCM数据

      OH_AVBuffer_GetBufferAttr(data, &attr); // 获取解码数据属性
      OH_AudioCodec_FreeOutputBuffer(decoder, index); // 释放缓冲区
      

3. 特殊功能支持

  • DRM解密

    OH_AudioCodec_SetDecryptionConfig(decoder, session, false);
    OH_AVCencInfo_SetAVBuffer(cencInfo, buffer); // 设置加密信息
    
  • Audio Vivid元数据

    OH_AVFormat_GetBuffer(format, OH_MD_KEY_AUDIO_VIVID_METADATA, &metadata, &metaSize);
    

三、重要注意事项

  1. 调用顺序强制要求

    创建 → 配置 → 准备 → 启动必须顺序执行,否则引发异常

  2. 解码格式限制

    • 支持AAC/FLAC/MP3/Vorbis等主流格式
    • 不同格式有特定参数要求(如Vorbis需ID Header)
  3. 资源释放

    OH_AudioCodec_Stop(decoder);   // 先停止
    OH_AudioCodec_Destroy(decoder); // 再销毁
    
  4. 动态库依赖

    target_link_libraries(sample 
        libnative_media_codecbase.so
        libnative_media_core.so
        libnative_media_acodec.so)
    

文档链接音频解码-音视频编解码-AVCodec Kit(音视频编解码服务)-媒体 - 华为HarmonyOS开发者

鸿蒙视频解码

作者 风冷
2025年8月17日 12:27
%% 鸿蒙 AVCodec Kit – 视频解码全景图
%% 开发者:Kimi 2024-08-16
%% 说明:一张图读懂“如何调用 Native API 完成视频解码”

flowchart TD
    subgraph 应用侧
        A([应用]) -->|1. 创建| B[OH_VideoDecoder_CreateByMime]
        A -->|2. 注册回调| C[OH_VideoDecoder_RegisterCallback]
        A -->|3. 配置| D[OH_VideoDecoder_Configure]
        A -->|4. 设 Surface| E[OH_VideoDecoder_SetSurface]:::surface
        A -->|5. 就绪| F[OH_VideoDecoder_Prepare]
        A -->|6. 启动| G[OH_VideoDecoder_Start]
    end

    subgraph 输入线程
        H([码流线程]) -->|7. 送入| I[OH_VideoDecoder_PushInputBuffer]
        I -->|携带 SPS/PPS| J{{AnnexB 格式}}
    end

    subgraph 解码内核
        K([解码器]) -->|8. 解码| L[YUV Frame]
        K -->|事件| M>回调]
        M -->|OnNeedInputBuffer| N[继续送码流]
        M -->|OnNewOutputBuffer| O[拿到输出 buffer]
        M -->|OnStreamChanged| P[分辨率变化]
        M -->|OnError| Q[错误处理]
    end

    subgraph 输出线程
        direction TB
        R([渲染线程]) -->|9a. Surface 送显| S[OH_VideoDecoder_RenderOutputBuffer]:::surface
        R -->|9b. 丢弃帧| T[OH_VideoDecoder_FreeOutputBuffer]
        R -->|9c. Buffer 保存| U[写 YUV 文件]
        style U fill:#f9f,stroke:#333
    end

    subgraph 生命周期
        V([Flush]) -->|清缓存| W[重新 Start]
        X([Reset]) -->|回初始态| Y[重新 Configure]
        Z([Stop]) -->|暂停| G
        AA([Destroy]) -->|释放| BB([Released])
    end

    %% 状态机泳道
    subgraph 状态机
        direction LR
        I1((Initialized)) -->|Configure| C1((Configured))
        C1 -->|Prepare| P1((Prepared))
        P1 -->|Start| E1((Executing))
        E1 -->|Flush| F1((Flushed))
        E1 -->|EOS| EOS((End-of-Stream))
        E1 -->|Error| ERR((Error))
        ERR -->|Reset| I1
        ERR -->|Destroy| R1((Released))
    end

    %% 样式
    classDef surface fill:#c2e0c6,stroke:#006100
    classDef buffer  fill:#fce8b4,stroke:#8f7f00
    classDef life    fill:#d5e8d4,stroke:#82b366

核心 API 设计速查表

阶段 关键函数 说明
创建 OH_VideoDecoder_CreateByMime()/ CreateByName() 按指定格式(MIME类型)或解码器名称创建解码器实例
配置 OH_VideoDecoder_Configure() 设置视频宽高、色彩空间、低时延模式等核心参数
绑定 OH_VideoDecoder_SetSurface() 关联 OHNativeWindow(通常来自XComponent或OpenGL渲染表面)
就绪 OH_VideoDecoder_Prepare() 内部预分配编解码资源,完成初始化准备
启动 OH_VideoDecoder_Start() 启动解码器,进入运行(Running)状态
送码流 OH_VideoDecoder_PushInputBuffer() 输入AnnexB格式的码流数据,需整帧一次性送入
取帧 OH_VideoDecoder_RenderOutputBuffer() 将解码帧送入绑定的Surface进行渲染(Surface模式)
OH_VideoDecoder_FreeOutputBuffer() 释放内存或存储YUV数据(Buffer模式)
生命周期 OH_VideoDecoder_Flush() 清空缓存中的输入/输出数据
OH_VideoDecoder_Reset() 重置解码器至初始状态
OH_VideoDecoder_Stop() 暂停解码操作,保留资源配置
OH_VideoDecoder_Destroy() 彻底释放解码器实例资源

模式差异一句话

  • Surface 模式:直接送显,性能高;支持动态切换 Surface;支持 DRM 安全通路。
  • Buffer 模式:拿到共享内存 YUV,可二次处理;仅非安全通路;需手动拷贝对齐数据。

常见坑提示

  1. Flush/Reset/Stop 后需重新送 SPS/PPS。
  2. Destroy 不能在回调线程里调。
  3. Buffer 模式必须 FreeOutputBuffer,否则阻塞后续输出。
  4. 多解码器共用同一 NativeWindow 时,先释放再启动,避免画面卡死。

鸿蒙视频编码

作者 风冷
2025年8月17日 12:27

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


✅ 一、能力概览

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


✅ 二、输入模式对比

模式数据来源适用场景性能接口差异 SurfaceOHNativeWindow相机等实时流高使用 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 链接库

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 获取 SurfaceOH_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. 目录结构
encode_demo/
 ├─ CMakeLists.txt
 └─ main.cpp

────────────────────────────
2. CMakeLists.txt

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
)

────────────────────────────
3. main.cpp(Surface 模式完整示例)

#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;
}

────────────────────────────
4. 编译 & 运行(在 DevEco Studio 或 OHOS NDK 命令行均可)

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 推送到设备:

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 播放验证。

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

producer 线程里的 memset 换成:

fread(virAddr, 1, bufSize, yuvFp);

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

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

❌
❌