]> git.sesse.net Git - nageru/commitdiff
Add a class for ALSA audio input. (No enumeration yet.)
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 7 Aug 2016 19:26:47 +0000 (21:26 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 19 Oct 2016 22:55:44 +0000 (00:55 +0200)
Makefile
alsa_input.cpp [new file with mode: 0644]
alsa_input.h [new file with mode: 0644]

index 8479712db521d7055c09e491fa6aa4e8f61085f5..1aae6cd4f94757215fe249dcb1ce814c39801e97 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@ OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o correlation
 OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o correlation_meter.moc.o aboutdialog.moc.o ellipsis_label.moc.o input_mapping_dialog.moc.o
 
 # Mixer objects
-OBJS += mixer.o audio_mixer.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 correlation_measurer.o disk_space_estimator.o
+OBJS += mixer.o audio_mixer.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_input.o alsa_output.o correlation_measurer.o disk_space_estimator.o
 
 # Streaming and encoding objects
 OBJS += quicksync_encoder.o x264_encoder.o x264_speed_control.o video_encoder.o metacube2.o mux.o audio_encoder.o ffmpeg_raii.o
diff --git a/alsa_input.cpp b/alsa_input.cpp
new file mode 100644 (file)
index 0000000..fc710a3
--- /dev/null
@@ -0,0 +1,146 @@
+
+#include "alsa_input.h"
+
+using namespace std;
+
+ALSAInput::ALSAInput(const char *device, unsigned sample_rate, unsigned num_channels, audio_callback_t audio_callback)
+       : device(device), sample_rate(sample_rate), num_channels(num_channels), audio_callback(audio_callback)
+{
+       die_on_error(device, snd_pcm_open(&pcm_handle,device, SND_PCM_STREAM_CAPTURE, 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));
+       snd_pcm_format_mask_t *format_mask;
+       snd_pcm_format_mask_alloca(&format_mask);
+       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S16_LE);
+       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S24_LE);
+       snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S32_LE);
+       die_on_error("snd_pcm_hw_params_set_format()", snd_pcm_hw_params_set_format_mask(pcm_handle, hw_params, format_mask));
+       die_on_error("snd_pcm_hw_params_set_rate_near()", snd_pcm_hw_params_set_rate_near(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));
+
+       die_on_error("snd_pcm_hw_params_set_channels()", snd_pcm_hw_params_set_channels(pcm_handle, hw_params, num_channels));
+
+       // Fragment size of 64 samples (about 1 ms at 48 kHz; a frame at 60
+       // fps/48 kHz is 800 samples.) We ask for 64 such periods in our buffer
+       // (~85 ms buffer); more than that, and our jitter is probably so high
+       // that the resampling queue can't keep up anyway.
+       // The entire thing with periods and such is a bit mysterious to me;
+       // seemingly I can get 96 frames at a time with no problems even if
+       // the period size is 64 frames. And if I set num_periods to e.g. 1,
+       // I can't have a big buffer.
+       num_periods = 16;
+       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 = 64;
+       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));
+       buffer_frames = 64 * 64;
+       die_on_error("snd_pcm_hw_params_set_buffer_size_near()", snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params, &buffer_frames));
+       die_on_error("snd_pcm_hw_params()", snd_pcm_hw_params(pcm_handle, hw_params));
+       //snd_pcm_hw_params_free(hw_params);
+
+       // Figure out which format the card actually chose.
+       die_on_error("snd_pcm_hw_params_current()", snd_pcm_hw_params_current(pcm_handle, hw_params));
+       snd_pcm_format_t chosen_format;
+       die_on_error("snd_pcm_hw_params_get_format()", snd_pcm_hw_params_get_format(hw_params, &chosen_format));
+
+       audio_format.num_channels = num_channels;
+       audio_format.bits_per_sample = 0;
+       switch (chosen_format) {
+       case SND_PCM_FORMAT_S16_LE:
+               audio_format.bits_per_sample = 16;
+               break;
+       case SND_PCM_FORMAT_S24_LE:
+               audio_format.bits_per_sample = 24;
+               break;
+       case SND_PCM_FORMAT_S32_LE:
+               audio_format.bits_per_sample = 32;
+               break;
+       default:
+               assert(false);
+       }
+       //printf("num_periods=%u period_size=%u buffer_frames=%u sample_rate=%u bits_per_sample=%d\n",
+       //      num_periods, unsigned(period_size), unsigned(buffer_frames), sample_rate, audio_format.bits_per_sample);
+
+       buffer.reset(new uint8_t[buffer_frames * num_channels * audio_format.bits_per_sample / 8]);
+
+       snd_pcm_sw_params_t *sw_params;
+       snd_pcm_sw_params_alloca(&sw_params);
+       die_on_error("snd_pcm_sw_params_current()", snd_pcm_sw_params_current(pcm_handle, sw_params));
+       die_on_error("snd_pcm_sw_params_set_start_threshold", snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, num_periods * period_size / 2));
+       die_on_error("snd_pcm_sw_params()", snd_pcm_sw_params(pcm_handle, sw_params));
+
+       die_on_error("snd_pcm_nonblock()", snd_pcm_nonblock(pcm_handle, 1));
+       die_on_error("snd_pcm_prepare()", snd_pcm_prepare(pcm_handle));
+
+}
+
+ALSAInput::~ALSAInput()
+{
+       die_on_error("snd_pcm_close()", snd_pcm_close(pcm_handle));
+}
+
+void ALSAInput::start_capture_thread()
+{
+       should_quit = false;
+       capture_thread = thread(&ALSAInput::capture_thread_func, this);
+}
+
+void ALSAInput::stop_capture_thread()
+{
+       should_quit = true;
+       capture_thread.join();
+}
+
+void ALSAInput::capture_thread_func()
+{
+       die_on_error("snd_pcm_start()", snd_pcm_start(pcm_handle));
+       uint64_t num_frames_output = 0;
+       while (!should_quit) {
+               int ret = snd_pcm_wait(pcm_handle, /*timeout=*/100);
+               if (ret == 0) continue;  // Timeout.
+               if (ret == -EPIPE) {
+                       fprintf(stderr, "[%s] ALSA overrun\n", device.c_str());
+                       snd_pcm_prepare(pcm_handle);
+                       snd_pcm_start(pcm_handle);
+                       continue;
+               }
+               die_on_error("snd_pcm_wait()", ret);
+
+               snd_pcm_sframes_t frames = snd_pcm_readi(pcm_handle, buffer.get(), buffer_frames);
+               if (frames == -EPIPE) {
+                       fprintf(stderr, "[%s] ALSA overrun\n", device.c_str());
+                       snd_pcm_prepare(pcm_handle);
+                       snd_pcm_start(pcm_handle);
+                       continue;
+               }
+               if (frames == 0) {
+                       fprintf(stderr, "snd_pcm_readi() returned 0\n");
+                       break;
+               }
+               die_on_error("snd_pcm_readi()", frames);
+
+               const int64_t prev_pts = frames_to_pts(num_frames_output);
+               const int64_t pts = frames_to_pts(num_frames_output + frames);
+               audio_callback(buffer.get(), frames, audio_format, pts - prev_pts);
+               num_frames_output += frames;
+       }
+}
+
+int64_t ALSAInput::frames_to_pts(uint64_t n) const
+{
+       return (n * TIMEBASE) / sample_rate;
+}
+
+void ALSAInput::die_on_error(const char *func_name, int err)
+{
+       if (err < 0) {
+               fprintf(stderr, "[%s] %s: %s\n", device.c_str(), func_name, snd_strerror(err));
+               exit(1);
+       }
+}
+
diff --git a/alsa_input.h b/alsa_input.h
new file mode 100644 (file)
index 0000000..724b640
--- /dev/null
@@ -0,0 +1,55 @@
+#ifndef _ALSA_INPUT_H
+#define _ALSA_INPUT_H 1
+
+// ALSA sound input, running in a separate thread and sending audio back
+// in callbacks.
+//
+// Note: “frame” here generally refers to the ALSA definition of frame,
+// which is a set of samples, exactly one for each channel. The only exception
+// is in frame_length, where it means the TIMEBASE length of the buffer
+// as a whole, since that's what AudioMixer::add_audio() wants.
+
+#include <alsa/asoundlib.h>
+
+#include <atomic>
+#include <functional>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "bmusb/bmusb.h"
+#include "timebase.h"
+
+class ALSAInput {
+public:
+       typedef std::function<void(const uint8_t *data, unsigned num_samples, bmusb::AudioFormat audio_format, int64_t frame_length)> audio_callback_t;
+
+       ALSAInput(const char *device, unsigned sample_rate, unsigned num_channels, audio_callback_t audio_callback);
+       ~ALSAInput();
+
+       // NOTE: Might very well be different from the sample rate given to the
+       // constructor, since the card might not support the one you wanted.
+       unsigned get_sample_rate() const { return sample_rate; }
+
+       void start_capture_thread();
+       void stop_capture_thread();
+
+private:
+       void capture_thread_func();
+       int64_t frames_to_pts(uint64_t n) const;
+       void die_on_error(const char *func_name, int err);
+
+       std::string device;
+       unsigned sample_rate, num_channels, num_periods;
+       snd_pcm_uframes_t period_size;
+       snd_pcm_uframes_t buffer_frames;
+       bmusb::AudioFormat audio_format;
+       audio_callback_t audio_callback;
+
+       snd_pcm_t *pcm_handle;
+       std::thread capture_thread;
+       std::atomic<bool> should_quit{false};
+       std::unique_ptr<uint8_t[]> buffer;
+};
+
+#endif  // !defined(_ALSA_INPUT_H)