音视频开发—使用FFmpeg将YUV文件编码成H264裸流文件 C语言实现

news2024/11/23 6:56:04

文章目录

    • 1.准备工作
    • 2.压缩编码工作流程
    • 3.详细步骤
      • 1. 初始化日志和参数检查
      • 2. 输入/输出文件的打开
      • 3. 查找和初始化编码器
      • 4. 打开编码器
      • 5. 帧内存的分配和初始化
      • 6. 设置转换上下文(SWS)
      • 7. 读取和转换数据
      • 8. 编码过程
      • 9. 资源清理
    • 4.完整示例代码

1.准备工作

原始YUV文件,只包含图像的原始信息,无论是播放还是进行H264压缩编码,都需要知晓文件格式。

  • 像素格式:常见的格式有YUV420P、YUV422P、YUV420打包格式:NV12 NV21 等等

  • 分辨率:宽度和高度

FFmpeg播放YUV文件示例:

ffplay -f rawvideo -pixel_format yuv422p -video_size 1920x1080 input_1920x1080_yuv422p_1.yuv 

命令解析:

  • ffplay: 这是用于播放视频和音频文件的命令行媒体播放器。
  • -f rawvideo: 这个选项指定输入文件的格式。rawvideo 表示输入文件是一个未经压缩处理的视频数据流。
  • -pixel_format yuv422p: 指定了像素格式。yuv422p 是一种 YUV 格式,其中 Y、U 和 V 分量是平面分隔的(p 表示平面),并且色度(U 和 V)采样是每两个像素共享一次(即 4:2:2 采样)。这个格式常见于专业视频编辑和后期处理中。
  • -video_size 1920x1080: 设置视频的分辨率为 1920x1080 像素,这通常是全高清视频的标准分辨率。
  • input_1920x1080_yuv422p_1.yuv: 这是输入文件的路径和名称。文件扩展名 .yuv 通常用于存储原始的 YUV 格式视频数据。

效果如下:这里以一张高清图片为示例

在这里插入图片描述

2.压缩编码工作流程

总体流程:在这里插入图片描述

需要注意的是:编码H264一般使用X264的居多,要求输入的格式一般为YUV420格式,因此如果是YUV422需要转换为YUV420格式

3.详细步骤

1. 初始化日志和参数检查

 av_log_set_level(AV_LOG_DEBUG);
    av_log(NULL, AV_LOG_INFO, "FFMPEG DEBUG LOG BEGIN.....\n");

    if (argc < 5)
    {
        printf("Usage: %s <input file> <width> <height> <output file>\n", argv[0]);
        return -1;
    }

  • 使用 av_log_set_level 设置日志级别,用于调试信息输出。
  • 检查传入的命令行参数数量,确保提供了足够的信息来进行后续处理。

2. 输入/输出文件的打开

 // 读取YUV 文件
    input_file = fopen(input_filename, "rb");
    if (!input_file)
    {

        fprintf(stderr, "Could not open input file '%s'\n", input_filename);
        CLEANUP(failure);
        return -1;
    }
    // 打开要写入的文件
    output_file = fopen(output_filename, "wb");
    if (!output_file)
    {
        fprintf(stderr, "Could not open output file '%s'\n", output_filename);
        CLEANUP(failure);
        return -1;
    }


  • 尝试打开输入文件(YUV数据源)和输出文件(存储编码后的视频)。如果文件打开失败,跳转到 failure 标签进行资源释放和退出。

3. 查找和初始化编码器

  • 查找H264编码器。如果找不到编码器,跳转到 failure
    // 查找编码器
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec)
    {

        av_log(NULL, AV_LOG_ERROR, "H264 Codec not found.....\n");
        CLEANUP(failure);
        return -1;
    }

    // 分配编码器上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.....\n");
        CLEANUP(failure);
        return -1;
    }
  • 为编码器分配上下文,并设置编码参数,如比特率、分辨率、帧率等。这些参数对最终视频的质量和文件大小有直接影响。
// 设置编码器的参数
    codec_ctx->bit_rate = 4000000;  //与图像质量直接挂钩
    codec_ctx->height = height;
    codec_ctx->width = width;
    codec_ctx->time_base = (AVRational){1, 25};
    codec_ctx->framerate = (AVRational){25, 1};
    codec_ctx->gop_size = 10;
    codec_ctx->max_b_frames = 0;             // 设置成0,可以减少编码器负担
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 使用YUV420P进行编码

4. 打开编码器

  • 尝试打开已配置的编码器。如果编码器无法打开,跳转到 failure
    // 打开编码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not open codec.....\n");
        CLEANUP(failure);
        return -1;
    }

5. 帧内存的分配和初始化

  • 分配内存给原始帧 frame 和用于转换的帧 sws_frame。如果分配失败,进行资源清理并跳转到 failure
  • 对这两个帧进行格式设置和内存分配。
  // 分配原始帧和转换帧
    frame = av_frame_alloc();
    sws_frame = av_frame_alloc();
    if (!frame || !sws_frame)
    {
        fprintf(stderr, "Could not allocate video frame\n");
        fclose(input_file);
        fclose(output_file);
        avcodec_free_context(&codec_ctx);
        if (frame)
            av_frame_free(&frame);
        if (sws_frame)
            av_frame_free(&sws_frame);
        return -1;
    }

    frame->format = codec_ctx->pix_fmt;
    frame->width = width;
    frame->height = height;

    ret = av_image_alloc(frame->data, frame->linesize, width, height, codec_ctx->pix_fmt, 32);
    if (ret < 0)
    {
        CLEANUP(failure);
        return -1;
    }

    sws_frame->format = AV_PIX_FMT_YUV422P;
    sws_frame->width = width;
    sws_frame->height = height;

    ret = av_image_alloc(sws_frame->data, sws_frame->linesize, width, height, AV_PIX_FMT_YUV422P, 32);
    if (ret < 0)
    {
        fprintf(stderr, "Could not allocate raw picture buffer for SWS frame\n");
        CLEANUP(failure);
        return -1;
    }

记得也要packet进行初始化,调了半个小时才发现,忘给AVPacket初始化了,一直报内存错误。。。。。。

    // 分配数据包
    av_init_packet(&packet); // 初始化packet
    packet.data = NULL;
    packet.size = 0;

6. 设置转换上下文(SWS)

  • 初始化用于像素格式转换的 SwsContext。这个上下文负责将YUV422P格式转换成编码器需要的YUV420P格式。
    // 创建SWS上下文
    sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV422P, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
    if (!sws_ctx)
    {
        fprintf(stderr, "Could not initialize the conversion context\n");
        fclose(input_file);
        fclose(output_file);
        if (frame)
            av_freep(&frame->data[0]);
        if (sws_frame)
            av_freep(&sws_frame->data[0]);
        av_frame_free(&frame);
        av_frame_free(&sws_frame);
        avcodec_free_context(&codec_ctx);
        return -1;
    }

7. 读取和转换数据

  • 从输入文件中读取YUV422P数据到 sws_frame,然后使用 sws_scale 函数进行格式转换,并存储到 frame
  • 如果读取失败,输出错误信息并跳转到 failure
fread(sws_frame->data[0], 1, width * height * 2, input_file)
sws_scale(sws_ctx, (const uint8_t *const *)sws_frame->data, sws_frame->linesize, 0, height, frame->data, frame->linesize);

8. 编码过程

  • 将转换后的帧发送到编码器。
   // 数据送入编码器
        ret = avcodec_send_frame(codec_ctx, frame);
        if (ret < 0)
        {
            fprintf(stderr, "Error sending frame for encoding\n");
            CLEANUP(failure);
        }
  • 循环调用 avcodec_receive_packet 来接收编码后的数据,并将其写入输出文件。
  • 在循环中,使用 av_packet_unref 来释放已经写入文件的数据包内存。
// 接收编码后的数据包
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(codec_ctx, &packet);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                break;
            else if (ret < 0)
            {
                fprintf(stderr, "Error during encoding\n");
                CLEANUP(failure);
                break;
            }
            // 写入到文件
            fwrite(packet.data, 1, packet.size, output_file);
            av_packet_unref(&packet);
        }
  • 最后,发送一个空帧到编码器以刷新所有待处理的帧。
    // 刷新编码器
    ret = avcodec_send_frame(codec_ctx, NULL);
    if (ret < 0)
    {
        fprintf(stderr, "Error sending flush packet to encoder\n");
        CLEANUP(failure);
    }

    while (ret >= 0)
    {
        ret = avcodec_receive_packet(codec_ctx, &packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0)
        {
            fprintf(stderr, "Error during encoding\n");
            CLEANUP(failure);
            break;
        }

        fwrite(packet.data, 1, packet.size, output_file);
        av_packet_unref(&packet);
    }

9. 资源清理

  • 在正常执行结束和错误处理 (failure 标签) 中,关闭文件、释放分配的内存和其他资源。
  fclose(input_file);
    fclose(output_file);
     if (frame)
        av_freep(&frame->data[0]);
    if (sws_frame)
        av_freep(&sws_frame->data[0]);
    av_frame_free(&frame);
    av_frame_free(&sws_frame);
    avcodec_free_context(&codec_ctx);

4.完整示例代码

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/log.h>
// 错误处理和资源释放
#define CLEANUP(label) \
    do                 \
    {                  \
        goto label;    \
    } while (0)

int main(int argc, char *argv[])
{
    av_log_set_level(AV_LOG_DEBUG);
    av_log(NULL, AV_LOG_INFO, "FFMPEG DEBUG LOG BEGIN.....\n");

    if (argc < 5)
    {
        printf("Usage: %s <input file> <width> <height> <output file>\n", argv[0]);
        return -1;
    }

    // 程序输入
    const char *input_filename = argv[1];
    int width = atoi(argv[2]);
    int height = atoi(argv[3]);
    const char *output_filename = argv[4];

    // 相关变量初始化
    AVCodecContext *codec_ctx = NULL;
    AVCodec *codec = NULL;
    AVFrame *frame = NULL;
    AVFrame *sws_frame = NULL;
    AVPacket packet;
    struct SwsContext *sws_ctx = NULL;
    FILE *input_file = NULL;
    FILE *output_file = NULL;
    int ret;

    // 读取YUV 文件
    input_file = fopen(input_filename, "rb");
    if (!input_file)
    {

        fprintf(stderr, "Could not open input file '%s'\n", input_filename);
        CLEANUP(failure);
        return -1;
    }
    // 打开要写入的文件
    output_file = fopen(output_filename, "wb");
    if (!output_file)
    {
        fprintf(stderr, "Could not open output file '%s'\n", output_filename);
        CLEANUP(failure);
        return -1;
    }

    // 查找编码器
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec)
    {

        av_log(NULL, AV_LOG_ERROR, "H264 Codec not found.....\n");
        CLEANUP(failure);
        return -1;
    }

    // 分配编码器上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.....\n");
        CLEANUP(failure);
        return -1;
    }

    // 设置编码器的参数
    codec_ctx->bit_rate = 4000000;  //与图像质量
    codec_ctx->height = height;
    codec_ctx->width = width;
    codec_ctx->time_base = (AVRational){1, 25};
    codec_ctx->framerate = (AVRational){25, 1};
    codec_ctx->gop_size = 10;
    codec_ctx->max_b_frames = 0;             // 设置成0,可以减少编码器负担
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 使用YUV420P进行编码

    // 打开编码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not open codec.....\n");
        CLEANUP(failure);
        return -1;
    }

    // 分配原始帧和转换帧
    frame = av_frame_alloc();
    sws_frame = av_frame_alloc();
    if (!frame || !sws_frame)
    {
        fprintf(stderr, "Could not allocate video frame\n");
        fclose(input_file);
        fclose(output_file);
        avcodec_free_context(&codec_ctx);
        if (frame)
            av_frame_free(&frame);
        if (sws_frame)
            av_frame_free(&sws_frame);
        return -1;
    }

    frame->format = codec_ctx->pix_fmt;
    frame->width = width;
    frame->height = height;

    ret = av_image_alloc(frame->data, frame->linesize, width, height, codec_ctx->pix_fmt, 32);
    if (ret < 0)
    {
        CLEANUP(failure);
        return -1;
    }

    sws_frame->format = AV_PIX_FMT_YUV422P;
    sws_frame->width = width;
    sws_frame->height = height;

    ret = av_image_alloc(sws_frame->data, sws_frame->linesize, width, height, AV_PIX_FMT_YUV422P, 32);
    if (ret < 0)
    {
        fprintf(stderr, "Could not allocate raw picture buffer for SWS frame\n");
        CLEANUP(failure);
        return -1;
    }

    // 创建SWS上下文
    sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV422P, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
    if (!sws_ctx)
    {
        fprintf(stderr, "Could not initialize the conversion context\n");
        fclose(input_file);
        fclose(output_file);
        if (frame)
            av_freep(&frame->data[0]);
        if (sws_frame)
            av_freep(&sws_frame->data[0]);
        av_frame_free(&frame);
        av_frame_free(&sws_frame);
        avcodec_free_context(&codec_ctx);
        return -1;
    }

    // 分配数据包
    av_init_packet(&packet); // 初始化packet
    packet.data = NULL;
    packet.size = 0;

    // 读取YUV422P数据到sws_frame中
    if (fread(sws_frame->data[0], 1, width * height * 2, input_file) == width * height * 2)
    {
        sws_scale(sws_ctx, (const uint8_t *const *)sws_frame->data, sws_frame->linesize, 0, height, frame->data, frame->linesize);

        frame->pts = 0; // 设置时间戳

        // 数据送入编码器
        ret = avcodec_send_frame(codec_ctx, frame);
        if (ret < 0)
        {
            fprintf(stderr, "Error sending frame for encoding\n");
            CLEANUP(failure);
        }

        // 接收编码后的数据包
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(codec_ctx, &packet);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                break;
            else if (ret < 0)
            {
                fprintf(stderr, "Error during encoding\n");
                CLEANUP(failure);
                break;
            }
            // 写入到文件
            fwrite(packet.data, 1, packet.size, output_file);
            av_packet_unref(&packet);
        }
    }
    else
    {
        fprintf(stderr, "Error reading input file\n");
    }

    // 刷新编码器
    ret = avcodec_send_frame(codec_ctx, NULL);
    if (ret < 0)
    {
        fprintf(stderr, "Error sending flush packet to encoder\n");
        CLEANUP(failure);
    }

    while (ret >= 0)
    {
        ret = avcodec_receive_packet(codec_ctx, &packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0)
        {
            fprintf(stderr, "Error during encoding\n");
            CLEANUP(failure);
            break;
        }

        fwrite(packet.data, 1, packet.size, output_file);
        av_packet_unref(&packet);
    }

    // 释放资源
    fclose(input_file);
    fclose(output_file);
     if (frame)
        av_freep(&frame->data[0]);
    if (sws_frame)
        av_freep(&sws_frame->data[0]);
    av_frame_free(&frame);
    av_frame_free(&sws_frame);
    avcodec_free_context(&codec_ctx);
    return 0;

failure:
    if (input_file)
        fclose(input_file);
    if (output_file)
        fclose(output_file);
    if (frame)
        av_freep(&frame->data[0]);
    if (sws_frame)
        av_freep(&sws_frame->data[0]);
    av_frame_free(&frame);
    av_frame_free(&sws_frame);
    avcodec_free_context(&codec_ctx);
    return -1;
}

该程序输出结果为.h264的码流文件,这里并没有添加文件容器(MP4/MKV/MOV)

可以直接调用ffplay进行播放,无需指定任何参数,因为已经封装到了NAL单元中,查看是否压编码成功。

可以看出有了显著的压缩效果,如果就降低码率,还可以进行缩小文件大小

在这里插入图片描述

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

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

相关文章

AI大模型探索之旅:深潜大语言模型的训练秘境

在人工智能的浩瀚星空中&#xff0c;大语言模型无疑是最耀眼的星辰之一&#xff0c;它们以无与伦比的语言理解与生成能力&#xff0c;引领着智能交互的新纪元。本文将带您踏上一场探索之旅&#xff0c;深入大语言模型的训练秘境&#xff0c;揭开其背后复杂而精妙的全景画卷。 …

Qt Quick qml自定义控件:qml实现电池控件

qml入门进阶专栏地址:https://blog.csdn.net/yao_hou/category_9951228.html?spm=1001.2014.3001.5482 本篇博客介绍如何使用qml来实现电池控件,效果图如下: 下面给出实现代码 Battery.qml /*电池组件*/import QtQuick 2.15 import QtQuick.Controls 2.15Rectangle {id: b…

Maven学习笔记——如何在pom.xml中通过坐标为项目导入jar包

注意&#xff1a;我们只导入了一个jar包坐标&#xff0c;但右边项目中确多出来了好几个jar包&#xff0c;这是因为我们导入的该jar包所依赖其他jar包&#xff0c;maven自动帮我们导入了进来

Android-- 集成谷歌地图

引言 项目需求需要在谷歌地图&#xff1a; 地图展示&#xff0c;设备点聚合&#xff0c;设备站点&#xff0c;绘制点和区域等功能。 我只针对我涉及到的技术做一下总结&#xff0c;希望能帮到开始接触谷歌地图的伙伴们。 集成步骤 1、在项目的modle的build.gradle中添加依赖如…

Java软件设计模式-单例设计模式

目录 1.软件设计模式的概念 2.设计模式分类 2.1 创建型模式 2.2 结构型模式 2.3 行为型模式 3.单例设计模式 3.1 单例模式的结构 3.2 单例模式的实现 3.2.1 饿汉式-方式1&#xff08;静态变量方式&#xff09; 3.2.2 懒汉式-方式1&#xff08;线程不安全&#xff09; 3.…

【linux】安装cuda11.0、cuDNN教程,简单易懂,包教包会

【linux】安装cuda11.0、cuDNN教程&#xff0c;简单易懂&#xff0c;包教包会 【创作不易&#xff0c;求点赞关注收藏】 文章目录 【linux】安装cuda11.0、cuDNN教程&#xff0c;简单易懂&#xff0c;包教包会一、版本情况介绍二、安装cuda1、到官网找到对应版本进行安装2、对…

【openwrt】Openwrt系统新增普通用户指南

文章目录 1 如何新增普通用户2 如何以普通用户权限运行服务3 普通用户如何访问root账户的ubus服务4 其他权限控制5 参考 Openwrt系统在默认情况下只提供一个 root账户&#xff0c;所有的服务都是以 root权限运行的&#xff0c;包括 WebUI也是通过root账户访问的&#xff0c;…

使用EndNote添加参考文献,如何区分中英文文献的et al和等?

一、背景 我们在用EndNote添加参考文献时&#xff0c;如遇到超过3个作者&#xff0c;需列出前三位作者&#xff0c;其余用“et al”代替。 但中文文献用“et al”显示不合适&#xff0c;如下图所示&#xff0c;需要用“等”代替。 二、中文参考文献大于3个作者&#xff0c;用等…

5G数字化转型redcap助您“轻”装上阵

RedCap&#xff08;Reduced Capability&#xff09;技术&#xff0c;也称为NR-Light&#xff0c;是针对5G网络的一种轻量化技术规范&#xff0c;旨在为具有较低性能要求的设备提供5G连接。 RedCap技术特点 低成本 降低芯片组和设备成本&#xff1a;RedCap通过减少终端带宽、收…

【Playwright+Python】系列 Pytest 插件在Playwright中的使用

一、命令行使用详解 使用 Pytest 插件在Playwright 中来编写端到端的测试。 1、命令行执行测试 pytest --browser webkit --headed 2、使用 pytest.ini 文件配置 内容如下&#xff1a; [pytest] # Run firefox with UIaddopts --headed --browser firefox效果&#xff1…

STM32入门开发操作记录(三)——按键控制LED

目录 一、模块化二、LED交替闪烁1. LED.c2. LED.h3. 主函数 三、按键控制LED1. Key.c2. Key.h3. LED.c4. LED.h5. 主函数 一、模块化 前篇介绍了如何向项目添加模块&#xff0c;本篇将进一步介绍模块的编写与封装。随着模块的增加&#xff0c;需要用到Manage Project Items&…

多终端文件互传

LocalSend - 下载下载LocalSend适用于Windows、macOS、Linux、Android和iOS。https://localsend.org/zh-CN/download

C语言 ——— 输入两个正整数,求出最小公倍数

目录 何为最小公倍数 题目要求 代码实现 方法一&#xff1a;暴力求解法&#xff08;不推荐&#xff09; 方法二&#xff1a;递乘试摸法&#xff08;推荐&#xff09; 何为最小公倍数 最小公倍数是指两个或者多个正整数&#xff08;除了0以外&#xff09;的最小的公共倍数…

浅谈RLHF---人类反馈强化学习

浅谈RLHF&#xff08;人类反馈强化学习&#xff09; RLHF&#xff08;Reinforcement Learning fromHuman Feedback&#xff09;人类反馈强化学习 RLHF是[Reinforcement Learning from Human Feedback的缩写&#xff0c;即从人类反馈中进行强化学习。这是一种结合了机器学习中…

Android Toast

Toast Toast是Android常用的简单控件&#xff0c;主要用来进行简短的信息提示&#xff0c;如图1所示。 图1 Toast效果图 Toast的基本用法很简单&#xff0c;不需要设置layout&#xff0c;只需要在程序中调用即可。Toast调用makeText()方法设置需要显示的界面、显示的内容、显…

简洁实用的原创度检测工具AntiPlagiarism NET 4.132

AntiPlagiarism NET是一个适用于Windows的程序&#xff0c;它允许您检查文本的唯一性和从不同Internet来源借用的存在。使用AntiPlagiarism NET&#xff0c;您可以&#xff1a; 将程序用于不同的目的该程序适用于学生、教师、记者、文案作者和其他需要检查其文本或其他作者文本…

SpringBoot实战:多表联查

1. 保存和更新公寓信息 请求数据的结构 Schema(description "公寓信息") Data public class ApartmentSubmitVo extends ApartmentInfo {Schema(description"公寓配套id")private List<Long> facilityInfoIds;Schema(description"公寓标签i…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 游乐园门票 (200分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; 最新华为O…

4000厂商默认账号密码、默认登录凭证汇总.pdf

获取方式&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1F8ho42HTQhebKURWWVW1BQ?pwdy2u5 提取码&#xff1a;y2u5

C语言 ——— 调试的时候如何查看当前程序的变量信息

目录 调试前/后的调试窗口 ​编辑 调试窗口 --- 监视 调试窗口 --- 内存 调试窗口 --- 调用堆栈 调试前/后的调试窗口 调试前的调试窗口&#xff1a; 调试前的调试窗口是没有显示的&#xff0c;只有在调试的时候才会有相对应的调试窗口 调试后的调试窗口&#xff1a…