webrtc 3A移植以及实时处理

news2024/11/29 10:32:37

文章目录

  • 前言
  • 一、交叉编译
    • 1.Pulse Audio webrtc-audio-processing
    • 2.交叉编译
  • 二、基于alsa进行实时3A处理
    • 1.demo源码
    • 2.注意项
    • 3.效果展示
  • 总结


前言

由于工作需要,硬件3A中的AEC效果实在太差,后面使用SpeexDSP的软3A,效果依旧不是很好,猜测是内部时延估计和时延补偿做的并不是很好,于是采用了webrtc的3A算法,这里记录一下3A移植过程。

|版本声明:山河君,未经博主允许,禁止转载


一、交叉编译

1.Pulse Audio webrtc-audio-processing

在linux下,webrtc 3A是比较好移植的,原因是Pulse Audio是支持webrtc 3A插件的,也就是说,不需要我们自己翻墙下载配置webrtc的环境以及编译链路,Pulse Audio已经帮我们做好了这一步,剩下的就是交叉编译的工作。

对应的gitlab地址:Pulse Audio webrtc-audio-processing

2.交叉编译

先选择好版本,截至到当前博客时间,最新版本是1.3.1,网上之前也有介绍的,基本都是0.3版本的
在这里插入图片描述
1.0版本和1.0之前的版本最大的区别就是编译器的不同,之前是通过脚本配置,1.0后都是使用meson 进行配置,所以需要下载meson 以及ninja(用于编译webrtc)

meson和代码下载之后,就需要配置交叉编译链路,meson对应交叉编译器环境需要我们自己写meson配置文件,在源文件目录下,打开cross_file.txt,内容如下:

[binaries]
c = '/usr/bin/aarch64-linux-gnu-gcc'
cpp = '/usr/bin/aarch64-linux-gnu-g++'
ar = '/usr/bin/aarch64-linux-gnu-ar'
strip = '/usr/bin/aarch64-linux-gnu-strip'
pkgconfig = '/usr/bin/aarch64-linux-gnu-pkg-config'

[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'armv8-a'
endian = 'little'

[paths]
prefix = '/home/aaron/workplace/webrtc-audio-processing/build'

在源文件目录下创建编译缓存目录以及安装目录

meson . build -Dprefix=$PWD/install --cross-file=cross_file.txt
ninja -C build
ninja -C build install

最后在install目录下可以看到编译好的文件
在这里插入图片描述

二、基于alsa进行实时3A处理

1.demo源码

编译好的完整demo下载:demo下载,如果没有积分的话就自己编译,这里只是少了demo的脚本

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <pthread.h>
#include <errno.h>
#include <mutex>
#include "modules/audio_processing/include/audio_processing.h"

constexpr int sample_rate_hz = 16000;               // 假设采样率为16kHz
constexpr size_t num_channels = 1;                  // 假设单声道
constexpr size_t frame_size = sample_rate_hz / 100; // 10ms 帧大小

#define CHANNELS 2
#define SAMPLE_RATE 16000
#define PERIOD_SIZE 160
#define BUFFER_SIZE (PERIOD_SIZE * 2)
bool gRun = true;

typedef struct
{
    snd_pcm_t *playback_handle;
    FILE *pcm_file;
    int8_t m_pPlayoutBuffer[PERIOD_SIZE * 2 * 2];
    int m_nPlayoutBufferSizeIn20MS = PERIOD_SIZE * 2 * 2;
    int m_nPlayoutFramesLeft = 0;

    snd_pcm_t *capture_handle;
    FILE *pcm_out_file;
    int8_t m_pRecordBuffer[PERIOD_SIZE * 2 * 2];
    int m_nRecordBufferSizeIn20MS = PERIOD_SIZE * 2 * 2;
    int m_nRecordingFramesLeft = PERIOD_SIZE;

    FILE *pcm_3a_file;
    FILE *pcm_ref_file;
    int8_t m_pRefBuffer[PERIOD_SIZE * 2];
    int8_t m_p3ABuffer[PERIOD_SIZE * 2];   // in byte
    int8_t m_pNearBuffer[PERIOD_SIZE * 2]; // in byte
    rtc::scoped_refptr<webrtc::AudioProcessing> apm;
} audio_data_t;

void monoTurnStereo(const int16_t *pSrc, int16_t *pDts, size_t size)
{
    for (int j = 0; j < size; j++)
    {
        pDts[2 * j] = pSrc[j];
        pDts[2 * j + 1] = pSrc[j];
    }
}

void stereoTurnMono(const unsigned char *pSrc, unsigned char *pDts, size_t size)
{
    int nLeftCount = 0;
    for (size_t i = 0; i < size; i += 4)
    {
        pDts[nLeftCount] = pSrc[i];
        pDts[nLeftCount + 1] = pSrc[i + 1];
        nLeftCount += 2;
    }
}

int32_t errorRecovery(int32_t nRet, snd_pcm_t *pDeviceHandle)
{
    int st = snd_pcm_state(pDeviceHandle);
    printf("Trying to recover from %s error: %s nRet:(%d) (state:%d)\n",
           ((snd_pcm_stream(pDeviceHandle) ==
             SND_PCM_STREAM_CAPTURE)
                ? "capture"
                : "playout"),
           snd_strerror(nRet),
           nRet,
           st);

    int res = snd_pcm_recover(pDeviceHandle, nRet, 1);
    if (0 == res)
    {
        printf("Recovery - snd_pcm_recover OK\n");

        if ((nRet == -EPIPE || nRet == -ESTRPIPE) &&
            snd_pcm_stream(pDeviceHandle) == SND_PCM_STREAM_CAPTURE)
        {
            // For capture streams we also have to repeat the explicit start()
            // to get data flowing again.
            int nRet = snd_pcm_start(pDeviceHandle);
            if (nRet != 0)
            {
                printf("Recovery - snd_pcm_start error: %d\n", nRet);
                return -1;
            }
        }

        if ((nRet == -EPIPE || nRet == -ESTRPIPE) &&
            snd_pcm_stream(pDeviceHandle) == SND_PCM_STREAM_PLAYBACK)
        {
            // For capture streams we also have to repeat the explicit start() to get
            // data flowing again.

            snd_pcm_state_t state = snd_pcm_state(pDeviceHandle);
            if (state != SND_PCM_STATE_PREPARED)
            {
                snd_pcm_prepare(pDeviceHandle);
            }

            int nRet = snd_pcm_start(pDeviceHandle);
            if (nRet != 0)
            {
                printf("Recovery - snd_pcm_start error: %s\n", snd_strerror(nRet));
                return -1;
            }
        }

        return -EPIPE == nRet ? 1 : 0;
    }
    else
    {
        printf("Unrecoverable alsa stream error: %d\n", res);
    }

    return res;
}

void *playback_thread(void *arg)
{
    printf("playback_thread\n");
    audio_data_t *data = (audio_data_t *)arg;

    const int policy = SCHED_FIFO;
    const int min_prio = sched_get_priority_min(policy);
    const int max_prio = sched_get_priority_max(policy);

    sched_param param;
    const int top_prio = max_prio - 1;
    const int low_prio = min_prio + 1;
    param.sched_priority = top_prio;
    pthread_setschedparam(pthread_self(), policy, &param);

    while (gRun)
    {
        int nRet;
        snd_pcm_sframes_t sndFrames;
        snd_pcm_sframes_t sndAvailFrames;
        sndAvailFrames = snd_pcm_avail_update(data->playback_handle);

        if (sndAvailFrames < 0)
        {
            printf("playout snd_pcm_avail_update error: %s\n", snd_strerror(sndAvailFrames));
            errorRecovery(sndAvailFrames, data->playback_handle);
            continue;
        }
        else if (sndAvailFrames == 0)
        {
            nRet = snd_pcm_wait(data->playback_handle, 2);
            // if (nRet == 0)
            //     printf("playout snd_pcm_wait timeout\n");
            continue;
        }

        if (data->m_nPlayoutFramesLeft <= 0)
        {
            size_t frames = fread(data->m_pRefBuffer, 2, PERIOD_SIZE, data->pcm_file);
            if (frames == 0 || frames != PERIOD_SIZE)
            { // 文件播放完毕,重新开始
                fseek(data->pcm_file, 0, SEEK_SET);
                continue;
            }
            monoTurnStereo((int16_t *)data->m_pRefBuffer, (int16_t *)data->m_pPlayoutBuffer, PERIOD_SIZE);
            data->m_nPlayoutFramesLeft = frames;
        }

        if ((uint32_t)(sndAvailFrames) > data->m_nPlayoutFramesLeft)
        {
            sndAvailFrames = (uint32_t)data->m_nPlayoutFramesLeft;
        }

        int size = snd_pcm_frames_to_bytes(data->playback_handle, data->m_nPlayoutFramesLeft);
        sndFrames = snd_pcm_writei(data->playback_handle, &data->m_pPlayoutBuffer[data->m_nPlayoutBufferSizeIn20MS - size], sndAvailFrames);

        if (sndFrames < 0)
        {
            printf("playout snd_pcm_writei error: %s\n", snd_strerror(sndFrames));
            data->m_nPlayoutFramesLeft = 0;
            errorRecovery(sndFrames, data->playback_handle);
            continue;
        }
        else
        {
            fwrite(&data->m_pRefBuffer[PERIOD_SIZE * 2 - data->m_nPlayoutFramesLeft * 2], 1, sndFrames * 2, data->pcm_ref_file);
            data->m_nPlayoutFramesLeft -= sndFrames;
        }
    }
    return NULL;
}

void *capture_thread(void *arg)
{
    printf("capture_thread\n");
    audio_data_t *data = (audio_data_t *)arg;

    const int policy = SCHED_FIFO;
    const int min_prio = sched_get_priority_min(policy);
    const int max_prio = sched_get_priority_max(policy);

    sched_param param;
    const int top_prio = max_prio - 1;
    const int low_prio = min_prio + 1;
    param.sched_priority = top_prio;
    pthread_setschedparam(pthread_self(), policy, &param);

    while (gRun)
    {
        int nRet;
        snd_pcm_sframes_t sndFrames;
        snd_pcm_sframes_t sndAvailFrames;
        int8_t buffer[data->m_nRecordBufferSizeIn20MS];

        sndAvailFrames = snd_pcm_avail_update(data->capture_handle);
        if (sndAvailFrames < 0)
        {
            printf("capture snd_pcm_avail_update error: %s\n", snd_strerror(sndAvailFrames));
            errorRecovery(sndAvailFrames, data->capture_handle);
            continue;
        }
        else if (sndAvailFrames == 0)
        {
            continue;
        }

        if ((uint32_t)(sndAvailFrames) > data->m_nRecordingFramesLeft)
            sndAvailFrames = data->m_nRecordingFramesLeft;

        sndFrames = snd_pcm_readi(data->capture_handle, buffer, sndAvailFrames);
        if (sndFrames < 0)
        {
            printf("capture snd_pcm_readi error: %s\n", snd_strerror(sndFrames));
            errorRecovery(sndFrames, data->capture_handle);
            continue;
        }
        else if (sndFrames > 0)
        {
            int nLeftSize = snd_pcm_frames_to_bytes(data->capture_handle, data->m_nRecordingFramesLeft);
            int size = snd_pcm_frames_to_bytes(data->capture_handle, sndFrames);
            memcpy(&data->m_pRecordBuffer[data->m_nRecordBufferSizeIn20MS - nLeftSize], buffer, size);
            data->m_nRecordingFramesLeft -= sndFrames;
        }

        if (!data->m_nRecordingFramesLeft)
        {
            data->m_nRecordingFramesLeft = PERIOD_SIZE;

            stereoTurnMono((unsigned char *)data->m_pRecordBuffer, (unsigned char *)data->m_pNearBuffer, PERIOD_SIZE * 2 * 2);

            fwrite(data->m_pNearBuffer, 1, PERIOD_SIZE * 2, data->pcm_out_file);

            webrtc::StreamConfig stream_config(sample_rate_hz, num_channels);
            if (data->apm->ProcessReverseStream((int16_t *)data->m_pRefBuffer, stream_config, stream_config, (int16_t *)data->m_pRefBuffer) != webrtc::AudioProcessing::kNoError)
            {
                printf("ProcessReverseStream fail\n");
            }
            if (data->apm->ProcessStream((int16_t *)data->m_pNearBuffer, stream_config, stream_config, (int16_t *)data->m_p3ABuffer) != webrtc::AudioProcessing::kNoError)
            {
                printf("ProcessStream fail\n");
            }

            fwrite(data->m_p3ABuffer, 1, PERIOD_SIZE * 2, data->pcm_3a_file);
        }
    }
    return NULL;
}

int setup_pcm_device(snd_pcm_t **handle, const char *device, snd_pcm_stream_t stream)
{
    snd_pcm_hw_params_t *params;
    int err;

    if ((err = snd_pcm_open(handle, device, stream, SND_PCM_NONBLOCK)) < 0)
    {
        printf("无法打开 PCM 设备 %s: %s\n", device, snd_strerror(err));
        return err;
    }

    snd_pcm_hw_params_alloca(&params);
    snd_pcm_hw_params_any(*handle, params);
    snd_pcm_hw_params_set_access(*handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
    snd_pcm_hw_params_set_format(*handle, params, SND_PCM_FORMAT_S16_LE);
    snd_pcm_hw_params_set_channels(*handle, params, CHANNELS);
    snd_pcm_hw_params_set_rate(*handle, params, SAMPLE_RATE, 0);
    snd_pcm_hw_params_set_period_size(*handle, params, PERIOD_SIZE, 0);
    snd_pcm_hw_params_set_buffer_size(*handle, params, PERIOD_SIZE * 4);
    if ((err = snd_pcm_hw_params(*handle, params)) < 0)
    {
        printf("设置 PCM 参数失败: %s\n", snd_strerror(err));
        snd_pcm_close(*handle);
        return err;
    }
    return 0;
}

int main(int argc, char *argv[])
{

    audio_data_t data;

    if ((data.pcm_file = fopen("./far.pcm", "rb")) == NULL ||
        (data.pcm_3a_file = fopen("./3a.pcm", "wb")) == NULL ||
        (data.pcm_out_file = fopen("./near.pcm", "wb")) == NULL ||
        (data.pcm_ref_file = fopen("./ref.pcm", "wb")) == NULL)
    {
        printf("fail to open file\n");
        return -1;
    }

    
    // 3a
    data.apm = webrtc::AudioProcessingBuilder().Create();
    webrtc::AudioProcessing::Config config;

    // 噪声抑制配置
    config.noise_suppression.enabled = true;
    config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::Level::kHigh;

    // 增益控制配置
    config.gain_controller1.enabled = true;
    config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveDigital;
    config.gain_controller1.target_level_dbfs = 3; // 目标输出电平

    // 回声抑制配置
    config.echo_canceller.enabled = true;
    config.echo_canceller.mobile_mode = false; // 如果是移动设备可以设置为 true

    // 语音活动检测(可选)
    config.voice_detection.enabled = true;

    // 应用配置
    data.apm->ApplyConfig(config);
    //

    if (setup_pcm_device(&data.playback_handle, "hw:0,0", SND_PCM_STREAM_PLAYBACK) < 0)
    {
        fclose(data.pcm_file);
        return -1;
    }

    if (setup_pcm_device(&data.capture_handle, "hw:0,0", SND_PCM_STREAM_CAPTURE) < 0)
    {
        snd_pcm_close(data.playback_handle);
        fclose(data.pcm_file);
        return -1;
    }

    pthread_t play_thread, record_thread;
    pthread_create(&play_thread, NULL, playback_thread, &data);
    int nRet = snd_pcm_prepare(data.playback_handle);
    if (nRet < 0)
    {
        printf("playout snd_pcm_prepare failed (%s)\n", snd_strerror(nRet));
    }

    pthread_create(&record_thread, NULL, capture_thread, &data);

    nRet = snd_pcm_prepare(data.capture_handle);
    if (nRet < 0)
    {
        printf("capture snd_pcm_prepare failed:%s \n", snd_strerror(nRet));
    }

    nRet = snd_pcm_start(data.capture_handle);
    if (nRet < 0)
    {
        printf("capture snd_pcm_start err:%s\n", snd_strerror(nRet));
        nRet = snd_pcm_start(data.capture_handle);
        if (nRet < 0)
        {
            printf("capture snd_pcm_start 2nd try err:%s\n", snd_strerror(nRet));
            return false;
        }
    }

    getchar();
    gRun = false;

    pthread_join(play_thread, NULL);
    pthread_join(record_thread, NULL);

    snd_pcm_close(data.playback_handle);
    snd_pcm_close(data.capture_handle);
    fclose(data.pcm_file);
    fclose(data.pcm_out_file);
    fclose(data.pcm_3a_file);
    fclose(data.pcm_ref_file);

    printf("end................... \n");

    return 0;
}

2.注意项

  1. webrtc audio processing是基于10ms为一帧进行处理的
  2. 当前版本中可以设置3A配置等级,具体3A参数调参请参考我另一篇文章音频3A一——webrtc源码3A的启用方法和具体流程
  3. 对于资源消耗,如果没有对资源特别要求,或者其他特殊情况,尽量不要追求类似于WebRtcAec_Process,WebRtcAgc_Process这种方式单独使用3A的某一个模块,而是通过audio_processing进行处理
  4. 对于时延,如果有固定时延,应该对于AEC进行设置

3.效果展示

远端参考信号
在这里插入图片描述
近端采集信号
在这里插入图片描述
回声消除后的信号
在这里插入图片描述


总结

webrtc不愧是音视频领域的顶尖,值得我们学习的东西太多了。实际上demo里对于设备的读写,也是从webrtc中摘录出来的。

如果对您有所帮助,请帮忙点个赞吧!

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

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

相关文章

Python学习第十天--处理CSV文件和JSON数据

CSV&#xff1a;简化的电子表格&#xff0c;被保存为纯文本文件 JSON&#xff1a;是一种数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也易于机器解析和生成&#xff0c;以JavaScript源代码的形式将信息保存在纯文本文件中 一、csv模块 CSV文件中的每行代表电…

Layui表格的分页下拉框新增“全部”选项

1、首先需要从后端接口获取表格的全部数据长度&#xff0c;这里以100为例。 2、根据请求到的数据进行表格的渲染。示例代码&#xff1a; let pageSize 5 let pageNo 1 let count 100 table.render({elem: XXX,done: function(res, curr, count){pageNo curr; // 将当前选…

CBK7运营安全

1 运营部门的角色 ​ prudent man、due care&#xff08;按要求执行&#xff09;VS due diligence&#xff08;承担管理者责任&#xff09; ​ 应尽关注&#xff1a;执行了负责任的动作降低了风险。 ​ 应尽职责&#xff1a;采取了所有必要的安全步骤以了解公司或个人的实际风…

AIGC引领金融大模型革命:未来已来

文章目录 金融大模型的应用场景1. **金融风险管理**2. **量化交易**3. **个性化投资建议**4. **金融欺诈检测和预防**5. **智能客户服务** 金融大模型开发面临的挑战应对策略《金融大模型开发基础与实践》亮点内容简介作者简介获取方式 在AIGC&#xff08;Artificial Intellige…

Linux操作系统2-进程控制3(进程替换,exec相关函数和系统调用)

上篇文章&#xff1a;Linux操作系统2-进程控制2(进程等待&#xff0c;waitpid系统调用&#xff0c;阻塞与非阻塞等待)-CSDN博客 本篇代码Gitee仓库&#xff1a;Linux操作系统-进程的程序替换学习 d0f7bb4 橘子真甜/linux学习 - Gitee.com 本篇重点&#xff1a;进程替换 目录 …

Java函数式编程+Lambda表达式

文章目录 函数式编程介绍纯函数Lambda表达式基础Lambda的引入传统方法1. 顶层类2. 内部类3. 匿名类 Lambda 函数式接口&#xff08;Functional Interface&#xff09;1. **函数式接口的定义**示例&#xff1a; 2. **函数式接口与Lambda表达式的关系**关联逻辑&#xff1a;示例&…

DI依赖注入详解

DI依赖注入 声明了一个成员变量&#xff08;对象&#xff09;之后&#xff0c;在该对象上面加上注解AutoWired注解&#xff0c;那么在程序运行时&#xff0c;该对象自动在IOC容器中寻找对应的bean对象&#xff0c;并且将其赋值给成员变量&#xff0c;完成依赖注入。 AutoWire…

自动化运维(k8s)之微服务信息自动抓取:namespaceName、deploymentName等全解析

前言&#xff1a;公司云原生k8s二开工程师发了一串通用性命令用来查询以下数值&#xff0c;我想着能不能将这命令写成一个自动化脚本。 起初设计的 版本一&#xff1a;开头加一条环境变量&#xff0c;执行脚本后&#xff0c;提示输入&#xff1a;需要查询的命名空间&#xff0c…

[Python/网络安全] Git漏洞之Githack工具基本安装及使用详析

前言 本文仅分享Githack工具基本安装及使用相关知识&#xff0c;不承担任何法律责任。 Git是一个非常流行的开源分布式版本控制系统&#xff0c;它被广泛用于协同开发和代码管理。许多网站和应用程序都使用Git作为其代码管理系统&#xff0c;并将其部署到生产环境中以维护其代…

解决水库安全监测难题 长期无外接电源 低功耗设备智能化监测系统

解决水库安全监测难题 长期无外接电源 低功耗设备智能化监测系统 国内某水库安全监测项目需要监测点分散&#xff0c;且无外接供电。项目年限为4年&#xff0c;不允许使用太阳能电板。因此&#xff0c;我们需要设备具备低功耗且内置电池的功能。为了满足客户的要求&#xff0c;…

蓝桥杯c++算法秒杀【6】之动态规划【上】(数字三角形、砝码称重(背包问题)、括号序列、组合数问题:::非常典型的必刷例题!!!)

下将以括号序列、组合数问题超级吧难的题为例子讲解动态规划 别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01;! ! ! ! &#xff01; 关注博主&#xff0c;更多蓝桥杯nice题目静待更新:) 动态规划 一、数字三角形 【问题描述】 上图给出了…

AD软件如何快速切换三维视图,由2D切换至3D,以及如何恢复

在Altium Designer软件中&#xff0c;切换三维视图以及恢复二维视图的操作相对简单。以下是具体的步骤&#xff1a; 切换三维视图 在PCB设计界面中&#xff0c;2D切换3D&#xff0c;快捷键按住数字键盘中的“3”即可切换&#xff1b; 快捷键ctrlf&#xff08;或者vb快捷键也…

学习threejs,使用CubeCamera相机创建反光效果

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️CubeCamera 立方体相机 二、…

长时间无事可做是个危险信号

小马加入的是技术开发部&#xff0c;专注于Java开发。团队里有一位姓隋的女同事&#xff0c;是唯一的web前端工程师&#xff0c;负责页面开发工作&#xff0c;比小马早两个月入职。公司的项目多以定制化OA系统为主&#xff0c;后端任务繁重&#xff0c;前端工作相对较少。在这样…

Llama模型分布式训练(微调)

1 常见大模型 1.1 参数量对照表 模型参数量发布时间训练的显存需求VGG-19143.68M2014~5 GB&#xff08;单 224x224 图像&#xff0c;batch_size32&#xff09;ResNet-15260.19M2015~7 GB&#xff08;单 224x224 图像&#xff0c;batch_size32&#xff09;GPT-2 117M117M2019~…

Linux 子进程 -- fork函数

子进程 什么是子进程? 子进程指的是由一个已经存在的进程&#xff08;称为父进程或父进程&#xff09;创建的进程. 如: OS (操作系统) 就可以当作是一个进程, 用来管理软硬件资源, 当我点击浏览器, 想让浏览器运行起来时, 实际上是由 OS 接收指令, 然后 OS 帮我们将浏览器运行…

DataLoade类与list ,iterator ,yield的用法

1 问题 探索DataLoader的属性&#xff0c;方法 Vscode中图标含意 list 与 iterator 的区别&#xff0c;尤其yield的用法 2 方法 知乎搜索DataLoader的属性&#xff0c;方法 pytorch基础的dataloader类是 from torch.utils.data.dataloader import Dataloader 其主要的参数如下&…

C++入门——“C++11-lambda”

引入 C11支持lambda表达式&#xff0c;lambda是一个匿名函数对象&#xff0c;它允许在函数体中直接定义。 一、初识lambda lambda的结构是&#xff1a;[ ] () -> 返回值类型 { }。从左到右依次是&#xff1a;捕捉列表 函数参数 -> 返回值类型 函数体。 以下是一段用lam…

【Linux网络编程】第二弹---Socket编程入门指南:从IP、端口号到传输层协议及编程接口全解析

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】 目录 1、Socket 编程预备 1.1、理解源 IP 和目的 IP 1.2、认识端口号 1.2.1、端口号范围划分 1.2.2、理解 &q…

《用Python实现3D动态旋转爱心模型》

简介 如果二维的爱心图案已经无法满足你的创意&#xff0c;那今天的内容一定适合你&#xff01;通过Python和matplotlib库&#xff0c;我们可以实现一个动态旋转的3D爱心模型&#xff0c;充满立体感和动感。# 实现代码&#xff08;完整代码底部名片私信&#xff09; 以下是完…