]> git.sesse.net Git - nageru/commitdiff
Add a soundcard output via ALSA.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 10 Nov 2015 23:07:37 +0000 (00:07 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 10 Nov 2015 23:10:06 +0000 (00:10 +0100)
Makefile
README
alsa_output.cpp [new file with mode: 0644]
alsa_output.h [new file with mode: 0644]
mixer.cpp
mixer.h

index 91ebdde2610ca730d3b486f39506fccbdb1aa243..6a8c445c27d2e8a95d99825cffefce59886d9b54 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,14 @@
 CXX=g++
 PKG_MODULES = Qt5Core Qt5Gui Qt5Widgets Qt5OpenGLExtensions Qt5OpenGL libusb-1.0 movit lua5.2 libmicrohttpd
 CXXFLAGS := -O2 -march=native -g -std=gnu++11 -Wall -Wno-deprecated-declarations -Werror -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -pthread -DMOVIT_SHADER_DIR=\"$(shell pkg-config --variable=shaderdir movit)\"
-LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lswscale -lzita-resampler
+LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lswscale -lzita-resampler -lasound
 
 # Qt objects
 OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o
 OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o
 
 # Mixer objects
-OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampling_queue.o httpd.o ebu_r128_proc.o flags.o image_input.o stereocompressor.o filter.o
+OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampling_queue.o httpd.o ebu_r128_proc.o flags.o image_input.o stereocompressor.o filter.o alsa_output.o
 
 %.o: %.cpp
        $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
diff --git a/README b/README
index 9208ab172883789b4a5955ceb6864f6d3b47b497..ebd9fff9c777acb6ba1c97c937c22d4d29e20361 100644 (file)
--- a/README
+++ b/README
@@ -11,7 +11,7 @@ Features (those marked with * are still in progress or not started yet):
    for intermediate calculations, dithered output.
 
  - Proper sound support: Syncing of multiple unrelated sources through
-   high-quality resampling, mixing (*), cue out for headphones (*),
+   high-quality resampling, mixing (*), cue out for headphones,
    dynamic range compression, fixed EQ, level meters conforming to EBU R128.
 
  - Theme engine encapsulating the design demands of each individual
diff --git a/alsa_output.cpp b/alsa_output.cpp
new file mode 100644 (file)
index 0000000..52f4936
--- /dev/null
@@ -0,0 +1,84 @@
+#include "alsa_output.h"
+
+#include <vector>
+
+using namespace std;
+
+namespace {
+
+void die_on_error(const char *func_name, int err)
+{
+       if (err < 0) {
+               fprintf(stderr, "%s: %s\n", func_name, snd_strerror(err));
+               exit(1);
+       }
+}
+
+}  // namespace
+
+ALSAOutput::ALSAOutput(int sample_rate, int num_channels)
+       : num_channels(num_channels)
+{
+       die_on_error("snd_pcm_open()", snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0));
+
+       // Set format.
+       snd_pcm_hw_params_t *hw_params;
+       snd_pcm_hw_params_alloca(&hw_params);
+       die_on_error("snd_pcm_hw_params_any()", snd_pcm_hw_params_any(pcm_handle, hw_params));
+       die_on_error("snd_pcm_hw_params_set_access()", snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED));
+       die_on_error("snd_pcm_hw_params_set_format()", snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_FLOAT_LE));
+       die_on_error("snd_pcm_hw_params_set_rate()", snd_pcm_hw_params_set_rate(pcm_handle, hw_params, sample_rate, 0));
+       die_on_error("snd_pcm_hw_params_set_channels", snd_pcm_hw_params_set_channels(pcm_handle, hw_params, num_channels));
+
+       // Fragment size of 512 samples. (A frame at 60 fps/48 kHz is 800 samples.)
+       // We ask for four such periods.
+       unsigned int num_periods = 4;
+       int dir = 0;
+       die_on_error("snd_pcm_hw_params_set_periods_near", snd_pcm_hw_params_set_periods_near(pcm_handle, hw_params, &num_periods, &dir));
+       period_size = 512;
+       dir = 0;
+       die_on_error("snd_pcm_hw_params_set_period_size_near", snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &period_size, &dir));
+       die_on_error("snd_pcm_hw_params", snd_pcm_hw_params(pcm_handle, hw_params));
+       //snd_pcm_hw_params_free(hw_params);
+
+       die_on_error("snd_pcm_nonblock", snd_pcm_nonblock(pcm_handle, 1));
+}
+
+void ALSAOutput::write(const vector<float> &samples)
+{
+       buffer.insert(buffer.end(), samples.begin(), samples.end());
+
+try_again:
+       int periods_to_write = buffer.size() / (period_size * num_channels);
+       if (periods_to_write == 0) {
+               return;
+       }
+
+       int ret = snd_pcm_writei(pcm_handle, buffer.data(), periods_to_write * period_size);
+       if (ret == -EPIPE) {
+               fprintf(stderr, "warning: snd_pcm_writei() reported underrun\n");
+               snd_pcm_recover(pcm_handle, ret, 1);
+               goto try_again;
+       } else if (ret == -EAGAIN) {
+               ret = 0;
+       } else if (ret < 0) {
+               fprintf(stderr, "error: snd_pcm_writei() returned '%s'\n", snd_strerror(ret));
+               exit(1);
+       } else if (ret > 0) {
+               buffer.erase(buffer.begin(), buffer.begin() + ret * num_channels);
+       }
+
+       if (buffer.size() >= period_size * num_channels) {  // Still more to write.
+               if (ret == 0) {
+                       if (buffer.size() >= period_size * num_channels * 8) {
+                               // OK, almost 100 ms. Giving up.
+                               fprintf(stderr, "warning: ALSA overrun, dropping some audio\n");
+                               buffer.clear();
+                       }
+               } else if (ret > 0) {
+                       // Not a completely failure (effectively a short write),
+                       // possibly due to a signal.
+                       goto try_again;
+               }
+       }
+}
diff --git a/alsa_output.h b/alsa_output.h
new file mode 100644 (file)
index 0000000..12af16e
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef _ALSA_OUTPUT_H
+#define _ALSA_OUTPUT_H 1
+
+// Extremely minimalistic ALSA output. Will not resample to fit
+// sound card clock, will not care much about over- or underflows
+// (so it will not block), will not care about A/V sync.
+//
+// This means that if you run it for long enough, clocks will
+// probably drift out of sync enough to make a little pop.
+
+#include <alsa/asoundlib.h>
+
+#include <vector>
+
+class ALSAOutput {
+public:
+       ALSAOutput(int sample_rate, int num_channels);
+       void write(const std::vector<float> &samples);
+
+private:
+       snd_pcm_t *pcm_handle;
+       std::vector<float> buffer;
+       snd_pcm_uframes_t period_size;
+       int num_channels;
+};
+
+#endif  // !defined(_ALSA_OUTPUT_H)
index 0e362f069395874c837dac1bf732c9e1d32d25a0..5fa13d5cc224890a7050f04642538ad2a3e3b84d 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -157,6 +157,8 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        // hlen=16 is pretty low quality, but we use quite a bit of CPU otherwise,
        // and there's a limit to how important the peak meter is.
        peak_resampler.setup(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY * 4, /*num_channels=*/2, /*hlen=*/16);
+
+       alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
 }
 
 Mixer::~Mixer()
@@ -623,7 +625,12 @@ void Mixer::process_audio_one_frame()
        float *ptrs[] = { left.data(), right.data() };
        r128.process(left.size(), ptrs);
 
-       // Actually add the samples to the output.
+       // Send the samples to the sound card.
+       if (alsa) {
+               alsa->write(samples_out);
+       }
+
+       // And finally add them to the output.
        h264_encoder->add_audio(pts_int, move(samples_out));
 }
 
diff --git a/mixer.h b/mixer.h
index 252f961f7668cbf4ceaeab2364bf2196c799131d..89a80dd52fabaccd8e81f1743c491f41b4d329ff 100644 (file)
--- a/mixer.h
+++ b/mixer.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "bmusb/bmusb.h"
+#include "alsa_output.h"
 #include "ebu_r128_proc.h"
 #include "h264encode.h"
 #include "httpd.h"
@@ -262,6 +263,8 @@ private:
        StereoCompressor compressor;
        std::atomic<float> compressor_threshold_dbfs{ref_level_dbfs - 12.0f};  // -12 dB.
        std::atomic<bool> compressor_enabled{true};
+
+       std::unique_ptr<ALSAOutput> alsa;
 };
 
 extern Mixer *global_mixer;