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 $<
--- /dev/null
+#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;
+ }
+ }
+}
--- /dev/null
+#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)
// 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()
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));
}