audiostream.cpp 7.29 KB
Newer Older
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
Guillaume Roguez's avatar
Guillaume Roguez committed
3
 *
4
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
5
 *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
6
7
8
9
10
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
11
 *
12
13
14
15
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
16
 *
17
18
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
19
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
20
21
 */

22
#include "audiostream.h"
Yun Liu's avatar
Yun Liu committed
23
#include "pulselayer.h"
24
#include "logger.h"
25
#include "compiler_intrinsics.h"
26

27
#include <stdexcept>
28

Adrien Béraud's avatar
Adrien Béraud committed
29
namespace jami {
30

Sébastien Blin's avatar
Sébastien Blin committed
31
32
33
AudioStream::AudioStream(pa_context* c,
                         pa_threaded_mainloop* m,
                         const char* desc,
34
                         AudioDeviceType type,
35
                         unsigned samplrate,
36
                         const PaDeviceInfos& infos,
37
                         bool ec,
38
                         OnReady onReady, OnData onData)
Sébastien Blin's avatar
Sébastien Blin committed
39
    : onReady_(std::move(onReady))
40
41
    , onData_(std::move(onData))
    , audiostream_(nullptr)
Sébastien Blin's avatar
Sébastien Blin committed
42
    , mainloop_(m)
Emmanuel Milou's avatar
Emmanuel Milou committed
43
{
Sébastien Blin's avatar
Sébastien Blin committed
44
45
    pa_sample_spec sample_spec = {PA_SAMPLE_S16LE, // PA_SAMPLE_FLOAT32LE,
                                  samplrate,
46
                                  infos.channel_map.channels};
47

48
    JAMI_DBG("%s: Creating stream with device %s (%dHz, %d channels)",
Sébastien Blin's avatar
Sébastien Blin committed
49
             desc,
50
             infos.name.c_str(),
Sébastien Blin's avatar
Sébastien Blin committed
51
             samplrate,
52
             infos.channel_map.channels);
53

54
    assert(pa_sample_spec_valid(&sample_spec));
55
    assert(pa_channel_map_valid(&infos.channel_map));
Rafaël Carré's avatar
* #6629    
Rafaël Carré committed
56

Sébastien Blin's avatar
Sébastien Blin committed
57
58
    std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl(pa_proplist_new(),
                                                                 pa_proplist_free);
Adrien Béraud's avatar
Adrien Béraud committed
59
    pa_proplist_sets(pl.get(), PA_PROP_FILTER_WANT, "echo-cancel");
Emmanuel Milou's avatar
Emmanuel Milou committed
60

Sébastien Blin's avatar
Sébastien Blin committed
61
62
63
    audiostream_ = pa_stream_new_with_proplist(c,
                                               desc,
                                               &sample_spec,
64
                                               &infos.channel_map,
Sébastien Blin's avatar
Sébastien Blin committed
65
                                               ec ? pl.get() : nullptr);
66
    if (!audiostream_) {
Sébastien Blin's avatar
Sébastien Blin committed
67
        JAMI_ERR("%s: pa_stream_new() failed : %s", desc, pa_strerror(pa_context_errno(c)));
68
        throw std::runtime_error("Could not create stream\n");
Rafaël Carré's avatar
* #6629    
Rafaël Carré committed
69
    }
Emmanuel Milou's avatar
Emmanuel Milou committed
70

Rafaël Carré's avatar
* #6629    
Rafaël Carré committed
71
    pa_buffer_attr attributes;
72
    attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec);
73
74
75
76
    attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
    attributes.prebuf = 0;
    attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
    attributes.minreq = (uint32_t) -1;
Sébastien Blin's avatar
Sébastien Blin committed
77
78
79
80
81
82
83
84
85

    pa_stream_set_state_callback(
        audiostream_,
        [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->stateChanged(s); },
        this);
    pa_stream_set_moved_callback(
        audiostream_,
        [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->moved(s); },
        this);
86

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
    constexpr pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(
        PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_START_CORKED);

    if (type == AudioDeviceType::PLAYBACK || type == AudioDeviceType::RINGTONE) {
        pa_stream_set_write_callback(audiostream_, [](pa_stream* /*s*/, size_t bytes, void* userdata) {
            static_cast<AudioStream*>(userdata)->onData_(bytes);
        }, this);

        pa_stream_connect_playback(audiostream_,
                                    infos.name.empty() ? nullptr : infos.name.c_str(),
                                    &attributes,
                                    flags,
                                    nullptr,
                                    nullptr);
    } else if (type == AudioDeviceType::CAPTURE) {
        pa_stream_set_read_callback(audiostream_, [](pa_stream* /*s*/, size_t bytes, void* userdata) {
            static_cast<AudioStream*>(userdata)->onData_(bytes);
        }, this);

        pa_stream_connect_record(audiostream_,
                                    infos.name.empty() ? nullptr : infos.name.c_str(),
                                    &attributes,
                                    flags);
110
    }
111
112
}

113
114
115
116
117
118
119
120
121
122
void disconnectStream(pa_stream* s) {
    // make sure we don't get any further callback
    pa_stream_set_write_callback(s, nullptr, nullptr);
    pa_stream_set_read_callback(s, nullptr, nullptr);
    pa_stream_set_moved_callback(s, nullptr, nullptr);
    pa_stream_set_underflow_callback(s, nullptr, nullptr);
    pa_stream_set_overflow_callback(s, nullptr, nullptr);
    pa_stream_set_suspended_callback(s, nullptr, nullptr);
    pa_stream_set_started_callback(s, nullptr, nullptr);
}
Emmanuel Milou's avatar
Emmanuel Milou committed
123

124
125
126
127
128
129
void destroyStream(pa_stream* s) {
    pa_stream_disconnect(s);
    pa_stream_set_state_callback(s, nullptr, nullptr);
    disconnectStream(s);
    pa_stream_unref(s);
}
130

131
132
133
AudioStream::~AudioStream()
{
    stop();
134
}
135

136
void
Sébastien Blin's avatar
Sébastien Blin committed
137
138
AudioStream::start()
{
139
140
141
    pa_stream_cork(audiostream_, 0, nullptr, nullptr);
}

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
void
AudioStream::stop()
{
    if (not audiostream_)
        return;
    JAMI_DBG("Destroying stream with device %s", pa_stream_get_device_name(audiostream_));
    if (pa_stream_get_state(audiostream_) == PA_STREAM_CREATING) {
        disconnectStream(audiostream_);
        pa_stream_set_state_callback(audiostream_, [](pa_stream* s, void*){
            destroyStream(s);
        }, nullptr);
    } else {
        destroyStream(audiostream_);
    }
    audiostream_ = nullptr;
}

Sébastien Blin's avatar
Sébastien Blin committed
159
160
void
AudioStream::moved(pa_stream* s)
Adrien Béraud's avatar
Adrien Béraud committed
161
162
{
    audiostream_ = s;
Adrien Béraud's avatar
Adrien Béraud committed
163
    JAMI_DBG("Stream %d to %s", pa_stream_get_index(s), pa_stream_get_device_name(s));
Adrien Béraud's avatar
Adrien Béraud committed
164
165
}

166
void
Adrien Béraud's avatar
Adrien Béraud committed
167
AudioStream::stateChanged(pa_stream* s)
168
{
169
    //UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX];
170

171
    switch (pa_stream_get_state(s)) {
Sébastien Blin's avatar
Sébastien Blin committed
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
    case PA_STREAM_CREATING:
        JAMI_DBG("Stream is creating...");
        break;

    case PA_STREAM_TERMINATED:
        JAMI_DBG("Stream is terminating...");
        break;

    case PA_STREAM_READY:
        JAMI_DBG("Stream successfully created, connected to %s", pa_stream_get_device_name(s));
        // JAMI_DBG("maxlength %u", pa_stream_get_buffer_attr(s)->maxlength);
        // JAMI_DBG("tlength %u", pa_stream_get_buffer_attr(s)->tlength);
        // JAMI_DBG("prebuf %u", pa_stream_get_buffer_attr(s)->prebuf);
        // JAMI_DBG("minreq %u", pa_stream_get_buffer_attr(s)->minreq);
        // JAMI_DBG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize);
        // JAMI_DBG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s)));
        onReady_();
        break;

    case PA_STREAM_UNCONNECTED:
        JAMI_DBG("Stream unconnected");
        break;

    case PA_STREAM_FAILED:
    default:
        JAMI_ERR("Stream failure: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        break;
199
    }
200
201
}

Sébastien Blin's avatar
Sébastien Blin committed
202
203
bool
AudioStream::isReady()
204
{
205
    if (!audiostream_)
206
207
        return false;

208
    return pa_stream_get_state(audiostream_) == PA_STREAM_READY;
209
}
210

Adrien Béraud's avatar
Adrien Béraud committed
211
} // namespace jami