playBuffer function
Play buffer of audio
Implementation
void playBuffer(
Uint8List audioData,
int rate,
int channels,
) {
final pcmHandlePtr = calloc<Pointer<a.snd_pcm_>>();
// https://github.com/dart-lang/ffigen/issues/72#issuecomment-672060509
final name = 'default'.toNativeUtf8().cast<Int8>();
final stream = 0;
final mode = 0;
final ratePtr = calloc<Uint32>();
ratePtr.value = rate;
final dirPtr = Pointer<Int32>.fromAddress(0);
final tmpPtr = calloc<Uint32>();
final framesPtr = calloc<Uint64>();
/* Open the PCM device in playback mode */
final openResult = alsa.snd_pcm_open(pcmHandlePtr, name, stream, mode);
if (openResult < 0) {
final errMesg = alsa.snd_strerror(openResult).cast<Utf8>().toDartString();
throw Exception('ERROR: Can\`t open $name PCM device: $errMesg');
}
var paramsPtr = calloc<Pointer<a.snd_pcm_hw_params_>>();
// Allocate parameters object
alsa.snd_pcm_hw_params_malloc(paramsPtr);
alsa.snd_pcm_hw_params_any(pcmHandlePtr.value, paramsPtr.value);
/* Set parameters */
var result = 0;
result = alsa.snd_pcm_hw_params_set_access(
pcmHandlePtr.value, paramsPtr.value, SND_PCM_ACCESS_RW_INTERLEAVED);
if (result < 0) {
throw Exception("ERROR: Can't set interleaved mode.");
}
result = alsa.snd_pcm_hw_params_set_format(
pcmHandlePtr.value, paramsPtr.value, SND_PCM_FORMAT_S16_LE);
if (result < 0) {
throw Exception("ERROR: Can't set playback format.");
}
result = alsa.snd_pcm_hw_params_set_channels(
pcmHandlePtr.value, paramsPtr.value, channels);
if (result < 0) {
throw Exception("ERROR: Can't set number of channels.");
}
result = alsa.snd_pcm_hw_params_set_rate_near(
pcmHandlePtr.value, paramsPtr.value, ratePtr, dirPtr);
if (result < 0) {
throw Exception("ERROR: Can't set playback rate.");
}
/* Write parameters */
result = alsa.snd_pcm_hw_params(pcmHandlePtr.value, paramsPtr.value);
if (result < 0) {
throw Exception(
"ERROR: Can't set hardware parameters. ${alsa.snd_strerror(result).cast<Utf8>().toDartString()}");
}
/* Resume information */
final pcmName =
(alsa.snd_pcm_name(pcmHandlePtr.value)).cast<Utf8>().toDartString();
_printDebug('PCM name: $pcmName');
_printDebug(
'PCM state: ${alsa.snd_pcm_state_name(alsa.snd_pcm_state(pcmHandlePtr.value)).cast<Utf8>().toDartString()}');
alsa.snd_pcm_hw_params_get_channels(paramsPtr.value, tmpPtr);
var channelType;
if (tmpPtr.value == 1) {
channelType = '(mono)';
} else if (tmpPtr.value == 2) {
channelType = '(stereo)';
}
_printDebug('channels: ${tmpPtr.value} $channelType');
alsa.snd_pcm_hw_params_get_rate(paramsPtr.value, tmpPtr, dirPtr);
_printDebug('rate: ${tmpPtr.value} bps');
/* Allocate buffer to hold single period */
alsa.snd_pcm_hw_params_get_period_size(paramsPtr.value, framesPtr, dirPtr);
final buff_size = framesPtr.value * channels * 2 /* 2 -> sample size */;
_printDebug('set buffer size: $buff_size');
final buff = calloc<Uint8>(buff_size);
alsa.snd_pcm_hw_params_get_period_time(paramsPtr.value, tmpPtr, dirPtr);
_printDebug('time period: ${tmpPtr.value}');
// TODO: use nicer way of reading buff_size chunks out of audioData
final bufferCount = (audioData.length ~/ buff_size);
for (var j = 0; j <= bufferCount; j++) {
for (var i = 0; i < buff_size; i++) {
final index = i + (j * buff_size);
if (index > audioData.length - 1) {
break;
}
final b = audioData[index];
buff[i] = b;
}
var pcm = alsa.snd_pcm_writei(
pcmHandlePtr.value, buff.cast<Void>(), framesPtr.value);
final EPIPE = 32;
if (pcm == -EPIPE) {
_printDebug('XRUN.'); // should client get callback for this?
alsa.snd_pcm_prepare(pcmHandlePtr.value);
} else if (pcm < 0) {
throw Exception(
"ERROR. Can't write to PCM device. ${alsa.snd_strerror(pcm).cast<Utf8>().toDartString()}");
}
}
alsa.snd_pcm_drain(pcmHandlePtr.value);
alsa.snd_pcm_close(pcmHandlePtr.value);
calloc.free(buff);
calloc.free(pcmHandlePtr);
calloc.free(paramsPtr);
calloc.free(ratePtr);
calloc.free(framesPtr);
calloc.free(dirPtr);
}