【MediaFoundation】OpenCV VideoCapture 读取音频源码
OpenCV 读取音频代码实例
在windows7 以及OpenCV4 过后可以使用 CAP_MSMF 读取音频,但是OpenCV没有播放音频的API。代码示例如下。 本文解析OpenCVCAP_MSMF 进行文件、设备的 音频读取,学习MediaFoundation 的使用。
#include <opencv2/core.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;int main(int argc, const char** argv)
{Mat videoFrame;Mat audioFrame;vector<vector<Mat>> audioData;VideoCapture cap;vector<int> params { CAP_PROP_AUDIO_STREAM, 0,CAP_PROP_VIDEO_STREAM, -1,CAP_PROP_AUDIO_DATA_DEPTH, CV_32F };//cap.open(file, CAP_MSMF, params);// 打开第一个音频输入设备cap.open(0, CAP_MSMF, params);if (!cap.isOpened()){cerr << "ERROR! Can't to open file: " + file << endl;return -1;}const int audioBaseIndex = (int)cap.get(CAP_PROP_AUDIO_BASE_INDEX);const int numberOfChannels = (int)cap.get(CAP_PROP_AUDIO_TOTAL_CHANNELS);cout << "CAP_PROP_AUDIO_DATA_DEPTH: " << depthToString((int)cap.get(CAP_PROP_AUDIO_DATA_DEPTH)) << endl;cout << "CAP_PROP_AUDIO_SAMPLES_PER_SECOND: " << cap.get(CAP_PROP_AUDIO_SAMPLES_PER_SECOND) << endl;cout << "CAP_PROP_AUDIO_TOTAL_CHANNELS: " << cap.get(CAP_PROP_AUDIO_TOTAL_CHANNELS) << endl;cout << "CAP_PROP_AUDIO_TOTAL_STREAMS: " << cap.get(CAP_PROP_AUDIO_TOTAL_STREAMS) << endl;int numberOfSamples = 0;int numberOfFrames = 0;audioData.resize(numberOfChannels);mfcap::AudioOutput audioOutput;audioOutput.Open((int)cap.get(CAP_PROP_AUDIO_TOTAL_CHANNELS),(int)cap.get(CAP_PROP_AUDIO_SAMPLES_PER_SECOND),16);for (;;){if (cap.grab()){//cap.retrieve(videoFrame);std::vector<const unsigned char*> planes;planes.resize(numberOfChannels);for (int nCh = 0; nCh < numberOfChannels; nCh++){cap.retrieve(audioFrame, audioBaseIndex+nCh);if (!audioFrame.empty()){audioData[nCh].push_back(audioFrame);//planes[nCh] = audioFrame.data + nCh * audioFrame.cols;}numberOfSamples+=audioFrame.cols;}} else { break; }}cout << "Number of audio samples: " << numberOfSamples << endl<< "Number of video frames: " << numberOfFrames << endl;return 0;
}
打开设备
bool CvCapture_MSMF::open(int index, const cv::VideoCaptureParameters* params)
{// 先重置环境close();if (index < 0)return false;if (params){// 开启硬件编解码加速,这里先省略,在后面的硬件加速上学习。configureHW(*params);/* configureStream 主要是配置是否捕获音频或视频流// 如果需要捕获音频流: audioStream = 0 否者 audioStream = -1// 视频流同理,对应的变量为: videoStream*//* setAudioProperties // outputAudioFormat: 音频的位深, CV_16S 等// audioSamplesPerSecond 采样率// syncLastFrame: 是否需要音视频同步,OpenCV里面只支持视频文件的音视频同步*/if (!(configureStreams(*params) && setAudioProperties(*params)))return false;}// 仅支持打开音频流或者视频流,不能在一个对象里面打开或者都不打开。if (videoStream != -1 && audioStream != -1 || videoStream == -1 && audioStream == -1){CV_LOG_DEBUG(NULL, "Only one of the properties CAP_PROP_AUDIO_STREAM " << audioStream << " and " << CAP_PROP_VIDEO_STREAM << " must be different from -1");return false;}// DeviceList 主要检测当前系统中,视频或音频输入设备的个数,以及激活某一个设备。DeviceList devices;UINT32 count = 0;// 如果是读音频设备就枚举音频输入设备if (audioStream != -1)count = devices.read(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID);// 读视频设备同理。值得注意的是,opencv只支持读音频或视频设备// 如果都设置为0, 则默认读视频if (videoStream != -1)count = devices.read(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);if (count == 0 || static_cast<UINT32>(index) > count){CV_LOG_DEBUG(NULL, "Device " << index << " not found (total " << count << " devices)");return false;}/* getDefaultSourceConfig 这个主要设置硬件加速相关,这里可以先跳过*/_ComPtr<IMFAttributes> attr = getDefaultSourceConfig();/*SourceReaderCB 读取回调设置读取回调,当设置了读取的回调之后,MediaFoundation 读取Source 为异步模式这个Open函数是读设备的,还有其他重载函数用于读文件在OpenCV里面,读取设备使用异步模式,读取文件使用同步模式。*/_ComPtr<IMFSourceReaderCallback> cb = new SourceReaderCB();attr->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, cb.Get());// 激活目标设备_ComPtr<IMFMediaSource> src = devices.activateSource(index);if (!src.Get() || FAILED(MFCreateSourceReaderFromMediaSource(src.Get(), attr.Get(), &videoFileSource))){CV_LOG_DEBUG(NULL, "Failed to create source reader");return false;}isOpen = true;device_status = true;camid = index;readCallback = cb;duration = 0;// 选择合适的摄像头的流,比如宽高,fps.一般一个摄像头有很多个拉流的格式// 音频设备同理,比如有不同的采样率,位深等。if (configureOutput()){frameStep = captureVideoFormat.getFrameStep();}if (isOpen && !openFinalize_(params)){close();return false;}if (isOpen){if (audioStream != -1)if (!checkAudioProperties())return false;}return isOpen;
}
选择合适的设备流 configureOutput
bool CvCapture_MSMF::configureOutput()
{// 读取设备所有的流,当然也可以读第一个流if (FAILED(videoFileSource->SetStreamSelection((DWORD)MF_SOURCE_READER_ALL_STREAMS, false))){CV_LOG_WARNING(NULL, "Failed to reset streams");return false;}bool tmp = true;// 配置视频流if (videoStream != -1)tmp = (!device_status)? configureVideoOutput(MediaType(), outputVideoFormat) : configureVideoOutput(MediaType::createDefault_Video(), outputVideoFormat);// 配置音频流if (audioStream != -1)tmp &= (!device_status)? configureAudioOutput(MediaType()) : configureAudioOutput(MediaType::createDefault_Audio());return tmp;
}
配置音频流 configureAudioOutput
bool CvCapture_MSMF::configureAudioOutput(MediaType newType)
{/*FormatStorage 存储设备的流信息,以及挑选最合适的流*/FormatStorage formats;formats.read(videoFileSource.Get());std::pair<FormatStorage::MediaID, MediaType> bestMatch;formats.countNumberOfAudioStreams(numberOfAudioStreams);if (device_status)bestMatch = formats.findBestAudioFormat(newType);elsebestMatch = formats.findAudioFormatByStream(audioStream);if (bestMatch.second.isEmpty(true)){CV_LOG_DEBUG(NULL, "Can not find audio stream with requested parameters");isOpen = false;return false;}dwAudioStreamIndex = bestMatch.first.stream;dwStreamIndices.push_back(dwAudioStreamIndex);MediaType newFormat = bestMatch.second;newFormat.majorType = MFMediaType_Audio;newFormat.nSamplesPerSec = (audioSamplesPerSecond == 0) ? 44100 : audioSamplesPerSecond;switch (outputAudioFormat){case CV_8S:newFormat.subType = MFAudioFormat_PCM;newFormat.bit_per_sample = 8;break;case CV_16S:newFormat.subType = MFAudioFormat_PCM;newFormat.bit_per_sample = 16;break;case CV_32S:newFormat.subType = MFAudioFormat_PCM;newFormat.bit_per_sample = 32;case CV_32F:newFormat.subType = MFAudioFormat_Float;newFormat.bit_per_sample = 32;break;default:break;}// 初始化流return initStream(dwAudioStreamIndex, newFormat);
}
初始化流 initStream
bool CvCapture_MSMF::initStream(DWORD streamID, const MediaType mt)
{CV_LOG_DEBUG(NULL, "Init stream " << streamID << " with MediaType " << mt);_ComPtr<IMFMediaType> mediaTypesOut;if (mt.majorType == MFMediaType_Audio){captureAudioFormat = mt;mediaTypesOut = mt.createMediaType_Audio();}if (mt.majorType == MFMediaType_Video){captureVideoFormat = mt;mediaTypesOut = mt.createMediaType_Video();}if (FAILED(videoFileSource->SetStreamSelection(streamID, true))){CV_LOG_WARNING(NULL, "Failed to select stream " << streamID);return false;}HRESULT hr = videoFileSource->SetCurrentMediaType(streamID, NULL, mediaTypesOut.Get());if (hr == MF_E_TOPO_CODEC_NOT_FOUND){CV_LOG_WARNING(NULL, "Failed to set mediaType (stream " << streamID << ", " << mt << "(codec not found)");return false;}else if (hr == MF_E_INVALIDMEDIATYPE){CV_LOG_WARNING(NULL, "Failed to set mediaType (stream " << streamID << ", " << mt << "(unsupported media type)");return false;}else if (FAILED(hr)){CV_LOG_WARNING(NULL, "Failed to set mediaType (stream " << streamID << ", " << mt << "(HRESULT " << hr << ")");return false;}return true;
}
捕获音频帧 Garb()
捕获帧的回调 SourceReaderCB
OpenCV捕获设备数据,采用异步模式,需要自定义一个捕获帧的回调
// 需要继承 IMFSourceReaderCallback
class SourceReaderCB : public IMFSourceReaderCallback
{
public:
// 最大的缓冲帧数量static const size_t MSMF_READER_MAX_QUEUE_SIZE = 3;SourceReaderCB() :m_nRefCount(0), m_hEvent(CreateEvent(NULL, FALSE, FALSE, NULL)), m_bEOS(FALSE), m_hrStatus(S_OK), m_reader(NULL), m_dwStreamIndex(0){}// COM 接口必须的,感觉就是照着格式写// IUnknown methodsSTDMETHODIMP QueryInterface(REFIID iid, void** ppv) CV_OVERRIDE{
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4838)
#endifstatic const QITAB qit[] ={QITABENT(SourceReaderCB, IMFSourceReaderCallback),{ 0 },};
#ifdef _MSC_VER
#pragma warning(pop)
#endifreturn QISearch(this, qit, iid, ppv);}STDMETHODIMP_(ULONG) AddRef() CV_OVERRIDE{return InterlockedIncrement(&m_nRefCount);}STDMETHODIMP_(ULONG) Release() CV_OVERRIDE{ULONG uCount = InterlockedDecrement(&m_nRefCount);if (uCount == 0){delete this;}return uCount;}// 在 调用wait()的时候 被调用STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample) CV_OVERRIDE{HRESULT hr = 0;cv::AutoLock lock(m_mutex);if (SUCCEEDED(hrStatus)){if (pSample){CV_LOG_DEBUG(NULL, "videoio(MSMF): got frame at " << llTimestamp);// 如果缓冲的帧的数量大于最大的数量,需要弹出前面的帧if (m_capturedFrames.size() >= MSMF_READER_MAX_QUEUE_SIZE){
#if 0CV_LOG_DEBUG(NULL, "videoio(MSMF): drop frame (not processed). Timestamp=" << m_capturedFrames.front().timestamp);m_capturedFrames.pop();
#else// this branch reduces latency if we drop frames due to slow processing.// avoid fetching of already outdated frames from the queue's front.CV_LOG_DEBUG(NULL, "videoio(MSMF): drop previous frames (not processed): " << m_capturedFrames.size());std::queue<CapturedFrameInfo>().swap(m_capturedFrames); // similar to missing m_capturedFrames.clean();
#endif}m_capturedFrames.emplace(CapturedFrameInfo{ llTimestamp, _ComPtr<IMFSample>(pSample), hrStatus });}}else{CV_LOG_WARNING(NULL, "videoio(MSMF): OnReadSample() is called with error status: " << hrStatus);}if (MF_SOURCE_READERF_ENDOFSTREAM & dwStreamFlags){// Reached the end of the stream.m_bEOS = true;}m_hrStatus = hrStatus;if (FAILED(hr = m_reader->ReadSample(dwStreamIndex, 0, NULL, NULL, NULL, NULL))){CV_LOG_WARNING(NULL, "videoio(MSMF): async ReadSample() call is failed with error status: " << hr);m_bEOS = true;}if (pSample || m_bEOS){SetEvent(m_hEvent);}return S_OK;}STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) CV_OVERRIDE{return S_OK;}STDMETHODIMP OnFlush(DWORD) CV_OVERRIDE{return S_OK;}HRESULT Wait(DWORD dwMilliseconds, _ComPtr<IMFSample>& mediaSample, LONGLONG& sampleTimestamp, BOOL& pbEOS){pbEOS = FALSE;for (;;){{cv::AutoLock lock(m_mutex);pbEOS = m_bEOS && m_capturedFrames.empty();if (pbEOS)return m_hrStatus;if (!m_capturedFrames.empty()){CV_Assert(!m_capturedFrames.empty());CapturedFrameInfo frameInfo = m_capturedFrames.front(); m_capturedFrames.pop();CV_LOG_DEBUG(NULL, "videoio(MSMF): handle frame at " << frameInfo.timestamp);mediaSample = frameInfo.sample;CV_Assert(mediaSample);sampleTimestamp = frameInfo.timestamp;ResetEvent(m_hEvent); // event is auto-reset, but we need this forced reset due time gap between wait() and mutex hold.return frameInfo.hrStatus;}}CV_LOG_DEBUG(NULL, "videoio(MSMF): waiting for frame... ");DWORD dwResult = WaitForSingleObject(m_hEvent, dwMilliseconds);if (dwResult == WAIT_TIMEOUT){return E_PENDING;}else if (dwResult != WAIT_OBJECT_0){return HRESULT_FROM_WIN32(GetLastError());}}}private:// Destructor is private. Caller should call Release.virtual ~SourceReaderCB(){CV_LOG_INFO(NULL, "terminating async callback");}public:long m_nRefCount; // Reference count.cv::Mutex m_mutex;HANDLE m_hEvent;BOOL m_bEOS;HRESULT m_hrStatus;IMFSourceReader *m_reader;DWORD m_dwStreamIndex;struct CapturedFrameInfo {LONGLONG timestamp;_ComPtr<IMFSample> sample;HRESULT hrStatus;};std::queue<CapturedFrameInfo> m_capturedFrames;
};
相关文章:
【MediaFoundation】OpenCV VideoCapture 读取音频源码
OpenCV 读取音频代码实例 在windows7 以及OpenCV4 过后可以使用 CAP_MSMF 读取音频,但是OpenCV没有播放音频的API。代码示例如下。 本文解析OpenCVCAP_MSMF 进行文件、设备的 音频读取,学习MediaFoundation 的使用。 #include <opencv2/core.hpp>…...
2024秋招,百度测试开发工程师一面
前言 大家好,今天我来回顾一下秋招中的一场很重要技术面试 一面面试官深挖我的项目经历,并提出了很多的实际场景,我现在回顾依然有很多新的认识 过程 自我介绍实习工作中,做得最好的地方是什么? 我先介绍了一下实习…...
Git 使用与问题记录 二(公司快速上手版)
写在前面 记录自己学习的内容,方便后面忘记的时候查看。给像我一样的新手提供一点参考 正文 上一章已经安装好了Git,如何使用呢。我这里会分享两种办法,第一种是在VS2022中克隆代码,修改和提交;第二种是用命令提交。…...
【C语言小游戏】贪吃蛇
文章目录 1.引言2.运行图2.涉及知识3 Windows API3.1 控制台3.2 控制台屏幕坐标3.3 操作句柄3.4 控制台屏幕光标3.5 监视按键 4. 设计说明5. 完整代码 1.引言 使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇 实现基本的功能: 贪吃蛇地图绘制蛇吃⻝物的…...
价值7500的在线授权网站源码支持IP+域名+双向授权全开源
PHP授权验证更新系统完整版,一键更新系统,一键卡密生成自助授权功能,域名ip双重验证功能等等 修复盗版检测,确保实时查看盗版 修复在线加密系统,一键加密 授权系统几乎所有的程序都能整合使用,包括您的app和计算机程序…...
haiku实现门控多头注意力模块
在多头注意力机制中,通常输入的数据包括查询(Q)、键(K)和值(V)。这些数据的维度以及权重矩阵的维度在多头注意力机制中扮演关键角色。下面对数据及权重的维度进行解释: 输入数据&…...
【React 常用的 TS 类型】持续更新
1)定义样式的 TS 类型 【 React.CSSProperties 】 一般定义样式时需要的类型限制,如下: const customStyle: React.CSSProperties {color: blue,fontSize: 16px,margin: 10px,}; 2)定义 Input Ref 属性时的 TS 类型限制 【 R…...
打破传统边界,VR技术与六西格玛设计理念的创新融合!
在科技飞速发展的今天,虚拟现实(VR)技术以其独特的沉浸式体验,正在改变我们的生活和工作方式。然而,要让VR真正成为主流,我们必须解决一些关键问题,其中最重要的就是用户体验。六西格玛设计&…...
[uniapp] uni-ui+vue3.2小程序评论列表组件 回复评论 点赞和删除
先看效果 下载地址 uni-app官方插件市场: cc-comment组件 环境 基于vue3.2和uni-ui开发; 依赖版本参考如下: "dependencies": {"dcloudio/uni-mp-weixin": "3.0.0-3090820231124001","dcloudio/uni-ui": "^1.4.28","…...
TongLINKQ(3):TongLINKQ常用命令
启动: tlq 暂停: tlq -cabort -y -w1 查看lic信息: tlqstat –lic 查看队列消息: tlqstat -qcu qcu名 -c 查看发送连接状态: tlqstat -snd qcu名 -1 -ct 1 查看指定的Qcu连接状态: tlqsta…...
抽水马桶出水慢解决记录
今天分享一些修马桶的小心得(雾) 家里的马桶出水很好,但是水却不怎么被冲下去(出水很慢),这会导致内容物滞留,造成很不好的使用体验。 出于成本考虑,首先选择自己维修。 首先直接…...
img标签的奇怪问题
本来只是为实现一个轮播图,img的url地址是从后端接口获取的,但不巧的是url地址的图片都过期了。 因为懒得重新到网上找图,就想直接用一下本地的图片,简单的想法遇到一堆问题。 问题一: 因为是springboot项目…...
深入探究Hibernate:优雅、强大的Java持久化框架
目录 1、前言 2、Hibernate简介 2.1 什么是Hibernate 2.2 为什么选择Hibernate 3、Hibernate核心概念 3.1 实体类和映射文件 3.2 数据库表和持久化类的映射 3.3 主键生成策略 3.4 持久化操作 3.5 查询语言(HQL和Criteria) 3.6 事务管理 4、Hibernate配置与连接 4…...
JavaScript高级特性详解
摘要:本文将深入探讨JavaScript中的一些高级特性,包括闭包、原型链、高阶函数和异步编程。我们将通过详细的注释和实例来帮助读者理解这些概念,并通过总结部分强调其在实际开发中的应用。 一、闭包 闭包是JavaScript中一个非常重要的概念&a…...
网站建设网络设计营销类网站eyouCMS模板(PC+WAP)
模板介绍: 本模板自带eyoucms内核,无需再下载eyou系统,原创设计、手工书写DIVCSS,完美兼容IE7、Firefox、Chrome、360浏览器等;主流浏览器;结构容易优化;多终端均可正常预览。...
迅为RK3568开发板Android11/12/Linux编译驱动到内核
在平时的驱动开发中,经常需要在内核中配置某种功能,为了方便大家开发和学习,本小 节讲解如何在内核中添加驱动。具体的讲解原理讲解请参考本手册的驱动教程。 Android11 源码如果想要修改内核,可以运行以下命令进行修改: cd ke…...
SaaS 应用深度解析:Marketo
随着数字营销的不断发展,企业需要强大而智能的工具来管理营销活动、吸引潜在客户、并实现销售目标。在众多营销自动化工具中,Marketo 是一款备受推崇的 SaaS 应用,为企业提供全面的营销解决方案。本文将深入了解 Marketo,探讨其功…...
闲聊篇-求职的点点滴滴~~
引言 求职之旅是一段充满挑战与机遇的旅程。它不仅仅是寻找工作的过程,更是一个自我探索和成长的过程。在这篇文章中,我们将探讨求职的各个方面,从准备简历到面试,再到最终拿到心仪的offer。 1. 简历:你的敲门砖 精…...
微软最新研究成果:使用GPT-4合成数据来训练AI模型,实现SOTA!
文本嵌入是各项NLP任务的基础,用于将自然语言转换为向量表示。现有的大部分方法通常采用复杂的多阶段训练流程,先在大规模数据上训练,再在小规模标注数据上微调。此过程依赖于手动收集数据制作正负样本对,缺乏任务的多样性和语言多…...
爬虫案例—抓取小米商店应用
爬虫案例—抓取小米商店应用 代码如下: # 抓取第一页的内容 import requests from lxml import etree url ‘https://app.mi.com/catTopList/0?page1’ headers { ‘User-Agent’: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (K…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...
