227 lines
6.4 KiB
C++
227 lines
6.4 KiB
C++
//
|
||
// FFmpeg 风格编解码器注册表与上下文管理实现(中文注释版)。
|
||
// 注意:此实现大量使用 std::vector、std::optional 等现代 C++ 容器与 RAII 思想。
|
||
//
|
||
|
||
#include "cpdecoder.hh"
|
||
|
||
#include <algorithm> // 提供 std::find_if 等算法,用于查找与遍历
|
||
#include <mutex> // std::mutex + std::lock_guard,保证注册表线程安全
|
||
|
||
namespace ffmsep {
|
||
|
||
namespace { // 匿名命名空间:限制辅助函数和静态变量作用域在当前编译单元
|
||
|
||
std::vector<const CPCodec*>& codec_registry() {
|
||
static std::vector<const CPCodec*> registry; // 使用静态局部变量 + vector 保留所有注册的编解码器
|
||
return registry;
|
||
}
|
||
|
||
std::mutex& registry_mutex() {
|
||
static std::mutex m; // 构建一个全局互斥量,用于守护注册表操作
|
||
return m;
|
||
}
|
||
|
||
void attach_codec(CPCodecContext* ctx, const CPCodec* codec) {
|
||
if (!ctx) {
|
||
return;
|
||
}
|
||
|
||
ctx->codec = codec;
|
||
if (!codec) {
|
||
ctx->codec_type = CPMediaType::Unknown;
|
||
ctx->priv_data = nullptr;
|
||
ctx->release_priv_storage();
|
||
return;
|
||
}
|
||
|
||
ctx->codec_type = codec->type;
|
||
ctx->priv_data = ctx->ensure_priv_storage(codec->priv_data_size);
|
||
}
|
||
|
||
bool codec_name_equals(const CPCodec* codec, std::string_view name) {
|
||
if (!codec || !codec->name) {
|
||
return false;
|
||
}
|
||
return std::string_view(codec->name) == name; // std::string_view 避免额外拷贝
|
||
}
|
||
|
||
} // namespace
|
||
|
||
void* CPCodecContext::ensure_priv_storage(std::size_t size) {
|
||
// 将 vector 当作动态缓冲区,确保大小满足编解码器私有数据需求
|
||
if (size == 0U) {
|
||
priv_storage.clear();
|
||
priv_data = nullptr;
|
||
return nullptr;
|
||
}
|
||
if (priv_storage.size() != size) {
|
||
priv_storage.assign(size, static_cast<std::uint8_t>(0));
|
||
}
|
||
priv_data = priv_storage.data();
|
||
return priv_data;
|
||
}
|
||
|
||
void CPCodecContext::release_priv_storage() noexcept {
|
||
// 与 ensure 配套,释放时直接清空 vector
|
||
priv_storage.clear();
|
||
priv_data = nullptr;
|
||
}
|
||
|
||
// 将单个编解码器指针注册到全局列表,重复注册时作安全检查。
|
||
void cpcodec_register(const CPCodec* codec) {
|
||
if (!codec || !codec->name) {
|
||
return;
|
||
}
|
||
|
||
std::lock_guard<std::mutex> lock(registry_mutex());
|
||
auto& reg = codec_registry();
|
||
auto already = std::find(reg.begin(), reg.end(), codec);
|
||
if (already != reg.end()) {
|
||
return;
|
||
}
|
||
|
||
auto same_id = std::find_if(reg.begin(), reg.end(), [codec](const CPCodec* entry) {
|
||
return entry && codec && entry->id == codec->id && codec->id != CPCodecID::Unknown;
|
||
});
|
||
if (same_id != reg.end()) {
|
||
*same_id = codec; // 如果 ID 已存在,则替换为新实现,以最新注册者为准
|
||
return;
|
||
}
|
||
|
||
reg.push_back(codec);
|
||
}
|
||
|
||
// 批量注册函数:利用 C++11 std::initializer_list 便捷传入多个指针。
|
||
void cpcodec_register_many(std::initializer_list<const CPCodec*> codecs) {
|
||
for (const CPCodec* codec : codecs) {
|
||
cpcodec_register(codec);
|
||
}
|
||
}
|
||
|
||
const CPCodec* cpcodec_find_decoder(CPCodecID id) {
|
||
std::lock_guard<std::mutex> lock(registry_mutex());
|
||
const auto& reg = codec_registry();
|
||
auto it = std::find_if(reg.begin(), reg.end(), [id](const CPCodec* codec) {
|
||
return codec && codec->id == id;
|
||
});
|
||
return it == reg.end() ? nullptr : *it; // 使用可空返回值:找不到时返回 nullptr
|
||
}
|
||
|
||
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name) {
|
||
std::lock_guard<std::mutex> lock(registry_mutex());
|
||
const auto& reg = codec_registry();
|
||
auto it = std::find_if(reg.begin(), reg.end(), [name](const CPCodec* codec) {
|
||
return codec_name_equals(codec, name);
|
||
});
|
||
return it == reg.end() ? nullptr : *it;
|
||
}
|
||
|
||
std::vector<const CPCodec*> cpcodec_list_codecs() {
|
||
std::lock_guard<std::mutex> lock(registry_mutex());
|
||
return codec_registry(); // 返回当前注册表的浅拷贝
|
||
}
|
||
|
||
// 分配一个新的上下文,并可选绑定已有编解码器。
|
||
CPCodecContext* cpcodec_alloc_context3(const CPCodec* codec) {
|
||
auto* ctx = new CPCodecContext();
|
||
if (codec) {
|
||
attach_codec(ctx, codec);
|
||
}
|
||
return ctx;
|
||
}
|
||
|
||
// 打开上下文:允许调用方在此时指定(或替换)具体编解码器。
|
||
int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec) {
|
||
if (!ctx) {
|
||
return CP_ERROR_INVALID_ARGUMENT;
|
||
}
|
||
|
||
if (ctx->is_open) {
|
||
return CP_ERROR_INVALID_STATE;
|
||
}
|
||
|
||
if (codec) {
|
||
attach_codec(ctx, codec);
|
||
}
|
||
|
||
if (!ctx->codec) {
|
||
return CP_ERROR_INVALID_ARGUMENT;
|
||
}
|
||
|
||
ctx->is_open = true;
|
||
if (ctx->codec->init) {
|
||
int rc = ctx->codec->init(ctx);
|
||
if (rc < 0) {
|
||
ctx->is_open = false;
|
||
if (ctx->codec->close) {
|
||
ctx->codec->close(ctx); // 初始化失败时调用 close 进行善后
|
||
}
|
||
return rc;
|
||
}
|
||
}
|
||
return CP_SUCCESS;
|
||
}
|
||
|
||
// 关闭上下文:调用编解码器收尾逻辑并释放私有状态。
|
||
int cpcodec_close(CPCodecContext* ctx) {
|
||
if (!ctx) {
|
||
return CP_ERROR_INVALID_ARGUMENT;
|
||
}
|
||
|
||
if (!ctx->is_open) {
|
||
return CP_SUCCESS;
|
||
}
|
||
|
||
if (ctx->codec && ctx->codec->close) {
|
||
ctx->codec->close(ctx);
|
||
}
|
||
|
||
ctx->is_open = false;
|
||
ctx->release_priv_storage();
|
||
ctx->codec_type = CPMediaType::Unknown;
|
||
ctx->codec = nullptr;
|
||
ctx->priv_data = nullptr;
|
||
return CP_SUCCESS;
|
||
}
|
||
|
||
// 释放上下文指针,防止悬挂引用。
|
||
void cpcodec_free_context(CPCodecContext** ctx) {
|
||
if (!ctx || !*ctx) {
|
||
return;
|
||
}
|
||
cpcodec_close(*ctx);
|
||
delete *ctx;
|
||
*ctx = nullptr;
|
||
}
|
||
|
||
// 输入侧 API:推送一帧串口数据,内部队列通常会缓冲。
|
||
int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet) {
|
||
if (!ctx || !packet) {
|
||
return CP_ERROR_INVALID_ARGUMENT;
|
||
}
|
||
if (!ctx->is_open || !ctx->codec) {
|
||
return CP_ERROR_NOT_OPEN;
|
||
}
|
||
if (!ctx->codec->send_packet) {
|
||
return CP_ERROR_INVALID_STATE;
|
||
}
|
||
return ctx->codec->send_packet(ctx, *packet);
|
||
}
|
||
|
||
// 输出侧 API:尝试从编解码器读取一帧解码结果。
|
||
int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame) {
|
||
if (!ctx || !frame) {
|
||
return CP_ERROR_INVALID_ARGUMENT;
|
||
}
|
||
if (!ctx->is_open || !ctx->codec) {
|
||
return CP_ERROR_NOT_OPEN;
|
||
}
|
||
if (!ctx->codec->receive_frame) {
|
||
return CP_ERROR_INVALID_STATE;
|
||
}
|
||
return ctx->codec->receive_frame(ctx, *frame);
|
||
}
|
||
|
||
} // namespace ffmsep
|