ALSA系统简析

article/2025/10/3 3:01:04

一 音频架构

如图所示 是 嵌入式系统的音频连接

音频编解码器将数字音频信号 转换成 扬声器播放所需要的模拟声音信号。而通过麦克风时,则执行相反的过程。

数字音频信号通过 PCM技术对模拟信号以某个比特率采样得到的,编解码器的任务就是以支持的PCM比特率 采样和记录音频,并能以不同的 PCM比特率播放采样的音频。
编解码器的任务:

1 即对PCM音频信号进行D/A转换,把数字的音频信号转换为模拟信号,输送给耳机或音响,即播放声音;

2 对麦克风或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号,即录音;

3 对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等

AC97 和 I2S总线是连接音频控制器和编解码器的工业标准接口的一个例子。
下图的嵌入式设备 使用I2S 将音频数据发送给编解码器。对编解码器的I/O寄存器的编程通过I2C总线进行。
在这里插入图片描述

二 ALSA概述

ALSA(Advanced Linux Sound Architecture)是linux上主流的音频结构,在没有出现ALSA架构之前,一直使用的是OSS(Open Sound System)音频架构主要的区别就是在OSS架构下,App访问底层是直接通过Sound设备节点访问的。而在ALSA音频架构下,App是通过ALSA提供的alsa-lib库访问底层硬件的操作,不再访问Sound设备节点了。这样做的好处可以简化App实现的难度。

**Linux ALSA子系统 : **
在这里插入图片描述

1 声音核心部分: 核心层有一定程度的隔离,是的声音子系统的每个部件都与其他部件无关,并且提供了向应用层输出 ALSA API 的重要功能。上图中显示的 /dev/snd/* 设备节点是由 ALSA核心层创建和管理的:

/dev/snd/controlC0 是一个控制节点,应用程序用它来控制音量等

/dev/snd/pcmC0D0p 是播放设备,设备名最后一个字符p表示播放

/dev/snd/pcmC0D0c 是录音设备,设备名最后一个字符c表示捕获

这些设备名中,C后的整数是卡号,D后的是设备号。

2 音频控制驱动程序:与控制器硬件相关的音频控制器驱动程序

3 音频编解码接口 : 协助控制器和编解码器之间通信的音频编解码接口

4 OSS模拟层 :在OSS应用程序和由ALSA启用的内核之间充当通道 的OSS模拟层。该层输出 /dev 节点,这些节点(如 /dev/dsp /dev/adsp /dev/mixer )允许OSS应用程序不用修改就在ALSA上运行。OSS节点 /dev/dsp 映射成 ALSA 节点 /dev/snd/pcmC0D0* , /dev/adsp 对应 /dev/snd/pcmC0D1* , /dev/mixer 对应 /dev/snd/controlC0

5 procfs 和 sysfs 接口实现,用于通过 /proc/asound 和 /sys/class/sound 获取信息

cd /proc/asound 
lscard0  //声卡0
cards  //系统可用的声卡
devices  //alsa下所有注册的声卡子设备,包括 control pcm timer seq等等
version //ALSA版本信息
...等等

6 用户空间 ALSA 库 alsa-lib。他提供了 libasound.so 对象。这个库通过提供一些访问 ALSA驱动程序的封装例程,使ALSA应用程序编写更加方便。

7 alsa-utils工具包,包括 alsamixer,amixer, alsactl, aplay等工具。alsamixer,amixer 用于改变音频信号(如 音频线输入,音频线输出,麦克风信号等)的音量。alsactl用于控制ALSA驱动程序的设置, aplay用于在ALSA上播放音频。

三 ALSA驱动

1 关于声卡设备抽象 和 声卡中的功能逻辑设备抽象

内核中用 snd_card来描述一个声卡设备。

struct snd_card {int number;         //声卡编号,因为系统中可能有多张声卡,需要对每一个声卡编号char id[16];        //声卡ID标识符char driver[16];       //声卡驱动名
...//链表,声卡的所有逻辑设备都会挂入该链表struct list_head devices;   //声卡的控制逻辑设备对应的devicestruct device ctl_dev;		/* control device */void *private_data;     //声卡的私有数据,可以在创建声卡时通过参数指定数据的大小 
...struct list_head ctl_files;     //若干个进程打开"/dev/snd/controlCx"时生成ctl_file的链 表头。一般用于pcm有多个substream时配置选用哪个substream.struct list_head controls;  //通过设备节点"/dev/snd/controlCx"提供给用户空间的controls. 常用于控制音量,音频流的通路等struct device *dev;     //此声卡的父设备,通过何种方式挂载声卡。如usb/pci/platform…struct device *card_dev;    //声卡对应的device,用于注册到设备模型
};

某款Codec内部结构图如下:

在这里插入图片描述
从接口上看,有:
INL-连接音响设备,输入模拟声音信号
MIC-连接麦克风,输入模拟语音信号
DMIC-连接数字麦克风,输入数字音频信号
I2S/PCM-连接CPU,输入数字音频信号
CONTROL INTERFACE-输入控制信号
HP-连接耳机,输出模拟声音信号
SPKOUT-连接喇叭,输出模拟声音信号
LINEOUT-连接功放设备,输出模拟声音信号

从内部看,有:
MIXER-混音器,多路模拟声音混合输出
PGA-放大器,调节音量
ADC-模拟信号转数字信号
DAC-数字信号转模拟信号
Filter-滤波器,把数字信号转换到不同的频率,控制播放快慢

ALSA Core把这些内部功能部件抽象成一个个的逻辑设备,用 struct snd_device结构体描述一个声卡的逻辑设备,结构体定义如下:

struct snd_device {struct list_head list;		    /* 用于挂入snd_card的devices链表 */struct snd_card *card;		    /* 指向所属的snd_card */enum snd_device_state state;	/* state of the device */enum snd_device_type type;	  	/* 逻辑设备的类型 */void *device_data;		        /* device structure */struct snd_device_ops *ops;	  	/* 操作集 */
};struct snd_device_ops {//释放逻辑设备时,会调用dev_free回调int (*dev_free)(struct snd_device *dev);//注册逻辑设备时,会调用dev_register回调int (*dev_register)(struct snd_device *dev);//注销逻辑设备时,会调用dev_disconnect回调int (*dev_disconnect)(struct snd_device *dev);
};

逻辑设备的类型如下种类:

enum snd_device_type {SNDRV_DEV_LOWLEVEL,SNDRV_DEV_CONTROL, //控制逻辑设备,对应与Codec里面的控制部件,控制音量大小、快慢等SNDRV_DEV_INFO,SNDRV_DEV_BUS,SNDRV_DEV_CODEC,SNDRV_DEV_PCM,    //pcm逻辑设备,对应与Codec里面的ADC或DAC,实现录音或播放声音的功能SNDRV_DEV_COMPRESS,SNDRV_DEV_RAWMIDI,SNDRV_DEV_TIMER,SNDRV_DEV_SEQUENCER,SNDRV_DEV_HWDEP,SNDRV_DEV_JACK,
};

这里说明一下 pcm逻辑设备 与 控制逻辑设备。

pcm逻辑设备,所代表的逻辑功能就是 录音和播放

playback,把用户空间的应用程序发过来的PCM数据进行D/A转换,转化为人耳可以辨别的模拟音频。
capture,把麦克风或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号。

控制逻辑设备,所代表的逻辑功能就是如音量调节等控制功能,主要让用户空间的应用程序可以访问和控制音频codec芯片中的多路开关,滑动控件等,进行音量控制、静音等。

所以综上所述。snd_card有一devices链表,声卡所有的逻辑设备会挂入该链表。

在这里插入图片描述

2 ALSA驱动
主要工作:

1 创建声卡snd_card的一个实例;
2 创建声卡snd_card的功能部件(逻辑设备)snd_device ,例如PCM、Control等;;
3 设置 逻辑设备 的操作方法
4 注册声卡设备snd_card;

1 创建声卡实例

/* 参数说明:* parent,声卡的父设备* idx,声卡的编号,如何传入-1,则让系统分配一个没占用的编号* xid,字符串,声卡标识符,alsa-lib通过标识符找到对应的snd_card* extra_size,私有数据大小,保存在返回的 snd_card结构体的 private_data字段,额外分配的内存由snd_card->private_data指向* card_ret,保存创建的snd_card实例的指针*/int snd_card_new(struct device *parent, int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)

与之相对的是 snd_card_free() 将snd_card 从ALSA 架构中释放。

2 创建 pcm 逻辑功能设备 实例

/* card,声卡实例* id,pcm设备的标识符* device,表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始* playback_count,表示该pcm将会有几个playback substream  即 支持的播放流数目* capture_count,表示该pcm将会有几个capture substream 即 支持的录音流数目*/int snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count, struct snd_pcm **rpcm)
{return _snd_pcm_new(card, id, device, playback_count, capture_count,false, rpcm);
}static int _snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count, bool internal,struct snd_pcm **rpcm)
{struct snd_pcm *pcm;int err;//不同的逻辑设备 操作集不同static struct snd_device_ops ops = {.dev_free = snd_pcm_dev_free,.dev_register =	snd_pcm_dev_register,.dev_disconnect = snd_pcm_dev_disconnect,};
...//创建一个snd_pcm pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);if (!pcm)return -ENOMEM;pcm->card = card;//所属声卡pcm->device = device;//设备索引
...//创建playback的substreamserr = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count);//创建capture的substreamserr = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);//创建snd_device,并挂入snd_card的devices链表err = snd_device_new(card, SNDRV_DEV_PCM, pcm,internal ? &internal_ops : &ops);
...}int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{int idx, err;struct snd_pcm_str *pstr = &pcm->streams[stream];struct snd_pcm_substream *substream, *prev;...pstr->stream = stream;pstr->pcm = pcm;pstr->substream_count = substream_count;...dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');...prev = NULL;for (idx = 0, prev = NULL; idx < substream_count; idx++) {substream = kzalloc(sizeof(*substream), GFP_KERNEL);if (!substream)return -ENOMEM;substream->pcm = pcm;substream->pstr = pstr;substream->number = idx;substream->stream = stream;sprintf(substream->name, "subdevice #%i", idx);...}return 0;
}				
EXPORT_SYMBOL(snd_pcm_new_stream);int snd_device_new(struct snd_card *card, enum snd_device_type type,void *device_data, struct snd_device_ops *ops)
{struct snd_device *dev;struct list_head *p;...dev->card = card;dev->type = type;dev->state = SNDRV_DEV_BUILD;dev->device_data = device_data;dev->ops = ops;/* insert the entry in an incrementally sorted list */list_for_each_prev(p, &card->devices) {struct snd_device *pdev = list_entry(p, struct snd_device, list);if ((unsigned int)pdev->type <= (unsigned int)type)break;}list_add(&dev->list, p);return 0;
}
EXPORT_SYMBOL(snd_device_new);

至此 可以得到如下关系图:

在这里插入图片描述

3 设置 pcm逻辑设备(播放或者录音)的子流的具体操作功能

/*** snd_pcm_set_ops - set the PCM operators* @pcm: the pcm instance* @direction: stream direction, SNDRV_PCM_STREAM_XXX* @ops: the operator table** Sets the given PCM operators to the pcm instance.*/
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,const struct snd_pcm_ops *ops)
{struct snd_pcm_str *stream = &pcm->streams[direction];struct snd_pcm_substream *substream;for (substream = stream->substream; substream != NULL; substream = substream->next)substream->ops = ops;
}
EXPORT_SYMBOL(snd_pcm_set_ops);

如:

static const struct snd_pcm_ops snd_ad1889_playback_ops = {.open = snd_ad1889_playback_open,.close = snd_ad1889_playback_close,.ioctl = snd_pcm_lib_ioctl,.hw_params = snd_ad1889_hw_params,.hw_free = snd_ad1889_hw_free,//如 准备传输音频流,设置音频格式,采样速率,配置时钟源,使能中断等操作.prepare = snd_ad1889_playback_prepare,//如 将ALSA框架生成的音频缓冲区映射等操作.trigger = snd_ad1889_playback_trigger,.pointer = snd_ad1889_playback_pointer, 
};static const struct snd_pcm_ops snd_ad1889_capture_ops = {.open = snd_ad1889_capture_open,.close = snd_ad1889_capture_close,.ioctl = snd_pcm_lib_ioctl,.hw_params = snd_ad1889_hw_params,.hw_free = snd_ad1889_hw_free,.prepare = snd_ad1889_capture_prepare,.trigger = snd_ad1889_capture_trigger,.pointer = snd_ad1889_capture_pointer, 
};snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,  &snd_ad1889_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1889_capture_ops);

4 注册声卡设备,将声卡注册进ALSA框架

int snd_card_register(struct snd_card *card)int snd_card_register(struct snd_card *card)
{int err;if (!card->registered) {err = device_add(&card->card_dev);if (err < 0)return err;card->registered = true;}if ((err = snd_device_register_all(card)) < 0)return err;
...}
...return 0;
}
EXPORT_SYMBOL(snd_card_register);/** register all the devices on the card.* called from init.c*/
int snd_device_register_all(struct snd_card *card)
{struct snd_device *dev;int err;
...list_for_each_entry(dev, &card->devices, list) {err = __snd_device_register(dev);if (err < 0)return err;}return 0;
}static int __snd_device_register(struct snd_device *dev)
{if (dev->state == SNDRV_DEV_BUILD) {if (dev->ops->dev_register) {//.dev_register =	snd_pcm_dev_register,int err = dev->ops->dev_register(dev);if (err < 0)return err;}dev->state = SNDRV_DEV_REGISTERED;}return 0;
}//pcm逻辑设备实体 所包含的两个流各自的 操作集函数
const struct file_operations snd_pcm_f_ops[2] = {{.owner =		THIS_MODULE,.write =		snd_pcm_write,.write_iter =		snd_pcm_writev,.open =			snd_pcm_playback_open,.release =		snd_pcm_release,.llseek =		no_llseek,.poll =			snd_pcm_poll,.unlocked_ioctl =	snd_pcm_ioctl,.compat_ioctl = 	snd_pcm_ioctl_compat,.mmap =			snd_pcm_mmap,.fasync =		snd_pcm_fasync,.get_unmapped_area =	snd_pcm_get_unmapped_area,},{.owner =		THIS_MODULE,.read =			snd_pcm_read,.read_iter =		snd_pcm_readv,.open =			snd_pcm_capture_open,.release =		snd_pcm_release,.llseek =		no_llseek,.poll =			snd_pcm_poll,.unlocked_ioctl =	snd_pcm_ioctl,.compat_ioctl = 	snd_pcm_ioctl_compat,.mmap =			snd_pcm_mmap,.fasync =		snd_pcm_fasync,.get_unmapped_area =	snd_pcm_get_unmapped_area,}
};static int snd_pcm_dev_register(struct snd_device *device)
{int cidx, err;struct snd_pcm_substream *substream;struct snd_pcm *pcm;...//对于一个pcm设备,有两个流,一个是playback,一个是capture,for (cidx = 0; cidx < 2; cidx++) {int devtype = -1;if (pcm->streams[cidx].substream == NULL || pcm->internal)continue;switch (cidx) {case SNDRV_PCM_STREAM_PLAYBACK://pcm功能部件(逻辑设备)命名规则sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;break;case SNDRV_PCM_STREAM_CAPTURE://pcm功能部件(逻辑设备)命名规则sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;break;}/* register pcm *//*1 流类型(播放/录音)2 pcm  所属的声卡 snd_card 3 	pcm逻辑设备实体 所包含的两个流各 其中一个的 操作集函数 file_operations 4 pcm 逻辑设备实体5 逻辑设备对应的struct device struct snd_pcm *pcm;struct snd_pcm_str streams[2];struct device dev;*/err = snd_register_device(devtype, pcm->card, pcm->device,&snd_pcm_f_ops[cidx], pcm,&pcm->streams[cidx].dev);
...
}/* 1 流类型(播放/录音)2 pcm  所属的声卡 snd_card 3 	pcm逻辑设备实体 所包含的两个流各 其中一个的 操作集函数 file_operations 4 pcm 逻辑设备实体5 逻辑设备对应的struct device struct snd_pcm *pcm;struct snd_pcm_str streams[2];struct device dev;*/int snd_register_device(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data, struct device *device)
{int minor;int err = 0;//声卡逻辑设备信息集合struct snd_minor *preg;if (snd_BUG_ON(!device))return -EINVAL;preg = kmalloc(sizeof *preg, GFP_KERNEL);if (preg == NULL)return -ENOMEM;/*流类型 播放/录音声卡卡号设备号pcm逻辑设备实体 所包含的两个流各 其中一个的 操作集函数 file_operations const struct file_operations snd_pcm_f_ops[2] = {所属代表的声卡逻辑设备实体 pcm所属的声卡设备 snd_card*/preg->type = type;preg->card = card ? card->number : -1;preg->device = dev;preg->f_ops = f_ops;preg->private_data = private_data;preg->card_ptr = card;mutex_lock(&sound_mutex);minor = snd_find_free_minor(type, card, dev);if (minor < 0) {err = minor;goto error;}preg->dev = device;device->devt = MKDEV(major, minor);err = device_add(device);if (err < 0)goto error;snd_minors[minor] = preg;error:mutex_unlock(&sound_mutex);if (err < 0)kfree(preg);return err;
}
EXPORT_SYMBOL(snd_register_device);

至此注册结束 有如下关系

在这里插入图片描述

三 应用层读写流

在sound/core/sound.c文件中会注册字符设备://字符设备,只有一个open 说明此处的open只是一个转接口,只起到中转换作用,后面一定会调用目标流自己的open()
static const struct file_operations snd_fops =
{.owner =    THIS_MODULE,.open =     snd_open,.llseek =   noop_llseek,
};//注册声卡设备
static int __init alsa_sound_init(void)
{snd_major = major;snd_ecards_limit = cards_limit;//注册 声卡设备 为字符设备if (register_chrdev(major, "alsa", &snd_fops)) {snd_printk(KERN_ERR "unable to register native major device number %d\n", major);return -EIO;}//在proc下创建 asound 目录,并且创建version、devices、module、cards等信息if (snd_info_init() < 0) {unregister_chrdev(major, "alsa");return -ENOMEM;}snd_info_minor_register();return 0;
} 

open

static int snd_open(struct inode *inode, struct file *file)
{//首先从设备节点中取出设备号unsigned int minor = iminor(inode);struct snd_minor *mptr = NULL;const struct file_operations *old_fops;int err = 0;//从 snd_minors[minor]全局数组中取出目标 逻辑设备信息集合 snd_minor,mptr = snd_minors[minor];...//并且把file->f_op替换为 逻辑设备实体的 某个流(播放/录音)的操作集f_ops//const struct file_operations snd_pcm_f_ops[2] = {file->f_op = fops_get(mptr->f_ops);if (file->f_op == NULL) {file->f_op = old_fops;err = -ENODEV;}//直接调用 播放/录音流的  file_operations --> open()// snd_pcm_playback_open()/snd_pcm_capture_open()if (file->f_op->open) {err = file->f_op->open(inode, file);if (err) {fops_put(file->f_op);file->f_op = fops_get(old_fops);}}...return err;
}

open(“/dev/snd/pcmC0D0p”,…) 后面则会逐级调用 最终会是 子流的snd_pcm_substream 的 open()


http://chatgpt.dhexx.cn/article/fYEDRlGU.shtml

相关文章

ALSA编程精华

https://www.cnblogs.com/cslunatic/p/3677729.html 一、前序 这里了解一下各个参数的含义以及一些基本概念。 声音是连续模拟量&#xff0c;计算机将它离散化之后用数字表示&#xff0c;就有了以下几个名词术语。 样本长度(sample)&#xff1a;样本是记录音频数据最基本的…

ALSA学习笔记

文章目录 一、概述二、系统架构三、常用操作命令1、安装ALSA2、查看音频设备3、列出音频设备4、音量控制器 四、常见问题1、cannot open mixer: 没有那个文件或目录 一、概述 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;&#xff0c;高级Linux声音架构的简…

音频ALSA架构简介

一、ALSA架构 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;即高级 Linux 声音架构。 嵌入式移动设备的音频子系统目前主要是ALSA 驱动 asoc 框架&#xff0c;其中包括 codec driver、 platform driver、 machine driver 等。 codec driver只关心 codec 本身…

Linux ALSA 之一:ALSA 架构简介

一、概述 ALSA是 Advanced Linux Sound Architecture 的缩写&#xff0c;目前已经成为了linux的主流音频体系结构。 在 Linux 内核设备驱动层&#xff0c;ALSA 提供了 alsa-driver&#xff0c;在应用层&#xff0c;ALSA 为我们提供了 alsa-lib&#xff0c;故在其支持下&#…

ALSA (高级Linux声音架构)、ASOC基础知识

目录 第一节&#xff1a;什么是ALSA和ASOC 第二节&#xff1a;ALSA框架 第三节&#xff1a;ALSA的使用 第四节&#xff1a;ASOC的硬件框架 第四节&#xff1a;ASOC的软件框架 第一节&#xff1a;什么是ALSA和ASOC ALSA是Advanced Linux Sound Architecture&#xff0c;高级…

动态链接库dlopen的函数的使用

转自&#xff1a;http://blog.const.net.cn/a/17154.htm 编译时候要加入 -ldl (指定dl库) dlopen 基本定义 功能&#xff1a;打开一个动态链接库 [喝小酒的网摘]http://blog.const.net.cn/a/17154.htm 包含头文件&#xff1a; #include <dlfcn.h> 函数定义&#xff…

dlopen的用法

1、前言 为了使程序方便扩展&#xff0c;具备通用性&#xff0c;可以采用插件形式。采用异步事件驱动模型&#xff0c;保证主程序逻辑不变&#xff0c;将各个业务已动态链接库的形式加载进来&#xff0c;这就是所谓的插件。linux提供了加载和处理动态链接库的系统调用&#xff…

织梦上一篇下一篇没有了改为英文

织梦上一篇下一篇没有了改为英文 网站根目录找到 include/arc.archives.class.php 文件 打开找到 上一篇改为 Previous上一篇后面的“没有了” 改为 No Previous原图修改后 接着放下翻&#xff0c;紧贴着下面的“下一篇&#xff0c;没有了” 找到 下一篇改为 在这里插入…

在wordpress文章页中显示上一篇和下一篇文章

查看原文&#xff1a;http://www.hellonet8.com/1162.html 今天博主在看别人博客的时候发现他们的文章末尾会有显示上一篇和下一篇的文章链接&#xff0c;所以也想在自己的博客中添加这个功能。这么做顺便可以增加文章之间的相关性&#xff0c;对搜索引擎的蜘蛛也会友好些。废话…

js 实现 点击上一篇、下一篇功能

列表界面&#xff1a; 详细界面&#xff1a; 思路&#xff1a; 1. 首先目录列表渲染的数据是通过接口调用取到的值&#xff0c;然后点击具体某一条数据的时候&#xff0c;获取到他的 ID&#xff0c;然后通过路由跳转的时候带到详细信息页面。 2. 在详细页面中&#xff0c;先再…

Vue中 实现上一篇下一篇的功能

效果&#xff1a; 看下html页面 <div class"NewsDetails_cont_footer"><!-- 使用三目运算符判断 按钮是否可以点击 --><div click"last" :class"lastNoShow ? noClick : btn"><img src"../assets/img/newsDetail/公共…

java实现上一篇下一篇功能

根据文章类型查询&#xff0c;实现上一篇、下一篇的效果 自定义实体Dto(这里只放出扩展字段) Getter Setter public class OsArticleDto extends BaseDto {/** */private static final long serialVersionUID 1L;/** 上一篇文章id*/private String beforeId;/*** 上一篇文章…

vue实现上一篇下一篇

先来看一下效果图 请求回来所有文章&#xff0c;根据索引进行上一篇下一篇的判断 首先为两个按钮绑定点击事件 <buttonclick"last(lastId, columnId)":disabled"isLast":class"{ disClick: isLast true }">上一篇:{{ lastTitle }}</b…

Django针对上一篇和下一篇文章标题的实现逻辑

1、要求显示效果 2、前端html内容 <div><nav aria-label"..."><ul class"pager"><li><a href"/blog/detail/{{ previous_article.article_id }}">上一篇&#xff1a;{{ previous_article.title }}</a><…

程序员,我们应该如何去学习

IT技术的发展日新月异&#xff0c;新技术层出不穷&#xff0c;具有良好的学习能力&#xff0c;能及时获取新知识、随时补充和丰富自己&#xff0c;已成为程序员职业发展的核心竞争力。本文中&#xff0c;作者结合多年的学习经验总结出了提高程序员学习能力的三个要点。 众所周知…

程序员该如何学习新知识

想必大家都不是张无忌&#xff0c;人家三十年才可以练成的乾坤大挪移&#xff0c;张无忌大侠两个时辰就可以搞定&#xff0c;作为一个普通的程序员&#xff0c;经常遇到很多新技术和新知识&#xff0c;it界就是这样&#xff0c;日新月异&#xff0c; 那么我们如何学习一门技术和…

@程序员,这四个学习建议值得收藏

在我看来&#xff0c;学习能力应该是一个人最重要的能力之一。因为我们赖以生存的所有技能&#xff0c;无一例外都是通过学习获得的。那些优秀的人&#xff0c;也不过是学习能力或者学习效率比一般人强而已。 这样的观点被很多人论证过&#xff0c;商业理论家阿里德赫斯&#…

程序员学习视频教程汇总

转载请注明出处:http://blog.csdn.net/lowprofile_coding/article/details/51059080 在IT这个节凑快的行业&#xff0c;我们每天都需要学习&#xff0c;需要get新技能&#xff0c;才能不被淘汰&#xff0c;成功的人总是贵在坚持&#xff0c;我觉得有一句话说的很好&#xff1a;…

想自学一下程序员,该学些什么?

程序员是一门职业&#xff08;手动滑稽&#xff09;&#xff0c;需要自学的是编程哦。 编程分为一个方向&#xff0c;方向不同需要学习的东西也大不相同 大数据前端开发后端开发移动端开发移动开发市场游戏开发人工智能服务器开发等等 前端开发难度较高&#xff0c;需要人员…

程序员,这四个学习建议值得收藏

大家好&#xff0c;我是本周的值班编辑 江南一点雨 &#xff0c;本周将由我为大家排版并送出技术干货&#xff0c;大家可以在公众号后台回复“springboot”&#xff0c;获取最新版 Spring Boot2.1.6 视频教程试看。 在我看来&#xff0c;学习能力应该是一个人最重要的能力之一。…