FFmpeg第三话:FFmpeg 视频解码详解
FFmpeg 探索之旅
一、FFmpeg 简介与环境搭建
二、FFmpeg 主要结构体剖析
三、FFmpeg 视频解码详解
FFmpeg第三话:FFmpeg 视频解码详解
- FFmpeg 探索之旅
- 前言
- 一、视频解码基础
- 二、FFmpeg 关键 API 深度剖析
- (一)avformat_open_input()
- (二)avformat_find_stream_info()
- (三)avcodec_find_decoder()
- (四)avcodec_alloc_context3() 与 avcodec_parameters_to_context()
- (五)avcodec_open2()
- (六)av_read_frame() 与解码循环(含 avcodec_send_packet()、avcodec_receive_frame())
- 四、实战案例全流程解析
- 总结
前言
在多媒体技术蓬勃发展的当下,视频处理已然成为众多领域不可或缺的关键环节。而 FFmpeg,这款开源、跨平台且功能强大到近乎“神器”级别的音视频处理库,始终站在行业的前沿,为视频解码、编码、转码、滤镜处理等一系列复杂操作提供坚实的技术支撑。今天,就让我们一同深入探寻 FFmpeg 视频解码的核心世界,从基础概念到实际代码,彻底揭开它神秘的面纱。
一、视频解码基础
视频解码本质:
视频在存储与传输过程中,为削减数据量、节省带宽以及提升存储效率,会借助如 H.264、H.265、AV1 等先进编码标准进行高强度压缩。视频解码,恰似一场逆向的精密工程,旨在将这些压缩后的数据依照特定算法与规则,逐步还原为可供显示设备直接呈现或后续深度处理的原始视频帧序列,这些帧通常采用 YUV 或 RGB 色彩空间格式,每帧都蕴含着丰富的像素信息,精准勾勒出画面的每一处细节。
例如,H.264 编码巧妙运用帧间预测、帧内预测、变换编码及熵编码等复杂技术,去除画面中的冗余信息,仅保留关键数据;解码时,则需依据编码规则,反向推算出每个像素的原始取值,涉及运动补偿以还原帧间动态变化、熵解码恢复原始数据分布等关键步骤,确保画面流畅、清晰地重现。
二、FFmpeg 关键 API 深度剖析
(一)avformat_open_input()
此 API 作为开启视频解码之旅的首道大门,肩负着至关重要的使命。它接受一个 AVFormatContext
结构体指针的地址作为参数,旨在精准打开指定路径的视频文件,并深度剖析文件头信息,从而精准判定视频流的封装格式,诸如常见的 MP4、AVI、MKV 等,抑或是新兴的网络流封装格式。成功调用后,AVFormatContext
结构体将宛如一位信息渊博的向导,装满视频文件的基础元数据,涵盖文件时长、码率、各路音视频流数量及基本特性等关键情报,为后续解码流程铺就坚实基石。
示例代码:
#include <libavformat\avformat.h>
#include <stdio.h>int main()
{AVFormatContext* fmt_ctx = NULL;// 指定输入文件的路径const char* input_file_name = "input_video.mp4";// 打开输入文件int ret = avformat_open_input(&fmt_ctx, input_file_name, NULL, NULL);if (ret < 0) {// 如果打开失败,打印错误信息char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "Unable to open input video file.");return -1;}// ...// 释放资源avformat_close_input(&fmt_ctx);return 0;
}
这段代码尝试打开名为 "input_video.mp4"
的文件,若遭遇阻碍,借助 av_strerror
获取详细错误信息并输出,随即终止程序,凸显严谨的错误处理逻辑。
(二)avformat_find_stream_info()
avformat_find_stream_info()
对已打开的视频文件展开深度扫描与剖析。它遍历视频文件的每一处角落,不仅进一步完善 AVFormatContext
结构体中既有信息的细节,更精准定位各路音视频流,详细解析出视频流的分辨率、帧率、编码参数,音频流的采样率、声道布局等核心要素,为后续精准分离与处理不同类型媒体流提供精准导航。
示例代码:
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "无法获取视频流信息: %s\n", errbuf);avformat_close_input(&fmt_ctx);return -1;
}
在此,若信息获取环节出现差池,及时关闭已打开文件资源,避免内存泄漏等隐患,同时输出错误详情,确保程序稳定性与可维护性。
(三)avcodec_find_decoder()
avcodec_find_decoder()
依据视频流特定编码 ID(如 AV_CODEC_ID_H264
、AV_CODEC_ID_HEVC
等),在 FFmpeg 庞大的解码器库中迅速定位匹配解码器。一旦觅得,即刻返回 AVCodec
结构体指针,此指针恰似解码器的操控手册,掌控着解码流程的核心算法与关键参数设置,是后续构建解码环境的核心依托。
示例代码:
AVCodec *codec = NULL;
int video_stream_index = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_index = i;codec = avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id);if (!codec) {fprintf(stderr, "未找到视频解码器\n");avformat_close_input(&fmt_ctx);return -1;}break;}
}
这段代码遍历视频文件所有流,锁定视频流后竭力寻找适配解码器,若搜寻无果,果断关闭文件资源,终止程序,以防陷入无意义的后续操作。
(四)avcodec_alloc_context3() 与 avcodec_parameters_to_context()
avcodec_alloc_context3()
为选定解码器精心分配AVCodecContext
结构体内存空间,并初始化一系列默认参数,搭建起解码操作的基础场地框架,准备迎接后续精细配置。
示例代码:
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {fprintf(stderr, "无法分配解码器上下文\n");avformat_close_input(&fmt_ctx);return -1;
}
若内存分配环节遇阻,迅速清理现场,关闭文件,保障程序稳健运行。
avcodec_parameters_to_context()
负责将视频流AVStream
结构体中AVCodecParameters
所蕴含的编码参数,毫厘不差地复制到AVCodecContext
结构体中,确保解码器精准遵循视频流原始编码规则行事,从像素格式到分辨率,从帧率到码率控制参数,全方位保障解码一致性。
示例代码:
ret = avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_index]->codecpar);
if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "无法复制编解码器参数: %s\n", errbuf);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}
复制过程若现异常,立即释放已分配解码器上下文内存,关闭文件,避免资源浪费与错误蔓延。
(五)avcodec_open2()
avcodec_open2()
依据 AVCodecContext
结构体中精心配置的参数,深度初始化解码器内部复杂算法机制,调配所需系统资源,完成解码器初始化的最后冲刺。此刻,解码器宛如一台蓄势待发的引擎,静候视频数据输入,准备释放强大解码效能。
示例代码:
ret = avcodec_open2(codec_ctx, codec, NULL);
if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "无法打开解码器: %s\n", errbuf);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}
一旦解码器启动失败,迅速拆解已构建的解码环境,关闭文件,严守程序稳定防线。
(六)av_read_frame() 与解码循环(含 avcodec_send_packet()、avcodec_receive_frame())
av_read_frame()
用于严格依循视频文件封装格式规则,逐帧从文件中读取数据包,将其妥善封装于AVPacket
结构体中,该结构体满载未解码的原始视频数据、所属流索引以及关键时间戳信息,成为解码流程数据源头的稳定供给站。
示例代码:
AVPacket pkt;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {if (pkt.stream_index == video_stream_index) {// 此数据包属视频流,送解码器处理// 后续解码代码......}av_packet_unref(&pkt);
}
循环读取数据包,一旦识别出视频流数据包,即刻送入后续解码流程,每轮循环末尾,借助 av_packet_unref()
释放数据包资源,避免内存泄漏,确保数据流转顺畅。
avcodec_send_packet()
恰似解码流水线的前端“调度员”,将AVPacket
数据包精准推送至解码器输入缓冲区,若缓冲区满溢或遭遇特殊状况,及时反馈错误码,巧妙调控解码节奏,开启帧数据解码之旅。
示例代码:
ret = avcodec_send_packet(codec_ctx, &pkt);
if (ret < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "发送数据包至解码器出错: %s\n", errbuf);av_packet_unref(&pkt);continue;
}
遇发送异常,迅速处理错误,释放数据包引用,无缝衔接下一轮数据读取,保障流程连贯性。
avcodec_receive_frame()
则扮演解码流水线末端的“收获者”角色,全神贯注地尝试从解码器获取解码完毕的完整视频帧(封装于AVFrame
结构体),该结构体满载珍贵原始像素数据,静候进一步处理或存储。成功收获帧数据则返回 0,若暂无帧就绪或已达视频尾声,则相应返回特定错误码,循环调用此函数直至完整视频帧序列尽收囊中。
示例代码:
AVFrame *frame = av_frame_alloc();
while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == 0) {// 成功获取解码帧,可处理或保存// 后续帧处理代码......} else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {// 无帧或已到视频尾,跳出或继续读取数据包break;} else {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "接收解码帧出错: %s\n", errbuf);break;}
}
av_frame_free(&frame);
每轮循环谨慎判断返回值,依据不同情形灵活抉择继续读取、跳出循环或处理错误,最终释放 AVFrame
资源,完美收官解码流程。
四、实战案例全流程解析
以下奉上一段基于 FFmpeg 完整解码本地视频文件并将解码后 YUV420P 格式帧数据存储至 output.yuv
文件的示例代码,全程穿插严谨错误处理机制,确保程序稳健运行:
#include <iostream>
#include <string>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
}void handle_ffmpeg_error(int ret, const char* msg) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "%s: %s\n", msg, errbuf);
}int main() {AVFormatContext* fmt_ctx = avformat_alloc_context();std::string file_path = "F:/QT/mp4_flv/x.mp4";// 打开输入视频文件,建立 AVFormatContextint ret = avformat_open_input(&fmt_ctx, file_path.c_str(), NULL, NULL);if (ret < 0) {handle_ffmpeg_error(ret, "Failed to open video file");return -1;}// 解析视频流信息,填充 AVFormatContext 细节ret = avformat_find_stream_info(fmt_ctx, NULL);if (ret < 0) {handle_ffmpeg_error(ret, "Error in obtaining video stream information");return -1;}// 定位视频流找到适合的解码器const AVCodec* codec = NULL;int video_stream_idx = -1;for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_idx = i;codec = avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id);if (!codec) {fprintf(stderr, "Video decoder not found\n");avformat_close_input(&fmt_ctx);return -1;}break;}}// 分配解码器上下文,关联解码器与参数AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Decoder context allocation failed\n");avformat_close_input(&fmt_ctx);return -1;}ret = avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_idx]->codecpar);if (ret < 0) {handle_ffmpeg_error(ret, "Copying codec parameters failed");avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}// 打开解码器ret = avcodec_open2(codec_ctx, codec, NULL);if (ret < 0) {handle_ffmpeg_error(ret, "Decoder open failed!");avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}// 分配 AVFrame 存储解码帧,准备输出 YUV 文件AVFrame* frame = av_frame_alloc();FILE* out_file = nullptr;if (fopen_s(&out_file, "output.yuv", "wb") != 0) {perror("无法创建输出 YUV 文件");av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}// 读取视频帧数据包,解码循环AVPacket pkt;while (av_read_frame(fmt_ctx, &pkt) >= 0) {if (pkt.stream_index == video_stream_idx) {ret = avcodec_send_packet(codec_ctx, &pkt);if (ret < 0) {handle_ffmpeg_error(ret, "Error sending data packet to decoder.");av_packet_unref(&pkt);continue;}while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == 0) {// 将解码后的 YUV 数据写入文件for (int i = 0; i < frame->height; i++) {fwrite(frame->data[0] + i * frame->linesize[0], 1, frame->width, out_file);}for (int i = 0; i < frame->height / 2; i++) {fwrite(frame->data[1] + i * frame->linesize[1], 1, frame->width / 2, out_file);}for (int i = 0; i < frame->height / 2; i++) {fwrite(frame->data[2] + i * frame->linesize[2], 1, frame->width / 2, out_file);}}else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;}else {handle_ffmpeg_error(ret, "Error receiving decoded frame.");break;}}}av_packet_unref(&pkt);}// 释放资源,关闭文件与上下文fclose(out_file);av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return 0;
}
总结
本文围绕 FFmpeg 视频解码进行了全面讲解,核心内容包括:
- 视频解码基础概念:介绍视频存储与传输时会压缩,解码则是逆向还原为原始视频帧序列的过程,以常见编码标准举例说明了编码和解码的关键技术要点。
- FFmpeg 关键 API 剖析:详细解读了多个关键 API,如avformat_open_input()用于打开文件获取基础元数据;avformat_find_stream_info()完善流信息解析;avcodec_find_decoder()定位解码器;avcodec_alloc_context3()和avcodec_parameters_to_context()搭建与配置解码环境;avcodec_open2()初始化解码器;以及av_read_frame()、avcodec_send_packet()、avcodec_receive_frame()协同完成数据读取、发送与帧接收等操作,各 API 都附带有示例代码与错误处理逻辑展示。
- 实战案例解析:呈现了完整解码本地视频并存储解码帧数据的示例代码,其中融入了严谨的错误处理机制,体现从视频文件打开到最终资源释放、文件关闭的全流程操作,确保程序稳定运行。
相关文章:
FFmpeg第三话:FFmpeg 视频解码详解
FFmpeg 探索之旅 一、FFmpeg 简介与环境搭建 二、FFmpeg 主要结构体剖析 三、FFmpeg 视频解码详解 FFmpeg第三话:FFmpeg 视频解码详解 FFmpeg 探索之旅前言一、视频解码基础二、FFmpeg 关键 API 深度剖析(一)avformat_open_input()ÿ…...

解决 vue3 中 echarts图表在el-dialog中显示问题
原因: 第一次点开不显示图表,第二次点开虽然显示图表,但是图表挤在一起,页面检查发现宽高只有100px,但是明明已经设置样式宽高100% 这可能是由于 el-dialog 还没有完全渲染完成,而你的 echarts 组件已经开始尝试渲染图…...

C++ OpenGL学习笔记(4、绘制贴图纹理)
相关链接: C OpenGL学习笔记(1、Hello World空窗口程序) C OpenGL学习笔记(2、绘制橙色三角形绘制、绿色随时间变化的三角形绘制) C OpenGL学习笔记(3、绘制彩色三角形、绘制彩色矩形) 通过前面…...

关于我的Java考试被老师挂掉的这件事......
目录 1.事情起源 2.问题出现 3.最后的考试结果 4.问题如何解决的 5.此件事情引发我的思考 1.事情起源 现在是2024-12-25中午的13:08分,我于今天上虞结束了这个学期的Java课程的学习,上午的课程内容就是开始,使用MVC实现对于题目要求的这…...

Websocket客户端从Openai Realtime api Sever只收到部分数据问题分析
目录 背景 分析 解决方案 背景 正常情况下,会从Openai Realtime api Sever收到正常的json数据,但是当返回音频数据时,总会返回非json数据。这是什么问题呢? 分析 期望的完整响应数据如下: {"session": {"inp…...
Unity 6 中的新增功能
Unity 6 是 Unity 的最新版本。 一、编辑器和工作流程 Unity 6 中引入的更改 在 Linux 上实现了将文件和资源从 Unity 拖放到外部应用程序的功能。将 Asset Manager for Unity 包添加到 Package Manager > Services > Content Management 部分中。此包允许用户轻松浏览…...

[ComfyUI]颜色提取插件,Flux专属,让出图更加可控
一、介绍 今天介绍这个好玩的插件 ComfyUI APQNodes,默认的Flux模型是无法理解准确的颜色代码。 而这个插件可以帮我忙将输入的十六进制颜色代码转换为 FLUX.1 Dev 已知的最相似的颜色名称(来自预先测试的 155 个颜色名称)。 所以就…...

【magic-dash】01:magic-dash创建单页面应用及二次开发
文章目录 一、magic-dash是什么1.1 安装1.2 使用1.2.1 查看内置项目模板1.2.2 生成指定项目模板1.2.3 查看当前magic-dash版本1.2.4 查看命令说明1.2.5 内置模板列表二、创建虚拟环境并安装magic-dash三、magic-dash单页工具应用开发3.1 创建单页面项目3.1.1 使用命令行创建单页…...
ChatGPT等大语言模型与水文水资源、水环境领域的深度融合
聚焦GPT等大语言模型与水文水资源领域的深度融合,通过系统化内容与实践案例,讲解如何高效完成时间序列分析、空间数据处理、水文模型优化以及智能科学写作等任务。同时,展示AI在高级机器学习模型开发、资源优化算法编程与模型微调中的最新应用…...
机器学习连载
1 机器学习基础知识 机器学习(Machine learning)是人工智能的子集,是实现人工智能的一种途径,但并不是唯一的途径。它是一门专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已…...

linux查看天气预报
wttr.in 是一个简单且功能强大的命令行天气查询工具,实现了命令行下查看天气的炫酷效果。 开源地址:GitHub - chubin/wttr.in: :partly_sunny: The right way to check the weather 一. 什么是 wttr.in? wttr.in 是一个基于 Web 的命令行天…...
minikube start --driver=docker --force
minikube start --driver=docker --force 😄 minikube v1.34.0 on Debian 11.7 (amd64) ❗ minikube skips various validations when --force is supplied; this may lead to unexpected behavior ✨ Using the docker driver based on user configuration 🛑 The…...

游戏引擎学习第58天
发现一个vscode Log 断点的用法 回顾 我们正在继续推进工作,之前做了一些测试和清理工作,但还有一件事没有完成,因此我们还没有完全回到功能平衡的状态。昨天我们已经为实体做了空间划分,所以接下来的目标是继续完成这部分工作&a…...

我用火语言RPA生成EXE可执行文件,并使用激活码对EXE进行管理
火语言RPA,不仅可以生成EXE独立可执行文件,还可以使用激活码的功能对EXE进行管理,限制激活类型:在线、离线,EXE有效天数等进行管理,有限制的自由才是真正的自由! 生成EXE的时候选择App注册码验证类型 当分享…...

【机器学习(九)】分类和回归任务-多层感知机(Multilayer Perceptron,MLP)算法-Sentosa_DSML社区版 (1)11
文章目录 一、算法概念11二、算法原理(一)感知机(二)多层感知机1、隐藏层2、激活函数sigma函数tanh函数ReLU函数 3、反向传播算法 三、算法优缺点(一)优点(二)缺点 四、MLP分类任务实…...
32位MCU主控智能电表方案
智能电表作为电网数据采集的核心设备,承担着至关重要的角色。它主要用于采集、计量和传输原始的电能数据,确保电力系统的高效运行。该设备配备了多种通讯接口,如RS485和以太网,使得用户能够轻松进行用电检测、集中抄表以及电力管理…...
ConstraintLayout是完美的布局吗?
非也! <TextViewandroid:id"id/tv_tittle_msg"android:layout_width"wrap_content"android:layout_height"wrap_content"android:layout_marginLeft"16dp"android:layout_marginRight"16dp"android:layout_ma…...
39.在 Vue3 中使用 OpenLayers 导出 GeoJSON 文件及详解 GEOJSON 格式
一、引言 在 Web 地图开发领域,Vue3 作为一款流行的前端框架,结合强大的 OpenLayers 地图库,能够实现丰富多样的地图功能。其中,将地图数据以 GeoJSON 格式导出是一项常见且实用的需求,本文将深入探讨如何在 Vue3 环境…...

Feign的调用demo 和 EnableFeignClients的包名
在你的场景下,如果刷题微服务通过 Maven 引入了 auth-api 模块,并且 auth-api 中定义了 Feign 接口(例如获取用户名的方法),你需要在 刷题微服务 中的启动类上配置 EnableFeignClients 注解。配置中 basePackages 参数…...
简化开发流程:如何通过 JDBC 自动生成符合 Java 命名规范的实体类
在这篇博客中,我分享了如何通过 Java 和 JDBC 自动生成数据库实体类的过程。通常,手动编写实体类代码既繁琐又容易出错,尤其是在数据库表结构发生变化时,手动更新代码的工作量非常大。为了提高开发效率,我利用 JDBC 连…...
Java Stream 高级实战:并行流、自定义收集器与性能优化
一、并行流深度实战:大规模数据处理的性能突破 1.1 并行流的核心应用场景 在电商用户行为分析场景中,需要对百万级用户日志数据进行实时统计。例如,计算某时段内活跃用户数(访问次数≥3次的用户),传统循环…...
Linux 常用命令语法总结
Linux 常用命令语法总结 1. 文件和目录操作 1.1 基本文件操作 # 列出文件和目录 ls # 列出当前目录内容 ls -l # 详细列表格式 ls -la # 显示隐藏文件 ls -lh # 人性化显示文件大小 ls...

Mac 安装git心路历程(心累版)
省流版:直接安装Xcode命令行工具即可,不用安Xcode。 git下载官网 第一部分 上网初步了解后,打算直接安装Binary installer,下载完安装时,苹果还阻止安装,只好在“设置–安全性与隐私”最下面的提示进行安…...
动静态库的使用(Linux)
1.库 通俗来说,库就是现有的,可复用的代码,例如:在C/C语言编译时,就需要依赖相关的C/C标准库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。通常我们可以在windows下看到一些后…...
daz3d + PBRSkin (MDL)+ SSS
好的,我们来解释一下 Daz3D 中的 PBRSkin (MDL) Shader。 简单来说,PBRSkin (MDL) 是 Daz Studio 中一种基于物理渲染(PBR)技术、专门用于创建高度逼真人物皮肤效果的着色器(Shader)。 它利用 NVIDIA 的材…...
计算矩阵A和B的乘积
根据矩阵乘法规则,编程计算矩阵的乘积。函数fix_prod_ele()是基本方法编写,函数fix_prod_opt()是优化方法编写。 程序代码 #define N 3 #define M 4 typedef int fix_matrix1[N][M]; typedef int fix_matrix2[M][N]; int fix_prod_ele(f…...
Vue Fragment vs React Fragment
文章目录 前言🧩 一、概念对比:Vue Fragment vs React Fragment📦 二、使用示例对比✅ Vue 3 中使用 Fragment✅ React 中使用 Fragment 🔍 三、差异解析1. **使用方式**2. **传递属性(如 key)**3. **插槽系…...

三维GIS开发cesium智慧地铁教程(4)城市白模加载与样式控制
一、添加3D瓦片 <!-- 核心依赖引入 --> <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"><!-- 模型数据路径 --> u…...
玩转抖音矩阵:核心玩法与高效运营规则
一、 抖音矩阵:流量协同的生态网络 抖音矩阵,本质是运营一个相互关联、互相支持的抖音账号群。核心目标在于通过账号间的深度协同(内容、流量、粉丝),打破单个账号的流量天花板,实现11>2的效果。它不仅…...

Vue-3-前端框架Vue基础入门之VSCode开发环境配置和Tomcat部署Vue项目
文章目录 1 安装配置VSCode1.1 安装中文语言插件1.2 主题颜色1.3 禁用自动更新1.4 开启代码提示设置1.5 安装open in browser插件2 安装配置nodejs2.1 配置环境变量2.2 npm与maven的区别2.3 使用npm避坑3 创建Vue项目3.1 两种创建方式3.2 package.json3.3 安装新的依赖3.4 运行…...