$OpenBSD: patch-media_audio_openbsd_sndio_output_cc,v 1.1 2011/05/20 08:35:24 robert Exp $
--- media/audio/openbsd/sndio_output.cc.orig	Thu May 19 14:21:00 2011
+++ media/audio/openbsd/sndio_output.cc	Thu May 19 17:15:21 2011
@@ -0,0 +1,268 @@
+// Copyright (c) 2010 The Chromium 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 "media/audio/openbsd/sndio_output.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "media/audio/audio_util.h"
+#include "media/audio/openbsd/audio_manager_openbsd.h"
+
+#include <poll.h>
+#include <errno.h>
+
+using base::Time;
+using base::TimeDelta;
+
+namespace {
+
+// A custom data structure to store information an AudioQueue buffer.
+struct AudioQueueUserData {
+  AudioQueueUserData() : empty_buffer(false) {}
+  bool empty_buffer;
+};
+
+}  // namespace
+
+// Overview of operation:
+// 1) An object of PCMQueueOutAudioOutputStream is created by the AudioManager
+// factory: audio_man->MakeAudioStream(). This just fills some structure.
+// 2) Next some thread will call Open(), at that point the underliying OS
+// queue is created and the audio buffers allocated.
+// 3) Then some thread will call Start(source) At this point the source will be
+// called to fill the initial buffers in the context of that same thread.
+// Then the OS queue is started which will create its own thread which
+// periodically will call the source for more data as buffers are being
+// consumed.
+// 4) At some point some thread will call Stop(), which we handle by directly
+// stoping the OS queue.
+// 5) One more callback to the source could be delivered in in the context of
+// the queue's own thread. Data, if any will be discared.
+// 6) The same thread that called stop will call Close() where we cleanup
+// and notifiy the audio manager, which likley will destroy this object.
+
+PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream(
+    AudioManagerOpenBSD* manager, AudioParameters params)
+    : sndio_hdl_(),
+      sndio_par_(),
+      source_(NULL),
+      manager_(manager),
+      silence_bytes_(0),
+      volume_(1),
+      hw_pos_(0),
+      sw_pos_(0),
+      buffer_(NULL),
+      buffer_size_(0),
+      thread_("PCMQueueOutAudioOutputStreamThread") {
+
+  DCHECK(manager_);
+
+  sio_initpar(&sndio_par_);
+  sndio_par_.rate = params.sample_rate;
+  sndio_par_.bits = params.bits_per_sample;
+  sndio_par_.le = SIO_LE_NATIVE;
+  sndio_par_.pchan = params.channels;
+  sndio_par_.sig = (params.bits_per_sample  == 8) ? 0 :1;
+
+  packet_size_ = params.GetPacketSize();
+
+  AddRef();
+}
+
+PCMQueueOutAudioOutputStream::~PCMQueueOutAudioOutputStream() {
+}
+
+bool PCMQueueOutAudioOutputStream::Open() {
+  sndio_hdl_ = sio_open(NULL, SIO_PLAY, 0);
+  if (!sndio_hdl_) {
+    printf("failed to open audio device\n");
+    return false;
+  }
+
+  if (!sio_setpar(sndio_hdl_, &sndio_par_) ||
+      !sio_getpar(sndio_hdl_, &sndio_par_)) {
+    sio_close(sndio_hdl_);
+    printf("failed to set configuration\n");
+    return false;
+  }
+
+  buffer_size_ = sndio_par_.bufsz * sndio_par_.bps * sndio_par_.pchan;
+  buffer_ = (uint8 *)malloc(buffer_size_);
+  if (!buffer_) {
+    printf("could not allocate buffer for sndio\n");
+    return false;
+  }
+
+  return true;
+}
+
+void PCMQueueOutAudioOutputStream::Close() {
+  if (buffer_)
+    free(buffer_);
+  if (sndio_hdl_)
+    sio_close(sndio_hdl_);
+}
+
+void PCMQueueOutAudioOutputStream::Stop() {
+  thread_.Stop();
+  if (sndio_hdl_ != NULL)
+    sio_stop(sndio_hdl_);
+}
+
+void PCMQueueOutAudioOutputStream::SetVolume(double volume) {
+  NOTIMPLEMENTED();
+}
+
+void PCMQueueOutAudioOutputStream::GetVolume(double* volume) {
+  NOTIMPLEMENTED();
+}
+
+void PCMQueueOutAudioOutputStream::RenderCallback(void* p_this) {
+  PCMQueueOutAudioOutputStream* audio_stream =
+    static_cast<PCMQueueOutAudioOutputStream*>(p_this);
+  size_t w;
+  int n;
+  struct pollfd pfd;
+
+  AudioSourceCallback* source = audio_stream->source_;
+  if (!source)
+    return;
+
+// make sure callbacks have been called
+// XXX can't do this here, since we're called from the onmove callback
+// XXX do we need a thread to do this, and to make sure we don't underrun???
+  n = sio_pollfd(audio_stream->sndio_hdl_, &pfd, POLLOUT);
+  while (poll(&pfd, n, 0) < 0 && errno == EINTR)
+    ; // nothing
+  sio_revents(audio_stream->sndio_hdl_, &pfd);
+
+  if (audio_stream->used_ < 0) {
+    printf("used_ < 0!!\n");
+    goto silence;
+    audio_stream->used_ = 0;
+  }
+
+  printf("audio_stream->filled_ = %d\n", audio_stream->filled_);
+  if (audio_stream->filled_ > audio_stream->buffer_avail_) {
+    printf("audio_stream->filled_ > avail!\n");
+    audio_stream->filled_ = audio_stream->buffer_avail_;
+  }
+  if (!audio_stream->filled_) {
+    return;
+    audio_stream->filled_ = audio_stream->hw_pos_ - audio_stream->sw_pos_;
+    if (audio_stream->filled_ > audio_stream->buffer_size_)
+      audio_stream->filled_ = audio_stream->buffer_size_;
+    else if (1 ==  0) {
+silence:
+      audio_stream->filled_ = audio_stream->buffer_size_;
+    }
+    memset(audio_stream->buffer_, 0, audio_stream->filled_);
+  }
+  printf("audio_stream->filled_ = %d\n", audio_stream->filled_);
+  w = sio_write(audio_stream->sndio_hdl_, audio_stream->buffer_, audio_stream->filled_);
+  if (!w) {
+    printf("sndio wrote 0 bytes\n");
+  }
+  audio_stream->sw_pos_ += w;
+}
+
+void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) {
+  PCMQueueOutAudioOutputStream* audio_stream =
+    static_cast<PCMQueueOutAudioOutputStream*>(this);
+  DCHECK(!thread_.IsRunning());
+  last_callback_time_ = Time::Now();
+  DCHECK(callback);
+  source_ = callback;
+  int w;
+
+  sw_pos_ = hw_pos_ = 0;
+
+  sio_onmove(sndio_hdl_, onmove_callback, this);
+
+  if (!sio_start(sndio_hdl_)) {
+    printf("could not start sndio\n");
+    return;
+  }
+
+  thread_.Start();
+  thread_.message_loop()->PostDelayedTask(
+      FROM_HERE,
+      NewRunnableMethod(this, &PCMQueueOutAudioOutputStream::DoCallback),
+      callback_interval_ms_);
+
+  printf("buffer_size_ = %d\n", buffer_size_);
+
+  /* we want to fill the buffer with silence, to be sure it gets
+   * filled and thus playback actually starts, and we start getting more
+   * onmove callbacks to keep us going.
+   */
+  memset(buffer_, 0, buffer_size_);
+
+  w = sio_write(sndio_hdl_, buffer_, buffer_size_);
+  if (!w || sio_eof(sndio_hdl_)) {
+    printf("sio_write error: wrote %d bytes\n", w);
+    return;
+  }
+  sw_pos_ += w;
+
+  while (sw_pos_ <= buffer_size_) {
+    audio_stream->filled_ = source_->OnMoreData(this, buffer_, buffer_size_,
+      AudioBuffersState(0, sw_pos_));
+    printf("%s: audio_stream->filled_=%d\n", __func__, audio_stream->filled_);
+    w = sio_write(sndio_hdl_, buffer_, audio_stream->filled_);
+    if (!w || sio_eof(sndio_hdl_)) {
+      printf("sio_write error: wrote %d bytes\n", w);
+      return;
+    }
+    sw_pos_ += w;
+  }
+}
+
+void PCMQueueOutAudioOutputStream::onmove_callback(void *p_this, int delta) {
+  PCMQueueOutAudioOutputStream* audio_stream =
+    static_cast<PCMQueueOutAudioOutputStream*>(p_this);
+  printf("delta = %d\n", delta);
+
+  audio_stream->hw_pos_ += delta *
+    audio_stream->sndio_par_.bps * audio_stream->sndio_par_.pchan;
+}
+
+void PCMQueueOutAudioOutputStream::DoCallback() {
+  PCMQueueOutAudioOutputStream* audio_stream =
+    static_cast<PCMQueueOutAudioOutputStream*>(this);
+
+  int max;
+
+  audio_stream->used_ = (audio_stream->hw_pos_ < 0) ? audio_stream->sw_pos_ :
+    audio_stream->sw_pos_ - audio_stream->hw_pos_;
+
+  audio_stream->buffer_avail_ = audio_stream->buffer_size_ - audio_stream->used_;
+  printf("used_ = %d, size = %d avail = %d\n",
+    audio_stream->used_, audio_stream->buffer_size_, audio_stream->buffer_avail_);
+
+  max = audio_stream->buffer_avail_ > 0 ? audio_stream->buffer_avail_ :
+    audio_stream->buffer_size_;
+  audio_stream->filled_ = audio_stream->source_->OnMoreData(audio_stream, audio_stream->buffer_, max,
+    AudioBuffersState(audio_stream->used_, 0));
+
+  Time now = Time::Now();
+  int64 next_callback_ms = (last_callback_time_ +
+      TimeDelta::FromMilliseconds(callback_interval_ms_ * 2) -
+      now).InMilliseconds();
+  // If we are falling behind, try to catch up as much as we can in the next
+  // callback.
+  if (next_callback_ms < 0)
+    next_callback_ms = 0;
+
+  last_callback_time_ = now;
+
+  thread_.message_loop()->PostDelayedTask(
+      FROM_HERE,
+      NewRunnableMethod(this, &PCMQueueOutAudioOutputStream::DoCallback),
+      next_callback_ms);
+
+  if (buffer_avail_)
+    RenderCallback(this);
+}
