【问题标题】:How does linux play a piece of music (ALSA)linux如何播放一段音乐(ALSA)
【发布时间】:2016-12-30 04:27:42
【问题描述】:

我最近对“如何在 linux 中演奏音调”感兴趣,因为我想将数学和音乐联系在一起。我想使用系统调用来做到这一点,因为这样我就不必使用像 *.mp3*.wav 这样的音乐文件。我已经在互联网上进行了研究,但只有像'这样的东西的答案如何在程序中播放音乐文件'。

我以为 linux 上有一个设备文件,比如 leds (/sys/class/leds/.../brightness) 或 usbs (/dev/usb/)。但是我的电脑上没有/dev/audio/dev/dsp/dev/sound

所以我想知道linux如何播放音乐文件,并从那里开始实现我的目标。

我的问题是不是“如何在 linux 中播放音乐 [文件]”,而是 INSTEAD“linux 如何播放音乐 (ALSA)”。回答“如何在程序中播放音调”也是可以接受的。

【问题讨论】:

    标签: linux audio alsa


    【解决方案1】:

    ALSA 是一个内核驱动程序,它为众多声卡提供支持。它通常由想要直接与声音系统交互的低级应用程序使用。

    ALSA 提供了一个您可以使用的library API。看看documentation 中的一些示例,并为正确的方向提供帮助。

    使用 ALSA,您可以访问缓冲区,并将样本放入其中,然后由声音设备播放。这是通过 PCM(脉冲编码调制)完成的。使用 ALSA,您可以进行很多配置(如 here 所示)。您想要配置通道数量(单声道、立体声等)、样本大小(8 位、16 位等)、速率(8000 Hz、16000 Hz、44100 Hz 等)。例如,您可以使用snd_pcm_writei 将这些样本写入 PCM 设备。

    ALSA 库的定义位于alsa/asoundlib.h。如果您使用的是 GCC,那么您可以使用 -lasound 链接到 ALSA 库。

    并非所有音乐播放器都会使用这些低级交互。许多软件都建立在 ALSA 之上,为声音系统提供更通用的接口(甚至独立于平台)。声音服务器的示例包括JACKPulseAudio。这些声音服务器的优点是它们通常更易于设置和使用,但不能提供您使用 ALSA 所拥有的精细控制。

    【讨论】:

    • 我应该包含标题alsa/asoundlib.h 以使用该功能吗?还是那个文件?而且,这是大多数音乐播放器使用的方法吗?
    • 知道了。既然你提到了pulseaudio,它也有图书馆吗?你也可以提供这些信息吗?
    • @Chromium 我不熟悉 PulseAudio 的详细信息,但 PulseAudio documentationthis question 应该会带你走向正确的方向。
    • 谢谢。但是您提到的问题中的link 已损坏。
    • @Chromium 发挥创意并寻找其他资源。谷歌“wav 格式”,它应该会引导您找到大量有用的资源。
    【解决方案2】:

    为了让 LINUX 播放声音(任何类型,如 mp3/wave/等),它使用了一个库可以 ALSA。见here。 ASLA 项目支持许多声卡,您可以在他们的 WiKi 中看到一些关于如何查看您的声卡是否受支持以及如何对其进行测试的提示。

    如果您要为新声卡添加驱动程序,您应该记住有 2 个单独的流程需要处理:

    1. 配置硬件(编解码器) - 通常这是由 I2C 总线制作的,这会配置硬件。例如:设置均衡器、设置/单声道/立体声、设置设置模拟放大器等。
    2. 数据流 - 数据是文件从 linux 堆栈到硬件的实际流。您将需要创建缓冲区等来处理流式传输,并且您可以使用 ALSA API 来开始/停止记录/播放

    这是一个非常广阔的领域,因此在尝试编写自己的驱动程序之前最好先查看一些已经存在的示例。 尝试沿着 ALSA 代码添加打印,以查看开始播放音频文件时发生的情况。

    【讨论】:

      【解决方案3】:

      在很多方面,您的问题都类似于“有人可以向我解释如何捕鱼吗?”。有这么多方法和这么多可用的工具,每个答案虽然在技术上是正确的,但只是说明了回答的人的方式,从拖网渔船的工作人员到飞钓者到长矛钓者。
      linux 上的音频就像狂野西部的水一样,“威士忌用于饮用,水用于争夺”。 只是为了好玩,请尝试以下链接以了解复杂性:

      https://ubuntuforums.org/showthread.php?t=843012

      http://alsa.opensrc.org/MultipleCards

      但是给你一个可以从命令行运行的“音调”示例(并且可以写入代码、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()
      

      【讨论】:

      • 有没有办法一次播放两个频率?
      • 最简单的创建 self.player2 的方式与 self.player 相同,这将提供组合输出。我想可能有一种方法可以将它们左右分开,但您必须自己进行研究。
      • audioconvaudiosink有什么用?
      • 它们只是变量名,分别分配了 gstreamer 的 audioconvert 和 autoaudiosink 函数。 Audioconvert 在各种可能的格式之间转换原始音频缓冲区,可以说在这种情况下不需要。 Autoaudiosink 是输出,在您的机器上自动检测到适当的音频接收器。请参阅:我在答案中提供的 gstreamer 链接。
      【解决方案4】:

      这里是一些 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();
      }
      

      也可以将其转换为音频服务器,在重复填充内存缓冲区时呈现音频

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-04
        • 1970-01-01
        • 2017-04-17
        • 2013-02-01
        • 1970-01-01
        • 2017-01-03
        相关资源
        最近更新 更多