当前位置: 首页 > news >正文

rockship/x86平台下的视频转码h264->svac

背景

需要将h264/h265编码转成svac

思路

先将采集过来的h264或者h265进行分析,看是否可以进行转码,如果可行,则交由中星微的转码设备进行svac转码,如果是rockship平台的话,因为要求性能要尽可能的高,就不经由ffmpeg来调用mpp了,而是直接调用mpp接口来进行h264->h265的转码,如果是普通的x86平台的话,考虑到通用性,用的ffmpg的库来进行的转码,调用svac的软库来进行转码,需要将这两种平台的差异抽象出来,方便上层应用来调用

代码

//lencoder.h #ifndef _LENCODER_H_ #define _LENCODER_H_ #include <stdint.h> #ifdef __cplusplus extern "C" { #endif enum H265_FRAME_TYPE { TRAIL_R = 1, BLA_W_LP = 16, BLA_W_RADL = 17, BLA_N_LP = 18, IDR_W_RADL = 19, IDR_N_LP = 20, VPS_NUT = 32, SPS_NUT = 33, PPS_NUT = 34, PREFIX_SEI_NUT = 39, SUFFIX_SEI_NUT = 40, }; enum H264_FRAME_TYPE { NAL_SLICE = 1, NAL_SLIC_DPA = 2, NAL_SLICE_DPB = 3, NAL_SLICE_DPC = 4, NAL_SLICE_IDR = 5, NAL_SEI = 6, NAL_SPS = 7, NAL_PPS = 8, NAL_AUD = 9, NAL_FILLER = 12, }; #define LENCODER_STATUS_OK 0X00000000 #define LENCODER_STATUS_ERR 0X01000000 #define LENCODER_NO_SPACE_ERR LENCODER_STATUS_ERR + 1 #define LENCODER_CREATE_ENCODER_ERR LENCODER_STATUS_ERR + 2 #define LENCODER_PREFETCH_ENCODER_ERR LENCODER_STATUS_ERR + 3 #define LENCODER_NO_IDLE_ERR LENCODER_STATUS_ERR + 4 #define LENCODER_TRANSOCER_ERR LENCODER_STATUS_ERR + 5 #define LENCODER_UNKNOWN_NALU_TYPE LENCODER_STATUS_ERR + 6 #define LENCODER_CONTINUING LENCODER_STATUS_ERR + 7 #define LENCODER_LOST_DEVICE LENCODER_STATUS_ERR + 8 #define LENCODER_OPEN_FAILED_DEVICE LENCODER_STATUS_ERR + 9 #define LENCODER_UNKNOWN_ENCODER_TYPE LENCODER_STATUS_ERR + 10 #define LENCODER_EMPTY_FRAME_TYPE LENCODER_STATUS_ERR + 11 #define LENCODER_MPP_CREATE_ERR LENCODER_STATUS_ERR + 12 #define LENCODER_MPP_WRITE_ERR LENCODER_STATUS_ERR + 13 #define LENCODER_MPP_GET_NULL LENCODER_STATUS_ERR + 14 #define LENCODER_MPP_WRITE_FULL LENCODER_STATUS_ERR + 15 #define LENCODER_LOAD_CFG_ERR LENCODER_STATUS_ERR + 16 #define LENCODER_LOAD_CFG_VALUE_ERR LENCODER_STATUS_ERR + 17 typedef struct lencoder_context_t * lencoder_context; typedef struct lencoder_mpp_context_t * lencoder_mpp_context; typedef struct lencoder_t * lencoder; /* typedef struct lencoder_frame_t { int frame_type; //1 I帧 2 P帧 unsigned char *frame; int frame_len; }lencoder_frame; */ int lencoder_init(lencoder *coder); int lencoder_maxnum_get(lencoder coder); int lencoder_open_ext(lencoder coder, lencoder_context *context, int fmt, unsigned char *pbuf, int buflen, int bit_rate, int fps, int gop); int lencoder_open(lencoder coder, lencoder_context *context, int fmt, unsigned char *pbuf, int buflen); //1 I帧 2 P帧 //while(lencoder_get_frame(ctx, &frame, &frame_len, &frame_type) == LENCODER_STATUS_OK) { // //free(frame); //} int lencoder_get_frame_ext(lencoder_context context, unsigned char **frame, unsigned int *frame_len, int *frame_type, int *iskey, int64_t *pts, int64_t *dts); int lencoder_get_frame(lencoder_context context, unsigned char **frame, unsigned int *frame_len, int *frame_type); int lencoder_transcode(lencoder_context context, unsigned char *in, int in_len); void lencoder_close(lencoder coder, lencoder_context *context); int lencoder_mpp_open(lencoder coder, lencoder_mpp_context *context); // @brief 指定待转码的编码格式以及解码后再编码格式 // @param coder 句柄 // @param context 转码会话 // @param src_fmt 1.H264 2.H265 // @param dst_fmt 1.H264 2.H265 // @return LENCODER_STATUS_OK 否则返回错误码 int lencoder_mpp_open_ext(lencoder coder, lencoder_mpp_context *context, int src_fmt, int dst_fmt); int lencoder_mpp_get_frame(lencoder_mpp_context context, unsigned char **out, int *out_len); int lencoder_mpp_transcode(lencoder_mpp_context context, unsigned char *in, int in_len); void lencoder_mpp_close(lencoder_mpp_context *context); int lencoder_decoder_get(lencoder coder); void lencoder_destroy(lencoder *coder); #ifdef __cplusplus } #endif #endif

#include <lencoder.h>
#include <locale.h>

#define LENCODER_CFG_FILE “/usr/local/media_encoder.json”

#if defined(i386) || defined(_M_IX86) || defined(x86_64)
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include “x86/soft_encoder.h”
#else
#include “arm/vimicro_encoder.h”
#include “arm/mpp_encoder.h”
#include <json-c/json.h>
#endif

#if defined(i386) || defined(_M_IX86) || defined(x86_64)
struct lencoder_t {
encoder_soft s;
unsigned char *resive;
};

struct lencoder_context_t {
int first;
encoder_soft_context ctx;
};
#else
struct lencoder_context_t {
usb_list *usb;
int first;
int current_num;
int hasread;
svac_frame *frameout;
int frame_count;
USBDEC_VIDEO_FORMAT srcfmt;
};
struct lencoder_mpp_context_t {
mpp_encoder encoder;
int first;
int nobuffer;
mpp_encoder decoder;
};

struct lencoder_t {
encoder_usb vimirco;
int maxnum;
};
#endif

#if defined(i386) || defined(_M_IX86) || defined(x86_64)
#else
int load_media_cfg(mpp_encoder coder)
{
int ret = LENCODER_STATUS_OK;
struct json_object *cfg = NULL, *watertext = NULL;
uint8_t text_water_enable, text_r, text_g, text_b;
uint8_t text_fontsize, text_x, text_y;
char text_context[128];
char text_font_file[64];

cfg = json_object_from_file(LENCODER_CFG_FILE); if(!cfg) { return LENCODER_LOAD_CFG_ERR; } if(json_object_get_type(cfg) != json_type_object) { json_object_put(cfg); return LENCODER_LOAD_CFG_VALUE_ERR; } watertext = json_object_object_get(cfg, "text"); if(json_object_get_type(watertext) != json_type_object) { json_object_put(watertext); json_object_put(cfg); return LENCODER_LOAD_CFG_VALUE_ERR; } json_object_object_foreach(watertext, key, val) { if(!key) { continue; } if(strcmp(key, "enable") == 0) { text_water_enable = (uint8_t)json_object_get_int(val); } if(strcmp(key, "color_r") == 0) { text_r = (uint8_t)json_object_get_int(val); } if(strcmp(key, "color_g") == 0) { text_g = (uint8_t)json_object_get_int(val); } if(strcmp(key, "color_b") == 0) { text_b = (uint8_t)json_object_get_int(val); } if(strcmp(key, "content") == 0) { snprintf(text_context, 128, "%s", json_object_get_string(val)); } if(strcmp(key, "position_x") == 0) { text_x = (uint8_t)json_object_get_int(val); } if(strcmp(key, "position_y") == 0) { text_y = (uint8_t)json_object_get_int(val); } if(strcmp(key, "font_size") == 0) { text_fontsize = (uint8_t)json_object_get_int(val); } if(strcmp(key, "font_file") == 0) { strcpy(text_font_file, json_object_get_string(val)); } } encoder_mpp_set_text_watertext(coder, text_context, text_r, text_g, text_b, text_fontsize, text_x, text_y, text_font_file, text_water_enable); json_object_put(cfg); return ret;

}
#endif

int lencoder_init(lencoder *coder)
{
int ret = LENCODER_STATUS_OK, max_num = 0;
lencoder t;

setlocale(LC_CTYPE, ""); t = (lencoder)malloc(sizeof(struct lencoder_t)); if (!t) { ret = LENCODER_NO_SPACE_ERR; goto EndP; } memset(t, 0, sizeof(struct lencoder_t));

#if defined(i386) || defined(_M_IX86) || defined(x86_64)
ret = soft_encoder_create(&t->s);
if(ret != SOFT_ENCODER_OK) {
ret = LENCODER_CREATE_ENCODER_ERR;
free(t);
goto EndP;
}
#else
ret = vimicro_encoder_create(&(t->vimirco), &max_num);
if (ret < 1) {
free(t);
ret = LENCODER_CREATE_ENCODER_ERR;
goto EndP;
}

t->maxnum = max_num;

#endif

*coder = t; ret = LENCODER_STATUS_OK;

EndP:
return ret;
}

int lencoder_maxnum_get(lencoder coder)
{
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
return 65535;
#else
return coder->maxnum;
#endif
};

int lencoder_mpp_open(lencoder coder, lencoder_mpp_context *context)
{
int ret = LENCODER_STATUS_OK;
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
#else
MPP_RET err;
lencoder_mpp_context ctx;

ctx = (lencoder_mpp_context)malloc(sizeof(struct lencoder_mpp_context_t)); if(!ctx) { ret = LENCODER_NO_SPACE_ERR; goto EndP; } memset(ctx, 0, sizeof(struct lencoder_mpp_context_t)); err = encoder_mpp_create(&(ctx->decoder), MPP_CTX_DEC, MPP_VIDEO_CodingAVC); if(err != MPP_OK) { ret = LENCODER_MPP_CREATE_ERR; free(ctx); goto EndP; } err = encoder_mpp_create(&(ctx->encoder), MPP_CTX_ENC, MPP_VIDEO_CodingHEVC); if(err != MPP_OK) { ret = LENCODER_MPP_CREATE_ERR; free(ctx); encoder_mpp_destroy(&(ctx->decoder)); goto EndP; } ret = load_media_cfg(ctx->encoder); if(ret != LENCODER_STATUS_OK) { goto EndP; } ctx->first = 1; *context = ctx; printf("context is %p, *context is %p\n", context, *context); ret = LENCODER_STATUS_OK;

EndP:
#endif
return ret;
}

// src_fmt 1 h264 dst_fmt 2 h265
int lencoder_mpp_open_ext(lencoder coder, lencoder_mpp_context *context, int src_fmt, int dst_fmt)
{
int ret = LENCODER_STATUS_OK;
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
#else
MPP_RET err;
lencoder_mpp_context ctx;
MppCodingType decode_type, encode_type;

ctx = (lencoder_mpp_context)malloc(sizeof(struct lencoder_mpp_context_t)); if(!ctx) { ret = LENCODER_NO_SPACE_ERR; goto EndP; } memset(ctx, 0, sizeof(struct lencoder_mpp_context_t)); if(src_fmt == 1) { decode_type = MPP_VIDEO_CodingAVC; } else { decode_type = MPP_VIDEO_CodingHEVC; } if(dst_fmt == 1) { encode_type = MPP_VIDEO_CodingAVC; } else { encode_type = MPP_VIDEO_CodingHEVC; } err = encoder_mpp_create(&(ctx->decoder), MPP_CTX_DEC, decode_type); if(err != MPP_OK) { ret = LENCODER_MPP_CREATE_ERR; free(ctx); goto EndP; } err = encoder_mpp_create(&(ctx->encoder), MPP_CTX_ENC, encode_type); if(err != MPP_OK) { ret = LENCODER_MPP_CREATE_ERR; free(ctx); encoder_mpp_destroy(&(ctx->decoder)); goto EndP; } ret = load_media_cfg(ctx->encoder); if(ret != LENCODER_STATUS_OK) { goto EndP; } ctx->first = 1; *context = ctx; printf("context is %p, *context is %p\n", context, *context); ret = LENCODER_STATUS_OK;

EndP:
#endif
return ret;
}

int lencoder_mpp_get_frame(lencoder_mpp_context context, unsigned char **out, int *out_len)
{
int ret = LENCODER_STATUS_OK;
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
#else
MPP_RET err = MPP_OK;

MppFrame frame = NULL; err = encoder_mpp_get_frame(context->decoder, &frame); if(frame == NULL) { ret = LENCODER_MPP_GET_NULL; goto EndP; } if(context->first) { err = encoder_mpp_cfg_setup(context->encoder, frame); if(err != MPP_OK) { ret = LENCODER_MPP_WRITE_ERR; encoder_mpp_destroy(&(context->encoder)); encoder_mpp_destroy(&(context->decoder)); goto EndP; } context->first = 0; } err = encoder_mpp_encode(context->encoder, frame, out, out_len); if(err != MPP_OK) { ret = LENCODER_MPP_WRITE_ERR; goto EndP; }

EndP:
if(frame) {
mpp_frame_deinit(&frame);
}
#endif
return ret;
}

int lencoder_mpp_transcode(lencoder_mpp_context context, unsigned char *in, int in_len)
{
int ret = LENCODER_STATUS_OK;
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
#else
MPP_RET err;

err = encoder_mpp_write_data(context->decoder, in, in_len); if(err != MPP_OK) { if(err == MPP_ERR_BUFFER_FULL) { ret = LENCODER_MPP_WRITE_FULL; } else { ret = LENCODER_MPP_WRITE_ERR; } }

#endif
return ret;
}

void lencoder_mpp_close(lencoder_mpp_context *context)
{
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
#else
encoder_mpp_destroy(&(*context)->decoder);
encoder_mpp_destroy(&(*context)->encoder);
free(*context);
#endif
}

//0 转码棒 1转码卡 2 CPU软解
int lencoder_decoder_get(lencoder coder)
{
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
return 2;
#else
return vimicro_encoder_device_type(coder->vimirco);
#endif
}

// fmt 3.SVAC2转H264
int lencoder_open_ext(lencoder coder, lencoder_context *context, int fmt, unsigned char *pbuf, int buflen, int bit_rate, int fps, int gop)
{
int ret = LENCODER_STATUS_OK, idle_num;
lencoder_context ctx;

ctx = (lencoder_context)malloc(sizeof(struct lencoder_context_t)); if(!ctx) { ret = LENCODER_NO_SPACE_ERR; goto EndP; } memset(ctx, 0, sizeof(struct lencoder_context_t));

#if defined(i386) || defined(_M_IX86) || defined(x86_64)
switch(fmt) {
case 3:
ret = soft_encoder_open(&ctx->ctx, bit_rate, fps, gop, pbuf, buflen);
if(ret != SOFT_ENCODER_OK) {
printf(“open soft encoder failed, %02x\n”, ret);
ret = LENCODER_OPEN_FAILED_DEVICE;
goto EndP;
}
break;
default:
ret = LENCODER_UNKNOWN_ENCODER_TYPE;
}
#else
#endif
*context = ctx;
EndP:
if(ret != LENCODER_STATUS_OK) {
free(ctx);
}
return ret;
}

// fmt 1.H264转SVAC2 2.H265转SVAC2 3.SVAC2转H264
int lencoder_open(lencoder coder, lencoder_context *context, int fmt, unsigned char *pbuf, int buflen)
{
int ret = LENCODER_STATUS_OK, idle_num;
lencoder_context ctx;

ctx = (lencoder_context)malloc(sizeof(struct lencoder_context_t)); if(!ctx) { ret = LENCODER_NO_SPACE_ERR; goto EndP; } memset(ctx, 0, sizeof(struct lencoder_context_t));

#if defined(i386) || defined(_M_IX86) || defined(x86_64)
switch(fmt) {
case 3:
ret = soft_encoder_open(&ctx->ctx, 512 * 1024, 25, 25, pbuf, buflen);
if(ret != SOFT_ENCODER_OK) {
printf(“open soft encoder failed, %02x\n”, ret);
ret = LENCODER_OPEN_FAILED_DEVICE;
goto EndP;
}
break;
default:
printf(“Unsuport fmt %d\n”, fmt);
ret = LENCODER_UNKNOWN_ENCODER_TYPE;
}
#else
vimicro_encoder_idle_get(coder->vimirco, &idle_num);
if (idle_num < 1) {
ret = LENCODER_NO_IDLE_ERR;
goto EndP;
}

ret = vimicro_encoder_prefetch(coder->vimirco, idle_num, pbuf, buflen); if (ret) { ret = LENCODER_PREFETCH_ENCODER_ERR; goto EndP; } switch(fmt) { case 1: //H264 ctx->usb = vimicro_encoder_open(coder->vimirco, idle_num, USBDEC_VIDEO_H264); ctx->srcfmt = USBDEC_VIDEO_H264; break; case 2: //H265 ctx->usb = vimicro_encoder_open(coder->vimirco, idle_num, USBDEC_VIDEO_H265); ctx->srcfmt = USBDEC_VIDEO_H265; break; default: printf("err video format:%d\n", fmt); ret = LENCODER_UNKNOWN_ENCODER_TYPE; goto EndP; } if(ctx->usb == NULL) { ret = LENCODER_OPEN_FAILED_DEVICE; goto EndP; } ctx->frameout = (svac_frame *)malloc(sizeof(svac_frame) * 1024); ctx->current_num = idle_num; ctx->hasread = -1;

#endif
*context = ctx;
EndP:
if(ret != LENCODER_STATUS_OK) {
free(ctx);
*context = NULL;
}
return ret;
}

int lencoder_transcode(lencoder_context context, unsigned char *in, int in_len)
{
int ret = LENCODER_STATUS_OK, frame_type, i = 0;

#if defined(i386) || defined(_M_IX86) || defined(x86_64)
ret = soft_encoder_decode_svac(context->ctx, in, in_len);
if(ret != SOFT_ENCODER_OK) {
printf(“soft_encoder_decode failed %02x\n”, ret);
ret = LENCODER_TRANSOCER_ERR;
goto EndP;
}
#else
unsigned char c;

c = (in[2] == 1 ? in[3] : in[4]); switch(context->srcfmt) { case USBDEC_VIDEO_H264: switch(c & 0x1f) { case NAL_SLICE: frame_type = 2; break; case NAL_SLICE_IDR: case NAL_SEI: case NAL_SPS: case NAL_PPS: frame_type = 1; break; default: ret = LENCODER_UNKNOWN_NALU_TYPE; goto EndP; } break; case USBDEC_VIDEO_H265: switch((c & 0x7e) >> 1) { case TRAIL_R: frame_type = 2; break; case BLA_W_LP: case BLA_W_RADL: case BLA_N_LP: case IDR_W_RADL: case IDR_N_LP: case VPS_NUT: case SPS_NUT: case PPS_NUT: case PREFIX_SEI_NUT: case SUFFIX_SEI_NUT: frame_type = 1; break; default: ret = LENCODER_UNKNOWN_NALU_TYPE; goto EndP; } break; default: break; } for(i = 0; i < context->frame_count; i ++) { free(context->frameout[i].buf); context->frameout[i].buf = NULL; } ret = vimicro_encoder_transcode(context->usb, context->current_num, in, in_len, frame_type, context->frameout, &(context->frame_count)); if(context->frame_count > 0) { context->hasread = 0; } else { context->hasread = -1; } if(ret == VMUSB_DEVICE_LOST) { ret = LENCODER_LOST_DEVICE; goto EndP; }

#endif
ret = LENCODER_STATUS_OK;
EndP:
return ret;
}

void lencoder_close(lencoder coder, lencoder_context *context)
{
lencoder_context ctx = *context;
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
soft_encoder_close(ctx->ctx);
#else
vimicro_encoder_close(ctx->usb, ctx->current_num);
if(ctx->frameout) {
free(ctx->frameout);
}
#endif
if(ctx) free(ctx);
*context = NULL;
}

void lencoder_destroy(lencoder *coder)
{
lencoder tt = *coder;
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
soft_encoder_destroy(tt->s);
#else
vimicro_encoder_destroy(&(tt->vimirco));
tt->maxnum = 0;
#endif
free(tt);

*coder = NULL;

}

int lencoder_get_frame_ext(lencoder_context context, unsigned char **frame, unsigned int *frame_len, int *frame_type, int *iskey, int64_t *pts, int64_t *dts)
{
int ret = LENCODER_STATUS_OK;
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
ret = soft_encoder_get_h264(context->ctx, frame, (size_t *)frame_len, iskey, pts, dts);
if(ret != SOFT_ENCODER_OK) {
//printf(“soft encoder get h264 failed %02x\n”, ret);
ret = LENCODER_CONTINUING;
}
else {
ret = LENCODER_STATUS_OK;
}
#else
#endif
return ret;
}

int lencoder_get_frame(lencoder_context context, unsigned char **frame, unsigned int *frame_len, int *frame_type)
{
int ret = LENCODER_STATUS_OK;
#if defined(i386) || defined(_M_IX86) || defined(x86_64)
int iskey;
int64_t pts, dts;
ret = soft_encoder_get_h264(context->ctx, frame, (size_t *)frame_len, &iskey, &pts, &dts);
if(ret != SOFT_ENCODER_OK) {
//printf(“soft encoder get h264 failed %02x\n”, ret);
ret = LENCODER_CONTINUING;
}
else {
ret = LENCODER_STATUS_OK;
}
#else
svac_frame *p;

if(context->hasread == -1) { return LENCODER_EMPTY_FRAME_TYPE; } //printf("has read before change is %d\n", context->hasread); p = &context->frameout[context->hasread]; *frame = (unsigned char *)malloc(p->buf_len); if(*frame == NULL) { return LENCODER_NO_SPACE_ERR; } //printf("frame is %p, p is %p, buflen is %d, count is %d, base is %p\n", *frame, p, p->buf_len, context->frame_count, context->frameout); memcpy(*frame, p->buf, p->buf_len); //printf("end-------------------\n"); *frame_len = p->buf_len; *frame_type = p->frame_type; if(context->hasread < (context->frame_count - 1)) { context->hasread ++; } else { context->hasread = -1; }

#endif
return ret;
}

http://www.cnnetsun.cn/news/100475.html

相关文章:

  • 创业者必看!深圳注册代办公司靠谱之选-权威盘点
  • 【异常检测】AdaptCLIP:适配CLIP用于通用视觉异常检测
  • 结合ASR构建完整对话系统:EmotiVoice的角色定位
  • EmotiVoice语音情感强度可视化分析工具介绍
  • 对长上下文能力有不同要求,怎么选择合适的模型?
  • 工程期刊投稿全攻略:高效发表指南
  • vue基于springboot的农业合作社果蔬批发农产品商城信息管理系统的设计与实现
  • vue基于springboot的社区医疗保健健康预警监控系统的设计与实现
  • EmotiVoice能否生成方言情感语音?粤语、川话实测
  • 什么是高带宽内存3(HBM3)?关于HBM3的架构、应用场景和性能表现
  • vue基于springboot的连锁超市销售商城 进销存员工与分析系统的设计与实现
  • AI率一夜飙红后,我用这套方法把论文拉回安全线(降AI率实测版)
  • vue基于springboot的基于建筑物识别的无人驾驶车辆路径规划系统
  • 启天 M 系列 Smart Power On/Fast boot 置灰?2 步解锁修改权限!
  • 告别繁琐问卷设计!百考通AI智能助手,5分钟生成专业调研问卷
  • 百考通AI:你的智能学术助手,让毕业论文写作化繁为简
  • IntelliJ IDEA 2025.3 正式发布
  • MyBatis-Flex 来了!完爆MyBatis-Plus?
  • 神经紧张素受体SORT1
  • 高盐高铵根工业废水去除重金属
  • 某211高校讲师晒工资条,网友:公积金数额令人瞩目...
  • Nature Electronics 一种用于多模态皮肤信号监测的柔性触觉接口
  • 小鼠T细胞激活:如何系统解析其发育分化与免疫功能表征?
  • 基于springboot和vue的民航飞机票务管理系统设计与实现
  • 2025年12月-2026年4月,计算机领域涵盖的前言学术会议推荐!
  • 基于单片机的智能镜子系统设计(有完整资料)
  • 国产化替代SSD的标杆之路:天硕TOPSSD以自主可控存储解决方案重塑高端工业存储格局
  • EmotiVoice本地化部署优势:数据安全与响应效率兼得
  • 【Java毕设全套源码+文档】基于springboot的数据库课程在线教学系统设计与实现(丰富项目+远程调试+讲解+定制)
  • 【Java毕设全套源码+文档】基于springboot的实验室安全考试系统设计与实现(丰富项目+远程调试+讲解+定制)