text: // Copyright 2017 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <audio-utils/audio-device-stream.h>
  #include <audio-utils/audio-input.h>
  #include <audio-utils/audio-output.h>
  #include <fbl/algorithm.h>
  #include <fbl/unique_fd.h>
  #include <fuchsia/hardware/audio/c/fidl.h>
  #include <limits>
  #include <fbl/auto_call.h>
  #include <fcntl.h>
  #include <inttypes.h>
  #include <lib/fzl/fdio.h>
  #include <lib/zx/channel.h>
  #include <lib/zx/handle.h>
  #include <lib/zx/vmar.h>
  #include <lib/zx/vmo.h>
  #include <stdio.h>
  #include <string.h>
  #include <zircon/assert.h>
  #include <zircon/device/audio.h>
  #include <zircon/process.h>
  #include <zircon/syscalls.h>
  #include <zircon/time.h>
  #include <zircon/types.h>
  
  namespace audio {
  namespace utils {
  
  static constexpr zx::duration CALL_TIMEOUT = zx::msec(500);
  template <typename ReqType, typename RespType>
  zx_status_t DoCallImpl(const zx::channel& channel,
                         const ReqType&     req,
                         RespType*          resp,
                         zx::handle*        resp_handle_out,
                         uint32_t*          resp_len_out = nullptr) {
      zx_channel_call_args_t args;
  
      ZX_DEBUG_ASSERT((resp_handle_out == nullptr) || !resp_handle_out->is_valid());
  
      args.wr_bytes       = const_cast<ReqType*>(&req);
      args.wr_num_bytes   = sizeof(ReqType);
      args.wr_handles     = nullptr;
      args.wr_num_handles = 0;
      args.rd_bytes       = resp;
      args.rd_num_bytes   = sizeof(RespType);
      args.rd_handles     = resp_handle_out ? resp_handle_out->reset_and_get_address() : nullptr;
      args.rd_num_handles = resp_handle_out ? 1 : 0;
  
      uint32_t bytes, handles;
  
      zx_status_t status = channel.call(0, zx::deadline_after(CALL_TIMEOUT), &args, &bytes, &handles);
      if (status != ZX_OK) {
          printf("Cmd failure (cmd %04x, res %d)\n", req.hdr.cmd, status);
          return status;
      }
  
      // If the caller wants to know the size of the response length, let them
      // check to make sure it is consistent with what they expect.  Otherwise,
      // make sure that the number of bytes we got back matches the size of the
      // response structure.
      if (resp_len_out != nullptr) {
          *resp_len_out = bytes;
      } else
      if (bytes != sizeof(RespType)) {
          printf("Unexpected response size (got %u, expected %zu)\n", bytes, sizeof(RespType));
          return ZX_ERR_INTERNAL;
      }
  
      return ZX_OK;
  }
  
  template <typename ReqType, typename RespType>
  zx_status_t DoCall(const zx::channel& channel,
                     const ReqType&     req,
                     RespType*          resp,
                     zx::handle*        resp_handle_out = nullptr) {
      zx_status_t res = DoCallImpl(channel, req, resp, resp_handle_out);
      return (res != ZX_OK) ? res : resp->result;
  }
  
  template <typename ReqType, typename RespType>
  zx_status_t DoNoFailCall(const zx::channel& channel,
                           const ReqType&     req,
                           RespType*          resp,
                           zx::handle*        resp_handle_out = nullptr) {
      return DoCallImpl(channel, req, resp, resp_handle_out);
  }
  
  AudioDeviceStream::AudioDeviceStream(bool input, uint32_t dev_id)
      : input_(input) {
      snprintf(name_, sizeof(name_), "/dev/class/audio-%s/%03u",
               input_ ? "input" : "output",
               dev_id);
  }
  
  AudioDeviceStream::AudioDeviceStream(bool input, const char* dev_path)
      : input_(input) {
      strncpy(name_, dev_path, sizeof(name_));
      name_[sizeof(name_) - 1] = 0;
  }
  
  AudioDeviceStream::~AudioDeviceStream() {
      Close();
  }
  
  zx_status_t AudioDeviceStream::Open() {
      if (stream_ch_ != ZX_HANDLE_INVALID)
          return ZX_ERR_BAD_STATE;
  
      fbl::unique_fd fd{::open(name(), O_RDONLY)};
      if (!fd.is_valid()) {
          printf("Failed to open \"%s\"\n", name());
          return ZX_ERR_NOT_FOUND;
      }
  
      fzl::FdioCaller dev(std::move(fd));
      zx_status_t res = fuchsia_hardware_audio_DeviceGetChannel(dev.borrow_channel(),
                                                                stream_ch_.reset_and_get_address());
  
      if (res != ZX_OK) {
          printf("Failed to obtain channel (res %d)\n", res);
      }
  
      return res;
  }
  
  zx_status_t AudioDeviceStream::GetSupportedFormats(
          fbl::Vector<audio_stream_format_range_t>* out_formats) const {
      constexpr uint32_t MIN_RESP_SIZE = offsetof(audio_stream_cmd_get_formats_resp_t, format_ranges);
      audio_stream_cmd_get_formats_req req;
      audio_stream_cmd_get_formats_resp resp;
      uint32_t rxed;
      zx_status_t res;
  
      if (out_formats == nullptr) {
          return ZX_ERR_INVALID_ARGS;
      }
  
      req.hdr.cmd = AUDIO_STREAM_CMD_GET_FORMATS;
      req.hdr.transaction_id = 1;
      res = DoCallImpl(stream_ch_, req, &resp, nullptr, &rxed);
      if ((res != ZX_OK) || (rxed < MIN_RESP_SIZE)) {
          printf("Failed to fetch initial suppored format list chunk (res %d, rxed %u)\n",
                  res, rxed);
          return res;
      }
  
      uint32_t expected_formats = resp.format_range_count;
      if (!expected_formats)
          return ZX_OK;
  
      out_formats->reset();
      fbl::AllocChecker ac;
      out_formats->reserve(expected_formats, &ac);
      if (!ac.check()) {
          printf("Failed to allocated %u entries for format ranges\n", expected_formats);
          return ZX_ERR_NO_MEMORY;
      }
  
      zx_txid_t txid = resp.hdr.transaction_id;
      uint32_t processed_formats = 0;
      while (true) {
          if (resp.hdr.cmd != AUDIO_STREAM_CMD_GET_FORMATS) {
              printf("Unexpected response command while fetching formats "
                     "(expected 0x%08x, got 0x%08x)\n",
                      AUDIO_STREAM_CMD_GET_FORMATS, resp.hdr.cmd);
              return ZX_ERR_INTERNAL;
          }
  
          if (resp.hdr.transaction_id != txid) {
              printf("Unexpected response transaction id while fetching formats "
                     "(expected 0x%08x, got 0x%08x)\n",
                      txid, resp.hdr.transaction_id);
              return ZX_ERR_INTERNAL;
          }
  
          if (resp.first_format_range_ndx != processed_formats) {
              printf("Bad format index while fetching formats (expected %u, got %hu)\n",
                      processed_formats, resp.first_format_range_ndx);
              return ZX_ERR_INTERNAL;
          }
  
          uint32_t todo = fbl::min(static_cast<uint32_t>(expected_formats - processed_formats),
                                    AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE);
          size_t min_size = MIN_RESP_SIZE + (todo * sizeof(audio_stream_format_range_t));
          if (rxed < min_size) {
              printf("Short response while fetching formats (%u < %zu)\n", rxed, min_size);
              return ZX_ERR_INTERNAL;
          }
  
          for (uint16_t i = 0; i < todo; ++i) {
              out_formats->push_back(resp.format_ranges[i]);
          }
  
          processed_formats += todo;
          if (processed_formats == expected_formats)
              break;
  
          zx_signals_t pending_sig;
          res = stream_ch_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
                                    zx::deadline_after(CALL_TIMEOUT),
                                    &pending_sig);
          if (res != ZX_OK) {
              printf("Failed to wait for next response after processing %u/%u formats (res %d)\n",
                      processed_formats, expected_formats, res);
              return res;
          }
  
          res = stream_ch_.read(0u, &resp, nullptr, sizeof(resp), 0, &rxed, nullptr);
          if (res != ZX_OK) {
              printf("Failed to read next response after processing %u/%u formats (res %d)\n",
                      processed_formats, expected_formats, res);
              return res;
          }
      }
  
      return ZX_OK;
  }
  
  zx_status_t AudioDeviceStream::GetPlugState(audio_stream_cmd_plug_detect_resp_t* out_state,
                                              bool enable_notify) const {
      ZX_DEBUG_ASSERT(out_state != nullptr);
      audio_stream_cmd_plug_detect_req req;
  
      req.hdr.cmd = AUDIO_STREAM_CMD_PLUG_DETECT;
      req.hdr.transaction_id = 1;
      req.flags = enable_notify ? AUDIO_PDF_ENABLE_NOTIFICATIONS : AUDIO_PDF_NONE;
  
      zx_status_t res = DoNoFailCall(stream_ch_, req, out_state);
      if (res != ZX_OK)
          printf("Failed to fetch plug detect information! (res %d)\n", res);
  
      return res;
  }
  
  void AudioDeviceStream::DisablePlugNotifications() {
      audio_stream_cmd_plug_detect_req req;
  
      req.hdr.cmd = static_cast<audio_cmd_t>(AUDIO_STREAM_CMD_PLUG_DETECT | AUDIO_FLAG_NO_ACK);
      req.hdr.transaction_id = 1;
      req.flags = AUDIO_PDF_DISABLE_NOTIFICATIONS;
  
      stream_ch_.write(0, &req, sizeof(req), nullptr, 0);
  }
  
  zx_status_t AudioDeviceStream::SetMute(bool mute) {
      audio_stream_cmd_set_gain_req  req;
      audio_stream_cmd_set_gain_resp resp;
  
      req.hdr.cmd = AUDIO_STREAM_CMD_SET_GAIN;
      req.hdr.transaction_id = 1;
      req.flags = mute
                ? static_cast<audio_set_gain_flags_t>(AUDIO_SGF_MUTE_VALID | AUDIO_SGF_MUTE)
                : AUDIO_SGF_MUTE_VALID;
  
      zx_status_t res = DoCall(stream_ch_, req, &resp);
      if (res != ZX_OK)
          printf("Failed to %smute stream! (res %d)\n", mute ? "" : "un", res);
      else
          printf("Stream is now %smuted\n", mute ? "" : "un");
  
      return res;
  }
  
  zx_status_t AudioDeviceStream::SetAgc(bool enabled) {
      audio_stream_cmd_set_gain_req  req;
      audio_stream_cmd_set_gain_resp resp;
  
      req.hdr.cmd = AUDIO_STREAM_CMD_SET_GAIN;
      req.hdr.transaction_id = 1;
      req.flags = enabled
                ? static_cast<audio_set_gain_flags_t>(AUDIO_SGF_AGC_VALID | AUDIO_SGF_AGC)
                : AUDIO_SGF_AGC_VALID;
  
      zx_status_t res = DoCall(stream_ch_, req, &resp);
      if (res != ZX_OK)
          printf("Failed to %sable AGC for stream! (res %d)\n", enabled ? "en" : "dis", res);
      else
          printf("Stream AGC is now %sabled\n", enabled ? "en" : "dis");
  
      return res;
  }
  
  zx_status_t AudioDeviceStream::SetGain(float gain) {
      audio_stream_cmd_set_gain_req  req;
      audio_stream_cmd_set_gain_resp resp;
  
      req.hdr.cmd = AUDIO_STREAM_CMD_SET_GAIN;
      req.hdr.transaction_id = 1;
      req.flags = AUDIO_SGF_GAIN_VALID;
      req.gain  = gain;
  
      zx_status_t res = DoCall(stream_ch_, req, &resp);
      if (res != ZX_OK) {
          printf("Failed to set gain to %.2f dB! (res %d)\n", gain, res);
      } else {
          printf("Gain is now %.2f dB.  Stream is %smuted.\n",
                  resp.cur_gain, resp.cur_mute ? "" : "un");
      }
  
      return res;
  }
  
  zx_status_t AudioDeviceStream::GetGain(audio_stream_cmd_get_gain_resp_t* out_gain) const {
      if (out_gain == nullptr)
          return ZX_ERR_INVALID_ARGS;
  
      audio_stream_cmd_get_gain_req req;
      req.hdr.cmd = AUDIO_STREAM_CMD_GET_GAIN;
      req.hdr.transaction_id = 1;
  
      return DoNoFailCall(stream_ch_, req, out_gain);
  }
  
  zx_status_t AudioDeviceStream::GetUniqueId(audio_stream_cmd_get_unique_id_resp_t* out_id) const {
      if (out_id == nullptr)
          return ZX_ERR_INVALID_ARGS;
  
      audio_stream_cmd_get_unique_id_req req;
      req.hdr.cmd = AUDIO_STREAM_CMD_GET_UNIQUE_ID;
      req.hdr.transaction_id = 1;
  
      return DoNoFailCall(stream_ch_, req, out_id);
  }
  
  zx_status_t AudioDeviceStream::GetString(audio_stream_string_id_t id,
                                           audio_stream_cmd_get_string_resp_t* out_str) const {
      if (out_str == nullptr)
          return ZX_ERR_INVALID_ARGS;
  
      audio_stream_cmd_get_string_req req;
      req.hdr.cmd = AUDIO_STREAM_CMD_GET_STRING;
      req.hdr.transaction_id = 1;
      req.id = id;
  
      return DoNoFailCall(stream_ch_, req, out_str);
  }
  
  zx_status_t AudioDeviceStream::PlugMonitor(float duration) {
      const double duration_ns = static_cast<double>(duration) * ZX_SEC(1);
      const zx_time_t deadline = zx_deadline_after(static_cast<zx_duration_t>(duration_ns));
      audio_stream_cmd_plug_detect_resp resp;
      zx_status_t res = GetPlugState(&resp, true);
      if (res != ZX_OK)
          return res;
  
      zx_time_t last_plug_time = resp.plug_state_time;
      bool last_plug_state = (resp.flags & AUDIO_PDNF_PLUGGED);
      printf("Initial plug state is : %s.\n", last_plug_state ? "plugged" : "unplugged");
  
      if (resp.flags & AUDIO_PDNF_HARDWIRED) {
          printf("Stream reports that it is hardwired, Monitoring is not possible.\n");
          return ZX_OK;
  
      }
  
      auto ReportPlugState = [&last_plug_time, &last_plug_state](bool plug_state,
                                                                 zx_time_t plug_time) {
          printf("Plug State now : %s (%.3lf sec since last change).\n",
                 plug_state ? "plugged" : "unplugged",
                 static_cast<double>(zx_time_sub_time(plug_time, last_plug_time)) /
                     static_cast<double>(ZX_SEC(1)));
  
          last_plug_state = plug_state;
          last_plug_time  = plug_time;
      };
  
      if (resp.flags & AUDIO_PDNF_CAN_NOTIFY) {
          printf("Stream is capable of async notification.  Monitoring for %.2f seconds\n",
                  duration);
  
          auto cleanup = fbl::MakeAutoCall([this]() { DisablePlugNotifications(); });
          while (true) {
              zx_signals_t pending;
              res = stream_ch_.wait_one(ZX_CHANNEL_PEER_CLOSED | ZX_CHANNEL_READABLE,
                                        zx::time(deadline), &pending);
  
              if ((res != ZX_OK) || (pending & ZX_CHANNEL_PEER_CLOSED)) {
                  if (res != ZX_ERR_TIMED_OUT)
                      printf("Error while waiting for plug notification (res %d)\n", res);
  
                  if (pending & ZX_CHANNEL_PEER_CLOSED)
                      printf("Peer closed while waiting for plug notification\n");
  
                  break;
              }
  
              ZX_DEBUG_ASSERT(pending & ZX_CHANNEL_READABLE);
  
              audio_stream_plug_detect_notify_t state;
              uint32_t bytes_read;
              res = stream_ch_.read(0, &state, nullptr, sizeof(state), 0, &bytes_read, nullptr);
              if (res != ZX_OK) {
                  printf("Read failure while waiting for plug notification (res %d)\n", res);
                  break;
              }
  
              if ((bytes_read != sizeof(state)) ||
                  (state.hdr.cmd != AUDIO_STREAM_PLUG_DETECT_NOTIFY)) {
                  printf("Size/type mismatch while waiting for plug notification.  "
                         "Got (%u/%u) Expected (%zu/%u)\n",
                         bytes_read, state.hdr.cmd,
                         sizeof(state), AUDIO_STREAM_PLUG_DETECT_NOTIFY);
                  break;
              }
  
              bool plug_state = (state.flags & AUDIO_PDNF_PLUGGED);
              ReportPlugState(plug_state, state.plug_state_time);
          }
      } else {
          printf("Stream is not capable of async notification.  Polling for %.2f seconds\n",
                  duration);
  
          while (true) {
              zx_time_t now = zx_clock_get_monotonic();
              if (now >= deadline)
                  break;
  
              zx_time_t next_wake = fbl::min(deadline, zx_time_add_duration(now, ZX_MSEC(100u)));
  
              zx_signals_t sigs;
              zx_status_t res = stream_ch_.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time(next_wake), &sigs);
  
              if ((res != ZX_OK) && (res != ZX_ERR_TIMED_OUT)) {
                  printf("Error waiting on stream channel (res %d)\n", res);
                  break;
              }
  
              if (sigs & ZX_CHANNEL_PEER_CLOSED) {
                  printf("Peer closed connection while polling plug state\n");
                  break;
              }
  
              res = GetPlugState(&resp, true);
              if (res != ZX_OK) {
                  printf("Failed to poll plug state (res %d)\n", res);
                  break;
              }
  
              bool plug_state = (resp.flags & AUDIO_PDNF_PLUGGED);
              if (plug_state != last_plug_state)
                  ReportPlugState(resp.flags, resp.plug_state_time);
          }
      }
  
      printf("Monitoring finished.\n");
  
      return ZX_OK;
  }
  
  zx_status_t AudioDeviceStream::SetFormat(uint32_t frames_per_second,
                                           uint16_t channels,
                                           audio_sample_format_t sample_format) {
      if ((stream_ch_ == ZX_HANDLE_INVALID) || (rb_ch_ != ZX_HANDLE_INVALID))
          return ZX_ERR_BAD_STATE;
  
      auto noflag_format = static_cast<audio_sample_format_t>(
              (sample_format & ~AUDIO_SAMPLE_FORMAT_FLAG_MASK));
  
      switch (noflag_format) {
      case AUDIO_SAMPLE_FORMAT_8BIT:         sample_size_ = 1; break;
      case AUDIO_SAMPLE_FORMAT_16BIT:        sample_size_ = 2; break;
      case AUDIO_SAMPLE_FORMAT_24BIT_PACKED: sample_size_ = 3; break;
      case AUDIO_SAMPLE_FORMAT_20BIT_IN32:
      case AUDIO_SAMPLE_FORMAT_24BIT_IN32:
      case AUDIO_SAMPLE_FORMAT_32BIT:
      case AUDIO_SAMPLE_FORMAT_32BIT_FLOAT:  sample_size_ = 4; break;
      default: return ZX_ERR_NOT_SUPPORTED;
      }
  
      channel_cnt_   = channels;
      frame_sz_      = channels * sample_size_;
      frame_rate_    = frames_per_second;
      sample_format_ = sample_format;
  
      audio_stream_cmd_set_format_req_t  req;
      audio_stream_cmd_set_format_resp_t resp;
      req.hdr.cmd            = AUDIO_STREAM_CMD_SET_FORMAT;
      req.hdr.transaction_id = 1;
      req.frames_per_second  = frames_per_second;
      req.channels           = channels;
      req.sample_format      = sample_format;
  
      zx::handle tmp;
      zx_status_t res = DoCall(stream_ch_, req, &resp, &tmp);
      if (res != ZX_OK) {
          printf("Failed to set format %uHz %hu-Ch fmt 0x%x (res %d)\n",
                  frames_per_second, channels, sample_format, res);
      }
  
      external_delay_nsec_ = resp.external_delay_nsec;
  
      // TODO(johngro) : Verify the type of this handle before transferring it to
      // our ring buffer channel handle.
      rb_ch_.reset(tmp.release());
  
      return res;
  }
  
  zx_status_t AudioDeviceStream::GetBuffer(uint32_t frames, uint32_t irqs_per_ring) {
      zx_status_t res;
  
      if(!frames)
          return ZX_ERR_INVALID_ARGS;
  
      if (!rb_ch_.is_valid() || rb_vmo_.is_valid() || !frame_sz_)
          return ZX_ERR_BAD_STATE;
  
      // Stash the FIFO depth, in case users need to know it.
      {
          audio_rb_cmd_get_fifo_depth_req_t  req;
          audio_rb_cmd_get_fifo_depth_resp_t resp;
  
          req.hdr.cmd = AUDIO_RB_CMD_GET_FIFO_DEPTH;
          req.hdr.transaction_id = 1;
          res = DoCall(rb_ch_, req, &resp);
          if (res != ZX_OK) {
              printf("Failed to fetch fifo depth (res %d)\n", res);
              return res;
          }
  
          fifo_depth_ = resp.fifo_depth;
      }
  
      uint64_t rb_sz;
      {
          // Get a VMO representing the ring buffer we will share with the audio driver.
          audio_rb_cmd_get_buffer_req_t  req;
          audio_rb_cmd_get_buffer_resp_t resp;
  
          req.hdr.cmd                = AUDIO_RB_CMD_GET_BUFFER;
          req.hdr.transaction_id     = 1;
          req.min_ring_buffer_frames = frames;
          req.notifications_per_ring = irqs_per_ring;
  
          zx::handle tmp;
          res = DoCall(rb_ch_, req, &resp, &tmp);
  
          if ((res == ZX_OK) && (resp.result != ZX_OK))
              res = resp.result;
  
          if (res != ZX_OK) {
              printf("Failed to get driver ring buffer VMO (res %d)\n", res);
              return res;
          }
  
          rb_sz = static_cast<uint64_t>(resp.num_ring_buffer_frames) * frame_sz_;
  
          // TODO(johngro) : Verify the type of this handle before transferring it to our VMO handle.
          rb_vmo_.reset(tmp.release());
      }
  
      // We have the buffer, fetch the underlying size of the VMO (a rounded up
      // multiple of pages) and sanity check it against the effective size the
      // driver reported.
      uint64_t rb_page_sz;
      res = rb_vmo_.get_size(&rb_page_sz);
      if (res != ZX_OK) {
          printf("Failed to fetch ring buffer VMO size (res %d)\n", res);
          return res;
      }
  
      if ((rb_sz > std::numeric_limits<decltype(rb_sz_)>::max()) || (rb_sz > rb_page_sz)) {
          printf("Bad ring buffer size returned by audio driver! "
                 "(kernel size = %lu driver size = %lu)\n", rb_page_sz,  rb_sz);
          return ZX_ERR_INVALID_ARGS;
      }
  
      rb_sz_ = static_cast<decltype(rb_sz_)>(rb_sz);
  
      // Map the VMO into our address space
      // TODO(johngro) : How do I specify the cache policy for this mapping?
      uint32_t flags = input_
                     ? ZX_VM_PERM_READ
                     : ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
      res = zx::vmar::root_self()->map(0u, rb_vmo_,
                                      0u, rb_sz_,
                                      flags, reinterpret_cast<uintptr_t*>(&rb_virt_));
  
      if (res != ZX_OK) {
          printf("Failed to map ring buffer VMO (res %d)\n", res);
          return res;
      }
  
      // Success!  If this is an output device, zero out the buffer and we are done.
      if (!input_) {
          memset(rb_virt_, 0, rb_sz_);
      }
  
      return ZX_OK;
  }
  
  zx_status_t AudioDeviceStream::StartRingBuffer() {
      if (rb_ch_ == ZX_HANDLE_INVALID)
          return ZX_ERR_BAD_STATE;
  
      audio_rb_cmd_start_req_t  req;
      audio_rb_cmd_start_resp_t resp;
  
      req.hdr.cmd = AUDIO_RB_CMD_START;
      req.hdr.transaction_id = 1;
  
      zx_status_t res = DoCall(rb_ch_, req, &resp);
  
      if (res == ZX_OK) {
          start_time_ = resp.start_time;
      }
  
      return res;
  }
  
  zx_status_t AudioDeviceStream::StopRingBuffer() {
      if (rb_ch_ == ZX_HANDLE_INVALID)
          return ZX_ERR_BAD_STATE;
  
      start_time_ = 0;
  
      audio_rb_cmd_stop_req_t  req;
      audio_rb_cmd_stop_resp_t resp;
  
      req.hdr.cmd = AUDIO_RB_CMD_STOP;
      req.hdr.transaction_id = 1;
  
      return DoCall(rb_ch_, req, &resp);
  }
  
  void AudioDeviceStream::ResetRingBuffer() {
      if (rb_virt_ != nullptr) {
          ZX_DEBUG_ASSERT(rb_sz_ != 0);
          zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(rb_virt_), rb_sz_);
      }
      rb_ch_.reset();
      rb_vmo_.reset();
      rb_sz_ = 0;
      rb_virt_ = nullptr;
  }
  
  void AudioDeviceStream::Close() {
      ResetRingBuffer();
      stream_ch_.reset();
  }
  
  bool AudioDeviceStream::IsChannelConnected(const zx::channel& ch) {
      if (!ch.is_valid())
          return false;
  
      zx_signals_t junk;
      return ch.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time(), &junk) != ZX_ERR_TIMED_OUT;
  }
  
  
  }  // namespace utils
  }  // namespace audio
  