summaryrefslogtreecommitdiffhomepage
path: root/PCM.h
blob: bfa1e24ab9ba3cc3ce1debb6430e540852f0330e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#pragma once

#include "ClickStream.h"

#include "config.h"

#include <alsa/asoundlib.h>
#include <fmt/format.h>

#include <iostream>
#include <string>

using namespace std::string_literals;

class PCM
{
public:
  PCM(ClickStream& stream): m_stream(stream)
  {
    // non-blocking
    if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
      throw std::runtime_error(fmt::format("Playback open error: {}", snd_strerror(err)));
    }
    if ((err = snd_pcm_set_params(handle,
                                  SND_PCM_FORMAT_S16_LE,
                                  SND_PCM_ACCESS_RW_INTERLEAVED,
                                  1,
                                  f_sample,
                                  1,
                                  100000)) < 0) {   // latency in us
      throw std::runtime_error(fmt::format("Playback open error: {}", snd_strerror(err)));
    }

    m_stream.generate();

    npfd = snd_pcm_poll_descriptors_count(handle);
    if (npfd < 0) {
      throw std::runtime_error("snd_pcm_poll_descriptors_count() failed");
    }
    pfd = (struct pollfd *)malloc(npfd * sizeof(struct pollfd));
    if (pfd == nullptr) {
      throw std::runtime_error("alloca() error for PCM");
    }
    if (0 > snd_pcm_poll_descriptors(handle, pfd, npfd))
    {
      throw std::runtime_error("snd_pcm_poll_descriptors() failure");
    }

    if (0 > snd_pcm_start(handle))
    {
     throw std::runtime_error("PCM could not be started");
    }

    if (npfd != 1) {
      std::cout << "Warning: " << std::to_string(npfd) << " poll fds for pcm" << std::endl;
    } else if (fd() <= 2) {
      std::cout << "Warning: Bad PCM fd: " << std::to_string(fd()) << std::endl;
    }
  }

  ~PCM()
  {
    // pass the remaining samples, otherwise they're dropped in close
    err = snd_pcm_drain(handle);
    if (err < 0)
        std::cerr << fmt::format("snd_pcm_drain failed: {}", snd_strerror(err)) << std::endl;
    snd_pcm_close(handle);

    free(pfd);
  }

  int fd()
  {
    return pfd->fd;
  }

  // write from buffer to ALSA PCM
  void write()
  {
    snd_pcm_sframes_t written = snd_pcm_writei(handle, m_stream.get_buffer(), nframes);
    if (written < 0) {
        if (written == -EPIPE) {
          std::cout << "Warning: PCM underrun" << std::endl;
        }
        std::cout << "Recovering." << std::endl;
        written = snd_pcm_recover(handle, written, 0);
    }
    if (written < 0) {
        throw std::runtime_error("snd_pcm_writei failed: "s + snd_strerror(written));
    }

    if (written != nframes) {
      std::cout << "Warning: written " << std::to_string(written) << " frames instead of "<< std::to_string(nframes) << std::endl;
    }

    m_stream.generate();
  }

  bool wait_for_event()
  {
    int result = poll(pfd, npfd, 10000);
    if (result > 0)
    {
      // event
      return true;
    }
    else if (result == 0) {
      // timeout
      return false;
    } else {
      throw std::runtime_error("Poll unsuccessful");
    }
  }

  bool write_available()
  {
    snd_pcm_sframes_t result = snd_pcm_avail(handle);
    if (0 > result) {
      std::cerr << "Error: snd_pcm_avail()" << std::endl;
      //throw std::runtime_error("snd_pcm_avail()");
    }

    return result >= nframes;
  }

private:
  int err;
  snd_pcm_t *handle;

  int npfd;
  struct pollfd* pfd;

  ClickStream& m_stream;
};