当前位置: 首页 > news >正文

基于ffmpeg和sdl2的简单视频播放器制作

基于ffmpeg和sdl2的简单视频播放器制作

    • 前言
    • 一、视频播放器开发的基础
      • 1.1 视频播放原理
      • 1.2 开发所需的库
    • 二、FFmpeg库详解
      • 2.1 FFmpeg库的组成
      • 2.2 关键数据结构
      • 2.3 打开视频文件并获取流信息
      • 2.4 查找视频流和解码器
      • 2.5 初始化解码器
    • 三、SDL库详解
      • 3.1 SDL库的功能
      • 3.2 初始化SDL
      • 3.3 创建窗口、渲染器和纹理
      • 3.4 事件处理
    • 四、视频播放流程
      • 4.1 读取和解码视频帧
      • 4.2 时间同步
      • 4.3 图像格式转换与渲染
    • 五、资源清理
    • 六、完整代码示例
    • 七、总结与展望


前言

本文将简单探讨视频播放器的开发过程,通过一个完整的代码示例,带你领略从打开视频文件到播放视频画面的每一个关键步骤。


一、视频播放器开发的基础

1.1 视频播放原理

视频,本质上是一系列连续的图像帧快速播放所形成的视觉效果。在数字视频中,这些图像帧被编码压缩以减小文件大小,便于存储和传输。常见的视频编码格式有H.264、H.265等。同时,视频文件通常还包含音频数据,音频也经过特定的编码方式,如AAC、MP3等。

当我们播放视频时,播放器需要执行以下主要步骤:

  1. 解封装:从视频文件中分离出视频流和音频流。视频文件通常采用某种封装格式,如MP4、AVI等,这些格式将视频和音频数据按照一定的结构组织在一起。
  2. 解码:对分离出的视频流和音频流进行解码,将压缩的数据还原为原始的图像帧和音频样本。这需要使用相应的解码器,不同的编码格式需要不同的解码器。
  3. 渲染:将解码后的图像帧显示在屏幕上,同时将音频样本通过音频设备播放出来。这涉及到图形渲染和音频输出的相关技术。

1.2 开发所需的库

在开发视频播放器时,我们需要借助一些强大的开源库来简化开发过程。在本文的示例中,我们主要使用了以下两个库:

  1. FFmpeg:这是一个功能强大的开源多媒体框架,提供了丰富的工具和函数,用于处理多媒体文件的解封装、解码、编码等操作。它支持几乎所有常见的多媒体格式,并且具有高效的性能。
  2. SDL (Simple DirectMedia Layer):这是一个跨平台的多媒体开发库,专注于提供硬件抽象层,用于创建窗口、渲染图形、播放音频以及处理输入事件等。它使得我们能够在不同的操作系统上轻松实现一致的多媒体交互功能。

二、FFmpeg库详解

2.1 FFmpeg库的组成

FFmpeg库由多个模块组成,每个模块都有其特定的功能:

  1. libavformat:负责处理多媒体文件的格式,包括解封装和封装操作。它能够识别各种常见的视频和音频封装格式,如MP4、AVI、FLV等,并从中提取出视频流和音频流。
  2. libavcodec:这是FFmpeg的核心编解码模块,支持众多的音频和视频编码格式。它包含了各种解码器和编码器,能够将压缩的多媒体数据进行解码或编码操作。
  3. libavutil:提供了一系列通用的工具函数和数据结构,如内存管理、错误处理、数学运算等。这些工具函数在整个FFmpeg库的其他模块中被广泛使用。
  4. libswscale:用于图像的缩放和格式转换。在视频播放过程中,由于解码后的图像格式可能与显示设备所需的格式不一致,需要使用该模块进行转换。
  5. libswresample:主要用于音频的重采样和格式转换。它可以将音频数据从一种采样率、声道数或样本格式转换为另一种,以适应不同的音频输出设备。

2.2 关键数据结构

  1. AVFormatContext:这个结构体是FFmpeg中用于管理多媒体文件格式的上下文。它包含了文件的各种信息,如流的数量、每个流的参数等。在打开视频文件时,我们会创建一个AVFormatContext对象,并使用它来读取文件的信息和解封装数据。
  2. AVCodecContext:代表编解码器的上下文,包含了编解码所需的各种参数,如编码格式、分辨率、帧率等。在找到合适的解码器后,我们需要创建一个AVCodecContext对象,并将其与解码器进行关联。
  3. AVFrame:用于存储解码后的音频或视频数据。对于视频来说,它包含了一帧图像的像素数据;对于音频来说,它包含了音频样本数据。
  4. AVPacket:用于存储从文件中读取的压缩数据,这些数据在经过解封装后以AVPacket的形式存在,然后被传递给解码器进行解码。

2.3 打开视频文件并获取流信息

在我们的代码示例中,首先需要打开视频文件并获取其流信息:

AVFormatContext* fmt_ctx = NULL;
std::string file_path = "F:/QT/mp4_flv/x.mp4";// 打开视频文件
int 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;
}// 读取视频流信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {handle_ffmpeg_error(ret, "Error in obtaining video stream information.");avformat_close_input(&fmt_ctx);return -1;
}

这里,avformat_open_input函数用于打开指定路径的视频文件,并将文件信息存储在fmt_ctx中。如果打开失败,会调用handle_ffmpeg_error函数进行错误处理。接着,avformat_find_stream_info函数用于读取视频文件中的流信息,包括视频流和音频流的参数等。同样,如果读取失败,也会进行相应的错误处理。

2.4 查找视频流和解码器

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;}
}

这段代码通过遍历fmt_ctx中的所有流,找到类型为视频流(AVMEDIA_TYPE_VIDEO)的流,并获取其索引video_stream_idx。然后,根据流的编码ID,使用avcodec_find_decoder函数查找对应的解码器。如果找不到解码器,会输出错误信息并关闭文件。

2.5 初始化解码器

找到解码器后,需要对其进行初始化:

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;
}

首先,使用avcodec_alloc_context3函数分配一个AVCodecContext对象。然后,将流的编码参数复制到codec_ctx中,最后使用avcodec_open2函数打开解码器。每一步操作都进行了错误处理,确保解码器能够正确初始化。

三、SDL库详解

3.1 SDL库的功能

SDL库主要用于创建图形窗口、渲染图像以及处理用户输入事件。它提供了一系列简单易用的函数,使得我们能够在不同的操作系统上实现一致的图形界面和交互功能。

  1. 窗口管理:SDL可以创建和管理窗口,设置窗口的大小、位置、标题等属性。它还支持窗口的最小化、最大化、关闭等操作。
  2. 图形渲染:提供了多种渲染方式,包括软件渲染和硬件加速渲染。可以将图像数据渲染到窗口上,实现视频画面的显示。
  3. 事件处理:能够捕获和处理各种用户输入事件,如鼠标点击、键盘按键、窗口关闭等事件,使得我们的程序能够响应用户的操作。

3.2 初始化SDL

在使用SDL之前,需要先对其进行初始化:

if (SDL_Init(SDL_INIT_VIDEO) < 0) {fprintf(stderr, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError());av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}

这里使用SDL_Init函数初始化SDL库的视频子系统。如果初始化失败,会输出错误信息并释放之前分配的资源。

3.3 创建窗口、渲染器和纹理

SDL_Window* window = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,codec_ctx->width, codec_ctx->height, SDL_WINDOW_SHOWN);
if (!window) {fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError());SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {fprintf(stderr, "Renderer could not be created! SDL_Error: %s\n", SDL_GetError());SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,codec_ctx->width, codec_ctx->height);
if (!texture) {fprintf(stderr, "Texture could not be created! SDL_Error: %s\n", SDL_GetError());SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;
}

这段代码依次创建了窗口、渲染器和纹理。SDL_CreateWindow函数用于创建一个指定大小和标题的窗口。SDL_CreateRenderer函数创建一个渲染器,用于将图像渲染到窗口上,这里使用了硬件加速渲染(SDL_RENDERER_ACCELERATED)。最后,SDL_CreateTexture函数创建一个纹理,用于存储视频图像数据,以便后续渲染。同样,每一步创建操作都进行了错误处理,如果创建失败,会释放之前创建的资源。

3.4 事件处理

在视频播放过程中,需要处理用户的输入事件,如关闭窗口事件:

SDL_Event event;
while (SDL_PollEvent(&event)) {if (event.type == SDL_QUIT) {goto cleanup;}
}

这里使用SDL_PollEvent函数不断检查是否有事件发生。如果检测到SDL_QUIT事件(即用户点击了窗口的关闭按钮),则跳转到cleanup标签处,进行资源清理操作。

四、视频播放流程

4.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, "send data decoder error.");av_packet_unref(&pkt);continue;}while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == 0) {// 处理解码后的帧}else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;}}}av_packet_unref(&pkt);
}

在循环中,使用av_read_frame函数从视频文件中读取一个数据包pkt。如果数据包属于视频流(通过pkt.stream_index判断),则将其发送给解码器进行解码。avcodec_send_packet函数将数据包发送给解码器,avcodec_receive_frame函数从解码器中接收解码后的帧。如果解码成功(ret == 0),则可以对解码后的帧进行进一步处理;如果解码器需要更多数据(ret == AVERROR(EAGAIN))或已经到达文件末尾(ret == AVERROR_EOF),则退出内层循环。每次处理完数据包后,使用av_packet_unref函数释放数据包的引用。

4.2 时间同步

为了保证视频播放的流畅性和音频视频的同步,需要进行时间同步:

int64_t start_time = av_gettime();// 在解码帧的处理部分
int64_t pts = frame->pts;
if (pts == AV_NOPTS_VALUE) {pts = av_rescale_q(pkt.dts, fmt_ctx->streams[video_stream_idx]->time_base, { 1, 1000000 });
}
else {pts = av_rescale_q(frame->pts, fmt_ctx->streams[video_stream_idx]->time_base, { 1, 1000000 });
}int64_t now = av_gettime();
if (pts > now - start_time) {SDL_Delay((pts - (now - start_time)) / 1000);
}

这里使用av_gettime函数获取当前时间。通过帧的显示时间戳(pts)和当前时间的比较,计算出需要延迟的时间,使用SDL_Delay函数进行延迟,以确保视频帧按照正确的时间顺序显示。

4.3 图像格式转换与渲染

解码后的帧需要进行格式转换并渲染到窗口上:

SDL_Rect rect = { 0, 0, codec_ctx->width, codec_ctx->height };
sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);// 在解码帧的处理部分
SDL_UpdateYUVTexture(texture, &rect,frame->data[0], frame->linesize[0],frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);

首先,使用sws_getContext函数创建一个图像格式转换上下文sws_ctx,将解码后的帧格式转换为适合SDL渲染的格式(这里是AV_PIX_FMT_YUV420P)。然后,使用SDL_UpdateYUVTexture函数将转换后的帧数据更新到纹理中。接着,使用SDL_RenderClear函数清空渲染器,SDL_RenderCopy函数将纹理数据复制到渲染器上,最后使用SDL_RenderPresent函数将渲染器的内容显示在窗口上。

五、资源清理

在视频播放结束后,需要释放所有分配的资源,以避免内存泄漏:

cleanup:sws_freeContext(sws_ctx);SDL_DestroyTexture(texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);

这里依次释放了图像格式转换上下文、纹理、渲染器、窗口、SDL库资源、视频帧、解码器上下文以及视频文件格式上下文。

六、完整代码示例


#include <iostream>
#include <string>
#include <SDL2\SDL.h>
extern "C" {
#include <libavcodec\avcodec.h>
#include <libavformat\avformat.h>
#include <libavutil\avutil.h>
#include <libswscale\swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil\pixfmt.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
}
#include <chrono> // 用于时间同步
#include <thread>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);
}
#undef main
int main() {AVFormatContext* fmt_ctx = NULL;std::string file_path = "F:/QT/mp4_flv/x.mp4";// 打开视频文件int 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;}// 读取视频流信息ret = avformat_find_stream_info(fmt_ctx, NULL);if (ret < 0) {handle_ffmpeg_error(ret, "Error in obtaining video stream information.");avformat_close_input(&fmt_ctx);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* frame = av_frame_alloc();SwsContext* sws_ctx = NULL;if (SDL_Init(SDL_INIT_VIDEO) < 0) {fprintf(stderr, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError());av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}SDL_Window* window = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,codec_ctx->width, codec_ctx->height, SDL_WINDOW_SHOWN);if (!window) {fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError());SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);if (!renderer) {fprintf(stderr, "Renderer could not be created! SDL_Error: %s\n", SDL_GetError());SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,codec_ctx->width, codec_ctx->height);if (!texture) {fprintf(stderr, "Texture could not be created! SDL_Error: %s\n", SDL_GetError());SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}SDL_Rect rect = { 0, 0, codec_ctx->width, codec_ctx->height };sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);int64_t start_time = av_gettime();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, "send data decoder error.");av_packet_unref(&pkt);continue;}while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == 0) {int64_t pts = frame->pts;if (pts == AV_NOPTS_VALUE) {pts = av_rescale_q(pkt.dts, fmt_ctx->streams[video_stream_idx]->time_base, { 1, 1000000 });}else {pts = av_rescale_q(frame->pts, fmt_ctx->streams[video_stream_idx]->time_base, { 1, 1000000 });}int64_t now = av_gettime();if (pts > now - start_time) {SDL_Delay((pts - (now - start_time)) / 1000);}SDL_UpdateYUVTexture(texture, &rect,frame->data[0], frame->linesize[0],frame->data[1], frame->linesize[1],frame->data[2], frame->linesize[2]);SDL_RenderClear(renderer);SDL_RenderCopy(renderer, texture, NULL, NULL);SDL_RenderPresent(renderer);}else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;}}}av_packet_unref(&pkt);SDL_Event event;while (SDL_PollEvent(&event)) {if (event.type == SDL_QUIT) {goto cleanup;}}}cleanup:sws_freeContext(sws_ctx);SDL_DestroyTexture(texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return 0;
}

在这里插入图片描述

七、总结与展望

通过本文的详细介绍和代码示例,我们深入了解了视频播放器的开发过程。从视频播放的基本原理,到使用FFmpeg库进行视频文件的解封装、解码,再到利用SDL库进行窗口创建、图形渲染和事件处理,每一个环节都紧密相扣,共同构成了一个完整的视频播放系统。

然而,这只是一个简单的视频播放器示例,实际应用中的视频播放器还需要具备更多的功能和优化。例如,支持更多的视频和音频格式、实现音频的播放和同步、添加播放控制功能(如暂停、快进、快退等)、优化性能以适应不同的硬件环境等。

相关文章:

基于ffmpeg和sdl2的简单视频播放器制作

基于ffmpeg和sdl2的简单视频播放器制作 前言一、视频播放器开发的基础1.1 视频播放原理1.2 开发所需的库 二、FFmpeg库详解2.1 FFmpeg库的组成2.2 关键数据结构2.3 打开视频文件并获取流信息2.4 查找视频流和解码器2.5 初始化解码器 三、SDL库详解3.1 SDL库的功能3.2 初始化SDL…...

卫星导航信号的形成及解算

引言 卫星导航信号是现代导航技术的核心&#xff0c;它利用卫星发射的信号实现全球范围内的精确定位和导航。本文将详细介绍卫星导航信号的形成及解算过程。 一、卫星导航信号的形成 卫星导航信号的形成主要包括信号的生成、调制和传播三个步骤。 1. 信号的生成 卫星导航信号主…...

硬件-射频-PCB-常见天线分类-ESP32实例

文章目录 一&#xff1a;常见天线1.1 PCB天线①蓝牙模块的蛇形走线-天线②倒F天线-IFA&#xff1a;③蛇形倒F天线-MIFA④立体的倒F天线-PIFA 1.2 实例示意图1.21 对数周期天线(LPDA):1.22 2.4GHZ的八木天线&#xff1a;1.23 陶瓷天线&#xff1a;1.24 外接天线&#xff1a; 二&…...

salesforce 验证规则判断一个picklist是否为none

在 Salesforce 验证规则中&#xff0c;如果你想判断一个 Picklist 字段是否等于 None&#xff0c;可以使用 ISPICKVAL 函数。 以下是具体的公式&#xff1a; ISPICKVAL(Picklist_Field__c, "None")示例解释&#xff1a; Picklist_Field__c: 是你的自定义 Picklist…...

解决 IntelliJ IDEA 中 Tomcat 日志乱码问题的详细指南

目录 前言1. 分析问题原因2. 解决方案 2.1 修改 IntelliJ IDEA 的 JVM 选项2.2 配置 Tomcat 实例的 VM 选项 2.2.1 设置 Tomcat 的 VM 选项2.2.2 添加环境变量 3. 进一步优化 3.1 修改 Tomcat 的 logging.properties3.2 修改操作系统默认编码 3.2.1 Windows 系统3.2.2 Linux …...

如何分析 Nginx 日志

分析 Nginx 日志可以帮助我们了解服务器性能、流量来源、用户行为&#xff0c;以及诊断问题&#xff08;如错误和攻击&#xff09;。以下是详细的分析方法&#xff1a; 1. 日志类型 Nginx 有两种主要日志&#xff1a; 访问日志 (Access Log)&#xff1a;记录客户端对服务器的…...

Kubernetes Gateway API-5-后端协议和网关基础设置标签

1 后端协议 自 v1.2.0 开始支持 并非所有网关API实现都支持自动协议选择。在某些情况下&#xff0c;协议在没有明确选择加入的情况下被禁用。 当 Route 的后端引用Kubernetes Service 时&#xff0c;应用程序开发人员可以使用 ServicePort appProtocol 字段指定协议。 例如…...

大数据架构演变

一、离线数仓 缺点&#xff1a; ETL计算、存储、时间成本高数据处理链路过长无法支持实时、近实时的数据分析数据采集对业务库造成影响 二、Lambda架构&#xff0c;离线实时分开 缺点&#xff1a; 组件多&#xff0c;不方便管理很难保证数据一致数据探查困难&#xff0c;出现…...

Bash语言的软件工程

Bash语言的软件工程 1. 引言 Bash&#xff08;Bourne Again SHell&#xff09;是一个Unix Shell和命令语言解释器&#xff0c;最初由Brian Fox为GNU项目编写。Bash不仅是Linux和macOS等现代操作系统的标准Shell&#xff0c;同时也是很多开发者和系统管理员进行自动化任务、开…...

OpenGL —— 流媒体播放器 - ffmpeg解码rtsp流,opengl渲染yuv视频(附源码,glfw+glad)

效果 说明 FFMpeg和OpenGL作为两大技术巨头,分别在视频解码和图形渲染领域发挥着举足轻重的作用。本文将综合两者实战视频播放器,大概技术流程为:ffmpeg拉取rtsp协议视频流,并经过解码、尺寸格式转换为yuv420p后,使用opengl逐帧循环渲染该yuv实时视频。 核心源码 vertexSh…...

CE中注册的符号地址如何通过编程获取

我的方式是先执行lua申请共享内存&#xff0c;内存名称是进程id&#xff0c;这样多开也不受影响&#xff0c;然后通过共享内存的名字就可以读到地址了。之后的人造指针的地址也都可以放这里集中管理。 -- 申请内存 local size 1024 -- 申请 1024 字节&#xff08;1 KB&#…...

Math Reference Notes: 积分因子

在求解一阶线性微分方程时&#xff0c;积分因子&#xff08;Integrating Factor&#xff09;是一个非常重要的工具&#xff0c;它能够将复杂的微分方程转化为一个可以直接积分的形式。通过使用积分因子&#xff0c;我们可以简化微分方程的结构&#xff0c;使得求解过程更加直接…...

解决7-Zip图标更换问题

手动美化7-Zip图标&#xff0c;告别Win95风格 之前下载的7z压缩的文件图标都是软件的黑白图形&#xff0c;但是电脑重置了默认应用后再改回7z&#xff0c;压缩的文件就变成黄色的图标了&#xff0c;试过很多问题&#xff0c;尝试过手动更改图标&#xff0c;或者代码更改&#…...

Java 性能监控工具详解:JConsole、VisualVM 和 Java Mission Control

在 Java 应用程序的开发和维护过程中&#xff0c;性能监控和故障诊断是至关重要的。本文将详细介绍三款常用的 Java 性能监控工具&#xff1a;JConsole、VisualVM 和 Java Mission Control&#xff08;JMC&#xff09;&#xff0c;并探讨它们的功能和使用方法。 1 JConsole 1…...

浏览器报错:您的连接不是私密连接,Kubernetes Dashboard无法打开

问题描述 部署完成Kubernetes Dashboard后&#xff0c;打开HTTPS的web页面&#xff0c;Chrome和Edge浏览器都无法正常加载页面&#xff0c;会提示您的连接不是私密连接的报错。 ​​​​​​​​​​​​ 原因&#xff1a; 浏览器不信任这些自签名的ssl证书&#xff0c;为了…...

用Python进行大数据处理:如何使用pandas和dask处理海量数据

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着数据量的爆炸式增长,大数据处理成为现代数据科学和工程领域的核心挑战。Python作为数据分析的重要工具,其生态系统中的pandas和dask库…...

机器人手眼标定

机器人手眼标定 一、机器人手眼标定1. 眼在手上标定基本原理2. 眼在手外标定基本原理 二、眼在手外标定实验三、标定精度分析 一、机器人手眼标定 要实现由图像目标点到实际物体上抓取点之间的坐标转换&#xff0c;就必须拥有准确的相机内外参信息。其中内参是相机内部的基本参…...

基于Springboot + vue实现的校园失物招领系统

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…...

关于C语言初步的一些基础知识整理(2)

“本篇是对于C语言初步中一些基础知识的简单整理&#xff0c;内容较为琐碎&#xff0c;但实用” 在C语言中&#xff0c;格式控制符是用于指定数据输出格式的特殊字符&#xff0c;不同的数据类型有着其对应的格式控制符。具体的&#xff1a; %f 用于输出浮点型数据&#xff08…...

Linux驱动开发:深入理解I2C时序(二)

在Linux驱动开发中,I2C时序的理解和正确处理是保证I2C设备正常工作和通信的关键。I2C协议的时序特性决定了数据的有效传输和设备间的协作。因此,掌握I2C的时序细节,以及如何在Linux内核中进行时序处理,能够让开发者更好地处理设备通信问题。 本文将继续深入探讨I2C通信协议…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

【WebSocket】SpringBoot项目中使用WebSocket

1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖&#xff0c;添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...

Docker、Wsl 打包迁移环境

电脑需要开启wsl2 可以使用wsl -v 查看当前的版本 wsl -v WSL 版本&#xff1a; 2.2.4.0 内核版本&#xff1a; 5.15.153.1-2 WSLg 版本&#xff1a; 1.0.61 MSRDC 版本&#xff1a; 1.2.5326 Direct3D 版本&#xff1a; 1.611.1-81528511 DXCore 版本&#xff1a; 10.0.2609…...

Linux入门(十五)安装java安装tomcat安装dotnet安装mysql

安装java yum install java-17-openjdk-devel查找安装地址 update-alternatives --config java设置环境变量 vi /etc/profile #在文档后面追加 JAVA_HOME"通过查找安装地址命令显示的路径" #注意一定要加$PATH不然路径就只剩下新加的路径了&#xff0c;系统很多命…...