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())
    • 三、实战案例全流程解析
  • 总结


前言

  在当今数字化多媒体的时代,音频内容无处不在,无论是音乐播放、语音通话,还是影视制作中的音效处理,音频解码都起着至关重要的作用。它是将经过编码压缩的音频数据还原为可播放的原始音频信号的关键环节。今天,就让我们一同深入了解音频解码的核心世界,从基础概念到实际代码,一探究竟。


一、音频编码与解码基础

(一)音频编码简述

  音频编码的出现主要是为了在有限的带宽和存储条件下,更高效地传输和保存音频信息。常见的音频编码格式有 MP3、AAC、WAV 等,它们各自运用不同的算法和技术来对原始音频数据进行压缩处理。例如,MP3 编码通过去除人耳不易察觉的音频频段以及利用心理声学模型等手段,大幅减小了音频文件的数据量,使其更便于存储和网络传输。

  这些编码格式在不同的应用场景中各有优势,像 MP3 在音乐播放领域应用广泛,AAC 则在很多移动端设备和在线流媒体平台上备受青睐,而 WAV 因其无损的特性常被用于对音频质量要求较高的专业音频制作环境中。

(二)音频解码本质

  音频解码是音频编码的逆向过程,就像是打开一个经过精心包装的“音频礼盒”。编码后的音频数据是经过一系列复杂算法压缩后的结果,音频解码则要依据相应的编码标准和算法规则,将这些压缩的数据还原为原始的音频样本序列,通常以 PCM(脉冲编码调制)格式呈现,PCM 数据包含了音频的采样值、声道信息等关键要素,能够直接被音频播放设备所识别和播放,从而让我们听到清晰、流畅的声音。

  比如对于 AAC 编码的音频,解码时需要按照其特定的码流结构,解析出各个音频帧的头部信息、量化参数等,再通过逆量化、逆变换等操作逐步恢复出原始的音频样本,涉及到哈夫曼解码、频谱重构等关键步骤,以此确保音频能准确还原出原本的音色、音调以及响度等特征。

二、音频解码关键 API 深度剖析

(一)avformat_open_input()

  在音频解码的起始阶段,avformat_open_input() 这个 API 发挥着关键作用。它同样接受一个 AVFormatContext 结构体指针的地址作为参数,负责打开指定路径的音频文件,并深入分析文件头信息,以此来准确判断音频流的封装格式,像常见的 MP3 文件对应的 MPEG 封装格式、AAC 音频常用的 ADTS 封装格式等。

  成功调用后,AVFormatContext 结构体就像是一本内容详尽的“音频档案”,里面装满了音频文件的基础元数据,涵盖文件时长、码率、音频流的声道数量、采样率等重要信息,为后续的解码流程提供了必不可少的基础资料。

示例代码如下:

AVFormatContext *fmt_ctx = NULL;
int ret = avformat_open_input(&fmt_ctx, "input_audio.mp3", NULL, 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);
    return -1;
}

  这段代码尝试打开名为 "input_audio.mp3" 的文件,若遇到问题,借助 av_strerror 获取详细错误信息并输出,随后终止程序,保证了严谨的错误处理逻辑。

(二)avformat_find_stream_info()

  这个 API 就如同一位经验丰富的“音频侦探”,对已经打开的音频文件进行全面细致的扫描与剖析。它会遍历音频文件的各个部分,不仅进一步完善 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_MP3AV_CODEC_ID_AAC 等),在 FFmpeg 庞大的解码器库中迅速定位与之匹配的解码器。一旦找到,就会返回 AVCodec 结构体指针,这个指针如同解码器的操作说明书,掌控着解码流程的核心算法以及关键参数设置,是后续构建解码环境的核心依据。

示例代码:

AVCodec *codec = NULL;
int audio_stream_index = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
    if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        audio_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[audio_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 == audio_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 完整解码本地音频文件并将解码后 PCM 格式音频样本数据存储至 output.pcm 文件的示例代码,全程穿插了严谨的错误处理机制,确保程序能够稳健运行:

#include <iostream>
#include <string>
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>
}

int main() {
    // 打开音视频文件
    std::string file_path = "F:/QT/mp4_flv/x.mp4";
    AVFormatContext* fmt_ctx = nullptr;
    if (avformat_open_input(&fmt_ctx, file_path.c_str(), nullptr, nullptr) < 0) {
        std::cerr << "无法打开文件: " << file_path << std::endl;
        return -1;
    }

    // 读取流信息
    if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
        std::cerr << "无法获取流信息" << std::endl;
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 打印文件信息
    av_dump_format(fmt_ctx, 0, file_path.c_str(), 0);

    // 找到音频流
    int audio_stream_index = -1;
    for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_index = i;
            break;
        }
    }
    if (audio_stream_index == -1) {
        std::cerr << "未找到音频流" << std::endl;
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 获取音频流的解码器
    AVCodecParameters* codecpar = fmt_ctx->streams[audio_stream_index]->codecpar;
    const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);
    if (!codec) {
        std::cerr << "未找到解码器" << std::endl;
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 创建解码上下文
    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        std::cerr << "无法分配解码器上下文" << std::endl;
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 将流参数复制到解码器上下文中
    if (avcodec_parameters_to_context(codec_ctx, codecpar) < 0) {
        std::cerr << "无法将流参数复制到解码器上下文" << std::endl;
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 打开解码器
    if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
        std::cerr << "无法打开解码器" << std::endl;
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 准备解码
    AVPacket* packet = av_packet_alloc();
    AVFrame* frame = av_frame_alloc();
    if (!packet || !frame) {
        std::cerr << "无法分配帧或数据包" << std::endl;
        av_packet_free(&packet);
        av_frame_free(&frame);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 打开输出 PCM 文件
    FILE* output_file = nullptr;
    if (fopen_s(&output_file, "output.pcm", "wb") != 0) {
        std::cerr << "无法创建输出文件" << std::endl;
        av_packet_free(&packet);
        av_frame_free(&frame);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 初始化重采样上下文
    SwrContext* swr_ctx = swr_alloc();
    if (!swr_ctx) {
        std::cerr << "无法分配重采样上下文" << std::endl;
        fclose(output_file);
        av_packet_free(&packet);
        av_frame_free(&frame);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 设置重采样参数
    av_opt_set_int(swr_ctx, "in_channel_layout", codec_ctx->ch_layout.nb_channels, 0);
    av_opt_set_int(swr_ctx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
    av_opt_set_int(swr_ctx, "in_sample_rate", codec_ctx->sample_rate, 0);
    av_opt_set_int(swr_ctx, "out_sample_rate", 44100, 0);
    av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", codec_ctx->sample_fmt, 0);
    av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);

    if (swr_init(swr_ctx) < 0) {
        std::cerr << "无法初始化重采样上下文" << std::endl;
        swr_free(&swr_ctx);
        fclose(output_file);
        av_packet_free(&packet);
        av_frame_free(&frame);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 解码循环
    while (av_read_frame(fmt_ctx, packet) >= 0) {
        if (packet->stream_index == audio_stream_index) {
            if (avcodec_send_packet(codec_ctx, packet) < 0) {
                std::cerr << "发送数据包到解码器失败" << std::endl;
                continue;
            }

            while (avcodec_receive_frame(codec_ctx, frame) >= 0) {
                uint8_t** out_buffer = nullptr;
                int out_line_size;
                int out_samples = av_samples_alloc_array_and_samples(
                    &out_buffer,
                    &out_line_size,
                    2, // 输出声道数
                    frame->nb_samples,
                    AV_SAMPLE_FMT_S16,
                    0
                );

                if (out_samples < 0) {
                    std::cerr << "分配输出缓冲区失败" << std::endl;
                    break;
                }

                int converted_samples = swr_convert(
                    swr_ctx,
                    out_buffer,
                    frame->nb_samples,
                    (const uint8_t**)frame->data,
                    frame->nb_samples
                );

                if (converted_samples > 0) {
                    fwrite(out_buffer[0], 1, converted_samples * 2 * sizeof(int16_t), output_file);
                }

                av_freep(&out_buffer[0]);
                av_freep(&out_buffer);
            }
        }
        av_packet_unref(packet);
    }

    // 清理资源
    swr_free(&swr_ctx);
    fclose(output_file);
    av_packet_free(&packet);
    av_frame_free(&frame);
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&fmt_ctx);

    std::cout << "音频解码完成,输出文件为 output.pcm" << std::endl;
    return 0;
}

总结

  FFmpeg 音频解码作为多媒体处理中的关键技术,通过本文的详细解读,能帮助读者更好地驾驭这一工具,在音频处理的世界里创造更多可能。

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

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

相关文章

排序算法 (插入,选择,冒泡,希尔,快速,归并,堆排序)

排序:经常在算法题中作为一个前置操作,为了之后的贪心or else做个铺垫,虽然我们经常都只是调用个sort,但是了解一些排序算法可以扩充下知识库 排序的分类: 从存储设备角度&#xff1a; ✓ 内排序&#xff1a;在排序过程中所有数据元素都在内存中&#xff1b; ✓ 外排序&a…

云途领航:现代应用架构助力企业转型新篇

在数字化转型的浪潮中&#xff0c;现代应用架构为企业带来了灵活性、效率和创新能力。各类服务模型相继出现&#xff0c;为企业提供了强有力的支持&#xff0c;助力其顺利转型。随着技术的快速发展&#xff0c;企业面临的挑战和机遇也在不断演变&#xff0c;这促使它们必须重新…

【IMU:视觉惯性SLAM系统】

视觉惯性SLAM系统简介 相机&#xff08;单目/双目/RGBD)与IMU结合起来就是视觉惯性&#xff0c;通常以单目/双目IMU为主。 IMU里面有个小芯片可以测量角速度与加速度&#xff0c;可分为6轴(6个自由度)和9轴&#xff08;9个自由度&#xff09;IMU&#xff0c;具体的关于IMU的介…

面试题整理3----nc命令的常见用法

面试题整理3----nc命令的常见用法 1. NC是什么2. NC的常用参数2.1 开启指定端口TCP监听(-l小写的L)2.2 测试端口是否能访问(-v)2.3 开启指定端口UDP监听(-u)2.4 端口扫描(-z)2.5 指定超时时间(-w)2.6 指定本地端口号连接(-p)2.7 指定的命令(-e) 1. NC是什么 nc&#xff08;Net…

ubuntu 如何重装你的apt【apt-get报错: symbol lookup error/undefined symbol】

副标题:解决error:apt-get: symbol lookup error: /lib/x86_64-linux-gnu/libapt-private.so.0.0: undefined symbol: _ZNK13pkgTagSection7FindULLENS_3KeyERKy, version APTPKG_6.0 文章目录 问题描述报错分析解决方案:重装你的apt1、查看你的ubuntu版本2、下载适配你的ap…

解决:excel鼠标滚动幅度太大如何调节?

在excel里为什么滚动一次跳过很多行呢&#xff1f;很不方便。。。 1. 问题&#xff1a; 一开始单元格从第1行开始&#xff1a; 鼠标轻轻滚动一下后&#xff0c;直接跳到第4行&#xff1a; 鼠标在word和浏览器里都是好好的。在excel里为什么不是滚动一次跳过一行呢&#xff…

VMWare 的克隆操作

零、碎碎念 VMWare 的这个克隆操作很简单&#xff0c;单拎出来成贴的目的是方便后续使用。 一、操作步骤 1.1、在“源”服务器上点右键&#xff0c;选择“管理--克隆” 1.2、选择“虚拟机的当前状态”为基础制作克隆&#xff0c;如下图所示&#xff0c;然后点击“下一页” 1.3、…

ARM 处理器平台 Ethernet Compliance 测试流程示例

By Toradex秦海 1). 简介 为了保证基于IEEE 802.3 协议设计的以太网设备接口可以互相兼容互联互通&#xff0c;需要进行 Ethernet Compliance 一致性测试&#xff0c;相关的技术原理说明请参考如下文章&#xff0c;本文就不赘述&#xff0c;主要展示基于 NXP i.MX8M Mini ARM…

门控循环单元(GRU):深度学习中的序列数据处理利器

目录 ​编辑 引言 GRU的诞生背景 GRU的核心机制 GRU的计算过程 GRU的数学公式 GRU的应用领域 代码示例&#xff1a;PyTorch中的GRU GRU与LSTM的比较 参数比较 GRU的技术发展 BiGRU&#xff08;双向GRU&#xff09; BiGRU的实现示例 GRU与CNN的结合 GRU的应用案例…

C#都可以找哪些工作?

在国内学习C#&#xff0c;可以找的工作主要是以下4个&#xff1a; 1、游戏开发 需要学习C#编程、Unity引擎操作、游戏设计和3D图形处理等。 2、PC桌面应用开发 需要学习C#编程、WinForm框架/WPF框架、MVVM设计模式和UI/UX设计等。 3、Web开发 需要学习C#编程、ASP.NET框架…

视频直播点播平台EasyDSS与无人机技术的森林防火融合应用

随着科技的飞速发展&#xff0c;无人机技术以其独特的优势在各个领域得到了广泛应用&#xff0c;特别是在森林防火这一关键领域&#xff0c;EasyDSS视频平台与无人机技术的融合应用更是为传统森林防火手段带来很大的变化。 一、无人机技术在森林防火中的优势 ‌1、快速响应与高…

【编译原理】编译原理知识点汇总·词法分析器(正则式到NFA、NFA到DFA、DFA最小化)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;编译原理_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …

SAP抓取外部https报错SSL handshake处理方法

一、问题描述 SAP执行报表抓取https第三方数据,数据获取失败。 报错消息: SSL handshake with XXX.COM:449 failed: SSSLERR_SSL_READ (-58)#SAPCRYPTO:SSL_read() failed##SapSSLSessionStartNB()==SSSLERR_SSL_READ# SSL:SSL_read() failed (536875120/0x20001070)# …

java栈

前言 java实现数据结构栈&#xff1a;用顺序表存储的栈和数组存储的栈。 本文源代码网址&#xff1a;https://gitee.com/zfranklin/java/tree/master/dataStructure/src/com/njupt/stack https://gitee.com/zfranklin/java/tree/master/dataStructure/src/com/njupt/stack 栈…

「Mac畅玩鸿蒙与硬件45」UI互动应用篇22 - 评分统计工具

本篇将带你实现一个评分统计工具&#xff0c;用户可以对多个选项进行评分。应用会实时更新每个选项的评分结果&#xff0c;并统计平均分。这一功能适合用于问卷调查或评分统计的场景。 关键词 UI互动应用评分统计状态管理数据处理多目标评分 一、功能说明 评分统计工具允许用…

使用 AI 辅助开发一个开源 IP 信息查询工具:一

本文将分享如何借助当下流行的 AI 工具,一步步完成一个开源项目的开发。 写在前面 在写代码时&#xff0c;总是会遇到一些有趣的机缘巧合。前几天&#xff0c;我在翻看自己之前的开源项目时&#xff0c;又看到了 DDNS 相关的讨论。虽然在 2021 年我写过两篇相对详细的教程&am…

高效处理PDF文件的终极工具:构建一个多功能PDF转换器

在日常工作中&#xff0c;处理PDF文件几乎是每个人都不可避免的任务。无论是从PDF中提取数据、合并多个PDF文件&#xff0c;还是处理文件中的敏感信息和图像&#xff0c;PDF文件的处理都可能成为繁琐且耗时的工作。如果你是数据分析师、工程师&#xff0c;或者从事文档管理的工…

差分矩阵(Difference Matrix)与累计和矩阵(Running Sum Matrix)的概念与应用:中英双语

本文是学习这本书的笔记: https://web.stanford.edu/~boyd/vmls/ 差分矩阵&#xff08;Difference Matrix&#xff09;与累计和矩阵&#xff08;Running Sum Matrix&#xff09;的概念与应用 在线性代数和信号处理等领域中&#xff0c;矩阵运算常被用来表示和计算各种数据变换…

【java面向对象编程】第七弹----Object类、类变量与类方法

笔上得来终觉浅,绝知此事要躬行 &#x1f525; 个人主页&#xff1a;星云爱编程 &#x1f525; 所属专栏&#xff1a;javase &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 一、Object类 1.1equa…

zookeeper分布式锁模拟12306买票

未加锁时容易出现重复买票情况 代码 public class Ticket12306 implements Runnable{// 票数private int ticketNums 10;Overridepublic void run() {while (true){if (ticketNums>0){System.out.println(Thread.currentThread() "抢到了第" ticketNums &qu…