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

12 KiB
Raw Blame History

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

#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 实现要点

// 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

# 收集 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 中编写测试。

验收标准

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. 风险与回退

风险 对策
FramePtrstd::shared_ptrC 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 的 AVFrameolive::Frame 转换在 C API 边界 保持内部实现不变,C API 只操作 olive::Frame