class_docstr: Asynchronously record and buffer audio using pyaudio.
  
  This class wraps the pyaudio interface. It contains a queue.Queue object to
  hold chunks of raw audio, and a callback function _enqueue_audio() which
  places raw audio into this queue. This allows the pyaudio.Stream object to
  record asynchronously at low latency.
  
  The class acts as a context manager. When entering the context it creates a
  pyaudio.Stream object and starts recording; it stops recording on exit. The
  Stream saves all of its audio to the Queue as two-tuples of
  (timestamp, raw_audio). The raw_audio is available from the queue as a numpy
  array using the get_audio() function.
  
  This class uses the term "frame" in the same sense that PortAudio does, so
  "frame" means something different here than elsewhere in the daredevil stack.
  A frame in PortAudio is one audio sample across all channels, so one frame of
  16-bit stereo audio is four bytes of data as two 16-bit integers.
docstr: Grab at least num_audio_frames frames of audio.
  
  Record at least num_audio_frames of audio and transform it into a
  numpy array. The term "frame" is in the sense used by PortAudio; see the
  note in the class docstring for details.
  
  Audio returned will be the earliest audio in the queue; it could be from
  before this function was called.
  
  Args:
    num_audio_frames: minimum number of samples of audio to grab.
  
  Returns:
    A tuple of (audio, first_timestamp, last_timestamp).
docstr_tok: ["Grab","at","least","num_audio_frames","frames","of","audio",".","Record","at","least","num_audio_frames","of","audio","and","transform","it","into","a","numpy","array",".","The","term","``","frame","''","is","in","the","sense","used","by","PortAudio",";","see","the","note","in","the","class","docstring","for","details",".","Audio","returned","will","be","the","earliest","audio","in","the","queue",";","it","could","be","from","before","this","function","was","called",".","Args",":","num_audio_frames",":","minimum","number","of","samples","of","audio","to","grab",".","Returns",":","A","tuple","of","(","audio",",","first_timestamp",",","last_timestamp",")","."]
code: def get_audio(self, num_audio_frames):
      """Grab at least num_audio_frames frames of audio.
  
      Record at least num_audio_frames of audio and transform it into a
      numpy array. The term "frame" is in the sense used by PortAudio; see the
      note in the class docstring for details.
  
      Audio returned will be the earliest audio in the queue; it could be from
      before this function was called.
  
      Args:
        num_audio_frames: minimum number of samples of audio to grab.
  
      Returns:
        A tuple of (audio, first_timestamp, last_timestamp).
      """
      num_audio_chunks = int(
          math.ceil(
              num_audio_frames
              * self._downsample_factor
              / self.frames_per_chunk
          )
      )
      logging.debug(
          "Capturing %d chunks to get at least %d frames.",
          num_audio_chunks,
          num_audio_frames,
      )
      if num_audio_chunks < 1:
          num_audio_chunks = 1
      try:
          timeout = self.timeout_factor * self._chunk_duration_seconds
          chunks, timestamps = list(
              zip(
                  *[
                      self._get_chunk(timeout=timeout)
                      for _ in range(num_audio_chunks)
                  ]
              )
          )
      except queue.Empty:
          error_message = (
              "Audio capture timed out after %.1f seconds." % timeout
          )
          logging.error(error_message)
          raise AudioTimeoutError(error_message)
  
      assert len(chunks) == num_audio_chunks
      logging.debug(
          "Got %d chunks. Chunk 0 has shape %s and dtype %s.",
          len(chunks),
          chunks[0].shape,
          chunks[0].dtype,
      )
      if self._raw_audio_queue.qsize() > (0.8 * self.max_queue_chunks):
          logging.warning(
              "%d chunks remain in the queue.",
              self._raw_audio_queue.qsize(),
          )
      else:
          logging.debug(
              "%d chunks remain in the queue.",
              self._raw_audio_queue.qsize(),
          )
  
      audio = np.concatenate(chunks)
      if self._downsample_factor != 1:
          audio = resample(
              audio,
              self._raw_audio_sample_rate_hz,
              self.audio_sample_rate_hz,
          )
  
      logging.debug(
          "Audio array has shape %s and dtype %s.",
          audio.shape,
          audio.dtype,
      )
      return audio, timestamps[0], timestamps[-1]
  
imports: import importlib
  import math
  import queue
  import time
  from absl import logging
  import numpy
  import pyaudio
  import scipy
  import six
label_desc: Use image node_id 14 for calling the AudioRecorder obj's underlying member method code with example usage: obj.get_audio(num_audio_frames) and returns: audio,  timestamps,  timestamps