ALSA是Advanced Linux Sound Architecture简称。它包含一组kernel 驱动,一个应用编程接口(API)库以及一组工具函数。本文中,我们会向读者展示ALSA项目和组成部件的概况。后面会重点介绍ALSA PCM接口的编程。

ALSA不仅仅是sound API。选择ALSA可以让你最大程度的控制和执行执行低级的audio函数,或者使用其它sound API不支持的特定功能。如果你已经写了一个audio应用程序,那么你可能希望为它增加ALSA sound驱动支持。如果你的主要兴趣不是audio,仅仅是想播放声音,那么你最好使用高级的sound toolkits,比如SDL,OpenAL或者桌面环境提供的其他开发包。如果你想使用ALSA,那么要确保你的Linux系统支持ALSA。

History of ALSA

ALSA项目的出现是因为Linux kernel sound driver OSS/Free
drivers不能得到很好的维护,而导致驱动无法支持新的sound技术。Jaroslav Kysela最初为一个sound
card写了一个驱动,启动了这个项目,随着时间的推移越来越多的开发者加入进来,重新定义了API以支持更多的声卡。

在Linux kernel 2.5的开发过程中,ALSA被merged到官方内核中。随着kernel2.6的发布,ALSA成为稳定版内核的一部分,并且得到了广泛使用。

Digital Audio Basics

声音由空气压力变化的波形组成,被转换器(如麦克风)转换为电信号。一个模数转换器(ADC)把模拟电压信号转换为离散值,称为采样,采样是按照固定时间间隔进行的,称为采样率。发送这些采样到数模转换器(DAC),再输出到loudspeaker,原始的声音就被重现了。

采样的位数大小,是决定声音数字精度的一个因素,另外一个主要因素是采样率。奈奎斯特理论指出当信号带宽小于1/2采样率时,可通过采样信号还原出原始信号。

ALSA Basics

ALSA包括支持各种声卡的Kernel设备驱动,API库libasound。应用开发者应该使用API而不是kernel系统调用接口。API库函数提供了高层次,编程友好的接口,开发者使用逻辑设备名而不需要考虑低级细节(如设备文件)。

与ALSA相反,OSS/Free要求应用在kernel系统调用级进行编程,这就需要开发者指定设备文件名并且使用ioctl来完成功能。为了兼容OSS,ALSA提供了内核模块来模拟OSS/Free声音驱动,所以大部分现存的audio应用都无须修。libaoss是模拟包装库,可以用来模拟OSS/Free
API而不需要内核模块的模拟。

ALSA还提供了plugins能力,允许扩展一个新设备,甚至包括完全用软件实现的虚拟设备。ALSA提供了一组命令行工具,包括mixer,声音文件播放,以及对特定声卡特定功能的控制。

ALSA Architecture

ALSA API可以分为以下几个主要部分:

  • Control接口:一个通用的功能,用来管理声卡的寄存器以及查询可用设备。
  • PCM 接口:管理数字audio capture和playback的接口,本文的其余部分将主要介绍着个接口,因为这是audio应用最常用的接口
  • Raw MIDI接口:支持MIDI(Musical Instrument DIgital Interface,电子音乐设备的标准)。这个API提供了对声卡MIDI bus的访问。Raw接口由MIDI events驱动,程序负责管理协议和计时
  • Timer 接口:提供对声卡上计时硬件的访问,用于同步声音事件。
  • Sequencer interface:一个MIPI编程和声音同步接口,比raw MIDI接口级别更高,它管理大部分MIDI协议和计时。
  • Mixer接口:控制声卡上的信号路由和音量调节的设备。它是建立在control接口之上的。

Device Naming

API操作的是逻辑设备名而不是设备文件,设备名可以是真正的硬件设备或者插件。硬件设备使用hw:i,j这种格式,i是卡号而j是在这个卡上的设备。第一个声音设备是hw:0,0。第一个sound设备的别名为defalut,本文后面的例子都使用default。Plugins使用另外一种命名模式:例如plughw:是一个插件除了提供对硬件设备的访问还提供某种功能,比如软件实现采样率转换,因为硬件不支持这种操作。dmix插件允许合成几路数据
dshare插件允许把单路数据动态分配到不同的应用中。

Sound Buffers and Data Transfer

声卡有一个硬件buffer。在录音时(capture)存储录音的采样值,当buffer填满后声卡生成一个中断,kernel
sound驱动使用DMA传输采样数据到内存buffer。类似的在playback时,应用buffer数据通过DMA传输给sound
card的硬件buffer

这个硬件buffer是一个ring
buffers,意味着当到达buffer末端后,就会重新回到起始端。一个指针用来维护硬件buffer和应用buffer的当前位置。在内核外部,仅能访问application
buffer,所以在这里我们主要讨论application buffer。

buffer的尺寸可以通过ALSA库函数调用指定。buffer可以非常的大,一次传输完整个buffer的数据可能导致无法接收的延迟,所以,ALSA把这个buffer分割为一系列periods,以period做为传输数据的单位。

Period由多个frames组成,每一个frames包含在一个时间点的采样值,对于立体声设备来说,一个frames包含两个channels的采用,图1演示了一个buffer,

period,sample之间的关系,在这里,左右声道的数据存储在一帧中,这种模式称为interleaved。对于non-interleaved
模式,所有的左声道数据存放在一起,然后所有的右声道保存在一起。

Over and Under Run

当一个声音设备被激活,数据持续的在硬件和应用buffer间传送。在录音情况下(capture),如果应用没有快速的从buffer中读取数据,环状buffer将被新到的数据覆盖,导致数据丢失,我们称之为overrun。在playback情况下,如果应用无法快速传送数据到buffer中,那么导致hardware无数据可播放,这种情况我们称之为underrun。ALSA文档有时把这两种情况统称为XRUN。正确设计的应用最小化XRUN的发生并且在XRUN发生后能够恢复操作。

A typical Sound Application

通常可以用下面伪代码表示PCM接口编程模式

  1. open interface for capture or playback
  2. set hardware parameters(access mode, data format, channels, rate, etc.)
  3. while there is data to be processed:
  4. read PCM data(capture) or write PCM data(playback)
  5. close interface

我们先看看一些示例代码。我推荐你在linux系统编译运行它们,观察输出然后尝试一些修改。这些测试代码的完整列表可以从以下地址下载:ftp.linuxjournal.com/pub/lj/listings/issue126/6735.tgz

Listing 1. Display Some PCM Types and Formats

  1. #include <alsa/asoundlib.h>
  2.  
  3. int main() {
  4. int val;
  5.  
  6. printf("ALSA library version: %s\n", SND_LIB_VERSION_STR);
  7.  
  8. printf("PCM stream types:\n");
  9. for (val = ; val <= SND_PCM_STREAM_LAST; val++) {
  10. printf(" %s\n", snd_pcm_stream_name((snd_pcm_stream_t)val));
  11. }
  12.  
  13. printf("PCM access types:\n");
  14. for (val = ; val <= SND_PCM_ACCESS_LAST; val++) {
  15. printf(" %s\n", snd_pcm_access_name((snd_pcm_access_t)val));
  16. }
  17.  
  18. printf("PCM formats:\n");
  19. for (val = ; val <= SND_PCM_STREAM_LAST; val++) {
  20. if (snd_pcm_format_name((snd_pcm_format_t)val) != NULL) {
  21. printf(" %s (%s)\n",
  22. snd_pcm_format_name((snd_pcm_format_t)val),
  23. snd_pcm_format_description((snd_pcm_format_t)val));
  24. }
  25. }
  26.  
  27. printf("\nPCM subformats:\n;);
  28. for (val = ; val <= SND_PCM_SUBFORMAT_LAST; val++) {
  29. printf(" %s (%s)\n", snd_pcm_subformat_name((snd_pcm_subformat_t)val),
  30. snd_pcm_subformat_description((snd_pcm_subformat_t)val));
  31. }
  32. return ;
  33. }

gcc -o test test.c -lasound

编译为可执行文件,程序必须链接ALSA库,libasound。有些ALSA库函数需要使用dlopen功能和浮点运算,所以有时需要增加-ldl和-lmgcc -o listing1 listing1.c -lasound在我的机器上,运行结果如下

  1. ALSA library version: 1.0.
  2.  
  3. PCM stream types:
  4. PLAYBACK
  5. CAPTURE
  6.  
  7. PCM access types:
  8. MMAP_INTERLEAVED
  9. MMAP_NONINTERLEAVED
  10. MMAP_COMPLEX
  11. RW_INTERLEAVED
  12. RW_NONINTERLEAVED
  13.  
  14. PCM formats:
  15. S8 (Signed bit)
  16. U8 (Unsigned bit)
  17.  
  18. PCM subformats:
  19. STD (Standard)

listing1 展示了一些ALSA使用的PCM数据类型和参数。需要包含头文件alsa/asoundlib.h,ALSA库函数以及常用宏都在这个文件中定义。这个程序的其余部分重复的打印了PCM数据类型。

Listing 2. Opening PCM Device and Setting Parameters

执行结果为

  1. [cpp] view plain copy
  2.  
  3. PCM handle name = 'default'
  4. PCM state = PREPARED
  5. format = 'S16_LE'(Signed bit Little Endian)
  6. subformat = 'STD' (Standard)
  7. channels=
  8. rate = bps
  9. period time = us
  10. period size = frames
  11. buffer time = us
  12. buffer size = frames
  13. periods per buffer = frames
  14. exact rate=/ bps
  15. significant bits =
  16. tick time = us
  17. is batch =
  18. is block transfer =
  19. is double =
  20. is half duplex =
  21. is joint duplex =
  22. can overrange =
  23. can mmap =
  24. can overrange =
  25. can sync start =

List2打开缺省的PCM设备,设置一些参数,然后显示大部分的硬件参数。这个测试程序不会执行playback和recording。调用snd_pcm_open打开缺省的PCM设备,打开模式为PLAYBACK。这个函数通过第一个参数返回一个句柄,后续的操作使用这个句柄操作这个PCM设备。像大部分ALSA库函数调用,snd_pcm_open返回一个整数表示调用状态,负数指明出错原因。为了使测试代码更简洁,我忽略了大部分的函数返回值。在产品级应用中,开发者应该检查每一个API调用,以便提供相应的出错处理。

为了设置流的硬件参数,我们需要分配一个snd_pcm_hw_param_t变量,首先调用macro
snd_pcm_hw_params_alloca,接下来使用snd_pcm_hw_params_any来初始化这个变量。接下来使用ALSA提供的API设置PCM
流的硬件参数,这些API的参数形式为(PCM handle, snd_pcm_hw_param_t *,
val)。我们设置流为interleaved mode,16 bit sample size,2
channels和44100Hz采样率。对于采样率,声卡硬件不一定支持指定的采样率。我们使用函数snd_pcm_hw_params_set_rate_near请求设置指定值附近的采样率。在调用snd_pcm_hw_params函数之前,所有设置的硬件参数并不会被激活。

这个程序的其他部分获得并显示PCM 流的参数,包括周期和buffer sizes。显示结果依赖于测试机器的硬件。

在你的机器上运行这个程序,并尝试做一些修改。比如把device名从defalut改为hw:0,0或者plughw:查看结果是否改变。修改硬件参数的值,查看显示部分的变化。

Listing 3. SImple Sound Playback

  1. /*
  2. * This example reads standard from input and writes to
  3. * the default PCM device for 5 seconds of data.
  4. */
  5. /* Use the newer ALSA API */
  6. #define ALSA_PCM_NEW_HW_PARAMS_API
  7.  
  8. #include <alsa/asoundlib.h>
  9. int main()
  10. {
  11. long loops;
  12. int rc;
  13. int size;
  14. snd_pcm_t *handle;
  15. snd_pcm_hw_params_t *params;
  16. unsigned int val;
  17. int dir;
  18. snd_pcm_uframes_t frames;
  19. char *buffer;
  20.  
  21. /* Open PCM device for playback */
  22. rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, );
  23. if (rc < ) {
  24. printf("unable to open pcm device: %s\n", snd_strerror(rc));
  25. exit();
  26. }
  27.  
  28. snd_pcm_hw_params_allocams);
  29.  
  30. snd_pcm_hw_params_any(handle, params);
  31.  
  32. snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
  33.  
  34. snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
  35.  
  36. snd_pcm_hw_params_set_channels(handle, params, );
  37.  
  38. val = ;
  39. snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
  40.  
  41. frames = ;
  42. snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
  43.  
  44. rc = snd_pcm_hw_params(handle, params);
  45. if (rc < ) {
  46. printf("unable to set hw parameters: %s\n", snd_strerror(rc));
  47. exit();
  48. }
  49.  
  50. snd_pcm_hw_params_get_period_size(params, &frames, &dir);
  51.  
  52. size = frames * ;
  53. buffer = (char *)malloc(size);
  54.  
  55. snd_pcm_hw_params_get_period_time(params, &val, &dir);
  56. loops = / val;
  57.  
  58. while (loops > ) {
  59. loops--;
  60. rc = read(, buffer, size);
  61.  
  62. rc = snd_pcm_writei(handle, buffer, frames);
  63. if (rc == -EPIPE) {
  64. printf("underrun occured\n");
  65. }
  66. else if (rc < ) {
  67. printf("error from writei: %s\n", snd_strerror(rc));
  68. }
  69. }
  70.  
  71. snd_pcm_drain(handle);
  72. snd_pcm_close(handle);
  73. free(buffer);
  74.  
  75. return ;
  76. }

Listing 3扩展了前面的例子,写了一些随机的采样数据到声卡的playback。在这个例子中,我们从标准输入获取数据,当获取的数据达到一个period时,就把采样数据写入声卡。

这个程序的开始部分和前面的例子一样:打开PCM设备并设置硬件参数。我们使用ALSA选择的period size作为储存采样数据buffer的大小。通过这个period time我们就可以计算出5秒钟大概需要多少个periods。

在循环中我们从标准输入读取数据填充一个period的采样数据到buffer。我们检查并处理文件结束以及读取字节数和期望数不一致的情况。

使用snd_pcm_write函数发送数据到PCM设备。这个操作很像内核的write系统调用,除了size的单位是frames。检查返回的错误码,错误码EPIPE表示underrun错误发生,这会导致PCM

流进入了XRUN状态,停止处理数据。从这种状态恢复的标准方法是调用snd_pcm_prepare,使得流进入PREPARED状态,这样我们就可以再次向流中写入数据了,如果接收的是其他的错误码,那么我们显示错误码,并且继续执行。

这个程序循环执行5秒或者到达输入文件的结束符。我们调用snd_pcm_drain使得所有pending的采样数据被传输,然后关闭这个流。释放分配的buffer并退出。

执行这个程序,并使用/dev/urandom作为输入

Listing 4. Simple Sound Recording

  1. /*
  2. This example reads from the default PCM device
  3. and writes to standard output for 5 seconds of data.
  4. */
  5.  
  6. /* Use the newer ALSA ALI */
  7. #define ALSA_PCM_NEW_HW_PARAMS_API
  8.  
  9. #include <alsa/asoundlib.h>
  10.  
  11. int main()
  12. {
  13. long loops;
  14. int rc;
  15. int size;
  16. snd_pcm_t *handle;
  17. snd_pcm_hw_params_t *params;
  18. unsigned int val;
  19. int dir;
  20. snd_pcm_uframes_t frames;
  21. char *buffer;
  22.  
  23. rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, );
  24. <pre name="code" class="programlisting"> if (rc < ) {
  25. fprintf(stderr,
  26. "unable to open pcm device: %s\n",
  27. snd_strerror(rc));
  28. exit();
  29. }
  30.  
  31. /* Allocate a hardware parameters object. */
  32. snd_pcm_hw_params_allocams);
  33.  
  34. /* Fill it in with default values. */
  35. snd_pcm_hw_params_any(handle, params);
  36.  
  37. /* Set the desired hardware parameters. */
  38.  
  39. /* Interleaved mode */
  40. snd_pcm_hw_params_set_access(handle, params,
  41. SND_PCM_ACCESS_RW_INTERLEAVED);
  42.  
  43. /* Signed 16-bit little-endian format */
  44. snd_pcm_hw_params_set_format(handle, params,
  45. SND_PCM_FORMAT_S16_LE);
  46.  
  47. /* Two channels (stereo) */
  48. snd_pcm_hw_params_set_channels(handle, params, );
  49.  
  50. /* 44100 bits/second sampling rate (CD quality) */
  51. val = ;
  52. snd_pcm_hw_params_set_rate_near(handle, params,
  53. &val, &dir);
  54.  
  55. /* Set period size to 32 frames. */
  56. frames = ;
  57. snd_pcm_hw_params_set_period_size_near(handle,
  58. params, &frames, &dir);
  59.  
  60. /* Write the parameters to the driver */
  61. rc = snd_pcm_hw_params(handle, params);
  62. if (rc < ) {
  63. fprintf(stderr,
  64. "unable to set hw parameters: %s\n",
  65. snd_strerror(rc));
  66. exit();
  67. }
  68.  
  69. /* Use a buffer large enough to hold one period */
  70. snd_pcm_hw_params_get_period_size(params,
  71. &frames, &dir);
  72. size = frames * ; /* 2 bytes/sample, 2 channels */
  73. buffer = (char *) malloc(size);
  74.  
  75. /* We want to loop for 5 seconds */
  76. snd_pcm_hw_params_get_period_time(params,
  77. &val, &dir);
  78. loops = / val;
  79.  
  80. while (loops > ) {
  81. loops--;
  82. rc = snd_pcm_readi(handle, buffer, frames);
  83. if (rc == -EPIPE) {
  84. /* EPIPE means overrun */
  85. fprintf(stderr, "overrun occurred\n");
  86. snd_pcm_prepare(handle);
  87. } else if (rc < ) {
  88. fprintf(stderr,
  89. "error from read: %s\n",
  90. snd_strerror(rc));
  91. } else if (rc != (int)frames) {
  92. fprintf(stderr, "short read, read %d frames\n", rc);
  93. }
  94. rc = write(, buffer, size);
  95. if (rc != size)
  96. fprintf(stderr,
  97. "short write: wrote %d bytes\n", rc);
  98. }
  99.  
  100. snd_pcm_drain(handle);
  101. snd_pcm_close(handle);
  102. free(buffer);
  103.  
  104. return ;
  105. }

Listing 4和Listing3很相像,除了执行了PCM capture。在我们打开PCM流时,我们指定了操作模式为SND_PCM_STREAM_CAPTURE。主循环中,使用snd_pcm_readi从本地声音硬件读取samples,然后把采样输出到标准输出。如果你有一个microphone连接到声卡,使用mixer程序设置录音源和级别。相应的,你可以运行一个CD player程序然后设置录音源为CD。如果你把listing4的输出重定向到一个文件,那么你可以用listing3来播放listing4的录音数据:

  1. ./listing4 > sound.raw
  2. ./listing3 < sound.raw

如果你的声卡支持全双工,那么你可以通过管道把这个执行命令连接起来,可以播放录音数据

  1. ./listing4 | ./listing3

通过修改PCM参数,你可以体验采样率和格式变化的效果。

Advanced Features

在前面的例子中,PCM流工作在阻塞模式,也就是说,在数据传输结束完之前不会函数不会返回。在一个交互式为主导的应用中,这种情况将使得应用长时间无响应。ALSA允许非阻塞模式操纵流,这种方式下read/write操作立刻返回,如果数据传输无法立刻进行,那么read/write立刻返回一个EBUSY错误码。
有些图形界面应用使用事件callback机制。ALSA支持异步PCM stream,当一个period的采样数据完成以后,注册的callback被调用。
snd_pcm_readi和snd_pcm_writei调用类似于linux的read/write系统调用。字母i表示帧是interleaved;相对的是non-interleaved模式。Linux下的一些设备也支持mmap系统调用,ALSA支持mmap模式打开PCM
channel,应用层不需要数据copy就可以有效访问声音数据

Conclusion

我希望这篇文章能够成为你使用ALSA的动力。当2.6 kernel被大部分发行版使用后,ALSA的使用更加广泛,它的高级features应该能帮助linux audio应用开发者。
感谢Jaroslav Kysela和Takashi Iwai reviewing本文的初稿并提供了有用的反馈。

ubuntu alsa2的更多相关文章

  1. 关于ubuntu实机与虚机互相copy

    我的开发环境是在ubuntu上的,但是ubuntu上没有官方支持的QQ,有些不太方便,所以在上面虚了一个Win7(先是win10,但是win10最新版本太坑了,不说了),不过经常会出现复制文件,或者文 ...

  2. 在Ubuntu下搭建ASP.NET 5开发环境

    在Ubuntu下搭建ASP.NET 5开发环境 0x00 写在前面的废话 年底这段时间实在太忙了,各种事情都凑在这个时候,没时间去学习自己感兴趣的东西,所以博客也好就没写了.最近工作上有个小功能要做成 ...

  3. Ubuntu 14.04中Elasticsearch集群配置

    Ubuntu 14.04中Elasticsearch集群配置 前言:本文可用于elasticsearch集群搭建参考.细分为elasticsearch.yml配置和系统配置 达到的目的:各台机器配置成 ...

  4. 在Ubuntu 16.10 安装 git 并上传代码至 git.oschina.net

    1. 注册一个账号和创建项目 先在git.oschina.net上注册一个账号和新建一个project ,如project name 是"myTest". 2.安装git sudo ...

  5. 在Ubuntu 16.10安装mysql workbench报未安装软件包 libpng12-0错误

    1.安装mysql workbench,提示未安装软件包 libpng12-0 下载了MySQL Workbench 6.3.8   在安装的时候报错: -1ubu1604-amd64.deb 提示: ...

  6. Ubuntu 16.10 安装KolourPaint 4画图工具

    KolourPaint 4画图工具简单实用,可以绘画.视频处理和图标编辑: • 绘画:绘制图表和“手绘” • 视频处理:编辑截图和照片;应用特效 • 图标编辑:绘画剪贴和标识透明化 1.在Ubuntu ...

  7. 使用ubuntu作为web开发环境的一些感受

    从ms-dos,win95,win98,winMe,winXp,vista,win7,win10我都有使用的经历,我使用时间最长的应属winxp,其次是win7,说实话,我觉得这两个系统是微软做的最好 ...

  8. ubuntu系统下如何修改host

    Ubuntu系统的Hosts只需修改/etc/hosts文件,在目录中还有一个hosts.conf文件,刚开始还以为只需要修改这个就可以了,结果发现是需要修改hosts.修改完之后要重启网络.具体过程 ...

  9. Mac OS、Ubuntu 安装及使用 Consul

    Consul 概念(摘录): Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发现的方案,比如 Airbnb 的 SmartStac ...

随机推荐

  1. eclipse配置google代码风格

    1.下载google code style的xml文件 地址:https://github.com/google/styleguide 导入xml文件 可能会遇到警告: 版本的问题,忽略即可. < ...

  2. Gearman的使用

    对于分布式网络环境或者有大量任务的应用,我们需要将任务在不同的服务器之间进行分布,这个时候正好是Gearman发挥实力的时候.虽然我们也可以使用MQ队列再加一些自己实现的调度算法来将任务进行分发,但是 ...

  3. Javascript动态操作CSS总结

    一.使用js操作css属性的写法 1.对于没有中划线的css属性一般直接使用style.属性名即可. 如:obj.style.margin,obj.style.width,obj.style.left ...

  4. SQL Server 2012 books

    SQL Server 2012 Introducing Microsoft SQL Server 2012 Microsoft SQL Server 2012 High-Performance T-S ...

  5. nano 命令 linux

    用途说明 nano是一个字符终端的文本编辑器,有点像DOS下的editor程序.它比vi/vim要简单得多,比较适合Linux初学者使用.某些Linux发行版的默认编辑器就是nano.(nano - ...

  6. hashMap put方法 第二行代码

    if (table == EMPTY_TABLE) { inflateTable(threshold); } table transient Entry<K,V>[] table = (E ...

  7. 关于Unity中天空盒的使用

    天空盒 是一个盒子,一个正方形的盒子.其实本身也是一种shader,需要材质球做载体. 1: 一个场景是由6幅正方形的纹理图无缝拼接而成, 在视野看来位于真实的视野一样;2: 两种天空盒: 场景天空盒 ...

  8. 关于Unity中Vector2和Vector3的使用

    Vector2是用来定义和描述2D游戏内部的一些参数,像刚体的速度等等 Vector3是 1.鼠标点击屏幕后要转化为3D坐标的时候用到的定义和描述 2.两个物体之间的相对距离,或者说偏移量的变量类型

  9. python 进行后端分页详细代码

    后端分页 两个接口 思路: 1. 先得到最大页和最小页数(1, 20) --> 传递给前端, 这样前端就可以知道有多少个页数 2. 通过传递页数得到当前页对应数据库的最大值和最小值 3. 通过s ...

  10. (笔记)Mysql实例:建库建表并插入数据2

    drop database if exists school;  // 如果存在SCHOOL则删除create database school;  // 建立库SCHOOLuse school;  / ...