alsa-lib 插件 dsnoop 实现简单分析

alsa-lib 提供的 API 接口和 Android 等系统中使用的 tinyalsa 提供的 API 接口差异巨大,alsa-lib 的复杂度及支持的功能特性,与 tinyalsa 的有着数量级上的差异。插件机制及 dmix 和 dsnoop 等内置 PCM 插件是 alsa-lib 支持的众多高级特性的一部分。ALSA 音频内核设备驱动,在特定时刻只能运行在特定模式和一组参数下,通常只能支持由单个应用程序打开。dmix 和 dsnoop PCM 插件则提供了通过 alsa-lib API,多个应用程序共享音频硬件设备的能力,dmix PCM 插件将多个音频流混音之后送进硬件设备,dsnoop 插件将一个音频采集流分割为多个分别提供给不同的应用程序,它的工作方式与 dmix 相反,支持从多个客户端并发地读取共享采集缓冲区。这里简单分析 alsa-lib 的 PCM 插件 dsnoop 的实现。

alsa-lib 中的 PCM 设备

alsa-lib 支持的 PCM 设备类型众多,alsa-lib/include/pcm.h 中 PCM 设备类型的声明如下:

/** PCM handle */
typedef struct _snd_pcm snd_pcm_t;/** PCM type */
enum _snd_pcm_type {/** Kernel level PCM */SND_PCM_TYPE_HW = 0,/** Hooked PCM */SND_PCM_TYPE_HOOKS,/** One or more linked PCM with exclusive access to selectedchannels */SND_PCM_TYPE_MULTI,/** File writing plugin */SND_PCM_TYPE_FILE,/** Null endpoint PCM */SND_PCM_TYPE_NULL,/** Shared memory client PCM */SND_PCM_TYPE_SHM,/** INET client PCM (not yet implemented) */SND_PCM_TYPE_INET,/** Copying plugin */SND_PCM_TYPE_COPY,/** Linear format conversion PCM */SND_PCM_TYPE_LINEAR,/** A-Law format conversion PCM */SND_PCM_TYPE_ALAW,/** Mu-Law format conversion PCM */SND_PCM_TYPE_MULAW,/** IMA-ADPCM format conversion PCM */SND_PCM_TYPE_ADPCM,/** Rate conversion PCM */SND_PCM_TYPE_RATE,/** Attenuated static route PCM */SND_PCM_TYPE_ROUTE,/** Format adjusted PCM */SND_PCM_TYPE_PLUG,/** Sharing PCM */SND_PCM_TYPE_SHARE,/** Meter plugin */SND_PCM_TYPE_METER,/** Mixing PCM */SND_PCM_TYPE_MIX,/** Attenuated dynamic route PCM (not yet implemented) */SND_PCM_TYPE_DROUTE,/** Loopback server plugin (not yet implemented) */SND_PCM_TYPE_LBSERVER,/** Linear Integer <-> Linear Float format conversion PCM */SND_PCM_TYPE_LINEAR_FLOAT,/** LADSPA integration plugin */SND_PCM_TYPE_LADSPA,/** Direct Mixing plugin */SND_PCM_TYPE_DMIX,/** Jack Audio Connection Kit plugin */SND_PCM_TYPE_JACK,/** Direct Snooping plugin */SND_PCM_TYPE_DSNOOP,/** Direct Sharing plugin */SND_PCM_TYPE_DSHARE,/** IEC958 subframe plugin */SND_PCM_TYPE_IEC958,/** Soft volume plugin */SND_PCM_TYPE_SOFTVOL,/** External I/O plugin */SND_PCM_TYPE_IOPLUG,/** External filter plugin */SND_PCM_TYPE_EXTPLUG,/** Mmap-emulation plugin */SND_PCM_TYPE_MMAP_EMUL,SND_PCM_TYPE_LAST = SND_PCM_TYPE_MMAP_EMUL
};

这些 PCM 设备类型中,只有 SND_PCM_TYPE_HW 类型会与内核及音频硬件设备交互,会访问 PCM 设备文件,即 PCM 硬件设备。其它包括 SND_PCM_TYPE_DMIXSND_PCM_TYPE_DSNOOP 等在内的 PCM 设备类型都是虚拟设备,它们为应用程序提供 PCM 设备操作接口,但并不直接访问 PCM 设备文件。

alsa-lib 用 snd_pcm_t 对象描述 PCM 设备,不同类型的 PCM 设备对应于不同的 snd_pcm_t 对象实现。在 alsa-lib 中,PCM 设备即 snd_pcm_t 对象,snd_pcm_t 对象即 PCM 设备。snd_pcm_t 类型的定义 (位于 alsa-lib/src/pcm/pcm_local.h) 如下:

typedef struct _snd_pcm_rbptr {snd_pcm_t *master;volatile snd_pcm_uframes_t *ptr;int fd;off_t offset;int link_dst_count;snd_pcm_t **link_dst;void *private_data;void (*changed)(snd_pcm_t *pcm, snd_pcm_t *src);
} snd_pcm_rbptr_t;typedef struct _snd_pcm_channel_info {unsigned int channel;void *addr;			/* base address of channel samples */unsigned int first;		/* offset to first sample in bits */unsigned int step;		/* samples distance in bits */enum { SND_PCM_AREA_SHM, SND_PCM_AREA_MMAP, SND_PCM_AREA_LOCAL } type;union {struct {struct snd_shm_area *area;int shmid;} shm;struct {int fd;off_t offset;} mmap;} u;char reserved[64];
} snd_pcm_channel_info_t;typedef struct {int (*close)(snd_pcm_t *pcm);int (*nonblock)(snd_pcm_t *pcm, int nonblock); /* always locked */int (*async)(snd_pcm_t *pcm, int sig, pid_t pid);int (*info)(snd_pcm_t *pcm, snd_pcm_info_t *info);int (*hw_refine)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);int (*hw_params)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);int (*hw_free)(snd_pcm_t *pcm);int (*sw_params)(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); /* always locked */int (*channel_info)(snd_pcm_t *pcm, snd_pcm_channel_info_t *info);void (*dump)(snd_pcm_t *pcm, snd_output_t *out);int (*mmap)(snd_pcm_t *pcm);int (*munmap)(snd_pcm_t *pcm);snd_pcm_chmap_query_t **(*query_chmaps)(snd_pcm_t *pcm);snd_pcm_chmap_t *(*get_chmap)(snd_pcm_t *pcm);int (*set_chmap)(snd_pcm_t *pcm, const snd_pcm_chmap_t *map);
} snd_pcm_ops_t;typedef struct {int (*status)(snd_pcm_t *pcm, snd_pcm_status_t *status); /* locked */int (*prepare)(snd_pcm_t *pcm); /* locked */int (*reset)(snd_pcm_t *pcm); /* locked */int (*start)(snd_pcm_t *pcm); /* locked */int (*drop)(snd_pcm_t *pcm); /* locked */int (*drain)(snd_pcm_t *pcm); /* need own locking */int (*pause)(snd_pcm_t *pcm, int enable); /* locked */snd_pcm_state_t (*state)(snd_pcm_t *pcm); /* locked */int (*hwsync)(snd_pcm_t *pcm); /* locked */int (*delay)(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp); /* locked */int (*resume)(snd_pcm_t *pcm); /* need own locking */int (*link)(snd_pcm_t *pcm1, snd_pcm_t *pcm2);int (*link_slaves)(snd_pcm_t *pcm, snd_pcm_t *master);int (*unlink)(snd_pcm_t *pcm);snd_pcm_sframes_t (*rewindable)(snd_pcm_t *pcm); /* locked */snd_pcm_sframes_t (*rewind)(snd_pcm_t *pcm, snd_pcm_uframes_t frames); /* locked */snd_pcm_sframes_t (*forwardable)(snd_pcm_t *pcm); /* locked */snd_pcm_sframes_t (*forward)(snd_pcm_t *pcm, snd_pcm_uframes_t frames); /* locked */snd_pcm_sframes_t (*writei)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size); /* need own locking */snd_pcm_sframes_t (*writen)(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size); /* need own locking */snd_pcm_sframes_t (*readi)(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size); /* need own locking */snd_pcm_sframes_t (*readn)(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size); /* need own locking */snd_pcm_sframes_t (*avail_update)(snd_pcm_t *pcm); /* locked */snd_pcm_sframes_t (*mmap_commit)(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t size); /* locked */int (*htimestamp)(snd_pcm_t *pcm, snd_pcm_uframes_t *avail, snd_htimestamp_t *tstamp); /* locked */int (*poll_descriptors_count)(snd_pcm_t *pcm); /* locked */int (*poll_descriptors)(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space); /* locked */int (*poll_revents)(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents); /* locked */int (*may_wait_for_avail_min)(snd_pcm_t *pcm, snd_pcm_uframes_t avail);int (*mmap_begin)(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames); /* locked */
} snd_pcm_fast_ops_t;struct _snd_pcm {void *open_func;char *name;snd_pcm_type_t type;snd_pcm_stream_t stream;int mode;long minperiodtime;		/* in us */int poll_fd_count;int poll_fd;unsigned short poll_events;int setup: 1,compat: 1;snd_pcm_access_t access;	/* access mode */snd_pcm_format_t format;	/* SND_PCM_FORMAT_* */snd_pcm_subformat_t subformat;	/* subformat */unsigned int channels;		/* channels */unsigned int rate;		/* rate in Hz */snd_pcm_uframes_t period_size;unsigned int period_time;	/* period duration */snd_interval_t periods;snd_pcm_tstamp_t tstamp_mode;	/* timestamp mode */snd_pcm_tstamp_type_t tstamp_type;	/* timestamp type */unsigned int period_step;snd_pcm_uframes_t avail_min;	/* min avail frames for wakeup */int period_event;snd_pcm_uframes_t start_threshold;snd_pcm_uframes_t stop_threshold;snd_pcm_uframes_t silence_threshold;	/* Silence filling happens whennoise is nearest than this */snd_pcm_uframes_t silence_size;	/* Silence filling size */snd_pcm_uframes_t boundary;	/* pointers wrap point */unsigned int info;		/* Info for returned setup */unsigned int msbits;		/* used most significant bits */unsigned int rate_num;		/* rate numerator */unsigned int rate_den;		/* rate denominator */unsigned int hw_flags;		/* actual hardware flags */snd_pcm_uframes_t fifo_size;	/* chip FIFO size in frames */snd_pcm_uframes_t buffer_size;snd_interval_t buffer_time;unsigned int sample_bits;unsigned int frame_bits;snd_pcm_rbptr_t appl;snd_pcm_rbptr_t hw;snd_pcm_uframes_t min_align;unsigned int mmap_rw: 1;	/* use always mmapped buffer */unsigned int mmap_shadow: 1;	/* don't call actual mmap,* use the mmaped buffer of the slave*/unsigned int donot_close: 1;	/* don't close this PCM */unsigned int own_state_check:1; /* plugin has own PCM state check */snd_pcm_channel_info_t *mmap_channels;snd_pcm_channel_area_t *running_areas;snd_pcm_channel_area_t *stopped_areas;const snd_pcm_ops_t *ops;const snd_pcm_fast_ops_t *fast_ops;snd_pcm_t *op_arg;snd_pcm_t *fast_op_arg;void *private_data;struct list_head async_handlers;
#ifdef THREAD_SAFE_APIint need_lock;		/* true = this PCM (plugin) is thread-unsafe,* thus it needs a lock.*/int lock_enabled;	/* thread-safety lock is enabled on the system;* it's set depending on $LIBASOUND_THREAD_SAFE.*/pthread_mutex_t lock;
#endif
};

snd_pcm_t 对象的 const snd_pcm_ops_t *opsconst snd_pcm_fast_ops_t *fast_ops 成员分别指向由众多函数指针组成的结构,它们决定着特定类型 PCM 设备各个操作的行为。

打开 dsnoop PCM 设备

alsa-lib 的 snd_pcm_open() 接口用于打开 PCM 设备,这个接口的声明 (位于 alsa-lib/include/pcm.h) 如下:

int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);

snd_pcm_open() 接口各个参数的含义如下:

  • pcmp:返回的 snd_pcm_t 对象,PCM 句柄。
  • name:PCM 句柄的 ASCII 标识符,用于指定要打开的 PCM 设备,如 dsnoopdsnoop:1
  • stream:流的类型,播放流和录制流分别为 SND_PCM_STREAM_PLAYBACKSND_PCM_STREAM_CAPTURE
  • mode:打开模式,可选值为 SND_PCM_NONBLOCKSND_PCM_ASYNC

arecord 命令可以指定通过 dsnoop 虚拟设备采集音频数据,如:

$ arecord -f S16_LE -r 48000 -c 2 -D dsnoop foobar.wav

arecord 命令支持设置 dsnoop 虚拟设备绑定到非默认的声卡 0,如绑定到硬件声卡 1 采集音频数据:

$ arecord -f S16_LE -r 48000 -c 2 -D dsnoop:1 foobar.wav

arecord 命令的 -D 参数,将在打开 PCM 设备时,作为 snd_pcm_open() 接口的 name 参数,指定要打开的 PCM 设备。

snd_pcm_open() 函数定义 (位于 alsa-lib/src/pcm/pcm.c) 如下:

/*** \brief Opens a PCM* \param pcmp Returned PCM handle* \param name ASCII identifier of the PCM handle* \param stream Wanted stream* \param mode Open mode (see #SND_PCM_NONBLOCK, #SND_PCM_ASYNC)* \return 0 on success otherwise a negative error code*/
int snd_pcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode)
{snd_config_t *top;int err;assert(pcmp && name);if (_snd_is_ucm_device(name)) {name = uc_mgr_alibcfg_by_device(&top, name);if (name == NULL)return -ENODEV;} else {err = snd_config_update_ref(&top);if (err < 0)return err;}err = snd_pcm_open_noupdate(pcmp, top, name, stream, mode, 0);snd_config_unref(top);return err;
}

UCM 设备是名称以 “_ucm” 开头的设备,这里忽略 UCM 设备的情况。snd_pcm_open() 函数的执行过程如下:

  1. 调用 snd_config_update_ref() 函数更新 ALSA 顶层配置 snd_config 并获取它的引用。
  2. 调用 snd_pcm_open_noupdate() 函数根据 ALSA 顶层配置打开 PCM 设备。
  3. 释放 ALSA 顶层配置 snd_config 的引用。

更新 ALSA 顶层配置的 snd_config_update_ref() 函数定义 (位于 alsa-lib/src/conf.c) 如下:

struct _snd_config {char *id;snd_config_type_t type;int refcount; /* default = 0 */union {long integer;long long integer64;char *string;double real;const void *ptr;struct {struct list_head fields;bool join;} compound;} u;struct list_head list;snd_config_t *parent;int hop;
};. . . . . .
const char *snd_config_topdir(void)
{static char *topdir;if (!topdir) {topdir = getenv("ALSA_CONFIG_DIR");if (!topdir || *topdir != '/' || strlen(topdir) >= PATH_MAX)topdir = ALSA_CONFIG_DIR;}return topdir;
}. . . . . .snd_config_t *snd_config = NULL;#ifndef DOC_HIDDEN
struct finfo {char *name;dev_t dev;ino64_t ino;time_t mtime;
};struct _snd_config_update {unsigned int count;struct finfo *finfo;
};
#endif /* DOC_HIDDEN */static snd_config_update_t *snd_config_global_update = NULL;. . . . . .
int snd_config_update_r(snd_config_t **_top, snd_config_update_t **_update, const char *cfgs)
{int err;const char *configs, *c;unsigned int k;size_t l;snd_config_update_t *local;snd_config_update_t *update;snd_config_t *top;assert(_top && _update);top = *_top;update = *_update;configs = cfgs;if (!configs) {configs = getenv(ALSA_CONFIG_PATH_VAR);if (!configs || !*configs) {const char *topdir = snd_config_topdir();char *s = alloca(strlen(topdir) +strlen("alsa.conf") + 2);sprintf(s, "%s/alsa.conf", topdir);configs = s;}}for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) {c += l;k++;if (!*c)break;c++;}if (k == 0) {local = NULL;goto _reread;}local = (snd_config_update_t *)calloc(1, sizeof(snd_config_update_t));if (!local)return -ENOMEM;local->count = k;local->finfo = calloc(local->count, sizeof(struct finfo));if (!local->finfo) {free(local);return -ENOMEM;}for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) {char name[l + 1];memcpy(name, c, l);name[l] = 0;err = snd_user_file(name, &local->finfo[k].name);if (err < 0)goto _end;c += l;k++;if (!*c)break;c++;}for (k = 0; k < local->count; ++k) {struct stat64 st;struct finfo *lf = &local->finfo[k];if (stat64(lf->name, &st) >= 0) {lf->dev = st.st_dev;lf->ino = st.st_ino;lf->mtime = st.st_mtime;} else {SNDERR("Cannot access file %s", lf->name);free(lf->name);memmove(&local->finfo[k], &local->finfo[k+1], sizeof(struct finfo) * (local->count - k - 1));k--;local->count--;}}if (!update)goto _reread;if (local->count != update->count)goto _reread;for (k = 0; k < local->count; ++k) {struct finfo *lf = &local->finfo[k];struct finfo *uf = &update->finfo[k];if (strcmp(lf->name, uf->name) != 0 ||lf->dev != uf->dev ||lf->ino != uf->ino ||lf->mtime != uf->mtime)goto _reread;}err = 0;_end:if (err < 0) {if (top) {snd_config_delete(top);*_top = NULL;}if (update) {snd_config_update_free(update);*_update = NULL;}}if (local)snd_config_update_free(local);return err;_reread:*_top = NULL;*_update = NULL;if (update) {snd_config_update_free(update);update = NULL;}if (top) {snd_config_delete(top);top = NULL;}err = snd_config_top(&top);if (err < 0)goto _end;if (!local)goto _skip;for (k = 0; k < local->count; ++k) {snd_input_t *in;err = snd_input_stdio_open(&in, local->finfo[k].name, "r");if (err >= 0) {err = snd_config_load(top, in);snd_input_close(in);if (err < 0) {SNDERR("%s may be old or corrupted: consider to remove or fix it", local->finfo[k].name);goto _end;}} else {SNDERR("cannot access file %s", local->finfo[k].name);}}_skip:err = snd_config_hooks(top, NULL);if (err < 0) {SNDERR("hooks failed, removing configuration");goto _end;}*_top = top;*_update = local;return 1;
}. . . . . .
/*** \brief Updates #snd_config and takes its reference.* \return 0 if #snd_config was up to date, 1 if #snd_config was*         updated, otherwise a negative error code.** Unlike #snd_config_update, this function increases a reference counter* so that the obtained tree won't be deleted until unreferenced by* #snd_config_unref.** This function is supposed to be thread-safe.*/
int snd_config_update_ref(snd_config_t **top)
{int err;if (top)*top = NULL;snd_config_lock();err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL);if (err >= 0) {if (snd_config) {if (top) {snd_config->refcount++;*top = snd_config;}} else {err = -ENODEV;}}snd_config_unlock();return err;
}

snd_config_update_ref() 函数根据从顶层配置文件读取的内容更新配置树,它是 snd_config_update_r() 函数的线程安全封装。snd_config_update_r() 函数的 cfgs 参数接受以冒号 (‘:’) 分割的顶层配置文件名列表,在snd_pcm_open()/snd_config_update_ref() 的场景中,cfgs 参数为空,取默认顶层配置文件。

snd_config_update_r() 函数按一定的优先级在多个目录中查找默认的 ALSA 顶层配置文件:

  1. 环境变量 ALSA_CONFIG_PATH 指向的 ALSA 顶层配置文件路径列表。
  2. 顶层配置目录下的 alsa.conf 文件。顶层配置目录按一定的优先级来查找:
    • 环境变量 ALSA_CONFIG_DIR 指向的目录。
    • /usr/share/alsa

通常默认的 ALSA 顶层配置文件为 /usr/share/alsa/alsa.conf。对于 ALSA 顶层配置文件的路径,snd_config_update_r() 函数支持 “~/” 的形式,它会通过 snd_user_file() 函数获得用户目录路径,并进而获得配置文件的绝对路径。

alsa-lib 用 snd_config_update_t 对象描述 ALSA 顶层配置文件集,并用 finfo 对象描述其中的一个配置文件。snd_config_update_r() 函数在获得 ALSA 顶层配置文件路径列表之后,将它们转换为 snd_config_update_t 对象的表示,这会处理文件路径中的 “~/”,并过滤掉无法访问的文件。

snd_config_update_r() 函数的 _update 参数用来传入老的配置,并用来传出新加载的配置。snd_config_update_r() 函数对比老的配置的配置文件信息和新获取的配置文件的信息,如果两者存在差异(包括文件路径、文件所在的设备号、文件的 inode 号和最近修改时间等方面),则加载新的配置,否则释放新创建的 snd_config_update_t 对象并返回。

加载新配置时,如果通过 _top_update 参数传入的老的配置已经存在,它们会先被释放掉。随后,通过 snd_config_top() 函数重新为 ALSA 顶层配置分配 snd_config_t 对象,通过 snd_input_stdio_open()/snd_config_load()/snd_input_close() 这组函数逐个加载配置文件,通过 snd_config_hooks() 函数处理 hooks。最后,通过传入的 _top_update 参数返回加载的配置。

ALSA 配置文件支持声明 hook,即一个函数,如 ALSA 顶层配置文件 /usr/share/alsa/alsa.conf 中声明的 hook:

@hooks [{func loadfiles ["/etc/alsa/conf.d""/etc/asound.conf""~/.asoundrc"]errors false}
]

hooksfunc 配置指定 hook 名称,而不直接对应 alsa-lib 内部的函数名,hook_func 用于指定 hook 的函数名,如 /usr/share/alsa/alsa.conf.d/pulse.conf 配置文件:

hook_func.pulse_load_if_running {lib "libasound_module_conf_pulse.so"func "conf_pulse_hook_load_if_running"
}@hooks [{func pulse_load_if_runningfiles ["/usr/share/alsa/pulse-alsa.conf"]errors false}
]

当没有为 hook 指定 hook 函数名时,hook 函数名将为 snd_config_hook_[func],如 snd_config_hook_loadsnd_config_hook_load() 函数将在 snd_config_hooks() 函数处理 hooks 过程中被调用。

如在 snd_config_update_ref() 函数中看到的,加载的 ALSA 顶层配置被保存在全局的 snd_configsnd_config_global_update 对象中。

ALSA 顶层配置文件的内容,如 /usr/share/alsa/alsa.conf 文件。关于 ALSA 配置文件更详细的信息,可以参考它的 WiKi 页面 Asoundrc。通常 /usr/share/alsa/alsa.conf 配置文件是 ALSA 配置文件的主入口点,它负责包含系统上 .asoundrc-format-type 格式文件的完整列表。

回到 snd_pcm_open() 函数,它在加载了 ALSA 顶层配置之后,调用 snd_pcm_open_noupdate() 函数打开 PCM 音频设备。snd_pcm_open_noupdate() 函数定义 (位于 alsa-lib/src/pcm/pcm.c) 如下:

static const char *const build_in_pcms[] = {"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat","linear", "meter", "mulaw", "multi", "null", "empty", "plug", "rate", "route", "share","shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",NULL
};static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name,snd_config_t *pcm_root, snd_config_t *pcm_

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pywinauto,一款 Win 自动化利器!

1.安装 pywinauto是一个用于自动化Python模块&#xff0c;适合Windows系统的软件&#xff08;GUI&#xff09;&#xff0c;可以通过Pywinauto遍历窗口&#xff08;对话框&#xff09;和窗口里的控件&#xff0c;也可以控制鼠标和键盘输入&#xff0c;所以它能做的事情比之前介…

论文速读:基于渐进式转移的无监督域自适应舰船检测

这篇文章的标题是《Unsupervised Domain Adaptation Based on Progressive Transfer for Ship Detection: From Optical to SAR Images》基于渐进式转移的无监督域自适应舰船检测:从光学图像到SAR图像&#xff0c;作者是Yu Shi等人。文章发表在IEEE Transactions on Geoscience…

ARTS Week 43

Algorithm 本周的算法题为 1822. 数组元素积的符号 已知函数 signFunc(x) 将会根据 x 的正负返回特定值&#xff1a; 如果 x 是正数&#xff0c;返回 1 。 如果 x 是负数&#xff0c;返回 -1 。 如果 x 是等于 0 &#xff0c;返回 0 。 给你一个整数数组 nums 。令 product 为数…

《神经网络》—— 循环神经网络RNN(Recurrent Neural Network)

文章目录 一、RNN 简单介绍二、RNN 基本结构1.隐藏中的计算2.输出层的计算3.循环 三、RNN 优缺点1.优点2.缺点 一、RNN 简单介绍 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一种用于处理序列数据的神经网络架构。 与传统的前馈神经网络&#xff08…

现代身份和访问管理 IAM 如何降低风险

您的公司是否仍在使用 1998 年时的身份管理系统&#xff1f;仅凭用户名和密码就能登录本地网络并访问几乎所有资源吗&#xff1f; 虽然大多数企业已经转向现代身份和访问管理(IAM) 平台&#xff0c;但成千上万的企业和其他组织仍然依赖过时的用户名/密码系统。 如果你看一下传…

微知-如何临时设置Linux系统时间?(date -s “2024-10-08 22:55:00“, time, hwclock, timedatectl)

背景 在tar解压包的时候经常出现时间不对&#xff0c;可以临时用date命令修改一下&#xff0c;也可以其他&#xff0c;本文主要介绍临时修改的方法 date命令修改 sudo date -s "2024-10-08 22:55:00"其他查看和修改的命令 本文只记录查看方式&#xff0c;修改的暂…

分享几个国外SSL证书提供商网站

国外SSL证书提供商 众所周知兼容性高的SSL证书肯定是在国外申请的&#xff0c;主要确保SSL证书的安全性的同时&#xff0c;对于安全标准在国外相比而言更成熟&#xff0c;保护程度也比较高。 另方面对需要申请的域名没有限制&#xff0c;可选性SSL证书类型种类比较多&#xf…

【C++打怪之路Lv7】-- 模板初阶

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文(平均质量分82)&#…

【图论】迪杰特斯拉算法

文章目录 迪杰特斯拉算法主要特点基本思想算法步骤示例 实现迪杰斯特拉算法基本步骤算法思路 总结 迪杰特斯拉算法 迪杰特斯拉算法是由荷兰计算机科学家艾兹赫尔迪杰特斯拉&#xff08;Edsger W. Dijkstra&#xff09;在1956年提出的&#xff0c;用于解决单源最短路径问题的经…

web开发(1)-基础

这是对b站课程的总结&#xff0c;后续可能会继续更 01 前后端分离介绍_哔哩哔哩_bilibili01 前后端分离介绍是Web应用开发-后端基础-基于Springboot框架的第1集视频&#xff0c;该合集共计29集&#xff0c;视频收藏或关注UP主&#xff0c;及时了解更多相关视频内容。https://w…

信息安全工程师(39)防火墙防御体系结构类型

前言 防火墙防御体系结构类型多样化&#xff0c;每种类型都针对不同的安全需求和应用场景&#xff0c;提供不同层次的保护。 一、传统防火墙系统 包过滤防火墙 原理&#xff1a;通过检查进出网络数据包的头信息&#xff08;如源IP地址、目的IP地址、源端口、目的端口和协议等&a…

用langchain+streamlit应用RAG实现个人知识库助手搭建

RAG原理概述 RAG&#xff08;Retrieval-Augmented Generation&#xff09; 是一种结合了信息检索和生成式人工智能技术的模型架构&#xff0c;旨在让模型生成更有根据和更准确的回答。通俗来讲&#xff0c;它让模型不只是凭借自己的“记忆”&#xff08;预训练数据&#xff09…

Python | Leetcode Python题解之第456题132模式

题目&#xff1a; 题解&#xff1a; class Solution:def find132pattern(self, nums: List[int]) -> bool:candidate_i, candidate_j [-nums[0]], [-nums[0]]for v in nums[1:]:idx_i bisect.bisect_right(candidate_i, -v)idx_j bisect.bisect_left(candidate_j, -v)if…

如何实现 C/C++ 与 Python 的通信?

在现代编程中&#xff0c;C/C与Python的通信已经成为一种趋势&#xff0c;尤其是在需要高性能和灵活性的场景中。本文将深入探讨如何实现这两者之间的互通&#xff0c;包括基础和高级方法&#xff0c;帮助大家在混合编程中游刃有余。 C/C 调用 Python&#xff08;基础篇&#…

APP自动化搭建与应用

APP自动化环境搭建 用于做APP端UI自动化&#xff0c;adb连接手机设备。 需要的工具java编辑器&#xff1a;jdk、Android-sdk软件开发工具组、appium的python客户端、nodes.js、夜神模拟器、apk包、uiautomatorviewer 第一步&#xff1a;安装sdk&#xff0c;里面包含建立工具bu…

QD1-P6 HTML常用标签:列表

本节视频 https://www.bilibili.com/video/BV1n64y1U7oj?p6 ‍ 本节学习HTML列表标签。HTML 列表有多种形式&#xff0c;最重要的有两种&#xff1a; 有序列表无序列表 一、有序列表 1.1 写法 <ol><li>首先</li><li>其次</li><li>最…

Shell入门基础学习笔记

目录 第1章 Shell概述 第2章 Shell解析器 第3章 Shell脚本入门 第4章 Shell中的变量 4.1 系统变量 4.2 自定义变量 4.3 特殊变量&#xff1a;$n 4.4 特殊变量&#xff1a;$# 4.5 特殊变量&#xff1a;$*、$ 4.6 特殊变量&#xff1a;$&#xff1f; 第5章 运算符 …

数据结构-4.5.KMP算法(旧版上)-朴素模式匹配算法的优化

朴素模式匹配算法最坏的情况&#xff1a; 一.实例&#xff1a; 第一轮匹配失败&#xff0c;开始下一轮的匹配&#xff1a; 不断的操作&#xff0c;最终匹配成功&#xff1a; 如上述图片所述&#xff0c;朴素模式匹配算法会导致时间开销增加&#xff0c; 优化思路&#xff1a;主…

Prometheus之Pushgateway使用

Pushgateway属于整个架构图的这一部分 The Pushgateway is an intermediary service which allows you to push metrics from jobs which cannot be scraped. The Prometheus Pushgateway exists to allow ephemeral and batch jobs to expose their metrics to Prometheus. S…

手撕数据结构 —— 顺序表(C语言讲解)

目录 1.顺序表简介 什么是顺序表 顺序表的分类 2.顺序表的实现 SeqList.h中接口总览 具体实现 顺序表的定义 顺序表的初始化 顺序表的销毁 打印顺序表 ​编辑 检查顺序表的容量 尾插 尾删 ​编辑 头插 头删 查找 在pos位置插入元素 删除pos位置的值 ​…