Android音视频: 引入FFmpeg

本文你可以了解到

本文将介绍如何将上一篇文章编译出来的  FFmpeg so 库,引入到  Android 工程中,并验证  so 是否可以正常使用。

一、开启 Android 原生 C/C++ 支持

在过去,通常使用 makefile 的方式在项目中引入 C/C++ 代码支持,随着 Android Studio 的普及,makefile 的方式已经基本被 CMake 替代。

有了 Android 官方的支持,NDK 层代码的开发变得更加容易。以前一谈到 Android NDK ,许多人就会大惊失色,感觉是深不可测的东西,一方面是 makefile 的编写很难,一方面是 C/C++ 相比 Java 来说,比较晦涩。

但是不必担心,一是有了 CMake ,二是对于 C/C++ 的基本使用其实和 Java 差不多,本系列涉及到的,也都是对 C/C++ 的基础使用,毕竟,高级的我也不会不是吗?哈哈哈~~

1. 安装 CMake

首先,需要下载 CMake 相关工具,在 Android Studio 中依次点击 Tools->SDK Manager->SDK Tools,然后勾选

CMake : CMake 构建工具

LLDB : C/C++ 代码调试工具

NDK : NDK 环境

最后依次点击 OK->OK->Finish ,开始下载(文件比较大,可能会比较慢,请耐心等待)。

下载CMake工具

2. 添加 C/C++ 支持

有两种方式:

一是,新建一个新的工程,并勾选  C/C++ 支持选项,系统将自动创建一个支持  C/C++ 编码的工程。
二是,在已有的项目上,手动添加所有的添加项来支持  C/C++ 编码,其实就是自己手动添加 「第一种方式」中  Android Studio 为我们自动创建的那些东西。

首先,通过新建一个新工程的方式,看看 IDE 为我们生成了那些东西。

1)新建 C/C++ 工程

依次点击 File -> New -> New Project,进入新建工程页面,拉到最后,选择 Native C++ 然后按照默认配置,一路 Next -> Next -> Finish 即可。

新建C++工程

2)Android Studio 自动生成了什么

生成的工程目录如下:

工程目录

重点关注上图标注的3个地方:

  • 第一,最上层的 MainActivity
  class MainActivity : AppCompatActivity() {
​
       override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
​
          // Example of a call to a native method
          sample_text.text = stringFromJNI()
      }
​
      /**
       * A native method that is implemented by the 'native-lib' native library,
       * which is packaged with this application.
       */
      external fun stringFromJNI(): String
​
      companion object {
​
          // Used to load the 'native-lib' library on application startup.
          init {
              System.loadLibrary("native-lib")
          }
      }
  }

很简单,使用过 so 库的应该都看得懂,这里简单说一下。

代码的最下面,companion object 在 Kotlin 中表示静态代码块,类似 Java 中的 static { },其中的代码有且只会被执行一次。

接着在 init{} 方法中,加载了 C/C++ 代码编译成的 so 库: native-lib

往上一句代码,用 external 声明了一个外部引用的方法 stringFromJNI() ,这个方法和 C/C++ 层的代码是对应的。

最终在最上面的 onCreate 中,将从 C/C++ 层返回的 String 显示出来。

【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~

  • 第二,创建了一个 cpp 文件包

其中有两个文件非常重要,分别是 native-lib.cpp 、 CMakeLists.txt

i. native-lib.cpp :是一个 C++ 接口文件,在 MainActivity 中声明的外部方法将在这里得到实现。

自动生成 native-lib.cpp 的内容如下:

  #include <jni.h>
  #include <string>
​
  extern "C" JNIEXPORT jstring JNICALL
  Java_com_chenlittleping_mynativeapp_MainActivity_stringFromJNI(
          JNIEnv *env,
          jobject /* this */) {
      std::string hello = "Hello from C++";
      return env->NewStringUTF(hello.c_str());
  }

可以看到,这个 cpp 文件中的方法命名非常的长,不过其实非常简单。

首先是头部固定写法 extern "C" JNIEXPORT jstring JNICALL

extern "C" 表示以 C语言 的方式来编译;

jstring 表示该方法返回类型是 Java 层的 String 类型,类似的还是有: void jint等;

然后是 Java 层对应方法的映射,即整个方法命名其实是 Java 层对应方法的绝对路径。

其中,最前面的 Java_ 是固定写法;

com_chenlittleping_mynativeapp_MainActivity_: 对应的是 com.chenlittleping.mynativeapp.MainActivity.,其实就是 . 换为 _

stringFromJNI 和 Java 层的方法一致。

最后是两个参数, JNIEnv *env 和 jobject,分别代表 JNI 的上下文环境和调用这个接口的 Java 的类的实例。

调用这个方法,将会在 C++ 层创建一个字符串,并以 Java#String 的类型返回。

ii. CMakeLists.txt : 也就是构建脚本。内容如下:

  # cmake 最低版本
  cmake_minimum_required(VERSION 3.4.1)
​
  # 配置so库编译信息
  add_library( 
          # 输出so库的名称
          native-lib
​
          # 设置生成库的方式,默认为SHARE动态库
          SHARED
​
          # 列出参与编译的所有源文件
          native-lib.cpp)
​
  # 查找代码中使用到的系统库
  find_library( # Sets the name of the path variable.
          log-lib
​
          # Specifies the name of the NDK library that
          # you want CMake to locate.
          log)
​
  # 指定编译目标库时,cmake要链接的库
  target_link_libraries(
          # 指定目标库,native-lib 是在上面 add_library 中配置的目标库
          native-lib
  
          # 列出所有需要链接的库
          ${log-lib})

这是最简单的编译配置,具体见上面的注释。

CMakeLists.txt 的目的就是配置可以编译出 native-lib so 库的构建信息。

说白了,就是告诉编译器:

  - 编译的目标是谁
  - 依赖的源文件在哪里找
  - 依赖的 `系统或第三方` 的 `动态或静态` 库在哪里找。
  • 第三,在 Gradle 文件中注册 CMake 脚本

在 第二步 中,已经把构建 so 库的信息配置好了,接下来要把这些信息注册到 Gradle 中,编译器才会去编译它。

app 的 build.gradle 内容如下:

  apply plugin: 'com.android.application'
​
  apply plugin: 'kotlin-android'
  
  apply plugin: 'kotlin-android-extensions'
​
  android {
      compileSdkVersion 28
      buildToolsVersion "29.0.1"
      defaultConfig {
          applicationId "com.chenlittleping.mynativeapp"
          minSdkVersion 19
          targetSdkVersion 28
          versionCode 1
          versionName "1.0"
          testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        
          // 1) CMake 编译配置
          externalNativeBuild {
              cmake {
                  cppFlags ""
              }
          }
      }
      buildTypes {
          release {
              minifyEnabled false
              proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
          }
      }
    
      // 2) 配置 CMakeLists 路径
      externalNativeBuild {
          cmake {
              path "src/main/cpp/CMakeLists.txt"
              version "3.10.2"
          }
      }
   }
​
  dependencies {
      // 省略无关代码
      //......
  }

最主要的两个地方是两个 externalNativeBuild 。

第 1 个 externalNativeBuild 中,可以做一些优化配置,比如只打包包含 armeabi 架构的 so :

  externalNativeBuild {
      cmake {
          cppFlags ""
      }
      ndk {
          abiFilters  "armeabi" //, "armeabi-v7a"
      }
  }

第 2 个 externalNativeBuild,主要是配置 CMakeLists.txt 的路径和版本。

Android Studio 为我们生成的关于  C/C++ 支持的主要就是以上三个地方,有了以上配置,就可以在  MainActivity 页面中正常的显示出  Hello from C++ 。

3) 在已有工程上添加 C/C++ 支持

前面就说过,在已有项目上添加 C/C++ 支持,就是由我们自己手动添加整个配置。那么根据签名介绍的三个步骤,依葫芦画瓢,就可以添加了。

这里刚好就用添加 FFMpeg so 到本系列文章现有 Demo 工程中来演示一遍。

二、引入 FFmpeg so

1. 新建 cpp 目录

首先,在 app/src/main/ 目录下,新建文件夹,并命名为 cpp 。

接着,在 cpp 目录下,右键 New -> C/C++ Source File ,新建 native-lib.cpp 文件。

接着,在 cpp 目录下,右键 New -> File ,新建 CMakeLists.txt ,先将上面 IDE 生成的那份代码粘贴进来, FFmpeg的配置在后面详细讲解。

  # CMakeLists.txt
​
  # cmake 最低版本
  cmake_minimum_required(VERSION 3.4.1)
​
  # 配置so库编译信息
  add_library( 
          # 输出so库的名称
          native-lib
​
          # 设置生成库的方式,默认为SHARE动态库
          SHARED
​
          # 列出参与编译的所有源文件
          native-lib.cpp)
​
  # 查找代码中使用到的系统库
  find_library( # Sets the name of the path variable.
        log-lib
​
        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)
​
# 指定编译目标库时,cmake要链接的库
target_link_libraries(
          # 指定目标库,native-lib 是在上面 add_library 中配置的目标库
          native-lib
​
          # 列出所有需要链接的库
          ${log-lib})

2. 将 CMakeLists 配置到 build.gradle 中

  android {
      // ...
    
      defaultConfig {
      // ...
    
      // 1) CMake 编译配置
      externalNativeBuild {
              cmake {
                  cppFlags ""
              }
          }
      }
    
      // ...
    
      // 2) 配置 CMakeLists 路径
      externalNativeBuild {
          cmake {
              path "src/main/cpp/CMakeLists.txt"
              version "3.10.2"
          }
      }
  }
​
  // ...

如果只是简单的编写 C/C++ 代码,以上基础配置就可以了。

接着来看看本文的重点,如何使用 CMakeLists.txt 引入 FFmpeg 的动态库。

3. 将 FFmpeg so 库放到对应的 CPU 架构目录

在 上一篇文章中,我们编译的 FFmpeg so 库的 CPU 架构为 armv7-a,所以,我们需要把所有的 so 库放置到 armeabi-v7a 目录下。

首先,在 app/src/main/ 目录下,新建文件夹,并命名为 jniLibs 。

app/src/main/jniLibs 是 Android Studio 默认的放置 so 动态库的目录。

接着,在 jniLibs 目录下,新建 armeabi-v7a 目录。

最后把 FFmpeg 编译得到的所有 so 库粘贴到 armeabi-v7a 目录。如下:

so目录

4. 添加 FFmpeg so 的头文件

在编译 FFmpeg 的时候,除了生成 so 外,还会生成对应的 .h 头文件,也就是 FFmpeg 对外暴露的所有接口。

FFmpeg编译输出

在 cpp 目录下,新建 ffmpeg 目录,然后把编译时生成的 include 文件粘贴进来。

头文件目录

5. 添加、链接 FFmpeg so 库

上面已经把 so 和 头文件 放置到对应的目录中了,但是编译器是不会把它们编译、链接、并打包到 Apk 中的,我们还需要在 CMakeLists.txt 中显性的把相关的 so 添加和链接起来。完整的 CMakeLists.txt 如下:

  cmake_minimum_required(VERSION 3.4.1)
​
  # 支持gnu++11
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
​
  # 1. 定义so库和头文件所在目录,方面后面使用
  set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
  set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR}/ffmpeg)
​
  # 2. 添加头文件目录
  include_directories(${ffmpeg_head_dir}/include)
​
  # 3. 添加ffmpeg相关的so库
  add_library( avutil
          SHARED
          IMPORTED )
  set_target_properties( avutil
          PROPERTIES IMPORTED_LOCATION
          ${ffmpeg_lib_dir}/libavutil.so )
​
  add_library( swresample
          SHARED
          IMPORTED )
  set_target_properties( swresample
          PROPERTIES IMPORTED_LOCATION
          ${ffmpeg_lib_dir}/libswresample.so )
        
  add_library( avcodec
          SHARED
          IMPORTED )
  set_target_properties( avcodec
          PROPERTIES IMPORTED_LOCATION
          ${ffmpeg_lib_dir}/libavcodec.so )
        
  add_library( avfilter
          SHARED
          IMPORTED)
  set_target_properties( avfilter
          PROPERTIES IMPORTED_LOCATION
          ${ffmpeg_lib_dir}/libavfilter.so )
        
  add_library( swscale
          SHARED
          IMPORTED)
  set_target_properties( swscale
          PROPERTIES IMPORTED_LOCATION
          ${ffmpeg_lib_dir}/libswscale.so )
​
  add_library( avformat
          SHARED
          IMPORTED)
  set_target_properties( avformat
          PROPERTIES IMPORTED_LOCATION
          ${ffmpeg_lib_dir}/libavformat.so )
​
  add_library( avdevice
          SHARED
          IMPORTED)
  set_target_properties( avdevice
          PROPERTIES IMPORTED_LOCATION
          ${ffmpeg_lib_dir}/libavdevice.so )
​
  # 查找代码中使用到的系统库
  find_library( # Sets the name of the path variable.
          log-lib
​
          # Specifies the name of the NDK library that
          # you want CMake to locate.
          log )
​
  # 配置目标so库编译信息
  add_library( # Sets the name of the library.
          native-lib
​
          # Sets the library as a shared library.
          SHARED
​
          # Provides a relative path to your source file(s).
          native-lib.cpp
          )
​
  # 指定编译目标库时,cmake要链接的库        
  target_link_libraries( 
​
          # 指定目标库,native-lib 是在上面 add_library 中配置的目标库
          native-lib
​
  # 4. 连接 FFmpeg 相关的库
          avutil
          swresample
          avcodec
          avfilter
          swscale
          avformat
          avdevice
​
          # Links the target library to the log library
          # included in the NDK.
          ${log-lib} )

主要看看注释中新加入的 1~4 点。

1)通过 set 方法定义了 so 和 头文件 所在目录,方便后面使用。

其中  CMAKE_SOURCE_DIR 为系统变量,指向  CMakeLists.txt 所在目录。  ANDROID_ABI 也是系统变量,指向 so 对应的  CPU 框架目录:armeabi、armeabi-v7a、x86 ...
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR}/ffmpeg)

2)通过 include_directories 设置头文件查找目录

include_directories(${ffmpeg_head_dir}/include)

3)通过 add_library 添加 FFmpeg 相关的 so 库,以及 set_target_properties 设置 so 对应的目录。

其中,add_library 第一个参数为 so 名字, SHARED 表示引入方式为动态库引入。
  add_library( avcodec
          SHARED
          IMPORTED )
  set_target_properties( avcodec
          PROPERTIES IMPORTED_LOCATION
          ${ffmpeg_lib_dir}/libavcodec.so )

4)最后,通过 target_link_libraries 把前面添加进来的 FFMpeg so 库都链接到目标库 native-lib 上。

这样,我们就将 FFMpeg 相关的 so 库都引入到当前工程中了。下面就要来测试一下,是否可以正常调用到 FFmpeg 相关的方法了。

三、使用 FFmpeg

要检查 FFmpeg 是否可以使用,可以通过获取 FFmpeg 基础信息来验证。

1. 在 FFmpegAcrtivity 中添加一个外部方法 ffmpegInfo

把获取到的 FFmpeg 信息显示出来。

  class FFmpegActivity: AppCompatActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_ffmpeg_info)
​
          tv.text = ffmpegInfo()
      }
​
      private external fun ffmpegInfo(): String
​
      companion object {
          init {
              System.loadLibrary("native-lib")
          }
      }
  }

2. 在 native-lib.cpp 中添加对应的 JNI 层方法

  #include <jni.h>
  #include <string>
  #include <unistd.h>
  
  extern "C" {
      #include <libavcodec/avcodec.h>
      #include <libavformat/avformat.h>
      #include <libavfilter/avfilter.h>
      #include <libavcodec/jni.h>
​
      JNIEXPORT jstring JNICALL
      Java_com_cxp_learningvideo_FFmpegActivity_ffmpegInfo(JNIEnv *env, jobject  /* this */) {
​
          char info[40000] = {0};
          AVCodec *c_temp = av_codec_next(NULL);
          while (c_temp != NULL) {
              if (c_temp->decode != NULL) {
                  sprintf(info, "%sdecode:", info);
                  switch (c_temp->type) {
                      case AVMEDIA_TYPE_VIDEO:
                          sprintf(info, "%s(video):", info);
                          break;
                      case AVMEDIA_TYPE_AUDIO:
                          sprintf(info, "%s(audio):", info);
                          break;
                      default:
                          sprintf(info, "%s(other):", info);
                          break;
                  }
                  sprintf(info, "%s[%10s]\n", info, c_temp->name);
              } else {
                  sprintf(info, "%sencode:", info);
              }
              c_temp = c_temp->next;
          }
          return env->NewStringUTF(info);
      }
  }

首先,我们看到代码被包裹在 extern "C" { } 当中,和前面的系统创建的稍微有些不同,通过这个大括号包裹,我们就不需要每个方法都添加单独的 extern "C" 开头了。

另外,由于 FFmpeg 是使用 C 语言编写的,所在 C++ 文件中引用 #include 的时候,也需要包裹在 extern "C" { },才能正确的编译。

方法的新建就不用说了,和前面介绍的命名方法一致。

在方法中,使用 FFmpeg 提供的方法 av_codec_next,获取到 FFmpeg 的编解码器,然后通过循环遍历,将所有的音视频编解码器信息拼接起来,最后返回给 Java 层。

至此,FFmpeg 加入到工程中,并被调用。

如果一切正常,App运行后,就会显示出 FFmpeg 音视频编解码器的信息。

原文 Android音视频: 引入FFmpeg - 掘金

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

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

相关文章

HTML5+CSS3+JS小实例:音频可视化

实例:音频可视化 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><m…

SQL语句详解二-DDL(数据定义语言)

文章目录 操作数据库创建&#xff1a;Create查询&#xff1a;Retrieve修改&#xff1a;Update删除&#xff1a;Delete使用数据库 操作表常见的几种数据类型创建&#xff1a;Create复制表 查询&#xff1a;Retrieve修改&#xff1a;Update删除&#xff1a;Delete 操作数据库 创…

【Linux】Linux系统编程——ls命令

【Linux】Linux 系统编程——ls 命令 1.命令概述 ls 命令是 Linux 和其他类 Unix 操作系统中最常用的命令之一。ls 命令是英文单词 list 的缩写&#xff0c;正如 list 的意思&#xff0c;ls 命令用于列出文件系统中的文件和目录。使用此命令&#xff0c;用户可以查看目录中的…

封装数据访问通用类DbHelper

为什么要封装通用类&#xff1f; 数据交互&#xff1a;增上改查 相同的事情&#xff1a;连接 T-SQL命令&#xff0c;Command 执行命令&#xff0c;选择执行方式 得到相应结果 关闭连接 将一些从重复的逻辑进行封装&#xff0c;达到通用的目的 提高复用率…

高防ip是什么,防护效果好吗

DDoS攻击是互联网最常见的网络攻击方式之一&#xff0c;通过大量虚假流量对目标服务器进行攻击&#xff0c;堵塞网络耗尽服务器性能&#xff0c;导致服务器崩溃&#xff0c;真正的用户无法访问。以前企业常用的防御手段就是高防服务器&#xff0c;也就是硬防&#xff0c;但由于…

C++采集亚马逊amazon产品数据教程

最近亚马逊电商非常火爆&#xff0c;今天我将用C语言写一个亚马逊商品数据的爬虫程序&#xff0c;只要是用来收集一些产品相关信息。例如产品自身特性以及产品所对应的销量&#xff0c;为了后期布局亚马逊做一些参考&#xff0c;提供数据支持&#xff0c;同时另外我也会用C语言…

鸿蒙Harmony--LocalStorage--页面级UI状态存储详解

走的太急疼的是脚&#xff0c;逼的太紧累的是心&#xff0c;很多时候&#xff0c;慢一点也没关系&#xff0c;多给自己一些耐心和等待&#xff0c;保持热爱&#xff0c;当下即是未来&#xff0c;生活自有安排! 目录 一&#xff0c;定义 二&#xff0c;LocalStorageProp定义 三…

Docker 的工作原理及安装步骤【云原生】

文章目录 1. Docker 的工作原理2. Docker 与虚拟机3. Docker 架构4. 安装 Docker5. 配置镜像加速 1. Docker 的工作原理 大型项目组件较多&#xff0c;运行环境也较为复杂&#xff0c;部署会碰到一些问题&#xff1a; ① 依赖关系复杂&#xff0c;容易出现兼容性问题&#xff1…

【WPF.NET开发】在用户控件上启用拖放功能

本文内容 创建应用程序项目向项目添加新的用户控件向主窗口添加用户控件在用户控件中实现拖动源事件向用户提供反馈在用户控件中实现拖放目标事件使面板能够接收放置的数据 在本演练中&#xff0c;将创建一个表示圆形的自定义 WPF UserControl。 你将在该控件上实现可通过拖放…

C++力扣题目112,113--路径总和,路径总和II

112路径总和 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶子节点 是…

PHP面试小结(20240108)

PHP 部分 1. php的包管理工具是如何实现自动加载的 换句话问&#xff1a;composer 实现原理是什么&#xff1f;spl_autoload_register() 首先&#xff0c;Composer 是 PHP 的一个包管理和包依赖管理的工具 &#xff0c; 打开安装之后生成的 "vendor" 文件, 里面有个…

【微信小程序】工具构建npm不生效问题

直接终端输入 npm init -y npm install express 会重新初始化package.json和重新刷新node_modules包 然后直接点npm构建 构建出来这个就完事了

机器学习_实战框架

文章目录 介绍机器学习的实战框架1.定义问题2.收集数据和预处理(1).收集数据(2).数据可视化(3).数据清洗(4).特征工程(5).构建特征集和标签集(6).拆分训练集、验证集和测试集。 3.选择算法并建立模型4.训练模型5.模型的评估和优化 介绍机器学习的实战框架 一个机器学习项目从开…

强化学习应用(四):基于Q-learning的无人机物流路径规划研究(提供Python代码)

一、Q-learning简介 Q-learning是一种强化学习算法&#xff0c;用于解决基于马尔可夫决策过程&#xff08;MDP&#xff09;的问题。它通过学习一个价值函数来指导智能体在环境中做出决策&#xff0c;以最大化累积奖励。 Q-learning算法的核心思想是通过不断更新一个称为Q值的…

STM32 定时器输入捕获1——初始化配置

当想检测高电平或低电平的持续时间的时候&#xff0c;就可以使用定时器输入捕获。例如示波器就是用到这个功能。这里就讲解一下定时器到底是如何输入捕获的&#xff1a; 由上图我们可以知道&#xff0c;周期 是每次连续的上升沿的时间差&#xff08;例如&#xff1a;T第二个方波…

AI赋能建筑设计 | VERYCLOUD睿鸿股份与亚马逊云科技协力为AIRI lab. 打造生成式AI应用案例

近年来&#xff0c;很多研究都致力于探索如何让建筑师借助人工智能的力量来促进并简化设计流程。生成式AI全球爆火以来&#xff0c;建筑设计领域也掀起了一场全新的思维变革。 AI为建筑设计带来更多可能 作为一家面向全球提供设计服务的企业&#xff0c;AIRI lab.计划推出一种…

SIP-2401VP SIP音频广播模块SIP-2401VP SIP号角音柱音箱解码poe广播播放核心板

SV-2401VP和SV-2403VP网络音频模块是一款通用的独立SIP音频功能模块&#xff0c;可以轻松地嵌入到OEM产品中。该模块对来自网络的SIP协议及RTP音频流进行编解码。 该模块支持多种网络协议和音频编解码协议&#xff0c;可用于VoIP和IP寻呼以及高质量音乐流媒体播放等应用。同时…

QT 小组件 列表框以及微调框

.cpp文件 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);QListWidgetItem *pPhone new QListWidgetItem;pPhone->setText("西瓜");pPhone->…

什么是云服务器,阿里云优势如何?

阿里云服务器ECS英文全程Elastic Compute Service&#xff0c;云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;阿里云提供多种云服务器ECS实例规格&#xff0c;如经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等&#xff0c;阿里云百科aliyunbai…

数据结构 模拟实现Queue队列(双链表模拟)

目录 一、队列的概念 二、队列的接口 三、队列的方法实现 &#xff08;1&#xff09;offer方法 &#xff08;2&#xff09;poll方法 &#xff08;3&#xff09;peek方法 &#xff08;4&#xff09;size方法 &#xff08;5&#xff09;isEmpty方法 四、最终代码 一、队…