PageRenderTime 50ms CodeModel.GetById 11ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 0ms

/media/libsydneyaudio/src/sydney_audio_android.c

http://github.com/zpao/v8monkey
C | 595 lines | 396 code | 104 blank | 95 comment | 72 complexity | 7cc7f4bb642bb5b55f1a8276d17c9bba MD5 | raw file
  1/* ***** BEGIN LICENSE BLOCK *****
  2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3 *
  4 * The contents of this file are subject to the Mozilla Public License Version
  5 * 1.1 (the "License"); you may not use this file except in compliance with
  6 * the License. You may obtain a copy of the License at
  7 * http://www.mozilla.org/MPL/
  8 *
  9 * Software distributed under the License is distributed on an "AS IS" basis,
 10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11 * for the specific language governing rights and limitations under the
 12 * License.
 13 *
 14 * The Initial Developer of the Original Code is
 15 * CSIRO
 16 * Portions created by the Initial Developer are Copyright (C) 2007
 17 * the Initial Developer. All Rights Reserved.
 18 *
 19 * Contributor(s): Michael Martin
 20 *                 Michael Wu <mwu@mozilla.com>
 21 *
 22 * Alternatively, the contents of this file may be used under the terms of
 23 * either the GNU General Public License Version 2 or later (the "GPL"), or
 24 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 25 * in which case the provisions of the GPL or the LGPL are applicable instead
 26 * of those above. If you wish to allow use of your version of this file only
 27 * under the terms of either the GPL or the LGPL, and not to allow others to
 28 * use your version of this file under the terms of the MPL, indicate your
 29 * decision by deleting the provisions above and replace them with the notice
 30 * and other provisions required by the GPL or the LGPL. If you do not delete
 31 * the provisions above, a recipient may use your version of this file under
 32 * the terms of any one of the MPL, the GPL or the LGPL.
 33 *
 34 * ***** END LICENSE BLOCK ***** *
 35 */
 36
 37#include <stdlib.h>
 38#include <time.h>
 39#include <jni.h>
 40#include "sydney_audio.h"
 41
 42#include "android/log.h"
 43
 44#ifndef ALOG
 45#if defined(DEBUG) || defined(FORCE_ALOG)
 46#define ALOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gecko - SYDNEY_AUDIO" , ## args)
 47#else
 48#define ALOG(args...)
 49#endif
 50#endif
 51
 52/* Android implementation based on sydney_audio_mac.c */
 53
 54#define NANOSECONDS_PER_SECOND     1000000000
 55#define NANOSECONDS_IN_MILLISECOND 1000000
 56#define MILLISECONDS_PER_SECOND    1000
 57
 58/* android.media.AudioTrack */
 59struct AudioTrack {
 60  jclass    class;
 61  jmethodID constructor;
 62  jmethodID flush;
 63  jmethodID getminbufsz;
 64  jmethodID pause;
 65  jmethodID play;
 66  jmethodID release;
 67  jmethodID setvol;
 68  jmethodID stop;
 69  jmethodID write;
 70  jmethodID getpos;
 71};
 72
 73enum AudioTrackMode {
 74  MODE_STATIC = 0,
 75  MODE_STREAM = 1
 76};
 77
 78/* android.media.AudioManager */
 79enum AudioManagerStream {
 80  STREAM_VOICE_CALL = 0,
 81  STREAM_SYSTEM = 1,
 82  STREAM_RING = 2,
 83  STREAM_MUSIC = 3,
 84  STREAM_ALARM = 4,
 85  STREAM_NOTIFICATION = 5,
 86  STREAM_DTMF = 8
 87};
 88
 89/* android.media.AudioFormat */
 90enum AudioFormatChannel {
 91  CHANNEL_OUT_MONO = 4,
 92  CHANNEL_OUT_STEREO = 12
 93};
 94
 95enum AudioFormatEncoding {
 96  ENCODING_PCM_16BIT = 2,
 97  ENCODING_PCM_8BIT = 3
 98};
 99
100struct sa_stream {
101  jobject output_unit;
102  jbyteArray output_buf;
103  unsigned int output_buf_size;
104
105  unsigned int rate;
106  unsigned int channels;
107  unsigned int isPaused;
108
109  int64_t lastStartTime;
110  int64_t timePlaying;
111  int64_t amountWritten;
112  unsigned int bufferSize;
113
114  jclass at_class;
115};
116
117static struct AudioTrack at;
118extern JNIEnv * GetJNIForThread();
119
120static jclass
121init_jni_bindings(JNIEnv *jenv) {
122  jclass class = (*jenv)->FindClass(jenv, "android/media/AudioTrack");
123  if (!class) {
124    return NULL;
125  }
126  at.constructor = (*jenv)->GetMethodID(jenv, class, "<init>", "(IIIIII)V");
127  at.flush       = (*jenv)->GetMethodID(jenv, class, "flush", "()V");
128  at.getminbufsz = (*jenv)->GetStaticMethodID(jenv, class, "getMinBufferSize", "(III)I");
129  at.pause       = (*jenv)->GetMethodID(jenv, class, "pause", "()V");
130  at.play        = (*jenv)->GetMethodID(jenv, class, "play",  "()V");
131  at.release     = (*jenv)->GetMethodID(jenv, class, "release",  "()V");
132  at.setvol      = (*jenv)->GetMethodID(jenv, class, "setStereoVolume",  "(FF)I");
133  at.stop        = (*jenv)->GetMethodID(jenv, class, "stop",  "()V");
134  at.write       = (*jenv)->GetMethodID(jenv, class, "write", "([BII)I");
135  at.getpos      = (*jenv)->GetMethodID(jenv, class, "getPlaybackHeadPosition", "()I");
136
137  return (*jenv)->NewGlobalRef(jenv, class);
138}
139
140/*
141 * -----------------------------------------------------------------------------
142 * Startup and shutdown functions
143 * -----------------------------------------------------------------------------
144 */
145
146int
147sa_stream_create_pcm(
148  sa_stream_t      ** _s,
149  const char        * client_name,
150  sa_mode_t           mode,
151  sa_pcm_format_t     format,
152  unsigned  int       rate,
153  unsigned  int       channels
154) {
155
156  /*
157   * Make sure we return a NULL stream pointer on failure.
158   */
159  if (_s == NULL) {
160    return SA_ERROR_INVALID;
161  }
162  *_s = NULL;
163
164  if (mode != SA_MODE_WRONLY) {
165    return SA_ERROR_NOT_SUPPORTED;
166  }
167  if (format != SA_PCM_FORMAT_S16_NE) {
168    return SA_ERROR_NOT_SUPPORTED;
169  }
170  if (channels != 1 && channels != 2) {
171    return SA_ERROR_NOT_SUPPORTED;
172  }
173
174  /*
175   * Allocate the instance and required resources.
176   */
177  sa_stream_t *s;
178  if ((s = malloc(sizeof(sa_stream_t))) == NULL) {
179    return SA_ERROR_OOM;
180  }
181
182  s->output_unit = NULL;
183  s->output_buf  = NULL;
184  s->output_buf_size = 0;
185  s->rate        = rate;
186  s->channels    = channels;
187  s->isPaused    = 0;
188
189  s->lastStartTime = 0;
190  s->timePlaying = 0;
191  s->amountWritten = 0;
192
193  s->bufferSize = 0;
194
195  *_s = s;
196  return SA_SUCCESS;
197}
198
199
200int
201sa_stream_open(sa_stream_t *s) {
202
203  if (s == NULL) {
204    return SA_ERROR_NO_INIT;
205  }
206  if (s->output_unit != NULL) {
207    return SA_ERROR_INVALID;
208  }
209
210  JNIEnv *jenv = GetJNIForThread();
211  if (!jenv) {
212    return SA_ERROR_NO_DEVICE;
213  }
214
215  if ((*jenv)->PushLocalFrame(jenv, 4)) {
216    return SA_ERROR_OOM;
217  }
218
219  s->at_class = init_jni_bindings(jenv);
220  if (!s->at_class) {
221    return SA_ERROR_NO_DEVICE;
222  }
223
224  int32_t chanConfig = s->channels == 1 ?
225    CHANNEL_OUT_MONO : CHANNEL_OUT_STEREO;
226
227  jint minsz = (*jenv)->CallStaticIntMethod(jenv, s->at_class, at.getminbufsz,
228                                            s->rate, chanConfig, ENCODING_PCM_16BIT);
229  if (minsz <= 0) {
230    (*jenv)->PopLocalFrame(jenv, NULL);
231    return SA_ERROR_INVALID;
232  }
233
234  s->bufferSize = s->rate * s->channels * sizeof(int16_t);
235  if (s->bufferSize < minsz) {
236    s->bufferSize = minsz;
237  }
238
239  jobject obj =
240    (*jenv)->NewObject(jenv, s->at_class, at.constructor,
241                       STREAM_MUSIC,
242                       s->rate,
243                       chanConfig,
244                       ENCODING_PCM_16BIT,
245                       s->bufferSize,
246                       MODE_STREAM);
247
248  jthrowable exception = (*jenv)->ExceptionOccurred(jenv);
249  if (exception) {
250    (*jenv)->ExceptionDescribe(jenv);
251    (*jenv)->ExceptionClear(jenv);
252    (*jenv)->PopLocalFrame(jenv, NULL);
253    return SA_ERROR_INVALID;
254  }
255
256  if (!obj) {
257    (*jenv)->PopLocalFrame(jenv, NULL);
258    return SA_ERROR_OOM;
259  }
260
261  s->output_unit = (*jenv)->NewGlobalRef(jenv, obj);
262
263  /* arbitrary buffer size.  using a preallocated buffer avoids churning
264     the GC every audio write. */
265  s->output_buf_size = 4096 * s->channels * sizeof(int16_t);
266  jbyteArray buf = (*jenv)->NewByteArray(jenv, s->output_buf_size);
267  if (!buf) {
268    (*jenv)->ExceptionClear(jenv);
269    (*jenv)->DeleteGlobalRef(jenv, s->output_unit);
270    (*jenv)->PopLocalFrame(jenv, NULL);
271    return SA_ERROR_OOM;
272  }
273
274  s->output_buf = (*jenv)->NewGlobalRef(jenv, buf);
275
276  (*jenv)->PopLocalFrame(jenv, NULL);
277
278  ALOG("%p - New stream %u %u bsz=%u min=%u obsz=%u", s, s->rate, s->channels,
279       s->bufferSize, minsz, s->output_buf_size);
280
281  return SA_SUCCESS;
282}
283
284
285int
286sa_stream_destroy(sa_stream_t *s) {
287
288  if (s == NULL) {
289    return SA_ERROR_NO_INIT;
290  }
291
292  JNIEnv *jenv = GetJNIForThread();
293  if (!jenv) {
294    return SA_SUCCESS;
295  }
296
297  if (s->output_buf) {
298    (*jenv)->DeleteGlobalRef(jenv, s->output_buf);
299  }
300  if (s->output_unit) {
301    (*jenv)->CallVoidMethod(jenv, s->output_unit, at.stop);
302    (*jenv)->CallVoidMethod(jenv, s->output_unit, at.flush);
303    (*jenv)->CallVoidMethod(jenv, s->output_unit, at.release);
304    (*jenv)->DeleteGlobalRef(jenv, s->output_unit);
305  }
306  if (s->at_class) {
307    (*jenv)->DeleteGlobalRef(jenv, s->at_class);
308  }
309  free(s);
310
311  ALOG("%p - Stream destroyed", s);
312  return SA_SUCCESS;
313}
314
315
316/*
317 * -----------------------------------------------------------------------------
318 * Data read and write functions
319 * -----------------------------------------------------------------------------
320 */
321
322int
323sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
324
325  if (s == NULL || s->output_unit == NULL) {
326    return SA_ERROR_NO_INIT;
327  }
328  if (nbytes == 0) {
329    return SA_SUCCESS;
330  }
331  JNIEnv *jenv = GetJNIForThread();
332  if ((*jenv)->PushLocalFrame(jenv, 2)) {
333    return SA_ERROR_OOM;
334  }
335
336  unsigned char *p = data;
337  jint r = 0;
338  size_t wrote = 0;
339  do {
340    size_t towrite = nbytes - wrote;
341    if (towrite > s->output_buf_size) {
342      towrite = s->output_buf_size;
343    }
344    (*jenv)->SetByteArrayRegion(jenv, s->output_buf, 0, towrite, p);
345
346    r = (*jenv)->CallIntMethod(jenv,
347                               s->output_unit,
348                               at.write,
349                               s->output_buf,
350                               0,
351                               towrite);
352    if (r < 0) {
353      ALOG("%p - Write failed %d", s, r);
354      break;
355    }
356
357    /* AudioTrack::write is blocking when the AudioTrack is playing.  When
358       it's not playing, it's a non-blocking call that will return a short
359       write when the buffer is full.  Use a short write to indicate a good
360       time to start the AudioTrack playing. */
361    if (r != towrite) {
362      ALOG("%p - Buffer full, starting playback", s);
363      sa_stream_resume(s);
364    }
365
366    p += r;
367    wrote += r;
368  } while (wrote < nbytes);
369
370  ALOG("%p - Wrote %u", s,  nbytes);
371  s->amountWritten += nbytes;
372
373  (*jenv)->PopLocalFrame(jenv, NULL);
374
375  return r < 0 ? SA_ERROR_INVALID : SA_SUCCESS;
376}
377
378
379/*
380 * -----------------------------------------------------------------------------
381 * General query and support functions
382 * -----------------------------------------------------------------------------
383 */
384
385int
386sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
387
388  if (s == NULL || s->output_unit == NULL) {
389    return SA_ERROR_NO_INIT;
390  }
391
392  /* No android API for this, so estimate based on how much we have played and
393   * how much we have written. */
394  *size = s->bufferSize - ((s->timePlaying * s->channels * s->rate * sizeof(int16_t) /
395                            MILLISECONDS_PER_SECOND) - s->amountWritten);
396
397  /* Available buffer space can't exceed bufferSize. */
398  if (*size > s->bufferSize) {
399    *size = s->bufferSize;
400  }
401  ALOG("%p - Write Size tp=%lld aw=%u sz=%zu", s, s->timePlaying, s->amountWritten, *size);
402
403  return SA_SUCCESS;
404}
405
406
407int
408sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
409
410  if (s == NULL || s->output_unit == NULL) {
411    return SA_ERROR_NO_INIT;
412  }
413
414  ALOG("%p - get position", s);
415
416  JNIEnv *jenv = GetJNIForThread();
417  *pos  = (*jenv)->CallIntMethod(jenv, s->output_unit, at.getpos);
418
419  /* android returns number of frames, so:
420     position = frames * (PCM_16_BIT == 2 bytes) * channels
421  */
422  *pos *= s->channels * sizeof(int16_t);
423  return SA_SUCCESS;
424}
425
426
427int
428sa_stream_pause(sa_stream_t *s) {
429
430  if (s == NULL || s->output_unit == NULL) {
431    return SA_ERROR_NO_INIT;
432  }
433
434  JNIEnv *jenv = GetJNIForThread();
435  s->isPaused = 1;
436
437  /* Update stats */
438  if (s->lastStartTime != 0) {
439    /* if lastStartTime is not zero, so playback has started */
440    struct timespec current_time;
441    clock_gettime(CLOCK_REALTIME, &current_time);
442    int64_t ticker = current_time.tv_sec * 1000 + current_time.tv_nsec / 1000000;
443    s->timePlaying += ticker - s->lastStartTime;
444  }
445  ALOG("%p - Pause total time playing: %lld total written: %lld", s,  s->timePlaying, s->amountWritten);
446
447  (*jenv)->CallVoidMethod(jenv, s->output_unit, at.pause);
448  return SA_SUCCESS;
449}
450
451
452int
453sa_stream_resume(sa_stream_t *s) {
454
455  if (s == NULL || s->output_unit == NULL) {
456    return SA_ERROR_NO_INIT;
457  }
458
459  ALOG("%p - resume", s);
460
461  JNIEnv *jenv = GetJNIForThread();
462  s->isPaused = 0;
463
464  /* Update stats */
465  struct timespec current_time;
466  clock_gettime(CLOCK_REALTIME, &current_time);
467  int64_t ticker = current_time.tv_sec * 1000 + current_time.tv_nsec / 1000000;
468  s->lastStartTime = ticker;
469
470  (*jenv)->CallVoidMethod(jenv, s->output_unit, at.play);
471  return SA_SUCCESS;
472}
473
474
475int
476sa_stream_drain(sa_stream_t *s)
477{
478  if (s == NULL || s->output_unit == NULL) {
479    return SA_ERROR_NO_INIT;
480  }
481
482/* This is somewhat of a hack (see bug 693131).  The AudioTrack documentation
483   doesn't make it clear how much data must be written before a chunk of data is
484   played, and experimentation with short streams required filling the entire
485   allocated buffer.  To guarantee that short streams (and the end of longer
486   streams) are audible, write an entire bufferSize of silence before sleeping.
487   This guarantees the short write logic in sa_stream_write is hit and the
488   stream is playing before sleeping.  Note that the sleep duration is
489   calculated from the duration of audio written before writing silence. */
490  size_t available;
491  sa_stream_get_write_size(s, &available);
492
493  void *p = calloc(1, s->bufferSize);
494  sa_stream_write(s, p, s->bufferSize);
495  free(p);
496
497  /* There is no way with the Android SDK to determine exactly how
498     long to playback.  So estimate and sleep for that long. */
499  unsigned long long x = (s->bufferSize - available) * 1000 / s->channels / s->rate /
500                         sizeof(int16_t) * NANOSECONDS_IN_MILLISECOND;
501  ALOG("%p - Drain - flush %u, sleep for %llu ns", s, available, x);
502
503  struct timespec ts = {x / NANOSECONDS_PER_SECOND, x % NANOSECONDS_PER_SECOND};
504  nanosleep(&ts, NULL);
505
506  return SA_SUCCESS;
507}
508
509
510/*
511 * -----------------------------------------------------------------------------
512 * Extension functions
513 * -----------------------------------------------------------------------------
514 */
515
516int
517sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
518
519  if (s == NULL || s->output_unit == NULL) {
520    return SA_ERROR_NO_INIT;
521  }
522
523  JNIEnv *jenv = GetJNIForThread();
524  (*jenv)->CallIntMethod(jenv, s->output_unit, at.setvol,
525                         (jfloat)vol, (jfloat)vol);
526
527  return SA_SUCCESS;
528}
529
530/*
531 * -----------------------------------------------------------------------------
532 * Unsupported functions
533 * -----------------------------------------------------------------------------
534 */
535#define UNSUPPORTED(func)   func { return SA_ERROR_NOT_SUPPORTED; }
536
537UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
538UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
539UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
540UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
541UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
542UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
543UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
544UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
545UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
546UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
547UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
548UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
549UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
550UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
551UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
552UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
553UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
554UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
555UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
556UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
557UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
558UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
559UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
560UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
561UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
562UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
563UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
564UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
565UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
566UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
567UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
568UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
569UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
570UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
571UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
572UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
573UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
574UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
575UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
576UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
577UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
578UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
579UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
580UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
581UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
582UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
583UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
584UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
585UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
586UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
587UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
588UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
589UNSUPPORTED(int sa_stream_pwrite_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
590UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
591UNSUPPORTED(int sa_stream_get_volume_abs(sa_stream_t *s, float *vol))
592UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
593
594const char *sa_strerror(int code) { return NULL; }
595