1717from __future__ import division
1818
1919import contextlib
20+ import functools
2021import re
2122import signal
2223import sys
23- import threading
2424
2525from google .cloud import credentials
2626from google .cloud .speech .v1beta1 import cloud_speech_pb2 as cloud_speech
@@ -76,8 +76,7 @@ def _audio_data_generator(buff):
7676 stop = False
7777 while not stop :
7878 # Use a blocking get() to ensure there's at least one chunk of data.
79- chunk = buff .get ()
80- data = [chunk ]
79+ data = [buff .get ()]
8180
8281 # Now consume whatever other data's still buffered.
8382 while True :
@@ -86,54 +85,47 @@ def _audio_data_generator(buff):
8685 except queue .Empty :
8786 break
8887
89- # If `_fill_buffer` adds ` None` to the buffer, the audio stream is
90- # closed. Yield the final bit of the buffer and exit the loop.
88+ # ` None` in the buffer signals that the audio stream is closed. Yield
89+ # the final bit of the buffer and exit the loop.
9190 if None in data :
9291 stop = True
9392 data .remove (None )
93+
9494 yield b'' .join (data )
9595
9696
97- def _fill_buffer (audio_stream , buff , chunk , stoprequest ):
97+ def _fill_buffer (buff , in_data , frame_count , time_info , status_flags ):
9898 """Continuously collect data from the audio stream, into the buffer."""
99- try :
100- while not stoprequest .is_set ():
101- buff .put (audio_stream .read (chunk ))
102- except IOError :
103- pass
104- finally :
105- # Add `None` to the buff, indicating that a stop request is made.
106- # This will signal `_audio_data_generator` to exit.
107- buff .put (None )
99+ buff .put (in_data )
100+ return None , pyaudio .paContinue
108101
109102
110103# [START audio_stream]
111104@contextlib .contextmanager
112- def record_audio (rate , chunk , stoprequest ):
105+ def record_audio (rate , chunk ):
113106 """Opens a recording stream in a context manager."""
107+ # Create a thread-safe buffer of audio data
108+ buff = queue .Queue ()
109+
114110 audio_interface = pyaudio .PyAudio ()
115111 audio_stream = audio_interface .open (
116112 format = pyaudio .paInt16 ,
117113 # The API currently only supports 1-channel (mono) audio
118114 # https://goo.gl/z757pE
119115 channels = 1 , rate = rate ,
120116 input = True , frames_per_buffer = chunk ,
117+ # Run the audio stream asynchronously to fill the buffer object.
118+ # This is necessary so that the input device's buffer doesn't overflow
119+ # while the calling thread makes network requests, etc.
120+ stream_callback = functools .partial (_fill_buffer , buff ),
121121 )
122122
123- # Create a thread-safe buffer of audio data
124- buff = queue .Queue ()
125-
126- # Spin up a separate thread to buffer audio data from the microphone
127- # This is necessary so that the input device's buffer doesn't overflow
128- # while the calling thread makes network requests, etc.
129- fill_buffer_thread = threading .Thread (
130- target = _fill_buffer , args = (audio_stream , buff , chunk , stoprequest ))
131- fill_buffer_thread .start ()
132-
133123 yield _audio_data_generator (buff )
134124
135- fill_buffer_thread . join ()
125+ audio_stream . stop_stream ()
136126 audio_stream .close ()
127+ # Signal the _audio_data_generator to finish
128+ buff .put (None )
137129 audio_interface .terminate ()
138130# [END audio_stream]
139131
@@ -172,7 +164,17 @@ def request_stream(data_stream, rate, interim_results=True):
172164 yield cloud_speech .StreamingRecognizeRequest (audio_content = data )
173165
174166
175- def listen_print_loop (recognize_stream , stoprequest ):
167+ def listen_print_loop (recognize_stream ):
168+ """Iterates through server responses and prints them.
169+
170+ The recognize_stream passed is a generator that will block until a response
171+ is provided by the server. When the transcription response comes, print it.
172+
173+ In this case, responses are provided for interim results as well. If the
174+ response is an interim one, print a line feed at the end of it, to allow
175+ the next result to overwrite it, until the response is a final one. For the
176+ final one, print a newline to preserve the finalized transcription.
177+ """
176178 num_chars_printed = 0
177179 for resp in recognize_stream :
178180 if resp .error .code != code_pb2 .OK :
@@ -204,7 +206,6 @@ def listen_print_loop(recognize_stream, stoprequest):
204206 # one of our keywords.
205207 if re .search (r'\b(exit|quit)\b' , transcript , re .I ):
206208 print ('Exiting..' )
207- stoprequest .set ()
208209 break
209210
210211 num_chars_printed = 0
@@ -213,17 +214,9 @@ def listen_print_loop(recognize_stream, stoprequest):
213214def main ():
214215 with cloud_speech .beta_create_Speech_stub (
215216 make_channel ('speech.googleapis.com' , 443 )) as service :
216-
217- # stoprequest is event object which is set in `listen_print_loop`
218- # to indicate that the trancsription should be stopped.
219- #
220- # The `_fill_buffer` thread checks this object, and closes
221- # the `audio_stream` once it's set.
222- stoprequest = threading .Event ()
223-
224217 # For streaming audio from the microphone, there are three threads.
225218 # First, a thread that collects audio data as it comes in
226- with record_audio (RATE , CHUNK , stoprequest ) as buffered_audio_data :
219+ with record_audio (RATE , CHUNK ) as buffered_audio_data :
227220 # Second, a thread that sends requests with that data
228221 requests = request_stream (buffered_audio_data , RATE )
229222 # Third, a thread that listens for transcription responses
@@ -235,7 +228,7 @@ def main():
235228
236229 # Now, put the transcription responses to use.
237230 try :
238- listen_print_loop (recognize_stream , stoprequest )
231+ listen_print_loop (recognize_stream )
239232
240233 recognize_stream .cancel ()
241234 except face .CancellationError :
0 commit comments