Files
oak-editor/docs/zh/modularization-plan/02-olivecodec.md
T

360 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# libolivecodec.so — 编解码库
> **依赖**`libolivecore.so`
> **外部依赖**FFmpeg (avcodec, avformat, avutil, swscale, swresample, avfilter), OpenImageIO, OpenEXR
> **包含源码**`app/codec/`, `app/common/`
> **当前状态**:单体 OBJECT 库的一部分
> **改造难度**:⭐⭐(较简单)
---
## 1. 当前状态分析
`app/codec/` 负责媒体文件的读取与写入,`app/common/` 提供通用工具(FFmpeg 辅助、XML 工具、文件操作等)。两者紧密耦合,且 `common/``codec/` 重度依赖,因此合并为一个动态库。
| 组件 | 说明 |
|---|---|
| `decoder.h/cpp` | 解码器抽象基类 |
| `ffmpeg/ffmpegdecoder` / `ffmpegencoder` | FFmpeg 视频/音频解码编码 |
| `oiio/oiiodecoder` / `oiioencoder` | OpenImageIO 图像序列解码编码 |
| `frame.h/cpp` | CPU 帧数据(`FramePtr` |
| `stream.h` | 媒体流信息 |
| `conformmanager.h/cpp` | 音频格式统一转换 |
| `common/ffmpegutils.h` | FFmpeg 辅助函数 |
| `common/xmlutils.h` | XML 序列化辅助 |
| `common/filefunctions.h` | 文件操作 |
---
## 2. C API 设计
### 2.1 头文件:`c_api/include/olive/codec_api.h`
```c
#ifndef OLIVE_CODEC_API_H
#define OLIVE_CODEC_API_H
#ifdef __cplusplus
extern "C" {
#endif
#include "core_api.h"
#define OLIVE_CODEC_API_VERSION 1
#ifdef OLIVE_BUILDING_CODEC
# define OLIVE_CODEC_API __attribute__((visibility("default")))
#else
# define OLIVE_CODEC_API
#endif
/* ========== 类型前向声明 ========== */
typedef struct OliveDecoder OliveDecoder;
typedef struct OliveEncoder OliveEncoder;
typedef struct OliveFrame OliveFrame;
typedef struct OliveStream OliveStream;
typedef struct OliveMediaInfo OliveMediaInfo;
/* ========== API 版本 ========== */
OLIVE_CODEC_API int olive_codec_api_version(void);
/* ========== MediaInfo(媒体文件信息) ========== */
OLIVE_CODEC_API OliveMediaInfo* olive_media_info_probe(const char* filename);
OLIVE_CODEC_API void olive_media_info_destroy(OliveMediaInfo* info);
OLIVE_CODEC_API int olive_media_info_stream_count(OliveMediaInfo* info);
OLIVE_CODEC_API int olive_media_info_stream_type(OliveMediaInfo* info, int stream_index); // 0=video, 1=audio, 2=subtitle
OLIVE_CODEC_API OliveVideoParams olive_media_info_video_params(OliveMediaInfo* info, int stream_index);
OLIVE_CODEC_API OliveAudioParams olive_media_info_audio_params(OliveMediaInfo* info, int stream_index);
OLIVE_CODEC_API OliveRational olive_media_info_duration(OliveMediaInfo* info);
OLIVE_CODEC_API const char* olive_media_info_codec_name(OliveMediaInfo* info, int stream_index);
/* ========== Decoder ========== */
OLIVE_CODEC_API OliveDecoder* olive_decoder_create(const char* codec_id);
OLIVE_CODEC_API void olive_decoder_destroy(OliveDecoder* decoder);
OLIVE_CODEC_API int olive_decoder_open(OliveDecoder* decoder,
const char* filename,
int stream_index);
OLIVE_CODEC_API void olive_decoder_close(OliveDecoder* decoder);
// 视频解码:解码指定时间的帧
OLIVE_CODEC_API int olive_decoder_decode_video(OliveDecoder* decoder,
OliveRational time,
OliveFrame** out_frame);
// 音频解码:解码指定时间范围的采样
OLIVE_CODEC_API int olive_decoder_decode_audio(OliveDecoder* decoder,
OliveRational start,
OliveRational duration,
OliveSampleBuffer** out_buffer);
// 获取解码器支持的流参数
OLIVE_CODEC_API OliveVideoParams olive_decoder_video_params(OliveDecoder* decoder);
OLIVE_CODEC_API OliveAudioParams olive_decoder_audio_params(OliveDecoder* decoder);
/* ========== Frame ========== */
OLIVE_CODEC_API void olive_frame_destroy(OliveFrame* frame);
OLIVE_CODEC_API int olive_frame_width(OliveFrame* frame);
OLIVE_CODEC_API int olive_frame_height(OliveFrame* frame);
OLIVE_CODEC_API int olive_frame_linesize(OliveFrame* frame);
OLIVE_CODEC_API OlivePixelFormat olive_frame_format(OliveFrame* frame);
OLIVE_CODEC_API void* olive_frame_data(OliveFrame* frame); // 指向像素数据的指针
OLIVE_CODEC_API size_t olive_frame_data_size(OliveFrame* frame);
// 将 Frame 转换为指定的像素格式(内部使用 swscale)
OLIVE_CODEC_API int olive_frame_convert(OliveFrame* src,
OlivePixelFormat dst_format,
OliveFrame** out_frame);
// 从原始数据创建 Frame(用于渲染结果回传)
OLIVE_CODEC_API OliveFrame* olive_frame_from_data(int width,
int height,
OlivePixelFormat format,
const void* data,
int linesize);
/* ========== Encoder ========== */
OLIVE_CODEC_API OliveEncoder* olive_encoder_create(const char* format_name,
const char* codec_name);
OLIVE_CODEC_API void olive_encoder_destroy(OliveEncoder* encoder);
OLIVE_CODEC_API int olive_encoder_open(OliveEncoder* encoder,
const char* filename,
OliveVideoParams vparams,
OliveAudioParams aparams);
OLIVE_CODEC_API int olive_encoder_write_video(OliveEncoder* encoder, OliveFrame* frame);
OLIVE_CODEC_API int olive_encoder_write_audio(OliveEncoder* encoder, OliveSampleBuffer* buffer);
OLIVE_CODEC_API int olive_encoder_close(OliveEncoder* encoder);
/* ========== Conform(音频格式统一) ========== */
OLIVE_CODEC_API int olive_audio_conform(const char* input_filename,
const char* output_filename,
OliveAudioParams target_params);
#ifdef __cplusplus
}
#endif
#endif // OLIVE_CODEC_API_H
```
### 2.2 实现要点
```cpp
// c_api/src/codec_api.cpp
#include "olive/codec_api.h"
#include "codec/decoder.h"
#include "codec/ffmpeg/ffmpegdecoder.h"
#include "codec/frame.h"
#include "codec/encoder.h"
#include "codec/ffmpeg/ffmpegencoder.h"
#include "common/ffmpegutils.h"
struct OliveDecoder {
olive::DecoderPtr impl;
};
struct OliveFrame {
olive::FramePtr impl;
};
// ... 其他不透明指针定义 ...
extern "C" {
OliveDecoder* olive_decoder_create(const char* codec_id) {
try {
auto* d = new OliveDecoder();
// 根据 codec_id 创建对应的解码器实例
// 若 codec_id 为 nullptr 或 "auto",则自动探测
d->impl = olive::Decoder::CreateFromID(QString::fromUtf8(codec_id));
return d;
} catch (...) {
return nullptr;
}
}
void olive_decoder_destroy(OliveDecoder* decoder) {
delete decoder;
}
int olive_decoder_open(OliveDecoder* decoder, const char* filename, int stream_index) {
if (!decoder || !filename) return OLIVE_ERROR_INVALID;
try {
bool ok = decoder->impl->Open(QString::fromUtf8(filename), stream_index);
return ok ? OLIVE_OK : OLIVE_ERROR_GENERIC;
} catch (...) {
return OLIVE_ERROR_GENERIC;
}
}
int olive_decoder_decode_video(OliveDecoder* decoder, OliveRational time, OliveFrame** out_frame) {
if (!decoder || !out_frame) return OLIVE_ERROR_INVALID;
try {
olive::Rational t(time.num, time.den);
olive::FramePtr frame = decoder->impl->RetrieveVideo(t);
if (!frame) return OLIVE_ERROR_NOT_FOUND;
auto* f = new OliveFrame();
f->impl = frame;
*out_frame = f;
return OLIVE_OK;
} catch (...) {
return OLIVE_ERROR_GENERIC;
}
}
// ... 其他函数类似封装 ...
} // extern "C"
```
---
## 3. CMake 改造
### 3.1 `app/codec/CMakeLists.txt`
```cmake
# 收集 codec/ 内部源文件
set(CODEC_INTERNAL_SOURCES
decoder.cpp decoder.h
encoder.cpp encoder.h
frame.cpp frame.h
stream.cpp stream.h
conformmanager.cpp conformmanager.h
ffmpeg/ffmpegdecoder.cpp ffmpeg/ffmpegdecoder.h
ffmpeg/ffmpegencoder.cpp ffmpeg/ffmpegencoder.h
oiio/oiiodecoder.cpp oiio/oiiodecoder.h
oiio/oiioencoder.cpp oiio/oiioencoder.h
# ...
)
# 收集 common/ 源文件(并入 codec 库)
set(COMMON_INTERNAL_SOURCES
../common/ffmpegutils.cpp ../common/ffmpegutils.h
../common/xmlutils.cpp ../common/xmlutils.h
../common/filefunctions.cpp ../common/filefunctions.h
../common/qtutils.cpp ../common/qtutils.h
../common/debug.cpp ../common/debug.h
# ...
)
# C API 封装层
set(CODEC_API_SOURCES
${CMAKE_SOURCE_DIR}/c_api/src/codec_api.cpp
)
add_library(olivecodec SHARED
${CODEC_INTERNAL_SOURCES}
${COMMON_INTERNAL_SOURCES}
${CODEC_API_SOURCES}
)
target_compile_definitions(olivecodec PRIVATE OLIVE_BUILDING_CODEC)
target_include_directories(olivecodec
PRIVATE
${CMAKE_SOURCE_DIR}/app
${CMAKE_SOURCE_DIR}/c_api/include
PUBLIC
$<INSTALL_INTERFACE:include>
)
target_link_libraries(olivecodec
PUBLIC
olivecore
FFMPEG::avcodec
FFMPEG::avformat
FFMPEG::avutil
FFMPEG::swscale
FFMPEG::swresample
FFMPEG::avfilter
${OIIO_LIBRARIES}
${OPENEXR_LIBRARIES}
)
set_target_properties(olivecodec PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)
install(TARGETS olivecodec DESTINATION lib)
install(FILES ${CMAKE_SOURCE_DIR}/c_api/include/olive/codec_api.h DESTINATION include/olive)
```
---
## 4. 小步快跑实施步骤
### Step 0: 分析依赖关系(半天)
- [ ] 梳理 `app/codec/``app/common/` 中的所有文件。
- [ ] 确认 `common/` 中不包含任何 Qt GUI 相关代码(若有,移出到 `liboliveui.so`)。
- [ ] 列出 `codec/``common/``node/` 的反向依赖(理论上不应有,若有需先解耦)。
**验收标准**:确认 `codec/` + `common/` 的依赖图只包含 `ext/core/`、FFmpeg、OIIO、Qt::Core。
### Step 1: 合并 common 到 codec 库(1 天)
- [ ] 修改 `app/codec/CMakeLists.txt`,将 `app/common/` 的源文件并入。
- [ ]`app/common/CMakeLists.txt` 改为空文件(或删除,保留 add_subdirectory 空壳以兼容)。
- [ ] 确保编译产物为 `libolivecodec.so`(或 `.dylib`/`.dll`)。
**验收标准**`libolivecodec.so` 编译成功,原有单元测试通过。
### Step 2: 设计 C API 的最小子集(1 天)
- [ ] 先只实现渲染流程**最必需**的接口:
- `olive_decoder_create/open/destroy`
- `olive_decoder_decode_video`
- `olive_frame_width/height/data/destroy`
- `olive_media_info_probe`
- [ ] 暂不实现:Encoder、Conform、音频解码的复杂场景。
**验收标准**:可以用 C API 打开一个视频文件并解码出一帧。
### Step 3: 编写 C API 实现(2 天)
- [ ] 编写 `c_api/include/olive/codec_api.h`(最小子集)。
- [ ] 编写 `c_api/src/codec_api.cpp`
- [ ] 每个函数用 `try/catch(...)` 包裹,异常转换为 `OLIVE_ERROR_GENERIC`
- [ ]`c_api/tests/test_codec_api.cpp` 中编写测试。
**验收标准**
```cpp
OliveDecoder* d = olive_decoder_create(nullptr);
olive_decoder_open(d, "test.mp4", 0);
OliveFrame* f = nullptr;
olive_decoder_decode_video(d, olive_rational_make(0, 1), &f);
assert(f != nullptr);
assert(olive_frame_width(f) > 0);
olive_frame_destroy(f);
olive_decoder_destroy(d);
```
### Step 4: 显式加载验证(1 天)
- [ ] 在主进程中通过 `ModuleLoader` 加载 `libolivecodec.so`
- [ ] 验证可以成功解码测试视频并显示帧尺寸。
**验收标准**:主进程日志输出成功加载 `codec`,并能获取测试视频的宽和高。
### Step 5: 扩展 C API(按需迭代)
- [ ] 根据 `node/``render/` 的需要,逐步增加 Encoder、音频解码、Conform 等接口。
- [ ] 每次增加后运行编解码单元测试。
---
## 5. 风险与回退
| 风险 | 对策 |
|---|---|
| `FramePtr``std::shared_ptr`C API 中需要管理引用计数 | `OliveFrame` 不透明指针内部持有 `std::shared_ptr`,销毁时自动减引用计数。若需要延长生命周期,可新增 `olive_frame_ref/unref`。 |
| `Decoder::Open` 是异步/多线程的 | C API 层面先做同步封装(等待 Open 完成)。若性能不满足,后续可新增异步回调接口。 |
| `common/` 中的 `xmlutils.h` 依赖 Qt XML | 这是允许的(Qt::Core 的一部分),但需注意 `common/` 中若混入 GUI 相关代码(如 `QMessageBox`),必须移出。 |
| FFmpeg 的 `AVFrame``olive::Frame` 转换在 C API 边界 | 保持内部实现不变,C API 只操作 `olive::Frame`。 |