普通视图
鸿蒙图标快捷菜单
HarmonyOS_Samples/DesktopShortcut
桌面快捷方式-程序包结构-应用框架 - 华为HarmonyOS开发者
直接看官方文档
1、快捷菜单返回回到主页面。 2、分层实现复杂底部菜单栏。
鸿蒙模块间资源引用
跨模块资源访问-程序包结构-应用框架 - 华为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
接口(如getStringSync
、getMediaContentSync
)动态获取资源。 - 使用
createModuleContext
创建其他模块的上下文,获取其resourceManager
对象。
- 通过
3. 资源优先级规则
-
优先级从高到低:
- 当前模块(HAP/HSP):自身模块的资源优先级最高。
-
依赖的 HAR/HSP 模块:
- 如果多个依赖模块中存在同名资源,按照依赖顺序覆盖(依赖顺序靠前的优先级更高)。
4. 官方文档补充
-
资源隔离与访问控制:
- 类似腾讯云 CAM(访问管理)的权限设计,HarmonyOS 通过模块化设计实现资源的逻辑隔离。
- 开发者可以通过显式依赖和资源命名规范避免冲突。
-
跨模块通信:
- 除了资源访问,还可以通过模块间接口调用实现功能共享。
5. 最佳实践
-
命名规范:为资源文件添加模块前缀(如
hsp1_icon.png
),避免命名冲突。 -
依赖管理:在
oh-package.json5
中明确模块依赖顺序,确保资源优先级符合预期。 -
动态加载:对于插件化场景,优先使用
resourceManager
动态加载资源。
6. 适用场景
- 多模块共享通用资源(如主题、图标、多语言文本)。
- 动态加载不同模块的资源(如插件化设计)。
如果需要进一步分析具体实现或优化建议,请告诉我!
鸿蒙Flex与Row/Column对比
在鸿蒙(HarmonyOS)应用开发中,Flex布局与Row/Column布局是两种核心的容器组件,它们在功能、性能及适用场景上存在显著差异。以下从五个维度进行详细对比:
📊 1. 核心差异对比
特性 | Flex布局 | Row/Column布局 |
---|---|---|
布局机制 | 动态弹性计算,支持二次布局(重新分配空间) | 单次线性排列,无二次布局 |
方向控制 | 支持水平(Row)、垂直(Column)及反向排列 | Row仅水平,Column仅垂直 |
换行能力 | 支持自动换行(FlexWrap.Wrap ) |
不支持换行,子组件溢出时被截断或压缩 |
子组件控制 | 支持flexGrow 、flexShrink 、flexBasis 动态分配空间 |
仅支持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)。
通过合理选择组件并优化属性配置,可显著提升鸿蒙应用的渲染效率与用户体验。
鸿蒙grid-hybrid项目UI滚动联动
鸿蒙AVSession Kit
—— 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 完整落地
- 依赖
import { avSession } from '@kit.AVSessionKit';
import { BackgroundTasks } from '@kit.BackgroundTasksKit';
- 会话提供方(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));
}
}
- 会话控制方(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 } });
}
}
- 枚举会话
const sessions = await avSession.getAllActiveSessions();
console.log('当前活跃会话', sessions.map(s => s.sessionTag));
三、C/C++(NDK)完整落地
- CMake
find_library(avsession-lib libohavsession.so)
target_link_libraries(your_target ${avsession-lib})
- 会话提供方(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);
- 会话控制方(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);
- 清理
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 全角色一次讲透。
鸿蒙各种生命周期
说真的记不住,用的时候自然会
层级 | 对象 | 主要回调 | 触发时机一句话 |
---|---|---|---|
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 | 热重载/性能追踪 |
鸿蒙音频编码
【HarmonyOS 音频编码开发速览】
文档地址:developer.huawei.com/consumer/cn…
-
能力总览
• 将任意来源的 PCM 数据编码为 AAC、FLAC、MP3、G.711μ、OPUS、AMR-NB/WB 等格式。
• 典型使用场景:录音后封装、PCM 编辑后导出文件。
• AAC 默认使用 VBR,码率可能与设定值存在偏差。
-
核心流程(12 步)
-
引入头文件。
-
创建编码器(OH_AVCodec):可按 MIME 或编解码器名称创建。
-
注册回调:
- onError、onNeedInputBuffer、onNewOutputBuffer(onStreamChanged 暂不支持)。
-
配置参数:
必须项:采样率、声道数、采样格式;
可/必须项:码率、声道布局、ADTS、Profile 等,各格式差异见表。
-
Prepare → 6) Start → 7) 逐帧输入 PCM(需按格式计算单次样点数) →
-
取出码流 → 9) FreeOutputBuffer →
10)(可选)Flush / Reset → 11) Stop → 12) Destroy。
-
-
关键参数表
• 采样率/声道数范围: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)。
-
开发注意
• 必须按顺序调用 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([结束])
下面给出
- 用 Mermaid 绘制的「音频编码状态/调用流程图」
- 一份可直接在 DevEco Studio 4.0(API 10+)里跑的 C++ 最小完整示例(CMake 工程),演示把 44.1 kHz/16-bit/立体声 PCM 编码为 AAC-LC(ADTS)并写入本地文件。
- 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()
- 最小可编译示例
目录结构
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 使用方式
- 把
test_44k_16bit_2ch.pcm
推送到/data/storage/el2/base/haps/entry/files/in.pcm
- ArkTS 侧调用:
import entry from '@ohos.entry';
entry.encodeFile();
- 运行后可在同目录得到
out.aac
(ADTS 封装,可直接播放验证)。
至此,完整的 Mermaid 流程图 + 可跑代码实现已给出,可直接集成到 HarmonyOS 工程中。
鸿蒙音频解码
以下是对鸿蒙音频解码模块的总结,包含核心流程、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()
-
支持的输入格式
AAC、FLAC、MP3、Vorbis、G711、AMR、APE、Opus、Audio Vivid 等,具体采样率/声道范围见文档表格。
-
PCM 输出格式
通过
OH_MD_KEY_AUDIO_SAMPLE_FORMAT
可选SAMPLE_S16LE
或SAMPLE_F32LE
,默认 S16LE。 -
线程模型
所有回调都在内部工作线程,请勿阻塞;应用需要保证线程安全。
-
DRM 解密
若内容加密,需在
Prepare
前调用OH_AudioCodec_SetDecryptionConfig
,并在PushInputBuffer
时把 cencInfo 通过OH_AVCencInfo_SetAVBuffer
写入。 -
EOS 处理
输入最后一包数据时把 flags 设为
AVCODEC_BUFFER_FLAGS_EOS
,解码器会在回调中同样给出 EOS,应用即可进入停止流程。 -
CMake 链接
target_link_libraries(xxx
native_media_codecbase.so
)
一句话总结
「鸿蒙音频解码」遵循 创建→配置→启动→循环喂数据→取 PCM→停止销毁 的极简五步模型;所有细节都围绕 OH_AVCodec*
句柄与 4 个回调完成,无需额外线程或同步,直接嵌入现有播放 / 编辑管线即可。
二、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. 关键开发步骤
-
创建实例
// 通过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);
-
注册回调
OH_AVCodecCallback cb = {OnError, OnOutputFormatChanged, OnInputBufferAvailable, OnOutputBufferAvailable}; OH_AudioCodec_RegisterCallback(decoder, cb, userData);
-
配置参数
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);
-
**数据流处理
-
输入:在
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);
三、重要注意事项
-
调用顺序强制要求:
创建 → 配置 → 准备 → 启动
必须顺序执行,否则引发异常 -
解码格式限制:
- 支持AAC/FLAC/MP3/Vorbis等主流格式
- 不同格式有特定参数要求(如Vorbis需ID Header)
-
资源释放:
OH_AudioCodec_Stop(decoder); // 先停止 OH_AudioCodec_Destroy(decoder); // 再销毁
-
动态库依赖:
target_link_libraries(sample libnative_media_codecbase.so libnative_media_core.so libnative_media_acodec.so)
鸿蒙视频解码
%% 鸿蒙 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,可二次处理;仅非安全通路;需手动拷贝对齐数据。
常见坑提示
- Flush/Reset/Stop 后需重新送 SPS/PPS。
- Destroy 不能在回调线程里调。
- Buffer 模式必须
FreeOutputBuffer
,否则阻塞后续输出。 - 多解码器共用同一 NativeWindow 时,先释放再启动,避免画面卡死。
鸿蒙视频编码
以下为对 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 硬编码)。
代码仅保留主干逻辑,省略了异常处理的细节,方便你先把流程跑通;真正工程化时再加上日志、异常检查、线程同步等即可。
────────────────────────────
- 目录结构
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 模式视频编码示例就完成了。