FFmpeg的入门实践系列五(编程入门之属性查看)

news2024/11/16 11:36:24

在这里插入图片描述

欢迎诸位来阅读在下的博文~
在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力

文章目录

  • 前期博客
  • 参考书籍
  • 一、AVFormatContext结构体
      • 1. 结构定义
      • 2. 字段说明
      • 3.示例1(打开与关闭音视频文件)
      • 4.实例2(查看音视频的信息)
  • 二、AVStream结构体
      • 实例:查看数据流的编解码器的参数
  • 附件

前期博客

FFmpeg的入门实践系列一(环境搭建)
FFmpeg的入门实践系列二(基础知识)
FFmpeg的入门实践系列三(基础知识)
FFmpeg的入门实践系列四(AVS)

参考书籍

《FFmpeg开发实战——从零基础到短视频上线》——欧阳燊

一、AVFormatContext结构体

AVFormatContext 是 FFmpeg 中用于处理多媒体文件格式的一个核心结构体。它包含了文件的格式信息,以及与文件相关的流信息,如音频流、视频流和字幕流。以下是 AVFormatContext 的详细信息:

1. 结构定义

typedef struct AVFormatContext {
    const AVClass *av_class;       // AVClass提供日志处理等功能
    struct AVInputFormat *iformat; // 输入格式
    struct AVOutputFormat *oformat;// 输出格式
    void *priv_data;               // 私有数据,特定于输入或输出格式
    AVIOContext *pb;               // 读写数据的IO上下文
    int ctx_flags;                 // 上下文标志
    
    unsigned int nb_streams;       // 流的数量
    AVStream **streams;            // 指向流的指针数组
    char filename[1024];           // 文件名
    // 一些时间基准和时长信息
    int64_t duration;              // 文件时长
    int64_t bit_rate;              // 比特率
    unsigned int packet_size;      // 数据包大小
    int max_delay;                 // 最大延迟
    // 其他与解码、时序、元数据等相关的字段
    AVDictionary *metadata;        // 文件元数据,如标题、作者等
    // 其他字段省略...
} AVFormatContext;

2. 字段说明

  • av_class:指向 AVClass 的指针,用于日志处理、调试和其他全局设置。
  • iformatoformat:分别指向输入格式和输出格式结构体。用于定义当前上下文是用于输入还是输出。
  • priv_data:格式特定的私有数据,通常由特定的输入或输出格式使用。
  • pbAVIOContext 用于管理输入输出操作的上下文,比如读写文件或网络数据。
  • ctx_flags:用于标志一些上下文状态,比如是否允许缺少流、是否实时流等。
  • nb_streams:表示文件中包含的流的数量(音频流、视频流、字幕流等)。
  • streams:一个指向 AVStream 指针数组的指针,每个 AVStream 结构体包含一个具体流的信息。
  • filename:文件名或 URL,用于标识输入或输出资源。
  • duration:表示文件的总时长,以 AV_TIME_BASE(通常是微秒)为单位。
  • bit_rate:文件的总比特率(包括所有流)。
  • packet_size:数据包的大小,主要用于输出。
  • max_delay:最大延迟,用于管理实时流的延迟处理。
  • metadata:文件的元数据,存储在一个 AVDictionary 中。

3.示例1(打开与关闭音视频文件)

#include <stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#ifdef __cplusplus
};
#endif

int main(int argc, char** argv){
    const char* filename = "./fuzhou.mp4";
    if(argc > 1){
        filename = argv[1];
    }
    AVFormatContext* fmt_ctx = NULL;
    //打开音视频文件
    int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "open file:%s fail\n", filename);
        return -1;
    }
    av_log(NULL, AV_LOG_INFO, "success open input_file %s \n", filename);
    // 查找音视频文件中的流信息
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "find streaminfo fail\n");
        return -1;
    }
    av_log(NULL, AV_LOG_INFO, "success find streaminfo\n");
    const AVInputFormat* iformat = fmt_ctx->iformat;
    av_log(NULL, AV_LOG_INFO, "format name is %s \n", iformat->name);
    av_log(NULL, AV_LOG_INFO, "format long_name is %s \n",iformat->long_name);
    avformat_close_input(&fmt_ctx); //关闭音频文件
    return 0; 
}

编译:

gcc helloffmpeg.c -o helloffmpeg -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm

输出结果:
在这里插入图片描述

4.实例2(查看音视频的信息)

#include <stdio.h>

// 之所以增加__cplusplus的宏定义,是为了同时兼容gcc编译器和g++编译器
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#ifdef __cplusplus
};
#endif

int main(int argc, char **argv) {
    const char *filename = "../fuzhou.mp4";
    if (argc > 1) {
        filename = argv[1];
    }
    AVFormatContext *fmt_ctx = NULL;
    // 打开音视频文件
    int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Can't open file %s.\n", filename);
        return -1;
    }
    av_log(NULL, AV_LOG_INFO, "Success open input_file %s.\n", filename);
    // 查找音视频文件中的流信息
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Can't find stream information.\n");
        return -1;
    }
    av_log(NULL, AV_LOG_INFO, "Success find stream information.\n");
    // 格式化输出文件信息
    av_dump_format(fmt_ctx, 0, filename, 0);
    av_log(NULL, AV_LOG_INFO, "duration=%d\n", fmt_ctx->duration); // 持续时间,单位微秒
    av_log(NULL, AV_LOG_INFO, "bit_rate=%d\n", fmt_ctx->bit_rate); // 比特率,单位比特每秒
    av_log(NULL, AV_LOG_INFO, "nb_streams=%d\n", fmt_ctx->nb_streams); // 数据流的数量
    av_log(NULL, AV_LOG_INFO, "max_streams=%d\n", fmt_ctx->max_streams); // 数据流的最大数量
    // 找到视频流的索引
    int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    av_log(NULL, AV_LOG_INFO, "video_index=%d\n", video_index);
    if (video_index >= 0) {
        AVStream *video_stream = fmt_ctx->streams[video_index];
        av_log(NULL, AV_LOG_INFO, "video_stream index=%d\n", video_stream->index);
        av_log(NULL, AV_LOG_INFO, "video_stream start_time=%d\n", video_stream->start_time);
        av_log(NULL, AV_LOG_INFO, "video_stream nb_frames=%d\n", video_stream->nb_frames);
        av_log(NULL, AV_LOG_INFO, "video_stream duration=%d\n", video_stream->duration);
    }
    // 找到音频流的索引
    int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    av_log(NULL, AV_LOG_INFO, "audio_index=%d\n", audio_index);
    if (audio_index >= 0) {
        AVStream *audio_stream = fmt_ctx->streams[audio_index];
        av_log(NULL, AV_LOG_INFO, "audio_stream index=%d\n", audio_stream->index);
        av_log(NULL, AV_LOG_INFO, "audio_stream start_time=%d\n", audio_stream->start_time);
        av_log(NULL, AV_LOG_INFO, "audio_stream nb_frames=%d\n", audio_stream->nb_frames);
        av_log(NULL, AV_LOG_INFO, "audio_stream duration=%d\n", audio_stream->duration);
    }
    avformat_close_input(&fmt_ctx); // 关闭音视频文件
    return 0;
}

编译:

gcc look.c -o look -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm

输出结果:
在这里插入图片描述

二、AVStream结构体

数据流对应FFmpeg的AVStream结构,调用avformat_new_stream函数会给音视频文件创建指定编码器的数据流。FFmpeg支持的数据流类型见表如下:
当然,以下是一个Markdown格式的表格,展示了FFmpeg支持的数据流类型及其对应的数值:

数据流类型对应数值描述
视频流0包含视频数据,支持多种编解码器,如H.264、H.265、VP8、VP9等。
音频流1包含音频数据,支持多种编解码器,如AAC、MP3、OGG、WMA等。
字幕流2包含视频的字幕或文本信息,可以是硬字幕或软字幕。
数据流3可以包含任何类型的数据,如图片、文本等。
附件流4通常包含与视频或音频相关的额外文件,如封面图片、歌词等。
媒体类型数量5表示媒体类型的数量。

AVFormatContext结构体中的stream字段就是AVStream的指针数组,里面放的是AVStream的流对象,有视频流、音频流等。AVStream结构体的常见字段如下:

  • index:当前数据流的索引
  • start_time:当前数据流的开始播放时间戳
  • nb_frames:当前数据流包含的数据帧个数
  • duration:当前数据流的结束播放时间戳
  • codecpar:一个指向 AVCodecParameters 结构的指针,包含与流的解码器相关的参数,如编码器类型、比特率、采样率、分辨率等

详细实例请看上面的实例2。

记得一提的是,每个音视频文件都有若干数据流,每个数据流也都有相应的编解码器,诸位可使用avcodec_find_decoder和avcodec_find_encoder分别查找,但是一般来说,编解码器的两个函数接口返回的值是一样的,比如都是H264。这里可以灵活使用。

以下是常见的编解码器与编码标准的对应关系:

编解码器编号对应数值编解码器名称采用的编码标准
AV_CODEC_ID_H26428H.264ITU-T H.264/AVC
AV_CODEC_ID_H265174H.265ITU-T H.265/HEVC
AV_CODEC_ID_VP8139VP8WebM Project
AV_CODEC_ID_VP9167VP9WebM Project
AV_CODEC_ID_AAC86018AACISO/IEC 13818-7
AV_CODEC_ID_MP386016MP3ISO/IEC 11172-3
AV_CODEC_ID_OGG86021OGG/VORBISXiph.Org Foundation
AV_CODEC_ID_WMAV186025WMAV1Microsoft
AV_CODEC_ID_WMAV286026WMAV2Microsoft
AV_CODEC_ID_AV1285AV1Alliance for Open Media
AV_CODEC_ID_FLAC86028FLACXiph.Org Foundation
AV_CODEC_ID_OPUS86030OPUSXiph.Org Foundation

实例:查看数据流的编解码器的参数

对于音视频的数据流AVStream来说,FFmpeg把编解码器的编号保存在其codecpar属性的codec_id字段。调用avcodec_find_decoder函数传入编解码器的编号,即可获得编解码器的结构指针。

#include <stdio.h>

// 之所以增加__cplusplus的宏定义,是为了同时兼容gcc编译器和g++编译器
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#ifdef __cplusplus
};
#endif

int main(int argc, char** argv){
    const char* filename = "./fuzhou.mp4";
    if(argc > 1){
        filename = argv[1];
    }
    AVFormatContext* fmt_ctx = NULL;
    //发开音视频文件
    int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "open file fail\n");
        return -1;
    }
    av_log(NULL, AV_LOG_INFO, "success open file\n");
    
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if(ret < 0){
        av_log(NULL, AV_LOG_ERROR, "stream info is empty\n");
        return -1;
    }
    //打印音视频文件属性
    av_log(NULL, AV_LOG_INFO, "Success find stream information.\n");
    av_log(NULL, AV_LOG_INFO, "duration=%d\n", fmt_ctx->duration); // 持续时间,单位微秒
    av_log(NULL, AV_LOG_INFO, "nb_streams=%d\n", fmt_ctx->nb_streams); // 数据流的数量
    av_log(NULL, AV_LOG_INFO, "max_streams=%d\n", fmt_ctx->max_streams); // 数据流的最大数量
    av_log(NULL, AV_LOG_INFO, "video_codec_id=%d\n", fmt_ctx->video_codec_id);
    av_log(NULL, AV_LOG_INFO, "audio_codec_id=%d\n", fmt_ctx->audio_codec_id);

    //获取视频流的索引
    int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    av_log(NULL, AV_LOG_INFO, "video_index=%d\n", video_index);
    if (video_index >= 0) {
        AVStream *video_stream = fmt_ctx->streams[video_index];
        enum AVCodecID video_codec_id = video_stream->codecpar->codec_id;
//        av_log(NULL, AV_LOG_INFO, "video_stream codec_id=%d\n", video_codec_id);
        // 查找视频解码器
        AVCodec *video_codec = (AVCodec*) avcodec_find_decoder(video_codec_id);
        if (!video_codec) {
            av_log(NULL, AV_LOG_ERROR, "video_codec not found\n");
            return -1;
        }
        av_log(NULL, AV_LOG_INFO, "video_codec id=%d\n", video_codec->id);
        av_log(NULL, AV_LOG_INFO, "video_codec name=%s\n", video_codec->name);
        av_log(NULL, AV_LOG_INFO, "video_codec long_name=%s\n", video_codec->long_name);
        // 下面的type字段来自AVMediaType定义,为0表示AVMEDIA_TYPE_VIDEO,为1表示AVMEDIA_TYPE_AUDIO
        av_log(NULL, AV_LOG_INFO, "video_codec type=%d\n", video_codec->type);
    }

    //获取音频流的索引
    int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    av_log(NULL, AV_LOG_INFO, "audio_index=%d\n", audio_index);
    if (audio_index >= 0) {
        AVStream *audio_stream = fmt_ctx->streams[audio_index];
        enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id;
//        av_log(NULL, AV_LOG_INFO, "audio_stream codec_id=%d\n", audio_codec_id);
        // 查找音频解码器
        AVCodec *audio_codec = (AVCodec*) avcodec_find_decoder(audio_codec_id);
        if (!audio_codec) {
            av_log(NULL, AV_LOG_ERROR, "audio_codec not found\n");
            return -1;
        }
        av_log(NULL, AV_LOG_INFO, "audio_codec id=%d\n", audio_codec->id);
        av_log(NULL, AV_LOG_INFO, "audio_codec name=%s\n", audio_codec->name);
        av_log(NULL, AV_LOG_INFO, "audio_codec long_name=%s\n", audio_codec->long_name);
        av_log(NULL, AV_LOG_INFO, "audio_codec type=%d\n", audio_codec->type);
    }

    
    avformat_close_input(&fmt_ctx); // 关闭音视频文件
    return 0;
}

编译:

gcc codec.c -o codec -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm

输出结果:
在这里插入图片描述

附件

编译代码要连接各种库,着实麻烦。为了方便大家编译代码,在下提供了CMakeLists.txt文件,只需要更改相应.c文件名和输出文件即可,诸君自取~

cmake_minimum_required(VERSION 3.10)

# 项目名称
project(Helloffmpeg)

# 设置 C 标准
set(CMAKE_C_STANDARD 99)

# 指定源文件(自行修改)
set(SRC codec.c) 

# 指定头文件搜索路径
include_directories(/usr/local/ffmpeg/include)

# 指定库文件搜索路径
link_directories(/usr/local/ffmpeg/lib)

# 添加可执行文件(自行修改)
add_executable(codec ${SRC})

# 链接 FFmpeg 库(自行修改)
target_link_libraries(codec
    avformat
    avdevice
    avfilter
    avcodec
    avutil
    swscale
    swresample
    postproc
    m
)

至此,结束~
在这里插入图片描述
望诸位不忘三连支持一下~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器人学——机械臂轨迹规划-1

引言 理想轨迹 步骤-1 步骤-2 笛卡尔空间下的轨迹规划 步骤-1 步骤-2 三次多项式 矩阵形式求解 det(T): 行列式&#xff0c;非齐次多项式&#xff0c;结果不为零&#xff0c;有唯一解、行列式为零&#xff08;无穷解/无解&#xff0c;还需查看增广矩阵的秩&#xff09; 速度…

Linux网络编程:多路转接--select

1. 初识select 系统提供select函数来实现多路复用输入/输出模型. select系统调用是用来让我们的程序监视多个文件描述符的状态变化的; 程序会停在select这里等待&#xff0c;直到被监视的文件描述符有一个或多个发生了状态改变 select只负责等待&#xff0c;可以等待多个fd&a…

内容创作者福音,4款文章改写神器轻松提升文章质量

在信息爆炸的时代&#xff0c;内容创作成为了连接世界的重要桥梁。作为一名专业创作者&#xff0c;我深知保持内容原创性和高质量的重要性。然而&#xff0c;灵感有时会枯竭&#xff0c;改写文章成为一项耗时且艰巨的任务。幸运的是&#xff0c;市面上有一些文章改写神器&#…

Flask+LayUI开发手记(四):弹出层实现增删改查功能

在上一节用dataTable实现数据列表时&#xff0c;已经加了表头工具栏和表内工具栏&#xff0c;栏内的按钮功能都是用来完成数据的增删改查了&#xff0c;这又分成两类功能&#xff0c;一类是删除或设置&#xff0c;这类功能简单&#xff0c;只需要选定记录&#xff0c;然后提交到…

Flutter 自动化测试 - 集成测试篇

Flutter集成测试 Flutter官方对Flutter应用测试类型做了三个阶段划分&#xff0c;分别为Unit&#xff08;单元&#xff09;测试、Widget&#xff08;组件&#xff09;测试、Integration&#xff08;集成&#xff09;测试。按照维护成本来看的话从左到右依次增高&#xff0c;按照…

预测癌症免疫治疗反应-TIDE数据库学习及知识整理

TIDE&#xff08;Tumor Immune Dysfunction and Exclusion&#xff09; 是一个用于预测癌症患者对免疫检查点抑制剂&#xff08;如PD-1/PD-L1抑制剂&#xff09;反应的算法。研究者通过检测肿瘤建模队列中每个基因的表达与效应性毒性T淋巴细胞(CTL)浸润水平的相互关系及对生存情…

Open3D 近似点体素滤波(36)

Open3D 近似点体素滤波(36) 一、算法介绍二、算法实现1.代码2.效果一、算法介绍 这个算法也是体素滤波, 它保留的点是近似点,也就是新的点,原始点云中对应位置是不存在这些点的。其他的看着类似,下面是代码,滤波抽稀结果 二、算法实现 1.代码 代码如下(示例): …

学习文件IO,让你从操作系统内核的角度去理解输入和输出(Java实践篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

【在Linux世界中追寻伟大的One Piece】应用层协议HTTP

目录 1 -> HTTP协议 2 -> 认识URL 2.1 -> urlencode和urldecode 3 -> HTTP协议请求与响应格式 3.1 -> HTTP请求 3.2 -> HTTP响应 4 -> HTTP的方法 4.1 -> HTTP常见方法 5 -> HTTP的状态码 6 -> HTTP常见Header 7 -> 最简单的HTTP服…

Linux系统报错“version ‘GLIBC_2.34‘ not found”解决方法

注意&#xff0c;此文章慎用&#xff0c;glibc不可随意升级&#xff0c;可能导致系统崩溃 一、查看版本 ldd --version 二、添加高版本源 sudo vi /etc/apt/sources.list.d/my.list 进入编辑页面 "i"键进入插入模式 输入源 deb http://th.archive.ubuntu.com/…

【信创】推荐一款超级好用的文件同步备份工具 _ 统信 _ 麒麟 _ 方德

往期好文&#xff1a;【信创】统信UOS打包工具介绍与使用教程 Hello&#xff0c;大家好啊&#xff01;今天给大家推荐一款在Linux系统上超级好用的文件同步和备份工具——FreeFileSync。无论是在日常工作还是数据管理中&#xff0c;文件同步和备份都是至关重要的任务。FreeFile…

【自动驾驶】控制算法(五)连续方程离散化与离散LQR原理

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

QT6 setCentralWidget 和 takeCentralWidget

qt6 中&#xff0c;初始化界面完成之后&#xff0c;可以使用setCentralWidget 设置当前的widget为中心页面 如果你存在多个widget想要多个切换 如果存在widget1 和 widget2 在初始化的时候 setCentralWidget(widget1)触发操作切换到 widget2 如果没有先takeCentralWidget 直…

13.深入解析ThreadPoolExecutor线程池

ThreadPoolExecutor线程池 线程池简介线程池的使用创建线程池ThreadPoolExecutor——推荐使用线程池的核心参数 Executors——不推荐使用 提交任务如何执行批量任务如何执行定时、延时任务如何执行周期、重复性任务 关闭线程池线程池的参数设计分析核心线程数(corePoolSize)最大…

EEMD-MPE-KPCA-BiLSTM、EEMD-MPE-BiLSTM、EEMD-PE-BiLSTM故障识别、诊断(Matlab)

EEMD-MPE-KPCA-BiLSTM(集合经验分解-多尺度排列熵-核主元分析-双向长短期网络)故障识别、诊断&#xff08;Matlab) 目录 EEMD-MPE-KPCA-BiLSTM(集合经验分解-多尺度排列熵-核主元分析-双向长短期网络)故障识别、诊断&#xff08;Matlab)效果一览基本介绍程序设计参考资料 效果一…

RK3588人工智能开发----【1】初识NPU

NPU 的诞生&#xff01; 随着人工智能和大数据时代的到来&#xff0c;传统嵌入式处理器中的CPU和GPU逐渐无法满足日益增长的深度学习需求。为了应对这一挑战&#xff0c;在一些高端处理器中&#xff0c;NPU&#xff08;神经网络处理单元&#xff09;也被集成到了处理器里。NPU的…

【GNSS射频前端】MA2769初识

MAX2769 芯片概述&#xff1a; MAX2769是一款单芯片多系统GNSS接收器&#xff0c;采用Maxim的低功耗SiGe BiCMOS工艺技术。集成了包括双输入低噪声放大器&#xff08;LNA&#xff09;、混频器、图像拒绝滤波器、可编程增益放大器&#xff08;PGA&#xff09;、压控振荡器&#…

note38:tdsql数据库迁移

数据迁移过程中遇到的具体问题&#xff1a; ①提供给系统团队的表结构与生产不一致&#xff0c;导致脚本报错。因为历史遗留问题&#xff0c;存在部分直接在生产环境更改字段长度或添加索引的情况&#xff0c;导致测试环境和生产环境的表结构不同步。 今后所有生产的变动&…

Vulkan 学习(5)---- Vulkan 内存分配

目录 Overview枚举内存信息分配内存内存映射 Overview Vulkan 将内存管理的工作交给了开发者自己负责&#xff0c;如何分配内存&#xff0c;如何指定内存策略都是由开发者自己决定的&#xff0c;当然处理问题也是由开发者自己负责的 Vulkan 将内存划分为两大类&#xff1a;主…

Android自定义简单仿QQ运动步数进展圆环

实现效果主要效果分为三个部分&#xff1a; 1.固定蓝色的大圆弧 color borderWidth 2.可以变化的小圆弧(红色) color borderWidth 3.中间的步数文字 color textSize drawArc方法 startAngle 确定角度的起始位置 sweepAngle 确定扫过的角度 useCenter 是否使用中心&#xff1a…