Skip to content
This repository was archived by the owner on Oct 19, 2023. It is now read-only.

Commit 7835bc7

Browse files
committed
Merge branch 'github/master'
2 parents fc14d8a + 030cb14 commit 7835bc7

File tree

3 files changed

+77
-37
lines changed

3 files changed

+77
-37
lines changed

google-assistant-sdk/googlesamples/assistant/grpc/audio_helpers.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515
"""Helper functions for audio streams."""
1616

17+
import array
1718
import logging
18-
import threading
19+
import math
1920
import time
21+
import threading
2022
import wave
21-
import math
22-
import array
2323

2424
import click
2525
import sounddevice as sd
@@ -165,6 +165,9 @@ def start(self):
165165
def stop(self):
166166
pass
167167

168+
def flush(self):
169+
pass
170+
168171

169172
class SoundDeviceStream(object):
170173
"""Audio stream based on an underlying sound device.
@@ -207,7 +210,7 @@ def write(self, buf):
207210
return len(buf)
208211

209212
def flush(self):
210-
if self._flush_size > 0:
213+
if self._audio_stream.active and self._flush_size > 0:
211214
self._audio_stream.write(b'\x00' * self._flush_size)
212215

213216
def start(self):
@@ -218,7 +221,6 @@ def start(self):
218221
def stop(self):
219222
"""Stop the underlying stream."""
220223
if self._audio_stream.active:
221-
self.flush()
222224
self._audio_stream.stop()
223225

224226
def close(self):
@@ -264,29 +266,43 @@ def __init__(self, source, sink, iter_size, sample_width):
264266
self._sink = sink
265267
self._iter_size = iter_size
266268
self._sample_width = sample_width
267-
self._stop_recording = threading.Event()
268-
self._start_playback = threading.Event()
269269
self._volume_percentage = 50
270+
self._stop_recording = threading.Event()
271+
self._source_lock = threading.RLock()
272+
self._recording = False
273+
self._playing = False
270274

271275
def start_recording(self):
272276
"""Start recording from the audio source."""
277+
self._recording = True
273278
self._stop_recording.clear()
274279
self._source.start()
275-
self._sink.start()
276280

277281
def stop_recording(self):
278282
"""Stop recording from the audio source."""
279283
self._stop_recording.set()
284+
with self._source_lock:
285+
self._source.stop()
286+
self._recording = False
280287

281288
def start_playback(self):
282289
"""Start playback to the audio sink."""
283-
self._start_playback.set()
290+
self._playing = True
291+
self._sink.start()
284292

285293
def stop_playback(self):
286294
"""Stop playback from the audio sink."""
287-
self._start_playback.clear()
288-
self._source.stop()
295+
self._sink.flush()
289296
self._sink.stop()
297+
self._playing = False
298+
299+
@property
300+
def recording(self):
301+
return self._recording
302+
303+
@property
304+
def playing(self):
305+
return self._playing
290306

291307
@property
292308
def volume_percentage(self):
@@ -299,19 +315,13 @@ def volume_percentage(self, new_volume_percentage):
299315

300316
def read(self, size):
301317
"""Read bytes from the source (if currently recording).
302-
303-
Will returns an empty byte string, if stop_recording() was called.
304318
"""
305-
if self._stop_recording.is_set():
306-
return b''
307-
return self._source.read(size)
319+
with self._source_lock:
320+
return self._source.read(size)
308321

309322
def write(self, buf):
310323
"""Write bytes to the sink (if currently playing).
311-
312-
Will block until start_playback() is called.
313324
"""
314-
self._start_playback.wait()
315325
buf = align_buf(buf, self._sample_width)
316326
buf = normalize_audio_buffer(buf, self.volume_percentage)
317327
return self._sink.write(buf)
@@ -323,7 +333,10 @@ def close(self):
323333

324334
def __iter__(self):
325335
"""Returns a generator reading data from the stream."""
326-
return iter(lambda: self.read(self._iter_size), b'')
336+
while True:
337+
if self._stop_recording.is_set():
338+
raise StopIteration
339+
yield self.read(self._iter_size)
327340

328341
@property
329342
def sample_rate(self):

google-assistant-sdk/googlesamples/assistant/grpc/pushtotalk.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,26 +121,29 @@ def assist(self):
121121
self.conversation_stream.start_recording()
122122
logging.info('Recording audio request.')
123123

124-
def iter_assist_requests():
124+
def iter_log_assist_requests():
125125
for c in self.gen_assist_requests():
126126
assistant_helpers.log_assist_request_without_audio(c)
127127
yield c
128-
self.conversation_stream.start_playback()
128+
logging.debug('Reached end of AssistRequest iteration.')
129129

130130
# This generator yields AssistResponse proto messages
131131
# received from the gRPC Google Assistant API.
132-
for resp in self.assistant.Assist(iter_assist_requests(),
132+
for resp in self.assistant.Assist(iter_log_assist_requests(),
133133
self.deadline):
134134
assistant_helpers.log_assist_response_without_audio(resp)
135135
if resp.event_type == END_OF_UTTERANCE:
136-
logging.info('End of audio request detected')
136+
logging.info('End of audio request detected.')
137+
logging.info('Stopping recording.')
137138
self.conversation_stream.stop_recording()
138139
if resp.speech_results:
139140
logging.info('Transcript of user request: "%s".',
140141
' '.join(r.transcript
141142
for r in resp.speech_results))
142-
logging.info('Playing assistant response.')
143143
if len(resp.audio_out.audio_data) > 0:
144+
if not self.conversation_stream.playing:
145+
self.conversation_stream.start_playback()
146+
logging.info('Playing assistant response.')
144147
self.conversation_stream.write(resp.audio_out.audio_data)
145148
if resp.dialog_state_out.conversation_state:
146149
conversation_state = resp.dialog_state_out.conversation_state

google-assistant-sdk/tests/test_audio_helpers.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import unittest
1717

1818
import time
19-
import threading
2019
import wave
2120

2221
from googlesamples.assistant.grpc import audio_helpers
@@ -87,12 +86,29 @@ def test_write_header(self):
8786
self.assertEqual(b'RIFF', self.stream.getvalue()[:4])
8887

8988

90-
class DummyStream(BytesIO):
89+
class DummyStream(BytesIO, object):
90+
started = False
91+
stopped = False
92+
flushed = False
93+
9194
def start(self):
92-
pass
95+
self.started = True
9396

9497
def stop(self):
95-
pass
98+
self.stopped = True
99+
100+
def read(self, *args):
101+
if self.stopped:
102+
return b''
103+
return super(DummyStream, self).read(*args)
104+
105+
def write(self, *args):
106+
if not self.started:
107+
return
108+
return super(DummyStream, self).write(*args)
109+
110+
def flush(self):
111+
self.flushed = True
96112

97113

98114
class ConversationStreamTest(unittest.TestCase):
@@ -114,17 +130,25 @@ def test_stop_recording(self):
114130

115131
def test_start_playback(self):
116132
self.playback_started = False
117-
118-
def start_playback():
119-
self.playback_started = True
120-
self.stream.start_playback()
121-
t = threading.Timer(0.1, start_playback)
122-
t.start()
123-
# write will block until start_playback is called.
124133
self.stream.write(b'foo')
125-
self.assertEqual(True, self.playback_started)
134+
self.assertEqual(b'', self.sink.getvalue())
135+
self.stream.start_playback()
136+
self.stream.write(b'foo')
126137
self.assertEqual(b'foo\0', self.sink.getvalue())
127138

139+
def test_sink_source_state(self):
140+
self.assertEquals(False, self.source.started)
141+
self.stream.start_recording()
142+
self.assertEquals(True, self.source.started)
143+
self.stream.stop_recording()
144+
self.assertEquals(True, self.source.stopped)
145+
146+
self.assertEquals(False, self.sink.started)
147+
self.stream.start_playback()
148+
self.assertEquals(True, self.sink.started)
149+
self.stream.stop_playback()
150+
self.assertEquals(True, self.sink.stopped)
151+
128152
def test_oneshot_conversation(self):
129153
self.assertEqual(b'audio', self.stream.read(5))
130154
self.stream.stop_recording()

0 commit comments

Comments
 (0)