Android程序中使用FFmpeg库
目录
前言
一、环境
二、创建APP
三. 添加FFmpeg库文件到app中
1. 复制ffmpeg头文件和so库到app中
2. 修改CMakeLists.txt文件内容.
3. 修改ffmpeglib.cpp 文件内容
4. 修改NativeLib.kt 文件添加方法和加载库
5. 调用
四. 增加解析视频文件信息功能
总结
前言
前面有一篇记录了windows上👉 编译Android平台使用的FFmpeg库。想知道的同学可以去看一下😄。这一篇记录一下在android app上怎么使用这些库。
一、环境
- 安装Android studio, 方法就不介绍了,网上太多安装的方法了。
- 安装NDK和cmake,直接使用SDK monitor安装。 看过我的编译ffmpeg库的知道用的ndk 版本是:
25.1.8937393
cmake版本:3.22.1
二、创建APP
创建成功后,目录结构跟下面差不多,只是没有assets和cpp这两个文件夹和NatvieLib文件。后面会说这两个文件夹和文件是干嘛的
三. 添加FFmpeg库文件到app中
1. 复制ffmpeg头文件和so库到app中
使用过NDK项目的都知道cpp这个是放CMakeLists.txt和所有的cpp文件的。
cpp这个文件夹下面创建一个ffmpeg文件夹用来放ffmpeg的头文件和so库文件。因为只编译了一个arm64-v8a 架构,所在在lib这个文件夹下面创建一个arm64-v8a用于放so库。目录结构如下图:
2. 修改CMakeLists.txt文件内容.
修改CMakeLists.txt文件内容编译ffmpeglib.cpp文件。
CMakeLists.txt文件内容如下,都添加注释了,不多说了。有不太清楚的可以自己创建一个Native library module,比对一下看看
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("ffmpeglib")#设定ffmpeg的头文件和so库文件到一个变量中
set(FFMPEG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/../cpp/ffmpeg/include)
set(FFMPEG_LIB_DIR ${CMAKE_SOURCE_DIR}/../cpp/ffmpeg/lib/${ANDROID_ABI})# 输出调试信息,用于查看路径是否正确
message(STATUS "FFMPEG_INCLUDE_DIR: ${FFMPEG_INCLUDE_DIR}")
message(STATUS "FFMPEG_LIB_DIR: ${FFMPEG_LIB_DIR}")# 检查库文件是否存在
file(GLOB FFMPEG_LIB_FILES "${FFMPEG_LIB_DIR}/*.so")
if(NOT FFMPEG_LIB_FILES)message(FATAL_ERROR "No FFmpeg library files found in ${FFMPEG_LIB_DIR}. Please check the paths and ensure the libraries exist.")
endif()# 包含FFmpeg头文件,只有包含头文件后,在cpp中才能正确引用头文件
include_directories(${FFMPEG_INCLUDE_DIR})# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED# List C/C++ source files with relative paths to this CMakeLists.txt.ffmpeglib.cpp)# 显式指定库文件路径
set(avformat_LIBRARY ${FFMPEG_LIB_DIR}/libavformat.so)
set(avcodec_LIBRARY ${FFMPEG_LIB_DIR}/libavcodec.so)
set(avutil_LIBRARY ${FFMPEG_LIB_DIR}/libavutil.so)
set(swresample_LIBRARY ${FFMPEG_LIB_DIR}/libswresample.so)
set(swscale_LIBRARY ${FFMPEG_LIB_DIR}/libswscale.so)
set(avdevice_LIBRARY ${FFMPEG_LIB_DIR}/libavdevice.so)
set(avfilter_LIBRARY ${FFMPEG_LIB_DIR}/libavfilter.so)# 检测so库文件,输出找到的库文件路径, c++引用so库是不用带lib前缀和.so扩展名的
foreach (LIB avformat avcodec avutil swresample swscale avdevice avfilter)if(EXISTS ${${LIB}_LIBRARY})message(STATUS "${LIB}_LIBRARY: ${${LIB}_LIBRARY}")else()message(FATAL_ERROR "${LIB}_LIBRARY not found at ${${LIB}_LIBRARY}. Please check the paths and ensure the libraries exist.")endif()
endforeach()#链接库文件
target_link_libraries(${CMAKE_PROJECT_NAME}${avutil_LIBRARY}${swresample_LIBRARY}${swscale_LIBRARY}${avcodec_LIBRARY}${avdevice_LIBRARY}${avfilter_LIBRARY}${avformat_LIBRARY}# List libraries link to the target libraryc++_sharedandroidlog)
3. 修改ffmpeglib.cpp 文件内容
添加一个initFfmpeg 方法用来初始化ffmpeg
extern "C" JNIEXPORT void JNICALL
Java_com_bob_ffmpegdemo_NativeLib_initFfmpeg(JNIEnv *env, jobject /* this */) {// 初始化 FFmpegavformat_network_init();// 打印 FFmpeg 版本信息到日志const char *version = avformat_configuration();LOGD("FFmpeg version: %s", version);
}
4. 修改NativeLib.kt 文件添加方法和加载库
package com.bob.ffmpegdemoclass NativeLib {/*** A native method that is implemented by the 'ffmpglib' native library,* which is packaged with this application.*/external fun stringFromJNI(): Stringexternal fun initFfmpeg()companion object {// Used to load the 'ffmpeglib' library on application startup.init {System.loadLibrary("ffmpeglib")}}
}
5. 调用
可以在Activity文件中直接调用NativeLab这个类中和方法
override fun onResume() {super.onResume()testFfmpeg()}private fun testFfmpeg() {val nativeLib = NativeLib()Log.d(TAG, "-------- ${nativeLib.stringFromJNI()}")nativeLib.initFfmpeg()}
直接运行app, 成功会输出下面的内容
四. 增加解析视频文件信息功能
通过前面三节内容后,ffmpeg的库就添加到app中了,但是只是输出了ffmpeg 编译的信息。不知道ffmpeg的功能是否能用。这节增加解析视频文件的功能
- 直接在ffmpeglib.cpp文件中添加testOpenVideo方法解析视频
extern "C" JNIEXPORT jstring JNICALL
Java_com_bob_ffmpegdemo_NativeLib_testOpenVideo(JNIEnv *env, jobject /* this */, jstring filePath) {const char *path = env->GetStringUTFChars(filePath, NULL);// 添加 'file://' 前缀以确保正确解析路径std::string full_path = "file://" + std::string(path);LOGD("Attempting to open video file: %s", full_path.c_str());AVFormatContext *pFormatCtx = nullptr;int ret = avformat_open_input(&pFormatCtx, full_path.c_str(), NULL, NULL);if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));LOGE("Failed to open video file: %s", errbuf);env->ReleaseStringUTFChars(filePath, path);return env->NewStringUTF("Failed to open video file.");}if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {LOGE("Failed to retrieve stream information.");avformat_close_input(&pFormatCtx);env->ReleaseStringUTFChars(filePath, path);return env->NewStringUTF("Failed to retrieve stream information.");}// 使用正确的函数名 av_dump_formatav_dump_format(pFormatCtx, 0, full_path.c_str(), 0); // 打印格式信息到标准输出// 计算持续时间和比特率int64_t duration = pFormatCtx->duration != AV_NOPTS_VALUE ?av_rescale_q(pFormatCtx->duration, AV_TIME_BASE_Q, {1, 1000}) : -1; // 转换为毫秒int64_t bitrate = pFormatCtx->bit_rate / 1000;// 解析流信息并处理无音频流的情况bool hasAudioStream = false;for (unsigned int i = 0; i < pFormatCtx->nb_streams; ++i) {AVStream *stream = pFormatCtx->streams[i];AVCodecParameters *codecpar = stream->codecpar;if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {LOGD("Video Stream: Codec %s, Resolution %dx%d",avcodec_get_name(codecpar->codec_id),codecpar->width, codecpar->height);} else if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {hasAudioStream = true;// 如果 channel_layout 存在,则使用它;否则提供一个默认值int channels = 2; // 默认立体声音频#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 9, 100)if (codecpar->channel_layout) {channels = av_get_channel_layout_nb_channels(codecpar->channel_layout);}#endifLOGD("Audio Stream: Codec %s, Sample Rate %d Hz, Channels %d",avcodec_get_name(codecpar->codec_id),codecpar->sample_rate,channels);}}if (!hasAudioStream) {LOGD("No audio streams found in the video file.");}char info[1024];snprintf(info, sizeof(info), "Duration: %lld ms, Bitrate: %lld kbps",static_cast<long long>(duration),static_cast<long long>(bitrate));avformat_close_input(&pFormatCtx);env->ReleaseStringUTFChars(filePath, path);return env->NewStringUTF(info);
}
因为使用的是我自己录制的mp4文件,没有声音的。所以添加了hasAudioStream的判断
- 修改NativeLib.kt 文件
增加:
external fun testOpenVideo(path:String): String
- 方法调用
val outputDir = getExternalFilesDir(null)val outputFile = File(outputDir, FILENAME)if (!outputFile.exists()) {Log.e(TAG, "File does not exist at path: ${outputFile.absolutePath}")return} else if (!outputFile.canRead()) {Log.e(TAG, "File is not readable at path: ${outputFile.absolutePath}")return}val result = nativeLib.testOpenVideo(outputFile.absolutePath)Log.d(TAG, "-------- $result")
运行成功后
上面贴的CMakeLists.txt的内容已经是完整的。下面贴一下ffmpeglib.cpp, NativeLib.kt 和 MainActivity.kt 完整代码。
ffmpeglib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>extern "C" {
#include "ffmpeg/include/libavformat/avformat.h"
}#define LOG_TAG "NativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)extern "C" JNIEXPORT jstring JNICALL
Java_com_bob_ffmpegdemo_NativeLib_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from FFmpeg";return env->NewStringUTF(hello.c_str());
}extern "C" JNIEXPORT void JNICALL
Java_com_bob_ffmpegdemo_NativeLib_initFfmpeg(JNIEnv *env, jobject /* this */) {// 初始化 FFmpegavformat_network_init();// 打印 FFmpeg 版本信息到日志const char *version = avformat_configuration();LOGD("FFmpeg version: %s", version);
}extern "C" JNIEXPORT jstring JNICALL
Java_com_bob_ffmpegdemo_NativeLib_testOpenVideo(JNIEnv *env, jobject /* this */, jstring filePath) {const char *path = env->GetStringUTFChars(filePath, NULL);// 添加 'file://' 前缀以确保正确解析路径std::string full_path = "file://" + std::string(path);LOGD("Attempting to open video file: %s", full_path.c_str());AVFormatContext *pFormatCtx = nullptr;int ret = avformat_open_input(&pFormatCtx, full_path.c_str(), NULL, NULL);if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));LOGE("Failed to open video file: %s", errbuf);env->ReleaseStringUTFChars(filePath, path);return env->NewStringUTF("Failed to open video file.");}if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {LOGE("Failed to retrieve stream information.");avformat_close_input(&pFormatCtx);env->ReleaseStringUTFChars(filePath, path);return env->NewStringUTF("Failed to retrieve stream information.");}// 使用正确的函数名 av_dump_formatav_dump_format(pFormatCtx, 0, full_path.c_str(), 0); // 打印格式信息到标准输出// 计算持续时间和比特率int64_t duration = pFormatCtx->duration != AV_NOPTS_VALUE ?av_rescale_q(pFormatCtx->duration, AV_TIME_BASE_Q, {1, 1000}) : -1; // 转换为毫秒int64_t bitrate = pFormatCtx->bit_rate / 1000;// 解析流信息并处理无音频流的情况bool hasAudioStream = false;for (unsigned int i = 0; i < pFormatCtx->nb_streams; ++i) {AVStream *stream = pFormatCtx->streams[i];AVCodecParameters *codecpar = stream->codecpar;if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {LOGD("Video Stream: Codec %s, Resolution %dx%d",avcodec_get_name(codecpar->codec_id),codecpar->width, codecpar->height);} else if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {hasAudioStream = true;// 如果 channel_layout 存在,则使用它;否则提供一个默认值int channels = 2; // 默认立体声音频#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 9, 100)if (codecpar->channel_layout) {channels = av_get_channel_layout_nb_channels(codecpar->channel_layout);}#endifLOGD("Audio Stream: Codec %s, Sample Rate %d Hz, Channels %d",avcodec_get_name(codecpar->codec_id),codecpar->sample_rate,channels);}}if (!hasAudioStream) {LOGD("No audio streams found in the video file.");}char info[1024];snprintf(info, sizeof(info), "Duration: %lld ms, Bitrate: %lld kbps",static_cast<long long>(duration),static_cast<long long>(bitrate));avformat_close_input(&pFormatCtx);env->ReleaseStringUTFChars(filePath, path);return env->NewStringUTF(info);
}
NativeLib.kt
package com.bob.ffmpegdemoclass NativeLib {/*** A native method that is implemented by the 'ffmpglib' native library,* which is packaged with this application.*/external fun stringFromJNI(): Stringexternal fun initFfmpeg()external fun testOpenVideo(path:String): Stringcompanion object {// Used to load the 'ffmpeglib' library on application startup.init {System.loadLibrary("ffmpeglib")}}
}
MainActivity.kt
package com.bob.ffmpegdemoimport android.os.Bundle
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.bob.ffmpegdemo.databinding.ActivityMainBinding
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStreamclass MainActivity : AppCompatActivity() {companion object {const val FILENAME = "abc.mp4"const val TAG = "TAG"}private val binding: ActivityMainBinding by lazy {ActivityMainBinding.inflate(layoutInflater)}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContentView(binding.root)ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)insets}copyAssetToFile()}private fun copyAssetToFile() {val dir = getExternalFilesDir(null)// Step 1: Create a file object in that directoryval outFile = File(dir, FILENAME)// Step 2: Copy the asset file to the external files directorytry {val `in`: InputStream = assets.open(FILENAME)val out: OutputStream = FileOutputStream(outFile)val buffer = ByteArray(1024)var read: Intwhile ((`in`.read(buffer).also { read = it }) != -1) {out.write(buffer, 0, read)}`in`.close()out.flush()out.close()Log.d(TAG, "Successfully copied $FILENAME to external files directory.")} catch (e: IOException) {Log.e(TAG, "Failed to copy asset file: " + e.message)}}override fun onResume() {super.onResume()testFfmpeg()}private fun testFfmpeg() {val nativeLib = NativeLib()Log.d(TAG, "-------- ${nativeLib.stringFromJNI()}")nativeLib.initFfmpeg()val outputDir = getExternalFilesDir(null)val outputFile = File(outputDir, FILENAME)if (!outputFile.exists()) {Log.e(TAG, "File does not exist at path: ${outputFile.absolutePath}")return} else if (!outputFile.canRead()) {Log.e(TAG, "File is not readable at path: ${outputFile.absolutePath}")return}val result = nativeLib.testOpenVideo(outputFile.absolutePath)Log.d(TAG, "-------- $result")}
}
总结
以上就是今天要讲的内容。
相关文章:

Android程序中使用FFmpeg库
目录 前言 一、环境 二、创建APP 三. 添加FFmpeg库文件到app中 1. 复制ffmpeg头文件和so库到app中 2. 修改CMakeLists.txt文件内容. 3. 修改ffmpeglib.cpp 文件内容 4. 修改NativeLib.kt 文件添加方法和加载库 5. 调用 四. 增加解析视频文件信息功能 总结 前言 前面…...

Spring 依赖注入详解:创建 Bean 和注入依赖是一回事吗?
1. 什么是依赖注入(Dependency Injection,DI)? 依赖注入 是 Spring IoC(控制反转)容器的核心功能。它的目标是将对象的依赖(如其他对象或配置)从对象本身中剥离,由容器负…...

【动态规划】落花人独立,微雨燕双飞 - 8. 01背包问题
本篇博客给大家带来的是01背包问题之动态规划解法技巧. 🐎文章专栏: 动态规划 🚀若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅🚀 要开心要快乐顺便…...

浅说树上差分——点差分
我们前面也学过差分,现在的话我们就把他放到树上来做。因为这是树,所以会有点和边之分,所以树上差分也会分为 点差分 和 边差分 。 引入 树上差分其实和线性差分没有什么区别,只不过是放到了树上的两点,而他们之间的…...

All in大模型!智能座舱语音交互决胜2025
大模型加速上车,AI智能座舱竞争更显白热化。 诚然,在语言大模型为核心的多模态能力加持下,智能语音助理能够理解复杂的语言指令,实现知识问答、文本生成等,以及根据上下文进行逻辑推理,提供更智能、准确的…...

windows git bash 使用zsh 并集成 oh my zsh
参考了 这篇文章 进行配置,记录了自己的踩坑过程,并增加了 zsh-autosuggestions 插件的集成。 主要步骤: 1. git bash 这个就不说了,自己去网上下,windows 使用git时候 命令行基本都有它。 主要也是用它不方便&…...

Git进阶笔记系列(01)Git核心架构原理 | 常用命令实战集合
读书笔记:卓越强迫症强大恐惧症,在亲子家庭、职场关系里尤其是纵向关系模型里,这两种状态很容易无缝衔接。尤其父母对子女、领导对下属,都有望子成龙、强将无弱兵的期望,然而在你的面前,他们才是永远强大的…...

IDEA导入Maven工程不识别pom.xml
0 现象 把阿里 sentinel 项目下载本地后,IDEA 中却没显示 maven 工具栏。 1 右键Maven Projects 点击IDEA右侧边栏的Maven Projects,再点击: 在出现的选择框中选择指定的未被识别的pom.xml即可: 2 Add as maven project 右键p…...

AT8870单通道直流电机驱动芯片
AT8870单通道直流电机驱动芯片 典型应用原理图 描述 AT8870是一款刷式直流电机驱动器,适用于打印机、电器、工业设备以及其他小型机器。两个逻辑输入控制H桥驱动器,该驱动器由四个N-MOS组成,能够以高达3.6A的峰值电流双向控制电机。利用电流…...

计算机视觉算法实战——实体物体跟踪
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 1. 领域介绍✨✨ 实体物体跟踪(Object Tracking)是计算机视觉领域中的一个重要研究方向&#x…...

网络协议如何确保数据的安全传输?
网络协议作为计算机网络通信的基石,其设计不仅旨在实现数据的有效传输,更在于确保数据在传输过程中的安全性。对于网络协议如何保障数据安全传输,是很多企业和网络IT部门的重点,本文将从多方面概述相关方法。 加密与解密机制 1. …...

在elasticsearch中,document数据的写入流程如何?
本文将为您介绍文档内容是如何写入ES集群中。 数据写入ES集群的流程图如下 流程介绍 用户携带数据发起POST请求指向集群9200端口。9200端口将数据写入请求发给主分片。主分片会对数据进行分片计算分发给具体分片。(计算方式:hash % primary_number_sha…...

【优选算法】6----查找总价格为目标值的两个商品
这道题相对于前寄到算法题较为容易~ 同样也是使用了双指针的算法哦~ ----------------------------------------begin-------------------------------------- 题目解析: 题目也是很简单地一句话,但是意图还是很明确~ 讲解算法原理: 同样的&…...

99.8 金融难点通俗解释:净资产收益率(ROE)
目录 0. 承前1. 简述2. 比喻:养母鸡赚钱2.1 第一步:投资母鸡2.2 第二步:母鸡下蛋2.3 第三步:计算赚钱2.4 第四步:计算ROE 3. 生活中的例子3.1 好的ROE3.2 一般的ROE3.3 差的ROE 4. 小朋友要注意4.1 ROE高不一定好4.2 R…...

Java设计模式—观察者模式
观察者模式 目录 观察者模式1、什么是观察者模式?2、观察者模式优缺点及注意事项?3、观察者模式实现?4、手写线程安全的观察者模式? 1、什么是观察者模式? - 实例:现实生活中很多事物都是依赖存在的&#x…...

人工智能在数字化转型中的角色:从数据分析到智能决策
引言 在数字化转型浪潮中,人工智能(AI)正迅速崛起,成为推动企业创新和变革的关键力量。面对日益复杂的市场环境和激烈的行业竞争,企业亟需借助技术手段提高运营效率、优化决策过程,并增强市场竞争力。而AI…...

论文阅读 Multi-view Classification Using Hybrid Fusion and Mutual Distillation
Multi-view Classification Using Hybrid Fusion and Mutual Distillation Intro 多视角问题可以分为两类: Structured。固定视角,或预先定义的视角的问题。unstructured。 本文的三大contributions: 引入了混合的多视角融合策略。使用了…...

AIGC浪潮下,图文内容社区数据指标体系如何构建?
文章目录 01 案例:以图文内容社区为例实践数据指标体构建02 4个步骤实现数据指标体系构建1. 明确业务目标,梳理北极星指标2. 梳理业务流程,明确过程指标3. 指标下钻分级,构建多层级数据指标体系4. 添加分析维度,构建完…...

”彩色的验证码,使用pytesseract识别出来的验证码内容一直是空“的解决办法
问题:彩色的验证码,使用pytesseract识别出来的验证码内容一直是空字符串 原因:pytesseract只识别黑色部分的内容 解决办法:先把彩色图片精确转换成黑白图片。再将黑白图片进行反相,将验证码部分的内容变成黑色&#…...

前端Vue2项目使用md编辑器
项目中有一个需求,要在前端给用户展示内容,内容有 AI 生成的,返回来的是 md 格式,所以需要给用户展示 md 格式,并且管理端也可以编辑这个 md 格式的文档。 使用组件库 v-md-editor。 https://code-farmer-i.github.i…...

OpenVela 架构剖析:从内核到应用
目录 一、总体架构概述 二、 内核层 2.1. OpenVela架构的内核基础 2.2. 内核层的主要职责 2.3. OpenVela对NuttX的扩展与优化 三、系统服务层 2.1. 进程管理 2.2. 内存管理 2.3. 文件系统 2.4. 网络通信 四、框架层 4.1. 模块化设计 4.2. API接口 4.3. 组件和服务…...

vue视频流播放,支持多种视频格式,如rmvb、mkv
先将视频转码为ts ffmpeg -i C:\test\3.rmvb -codec: copy -start_number 0 -hls_time 10 -hls_list_size 0 -f hls C:\test\a\output.m3u8 后端配置接口 import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.spring…...

记一个Timestamp时区问题的坑
resultSet.getTimestamp(“kpi_collect_time”)查出来的Timestamp居然是带时区的, 如果该Timestamp不是UTC时区的,Timestamp.toInstant().atZone(ZoneId.of(“UTC”))会把Timestamp转成UTC时区 使用Timestamp.toLocalDateTime()可以直接把时区信息抹除 …...

新年好(Dijkstra+dfs/全排列)
1135. 新年好 - AcWing题库 思路: 1.先预处理出1,a,b,c,d,e到其他点的单源最短路,也就是进行6次Dijkstra 2.计算以1为起点的这6个数的全排列,哪种排列方式所得距离最小,也可以使用dfs 1.Dijkstradfs #define int long longusing …...

如何“看到” Spring 容器?
Spring 容器是一个运行时的抽象工具,用来管理 Bean 的生命周期和依赖。虽然它本身不可直接观察,但可以通过以下方式间接“看到”容器的内容或行为。 2.1 容器是如何实例化的? Spring 容器的实例化是通过 ApplicationContext 或 BeanFactory …...

怎么使用CRM软件?操作方法和技巧有哪些?
什么是CRM? 嘿,大家好!你知道吗,在当今这个数字化时代里,我们每天都在与各种各样的客户打交道。无论是大公司还是小型企业,都希望能够更好地管理这些关系并提高业务效率。这时候就轮到我们的“老朋友”——…...

Spingboot整合Netty,简单示例
Netty介绍在文章末尾 Netty介绍 项目背景 传统socket通信,有需要自身管理整个状态,业务繁杂等问题。 pom.xml <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.117.F…...

grafana新增email告警
选择一个面板 比如cpu 新增一个临界点表达式 input选A 就是A的值达到某个临界点 触发告警 我这边IS ABOVE0.15就是cpu大于0.15%就触发报警,这个值怎么填看指标的值显示 这里要设置一下报警条件 这边随便配置下 配置标签和通知,选择你的邮件 看下告警…...

Github 2025-01-20 开源项目周报 Top15
根据Github Trendings的统计,本周(2025-01-20统计)共有15个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10Rust项目2TypeScript项目1C++项目1Jupyter Notebook项目1Go项目1Tabby: 自托管的AI编码助手 创建周期:310 天开发语言:Rust协议类…...

【Rabbitmq】Rabbitmq高级特性-发送者可靠性
Rabbitmq发送者可靠性 发送者重连发送者确认1.开启确认机制2.ReturnCallback3.ConfirmCallback MQ的可靠性数据持久化交换机持久化队列持久化消息持久化 Lazy Queue 总结其他文章 Rabbitmq提供了两种发送来保证发送者的可靠性,第一种叫发送者重连,第二种…...