Android程序中使用FFmpeg库

目录

前言

一、环境

二、创建APP

三. 添加FFmpeg库文件到app中

1. 复制ffmpeg头文件和so库到app中

2. 修改CMakeLists.txt文件内容.

3. 修改ffmpeglib.cpp 文件内容

4. 修改NativeLib.kt 文件添加方法和加载库

5. 调用

四. 增加解析视频文件信息功能

总结


前言

        前面有一篇记录了windows上👉 编译Android平台使用的FFmpeg库。想知道的同学可以去看一下😄。这一篇记录一下在android app上怎么使用这些库。


一、环境

  1. 安装Android studio, 方法就不介绍了,网上太多安装的方法了。
  2. 安装NDK和cmake,直接使用SDK monitor安装。 看过我的编译ffmpeg库的知道用的ndk 版本是:
    25.1.8937393

cmake版本:3.22.1

二、创建APP

android studio 创建module

创建成功后,目录结构跟下面差不多,只是没有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 library
        c++_shared
        android
        log)

3. 修改ffmpeglib.cpp 文件内容

添加一个initFfmpeg 方法用来初始化ffmpeg

extern "C" JNIEXPORT void JNICALL
Java_com_bob_ffmpegdemo_NativeLib_initFfmpeg(JNIEnv *env, jobject /* this */) {
    // 初始化 FFmpeg
    avformat_network_init();

    // 打印 FFmpeg 版本信息到日志
    const char *version = avformat_configuration();
    LOGD("FFmpeg version: %s", version);
}

4. 修改NativeLib.kt 文件添加方法和加载库

package com.bob.ffmpegdemo

class NativeLib {
    /**
     * A native method that is implemented by the 'ffmpglib' native library,
     * which is packaged with this application.
     */
    external fun stringFromJNI(): String
    external 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_format
    av_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);
                            }
            #endif

            LOGD("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 */) {
    // 初始化 FFmpeg
    avformat_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_format
    av_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);
                            }
            #endif

            LOGD("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.ffmpegdemo

class NativeLib {

    /**
     * A native method that is implemented by the 'ffmpglib' native library,
     * which is packaged with this application.
     */
    external fun stringFromJNI(): String
    external fun initFfmpeg()
    external fun testOpenVideo(path:String): String

    companion object {
        // Used to load the 'ffmpeglib' library on application startup.
        init {
            System.loadLibrary("ffmpeglib")
        }
    }
}

 MainActivity.kt

package com.bob.ffmpegdemo

import 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.OutputStream

class 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 directory
        val outFile = File(dir, FILENAME)

        // Step 2: Copy the asset file to the external files directory
        try {
            val `in`: InputStream = assets.open(FILENAME)
            val out: OutputStream = FileOutputStream(outFile)

            val buffer = ByteArray(1024)
            var read: Int
            while ((`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")
    }
}

总结

以上就是今天要讲的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/958246.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI 编程工具—Cursor进阶使用 Rules for AI

AI 编程工具—Cursor进阶使用 Rules for AI 这里配置是给所有的会话和内嵌模式的,你可以理解为是一个全局的配置 下面的代码是之前Cursor 给我们生成的,下面我们开始配置Rules ,来让Cursor生成的代码更加符合我们的编程习惯 def quick_sort(arr):"""使用快…

【系统环境丢失恢复】如何恢复和重建 Ubuntu 中的 .bashrc 文件

r如果你遇到这种情况&#xff0c;说明系统环境的.bashrc 文件丢失恢复&#xff1a; 要恢复 ~/.bashrc 文件&#xff0c;可以按照以下几种方式操作&#xff1a; 恢复默认的 ~/.bashrc 文件 如果 ~/.bashrc 文件被删除或修改&#xff0c;你可以恢复到默认的版本。可以参考以下…

PyTorch使用教程(8)-一文了解torchvision

一、什么是torchvision torchvision提供了丰富的功能&#xff0c;主要包括数据集、模型、转换工具和实用方法四大模块。数据集模块内置了多种广泛使用的图像和视频数据集&#xff0c;如ImageNet、CIFAR-10、MNIST等&#xff0c;方便开发者进行训练和评估。模型模块封装了大量经…

实战演示:利用ChatGPT高效撰写论文

在当今学术界&#xff0c;撰写论文是一项必不可少的技能。然而&#xff0c;许多研究人员和学生在写作过程中常常感到困惑和压力。幸运的是&#xff0c;人工智能的快速发展为我们提供了新的工具&#xff0c;其中ChatGPT便是一个优秀的选择。本文将通过易创AI创作平台&#xff0c…

群晖部署-Calibreweb

最近家里搞了台群晖&#xff0c;准备部署个Calibreweb看看电子书&#xff0c;看了好多部署的教程老是不太成功&#xff0c;要么报错要么有问题的&#xff0c;很难搞。下面将部署流程分享一下&#xff0c;给大家参考&#xff0c;少走点弯路 镜像的选择 我们使用johngong/calibr…

WordPress果果对象存储插件

将网站上的图片等静态资源文件上传至七牛云对象存储&#xff0c;可以减轻服务器文件存储压力&#xff0c;提升静态文件访问速度&#xff0c;从而加速网站访问速度。 支持&#xff1a;阿里云对象存储、华为云对象存储、百度云对象存储、腾讯云对象存储、七牛云对象存储。 下载…

电路研究9.1.1——合宙 Air780EP 模组外围线路

本来要继续研究AT指令来着&#xff0c;结果发现后面还有之前用到的电路设计资料&#xff0c;所以就贴过来了。 5.3.2 工作模式&#xff1a; 注意&#xff1a;  当模块进入休眠模式或深度休眠模式后&#xff0c; VDD_EXT 电源会掉电&#xff0c;相应电压域的 GPIO 以及串口…

LabVIEW 太阳能光伏发电系统智能监控

本文介绍了基于 LabVIEW 的太阳能光伏发电监控系统的设计与实现&#xff0c;着重探讨了其硬件配置、软件架构以及系统的实现方法。该系统能够有效提高太阳能光伏发电的监控效率和精确性&#xff0c;实现了远程监控和数据管理的智能化。 ​ 项目背景 在当前能源紧张与环境污染…

风光并网对电网电能质量影响的matlab/simulink仿真建模

这个课题早在一几年的时候比较热门&#xff0c;之前作电科院配电网的一个项目中也有所涉及&#xff0c;我把其中一部分经典仿真模型思路分享给大家&#xff0c;电能质量影响这部分&#xff0c;我在模型中主要体现的就是不同容量的光伏、风电接入&#xff0c;对并网点的电压影响…

大模型应用编排工具Dify之常用编排组件

1.前言 dify的核心能力有&#xff1a;支持接入常见的 LLM、工作流编排、知识库和聊天助手等&#xff0c;架构图如下&#xff1a; 本文将结合实际项目落地经验&#xff0c;针对工作流编排中的常用编排组件进行介绍&#xff0c;以及如何在后端调用工作流编排。 2.落地案例 某 …

Ubuntu16.04 安装OpenCV4.5.4 避坑

Ubuntu16.04 安装C版OpenCV4.5.4 Ubuntu16.04 VSCode下cmakeclanglldb调试c 文章目录 Ubuntu16.04 安装C版OpenCV4.5.41. 下载Opencv压缩包2. 安装Opencv-4.5.43. 配置OpenCV的编译环境4.测试是否安装成功 1. 下载Opencv压缩包 下载Opencv压缩包&#xff0c;选择source版本。…

pytest执行报错:found no collectors

今天在尝试使用pytest运行用例的时候出现报错&#xff1a;found no collectors&#xff1b;从两个方向进行排查&#xff0c;一是看文件名和函数名是不是符合规范&#xff0c;命名要是"test_*"格式&#xff1b;二是是否存在修改文件名的情况&#xff0c;如果修改过文件…

嵌入式知识点总结 C/C++ 专题提升(七)-位操作

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.位操作基础 2.如何求解整型数的二进制表示中1的个数 ? 3.如何求解二进制中0的个数 4.交换两个变量的值&#xff0c;不使用第三个变量。即a3,b5,交换之后a5,b3: 5.给定一个…

两台局域网电脑通过飞秋传输大文件失败的解决方案

问题描述&#xff1a; 局域网两台电脑之间传输大文件&#xff08;超过20G&#xff09;&#xff0c;不想太复杂&#xff0c;就各装个飞秋。但是通过直接发送文件发现总是失败&#xff0c;一会就中断了。 解决方法&#xff1a; 主界面上有一个文件共享的按钮&#xff0c;通过文…

Picsart美易照片编辑器和视频编辑器

使用Picsart美易照片编辑器和视频编辑器&#xff0c;将您的创意变为现实。制作专业水准的拼贴画、设计并添加贴纸、快速移除和更换背景&#xff0c;体验流行编辑&#xff0c;比如 黄金时刻、镜中自拍、复古噪点滤镜或千禧滤镜。Picsart美易是一款一体式编辑器&#xff0c;拥有众…

AR智慧点巡检系统探究和技术方案设计

一、项目背景 随着工业生产规模的不断扩大和设备复杂度的提升&#xff0c;传统的人工点巡检方式效率低下、易出错&#xff0c;难以满足现代化企业对设备运行可靠性和安全性的要求。AR&#xff08;增强现实&#xff09;技术的发展为点巡检工作带来了新的解决方案&#xff0c;通…

游戏设备升级怎么选?RTX4070独显,ToDesk云电脑更具性价比

过新年、添喜气&#xff01;正逢节期来临不知道各位是否都跟小编一样在考虑购置生活中的各样所需呐&#xff1f; 25年可谓是3A游戏大作之年&#xff0c;例如《GTA6》《文明7》《死亡搁浅2》《刺客信条&#xff1a;影》下半年落地的《塞尔达传说&#xff1a;新篇章》《生化危机9…

算法刷题笔记——图论篇

这里写目录标题 理论基础图的基本概念图的种类度 连通性连通图强连通图连通分量强连通分量 图的构造邻接矩阵邻接表 图的遍历方式 深度优先搜索理论基础dfs 与 bfs 区别dfs 搜索过程深搜三部曲所有可达路径广度优先搜索理论基础广搜的使用场景广搜的过程 岛屿数量孤岛的总面积沉…

怎么使用python 调用高德地图api查询位置和导航?

环境&#xff1a; python 3.10 问题描述&#xff1a; 怎么使用python 调用高德地图api查询位置和导航? 解决方案&#xff1a; 要使用Python调用高德地图API查询位置和导航&#xff0c;需要先注册高德开发者账号并获取API Key。以下是基本步骤&#xff1a; 1. 注册高德开…

【阿里云】使用docker安装nginx后可以直接访问

一、创建目录 mkdir -p config/{cert,conf.d} html logs二、上传nginx.conf的配置文件 user nginx; worker_processes auto;error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;events {worker_connections 1024; }http {include /etc/ngin…