鸿蒙开发入门指南:前端开发者快速理解视频编码概念——输入模式

# 鸿蒙开发入门指南:前端开发者快速掌握视频编码

大家好,我是木斯佳。今天聊点不一样的------鸿蒙的视频编码。说实话,第一次看到这玩意儿我有点懵,这跟前端有啥关系?但后来想想,现在哪个 App 不上传视频?做音视频编辑、视频上传、直播推流,都绕不开编码。鸿蒙这套 API 是 C++ 的,但设计思路其实挺有意思,咱们用前端的思维来理解一下。

一、视频编码到底是干啥的?

先说说我的理解:你手机拍视频,录出来的是原始数据,一秒钟几十兆,根本存不下也传不动。编码就是把这些原始数据压缩成 H.264、H.265 这种格式,体积能小几十倍。

鸿蒙提供的这套 Native API,就是让你在 C++ 层干这个活。支持 H.264、H.265,还支持 HDR Vivid(就是高动态范围标准)。

官网文档里区分了两种输入模式:

  • Surface模式:数据来自摄像头预览画面、屏幕录制这些地方,直接对接 GPU 纹理,效率最高
  • Buffer模式:数据来自文件,你自己往内存里塞,适合读本地 YUV 文件转码

打个比方:Surface模式像「流水线直接对接」,Buffer模式像「自己搬砖」。

二、先看一张图:编码器的状态机

官网给了个状态机图,比较难理解,我用自己的语言给大家翻译一下,这其实就和我们常用的生命周期是类似原理:

状态 前端类比
Initialized new 了一个实例,还没配置
Configured 参数配好了,宽高、码率都设置完
Prepared 准备工作做完,等着启动
Executing 正在编码,处理每一帧
Flushed 清空了缓冲区,但没停
EOS 文件末尾,最后一帧处理完了
Error 出错了,比如输入数据格式不对
Released 实例销毁,资源释放

流程大概是:创建 → 配置 → 准备 → 启动 → 编码 → 结束 → 销毁。

如果中途出错或者想重置,可以走 Flush 或者 Reset 回到之前的状态。

前端视角:这有点像 Video 元素的生命周期,或者 WebCodecs API 的状态管理。

三、Surface模式:从摄像头到视频文件

官网给了完整的示例代码,我把核心步骤串一遍。

第1步:创建编码器实例

有两种方式,按名字创建或者按 MIME 类型创建:

cpp 复制代码
// 按名字创建(可以指定硬件编码器)
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
const char *codecName = OH_AVCapability_GetName(capability);
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByName(codecName);

// 按 MIME 创建(更简单)
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);

前端视角 :类似 new VideoEncoder(),但这里需要指定用软解还是硬解。

第2步:注册回调函数

编码器是异步的,数据准备好了会回调你:

cpp 复制代码
// 错误回调
static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData) {
    // 编码出错了
}

// 数据流变化回调(比如分辨率变了)
static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) {
    // 从 format 里取出新的宽高
    OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &width);
    OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &height);
}

// 输入回调(Surface模式下没啥用,数据从 surface 来)
static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
    // Surface 模式不处理这个
}

// 输出回调(编码完了一帧,来这里拿数据)
static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
    outQueue.Enqueue(...);  // 把编码好的数据存起来
}

OH_AVCodecCallback cb = {&OnError, &OnStreamChanged, &OnNeedInputBuffer, &OnNewOutputBuffer};
OH_VideoEncoder_RegisterCallback(videoEnc, cb, nullptr);

前端视角:就是 Promise 或者事件监听,数据到了告诉你。

第3步:配置编码参数

这一步最繁琐,要设置一堆参数:

cpp 复制代码
auto format = OH_AVFormat_Create();
OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, 320);      // 宽度
OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, 240);     // 高度
OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, AV_PIXEL_FORMAT_NV12);  // 像素格式
OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, 30);  // 帧率
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, 5000000);   // 码率 5Mbps
OH_AVFormat_SetIntValue(format, OH_MD_KEY_I_FRAME_INTERVAL, 1000);  // 关键帧间隔 1 秒

OH_VideoEncoder_Configure(videoEnc, format);

前端视角 :类似 new VideoEncoder({output, error}) 之后调用 configure(),参数差不多。

第4步:获取 Surface 并启动

这是 Surface 模式的关键------从编码器拿一个 Surface,交给相机或者其他生产者:

cpp 复制代码
// 获取 Surface
OHNativeWindow *nativeWindow;
OH_VideoEncoder_GetSurface(videoEnc, &nativeWindow);

// 准备就绪
OH_VideoEncoder_Prepare(videoEnc);

// 开始编码
OH_VideoEncoder_Start(videoEnc);

拿到 nativeWindow 之后,可以传给相机模块,相机的预览数据就直接送进编码器了,你不需要手动往编码器塞数据。

第5步:通知编码结束

数据送完了,通知编码器收工:

cpp 复制代码
OH_VideoEncoder_NotifyEndOfStream(videoEnc);

第6步:取编码结果

OnNewOutputBuffer 回调里,编码好的数据会送过来,你需要把它写进文件:

cpp 复制代码
void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
    // 获取编码后的数据
    OH_AVCodecBufferAttr info;
    OH_AVBuffer_GetBufferAttr(buffer, &info);
    
    // 写入文件
    uint8_t *data = OH_AVBuffer_GetAddr(buffer);
    outputFile->write(data, info.size);
    
    // 释放这个 buffer
    OH_VideoEncoder_FreeOutputBuffer(videoEnc, index);
}

第7步:清理

用完了记得销毁,不然会内存泄漏:

cpp 复制代码
OH_NativeWindow_DestroyNativeWindow(nativeWindow);
OH_VideoEncoder_Stop(videoEnc);
OH_VideoEncoder_Destroy(videoEnc);
videoEnc = nullptr;

四、Buffer模式:从文件到文件

Buffer 模式和 Surface 模式的区别在于:数据来源不是 Surface,而是你自己往 buffer 里塞。

大部分步骤一样,主要区别在这几处:

区别1:需要自己准备输入数据

Buffer 模式下,OnNeedInputBuffer 回调是有用的:

cpp 复制代码
void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
    // 从文件读取一帧 YUV 数据
    uint8_t *addr = OH_AVBuffer_GetAddr(buffer);
    inputFile->read(addr, frameSize);
    
    // 配置这一帧的信息(大小、时间戳、是不是关键帧)
    OH_AVCodecBufferAttr info;
    info.size = frameSize;
    info.pts = frameIndex * 1000000 / frameRate;  // 时间戳
    info.flags = 0;  // 普通帧,最后一帧设为 AVCODEC_BUFFER_FLAGS_EOS
    OH_AVBuffer_SetBufferAttr(buffer, &info);
    
    // 推给编码器
    OH_VideoEncoder_PushInputBuffer(videoEnc, index);
}

区别2:需要处理跨距

YUV 数据在内存里不一定连续存放,有跨距(stride)的概念------每行数据之间可能有 padding。

官网给了个示例代码,把源数据按跨距复制到目标 buffer:

cpp 复制代码
for (int32_t i = 0; i < rect.height; ++i) {
    memcpy(dstTemp, srcTemp, rect.width);
    dstTemp += dstRect.wStride;  // 跳过 padding
    srcTemp += srcRect.wStride;
}

前端视角:类似处理 Canvas 的 ImageData,每行可能有额外的字节对齐。

五、运行时动态调整参数

编码过程中可以动态调整参数,比如突然要切一个关键帧,或者码率要动态变化:

cpp 复制代码
OH_AVFormat *format = OH_AVFormat_Create();

// 强制下一帧是关键帧
OH_AVFormat_SetIntValue(format, OH_MD_KEY_REQUEST_I_FRAME, true);

// 动态调整码率(VBR 模式)
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, 2000000);

// 动态调整帧率
OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, 60.0);

OH_VideoEncoder_SetParameter(videoEnc, format);
OH_AVFormat_Destroy(format);

这个能力挺实用的------网络不好的时候降码率,用户滑动进度条的时候切关键帧。

六、总结一下两种模式怎么选

场景 推荐模式 原因
相机拍摄 Surface 直接对接相机输出,零拷贝
屏幕录制 Surface 直接从 GPU 拿数据
文件转码 Buffer 自己控制数据来源
视频编辑 Buffer 需要逐帧处理
实时滤镜 Buffer 需要拿到 YUV 数据做滤镜处理

一句话:能走 Surface 就走 Surface,性能好;只有需要逐帧操作数据时才用 Buffer。

总结

虽然这套 API 是 C++ 的,但设计思路和 WebCodecs 有相似之处:

概念 WebCodecs 鸿蒙
编码器 VideoEncoder OH_VideoEncoder
配置参数 configure() OH_VideoEncoder_Configure()
编码帧 encode() OH_VideoEncoder_PushInputBuffer()
输出帧 output 回调 OnNewOutputBuffer 回调
结束 flush() NotifyEndOfStream() / EOS flag

如果用过 WebCodecs,上手鸿蒙这套 API 会快很多。没接触过也没关系,核心就是:配置参数 → 塞数据 → 拿结果 → 清理,所有编解码器都是这个套路。

最后提醒一下:硬件编码器资源有限,用完必须调用 OH_VideoEncoder_Destroy 释放,否则可能影响其他应用甚至被系统杀掉。另外不能在回调函数里销毁编码器,会死锁。

有问题评论区聊。

相关推荐
不羁的木木2 小时前
《HarmonyOS技术精讲》二:用户动作与状态感知实战
华为·harmonyos
EasyDSS5 小时前
私有化音视频系统/视频直播点播/高清点播/云点播/云直播EasyDSS优化升级重塑智慧文旅直播运营新体系
音视频
G_dou_5 小时前
Flutter+OpenHarmony 实战:stopwatch 秒表应用
flutter·harmonyos
亚信安全官方账号5 小时前
AISTrustOne鸿蒙版安全方案 让终端防护“内生”力量觉醒
安全·华为·harmonyos
夜勤月6 小时前
HarmonyOS 6.0 ArkWeb实战:PDF背景色自定义功能全解析(附完整代码+避坑指南)
华为·pdf·harmonyos
CV实验室7 小时前
Remote Sensing 29个SITS基准数据集综述:多模态遥感分类的新起点
人工智能·深度学习·计算机视觉·音视频
想你依然心痛7 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“药界智脑“——PC端AI智能体沉浸式药物研发与分子模拟工作台
人工智能·华为·ar·harmonyos·智能体
G_dou_8 小时前
Flutter +OpenHarmony 实战:clock 时钟应用
flutter·harmonyos