音视频开发—FFmpeg播放YUV文件,YUV转换为JPEG操作

文章目录

    • 1.使用命令行播放YUV数据
      • 1.1命令解析
      • 1.2参数说明
    • 2.使用C语言实现将YUV数据转为JPEG图片格式
      • 2.1需求分析
      • 2.2读取YUV源文件
      • 2.3将YUV数据封装为AVFrame
      • 2.4将NV12 转换为YUV420平面格式
      • 2.5初始化MJPEG编码器
      • 2.6将YUV420P编码为JPEG
      • 2.7将编码数据写入图片文件
      • 2.8完整代码

1.使用命令行播放YUV数据

使用 FFmpeg 的命令行工具 ffplay 可以很方便地播放 YUV 文件。但是不同于MP4或MKV格式文件,YUV 存储的原始的信息,需要指定分辨率参数和数据格式。

以下是一个典型的命令及其参数解析:

ffplay -pix_fmt nv12 -s 1920*1080 input_test.yuv 

1.1命令解析

  • ffplay:这是 FFmpeg 的播放工具,用于播放音视频文件和流。
  • -f rawvideo:指定输入文件格式为原始视频数据(未压缩)。
  • -pixel_format yuv420p:指定像素格式为 YUV 4:2:0 平面格式(planar)。YUV420p 是一种常见的 YUV 格式,其中 Y、U、V 分量分别存储在独立的平面上。
  • -video_size 1920x1080:指定视频的宽度和高度。这个参数对于原始视频数据非常重要,因为它没有头文件来存储这些信息。
  • input.yuv:这是输入文件的名称。

1.2参数说明

  1. -f rawvideo
    • 表示输入文件是原始视频数据,FFmpeg 不会自动检测文件格式,需要显式指定。
  2. -pixel_format yuv420p
    • yuv420p 表示 YUV 4:2:0 平面格式,Y、U、V 分量分别存储在独立的平面上。
    • 常见的像素格式还有 nv12(UV 分量交织存储)等。
  3. -video_size 1920x1080
    • 指定视频的分辨率(宽度 x 高度)。
    • 必须准确指定,否则播放时会出现图像错位或播放失败。

2.使用C语言实现将YUV数据转为JPEG图片格式

2.1需求分析

项目需求:从camera sensor传出来的数据为一帧帧的NV12格式数据,需要将每帧数据转为JPEG图片。

分析转换流程:

在这里插入图片描述

其中如果一开始的YUV数据本身已经为平面格式的话,无需进行转换,直接可以在初始化完之后,进行编码

MJPEG支持主流的平面格式,如果是打包格式,则需要进行转换为平面格式

AV_PIX_FMT_YUVJ420P,
AV_PIX_FMT_YUVJ422P,  
AV_PIX_FMT_YUVJ444P,  

2.2读取YUV源文件

int readYUVFile(const char *filename, int width, int height, unsigned char *&originalData)
{
    // 读取YUV 文件
    FILE *file = fopen(filename, "rb");
    if (!file)
    {
        cerr << "Failed to open file." << endl;
        return -1;
    }
    size_t originalSize = width * height * 3 / 2; // YUV420 8-bit 1.5 bytes per pixel
    originalData = new unsigned char[originalSize];

    // 读取原始数据  这里是读取一帧, 如果要多帧数据,则使用循环
    if (fread(originalData, sizeof(unsigned char), originalSize, file) != originalSize)
    {
        cerr << "Failed to read data." << endl;
        delete[] originalData;
        fclose(file);
        return -1;
    }
    fclose(file);
    return 0;
}

这里是读取了一帧,因为我的文件本身只有一帧数据,如果是多帧数据,那么需要考虑使用while循环

2.3将YUV数据封装为AVFrame

// 将一帧YUV数据 封装成AVFrame
int yuv_dataToAVFrame(unsigned char *originalData)
{
    original_frame = av_frame_alloc();
    if (!original_frame)
    {
        cerr << "Failed to allocate AVFrame." << std::endl;
        return -1;
    }
    // 设置frame 参数
    original_frame->width = 1920;
    original_frame->height = 1080;
    original_frame->format = AV_PIX_FMT_NV12;
    if (av_frame_get_buffer(original_frame, 0) < 0)
    {
        cerr << "Failed to alloc frame data" << endl;
        av_frame_free(&original_frame);
        return -1;
    }
    // copy data to frame
    int ret = av_image_fill_arrays(original_frame->data, original_frame->linesize, originalData, AV_PIX_FMT_NV12, 1920, 1080, 1);
    if (ret < 0)
    {
        std::cerr << "Failed to fill frame data." << std::endl;
        av_frame_free(&original_frame);
        return -1;
    }
}

Frame不仅仅存放了原数据,还需要指定一些参数,宽度和高度,编码格式,

2.4将NV12 转换为YUV420平面格式

int nv12_to_yuv420p( AVFrame *&frame, AVFrame *&nv12_frame,int width,int height,const unsigned char *nv12_data){
    frame = av_frame_alloc();
    if (!frame) {
        std::cerr << "Failed to allocate AVFrame." << std::endl;
        return -1;
    }
    frame->format = AV_PIX_FMT_YUV420P;
    frame->width = width;
    frame->height = height;
    av_image_alloc(frame->data, frame->linesize, width, height, AV_PIX_FMT_YUV420P, 1);
    nv12_frame = av_frame_alloc();
    if (!nv12_frame) {
        std::cerr << "Failed to allocate AVFrame for NV12." << std::endl;
        av_frame_free(&frame);
        return -1;
    }
    nv12_frame->format = AV_PIX_FMT_NV12;
    nv12_frame->width = width;
    nv12_frame->height = height;
    av_image_fill_arrays(nv12_frame->data, nv12_frame->linesize, nv12_data, AV_PIX_FMT_NV12, width, height, 1);

    struct SwsContext *sws_ctx = sws_getContext(width, height, AV_PIX_FMT_NV12, width, height, AV_PIX_FMT_YUV420P, 0, nullptr, nullptr, nullptr);
    if (!sws_ctx) {
        std::cerr << "Could not create SwsContext." << std::endl;
        av_frame_free(&nv12_frame);
        av_frame_free(&frame);
        return -1;
    }

    cout<<"scale begin"<<endl;
    sws_scale(sws_ctx, nv12_frame->data, nv12_frame->linesize, 0, height, frame->data, frame->linesize);
    sws_freeContext(sws_ctx);
    cout<<"scale endl"<<endl;
}

如果数据本身已经是平面格式,无需这个步骤

2.5初始化MJPEG编码器

int init_mjpeg_encoder(AVFrame *frame)
{
    // 寻找编码器
    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
    if (!codec)
    {
        std::cerr << "Codec not found" << std::endl;
        return -1;
    }
    // 分配编码器上下文
   codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        std::cerr << "Could not allocate video codec context" << std::endl;
        return -1;
    }
    // 配置编码器参数
    codec_ctx->bit_rate = 400000;
    codec_ctx->width = frame->width;
    codec_ctx->height = frame->height;
    codec_ctx->time_base = {1, 25};
    codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;   // 
    // 打开编码器
    if (avcodec_open2(codec_ctx, codec, nullptr) < 0)
    {
        std::cerr << "Could not open codec" << std::endl;
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    return 0;
}

这里需注意:设定编码器上下文支持的格式,而且要与Frame的格式对应,为平面格式,否则会编码失败

2.6将YUV420P编码为JPEG

int frame_encode(AVFrame *frame)
{

    //初始化数据包
    packet = av_packet_alloc();
    if (!packet)
    {
        std::cerr << "Could not allocate AVPacket" << std::endl;
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    //发送原始数据帧
    if(!codec_ctx){
        cout<<"codec_ctx is null"<<endl;
        return -1;
    }
    if(!frame){
        cout<<"frame is null"<<endl;
        return -1;
    }
    int ret = avcodec_send_frame(codec_ctx,frame);
    if(ret<0){
        std::cerr << "Error sending frame to codec context" << std::endl;
        av_packet_free(&packet);
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    // 接收编码后的数据帧
     ret = avcodec_receive_packet(codec_ctx, packet);
    if (ret < 0) {
        std::cerr << "Error receiving packet from codec context" << std::endl;
        av_packet_free(&packet);
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    return 0;
}

这里仅针对一帧完成了编码,多帧请考虑使用循环

2.7将编码数据写入图片文件

int save_frame_as_jpeg(const char *filename){
    std::ofstream outfile(filename, std::ios::out | std::ios::binary);
    if (!outfile.is_open()) {
        std::cerr << "Could not open output file" << std::endl;
        av_packet_free(&packet);
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    outfile.write(reinterpret_cast<const char *>(packet->data), packet->size);
    outfile.close();
    return 0;
}

2.8完整代码

头文件 hpp

#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif

extern "C"
{
#include <stdio.h>
#include "libavutil/avutil.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include <libswscale/swscale.h>
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
}
#include <fstream>
#include <iostream>
using namespace std;
AVFormatContext *format_ctx;
AVPacket *packet;
AVFrame *original_frame,*new_frame;
AVCodec *codec;
AVCodecContext *codec_ctx;

c++实现

#include "readYUV.hpp"
int readYUVFile(const char *filename, int width, int height, unsigned char *&originalData)
{
    // 读取YUV 文件
    FILE *file = fopen(filename, "rb");
    if (!file)
    {
        cerr << "Failed to open file." << endl;
        return -1;
    }
    size_t originalSize = width * height * 3 / 2; // YUV420 8-bit 1.5 bytes per pixel
    originalData = new unsigned char[originalSize];

    // 读取原始数据  这里是读取一帧, 如果要多帧数据,则使用循环
    if (fread(originalData, sizeof(unsigned char), originalSize, file) != originalSize)
    {
        cerr << "Failed to read data." << endl;
        delete[] originalData;
        fclose(file);
        return -1;
    }
    fclose(file);
    return 0;
}
// 将一帧YUV数据 封装成AVFrame
int yuv_dataToAVFrame(unsigned char *originalData)
{
    original_frame = av_frame_alloc();
    if (!original_frame)
    {
        cerr << "Failed to allocate AVFrame." << std::endl;
        return -1;
    }
    // 设置frame 参数
    original_frame->width = 1920;
    original_frame->height = 1080;
    original_frame->format = AV_PIX_FMT_NV12;
    if (av_frame_get_buffer(original_frame, 0) < 0)
    {
        cerr << "Failed to alloc frame data" << endl;
        av_frame_free(&original_frame);
        return -1;
    }
    // copy data to frame
    int ret = av_image_fill_arrays(original_frame->data, original_frame->linesize, originalData, AV_PIX_FMT_NV12, 1920, 1080, 1);
    if (ret < 0)
    {
        std::cerr << "Failed to fill frame data." << std::endl;
        av_frame_free(&original_frame);
        return -1;
    }
}
// 初始化 MJPEG编码器
int init_mjpeg_encoder(AVFrame *frame)
{
    // 寻找编码器
    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
    if (!codec)
    {
        std::cerr << "Codec not found" << std::endl;
        return -1;
    }
    // 分配编码器上下文
   codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        std::cerr << "Could not allocate video codec context" << std::endl;
        return -1;
    }
    // 配置编码器参数
    codec_ctx->bit_rate = 400000;
    codec_ctx->width = frame->width;
    codec_ctx->height = frame->height;
    codec_ctx->time_base = {1, 25};
    codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;   // 
    // 打开编码器
    if (avcodec_open2(codec_ctx, codec, nullptr) < 0)
    {
        std::cerr << "Could not open codec" << std::endl;
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    return 0;
}
// 对一帧 frame 进行编码
int frame_encode(AVFrame *frame)
{

    //初始化数据包
    packet = av_packet_alloc();
    if (!packet)
    {
        std::cerr << "Could not allocate AVPacket" << std::endl;
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    //发送原始数据帧
    if(!codec_ctx){
        cout<<"codec_ctx is null"<<endl;
        return -1;
    }
    if(!frame){
        cout<<"frame is null"<<endl;
        return -1;
    }
    int ret = avcodec_send_frame(codec_ctx,frame);
    if(ret<0){
        std::cerr << "Error sending frame to codec context" << std::endl;
        av_packet_free(&packet);
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    // 接收编码后的数据帧
     ret = avcodec_receive_packet(codec_ctx, packet);
    if (ret < 0) {
        std::cerr << "Error receiving packet from codec context" << std::endl;
        av_packet_free(&packet);
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    return 0;
}
// 将 NV12 数据帧 转换为 YUV420P 数据帧  yuv420sp ---->yuv420p 从打包格式转换为平面格式
int nv12_to_yuv420p( AVFrame *&frame, AVFrame *&nv12_frame,int width,int height,const unsigned char *nv12_data){
    frame = av_frame_alloc();
    if (!frame) {
        std::cerr << "Failed to allocate AVFrame." << std::endl;
        return -1;
    }
    frame->format = AV_PIX_FMT_YUV420P;
    frame->width = width;
    frame->height = height;
    av_image_alloc(frame->data, frame->linesize, width, height, AV_PIX_FMT_YUV420P, 1);
    nv12_frame = av_frame_alloc();
    if (!nv12_frame) {
        std::cerr << "Failed to allocate AVFrame for NV12." << std::endl;
        av_frame_free(&frame);
        return -1;
    }
    nv12_frame->format = AV_PIX_FMT_NV12;
    nv12_frame->width = width;
    nv12_frame->height = height;
    av_image_fill_arrays(nv12_frame->data, nv12_frame->linesize, nv12_data, AV_PIX_FMT_NV12, width, height, 1);

    struct SwsContext *sws_ctx = sws_getContext(width, height, AV_PIX_FMT_NV12, width, height, AV_PIX_FMT_YUV420P, 0, nullptr, nullptr, nullptr);
    if (!sws_ctx) {
        std::cerr << "Could not create SwsContext." << std::endl;
        av_frame_free(&nv12_frame);
        av_frame_free(&frame);
        return -1;
    }

    cout<<"scale begin"<<endl;
    sws_scale(sws_ctx, nv12_frame->data, nv12_frame->linesize, 0, height, frame->data, frame->linesize);
    sws_freeContext(sws_ctx);
    cout<<"scale endl"<<endl;
}
int save_frame_as_jpeg(const char *filename){
    std::ofstream outfile(filename, std::ios::out | std::ios::binary);
    if (!outfile.is_open()) {
        std::cerr << "Could not open output file" << std::endl;
        av_packet_free(&packet);
        avcodec_free_context(&codec_ctx);
        return -1;
    }
    outfile.write(reinterpret_cast<const char *>(packet->data), packet->size);
    outfile.close();
    return 0;
}
int main()
{
    const char *filename = "input_test.yuv";
    unsigned char *originalData = nullptr;
    // 将读取的内存数据 赋值给originalData
    int ret = readYUVFile(filename, 1920, 1080, originalData);
    if (ret < 0)
    {
        cout << "open file fail " << endl;
        return -1;
    }
    // 将yuvdata 转化为 frame
    yuv_dataToAVFrame(originalData);
    //originalFrame 转化为 yuv420p frame
    nv12_to_yuv420p(new_frame,original_frame,1920,1080,originalData);
    // 打开编码器
    ret = init_mjpeg_encoder(new_frame);
    if (ret < 0)
    {
        cout << "open mjpeg encoder fail" << endl;
        return -1;
    }
    //编码
    ret =  frame_encode(new_frame);
    if(ret <0){
        cout <<"encode fail"<<endl;
        return -1;
    }

    //输出文件
    ret = save_frame_as_jpeg("output.jpeg");
     if(ret <0){
        cout <<"save jpeg fail"<<endl;
        return -1;
    }
    cout<<"yuv data susccess convert jpeg!"<<endl;
    //收尾工作
    av_packet_free(&packet);
    avcodec_free_context(&codec_ctx);
    delete[] originalData;
    av_frame_free(&original_frame);
    av_frame_free(&new_frame);
    
    return 0;
}

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

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

相关文章

从零开始实现自己的串口调试助手(4) -实现自动发送 / 时间显示

实现自动发送:checkBox 添加bool槽函数 bool 值&#xff0c;当√的时候为true 取消√ 位false 实现带bool 类型的槽函数: void Widget::on_checkBox_SendInTime_clicked(bool checked) {qDebug()<<"checkStatus:"<<checked;if(checked){ // 被勾选了//…

Python语言进阶学习

目录 一、类、对象和成员方法 二、构造方法 三、面向对象 &#xff08;1&#xff09;封装 &#xff08;2&#xff09;继承 单继承 多继承 复写 super&#xff1a;调用父类同名成员 &#xff08;3&#xff09;多态 &#xff08;4&#xff09;抽象类 五、Python操作…

Leecode---技巧---只出现一次的数字 / 多数元素

题解&#xff1a; 利用异或运算 a⊕a 0 的性质&#xff0c;可用来消除所有出现了两次的元素&#xff0c;最后剩余的即为所得。 class Solution { public:int singleNumber(vector<int>& nums){// 初始化为0int ans 0;for(int x: nums){// 异或操作ans ^ x;}retur…

Java循环结构while

1.while 是最基本的循环&#xff0c;它的结构为 while&#xff08;布尔表达式&#xff09;{ //循环内容 } 2.只要布尔表达式为true&#xff0c;循环就会一直执行下去 3.我们大多数情况是会让循环停止下来的&#xff0c;我们需要一个让表达式时效的方式来结束…

05C零碎语法

C零碎语法 目录 文章目录 C零碎语法1.函数指针2.回调函数3.数据拷贝3.1静态内存分配![请添加图片描述](https://img-blog.csdnimg.cn/direct/54d44e32bb7944f0866d4ca1e2667ce8.png)### 4.1动态内存分配 字符串6.sizeof()和strlen()的区别7.strcpy()/strncpy()函数7.1**strcp…

重生之 SpringBoot3 入门保姆级学习(16、函数式 Web 编程)

重生之 SpringBoot3 入门保姆级学习&#xff08;16、函数式 Web 编程&#xff09; 3.4 函数式 Web 3.4 函数式 Web 个人写过 go 类似于 go gin 框架 1、场景 场景: User RESTful - CRUD GET/user/1 获取1号用户GET/users 获取所有用户POST/user 清求体携带JSON&#xff0c;新…

【Tlias智能学习辅助系统】04 部门管理 删除 和 新增

Tlias智能学习辅助系统 04 部门管理 删除 和 新增 删除部门APIDeptController.javaDeptService.javaDeptServiceImpl.javaDeptMapper.java前端联调 新增部门API有一步简化DeptController.javaDeptService.javaDeptServiceImpl.javaDeptMapper.java前端联调 删除部门API 请求路径…

友善RK3399v2平台利用rkmpp实现硬件编解码加速

测试VPU 编译mpp sudo apt update sudo apt install gcc g cmake make cd ~ git clone https://github.com/rockchip-linux/mpp.git cd mpp/build/linux/aarch64/ sed -i s/aarch64-linux-gnu-gcc/gcc/g ./arm.linux.cross.cmake sed -i s/aarch64-linux-gnu-g/g/g ./arm.lin…

6月2号总结

刷题记录Codeforces Round 925 &#xff08;A,B,C&#xff09; 1.A. Recovering a Small String 问题 - A - Codeforces 输入&#xff1a; 5 24 70 3 55 48 输出&#xff1a; aav rzz aaa czz auz 给定一个3~78的整数&#xff0c;将这个整数分成a,b,c&#xff0c;其中1表示…

洛谷P2370yyy2015c01 的 U 盘

传送门——P2370 yyy2015c01 的 U 盘 题解&#xff1a;题目意思很好理解&#xff0c;就是说&#xff0c;当能够达到预期的U盘的最小接口&#xff08;接口越大&#xff0c;能传递的文件越大&#xff09;&#xff0c;然后我们就需要先看题目了&#xff0c;有n个文件&#xff0c;每…

C# 使用Aspose生成和修改文档

Aspose库 C#中的Aspose库是一个强大的文件处理库&#xff0c;可以用于各种文件格式的创建、编辑、转换和操作。该库提供了丰富的功能&#xff0c;包括处理文档、电子表格、幻灯片、PDF、图像等多种文件格式&#xff0c;能够轻松实现文件的读取、写入、格式化、样式设置、数据操…

双指针法 ( 三数之和 )

题目 &#xff1a;给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复…

设计模式——结构型模式——责任链模式

责任链模式简介 责任链模式&#xff0c;又名职责链模式&#xff0c;为了避免请求发送者与多个请求处理者耦合在一起&#xff0c;将所有请求处理者通过前一对象记住其下一个对象的引用而成一条链&#xff1b;当有请求发生时&#xff0c;可将请求沿着这条链传递&#xff0c;传递过…

山东大学软件学院项目实训-创新实训-基于大模型的旅游平台(二十四)- 微服务(4)

目录 8. http客户端Feign 8.1 feign远程调用 8.2 feign自定义配置 8.3 feign性能优化 8.4 feign最佳实践 8. http客户端Feign 8.1 feign远程调用 RestTemplate存在的问题 &#xff1a; 代码可读性差 参数复杂URL难以维护 Feign是声明式的http客户端 使用步骤 &#xf…

03 使用堡塔和xshell

课程的目标 1、理解windows于Linux实现远程通信的过程和所使用的协议SSH 2、熟练使用远程连接工具堡塔和xshell等工具以及SSH和SCP&#xff0c;命令 课程实验 使用堡塔远程并操作centos并实现与windows之间的文件传输 2、使用xshell远程连接并操作centos与windows之间的文…

Day 10:100322. 删除星号以后字典序最小的字符串

Leetcode 100322. 删除星号以后字典序最小的字符串 给你一个字符串 s 。它可能包含任意数量的 ‘’ 字符。你的任务是删除所有的 ’ 字符。 当字符串还存在至少一个 ‘*’ 字符时&#xff0c;你可以执行以下操作&#xff1a; 删除最左边的 ‘*’ 字符&#xff0c;同时删除该星号…

EasyX的安装及使用

Easy X的下载 首先&#xff0c;打开EasyX的官网&#xff0c;点击下载。 下载完成&#xff0c;直接双击文件。点击下一步&#xff0c;直接点击安装&#xff0c;即可安装完成。 Easy X的使用 要使用Easy X&#xff0c;就要在编写代码时&#xff0c;使用头文件&#xff0c;其有两…

自定义注解处理器生成代码

前言 源码中给的几种注解处理器代码都是网上抄的&#xff0c;本文主要是提供了 Maven 源码&#xff0c;不需要自己网上研究爬坑(当然具体生成代码的逻辑还是得自己写)。且从 lombok 抄了可以解决 idea 代理 ProcessingEnvironment 类后所产生的问题。 以下是网上抄的注解处理…

用java实现客服聊天+网络爬虫下载音乐(java网络编程,io,多线程)

一 灵感&#xff1a; 在2022年的暑假&#xff0c;也就是我即将迈进高三的那个暑假&#xff0c;我并没有察觉自己应该要学习了&#xff0c;还是和过往的暑假一样玩着王者荣耀&#xff0c;凌晨2点睡觉&#xff0c;中午12点起床。我依稀记得这种状态一直持续到8月19。然而离开学还…