mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 16:41:52 +08:00
feat: 支持 MJPEG 解码与 MSD 目录配置
- FFmpeg/hwcodec 增加 RKMPP MJPEG 解码与 RAM FFI,ARM 构建启用对应解码器 - 共享视频管线新增 MJPEG 解码路径(RKMPP/TurboJPEG),优化 WebRTC 发送与 MJPEG 去重 - MSD 配置改为 msd_dir 并自动创建子目录,接口与前端设置同步更新 - 更新包依赖与版本号
This commit is contained in:
@@ -6,6 +6,7 @@ description = "Hardware video codec for IP-KVM (Windows/Linux)"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
rkmpp = []
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
|
||||
@@ -152,7 +152,12 @@ mod ffmpeg {
|
||||
} else {
|
||||
// RKMPP for ARM
|
||||
println!("cargo:rustc-link-lib=rockchip_mpp");
|
||||
println!("cargo:rustc-link-lib=rga");
|
||||
let rga_static = lib_dir.join("librga.a");
|
||||
if rga_static.exists() {
|
||||
println!("cargo:rustc-link-lib=static=rga");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=rga");
|
||||
}
|
||||
}
|
||||
|
||||
// Software codec dependencies (dynamic - GPL)
|
||||
@@ -198,15 +203,24 @@ mod ffmpeg {
|
||||
if let Ok(output) = Command::new("pkg-config").args(&pkg_config_args).output() {
|
||||
if output.status.success() {
|
||||
let libs_str = String::from_utf8_lossy(&output.stdout);
|
||||
let mut link_paths: Vec<String> = Vec::new();
|
||||
for flag in libs_str.split_whitespace() {
|
||||
if flag.starts_with("-L") {
|
||||
println!("cargo:rustc-link-search=native={}", &flag[2..]);
|
||||
let path = flag[2..].to_string();
|
||||
println!("cargo:rustc-link-search=native={}", path);
|
||||
link_paths.push(path);
|
||||
} else if flag.starts_with("-l") {
|
||||
let lib_name = &flag[2..];
|
||||
if use_static {
|
||||
// For static linking, link FFmpeg libs statically, others dynamically
|
||||
if lib_name.starts_with("av") || lib_name == "swresample" {
|
||||
println!("cargo:rustc-link-lib=static={}", lib_name);
|
||||
} else if lib_name == "rga"
|
||||
&& link_paths
|
||||
.iter()
|
||||
.any(|path| Path::new(path).join("librga.a").exists())
|
||||
{
|
||||
println!("cargo:rustc-link-lib=static=rga");
|
||||
} else {
|
||||
// Runtime libraries (va, drm, etc.) must be dynamic
|
||||
println!("cargo:rustc-link-lib={}", lib_name);
|
||||
@@ -343,6 +357,20 @@ mod ffmpeg {
|
||||
.write_to_file(Path::new(&env::var_os("OUT_DIR").unwrap()).join("ffmpeg_ram_ffi.rs"))
|
||||
.unwrap();
|
||||
|
||||
builder.files(["ffmpeg_ram_encode.cpp"].map(|f| ffmpeg_ram_dir.join(f)));
|
||||
builder.file(ffmpeg_ram_dir.join("ffmpeg_ram_encode.cpp"));
|
||||
|
||||
// RKMPP decode only exists on ARM builds where FFmpeg is compiled with RKMPP support.
|
||||
// Avoid compiling this file on x86/x64 where `AV_HWDEVICE_TYPE_RKMPP` doesn't exist.
|
||||
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
let enable_rkmpp = matches!(target_arch.as_str(), "aarch64" | "arm")
|
||||
|| std::env::var_os("CARGO_FEATURE_RKMPP").is_some();
|
||||
if enable_rkmpp {
|
||||
builder.file(ffmpeg_ram_dir.join("ffmpeg_ram_decode.cpp"));
|
||||
} else {
|
||||
println!(
|
||||
"cargo:info=Skipping ffmpeg_ram_decode.cpp (RKMPP) for arch {}",
|
||||
target_arch
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
280
libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp
Normal file
280
libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
// Minimal FFmpeg RAM MJPEG decoder (RKMPP only) -> NV12 in CPU memory.
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/error.h>
|
||||
#include <libavutil/hwcontext.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#define LOG_MODULE "FFMPEG_RAM_DEC"
|
||||
#include <log.h>
|
||||
#include <util.h>
|
||||
|
||||
typedef void (*RamDecodeCallback)(const uint8_t *data, int len, int width,
|
||||
int height, int pixfmt, const void *obj);
|
||||
|
||||
namespace {
|
||||
thread_local std::string g_last_error;
|
||||
|
||||
static void set_last_error(const std::string &msg) {
|
||||
g_last_error = msg;
|
||||
}
|
||||
|
||||
class FFmpegRamDecoder {
|
||||
public:
|
||||
AVCodecContext *c_ = NULL;
|
||||
AVPacket *pkt_ = NULL;
|
||||
AVFrame *frame_ = NULL;
|
||||
AVFrame *sw_frame_ = NULL;
|
||||
std::string name_;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
AVPixelFormat sw_pixfmt_ = AV_PIX_FMT_NV12;
|
||||
int thread_count_ = 1;
|
||||
RamDecodeCallback callback_ = NULL;
|
||||
|
||||
AVHWDeviceType hw_device_type_ = AV_HWDEVICE_TYPE_NONE;
|
||||
AVPixelFormat hw_pixfmt_ = AV_PIX_FMT_NONE;
|
||||
AVBufferRef *hw_device_ctx_ = NULL;
|
||||
|
||||
explicit FFmpegRamDecoder(const char *name, int width, int height, int sw_pixfmt,
|
||||
int thread_count, RamDecodeCallback callback) {
|
||||
name_ = name ? name : "";
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
sw_pixfmt_ = (AVPixelFormat)sw_pixfmt;
|
||||
thread_count_ = thread_count > 0 ? thread_count : 1;
|
||||
callback_ = callback;
|
||||
|
||||
if (name_.find("rkmpp") != std::string::npos) {
|
||||
hw_device_type_ = AV_HWDEVICE_TYPE_RKMPP;
|
||||
}
|
||||
}
|
||||
|
||||
~FFmpegRamDecoder() {}
|
||||
|
||||
static enum AVPixelFormat get_hw_format(AVCodecContext *ctx,
|
||||
const enum AVPixelFormat *pix_fmts) {
|
||||
FFmpegRamDecoder *dec = reinterpret_cast<FFmpegRamDecoder *>(ctx->opaque);
|
||||
if (dec && dec->hw_pixfmt_ != AV_PIX_FMT_NONE) {
|
||||
const enum AVPixelFormat *p;
|
||||
for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
|
||||
if (*p == dec->hw_pixfmt_) {
|
||||
return *p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pix_fmts[0];
|
||||
}
|
||||
|
||||
bool init() {
|
||||
g_last_error.clear();
|
||||
const AVCodec *codec = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (!(codec = avcodec_find_decoder_by_name(name_.c_str()))) {
|
||||
set_last_error(std::string("Decoder not found: ") + name_);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(c_ = avcodec_alloc_context3(codec))) {
|
||||
set_last_error(std::string("Could not allocate decoder context"));
|
||||
return false;
|
||||
}
|
||||
|
||||
c_->width = width_;
|
||||
c_->height = height_;
|
||||
c_->thread_count = thread_count_;
|
||||
c_->opaque = this;
|
||||
|
||||
if (hw_device_type_ != AV_HWDEVICE_TYPE_NONE) {
|
||||
const AVCodecHWConfig *cfg = NULL;
|
||||
for (int i = 0; (cfg = avcodec_get_hw_config(codec, i)); i++) {
|
||||
if (cfg->device_type == hw_device_type_) {
|
||||
hw_pixfmt_ = cfg->pix_fmt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hw_pixfmt_ == AV_PIX_FMT_NONE) {
|
||||
set_last_error(std::string("No suitable HW pixfmt for decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = av_hwdevice_ctx_create(&hw_device_ctx_, hw_device_type_, NULL, NULL, 0);
|
||||
if (ret < 0) {
|
||||
set_last_error(std::string("av_hwdevice_ctx_create failed, ret = ") + av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
c_->hw_device_ctx = av_buffer_ref(hw_device_ctx_);
|
||||
c_->get_format = get_hw_format;
|
||||
|
||||
AVBufferRef *frames_ref = av_hwframe_ctx_alloc(c_->hw_device_ctx);
|
||||
if (!frames_ref) {
|
||||
set_last_error(std::string("av_hwframe_ctx_alloc failed"));
|
||||
return false;
|
||||
}
|
||||
AVHWFramesContext *frames_ctx = (AVHWFramesContext *)frames_ref->data;
|
||||
frames_ctx->format = hw_pixfmt_;
|
||||
frames_ctx->sw_format = sw_pixfmt_;
|
||||
frames_ctx->width = width_;
|
||||
frames_ctx->height = height_;
|
||||
frames_ctx->initial_pool_size = 8;
|
||||
ret = av_hwframe_ctx_init(frames_ref);
|
||||
if (ret < 0) {
|
||||
av_buffer_unref(&frames_ref);
|
||||
set_last_error(std::string("av_hwframe_ctx_init failed, ret = ") + av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
c_->hw_frames_ctx = av_buffer_ref(frames_ref);
|
||||
av_buffer_unref(&frames_ref);
|
||||
}
|
||||
|
||||
if ((ret = avcodec_open2(c_, codec, NULL)) < 0) {
|
||||
set_last_error(std::string("avcodec_open2 failed, ret = ") + av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
pkt_ = av_packet_alloc();
|
||||
frame_ = av_frame_alloc();
|
||||
sw_frame_ = av_frame_alloc();
|
||||
if (!pkt_ || !frame_ || !sw_frame_) {
|
||||
set_last_error(std::string("Failed to allocate packet/frame"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int decode(const uint8_t *data, int length, const void *obj) {
|
||||
g_last_error.clear();
|
||||
int ret = 0;
|
||||
if (!c_ || !pkt_ || !frame_) {
|
||||
set_last_error(std::string("Decoder not initialized"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
av_packet_unref(pkt_);
|
||||
ret = av_new_packet(pkt_, length);
|
||||
if (ret < 0) {
|
||||
set_last_error(std::string("av_new_packet failed, ret = ") + av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
memcpy(pkt_->data, data, length);
|
||||
pkt_->size = length;
|
||||
|
||||
ret = avcodec_send_packet(c_, pkt_);
|
||||
av_packet_unref(pkt_);
|
||||
if (ret < 0) {
|
||||
set_last_error(std::string("avcodec_send_packet failed, ret = ") + av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
ret = avcodec_receive_frame(c_, frame_);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
set_last_error(std::string("avcodec_receive_frame failed, ret = ") + av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
AVFrame *out = frame_;
|
||||
if (frame_->format == hw_pixfmt_) {
|
||||
av_frame_unref(sw_frame_);
|
||||
ret = av_hwframe_transfer_data(sw_frame_, frame_, 0);
|
||||
if (ret < 0) {
|
||||
set_last_error(std::string("av_hwframe_transfer_data failed, ret = ") + av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
out = sw_frame_;
|
||||
}
|
||||
|
||||
int buf_size =
|
||||
av_image_get_buffer_size((AVPixelFormat)out->format, out->width, out->height, 1);
|
||||
if (buf_size < 0) {
|
||||
set_last_error(std::string("av_image_get_buffer_size failed, ret = ") + av_err2str(buf_size));
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buf(buf_size);
|
||||
ret = av_image_copy_to_buffer(buf.data(), buf_size,
|
||||
(const uint8_t *const *)out->data, out->linesize,
|
||||
(AVPixelFormat)out->format, out->width, out->height, 1);
|
||||
if (ret < 0) {
|
||||
set_last_error(std::string("av_image_copy_to_buffer failed, ret = ") + av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (callback_) {
|
||||
callback_(buf.data(), buf_size, out->width, out->height, out->format, obj);
|
||||
}
|
||||
|
||||
av_frame_unref(frame_);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fini() {
|
||||
if (pkt_) {
|
||||
av_packet_free(&pkt_);
|
||||
}
|
||||
if (frame_) {
|
||||
av_frame_free(&frame_);
|
||||
}
|
||||
if (sw_frame_) {
|
||||
av_frame_free(&sw_frame_);
|
||||
}
|
||||
if (c_) {
|
||||
avcodec_free_context(&c_);
|
||||
}
|
||||
if (hw_device_ctx_) {
|
||||
av_buffer_unref(&hw_device_ctx_);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
extern "C" void *ffmpeg_ram_new_decoder(const char *name, int width, int height,
|
||||
int sw_pixfmt, int thread_count,
|
||||
RamDecodeCallback callback) {
|
||||
FFmpegRamDecoder *dec =
|
||||
new FFmpegRamDecoder(name, width, height, sw_pixfmt, thread_count, callback);
|
||||
if (!dec->init()) {
|
||||
dec->fini();
|
||||
delete dec;
|
||||
return NULL;
|
||||
}
|
||||
return dec;
|
||||
}
|
||||
|
||||
extern "C" int ffmpeg_ram_decode(void *decoder, const uint8_t *data, int length,
|
||||
const void *obj) {
|
||||
FFmpegRamDecoder *dec = reinterpret_cast<FFmpegRamDecoder *>(decoder);
|
||||
if (!dec) {
|
||||
return -1;
|
||||
}
|
||||
return dec->decode(data, length, obj);
|
||||
}
|
||||
|
||||
extern "C" void ffmpeg_ram_free_decoder(void *decoder) {
|
||||
FFmpegRamDecoder *dec = reinterpret_cast<FFmpegRamDecoder *>(decoder);
|
||||
if (!dec) {
|
||||
return;
|
||||
}
|
||||
dec->fini();
|
||||
delete dec;
|
||||
}
|
||||
|
||||
extern "C" const char *ffmpeg_ram_last_error(void) {
|
||||
return g_last_error.c_str();
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
typedef void (*RamEncodeCallback)(const uint8_t *data, int len, int64_t pts,
|
||||
int key, const void *obj);
|
||||
typedef void (*RamDecodeCallback)(const uint8_t *data, int len, int width,
|
||||
int height, int pixfmt, const void *obj);
|
||||
|
||||
void *ffmpeg_ram_new_encoder(const char *name, const char *mc_name, int width,
|
||||
int height, int pixfmt, int align, int fps,
|
||||
@@ -23,4 +25,12 @@ int ffmpeg_ram_get_linesize_offset_length(int pix_fmt, int width, int height,
|
||||
int ffmpeg_ram_set_bitrate(void *encoder, int kbs);
|
||||
void ffmpeg_ram_request_keyframe(void *encoder);
|
||||
|
||||
void *ffmpeg_ram_new_decoder(const char *name, int width, int height,
|
||||
int sw_pixfmt, int thread_count,
|
||||
RamDecodeCallback callback);
|
||||
int ffmpeg_ram_decode(void *decoder, const uint8_t *data, int length,
|
||||
const void *obj);
|
||||
void ffmpeg_ram_free_decoder(void *decoder);
|
||||
const char *ffmpeg_ram_last_error(void);
|
||||
|
||||
#endif // FFMPEG_RAM_FFI_H
|
||||
|
||||
127
libs/hwcodec/src/ffmpeg_ram/decode.rs
Normal file
127
libs/hwcodec/src/ffmpeg_ram/decode.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use crate::{
|
||||
ffmpeg::{init_av_log, AVPixelFormat},
|
||||
ffmpeg_ram::{
|
||||
ffmpeg_ram_decode, ffmpeg_ram_free_decoder, ffmpeg_ram_last_error,
|
||||
ffmpeg_ram_new_decoder,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
ffi::{c_void, CString},
|
||||
os::raw::c_int,
|
||||
slice,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DecodeContext {
|
||||
pub name: String,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub sw_pixfmt: AVPixelFormat,
|
||||
pub thread_count: i32,
|
||||
}
|
||||
|
||||
pub struct DecodeFrame {
|
||||
pub data: Vec<u8>,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub pixfmt: AVPixelFormat,
|
||||
}
|
||||
|
||||
pub struct Decoder {
|
||||
codec: *mut c_void,
|
||||
frames: *mut Vec<DecodeFrame>,
|
||||
pub ctx: DecodeContext,
|
||||
}
|
||||
|
||||
// Safety: Decoder is only accessed through higher-level synchronization
|
||||
// (a tokio::Mutex in the video pipeline). It is never accessed concurrently,
|
||||
// but may be moved across threads; the underlying FFmpeg RAM decoder state
|
||||
// is thread-confined per instance, so Send (but not Sync) is acceptable.
|
||||
unsafe impl Send for Decoder {}
|
||||
|
||||
impl Decoder {
|
||||
pub fn new(ctx: DecodeContext) -> Result<Self, ()> {
|
||||
init_av_log();
|
||||
unsafe {
|
||||
let codec = ffmpeg_ram_new_decoder(
|
||||
CString::new(ctx.name.as_str()).map_err(|_| ())?.as_ptr(),
|
||||
ctx.width,
|
||||
ctx.height,
|
||||
ctx.sw_pixfmt as c_int,
|
||||
ctx.thread_count,
|
||||
Some(Decoder::callback),
|
||||
);
|
||||
if codec.is_null() {
|
||||
let msg = last_error_message();
|
||||
if !msg.is_empty() {
|
||||
log::error!("ffmpeg_ram_new_decoder failed: {}", msg);
|
||||
}
|
||||
return Err(());
|
||||
}
|
||||
Ok(Decoder {
|
||||
codec,
|
||||
frames: Box::into_raw(Box::new(Vec::<DecodeFrame>::new())),
|
||||
ctx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(&mut self, data: &[u8]) -> Result<&mut Vec<DecodeFrame>, i32> {
|
||||
unsafe {
|
||||
(&mut *self.frames).clear();
|
||||
let ret = ffmpeg_ram_decode(
|
||||
self.codec,
|
||||
data.as_ptr(),
|
||||
data.len() as c_int,
|
||||
self.frames as *const _ as *const c_void,
|
||||
);
|
||||
if ret != 0 {
|
||||
let msg = last_error_message();
|
||||
if !msg.is_empty() {
|
||||
log::error!("ffmpeg_ram_decode failed: {}", msg);
|
||||
}
|
||||
return Err(ret);
|
||||
}
|
||||
Ok(&mut *self.frames)
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn callback(
|
||||
data: *const u8,
|
||||
size: c_int,
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
pixfmt: c_int,
|
||||
obj: *const c_void,
|
||||
) {
|
||||
unsafe {
|
||||
let frames = &mut *(obj as *mut Vec<DecodeFrame>);
|
||||
frames.push(DecodeFrame {
|
||||
data: slice::from_raw_parts(data, size as usize).to_vec(),
|
||||
width,
|
||||
height,
|
||||
pixfmt: std::mem::transmute::<i32, AVPixelFormat>(pixfmt),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Decoder {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ffmpeg_ram_free_decoder(self.codec);
|
||||
drop(Box::from_raw(self.frames));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn last_error_message() -> String {
|
||||
unsafe {
|
||||
let ptr = ffmpeg_ram_last_error();
|
||||
if ptr.is_null() {
|
||||
return String::new();
|
||||
}
|
||||
let cstr = std::ffi::CStr::from_ptr(ptr);
|
||||
cstr.to_string_lossy().to_string()
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,47 @@ use std::ffi::c_int;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/ffmpeg_ram_ffi.rs"));
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp"))]
|
||||
pub mod decode;
|
||||
|
||||
// Provide a small stub on non-ARM builds so dependents can still compile, but decoder
|
||||
// construction will fail (since the C++ RKMPP decoder isn't built/linked).
|
||||
#[cfg(not(any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp")))]
|
||||
pub mod decode {
|
||||
use crate::ffmpeg::AVPixelFormat;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DecodeContext {
|
||||
pub name: String,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub sw_pixfmt: AVPixelFormat,
|
||||
pub thread_count: i32,
|
||||
}
|
||||
|
||||
pub struct DecodeFrame {
|
||||
pub data: Vec<u8>,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub pixfmt: AVPixelFormat,
|
||||
}
|
||||
|
||||
pub struct Decoder {
|
||||
pub ctx: DecodeContext,
|
||||
}
|
||||
|
||||
impl Decoder {
|
||||
pub fn new(ctx: DecodeContext) -> Result<Self, ()> {
|
||||
let _ = ctx;
|
||||
Err(())
|
||||
}
|
||||
|
||||
pub fn decode(&mut self, _data: &[u8]) -> Result<&mut Vec<DecodeFrame>, i32> {
|
||||
Err(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod encode;
|
||||
|
||||
pub enum Priority {
|
||||
|
||||
Reference in New Issue
Block a user