linux如何播放一段音乐(ALSA)
How does linux play a piece of music (ALSA)
我最近对'how to play a tone in linux'感兴趣,因为我想link数学和音乐结合起来。我想使用系统调用来执行此操作,因为这样我就不必使用 *.mp3
或 *.wav
等音乐文件。我已经在互联网上进行了研究,但只有答案对于 'how to play a music file in a program' 这样的事情。
我认为 linux 上有一个设备文件,例如 leds (/sys/class/leds/.../brightness
) 或 usbs (/dev/usb/
)。但是我的电脑上没有/dev/audio
、/dev/dsp
、/dev/sound
所以我想知道如何linux播放音乐文件,并从那里开始实现我的目标。
我的问题是 不是 "how to play music [file] in linux",而是 INSTEAD "how linux plays music (ALSA)"。 "how to play a tone in program" 上的回答也是可以接受的。
ALSA是一个内核驱动程序,提供对众多声卡的支持。它通常由希望与声音系统直接交互的低级应用程序使用。
ALSA 提供了一些示例 library API that you can use. Take a look at the documentation 并在正确的方向上提供帮助。
使用 ALSA,您可以访问缓冲区,并将样本放入其中,声音设备将播放这些样本。这是通过 PCM(脉冲编码调制)完成的。使用 ALSA,您需要进行很多配置(如 here 所示)。您要配置通道数量(单声道、立体声等)、样本大小(8 位、16 位等)、速率(8000 Hz、16000 Hz、44100 Hz 等)。例如,您使用 snd_pcm_writei
将这些示例写入 PCM 设备。
ALSA 库的定义位于 alsa/asoundlib.h
。如果您使用的是 GCC,那么您可以 link 使用带有 -lasound
的 ALSA 库。
并非所有音乐播放器都会使用这些低级交互。许多软件都建立在 ALSA 之上,以便为声音系统提供更通用的接口(甚至与平台无关)。声音服务器的示例包括 JACK and PulseAudio。这些声音服务器的优点是它们通常更易于设置和使用,但不能像使用 ALSA 那样提供精细的控制。
为了 LINUX 播放声音(任何类型,例如 mp3/wave/etc。)它使用库可以是 ALSA。参见 here。
ASLA 项目支持许多声卡,您可以在他们的 WiKi 中看到一些提示,了解如何查看您的声卡是否受支持以及如何测试它。
如果您要为新声卡添加驱动程序,您应该记住需要处理 2 个独立的流程:
- 配置 HW (CODEC) - 通常这是由 I2C 总线完成的,这会配置 h/w。例如:设置均衡器,set/mono/stero,设置模拟放大器等。
- 数据流 - 数据是文件从 linux 堆栈到 h/w 的实际流。您将需要创建缓冲区等来处理流式传输,您可以将 ALSA API 用于 start/stop record/play
这是一个非常广阔的领域,因此在尝试编写您自己的驱动程序之前最好先查看一些已经存在的示例。
尝试在 ALSA 代码中添加打印以查看开始播放音频文件时发生了什么。
在很多方面你的问题都像 "could someone explain to me how to catch fish?"。有这么多的方法和工具可用,每个答案虽然在技术上都是正确的,但只是说明了回答者是如何做的,从拖网渔船的工作人员,到飞钓者再到鱼叉捕鱼者。
linux 上的音频是一个主题,就像狂野西部的水,"Whisky's for drinking, Water's for fighting over"。
只是为了好玩,请尝试以下链接以了解复杂性:
https://ubuntuforums.org/showthread.php?t=843012
http://alsa.opensrc.org/MultipleCards
但是给你一个"Tone"的例子,可以从命令行运行(并且可以写入代码,python和C肯定)加载gstreamer-1.0在你的盒子上 运行 以下内容:
gst-launch-1.0 audiotestsrc freq=329.63 volume=0.5 ! autoaudiosink
gst-launch-1.0 audiotestsrc freq=987.77 ! autoaudiosink
gst-launch-1.0 audiotestsrc wave=2 freq=200 volume=0.2 ! tee name=t ! queue ! audioconvert ! autoaudiosink t. ! queue ! audioconvert ! libvisual_lv_scope ! videoconvert ! autovideosink
然后查看:
https://gstreamer.freedesktop.org/documentation/plugins.html
注意:gstreamer 只是飞钓者的故事,那是战斗的谈话!
这里有一些 Gtk 代码供您使用:
#!/usr/bin/env python
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk
class Tone(object):
def __init__(self):
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
window.set_title("Tone-Player")
window.set_default_size(500, 200)
window.connect("destroy", Gtk.main_quit, "WM destroy")
vbox = Gtk.VBox()
window.add(vbox)
self.tone_entry = Gtk.Entry()
self.tone_entry.set_text('300.00')
vbox.pack_start(self.tone_entry, False, False, 0)
self.button = Gtk.Button("Start")
vbox.add(self.button)
self.button.connect("clicked", self.start_stop)
window.show_all()
self.player = Gst.Pipeline.new("player")
source = Gst.ElementFactory.make("audiotestsrc", "tone-source")
audioconv = Gst.ElementFactory.make("audioconvert", "converter")
audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output")
self.player.add(source)
self.player.add(audioconv)
self.player.add(audiosink)
source.link(audioconv)
audioconv.link(audiosink)
def start_stop(self, w):
if self.button.get_label() == "Start":
self.button.set_label("Stop")
tone = float(self.tone_entry.get_text())
self.player.get_by_name("tone-source").set_property("freq", tone)
self.player.set_state(Gst.State.PLAYING)
else:
self.player.set_state(Gst.State.NULL)
self.button.set_label("Start")
GObject.threads_init()
Gst.init(None)
Tone()
Gtk.main()
这是一些 C 代码,它用音频数据填充内存缓冲区,然后使用 OpenAL 渲染音频数据 - 没有音频文件
// sudo apt-get install libopenal-dev
//
// gcc -o gen_tone gen_tone.c -lopenal -lm
//
#include <stdio.h>
#include <stdlib.h> // gives malloc
#include <math.h>
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#elif __linux
#include <AL/al.h>
#include <AL/alc.h>
#endif
ALCdevice * openal_output_device;
ALCcontext * openal_output_context;
ALuint internal_buffer;
ALuint streaming_source[1];
int al_check_error(const char * given_label) {
ALenum al_error;
al_error = alGetError();
if(AL_NO_ERROR != al_error) {
printf("ERROR - %s (%s)\n", alGetString(al_error), given_label);
return al_error;
}
return 0;
}
void MM_init_al() {
const char * defname = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
openal_output_device = alcOpenDevice(defname);
openal_output_context = alcCreateContext(openal_output_device, NULL);
alcMakeContextCurrent(openal_output_context);
// setup buffer and source
alGenBuffers(1, & internal_buffer);
al_check_error("failed call to alGenBuffers");
}
void MM_exit_al() {
ALenum errorCode = 0;
// Stop the sources
alSourceStopv(1, & streaming_source[0]); // streaming_source
int ii;
for (ii = 0; ii < 1; ++ii) {
alSourcei(streaming_source[ii], AL_BUFFER, 0);
}
// Clean-up
alDeleteSources(1, &streaming_source[0]);
alDeleteBuffers(16, &streaming_source[0]);
errorCode = alGetError();
alcMakeContextCurrent(NULL);
errorCode = alGetError();
alcDestroyContext(openal_output_context);
alcCloseDevice(openal_output_device);
}
void MM_render_one_buffer() {
/* Fill buffer with Sine-Wave */
// float freq = 440.f;
float freq = 850.f;
float incr_freq = 0.1f;
int seconds = 4;
// unsigned sample_rate = 22050;
unsigned sample_rate = 44100;
double my_pi = 3.14159;
size_t buf_size = seconds * sample_rate;
short * samples = malloc(sizeof(short) * buf_size);
printf("\nhere is freq %f\n", freq);
int i=0;
for(; i<buf_size; ++i) {
samples[i] = 32760 * sin( (2.f * my_pi * freq)/sample_rate * i );
freq += incr_freq;
// incr_freq += incr_freq;
// freq *= factor_freq;
if (100.0 > freq || freq > 5000.0) {
incr_freq *= -1.0f;
}
}
/* upload buffer to OpenAL */
alBufferData( internal_buffer, AL_FORMAT_MONO16, samples, buf_size, sample_rate);
al_check_error("populating alBufferData");
free(samples);
/* Set-up sound source and play buffer */
// ALuint src = 0;
// alGenSources(1, &src);
// alSourcei(src, AL_BUFFER, internal_buffer);
alGenSources(1, & streaming_source[0]);
alSourcei(streaming_source[0], AL_BUFFER, internal_buffer);
// alSourcePlay(src);
alSourcePlay(streaming_source[0]);
// ---------------------
ALenum current_playing_state;
alGetSourcei(streaming_source[0], AL_SOURCE_STATE, & current_playing_state);
al_check_error("alGetSourcei AL_SOURCE_STATE");
while (AL_PLAYING == current_playing_state) {
printf("still playing ... so sleep\n");
sleep(1); // should use a thread sleep NOT sleep() for a more responsive finish
alGetSourcei(streaming_source[0], AL_SOURCE_STATE, & current_playing_state);
al_check_error("alGetSourcei AL_SOURCE_STATE");
}
printf("end of playing\n");
/* Dealloc OpenAL */
MM_exit_al();
} // MM_render_one_buffer
int main() {
MM_init_al();
MM_render_one_buffer();
}
也可以将其转换为音频服务器,在重复填充内存缓冲区时呈现音频
我最近对'how to play a tone in linux'感兴趣,因为我想link数学和音乐结合起来。我想使用系统调用来执行此操作,因为这样我就不必使用 *.mp3
或 *.wav
等音乐文件。我已经在互联网上进行了研究,但只有答案对于 'how to play a music file in a program' 这样的事情。
我认为 linux 上有一个设备文件,例如 leds (/sys/class/leds/.../brightness
) 或 usbs (/dev/usb/
)。但是我的电脑上没有/dev/audio
、/dev/dsp
、/dev/sound
所以我想知道如何linux播放音乐文件,并从那里开始实现我的目标。
我的问题是 不是 "how to play music [file] in linux",而是 INSTEAD "how linux plays music (ALSA)"。 "how to play a tone in program" 上的回答也是可以接受的。
ALSA是一个内核驱动程序,提供对众多声卡的支持。它通常由希望与声音系统直接交互的低级应用程序使用。
ALSA 提供了一些示例 library API that you can use. Take a look at the documentation 并在正确的方向上提供帮助。
使用 ALSA,您可以访问缓冲区,并将样本放入其中,声音设备将播放这些样本。这是通过 PCM(脉冲编码调制)完成的。使用 ALSA,您需要进行很多配置(如 here 所示)。您要配置通道数量(单声道、立体声等)、样本大小(8 位、16 位等)、速率(8000 Hz、16000 Hz、44100 Hz 等)。例如,您使用 snd_pcm_writei
将这些示例写入 PCM 设备。
ALSA 库的定义位于 alsa/asoundlib.h
。如果您使用的是 GCC,那么您可以 link 使用带有 -lasound
的 ALSA 库。
并非所有音乐播放器都会使用这些低级交互。许多软件都建立在 ALSA 之上,以便为声音系统提供更通用的接口(甚至与平台无关)。声音服务器的示例包括 JACK and PulseAudio。这些声音服务器的优点是它们通常更易于设置和使用,但不能像使用 ALSA 那样提供精细的控制。
为了 LINUX 播放声音(任何类型,例如 mp3/wave/etc。)它使用库可以是 ALSA。参见 here。 ASLA 项目支持许多声卡,您可以在他们的 WiKi 中看到一些提示,了解如何查看您的声卡是否受支持以及如何测试它。
如果您要为新声卡添加驱动程序,您应该记住需要处理 2 个独立的流程:
- 配置 HW (CODEC) - 通常这是由 I2C 总线完成的,这会配置 h/w。例如:设置均衡器,set/mono/stero,设置模拟放大器等。
- 数据流 - 数据是文件从 linux 堆栈到 h/w 的实际流。您将需要创建缓冲区等来处理流式传输,您可以将 ALSA API 用于 start/stop record/play
这是一个非常广阔的领域,因此在尝试编写您自己的驱动程序之前最好先查看一些已经存在的示例。 尝试在 ALSA 代码中添加打印以查看开始播放音频文件时发生了什么。
在很多方面你的问题都像 "could someone explain to me how to catch fish?"。有这么多的方法和工具可用,每个答案虽然在技术上都是正确的,但只是说明了回答者是如何做的,从拖网渔船的工作人员,到飞钓者再到鱼叉捕鱼者。
linux 上的音频是一个主题,就像狂野西部的水,"Whisky's for drinking, Water's for fighting over"。
只是为了好玩,请尝试以下链接以了解复杂性:
https://ubuntuforums.org/showthread.php?t=843012
http://alsa.opensrc.org/MultipleCards
但是给你一个"Tone"的例子,可以从命令行运行(并且可以写入代码,python和C肯定)加载gstreamer-1.0在你的盒子上 运行 以下内容:
gst-launch-1.0 audiotestsrc freq=329.63 volume=0.5 ! autoaudiosink
gst-launch-1.0 audiotestsrc freq=987.77 ! autoaudiosink
gst-launch-1.0 audiotestsrc wave=2 freq=200 volume=0.2 ! tee name=t ! queue ! audioconvert ! autoaudiosink t. ! queue ! audioconvert ! libvisual_lv_scope ! videoconvert ! autovideosink
然后查看:
https://gstreamer.freedesktop.org/documentation/plugins.html
注意:gstreamer 只是飞钓者的故事,那是战斗的谈话!
这里有一些 Gtk 代码供您使用:
#!/usr/bin/env python
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk
class Tone(object):
def __init__(self):
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
window.set_title("Tone-Player")
window.set_default_size(500, 200)
window.connect("destroy", Gtk.main_quit, "WM destroy")
vbox = Gtk.VBox()
window.add(vbox)
self.tone_entry = Gtk.Entry()
self.tone_entry.set_text('300.00')
vbox.pack_start(self.tone_entry, False, False, 0)
self.button = Gtk.Button("Start")
vbox.add(self.button)
self.button.connect("clicked", self.start_stop)
window.show_all()
self.player = Gst.Pipeline.new("player")
source = Gst.ElementFactory.make("audiotestsrc", "tone-source")
audioconv = Gst.ElementFactory.make("audioconvert", "converter")
audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output")
self.player.add(source)
self.player.add(audioconv)
self.player.add(audiosink)
source.link(audioconv)
audioconv.link(audiosink)
def start_stop(self, w):
if self.button.get_label() == "Start":
self.button.set_label("Stop")
tone = float(self.tone_entry.get_text())
self.player.get_by_name("tone-source").set_property("freq", tone)
self.player.set_state(Gst.State.PLAYING)
else:
self.player.set_state(Gst.State.NULL)
self.button.set_label("Start")
GObject.threads_init()
Gst.init(None)
Tone()
Gtk.main()
这是一些 C 代码,它用音频数据填充内存缓冲区,然后使用 OpenAL 渲染音频数据 - 没有音频文件
// sudo apt-get install libopenal-dev
//
// gcc -o gen_tone gen_tone.c -lopenal -lm
//
#include <stdio.h>
#include <stdlib.h> // gives malloc
#include <math.h>
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#elif __linux
#include <AL/al.h>
#include <AL/alc.h>
#endif
ALCdevice * openal_output_device;
ALCcontext * openal_output_context;
ALuint internal_buffer;
ALuint streaming_source[1];
int al_check_error(const char * given_label) {
ALenum al_error;
al_error = alGetError();
if(AL_NO_ERROR != al_error) {
printf("ERROR - %s (%s)\n", alGetString(al_error), given_label);
return al_error;
}
return 0;
}
void MM_init_al() {
const char * defname = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
openal_output_device = alcOpenDevice(defname);
openal_output_context = alcCreateContext(openal_output_device, NULL);
alcMakeContextCurrent(openal_output_context);
// setup buffer and source
alGenBuffers(1, & internal_buffer);
al_check_error("failed call to alGenBuffers");
}
void MM_exit_al() {
ALenum errorCode = 0;
// Stop the sources
alSourceStopv(1, & streaming_source[0]); // streaming_source
int ii;
for (ii = 0; ii < 1; ++ii) {
alSourcei(streaming_source[ii], AL_BUFFER, 0);
}
// Clean-up
alDeleteSources(1, &streaming_source[0]);
alDeleteBuffers(16, &streaming_source[0]);
errorCode = alGetError();
alcMakeContextCurrent(NULL);
errorCode = alGetError();
alcDestroyContext(openal_output_context);
alcCloseDevice(openal_output_device);
}
void MM_render_one_buffer() {
/* Fill buffer with Sine-Wave */
// float freq = 440.f;
float freq = 850.f;
float incr_freq = 0.1f;
int seconds = 4;
// unsigned sample_rate = 22050;
unsigned sample_rate = 44100;
double my_pi = 3.14159;
size_t buf_size = seconds * sample_rate;
short * samples = malloc(sizeof(short) * buf_size);
printf("\nhere is freq %f\n", freq);
int i=0;
for(; i<buf_size; ++i) {
samples[i] = 32760 * sin( (2.f * my_pi * freq)/sample_rate * i );
freq += incr_freq;
// incr_freq += incr_freq;
// freq *= factor_freq;
if (100.0 > freq || freq > 5000.0) {
incr_freq *= -1.0f;
}
}
/* upload buffer to OpenAL */
alBufferData( internal_buffer, AL_FORMAT_MONO16, samples, buf_size, sample_rate);
al_check_error("populating alBufferData");
free(samples);
/* Set-up sound source and play buffer */
// ALuint src = 0;
// alGenSources(1, &src);
// alSourcei(src, AL_BUFFER, internal_buffer);
alGenSources(1, & streaming_source[0]);
alSourcei(streaming_source[0], AL_BUFFER, internal_buffer);
// alSourcePlay(src);
alSourcePlay(streaming_source[0]);
// ---------------------
ALenum current_playing_state;
alGetSourcei(streaming_source[0], AL_SOURCE_STATE, & current_playing_state);
al_check_error("alGetSourcei AL_SOURCE_STATE");
while (AL_PLAYING == current_playing_state) {
printf("still playing ... so sleep\n");
sleep(1); // should use a thread sleep NOT sleep() for a more responsive finish
alGetSourcei(streaming_source[0], AL_SOURCE_STATE, & current_playing_state);
al_check_error("alGetSourcei AL_SOURCE_STATE");
}
printf("end of playing\n");
/* Dealloc OpenAL */
MM_exit_al();
} // MM_render_one_buffer
int main() {
MM_init_al();
MM_render_one_buffer();
}
也可以将其转换为音频服务器,在重复填充内存缓冲区时呈现音频