Как Linux воспроизводит музыкальное произведение (ALSA)

В последнее время меня интересует «как играть тембр в Linux», потому что я хочу связать математику и музыку вместе. Я хочу использовать системные вызовы для этого, потому что таким образом мне не нужно использовать музыкальный файл, такой как *.mp3 или *.wav и т. д. Я провел исследования в Интернете, но есть только ответы на такие вещи, как «как играть на музыкальный файл в программе».

Я думал, что в Linux есть файл устройства, например, светодиоды (/sys/class/leds/.../brightness) или USB (/dev/usb/). Но на моем компьютере нет /dev/audio, /dev/dsp, /dev/sound.

Поэтому я хочу знать, как Linux воспроизводит музыкальный файл, и начать с этого, чтобы достичь своей цели.

Мой вопрос НЕ «как воспроизводить музыку [файл] в Linux», а ВМЕСТО «как Linux воспроизводит музыку (ALSA)». Также приемлем ответ на тему "как играть тембр в программе".


person Chromium    schedule 23.08.2016    source источник


Ответы (4)


Во многом ваш вопрос похож на «кто-нибудь может объяснить мне, как ловить рыбу?». Существует так много способов и так много доступных инструментов, что каждый ответ, хотя и технически правильный, просто иллюстрирует то, как это делает человек, который работает на траулере, через нахлыстовика до подводного рыболова.
Аудио в linux это тема, как вода на Диком Западе: «Виски для питья, Вода для борьбы». Просто для удовольствия попробуйте следующие ссылки для представления сложности:

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()
person Rolf of Saxony    schedule 23.08.2016
comment
Есть ли способ воспроизвести две частоты одновременно? - person Chromium; 24.08.2016
comment
В самом простом случае создайте self.player2 так же, как self.player, который даст комбинированный результат. Я предполагаю, что, вероятно, есть способ разделить их налево и направо, но вам придется провести собственное исследование. - person Rolf of Saxony; 24.08.2016
comment
Какая польза от audioconv и audiosink? - person Chromium; 25.08.2016
comment
Это просто имена переменных, которым назначены функции gstreamer audioconvert и autoaudiosink соответственно. Audioconvert преобразует необработанные аудиобуферы между различными возможными форматами и, возможно, в этом сценарии не требуется. Autoaudiosink — это выход, при котором на вашем компьютере автоматически определяется соответствующий аудиоприемник. См.: ссылку на gstreamer, которую я предоставил в ответе. - person Rolf of Saxony; 25.08.2016

ALSA — это драйвер ядра, обеспечивающий поддержку множества звуковых карт. Обычно он используется низкоуровневыми приложениями, которые хотят напрямую взаимодействовать со звуковой системой.

ALSA предлагает API-библиотеку, которую вы можете использовать. Взгляните на документацию для некоторых примеров и помощи в правильном направлении. .

С ALSA вы можете получить доступ к буферу и поместить в него сэмплы, которые будут воспроизводиться звуковым устройством. Это делается с помощью ИКМ (импульсно-кодовой модуляции). С ALSA вам нужно многое настроить (как показано здесь). Вы хотите настроить количество каналов (моно, стерео и т. д.), размер сэмплов (8 бит, 16 бит и т. д.), частоту (8000 Гц, 16000 Гц, 44 100 Гц и т. д.). Например, вы записываете эти сэмплы на устройство PCM с помощью snd_pcm_writei.

Определения библиотеки ALSA находятся в alsa/asoundlib.h. Если вы используете GCC, вы можете связать библиотеку ALSA с помощью -lasound.

Не все музыкальные плееры будут использовать эти низкоуровневые взаимодействия. Большая часть программного обеспечения построена поверх ALSA, чтобы предоставить звуковой системе более общие интерфейсы (даже независимые от платформы). Примеры звуковых серверов включают JACK и PulseAudio. Преимущество этих звуковых серверов заключается в том, что их обычно легче настроить и использовать, но они не дают точного управления, которое вы имели бы с ALSA.

person bzeaman    schedule 23.08.2016
comment
Должен ли я включать заголовок alsa/asoundlib.h для использования функции? или это файл? И это метод, который будет использовать большинство музыкальных плееров? - person Chromium; 23.08.2016
comment
Понятно. И раз уж вы упомянули pulseaudio, у него тоже есть библиотека? Можете ли вы предоставить эту информацию тоже? - person Chromium; 23.08.2016
comment
@Chromium Я не знаком с деталями PulseAudio, но документация PulseAudio и этот вопрос должен привести вас в правильном направлении. - person bzeaman; 23.08.2016
comment
Спасибо. Но ссылка в упомянутом вами вопросе не работает. - person Chromium; 23.08.2016
comment
@Chromium Будьте изобретательны и находите другие ресурсы. Google «формат wav», и это должно привести вас к множеству полезных источников. - person bzeaman; 23.08.2016
comment
с учебником ALSA, который вы предоставляете, я пытаюсь использовать miniFMsynth.c, который он предоставляет. Но когда я компилирую, многие переменные и функции не определены, в том числе snd_pcm_writei. Это указывает на проблему? Примечание. У меня есть только sound/asound.h, и я связываю его с alsa/asoundlib.h. - person Chromium; 30.08.2016
comment
Откройте новый вопрос, и я буду рад помочь. Я не думаю, что вы полностью понимаете компоновку и что такое файлы заголовков. Вы не можете «связать» с заголовочным файлом. Функции должны быть объявлены, но могут не быть определены, если вы неправильно свяжете их. - person bzeaman; 31.08.2016

для того, чтобы LINUX воспроизводил звук (любой, такой как mp3/wave/etc.), он может использовать библиотеку ALSA. см. здесь. Проект ASLA поддерживает множество звуковых карт, и вы можете увидеть в их WiKi несколько советов о том, как узнать, поддерживается ли ваша звуковая карта, и как ее протестировать.

Если вы собираетесь добавить драйвер для новой звуковой карты, вы должны иметь в виду, что есть два отдельных потока, которые необходимо обработать:

  1. настроить HW (CODEC) - обычно это делается по шине I2C, а это настроить h/w. например: установить эквалайзер, установить/моно/стерео, установить установить аналоговые усилители и так далее.
  2. поток данных - данные - это фактическая потоковая передача файла из стека linux в h/w. вам нужно будет создать буферы и т. д. для обработки потоковой передачи, и вы можете использовать API-интерфейсы ALSA для запуска/остановки записи/воспроизведения.

это чрезвычайно обширная область, поэтому лучше увидеть некоторые уже существующие примеры, прежде чем пытаться написать свой собственный драйвер. попробуйте добавить отпечатки по коду ALSA, чтобы увидеть, что происходит при запуске воспроизведения аудиофайла.

person stzahi    schedule 23.08.2016

Вот некоторый код 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();
}

также возможно преобразовать это в аудиосервер, который воспроизводит звук, в то время как буфер памяти повторно заполняется

person Scott Stensland    schedule 25.08.2016