linux下ffmpeg调用GPU硬件解码(VDPAU/VAAPI)保存文件

news2024/11/29 14:53:31

本文讲解在linux下面,如何通过ffmpeg调用GPU硬件解码,并保存解码完的yuv文件。
其实,ffmpeg自带的例子hw_decode.c这个文件,就已经能满足要求了,因此,本文就尝试讲解以下hw_decode这个例子。hw_decode.c可以调用VDPAU硬件解码,也可以调用VAAPI硬件解码,下面依次讲解如何进行操作。

下载hw_decode.c文件

我是从网上直接下载ffmpeg源码,下载地址如下:https://ffmpeg.org/releases/ffmpeg-4.2.9.tar.bz2
我这里下载的是4.2.9的源码,然后解压缩之后,在ffmpeg-4.2.9/doc/examples/hw_decode.c路径,就保存了我们需要的hw_decode.c文件。

搭建开发环境

搭建开发环境分2种,一种是直接使用系统自带的软件源里面的软件包进行开发,另外一种就是自己重新编译ffmpeg并进行开发,这两种选一种就可以了。推荐使用软件源的软件包进行开发,因为相对简单一些。下面分别讲解如何操作。

使用软件源的软件包进行开发

需要安装的依赖项如下,我这里是deb系列安装方式。

sudo apt install libvdpau-dev libva-dev ffmpeg libavcodec-dev libavformat-dev libavutil-dev

编译, cd 到ffmpeg-4.2.9/doc/examples目录,执行如下命令

gcc hw_decode.c -lavcodec -lavutil -lavformat -o hw_decode

就可以得到hw_decode这个可执行文件。

自己编译ffmpeg进行开发

自己编译ffmpeg,首先要下载ffmpeg源码,下载地址如下:https://ffmpeg.org/releases/ffmpeg-4.2.9.tar.bz2。
然后解压缩,cd ffmpeg-4.2.9,然后进行configure配置,如果你想使用VDPAU解码,那么configure命令如下

./configure --enable-shared --enable-vdpau

如果你想使用vaapi解码,那么configure命令如下

./configure --enable-shared --enable-vaapi

如果你vdpau和vaapi都想使用,那么进行如下configure。

./configure --enable-shared --enable-vdpau --enable-vaapi

然后,这里可能会遇到问题,可能就是没有安装vdpau开发包,或者没有安装vaapi开发包导致的,输入如下命令安装就可以了。

sudo apt install libvdpau-dev libva-dev 

然后再进行configure操作。
之后,再进行如下操作:

make -j8 
make examples 
sudo make install

其中,make -j8是使用8线程进行ffmpeg编译。
make examples,就是把ffmpeg所有的例子都编译,这样在ffmpeg-4.2.9/doc/exmaples目录,就会生成hw_decode这个可执行文件。
sudo make install,会将ffmpeg的动态库安装到/usr/local/lib下面,可执行文件安装到/usr/local/bin下面,头文件安装到/usr/local/include目录下面。

运行hw_decode例子

cd 到生成hw_decode的目录,如果使用vdpau解码,那么执行如下命令,你需要将第2个参数的视频路径,替换成你的视频路径。

./hw_decode vdpau ~/视频/210329_06B_Bali_1080p_013.mp4  ./out.yuv

如果使用vaapi解码,那么需要使用如下命令:

./hw_decode vaapi ~/视频/210329_06B_Bali_1080p_013.mp4  ./out.yuv

同样,需要将第2个参数替换成你的视频路径。
有的显卡,需要添加环境变量LIBVA_DRIVER_NAME。比如景嘉微JM9系列显卡,需要使用如下命令:

LIBVA_DRIVER_NAME=jmgpu ./hw_decode vaapi ~/视频/210329_06B_Bali_1080p_013.mp4  ./out.yuv

检验out.yuv结果

ffplay -pix_fmt nv12 -s 1920x1080 out.yuv

如上所示,使用ffmpeg自带的播放器ffplay,然后-pix_fmt 指定yuv格式, -s指定分辨率,然后播放。
在这里插入图片描述

hw_decode例子源码讲解

下面开始讲解代码,从main函数开始讲解。

int main(int argc, char *argv[])
{
    AVFormatContext *input_ctx = NULL;
    int video_stream, ret;
    AVStream *video = NULL;
    AVCodecContext *decoder_ctx = NULL;
    AVCodec *decoder = NULL;
    AVPacket packet;
    enum AVHWDeviceType type;
    int i;

    if (argc < 4) {
        fprintf(stderr, "Usage: %s <device type> <input file> <output file>\n", argv[0]);
        return -1;
    }

刚开始的一段,全是变量声明和定义,这些变量都是后面用的到的。然后if (argc < 4)这个判断,是用来判断使用方式的,下面的使用方式,正好是4个argc,第一个./hw_decode是程序名字,第2个参数vaapi表示使用的解码接口,第3个参数是视频路径,第4个参数是输出yuv路径。

./hw_decode vaapi ~/视频/210329_06B_Bali_1080p_013.mp4  ./out.yuv

如果argc < 4,那么提示使用方式,然后返回-1,程序结束。

    type = av_hwdevice_find_type_by_name(argv[1]);
    if (type == AV_HWDEVICE_TYPE_NONE) {
        fprintf(stderr, "Device type %s is not supported.\n", argv[1]);
        fprintf(stderr, "Available device types:");
        while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
            fprintf(stderr, " %s", av_hwdevice_get_type_name(type));
        fprintf(stderr, "\n");
        return -1;
    }

接下来,就是去寻找第2个参数对应的硬件解码类型,argv[1]就对应我们解码程序的参数"vdpau",或者"vaapi",如果找到了,就保存在变量type中,如果没找到,就通过一个while循环把支持的硬件类型列举,并打印出来,然后return -1程序退出。

    /* open the input file */
    if (avformat_open_input(&input_ctx, argv[2], NULL, NULL) != 0) {
        fprintf(stderr, "Cannot open input file '%s'\n", argv[2]);
        return -1;
    }

接下来,avformat_open_input,就是打开输入文件,在我这里,对应的就是打开“~/视频/210329_06B_Bali_1080p_013.mp4”这个文件,argv[2]就是输入视频路径,如果失败了,就返回-1,否则继续。

    if (avformat_find_stream_info(input_ctx, NULL) < 0) {
        fprintf(stderr, "Cannot find input stream information.\n");
        return -1;
    }

然后,查找视频文件里面的码流信息,一般就是找这个视频里面,有几个视频流,有几个音频流,如果没有找到因视频信息,就加一条错误打印,然后返回-1.

    /* find the video stream information */
    ret = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
    if (ret < 0) {
        fprintf(stderr, "Cannot find a video stream in the input file\n");
        return -1;
    }
    video_stream = ret;

接下来,查找AVMEDIA_TYPE_VIDEO,也就是查找视频流信息,并将视频流的索引号,保存在video_stream中。

    for (i = 0;; i++) {
        const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
        if (!config) {
            fprintf(stderr, "Decoder %s does not support device type %s.\n",
                    decoder->name, av_hwdevice_get_type_name(type));
            return -1;
        }
        if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
            config->device_type == type) {
            hw_pix_fmt = config->pix_fmt;
            break;
        }
    }

接下来,就是通过一个循环,查找能支持的硬件格式对应的pix_fmt,比如我这里使用vaapi,那么通过AV_HWDEVICE_TYPE_VAAPI找到了pix_fmt为AV_PIX_FMT_VAAPI_VLD。
在这里插入图片描述

    if (!(decoder_ctx = avcodec_alloc_context3(decoder)))
        return AVERROR(ENOMEM);

    video = input_ctx->streams[video_stream];
    if (avcodec_parameters_to_context(decoder_ctx, video->codecpar) < 0)
        return -1;

	decoder_ctx->get_format  = get_hw_format;

继续,分配一个解码上下文 decoder_ctx,然后根据视频码流信息,填充decoder_ctx里面内容。
并将get_hw_format这个函数地址,给到decoder_ctx->get_format中,这样后续解码器解码时会调用这个get_fomat函数指针来对格式进行判断。

    if (hw_decoder_init(decoder_ctx, type) < 0)
        return -1;

初始化完了解码上下文,再初始化硬件解码器。

    if ((ret = avcodec_open2(decoder_ctx, decoder, NULL)) < 0) {
        fprintf(stderr, "Failed to open codec for stream #%u\n", video_stream);
        return -1;
    }

打开解码器。

    /* open the file to dump raw data */
    output_file = fopen(argv[3], "w+");

打开输出文件,这个argv[3],就对应我们命令行里面的out.yuv,就是打开这个文件,方便后面写入使用。

    /* actual decoding and dump the raw data */
    while (ret >= 0) {
        if ((ret = av_read_frame(input_ctx, &packet)) < 0)
            break;

        if (video_stream == packet.stream_index)
            ret = decode_write(decoder_ctx, &packet);

        av_packet_unref(&packet);
    }

重点戏来了,就是这个while循环,av_read_frame读取一帧数据,保存在packet中,然后判断以下这个packet的stream_index是不是video_stream,如果是视频数据,就调用decode_write,否则就什么也不做,处理完之后,调用av_packet_unref取消packet的引用。看来重点就在这个decode_write函数里面。

static int decode_write(AVCodecContext *avctx, AVPacket *packet)
{
    AVFrame *frame = NULL, *sw_frame = NULL;
    AVFrame *tmp_frame = NULL;
    uint8_t *buffer = NULL;
    int size;
    int ret = 0;

    ret = avcodec_send_packet(avctx, packet);
    if (ret < 0) {
        fprintf(stderr, "Error during decoding\n");
        return ret;
    }

decode_write拿到packet数据,调用avcodec_send_packet,将packet发送给解码器。


    while (1) {
        if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) {
            fprintf(stderr, "Can not alloc frame\n");
            ret = AVERROR(ENOMEM);
            goto fail;
        }

        ret = avcodec_receive_frame(avctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            av_frame_free(&frame);
            av_frame_free(&sw_frame);
            return 0;
        } else if (ret < 0) {
            fprintf(stderr, "Error while decoding\n");
            goto fail;
        }

        if (frame->format == hw_pix_fmt) {
            /* retrieve data from GPU to CPU */
            if ((ret = av_hwframe_transfer_data(sw_frame, frame, 0)) < 0) {
                fprintf(stderr, "Error transferring the data to system memory\n");
                goto fail;
            }
            tmp_frame = sw_frame;
        } else
            tmp_frame = frame;

        size = av_image_get_buffer_size(tmp_frame->format, tmp_frame->width,
                                        tmp_frame->height, 1);
        buffer = av_malloc(size);
        if (!buffer) {
            fprintf(stderr, "Can not alloc buffer\n");
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        ret = av_image_copy_to_buffer(buffer, size,
                                      (const uint8_t * const *)tmp_frame->data,
                                      (const int *)tmp_frame->linesize, tmp_frame->format,
                                      tmp_frame->width, tmp_frame->height, 1);
        if (ret < 0) {
            fprintf(stderr, "Can not copy image to buffer\n");
            goto fail;
        }

        if ((ret = fwrite(buffer, 1, size, output_file)) < 0) {
            fprintf(stderr, "Failed to dump raw data.\n");
            goto fail;
        }

    fail:
        av_frame_free(&frame);
        av_frame_free(&sw_frame);
        av_freep(&buffer);
        if (ret < 0)
            return ret;
    }

然后一个大的while循环,这里其实就是让解码器去解码,如果解码得到数据,就将数据从GPU显存拷贝到CPU内存,然后再写入out.yuv文件中。下面分开讲解。

    while (1) {
        if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) {
            fprintf(stderr, "Can not alloc frame\n");
            ret = AVERROR(ENOMEM);
            goto fail;
        }

while的开始,分配了2个frame,第一个frame,是用来保存GPU解码完毕的数据,这个数据位于显存。第2个sw_frame是用来保存内存数据,用来将GPU显存的yuv数据拷贝到内存用的。

        ret = avcodec_receive_frame(avctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            av_frame_free(&frame);
            av_frame_free(&sw_frame);
            return 0;
        } else if (ret < 0) {
            fprintf(stderr, "Error while decoding\n");
            goto fail;
        }

avcode_receive_frame,用来接受解码器传过来的frame数据,也就是如果解码器解码完了,会得到一个解码完毕的AVFrame数据,这个数据就保存在frame中。如果返回值为EAGAIN或者AVERROR_EOF,说明之前的packet并没有解码得到一个完整的AVFrame数据,因此需要把前面分配的2个frame和sw_frame都释放掉,然后返回0,说明这一个packet处理完毕了。如果ret 是其他值 < 0,说明解码出错了,goto fail。fail标签后面再说。

        if (frame->format == hw_pix_fmt) {
            /* retrieve data from GPU to CPU */
            if ((ret = av_hwframe_transfer_data(sw_frame, frame, 0)) < 0) {
                fprintf(stderr, "Error transferring the data to system memory\n");
                goto fail;
            }
            tmp_frame = sw_frame;
        } else
            tmp_frame = frame;

否则,我们解码得到了一帧数据,判断一下,这一帧数据的格式,如果格式正好是hw_pix_fmt,那么调用av_hwframe_transfer_data,将frame里面的GPU数据,传输到sw_frame里面,tmp_frame正好等于sw_frame。如果不是hw_pix_fmt,那么tmp_frame就是frame。这个执行完之后,tmp_frame里面保存的就是内存数据了。

        size = av_image_get_buffer_size(tmp_frame->format, tmp_frame->width,
                                        tmp_frame->height, 1);
        buffer = av_malloc(size);
        if (!buffer) {
            fprintf(stderr, "Can not alloc buffer\n");
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        ret = av_image_copy_to_buffer(buffer, size,
                                      (const uint8_t * const *)tmp_frame->data,
                                      (const int *)tmp_frame->linesize, tmp_frame->format,
                                      tmp_frame->width, tmp_frame->height, 1);
        if (ret < 0) {
            fprintf(stderr, "Can not copy image to buffer\n");
            goto fail;
        }

接下来,判断tmp_frame的数据大小,分配一个size大小的buffer,将tmp_frame的数据,搬到buffer中。

        if ((ret = fwrite(buffer, 1, size, output_file)) < 0) {
            fprintf(stderr, "Failed to dump raw data.\n");
            goto fail;
        }

然后将buffer中的数据,写入到output_file中,也就是写入到out.yuv中。

    fail:
        av_frame_free(&frame);
        av_frame_free(&sw_frame);
        av_freep(&buffer);
        if (ret < 0)
            return ret;
    }
}

如果失败了,释放frame, sw_frame, buffer内容,并且如果ret <0, 返回ret。

    /* actual decoding and dump the raw data */
    while (ret >= 0) {
        if ((ret = av_read_frame(input_ctx, &packet)) < 0)
            break;

        if (video_stream == packet.stream_index)
            ret = decode_write(decoder_ctx, &packet);

        av_packet_unref(&packet);
    }

    /* flush the decoder */
    packet.data = NULL;
    packet.size = 0;
    ret = decode_write(decoder_ctx, &packet);
    av_packet_unref(&packet);

    if (output_file)
        fclose(output_file);
    avcodec_free_context(&decoder_ctx);
    avformat_close_input(&input_ctx);
    av_buffer_unref(&hw_device_ctx);

    return 0;
}

然后一直循环av_read_frame,解码写文件,直到av_read_frame < 0,也就是把整个输入文件都处理完了,这个while循环结束。
接下来,还设置了一个packet.data = NULL, 调用了一次decode_write,就是告诉解码器,我没有数据了,你里面如果还缓存一些数据,都给我输出出来吧。

最后就是关闭输出文件,释放解码器上下文,关闭输出,释放硬件设备上下文。至此, hw_decode解析完毕。

常见问题

  1. 为什么硬件解码这么慢,CPU占用率也很高?
    答: 之所以这么慢,CPU占用率高,是因为有2个操作,1个操作是需要将数据从GPU显存拷贝到CPU内存,另外1个操作是需要写文件。如果你屏蔽av_hwframe_transfer_data及之后的操作,这里对应代码107行到139行,那么速度将会特别快。
    2. 为什么运行vaapi时提示找不到vaapi device。
    答:可能原因是没有安装 vaapi驱动,或者没有指定LIBVA_DRIVER_NAME这个环境变量。

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

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

相关文章

使用vscode传入参数的方式进行debug

使用vscode传入参数的方式进行debug {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387"version": "0.2.0","configurations": [{&quo…

【AGI视频】Sora的奇幻之旅:未来影视创作的无限可能

在五年后的未来&#xff0c;科技的发展为影视创作带来了翻天覆地的变化。其中&#xff0c;Sora视频生成软件成为了行业的翘楚&#xff0c;引领着全新的创作潮流。Sora基于先进的Transformer架构&#xff0c;将AI与人类的创造力完美结合&#xff0c;为观众带来了前所未有的视听盛…

【分享】windows11 vmware centos7 搭建k8s完整实验

概述 开年第一天&#xff0c;补充下自己的技术栈。 参考文章: k8s安装 - 知乎 【Kubernetes部署篇】K8s图形化管理工具Dasboard部署及使用_k8s可视化管理工具-CSDN博客 centos7环境下安装k8s 1.18.0版本带dashboard界面全记录&#xff08;纯命令版&#xff09;_sysconfig1.…

通俗易懂地解释OpenAI Sora视频生成的特点有哪些?与Runway Gen2、Pika有什么区别?缺点是什么?

OpenAI的Sora模型是最近两天最火热的模型。它生成的视频无论是清晰度、连贯性和时间上都有非常好的结果。在Sora之前&#xff0c;业界已经有了很多视频生成工具和平台。但为什么Sora可以引起如此大的关注&#xff1f;Sora生成的视频与此前其它平台生成的视频到底有哪些区别&…

MATLAB知识点:meshgrid函数(★★★★☆)返回二维网格坐标(在MATLAB中经常用于生成绘制三维图的数据)

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章&#xff1a;课后习题讲解中拓展的函数 在讲解第三…

入门级10寸加固行业平板—EM-I10J

亿道信息以其坚固耐用的智能终端设备而闻名&#xff0c;近日发布了一款理想入门级 10 英寸加固平板电脑—I10J。 EM-I10J​​ 这是一款 10 英寸的平板电脑&#xff0c;主要运行 Windows 10操作系统&#xff0c;带有硬化塑料外壳&#xff0c;具有 IP65 防水防尘功能和 MIL-STD 8…

踩坑实录(Fourth Day)

今天开工了&#xff0c;其实还沉浸在过年放假的喜悦中……今天在自己写 Vue3 的项目&#xff0c;虽说是跟着 B 站在敲&#xff0c;但是依旧是踩了一些个坑&#xff0c;就离谱……照着敲都能踩到坑&#xff0c;我也是醉了…… 此为第四篇&#xff08;2024 年 02 月 18 日&#x…

2024免费版EasyRecovery软件有哪些功能限制?

EasyRecovery软件的主要功能包括&#xff1a; 数据恢复&#xff1a;这是EasyRecovery软件的核心功能。它可以恢复因各种原因丢失或删除的数据&#xff0c;无论是由于磁盘格式化、文件删除还是其他因素。EasyRecovery使用高级的数据恢复算法&#xff0c;能够快速扫描整个磁盘&a…

python绘制k线图均线图

AAPL.csv 数据文件 Date,Close,Volume,Open,High,Low 06/23/2023,$186.68,53117000,$185.55,$187.56,$185.01 06/22/2023,$187.00,51245330,$183.74,$187.045,$183.67 06/21/2023,$183.96,49515700,$184.90,$185.41,$182.5901 06/20/2023,$185.01,49799090,$184.41,$1…

互联网加竞赛 多目标跟踪算法 实时检测 - opencv 深度学习 机器视觉

文章目录 0 前言2 先上成果3 多目标跟踪的两种方法3.1 方法13.2 方法2 4 Tracking By Detecting的跟踪过程4.1 存在的问题4.2 基于轨迹预测的跟踪方式 5 训练代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习多目标跟踪 …

基于Doris构建亿级数据实时数据分析系统

背景 随着公司业务快速发展&#xff0c;对业务数据进行增长分析的需求越来越迫切&#xff0c;与此同时我们的业务数据量也在快速激增、每天的数据新增量大概在30w 左右&#xff0c;一年就会产生1 个亿的数据&#xff0c;显然基于传统MySQL数据库已经无法支撑满足以上需求 基于上…

《Linux 简易速速上手小册》第2章: 命令行的艺术(2024 最新版)

文章目录 2.1 基本 Linux 命令2.1.1 重点基础知识2.1.2 重点案例&#xff1a;整理下载文件夹2.1.3 拓展案例 1&#xff1a;批量重命名文件2.1.4 拓展案例 2&#xff1a;查找并删除特定文件 2.2 文件和目录管理2.2.1 重点基础知识2.2.2 重点案例&#xff1a;部署一个简单的网站2…

RabbitMQ鉴权设计以及相关探讨

文章目录 1. rabbitmq的鉴权设计2. rabbitmq鉴权应用范围3. rabbitmq鉴权的常用方法3.1 用户管理3.2 角色管理3.3 权限管理 4. 默认鉴权4.1 默认用户4.2 默认角色 5. 参考文档 鉴权&#xff0c;分别由鉴和权组成 鉴&#xff1a; 表示身份认证&#xff0c;认证相关用户是否存在…

AlexNet的出现推动深度学习的巨大发展

尽管AlexNet&#xff08;2012&#xff09;的代码只比LeNet&#xff08;1998&#xff09;多出几行&#xff0c;但学术界花了很多年才接受深度学习这一概念&#xff0c;并应用其出色的实验结果。 AlexNet&#xff08;由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton共同设计…

Docker原理及概念相关

Docker最核心的组件 image&#xff1a;镜像&#xff0c;构建容器&#xff0c;也可以通过Dockerfile文本描述镜像的内容。 (我们将应用程序运行所需的环境&#xff0c;打包为镜像文件) Container&#xff1a;容器 (你的应用程序&#xff0c;就跑在容器中 ) 镜像仓库(dockerhub)(…

Java学习笔记2024/2/18

1.API 1.1API概述 什么是API API (Application Programming Interface) &#xff1a;应用程序编程接口 java中的API 指的就是 JDK 中提供的各种功能的 Java类&#xff0c;这些类将底层的实现封装了起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要学习这…

kali无线渗透之蓝牙原理与探测与侦听

“传统蓝牙”规范在2.4GHz的ISM波段上定义了79个信道&#xff0c;每个信道有1MHz的带宽。设备在这些信道中以每秒1600次的频率进行跳转&#xff0c;换句话说&#xff0c;就是每微秒625次跳转。这项信道跳转技术被称为“跳频扩频”(Frequency HoppingSpread Spectrum&#xff0c…

电路设计(20)——数字电子钟的multism仿真

1.设计要求 使用数字芯片&#xff0c;设计一个电子钟&#xff0c;用数码管显示&#xff0c;可以显示星期&#xff0c;时、分、秒&#xff0c;可以有按键校准时间。有整点报警功能。 2.设计电路 设计好的multism电路图如下所示 3.芯片介绍 时基脉冲使用555芯片产生。在仿真里面…

刷题Day2

&#x1f308;个人主页&#xff1a;小田爱学编程 &#x1f525; 系列专栏&#xff1a;刷题日记 &#x1f3c6;&#x1f3c6;关注博主&#xff0c;随时获取更多关于IT的优质内容&#xff01;&#x1f3c6;&#x1f3c6; &#x1f600;欢迎来到小田代码世界~ &#x1f601; 喜欢…

Win11家庭版,鸿蒙DevEco 模拟器启动失败,成功解决了

本人电脑系统&#xff1a;Windows 11 家庭版 正常安装模拟器后&#xff0c;启动失败&#xff0c;查了各种方法&#xff0c;最终发现是电脑虚拟机未启动导致的。 官方给出的解决方法&#xff08;对我无效&#xff01;&#xff01;&#xff01;&#xff09;&#xff1a; 我的…