RK3568笔记二十六:音频应用

若该文为原创文章,转载请注明原文出处。

一、介绍

音频是我们最常用到的功能,音频也是 linux 和安卓的重点应用场合。

测试使用的是ATK-DLR3568板子,板载外挂RK809 CODEC芯片,RK官方驱动是写好的,不用在自己重新写。测试应用层录音和放音使用的是ALSA方式。

二、硬件原理图

I2S 接口一共用到了 5 根线,这个 5 根线用于 RK3568 与 RK809 之间的音频数据收
发。

三、音频驱动使能

RK 官方已经写好了 RK809 CODEC 驱动,直接配置内核使能 RK809 CODEC 驱动即可。

打开kernel/arch/arm64/configs/rockchip_linux_defconfig

修改RK817为y, 重新编译kenel.RK官方把RK809 与 RK817 CODEC 部分驱动代码做了兼容。

驱动源码路径:kernel/sound/soc/codecs/rk817_codec.c

四、设备树配置

RK809 CODEC芯片的I2S连接到RK3568的i2c0, 内核使能后,需要通过I2C配置一系列参数。

1、I2C设备树配置

设备树路径: arch/arm64/boot/dts/rockchip/rk3568-evb.dtsi

2、声卡设备树配置

配置声卡主要是为了和RK809驱动关联。

声卡驱动文件路径为 sound/soc/generic/simple-card.c

五、声卡设置与测试

alsa-utils 自带了 amixer 这个声卡设置工具,使用amixer测试录音和放音。

1、使用 amixer 设置声卡

amixer cset name='Capture MIC Path' 'Main Mic'

2、使用 arecord 录制音频

arecord -r 44100 -f S16_LE -d 10 record.wav
录制一段 10 秒音频, 采样率 44.1K,S16_LE 格式进行采样,

3、播放音频

aplay record.wav

六、开机自动配置声卡

声卡设置的保存通过 alsactl 工具来完成,此工具也是 alsa-utils 编译出来的。alsactl 默认将
声卡配置文件保存在/var/lib/alsa 目录下。
使用 amixer 设置声卡,然后输入如下命令保存声卡设置
alsactl -f /var/lib/alsa/asound.state store //保存声卡设置
如果要使用 asound.state 中的配置信息来配置声卡,执行如下命令即可:
alsactl -f /var/lib/alsa/asound.state restore

创建自启动文件

/etc/init.d/S98alactl
PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
start_restore()
{
  if [ -f "/var/lib/alsa/asound.state" ]; then
  /usr/sbin/alsactl -f /var/lib/alsa/asound.state restore &
  fi
}
stop_restore()
{
  echo -n ""
}
case "$1" in
  start)
  echo -n "ALSA: Restoring mixer setting......"
  start_restore
  echo "done."
  ;;
  stop)
  stop_restore
  echo "done."
  ;;
  restart|reload)
  echo -n "stoping restore... "
  stop_restore && sleep .3
  echo "done."
  echo -n "starting restore... "
  start_restore
  echo "done."
 ;;
 *)
 echo "Usage: $0 {start|stop|restart}"
 exit 1
 esac
 exit 0
chmod 777 /etc/init.d/S98alactl   # 赋予文件可执行权限

修改/etc/init.d/rcS,添加执行文件命令,上电后会自动配置声卡

七、使用ALSA录制音频和放音

ALSA程序网上很多,不多做介绍,直接上代码

录制音频alsa_pcm_rec.c


/*
  进行音频采集,采集pcm数据并直接保存pcm数据
  音频参数:
   	 声道数:		1
   	 采样位数:	16bitLE格式t、LE格式
   	 采样频率:	44100Hz
  运行示例:
  $ gcc linux_pcm_save.c -lasound
  $ ./a.out
*/

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <signal.h>

/*
 * 指定音频的格式,其他常用格式:
 * SND_PCM_FORMAT_U24_LE、
 * SND_PCM_FORMAT_U32_LE
 */
#define AudioFormat SND_PCM_FORMAT_S16_LE
/* 1单声道   2立体声*/
#define AUDIO_CHANNEL_SET 1
/*
 * 音频采样率,常用的采样频率:
 * 44100Hz 、16000HZ、8000HZ、48000HZ、22050HZ
 */
#define AUDIO_RATE_SET 44100

#define PCM_FILE "test.pcm"

FILE *pcm_data_file = NULL;
int run_flag = 0;

void exit_sighandler(int sig)
{
  run_flag = 1;
}

int main(int argc, char *argv[])
{
  int  i;
  int  err;
  char *buffer;
  int  buffer_frames = 1024;
  unsigned int rate = AUDIO_RATE_SET;
  snd_pcm_t *capture_handle;       // 一个指向PCM设备的	句柄
  snd_pcm_hw_params_t *hw_params;  //此结构包含有关硬件的信息,可用于指定PCM流的配置

  /* 注册信号捕获退出接口 */
  signal(2, exit_sighandler);

  /* PCM的采样格式在pcm.h文件里有定义 */
  snd_pcm_format_t format = AudioFormat;  // 采样位数:16bit、LE格式

  /*打开音频采集卡硬件,并判断硬件是否打开成功,若打开失败则打印出错误提示
	  * SND_PCM_STREAM_PLAYBACK 输出流
   * SND_PCM_STREAM_CAPTURE  输入流
   */
  if((err = snd_pcm_open(&capture_handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0)
  {
    printf("无法打开音频设备.\n");
    exit(1);
  }
  printf("音频接口打开成功.\n");

  /* 创建一个保存PCM数据的文件 */
  if((pcm_data_file = fopen(PCM_FILE, "wb")) == NULL)
  {
    printf("无法创建音频文件.\n");
    exit(1);
  }
  printf(" 用于录制的音频文件已打开.\n");

  /* 分配硬件参数结构对象,并判断是否分配成功 */
  if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
  {
    printf("无法分配硬件参数结构.\n");
    exit(1);
  }
  printf("硬件参数结构分配成功.\n");

  /* 按照默认设置对硬件对象进行设置,并判断是否成功 */
  if((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
  {
    printf("无法初始化硬件参数结构.\n");
    exit(1);
  }
  printf("硬件参数结构初始化成功.\n");


  /*
  		设置数据为交叉模式,并判断是否设置成功
  		interleaved/non interleaved:交叉/非交叉模式。
    表示在多声道数据传输的过程中是采样交叉的模式还是非交叉的模式。
  		对多声道数据,如果采样交叉模式,使用一块buffer即可,其中各声道的数据交叉传输;
    如果使用非交叉模式,需要为各声道分别分配一个buffer,各声道数据分别传输。
 	*/
        if((err = snd_pcm_hw_params_set_access (capture_handle,hw_params,SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
 	{
 	  printf("无法设置访问类型(%s)\n",snd_strerror(err));
 	  exit(1);
 	}
 	printf("访问类型设置成功.\n");

 	/* 设置数据编码格式,并判断是否设置成功 */
 	if((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0)
 	{
 	  printf("无法设置格式.\n");
 	  exit(1);
 	}
 	printf("PCM数据格式设置成功.\n");

 	/* 设置采样频率,并判断是否设置成功 */
 	if((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0)
 	{
 	  printf("无法设置采样率.\n");
 	  exit(1);
 	}
 	printf("采样率设置成功.\n");

 	/* 设置声道,并判断是否设置成功 */
 	if((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, AUDIO_CHANNEL_SET)) < 0)
 	{
 	  printf("无法设置声道参数.\n");
 	  exit(1);
 	}
 	printf("声道参数设置成功.\n");

 	/* 将配置写入驱动程序中,并判断是否配置成功 */
 	if((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
 	{
 	  printf("无法向驱动程序设置参数.\n");
 	  exit(1);
 	}
 	printf("参数设置成功.\n");

 	/* 使采集卡处于空闲状态 */
 	snd_pcm_hw_params_free(hw_params);

 	/* 准备音频接口,并判断是否准备好 */
 	if((err = snd_pcm_prepare(capture_handle)) < 0)
 	{
 	  printf("无法使用音频接口.\n");
 	  exit(1);
 	}
 	printf("音频接口准备好.\n");

 	/* 配置一个数据缓冲区用来缓冲数据 */
 	/* snd_pcm_format_width(format) 获取样本格式对应的大小(单位是:bit) */
 	int frame_byte = snd_pcm_format_width(format)/8;
 	buffer = malloc(buffer_frames*frame_byte*AUDIO_CHANNEL_SET);
 	printf("缓冲区分配成功.\n");

 	/* 开始采集音频PCM数据 */
 	printf("开始采集数据...\n");

 	while(1)
 	{
 	  /* 从声卡设备读取一帧音频数据 2048字节 */
 	  if((err=snd_pcm_readi(capture_handle, buffer, buffer_frames)) != buffer_frames)
 	  {
 	    printf("从音频接口读取失败.\n");
 	    exit(1);
 	  }

 	  /* 写数据到文件: 音频的每帧数据样本大小是16位=2个字节 */
 	  fwrite(buffer, (buffer_frames*AUDIO_CHANNEL_SET), frame_byte, pcm_data_file);
 	  if(run_flag)
 	  {
 	    printf("停止采集.\n");
 	    break;
 	  }
 	}

 	/* 释放数据缓冲区 */
 	free(buffer);

 	/* 关闭音频采集卡硬件 */
 	snd_pcm_close(capture_handle);

 	/* 关闭文件流 */
 	fclose(pcm_data_file);

 	return 0;
}

播放音频alsa_pcm_play.c


/*
 * 进行音频采集,读取存放pcm数据文件通过声卡进行播放'
 * 音频参数:
 *     声道数:      1
 *     采样位叔:   16bit、LE格式
 *     采样频率:    44100Hz
 * 运行救命:
 * $ gcc alsa_pcm_play.c -lasound
 * $ ./a.out test.pcm
 */

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <signal.h>

/* 指定音频的格式,其他常用格式:
 *
 * SND_PCM_FORMAT_U24_LE、
 * SND_PCM_FORMAT_U32_LE
 */
#define AudioFormat  SND_PCM_FORMAT_S16_LE
/* 1单声道   2立体声 */
#define AUDIO_CHANNEL_SET 1
/* 音频采样率,常用的采样频率:
 * 44100Hz 、16000HZ、8000HZ、48000HZ、22050HZ
 */
#define AUDIO_RATE_SET     44100

FILE *pcm_data_file = NULL;

int run_flag = 0;

void exit_sighandler(int sig)
{
  run_flag = 1;
}

int main(int argc, char *argv[])
{
  int  i;
  int  err;
  char *buffer;
  int  buffer_frames = 1024;
  unsigned int rate = AUDIO_RATE_SET;
  snd_pcm_t *capture_handle;   // 一个指向PCM设备的句柄
  snd_pcm_hw_params_t *hw_params; //此结构包含有关硬件的信息,可用于指定PCM流的配置

  /* 注册信号捕获退出接口 */
  signal(2, exit_sighandler);

  /* PCM的采样格式在pcm.h文件里有定义 */
  snd_pcm_format_t format = AudioFormat;

  /*打开音频采集卡硬件,并判断硬件是否打开成功,若打开失败则打印出错误提示
	  * SND_PCM_STREAM_PLAYBACK 输出流
   * SND_PCM_STREAM_CAPTURE  输入流
   */
  if((err = snd_pcm_open(&capture_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
  {
    printf("无法打开音频设备.\n");
    exit(1);
  }
  printf("音频接口打开.\n");

  /* 打开存放PCM数据的文件 */
  if((pcm_data_file = fopen(argv[1], "rb")) == NULL)
  {

    printf("无法打开音频文件.\n");
    exit(1);
  }
  printf("用于播放的音频文件已打开.\n");

  /* 分配硬件参数结构对象, 并判断是否分配成功 */
  if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
  {
    printf("无法分配参数结构.\n");
    exit(1);
  }
  printf("硬件参数结构已分配成功.\n");

  /* 按照默认设置对硬件对象进行设置,并判断是否设置成功 */
  if((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
  {
    printf("无法初始化硬件参数结构.\n");
    exit(1);
  }
  printf("硬件参数结构初始化成功.\n");

  /*
  		设置数据为交叉模式,并判断是否设置成功
  		interleaved/non interleaved:交叉/非交叉模式。
    表示在多声道数据传输的过程中是采样交叉的模式还是非交叉的模式。
  		对多声道数据,如果采样交叉模式,使用一块buffer即可,其中各声道的数据交叉传输;
    如果使用非交叉模式,需要为各声道分别分配一个buffer,各声道数据分别传输。
 	*/
	 if((err = snd_pcm_hw_params_set_access (capture_handle,hw_params,SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
  {
    printf("无法设置访问类型.\n");
    exit(1);
  }
  printf("访问类型访问.\n");

  /* 设置数据编码格式,并判断是否设置成功 */
  if((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0)
  {
    printf("无法设置格式.\n");
    exit(1);
  }
  printf("PCM数据格式设置成功.\n");

  /* 设置采样频率,并判断是否设置成功 */
  if((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0)
  {
    printf("无法设置采样率.\n");
    exit(1);
  }
  printf("采样率设置成功.\n");

  /* 设置声道,并判断是否设置成功 */
  if((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, AUDIO_CHANNEL_SET)) < 0)
  {
    printf("无法设置声道数.\n");
    exit(1);
  }
  printf("声道数设置成功.\n");

  /* 将配置写入驱动程序中,并判断是否配置成功 */
  if((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
  {
    printf("无法向驱动程序设置参数.\n");
    exit(1);
  }
  printf("参数设置成功.\n");

  /* 使采集卡处理空闲状态 */
  snd_pcm_hw_params_free(hw_params);

  /* 准备音频接口, 并判断是否准备好  */
  if((err = snd_pcm_prepare(capture_handle)) < 0)
  {
    printf("无法使用音频接口.\n");
    exit(1);
  }
  printf("音频接口准备好.\n");

 	/* 配置一个数据缓冲区用来缓冲数据 */
 	/* snd_pcm_format_width(format) 获取样本格式对应的大小(单位是:bit) */
 	int frame_byte = snd_pcm_format_width(format)/8;
 	buffer = malloc(buffer_frames*frame_byte*AUDIO_CHANNEL_SET);
 	printf("缓冲区分配成功.\n");

  /* 开始播放音频数据...*/
  printf("开始播放音频数据...\n");

  int read_cnt;

  while(1)
  {
    read_cnt = fread(buffer, 1, frame_byte*(buffer_frames*AUDIO_CHANNEL_SET), pcm_data_file);
    if(read_cnt <= 0)
      break;

    /* 向声卡设备写一帧音频数据: 2048字节 */
    if((err = snd_pcm_writei(capture_handle, buffer, buffer_frames)) != buffer_frames)
    {
      printf("向音频接口写数据失败.\n");
      exit(1);
    }

    if(run_flag)
    {
      printf("停止播放.\n");
      break;
    }
  }

  printf("播放完成.\n");
  /* 释放数据缓冲区 */
  free(buffer);

  /* 关闭音频采集卡硬件 */
  snd_pcm_close(capture_handle);

  /* 关闭文件流 */
  fclose(pcm_data_file);

  return 0;
}

编译:

/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-g++ alsa_pcm_rec.c -o alsa_pcm_play -lasound
/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-g++ alsa_pcm_play.c -o alsa_pcm_play -lasound

生成的可执行文件,通过ADB或TFTP等方式上传到板子运行。

测试录音和放音正常。

如有侵权,或需要完整代码,请及时联系博主。

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

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

相关文章

家居的3D交互展示用什么工具比较专业?

家居的3D交互展示可以使用多种专业工具来实现&#xff0c;这些工具不仅能够在手机和电脑上查看&#xff0c;还能在手机上进行交互操作&#xff0c;如放缩、旋转等&#xff0c;并且支持高清流畅的画面展示。以下是一些推荐的3D交互展示工具&#xff1a; 1、在线3D展示软件&…

牛客热题:寻找第K大

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;寻找第K大题目链接方法一&#…

Docker基础篇之Docker入门介绍

文章目录 1. 为什么要有Docker&#xff1f;2. Docker简介3. 容器和虚拟机的区别4. Docker下载 1. 为什么要有Docker&#xff1f; 假设我们现在正在开发一个项目&#xff0c;使用的是一台笔记本电脑而且开发环境具有特定的配置&#xff0c;其他开发人员身处的环境配置也各不相同…

ZeroTier+Nomachine远程

目录 前述&#xff1a;一、Zero二、Nomachine 前述&#xff1a; 需要远程控制时&#xff0c;服务端与客户端都必须下载这两个软件&#xff01;远程主机&#xff08;被控制的主机&#xff09;和远程客户端&#xff08;控制主机的用户&#xff09;都必须具有网络连接&#xff0c;…

地铁判官:啥时候B端系统界面,也出个“判官”,讲好不准打脸。

小编所在的城市——山东青岛&#xff0c;出了个地铁判官&#xff0c;我看了视频&#xff0c;哈哈哈&#xff0c;俗世的判断标准就是那么简单直接&#xff0c;而放到B端系统那就难说啦。 如何判断B端系统的优劣&#xff0c;各位看官&#xff0c;各抒己见吧。 判断B端系统界面的…

如何深入理解、应用及扩展 Twemproxy?no.15

Twemproxy 架构及应用 Twemproxy 是 Twitter 的一个开源架构&#xff0c;它是一个分片资源访问的代理组件。如下图所示&#xff0c;它可以封装资源池的分布及 hash 规则&#xff0c;解决后端部分节点异常后的探测和重连问题&#xff0c;让 client 访问尽可能简单&#xff0c;同…

揭秘:如何使用Python统计女友生日还剩几天?

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;为何需要统计生日天数&#xff1f; 二、需求分析与准备 1. 用户输入格…

为什么我们会固执己见、为什么我们总认为自己是对的?

人为什么固执己见&#xff0c;是其所是&#xff0c;而非其所非&#xff1f;我们要有什么样的思维模式才能使自己有良好的判断力&#xff0c;才能作出恰当的预测和良好的决定呢&#xff1f;作者Julia Galef对TED发表演讲时提出自己的观点。以下是演讲的文字实录。 我想让你们想象…

spring boot 之 结合aop整合日志

AOP 该切面仅用于请求日志记录&#xff0c;若有其他需求&#xff0c;在此基础上扩展即可&#xff0c;不多逼逼&#xff0c;直接上代码。 引入切面依赖 <!-- 切面 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>sp…

如果查看svn的账号和密码

一、找到svn存放目录&#xff08;本地默认存放SVN用户信息的目录为&#xff1a;C:\Users\Administrator\AppData\Roaming\Subversion\auth\svn.simple&#xff09;每个人的电脑环境不一样&#xff0c;因人而异。 如果找不到直接搜索svn.simple 二、下载密码查看工具 链接: 百…

面试被问到不懂的东西,是直接说不懂还是坚持狡辩一下?

大家好&#xff0c;我是瑶琴呀。 面试被问到不懂的东西&#xff0c;是直接说不懂还是坚持狡辩一下&#xff1f;这个问题可以转变一下&#xff0c;如果你顺利拿到 offer&#xff0c;公司安排的工作跟你之前的技术和经验不匹配&#xff0c;你还愿意干下去吗&#xff1f; 转变一…

条款7:千万不要重载,||和,操作符

&&和|| 和C一样&#xff0c;C对于“真假值表达式”采用所谓的“骤死式”评估方式。意思是一旦该表达式的真假值确定&#xff0c;即使表达式中还有部分尚未检验&#xff0c;整个评估工作仍告结束。 举个例子&#xff0c;在下面情况中: char *p; ... if ((p!0)&&a…

单片机超声波测距+WTD588D语音播报的设计

第一章 绪论 1.1 课题设计目的及意义 1.1.1设计的目的 随着科学技术的快速发展&#xff0c;超声波在测距中的应用越来越广。但就目前的急速水平来说&#xff0c;人们可以具体利用的测距技术还十分有限&#xff0c;因此&#xff0c;这是一个正在蓬勃发展而又有无限前景的技术…

基于51单片机多功能太阳能充电器设计

1 绪论1.1 本课题研究背景及现状 当代社会随着一些不可再生资源如煤炭&#xff0c;石油等日益减少&#xff0c;使得各国社会经济越来越受能源问题的约制&#xff0c;因此许多国家开始逐渐的实行“阳光计划”&#xff0c;开发洁净的能源如太阳能&#xff0c;用以成为本国经济发…

C++多态总结与原理、菱形继承问题

文章目录 多态什么是多态 多态的定义及实现多态的构成条件虚函数 虚函数的重写虚函数重写的两个例外协变 重写C11 override 和 final重载、覆盖(重写)、隐藏(重定义)的对比 抽象类概念接口继承和实现继承小结 多态的原理虚函数表多态的原理动态绑定与静态绑定 单继承和多继承关…

数据结构之二叉树的超详细讲解(2)--(堆的概念和结构的实现,堆排序和堆排序的应用)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 数据结构之二叉树的超详细讲解(2)--(堆的概念和结构的实现,堆排序和堆排序的应用) 收录于专栏【数据结构初阶】 本专栏旨在分享学习数据结构学习的一点学习笔记…

IC解析之TPS1HB08-Q1

目录 1.主要参数2. 接口定义3. 工作原理分析高低边驱动的作用TPS1HB08-Q1架构TPS1HB08-Q1典型应用电路参数设置 4.总结 1.主要参数 2. 接口定义 其中&#xff0c;不同的IC版本在故障反馈引脚有所差异&#xff0c;A/B版本则为ILIM功能&#xff0c;F版本则为FLT功能&#xff0c;两…

GpuMall智算云:QwenLM/Qwen1.5/Qwen1.5-7B-Chat

Qwen 是阿里巴巴集团 Qwen 团队的大型语言模型和大型多模态模型系列&#xff0c;现在大型语言模型已经升级到 Qwen1.5 版本。 GpuMall智算云 | 省钱、好用、弹性。租GPU就上GpuMall,面向AI开发者的GPU云平台 无论是语言模型还是多模态模型&#xff0c;都在大规模的多语言和多模…

【Python】 从Python列表中获取唯一值

基本原理 在Python中&#xff0c;列表是一种非常灵活的数据结构&#xff0c;它允许存储不同类型的元素。然而&#xff0c;有时我们可能需要从列表中提取唯一的值&#xff0c;即去除重复的元素。这在处理数据集或进行数据分析时尤其有用。Python提供了几种方法来实现这一目标。…

甘肃省大学生志愿服务西部计划报名流程及免冠证件照处理

在甘肃省&#xff0c;大学生志愿服务西部计划是一项旨在鼓励和引导大学生参与西部地区社会服务与发展的重要项目。随着2024年报名季的到来&#xff0c;许多有志青年正准备投身这一有意义的事业。本文将详细介绍报名流程&#xff0c;并提供免冠证件照的处理技巧&#xff0c;帮助…