From: Steinar H. Gunderson Date: Tue, 10 Nov 2015 23:07:37 +0000 (+0100) Subject: Add a soundcard output via ALSA. X-Git-Tag: 1.0.0~135 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=194872611b21bb070f4dfcf5beda6c36d25459ea Add a soundcard output via ALSA. --- diff --git a/Makefile b/Makefile index 91ebdde..6a8c445 100644 --- 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 9208ab1..ebd9fff 100644 --- 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 index 0000000..52f4936 --- /dev/null +++ b/alsa_output.cpp @@ -0,0 +1,84 @@ +#include "alsa_output.h" + +#include + +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 &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 index 0000000..12af16e --- /dev/null +++ b/alsa_output.h @@ -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 + +#include + +class ALSAOutput { +public: + ALSAOutput(int sample_rate, int num_channels); + void write(const std::vector &samples); + +private: + snd_pcm_t *pcm_handle; + std::vector buffer; + snd_pcm_uframes_t period_size; + int num_channels; +}; + +#endif // !defined(_ALSA_OUTPUT_H) diff --git a/mixer.cpp b/mixer.cpp index 0e362f0..5fa13d5 100644 --- 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 252f961..89a80dd 100644 --- a/mixer.h +++ b/mixer.h @@ -22,6 +22,7 @@ #include #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 compressor_threshold_dbfs{ref_level_dbfs - 12.0f}; // -12 dB. std::atomic compressor_enabled{true}; + + std::unique_ptr alsa; }; extern Mixer *global_mixer;