summarylogtreecommitdiffstats
path: root/00-bluetooth-buffer.patch
blob: ef80d721fe171b207f884c7fa14ddf228ef42a39 (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
199
200
201
202
203
204
205
206
207
208
From 12b13c75d3a9b377e0f7de7c86116e3af41ce5ee Mon Sep 17 00:00:00 2001
From: Dmitry Kalyanov <Kalyanov.Dmitry@gmail.com>
Date: Sat, 7 May 2016 21:53:42 +0300
Subject: [PATCH] Naive rewrite of IO thread function. Almost fully eliminates
 lags after bluetooth packet loss.

---
 src/modules/bluetooth/module-bluez5-device.c | 130 ++++++++++++++-------------
 1 file changed, 67 insertions(+), 63 deletions(-)

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 84e6d55..b96a80d 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -649,6 +649,29 @@ static int a2dp_process_push(struct userdata *u) {
     return ret;
 }
 
+static void update_buffer_size(struct userdata *u) {
+    int old_bufsize, new_bufsize;
+    socklen_t len = sizeof(int);
+    int rc;
+    
+    rc = getsockopt(u->stream_fd, SOL_SOCKET, SO_SNDBUF, &old_bufsize, &len);
+    if (rc == -1) {
+        pa_log_warn("Changing bluetooth buffer size: Failed to getsockopt(SO_SNDBUF): %s", pa_cstrerror(errno));
+    } else {
+        bool ok = false;
+        for (int n = 2; !ok && n < 16; ++n) {
+            new_bufsize = n * u->write_link_mtu;
+            rc = setsockopt(u->stream_fd, SOL_SOCKET, SO_SNDBUF, &new_bufsize, len);
+            if (rc == -1) {
+                pa_log_warn("Changing bluetooth buffer size: Failed to change from %d to %d: %s", old_bufsize, new_bufsize, pa_cstrerror(errno));
+            } else {
+                ok = true;
+                pa_log_info("Changing bluetooth buffer size: Changed from %d to %d", old_bufsize, new_bufsize);
+            }
+        }
+    }
+}
+
 /* Run from I/O thread */
 static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
     struct sbc_info *sbc_info;
@@ -683,6 +706,8 @@ static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) {
     pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
     pa_sink_set_fixed_latency_within_thread(u->sink,
             FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+    
+    update_buffer_size(u);
 }
 
 /* Run from I/O thread */
@@ -814,6 +839,8 @@ static void setup_stream(struct userdata *u) {
 
     if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
         a2dp_set_bitpool(u, u->sbc_info.max_bitpool);
+    
+    update_buffer_size(u);
 
     u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
     pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
@@ -1327,6 +1354,7 @@ static void thread_func(void *userdata) {
     unsigned do_write = 0;
     unsigned pending_read_bytes = 0;
     bool writable = false;
+    pa_usec_t ts_last_frame;
 
     pa_assert(u);
     pa_assert(u->transport);
@@ -1341,11 +1369,16 @@ static void thread_func(void *userdata) {
     /* Setup the stream only if the transport was already acquired */
     if (u->transport_acquired)
         setup_stream(u);
+    u->started_at = pa_rtclock_now();
 
+    ts_last_frame = u->started_at;
+    
     for (;;) {
         struct pollfd *pollfd;
         int ret;
         bool disable_timer = true;
+        pa_usec_t ts_now = pa_rtclock_now();
+        pa_usec_t ts_next_frame = ts_last_frame + pa_bytes_to_usec(u->write_block_size, &u->sample_spec);
 
         pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
 
@@ -1404,86 +1437,57 @@ static void thread_func(void *userdata) {
                 if (pollfd->revents & POLLOUT)
                     writable = true;
 
-                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {
-                    pa_usec_t time_passed;
-                    pa_usec_t audio_sent;
+                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state))) {
 
-                    /* Hmm, there is no input stream we could synchronize
-                     * to. So let's do things by time */
+                    if (ts_now >= ts_next_frame) {
 
-                    time_passed = pa_rtclock_now() - u->started_at;
-                    audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+                        if (writable) {
+                            int n_written;
+
+                            if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+                                if ((n_written = a2dp_process_render(u)) < 0)
+                                    goto fail;
+                            } else {
+                                if ((n_written = sco_process_render(u)) < 0)
+                                    goto fail;
+                            }
 
-                    if (audio_sent <= time_passed) {
-                        pa_usec_t audio_to_send = time_passed - audio_sent;
+                            if (n_written == 0)
+                                pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!");
 
-                        /* Never try to catch up for more than 100ms */
-                        if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) {
+                            do_write -= n_written;
+                            writable = false;
+                        } else {
                             pa_usec_t skip_usec;
                             uint64_t skip_bytes;
+                            pa_memchunk tmp;
 
-                            skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC;
+                            skip_usec = ts_now - ts_last_frame;
                             skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec);
 
-                            if (skip_bytes > 0) {
-                                pa_memchunk tmp;
+                            pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
+                                        (unsigned long long) skip_usec,
+                                        (unsigned long long) skip_bytes);
 
-                                pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
-                                            (unsigned long long) skip_usec,
-                                            (unsigned long long) skip_bytes);
+                            pa_sink_render_full(u->sink, skip_bytes, &tmp);
+                            pa_memblock_unref(tmp.memblock);
+                            u->write_index += skip_bytes;
 
-                                pa_sink_render_full(u->sink, skip_bytes, &tmp);
-                                pa_memblock_unref(tmp.memblock);
-                                u->write_index += skip_bytes;
-
-                                if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
-                                    a2dp_reduce_bitpool(u);
-                            }
+                            if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
+                                a2dp_reduce_bitpool(u);
                         }
-
-                        do_write = 1;
+                        ts_last_frame = ts_now;
+                        // TODO: ts_last_frame might be slightly inaccurate; it should depend on ts_next_frame
                         pending_read_bytes = 0;
                     }
-                }
-
-                if (writable && do_write > 0) {
-                    int n_written;
 
-                    if (u->write_index <= 0)
-                        u->started_at = pa_rtclock_now();
+                    {
+                        pa_usec_t ts_now2 = pa_rtclock_now();
+                        pa_usec_t sleep_for = ts_now2 > ts_next_frame ? 0 : ts_next_frame - ts_now2;
 
-                    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
-                        if ((n_written = a2dp_process_render(u)) < 0)
-                            goto fail;
-                    } else {
-                        if ((n_written = sco_process_render(u)) < 0)
-                            goto fail;
+                        pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for);
+                        disable_timer = false;
                     }
-
-                    if (n_written == 0)
-                        pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!");
-
-                    do_write -= n_written;
-                    writable = false;
-                }
-
-                if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0) {
-                    pa_usec_t sleep_for;
-                    pa_usec_t time_passed, next_write_at;
-
-                    if (writable) {
-                        /* Hmm, there is no input stream we could synchronize
-                         * to. So let's estimate when we need to wake up the latest */
-                        time_passed = pa_rtclock_now() - u->started_at;
-                        next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
-                        sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
-                        /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
-                    } else
-                        /* drop stream every 500 ms */
-                        sleep_for = PA_USEC_PER_MSEC * 500;
-
-                    pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for);
-                    disable_timer = false;
                 }
             }
         }