mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 852401 - Remove sydneyaudio. r=doublec
--HG-- rename : media/libsydneyaudio/src/gonk/AudioSystem.h => dom/system/gonk/android_audio/AudioSystem.h rename : media/libsydneyaudio/src/gonk/AudioTrack.h => dom/system/gonk/android_audio/AudioTrack.h rename : media/libsydneyaudio/src/gonk/EffectApi.h => dom/system/gonk/android_audio/EffectApi.h rename : media/libsydneyaudio/src/gonk/IAudioFlinger.h => dom/system/gonk/android_audio/IAudioFlinger.h rename : media/libsydneyaudio/src/gonk/IAudioFlingerClient.h => dom/system/gonk/android_audio/IAudioFlingerClient.h rename : media/libsydneyaudio/src/gonk/IAudioRecord.h => dom/system/gonk/android_audio/IAudioRecord.h rename : media/libsydneyaudio/src/gonk/IAudioTrack.h => dom/system/gonk/android_audio/IAudioTrack.h rename : media/libsydneyaudio/src/gonk/IEffect.h => dom/system/gonk/android_audio/IEffect.h rename : media/libsydneyaudio/src/gonk/IEffectClient.h => dom/system/gonk/android_audio/IEffectClient.h
This commit is contained in:
parent
cf1bc0d3de
commit
56f37c387c
@ -1107,7 +1107,6 @@ vpx/vpx_decoder.h
|
||||
vpx/vpx_encoder.h
|
||||
vpx/vp8cx.h
|
||||
vpx/vp8dx.h
|
||||
sydneyaudio/sydney_audio.h
|
||||
vorbis/codec.h
|
||||
theora/theoradec.h
|
||||
tremor/ivorbiscodec.h
|
||||
|
33
configure.in
33
configure.in
@ -4211,7 +4211,6 @@ MOZ_JSDEBUGGER=1
|
||||
MOZ_AUTH_EXTENSION=1
|
||||
MOZ_OGG=1
|
||||
MOZ_RAW=
|
||||
MOZ_SYDNEYAUDIO=
|
||||
MOZ_SPEEX_RESAMPLER=1
|
||||
MOZ_SOUNDTOUCH=1
|
||||
MOZ_CUBEB=
|
||||
@ -5370,7 +5369,6 @@ MOZ_ARG_DISABLE_BOOL(ogg,
|
||||
|
||||
if test -n "$MOZ_OGG"; then
|
||||
AC_DEFINE(MOZ_OGG)
|
||||
MOZ_SYDNEYAUDIO=1
|
||||
MOZ_CUBEB=1
|
||||
MOZ_MEDIA=1
|
||||
|
||||
@ -5453,7 +5451,6 @@ MOZ_ARG_DISABLE_BOOL(wmf,
|
||||
|
||||
if test -n "$MOZ_WMF"; then
|
||||
AC_DEFINE(MOZ_WMF)
|
||||
MOZ_SYDNEYAUDIO=1
|
||||
MOZ_CUBEB=1
|
||||
MOZ_MEDIA=1
|
||||
fi;
|
||||
@ -5550,7 +5547,6 @@ AC_SUBST(MOZ_LIBVPX_CFLAGS)
|
||||
AC_SUBST(MOZ_LIBVPX_LIBS)
|
||||
|
||||
if test "$MOZ_WEBM"; then
|
||||
MOZ_SYDNEYAUDIO=1
|
||||
MOZ_CUBEB=1
|
||||
MOZ_MEDIA=1
|
||||
if test "$MOZ_SAMPLE_TYPE_FLOAT32"; then
|
||||
@ -5654,19 +5650,14 @@ MOZ_ARG_DISABLE_BOOL(wave,
|
||||
|
||||
if test -n "$MOZ_WAVE"; then
|
||||
AC_DEFINE(MOZ_WAVE)
|
||||
MOZ_SYDNEYAUDIO=1
|
||||
MOZ_CUBEB=1
|
||||
MOZ_MEDIA=1
|
||||
fi
|
||||
|
||||
dnl ========================================================
|
||||
dnl = Handle dependent SYDNEYAUDIO, CUBEB, and MEDIA defines
|
||||
dnl = Handle dependent CUBEB and MEDIA defines
|
||||
dnl ========================================================
|
||||
|
||||
if test -n "$MOZ_SYDNEYAUDIO"; then
|
||||
AC_DEFINE(MOZ_SYDNEYAUDIO)
|
||||
fi
|
||||
|
||||
if test -n "$MOZ_SPEEX_RESAMPLER"; then
|
||||
AC_DEFINE(MOZ_SPEEX_RESAMPLER)
|
||||
fi
|
||||
@ -5676,26 +5667,7 @@ if test -n "$MOZ_SOUNDTOUCH"; then
|
||||
fi
|
||||
|
||||
if test -n "$MOZ_CUBEB"; then
|
||||
case "$target" in
|
||||
*-android*|*-linuxandroid*)
|
||||
AC_DEFINE(MOZ_CUBEB)
|
||||
;;
|
||||
*-linux*)
|
||||
AC_DEFINE(MOZ_CUBEB)
|
||||
;;
|
||||
*-mingw*)
|
||||
AC_DEFINE(MOZ_CUBEB)
|
||||
;;
|
||||
*-darwin*)
|
||||
AC_DEFINE(MOZ_CUBEB)
|
||||
;;
|
||||
*-openbsd*)
|
||||
AC_DEFINE(MOZ_CUBEB)
|
||||
;;
|
||||
*)
|
||||
dnl Other targets will be enabled soon.
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if test -n "$MOZ_MEDIA"; then
|
||||
@ -5727,7 +5699,7 @@ dnl = Check alsa availability on Linux if using sydneyaudio
|
||||
dnl ========================================================
|
||||
|
||||
dnl If using sydneyaudio with Linux, ensure that the alsa library is available
|
||||
if test -n "$MOZ_SYDNEYAUDIO" -a "$OS_TARGET" = "Linux"; then
|
||||
if test -n "$MOZ_CUBEB" -a "$OS_TARGET" = "Linux"; then
|
||||
MOZ_ALSA=1
|
||||
fi
|
||||
|
||||
@ -8851,7 +8823,6 @@ AC_SUBST(MOZ_APP_COMPONENT_LIBS)
|
||||
AC_SUBST(MOZ_APP_EXTRA_LIBS)
|
||||
|
||||
AC_SUBST(MOZ_MEDIA)
|
||||
AC_SUBST(MOZ_SYDNEYAUDIO)
|
||||
AC_SUBST(MOZ_SPEEX_RESAMPLER)
|
||||
AC_SUBST(MOZ_SOUNDTOUCH)
|
||||
AC_SUBST(MOZ_CUBEB)
|
||||
|
@ -13,9 +13,6 @@
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include <algorithm>
|
||||
extern "C" {
|
||||
#include "sydneyaudio/sydney_audio.h"
|
||||
}
|
||||
#include "mozilla/Preferences.h"
|
||||
|
||||
#if defined(MOZ_CUBEB)
|
||||
@ -33,63 +30,15 @@ public:
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
#define SA_PER_STREAM_VOLUME 1
|
||||
#endif
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
PRLogModuleInfo* gAudioStreamLog = nullptr;
|
||||
#endif
|
||||
|
||||
static const uint32_t FAKE_BUFFER_SIZE = 176400;
|
||||
|
||||
// Number of milliseconds per second.
|
||||
static const int64_t MS_PER_S = 1000;
|
||||
|
||||
class NativeAudioStream : public AudioStream
|
||||
{
|
||||
public:
|
||||
~NativeAudioStream();
|
||||
NativeAudioStream();
|
||||
|
||||
nsresult Init(int32_t aNumChannels, int32_t aRate,
|
||||
const dom::AudioChannelType aAudioChannelType);
|
||||
void Shutdown();
|
||||
nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames);
|
||||
uint32_t Available();
|
||||
void SetVolume(double aVolume);
|
||||
void Drain();
|
||||
void Start();
|
||||
void Pause();
|
||||
void Resume();
|
||||
int64_t GetPosition();
|
||||
int64_t GetPositionInFrames();
|
||||
int64_t GetPositionInFramesInternal();
|
||||
bool IsPaused();
|
||||
int32_t GetMinWriteSize();
|
||||
|
||||
private:
|
||||
int32_t WriteToBackend(const float* aBuffer, uint32_t aFrames);
|
||||
int32_t WriteToBackend(const short* aBuffer, uint32_t aFrames);
|
||||
|
||||
double mVolume;
|
||||
void* mAudioHandle;
|
||||
|
||||
// True if this audio stream is paused.
|
||||
bool mPaused;
|
||||
|
||||
// True if this stream has encountered an error.
|
||||
bool mInError;
|
||||
|
||||
};
|
||||
|
||||
#define PREF_VOLUME_SCALE "media.volume_scale"
|
||||
#define PREF_USE_CUBEB "media.use_cubeb"
|
||||
#define PREF_CUBEB_LATENCY "media.cubeb_latency_ms"
|
||||
|
||||
static Mutex* gAudioPrefsLock = nullptr;
|
||||
static double gVolumeScale;
|
||||
static bool gUseCubeb;
|
||||
static uint32_t gCubebLatency;
|
||||
|
||||
static int PrefChanged(const char* aPref, void* aClosure)
|
||||
@ -103,10 +52,6 @@ static int PrefChanged(const char* aPref, void* aClosure)
|
||||
NS_ConvertUTF16toUTF8 utf8(value);
|
||||
gVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr));
|
||||
}
|
||||
} else if (strcmp(aPref, PREF_USE_CUBEB) == 0) {
|
||||
bool value = Preferences::GetBool(aPref, true);
|
||||
MutexAutoLock lock(*gAudioPrefsLock);
|
||||
gUseCubeb = value;
|
||||
} else if (strcmp(aPref, PREF_CUBEB_LATENCY) == 0) {
|
||||
// Arbitrary default stream latency of 100ms. The higher this
|
||||
// value, the longer stream volume changes will take to become
|
||||
@ -125,12 +70,6 @@ static double GetVolumeScale()
|
||||
}
|
||||
|
||||
#if defined(MOZ_CUBEB)
|
||||
static bool GetUseCubeb()
|
||||
{
|
||||
MutexAutoLock lock(*gAudioPrefsLock);
|
||||
return gUseCubeb;
|
||||
}
|
||||
|
||||
static cubeb* gCubebContext;
|
||||
|
||||
static cubeb* GetCubebContext()
|
||||
@ -151,29 +90,6 @@ static uint32_t GetCubebLatency()
|
||||
}
|
||||
#endif
|
||||
|
||||
static sa_stream_type_t ConvertChannelToSAType(dom::AudioChannelType aType)
|
||||
{
|
||||
switch(aType) {
|
||||
case dom::AUDIO_CHANNEL_NORMAL:
|
||||
return SA_STREAM_TYPE_SYSTEM;
|
||||
case dom::AUDIO_CHANNEL_CONTENT:
|
||||
return SA_STREAM_TYPE_MUSIC;
|
||||
case dom::AUDIO_CHANNEL_NOTIFICATION:
|
||||
return SA_STREAM_TYPE_NOTIFICATION;
|
||||
case dom::AUDIO_CHANNEL_ALARM:
|
||||
return SA_STREAM_TYPE_ALARM;
|
||||
case dom::AUDIO_CHANNEL_TELEPHONY:
|
||||
return SA_STREAM_TYPE_VOICE_CALL;
|
||||
case dom::AUDIO_CHANNEL_RINGER:
|
||||
return SA_STREAM_TYPE_RING;
|
||||
case dom::AUDIO_CHANNEL_PUBLICNOTIFICATION:
|
||||
return SA_STREAM_TYPE_ENFORCED_AUDIBLE;
|
||||
default:
|
||||
NS_ERROR("The value of AudioChannelType is invalid");
|
||||
return SA_STREAM_TYPE_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(MOZ_CUBEB) && (__ANDROID__)
|
||||
static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannelType aType)
|
||||
{
|
||||
@ -216,8 +132,6 @@ void AudioStream::InitLibrary()
|
||||
PrefChanged(PREF_VOLUME_SCALE, nullptr);
|
||||
Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
|
||||
#if defined(MOZ_CUBEB)
|
||||
PrefChanged(PREF_USE_CUBEB, nullptr);
|
||||
Preferences::RegisterCallback(PrefChanged, PREF_USE_CUBEB);
|
||||
PrefChanged(PREF_CUBEB_LATENCY, nullptr);
|
||||
Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
|
||||
#endif
|
||||
@ -227,7 +141,7 @@ void AudioStream::ShutdownLibrary()
|
||||
{
|
||||
Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
|
||||
#if defined(MOZ_CUBEB)
|
||||
Preferences::UnregisterCallback(PrefChanged, PREF_USE_CUBEB);
|
||||
Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
|
||||
#endif
|
||||
delete gAudioPrefsLock;
|
||||
gAudioPrefsLock = nullptr;
|
||||
@ -315,258 +229,6 @@ int64_t AudioStream::GetWritten()
|
||||
return mWritten;
|
||||
}
|
||||
|
||||
NativeAudioStream::NativeAudioStream() :
|
||||
mVolume(1.0),
|
||||
mAudioHandle(0),
|
||||
mPaused(false),
|
||||
mInError(false)
|
||||
{
|
||||
}
|
||||
|
||||
NativeAudioStream::~NativeAudioStream()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
nsresult NativeAudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
||||
const dom::AudioChannelType aAudioChannelType)
|
||||
{
|
||||
mInRate = mOutRate = aRate;
|
||||
mChannels = aNumChannels;
|
||||
|
||||
if (sa_stream_create_pcm(reinterpret_cast<sa_stream_t**>(&mAudioHandle),
|
||||
NULL,
|
||||
SA_MODE_WRONLY,
|
||||
SA_PCM_FORMAT_S16_NE,
|
||||
aRate,
|
||||
aNumChannels) != SA_SUCCESS) {
|
||||
mAudioHandle = nullptr;
|
||||
mInError = true;
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_create_pcm error"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
int saError = sa_stream_set_stream_type(static_cast<sa_stream_t*>(mAudioHandle),
|
||||
ConvertChannelToSAType(aAudioChannelType));
|
||||
if (saError != SA_SUCCESS && saError != SA_ERROR_NOT_SUPPORTED) {
|
||||
mAudioHandle = nullptr;
|
||||
mInError = true;
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_set_stream_type error"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (sa_stream_open(static_cast<sa_stream_t*>(mAudioHandle)) != SA_SUCCESS) {
|
||||
sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
|
||||
mAudioHandle = nullptr;
|
||||
mInError = true;
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_open error"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mInError = false;
|
||||
|
||||
mAudioClock.Init();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void NativeAudioStream::Shutdown()
|
||||
{
|
||||
if (!mAudioHandle)
|
||||
return;
|
||||
|
||||
sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
|
||||
mAudioHandle = nullptr;
|
||||
mInError = true;
|
||||
}
|
||||
|
||||
int32_t NativeAudioStream::WriteToBackend(const AudioDataValue* aBuffer, uint32_t aSamples)
|
||||
{
|
||||
double scaledVolume = GetVolumeScale() * mVolume;
|
||||
|
||||
nsAutoArrayPtr<short> outputBuffer(new short[aSamples]);
|
||||
ConvertAudioSamplesWithScale(aBuffer, outputBuffer.get(), aSamples, scaledVolume);
|
||||
|
||||
if (sa_stream_write(static_cast<sa_stream_t*>(mAudioHandle),
|
||||
outputBuffer,
|
||||
aSamples * sizeof(short)) != SA_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
mAudioClock.UpdateWritePosition(aSamples / mChannels);
|
||||
return aSamples;
|
||||
}
|
||||
|
||||
nsresult NativeAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames)
|
||||
{
|
||||
NS_ASSERTION(!mPaused, "Don't write audio when paused, you'll block");
|
||||
|
||||
if (mInError)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
uint32_t samples = aFrames * mChannels;
|
||||
int32_t written = -1;
|
||||
|
||||
if (mInRate != mOutRate) {
|
||||
if (EnsureTimeStretcherInitialized() != NS_OK) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mTimeStretcher->putSamples(aBuf, aFrames);
|
||||
uint32_t numFrames = mTimeStretcher->numSamples();
|
||||
uint32_t arraySize = numFrames * mChannels * sizeof(AudioDataValue);
|
||||
nsAutoArrayPtr<AudioDataValue> data(new AudioDataValue[arraySize]);
|
||||
uint32_t framesAvailable = mTimeStretcher->receiveSamples(data, numFrames);
|
||||
NS_ASSERTION(mTimeStretcher->numSamples() == 0,
|
||||
"We did not get all the data from the SoundTouch pipeline.");
|
||||
// It is possible to have nothing to write: the data are in the processing
|
||||
// pipeline, and will be written to the backend next time.
|
||||
if (framesAvailable) {
|
||||
written = WriteToBackend(data, framesAvailable * mChannels);
|
||||
} else {
|
||||
written = 0;
|
||||
}
|
||||
} else {
|
||||
written = WriteToBackend(aBuf, samples);
|
||||
}
|
||||
|
||||
mWritten += aFrames;
|
||||
|
||||
if (written == -1) {
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_write error"));
|
||||
mInError = true;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t NativeAudioStream::Available()
|
||||
{
|
||||
// If the audio backend failed to open, lie and say we'll accept some
|
||||
// data.
|
||||
if (mInError)
|
||||
return FAKE_BUFFER_SIZE;
|
||||
|
||||
size_t s = 0;
|
||||
if (sa_stream_get_write_size(static_cast<sa_stream_t*>(mAudioHandle), &s) != SA_SUCCESS)
|
||||
return 0;
|
||||
|
||||
return s / mChannels / sizeof(short);
|
||||
}
|
||||
|
||||
void NativeAudioStream::SetVolume(double aVolume)
|
||||
{
|
||||
NS_ASSERTION(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
|
||||
#if defined(SA_PER_STREAM_VOLUME)
|
||||
if (sa_stream_set_volume_abs(static_cast<sa_stream_t*>(mAudioHandle), aVolume) != SA_SUCCESS) {
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_set_volume_abs error"));
|
||||
mInError = true;
|
||||
}
|
||||
#else
|
||||
mVolume = aVolume;
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeAudioStream::Drain()
|
||||
{
|
||||
NS_ASSERTION(!mPaused, "Don't drain audio when paused, it won't finish!");
|
||||
|
||||
// Write all the frames still in the time stretcher pipeline.
|
||||
if (mTimeStretcher) {
|
||||
uint32_t numFrames = mTimeStretcher->numSamples();
|
||||
uint32_t arraySize = numFrames * mChannels * sizeof(AudioDataValue);
|
||||
nsAutoArrayPtr<AudioDataValue> data(new AudioDataValue[arraySize]);
|
||||
uint32_t framesAvailable = mTimeStretcher->receiveSamples(data, numFrames);
|
||||
int32_t written = 0;
|
||||
if (framesAvailable) {
|
||||
written = WriteToBackend(data, framesAvailable * mChannels);
|
||||
}
|
||||
|
||||
if (written == -1) {
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_write error"));
|
||||
mInError = true;
|
||||
}
|
||||
|
||||
NS_ASSERTION(mTimeStretcher->numSamples() == 0,
|
||||
"We did not get all the data from the SoundTouch pipeline.");
|
||||
}
|
||||
|
||||
if (mInError)
|
||||
return;
|
||||
|
||||
int r = sa_stream_drain(static_cast<sa_stream_t*>(mAudioHandle));
|
||||
if (r != SA_SUCCESS && r != SA_ERROR_INVALID) {
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_drain error"));
|
||||
mInError = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NativeAudioStream::Start()
|
||||
{
|
||||
// Since sydneyaudio is a push API, the playback is started when enough frames
|
||||
// have been written. Hence, Start() is a noop.
|
||||
}
|
||||
|
||||
void NativeAudioStream::Pause()
|
||||
{
|
||||
if (mInError)
|
||||
return;
|
||||
mPaused = true;
|
||||
sa_stream_pause(static_cast<sa_stream_t*>(mAudioHandle));
|
||||
}
|
||||
|
||||
void NativeAudioStream::Resume()
|
||||
{
|
||||
if (mInError)
|
||||
return;
|
||||
mPaused = false;
|
||||
sa_stream_resume(static_cast<sa_stream_t*>(mAudioHandle));
|
||||
}
|
||||
|
||||
int64_t NativeAudioStream::GetPosition()
|
||||
{
|
||||
return mAudioClock.GetPosition();
|
||||
}
|
||||
|
||||
int64_t NativeAudioStream::GetPositionInFrames()
|
||||
{
|
||||
return mAudioClock.GetPositionInFrames();
|
||||
}
|
||||
|
||||
int64_t NativeAudioStream::GetPositionInFramesInternal()
|
||||
{
|
||||
if (mInError) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sa_position_t positionType = SA_POSITION_WRITE_SOFTWARE;
|
||||
#if defined(XP_WIN)
|
||||
positionType = SA_POSITION_WRITE_HARDWARE;
|
||||
#endif
|
||||
int64_t position = 0;
|
||||
if (sa_stream_get_position(static_cast<sa_stream_t*>(mAudioHandle),
|
||||
positionType, &position) == SA_SUCCESS) {
|
||||
return position / mChannels / sizeof(short);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool NativeAudioStream::IsPaused()
|
||||
{
|
||||
return mPaused;
|
||||
}
|
||||
|
||||
int32_t NativeAudioStream::GetMinWriteSize()
|
||||
{
|
||||
size_t size;
|
||||
int r = sa_stream_get_min_write(static_cast<sa_stream_t*>(mAudioHandle),
|
||||
&size);
|
||||
if (r == SA_ERROR_NOT_SUPPORTED)
|
||||
return 1;
|
||||
else if (r != SA_SUCCESS || size > INT32_MAX)
|
||||
return -1;
|
||||
|
||||
return static_cast<int32_t>(size / mChannels / sizeof(short));
|
||||
}
|
||||
|
||||
#if defined(MOZ_CUBEB)
|
||||
class nsCircularByteBuffer
|
||||
{
|
||||
@ -653,7 +315,6 @@ class BufferedAudioStream : public AudioStream
|
||||
int64_t GetPositionInFrames();
|
||||
int64_t GetPositionInFramesInternal();
|
||||
bool IsPaused();
|
||||
int32_t GetMinWriteSize();
|
||||
// This method acquires the monitor and forward the call to the base
|
||||
// class, to prevent a race on |mTimeStretcher|, in
|
||||
// |AudioStream::EnsureTimeStretcherInitialized|.
|
||||
@ -739,11 +400,9 @@ private:
|
||||
AudioStream* AudioStream::AllocateStream()
|
||||
{
|
||||
#if defined(MOZ_CUBEB)
|
||||
if (GetUseCubeb()) {
|
||||
return new BufferedAudioStream();
|
||||
}
|
||||
#endif
|
||||
return new NativeAudioStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if defined(MOZ_CUBEB)
|
||||
@ -878,12 +537,6 @@ BufferedAudioStream::Available()
|
||||
return BytesToFrames(mBuffer.Available());
|
||||
}
|
||||
|
||||
int32_t
|
||||
BufferedAudioStream::GetMinWriteSize()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::SetVolume(double aVolume)
|
||||
{
|
||||
|
@ -161,10 +161,6 @@ public:
|
||||
// Returns true when the audio stream is paused.
|
||||
virtual bool IsPaused() = 0;
|
||||
|
||||
// Returns the minimum number of audio frames which must be written before
|
||||
// you can be sure that something will be played.
|
||||
virtual int32_t GetMinWriteSize() = 0;
|
||||
|
||||
int GetRate() { return mOutRate; }
|
||||
int GetChannels() { return mChannels; }
|
||||
|
||||
|
@ -23,6 +23,7 @@ EXPORTS = \
|
||||
AudioNodeStream.h \
|
||||
AudioSampleFormat.h \
|
||||
AudioSegment.h \
|
||||
AudioStream.h \
|
||||
BufferMediaResource.h \
|
||||
DecoderTraits.h \
|
||||
DOMMediaStream.h \
|
||||
@ -51,6 +52,7 @@ CPPSRCS = \
|
||||
AudioNodeEngine.cpp \
|
||||
AudioNodeStream.cpp \
|
||||
AudioSegment.cpp \
|
||||
AudioStream.cpp \
|
||||
DecoderTraits.cpp \
|
||||
DOMMediaStream.cpp \
|
||||
FileBlockCache.cpp \
|
||||
@ -67,15 +69,6 @@ CPPSRCS = \
|
||||
VideoUtils.cpp \
|
||||
$(NULL)
|
||||
|
||||
ifdef MOZ_SYDNEYAUDIO
|
||||
EXPORTS += \
|
||||
AudioStream.h \
|
||||
$(NULL)
|
||||
CPPSRCS += \
|
||||
AudioStream.cpp \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
FORCE_STATIC_LIB = 1
|
||||
|
||||
include $(topsrcdir)/config/config.mk
|
||||
|
@ -986,7 +986,6 @@ void MediaDecoderStateMachine::AudioLoop()
|
||||
bool setPlaybackRate;
|
||||
bool preservesPitch;
|
||||
bool setPreservesPitch;
|
||||
int32_t minWriteFrames = -1;
|
||||
AudioChannelType audioChannelType;
|
||||
|
||||
{
|
||||
@ -1089,9 +1088,6 @@ void MediaDecoderStateMachine::AudioLoop()
|
||||
NS_WARNING("Setting the pitch preservation failed in AudioLoop.");
|
||||
}
|
||||
}
|
||||
if (minWriteFrames == -1) {
|
||||
minWriteFrames = mAudioStream->GetMinWriteSize();
|
||||
}
|
||||
NS_ASSERTION(mReader->AudioQueue().GetSize() > 0,
|
||||
"Should have data to play");
|
||||
// See if there's a gap in the audio. If there is, push silence into the
|
||||
@ -1149,23 +1145,6 @@ void MediaDecoderStateMachine::AudioLoop()
|
||||
// before the audio thread terminates.
|
||||
bool seeking = false;
|
||||
{
|
||||
int64_t unplayedFrames = audioDuration % minWriteFrames;
|
||||
if (minWriteFrames > 1 && unplayedFrames > 0) {
|
||||
// Sound is written by libsydneyaudio to the hardware in blocks of
|
||||
// frames of size minWriteFrames. So if the number of frames we've
|
||||
// written isn't an exact multiple of minWriteFrames, we'll have
|
||||
// left over audio data which hasn't yet been written to the hardware,
|
||||
// and so that audio will not start playing. Write silence to ensure
|
||||
// the last block gets pushed to hardware, so that playback starts.
|
||||
int64_t framesToWrite = minWriteFrames - unplayedFrames;
|
||||
if (framesToWrite < UINT32_MAX / channels) {
|
||||
// Write silence manually rather than using PlaySilence(), so that
|
||||
// the AudioAPI doesn't get a copy of the audio frames.
|
||||
ReentrantMonitorAutoExit exit(mDecoder->GetReentrantMonitor());
|
||||
WriteSilence(mAudioStream, framesToWrite);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t oldPosition = -1;
|
||||
int64_t position = GetMediaTime();
|
||||
while (oldPosition != position &&
|
||||
|
@ -10,7 +10,7 @@ Each video element for a media file has two threads:
|
||||
hardware. This is done in a separate thread to ensure that the
|
||||
audio hardware gets a constant stream of data without
|
||||
interruption due to decoding or display. At some point
|
||||
libsydneyaudio will be refactored to have a callback interface
|
||||
AudioStream will be refactored to have a callback interface
|
||||
where it asks for data and an extra thread will no longer be
|
||||
needed.
|
||||
|
||||
@ -70,7 +70,7 @@ to shut down the decode thread in order to conserve resources.
|
||||
During playback the audio thread will be idle (via a Wait() on the
|
||||
monitor) if the audio queue is empty. Otherwise it constantly pops
|
||||
audio data off the queue and plays it with a blocking write to the audio
|
||||
hardware (via AudioStream and libsydneyaudio).
|
||||
hardware (via AudioStream).
|
||||
|
||||
*/
|
||||
#if !defined(MediaDecoderStateMachine_h__)
|
||||
|
@ -12,9 +12,9 @@
|
||||
* The decoder implementation is currently limited to Linear PCM encoded
|
||||
* audio data with one or two channels of 8- or 16-bit samples at sample
|
||||
* rates from 100 Hz to 96 kHz. The number of channels is limited by what
|
||||
* the audio backend (sydneyaudio via AudioStream) currently supports. The
|
||||
* supported sample rate is artificially limited to arbitrarily selected sane
|
||||
* values. Support for additional channels (and other new features) would
|
||||
* the audio backend (via AudioStream) currently supports. The supported
|
||||
* sample rate is artificially limited to arbitrarily selected sane values.
|
||||
* Support for additional channels (and other new features) would
|
||||
* require extending WaveDecoder to support parsing the newer
|
||||
* WAVE_FORMAT_EXTENSIBLE chunk format.
|
||||
**/
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
#include "mozilla/Hal.h"
|
||||
#include "AudioManager.h"
|
||||
#include "gonk/AudioSystem.h"
|
||||
#include "android_audio/AudioSystem.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "AudioChannelService.h"
|
||||
|
@ -59,8 +59,6 @@ CPPSRCS += \
|
||||
VolumeServiceIOThread.cpp \
|
||||
VolumeServiceTest.cpp \
|
||||
$(NULL)
|
||||
# for our local copy of AudioSystem.h
|
||||
LOCAL_INCLUDES += -I$(topsrcdir)/media/libsydneyaudio/src
|
||||
EXPORTS = \
|
||||
GonkGPSGeolocationProvider.h \
|
||||
nsVolume.h \
|
||||
|
@ -1107,7 +1107,6 @@ vpx/vpx_decoder.h
|
||||
vpx/vpx_encoder.h
|
||||
vpx/vp8cx.h
|
||||
vpx/vp8dx.h
|
||||
sydneyaudio/sydney_audio.h
|
||||
vorbis/codec.h
|
||||
theora/theoradec.h
|
||||
tremor/ivorbiscodec.h
|
||||
|
@ -88,9 +88,7 @@
|
||||
#include "GStreamerFormatHelper.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_SYDNEYAUDIO
|
||||
#include "AudioStream.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include "nsVolumeService.h"
|
||||
@ -246,9 +244,7 @@ nsLayoutStatics::Initialize()
|
||||
return rv;
|
||||
}
|
||||
|
||||
#ifdef MOZ_SYDNEYAUDIO
|
||||
AudioStream::InitLibrary();
|
||||
#endif
|
||||
|
||||
nsContentSink::InitializeStatics();
|
||||
nsHtml5Module::InitializeStatics();
|
||||
@ -354,9 +350,7 @@ nsLayoutStatics::Shutdown()
|
||||
GStreamerFormatHelper::Shutdown();
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_SYDNEYAUDIO
|
||||
AudioStream::ShutdownLibrary();
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WMF
|
||||
WMFDecoder::UnloadDLLs();
|
||||
|
@ -71,12 +71,6 @@ SHARED_LIBRARY_LIBS += \
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef MOZ_SYDNEYAUDIO
|
||||
SHARED_LIBRARY_LIBS += \
|
||||
$(DEPTH)/media/libsydneyaudio/src/$(LIB_PREFIX)sydneyaudio.$(LIB_SUFFIX) \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifdef MOZ_SPEEX_RESAMPLER
|
||||
SHARED_LIBRARY_LIBS += \
|
||||
$(DEPTH)/media/libspeex_resampler/src/$(LIB_PREFIX)speex_resampler.$(LIB_SUFFIX) \
|
||||
|
@ -83,19 +83,6 @@ vorbis_synthesis_pcmout
|
||||
vorbis_synthesis_read
|
||||
vorbis_synthesis_restart
|
||||
#endif
|
||||
#ifdef MOZ_SYDNEYAUDIO
|
||||
sa_stream_create_pcm
|
||||
sa_stream_destroy
|
||||
sa_stream_drain
|
||||
sa_stream_get_min_write
|
||||
sa_stream_get_position
|
||||
sa_stream_get_write_size
|
||||
sa_stream_open
|
||||
sa_stream_pause
|
||||
sa_stream_resume
|
||||
sa_stream_write
|
||||
sa_stream_set_stream_type
|
||||
#endif
|
||||
#ifdef MOZ_SPEEX_RESAMPLER
|
||||
speex_resampler_init
|
||||
speex_resampler_init_frac
|
||||
|
@ -1,34 +0,0 @@
|
||||
Jean-Marc Valin (jmspeex) <jean-marc.valin@usherbrooke.ca>
|
||||
- Design
|
||||
|
||||
Lennart Poettering <lennart@poettering.net>
|
||||
- Design
|
||||
|
||||
Shane Stephens (shans) <shans@annodex.net>
|
||||
- First Implementation
|
||||
|
||||
Chris Double (doublec) <chris.double@double.co.nz>
|
||||
- ALSA support
|
||||
- OSS support
|
||||
|
||||
Brian Lu <brian.lu@sun.com>
|
||||
- Sun Audio support
|
||||
|
||||
Jeremy D. Lea (reg@openpave.org)
|
||||
- OSS support
|
||||
|
||||
Marcin Lubonski <marcin@it.uts.edu.au>
|
||||
- Port to windows
|
||||
|
||||
Michael Martin (tahn) <myk.martin@gmail.com>
|
||||
- Port to Max OS X
|
||||
- OSS support
|
||||
- ALSA support
|
||||
|
||||
Jan Gerber (j^) <j@oil21.org>
|
||||
- Library creation
|
||||
|
||||
Silvia Pfeiffer (ginger) <silvia@annodex.net>
|
||||
- general maintenance
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
@ -1,4 +0,0 @@
|
||||
This source was originally based on libsydneyaudio from
|
||||
http://git.xiph.org/?p=libsydneyaudio.git commit 716c3c17. As this project
|
||||
appears to be dead, substantial local changes have been made to this version.
|
||||
Refer to the version control logs for details.
|
@ -1,18 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
EXPORTS_NAMESPACES = sydneyaudio
|
||||
|
||||
EXPORTS_sydneyaudio = \
|
||||
sydney_audio.h \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
@ -1,5 +0,0 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
@ -1,460 +0,0 @@
|
||||
#ifndef foosydneyhfoo
|
||||
#define foosydneyhfoo
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/* Requirements:
|
||||
|
||||
- In sync mode, the device will automatically write data so that an initial read causes writes
|
||||
of zeros to be issued to that one can do "while (1); {read(); write()}
|
||||
|
||||
- All functions are thread-safe and can be called in any thread context. None of the functions is
|
||||
async-signal safe.
|
||||
|
||||
- It is assumed that duplex streams have a single clock (synchronised)
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#if !defined (WIN32)
|
||||
#include <sys/param.h>
|
||||
#include <inttypes.h>
|
||||
#if defined(__FreeBSD__) || defined(ANDROID)
|
||||
#include <sys/endian.h>
|
||||
#endif
|
||||
#else
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
|
||||
/* Detect byte order, based on sys/param.h */
|
||||
#undef SA_LITTLE_ENDIAN
|
||||
#undef SA_BIG_ENDIAN
|
||||
|
||||
#if defined(__BYTE_ORDER)
|
||||
# if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
# define SA_LITTLE_ENDIAN 1
|
||||
# elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# define SA_BIG_ENDIAN 1
|
||||
# endif
|
||||
#elif defined(_BYTE_ORDER)
|
||||
# if _BYTE_ORDER == _LITTLE_ENDIAN
|
||||
# define SA_LITTLE_ENDIAN 1
|
||||
# elif _BYTE_ORDER == _BIG_ENDIAN
|
||||
# define SA_BIG_ENDIAN 1
|
||||
# endif
|
||||
#elif defined(WIN32)
|
||||
# define SA_LITTLE_ENDIAN 1
|
||||
#elif defined(__APPLE__)
|
||||
# if defined(__BIG_ENDIAN__)
|
||||
# define SA_BIG_ENDIAN 1
|
||||
# else
|
||||
# define SA_LITTLE_ENDIAN 1
|
||||
# endif
|
||||
#elif defined(SOLARIS)
|
||||
# if defined(_BIG_ENDIAN)
|
||||
# define SA_BIG_ENDIAN 1
|
||||
# else
|
||||
# define SA_LITTLE_ENDIAN 1
|
||||
# endif
|
||||
#elif defined(AIX)
|
||||
# define SA_BIG_ENDIAN 1
|
||||
#else
|
||||
# error "Cannot determine byte order!"
|
||||
#endif
|
||||
|
||||
#if defined(WIN32)
|
||||
#if !defined(int32_t)
|
||||
typedef __int32 int32_t;
|
||||
#endif
|
||||
#if !defined(int64_t)
|
||||
typedef __int64 int64_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct sa_stream sa_stream_t;
|
||||
|
||||
#if defined(WIN32) || defined(OS2)
|
||||
/* (left << 16 | right) (16 bits per channel) */
|
||||
#define SA_VOLUME_MUTED ((int32_t) (0x00000000))
|
||||
#else
|
||||
/** Volume that corresponds to muted in/out */
|
||||
#define SA_VOLUME_MUTED ((int32_t) (-0x80000000))
|
||||
#endif
|
||||
|
||||
/** Ways to express seek offsets for pread/pwrite */
|
||||
typedef enum {
|
||||
SA_SEEK_RELATIVE,
|
||||
SA_SEEK_ABSOLUTE,
|
||||
SA_SEEK_RELATIVE_END,
|
||||
_SA_SEEK_MAX
|
||||
} sa_seek_t;
|
||||
|
||||
/** Supported formats */
|
||||
typedef enum {
|
||||
SA_PCM_FORMAT_U8,
|
||||
SA_PCM_FORMAT_ULAW,
|
||||
SA_PCM_FORMAT_ALAW,
|
||||
SA_PCM_FORMAT_S16_LE,
|
||||
SA_PCM_FORMAT_S16_BE,
|
||||
SA_PCM_FORMAT_S24_LE,
|
||||
SA_PCM_FORMAT_S24_BE,
|
||||
SA_PCM_FORMAT_S32_LE,
|
||||
SA_PCM_FORMAT_S32_BE,
|
||||
SA_PCM_FORMAT_FLOAT32_LE,
|
||||
SA_PCM_FORMAT_FLOAT32_BE,
|
||||
_SA_PCM_FORMAT_MAX
|
||||
} sa_pcm_format_t;
|
||||
|
||||
/* Native/reverse endianness definitions for PCM */
|
||||
#ifdef SA_LITTLE_ENDIAN
|
||||
#define SA_PCM_FORMAT_S16_NE SA_PCM_FORMAT_S16_LE
|
||||
#define SA_PCM_FORMAT_S24_NE SA_PCM_FORMAT_S24_LE
|
||||
#define SA_PCM_FORMAT_S32_NE SA_PCM_FORMAT_S32_LE
|
||||
#define SA_PCM_FORMAT_FLOAT32_NE SA_PCM_FORMAT_FLOAT32_LE
|
||||
#define SA_PCM_FORMAT_S16_RE SA_PCM_FORMAT_S16_BE
|
||||
#define SA_PCM_FORMAT_S24_RE SA_PCM_FORMAT_S24_BE
|
||||
#define SA_PCM_FORMAT_S32_RE SA_PCM_FORMAT_S32_BE
|
||||
#define SA_PCM_FORMAT_FLOAT32_RE SA_PCM_FORMAT_FLOAT32_BE
|
||||
#else
|
||||
#define SA_PCM_FORMAT_S16_NE SA_PCM_FORMAT_S16_BE
|
||||
#define SA_PCM_FORMAT_S24_NE SA_PCM_FORMAT_S24_BE
|
||||
#define SA_PCM_FORMAT_S32_NE SA_PCM_FORMAT_S32_BE
|
||||
#define SA_PCM_FORMAT_FLOAT32_NE SA_PCM_FORMAT_FLOAT32_BE
|
||||
#define SA_PCM_FORMAT_S16_RE SA_PCM_FORMAT_S16_LE
|
||||
#define SA_PCM_FORMAT_S24_RE SA_PCM_FORMAT_S24_LE
|
||||
#define SA_PCM_FORMAT_S32_RE SA_PCM_FORMAT_S32_LE
|
||||
#define SA_PCM_FORMAT_FLOAT32_RE SA_PCM_FORMAT_FLOAT32_LE
|
||||
#endif
|
||||
|
||||
#define SA_CODEC_MPEG "mpeg"
|
||||
#define SA_CODEC_AC3 "ac3"
|
||||
#define SA_CODEC_GSM "gsm"
|
||||
#define SA_CODEC_VORBIS "vorbis"
|
||||
#define SA_CODEC_SPEEX "speex"
|
||||
|
||||
/** Device opening modes */
|
||||
typedef enum {
|
||||
SA_MODE_WRONLY = 1,
|
||||
SA_MODE_RDONLY = 2,
|
||||
SA_MODE_RDWR = 3,
|
||||
_SA_MODE_MAX = 4
|
||||
} sa_mode_t;
|
||||
|
||||
/** Error codes */
|
||||
typedef enum {
|
||||
SA_SUCCESS = 0,
|
||||
SA_ERROR_NOT_SUPPORTED = -1,
|
||||
SA_ERROR_INVALID = -2,
|
||||
SA_ERROR_STATE = -3,
|
||||
SA_ERROR_OOM = -4,
|
||||
SA_ERROR_NO_DEVICE = -5,
|
||||
SA_ERROR_NO_DRIVER = -6,
|
||||
SA_ERROR_NO_CODEC = -7,
|
||||
SA_ERROR_NO_PCM_FORMAT = -7,
|
||||
SA_ERROR_SYSTEM = -8,
|
||||
SA_ERROR_NO_INIT = -9,
|
||||
SA_ERROR_NO_META = -10,
|
||||
SA_ERROR_NO_DATA = -11,
|
||||
SA_ERROR_NO_SPACE = -12,
|
||||
_SA_ERROR_MAX = -13
|
||||
} sa_error_t;
|
||||
|
||||
/** Possible events for notifications */
|
||||
typedef enum {
|
||||
SA_NOTIFY_REQUEST_START,
|
||||
SA_NOTIFY_REQUEST_STOP,
|
||||
SA_NOTIFY_CHANGED_READ_VOLUME,
|
||||
SA_NOTIFY_CHANGED_WRITE_VOLUME,
|
||||
SA_NOTIFY_CHANGED_DEVICE,
|
||||
_SA_NOTIFY_MAX
|
||||
} sa_notify_t;
|
||||
|
||||
/** Classes of events */
|
||||
typedef enum {
|
||||
SA_EVENT_REQUEST_IO,
|
||||
SA_EVENT_INIT_THREAD,
|
||||
SA_EVENT_NOTIFY,
|
||||
SA_EVENT_ERROR,
|
||||
_SA_EVENT_MAX
|
||||
} sa_event_t;
|
||||
|
||||
/** List of sample position queries */
|
||||
typedef enum {
|
||||
SA_POSITION_WRITE_DELAY,
|
||||
SA_POSITION_WRITE_HARDWARE,
|
||||
SA_POSITION_WRITE_SOFTWARE,
|
||||
SA_POSITION_READ_DELAY,
|
||||
SA_POSITION_READ_HARDWARE,
|
||||
SA_POSITION_READ_SOFTWARE,
|
||||
SA_POSITION_DUPLEX_DELAY,
|
||||
_SA_POSITION_MAX
|
||||
} sa_position_t;
|
||||
|
||||
/* Channel positions */
|
||||
typedef enum {
|
||||
SA_CHANNEL_MONO,
|
||||
SA_CHANNEL_LEFT,
|
||||
SA_CHANNEL_RIGHT,
|
||||
SA_CHANNEL_CENTER,
|
||||
SA_CHANNEL_FRONT_LEFT,
|
||||
SA_CHANNEL_FRONT_RIGHT,
|
||||
SA_CHANNEL_FRONT_CENTER,
|
||||
SA_CHANNEL_REAR_LEFT,
|
||||
SA_CHANNEL_REAR_RIGHT,
|
||||
SA_CHANNEL_REAR_CENTER,
|
||||
SA_CHANNEL_LFE,
|
||||
SA_CHANNEL_FRONT_LEFT_OF_CENTER,
|
||||
SA_CHANNEL_FRONT_RIGHT_OF_CENTER,
|
||||
SA_CHANNEL_SIDE_LEFT,
|
||||
SA_CHANNEL_SIDE_RIGHT,
|
||||
SA_CHANNEL_TOP_CENTER,
|
||||
SA_CHANNEL_TOP_FRONT_LEFT,
|
||||
SA_CHANNEL_TOP_FRONT_RIGHT,
|
||||
SA_CHANNEL_TOP_FRONT_CENTER,
|
||||
SA_CHANNEL_TOP_REAR_LEFT,
|
||||
SA_CHANNEL_TOP_REAR_RIGHT,
|
||||
SA_CHANNEL_TOP_REAR_CENTER,
|
||||
SA_CHANNEL_AUX0,
|
||||
SA_CHANNEL_AUX1,
|
||||
SA_CHANNEL_AUX2,
|
||||
SA_CHANNEL_AUX3,
|
||||
SA_CHANNEL_AUX4,
|
||||
SA_CHANNEL_AUX5,
|
||||
SA_CHANNEL_AUX6,
|
||||
SA_CHANNEL_AUX7,
|
||||
SA_CHANNEL_AUX8,
|
||||
SA_CHANNEL_AUX9,
|
||||
SA_CHANNEL_AUX10,
|
||||
SA_CHANNEL_AUX11,
|
||||
SA_CHANNEL_AUX12,
|
||||
SA_CHANNEL_AUX13,
|
||||
SA_CHANNEL_AUX14,
|
||||
SA_CHANNEL_AUX15,
|
||||
SA_CHANNEL_AUX16,
|
||||
SA_CHANNEL_AUX17,
|
||||
SA_CHANNEL_AUX18,
|
||||
SA_CHANNEL_AUX19,
|
||||
SA_CHANNEL_AUX20,
|
||||
SA_CHANNEL_AUX21,
|
||||
SA_CHANNEL_AUX22,
|
||||
SA_CHANNEL_AUX23,
|
||||
SA_CHANNEL_AUX24,
|
||||
SA_CHANNEL_AUX25,
|
||||
SA_CHANNEL_AUX26,
|
||||
SA_CHANNEL_AUX27,
|
||||
SA_CHANNEL_AUX28,
|
||||
SA_CHANNEL_AUX29,
|
||||
SA_CHANNEL_AUX30,
|
||||
SA_CHANNEL_AUX31,
|
||||
_SA_CHANNEL_MAX
|
||||
} sa_channel_t;
|
||||
|
||||
typedef enum {
|
||||
SA_STATE_INIT,
|
||||
SA_STATE_RUNNING,
|
||||
SA_STATE_STOPPED,
|
||||
/* put more stuff */
|
||||
_SA_STATE_MAX
|
||||
} sa_state_t;
|
||||
|
||||
typedef enum {
|
||||
SA_XRUN_MODE_STOP,
|
||||
SA_XRUN_MODE_SPIN,
|
||||
_SA_XRUN_MODE_MAX
|
||||
} sa_xrun_mode_t;
|
||||
|
||||
typedef enum {
|
||||
SA_ADJUST_UP = 1,
|
||||
SA_ADJUST_DOWN = -1,
|
||||
SA_ADJUST_NONE = 0
|
||||
} sa_adjust_t;
|
||||
|
||||
typedef enum {
|
||||
SA_STREAM_TYPE_VOICE_CALL = 0,
|
||||
SA_STREAM_TYPE_SYSTEM = 1,
|
||||
SA_STREAM_TYPE_RING = 2,
|
||||
SA_STREAM_TYPE_MUSIC = 3,
|
||||
SA_STREAM_TYPE_ALARM = 4,
|
||||
SA_STREAM_TYPE_NOTIFICATION = 5,
|
||||
SA_STREAM_TYPE_BLUETOOTH_SCO = 6,
|
||||
SA_STREAM_TYPE_ENFORCED_AUDIBLE = 7,
|
||||
SA_STREAM_TYPE_DTMF = 8,
|
||||
SA_STREAM_TYPE_TTS = 9,
|
||||
SA_STREAM_TYPE_FM = 10,
|
||||
|
||||
SA_STREAM_TYPE_MAX
|
||||
} sa_stream_type_t;
|
||||
|
||||
/* Some kind of meta information. */
|
||||
#define SA_META_CLIENT_NAME "sydney.client-name" /* utf-8 */
|
||||
#define SA_META_PROCESS_ID "sydney.process-id" /* getpid() */
|
||||
#define SA_META_LANGUAGE "sydney.language" /* de_DE and similar */
|
||||
|
||||
/* Some kind of meta information. Not filled in */
|
||||
#define SA_META_STREAM_NAME "sydney.stream-name" /* utf-8 */
|
||||
#define SA_META_ICON_NAME "sydney.icon-name" /* file name (no slashes) */
|
||||
#define SA_META_ICON_PNG "sydney.icon-png" /* PNG blob */
|
||||
#define SA_META_ROLE "sydney.role" /* one of: "music", "phone", "game", "event" */
|
||||
#define SA_META_X11_DISPLAY "sydney.x11-display" /* X11 display */
|
||||
#define SA_META_X11_WINDOW "sydney.x11-window" /* X11 window id */
|
||||
|
||||
/** Main callback function */
|
||||
typedef int (*sa_event_callback_t)(sa_stream_t *s, sa_event_t event);
|
||||
|
||||
/** Create an opaque (e.g. AC3) codec stream */
|
||||
int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec);
|
||||
|
||||
/** Normal way to open a PCM device */
|
||||
int sa_stream_create_pcm(sa_stream_t **s, const char *client_name, sa_mode_t mode, sa_pcm_format_t format, unsigned int rate, unsigned int nchannels);
|
||||
|
||||
/** Assign audio stream type.
|
||||
This function should be called after sa_stream_create_pcm(...) and before
|
||||
sa_stream_open(...) so the stream type can be assigned into lower layer */
|
||||
int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type);
|
||||
|
||||
/** Initialise the device */
|
||||
int sa_stream_open(sa_stream_t *s);
|
||||
|
||||
/** Close/destroy everything */
|
||||
int sa_stream_destroy(sa_stream_t *s);
|
||||
|
||||
/* "Soft" params */
|
||||
int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size);
|
||||
int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size);
|
||||
int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size);
|
||||
int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size);
|
||||
|
||||
/** Set the mapping between channels and the loudspeakers */
|
||||
int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n);
|
||||
|
||||
/** Whether xruns cause the card to reset */
|
||||
int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode);
|
||||
|
||||
/** Set the device to non-interleaved mode */
|
||||
int sa_stream_set_non_interleaved(sa_stream_t *s, int enable);
|
||||
|
||||
/** Require dynamic sample rate */
|
||||
int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable);
|
||||
|
||||
/** Select driver */
|
||||
int sa_stream_set_driver(sa_stream_t *s, const char *driver);
|
||||
|
||||
/** Start callback */
|
||||
int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback);
|
||||
|
||||
/** Start callback */
|
||||
int sa_stream_stop_thread(sa_stream_t *s);
|
||||
|
||||
/** Change the device connected to the stream */
|
||||
int sa_stream_change_device(sa_stream_t *s, const char *device_name);
|
||||
|
||||
/** volume in hundreths of dB*/
|
||||
int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n);
|
||||
|
||||
/** volume in hundreths of dB*/
|
||||
int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n);
|
||||
|
||||
/** Change the sampling rate */
|
||||
int sa_stream_change_rate(sa_stream_t *s, unsigned int rate);
|
||||
|
||||
/** Change some meta data that is attached to the stream */
|
||||
int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size);
|
||||
|
||||
/** Associate opaque user data */
|
||||
int sa_stream_change_user_data(sa_stream_t *s, const void *value);
|
||||
|
||||
/* Hardware-related. This is implementation-specific and hardware specific. */
|
||||
int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction);
|
||||
int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction);
|
||||
int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction);
|
||||
int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction);
|
||||
|
||||
/* Query functions */
|
||||
|
||||
int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode);
|
||||
int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size);
|
||||
int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format);
|
||||
int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate);
|
||||
int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels);
|
||||
int sa_stream_get_user_data(sa_stream_t *s, void **value);
|
||||
int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size);
|
||||
int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size);
|
||||
int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size);
|
||||
int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size);
|
||||
int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n);
|
||||
int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode);
|
||||
int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled);
|
||||
int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled);
|
||||
int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size);
|
||||
int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size);
|
||||
int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n);
|
||||
int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n);
|
||||
int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size);
|
||||
int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction);
|
||||
int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction);
|
||||
int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction);
|
||||
int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction);
|
||||
|
||||
/** Get current state of the audio device */
|
||||
int sa_stream_get_state(sa_stream_t *s, sa_state_t *state);
|
||||
|
||||
/** Obtain the error code */
|
||||
int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error);
|
||||
|
||||
/** Obtain the notification code */
|
||||
int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify);
|
||||
|
||||
/** sync/timing */
|
||||
int sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos);
|
||||
|
||||
|
||||
/* Blocking IO calls */
|
||||
|
||||
/** Interleaved capture function */
|
||||
int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes);
|
||||
/** Non-interleaved capture function */
|
||||
int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes);
|
||||
|
||||
/** Interleaved playback function */
|
||||
int sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes);
|
||||
/** Non-interleaved playback function */
|
||||
int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes);
|
||||
/** Interleaved playback function with seek offset */
|
||||
int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence);
|
||||
/** Non-interleaved playback function with seek offset */
|
||||
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);
|
||||
|
||||
|
||||
/** Query how much can be read without blocking */
|
||||
int sa_stream_get_read_size(sa_stream_t *s, size_t *size);
|
||||
/** Query how much can be written without blocking */
|
||||
int sa_stream_get_write_size(sa_stream_t *s, size_t *size);
|
||||
|
||||
|
||||
/* Control/xrun */
|
||||
|
||||
/** Resume playing after a pause */
|
||||
int sa_stream_resume(sa_stream_t *s);
|
||||
|
||||
/** Pause audio playback (do not empty the buffer) */
|
||||
int sa_stream_pause(sa_stream_t *s);
|
||||
|
||||
/** Block until all audio has been played */
|
||||
int sa_stream_drain(sa_stream_t *s);
|
||||
|
||||
/** Returns the minimum number of bytes which must be written before any
|
||||
audio is played by the hardware. */
|
||||
int sa_stream_get_min_write(sa_stream_t *s, size_t *size);
|
||||
|
||||
/** Return a human readable error */
|
||||
const char *sa_strerror(int code);
|
||||
|
||||
/* Extensions */
|
||||
int
|
||||
sa_stream_set_volume_abs(sa_stream_t *s, float vol);
|
||||
|
||||
int
|
||||
sa_stream_get_volume_abs(sa_stream_t *s, float *vol);
|
||||
|
||||
#endif
|
@ -1,9 +0,0 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += ['include', 'src']
|
||||
|
||||
MODULE = 'sydneyaudio'
|
||||
|
@ -1,80 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
LIBRARY_NAME = sydneyaudio
|
||||
FORCE_STATIC_LIB= 1
|
||||
ifeq (WINNT,$(OS_TARGET))
|
||||
VISIBILITY_FLAGS =
|
||||
endif
|
||||
|
||||
ifneq (,$(filter DragonFly FreeBSD GNU GNU_% NetBSD OpenBSD,$(OS_ARCH)))
|
||||
CSRCS = \
|
||||
sydney_audio_oss.c \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(MOZ_WIDGET_TOOLKIT),gonk)
|
||||
CPPSRCS = \
|
||||
sydney_audio_gonk.cpp \
|
||||
$(NULL)
|
||||
else ifeq ($(MOZ_WIDGET_TOOLKIT),android)
|
||||
CSRCS = \
|
||||
sydney_audio_android.c \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
CSRCS = \
|
||||
sydney_audio_waveapi.c \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),OS2)
|
||||
CSRCS = \
|
||||
sydney_audio_os2.c \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),Darwin)
|
||||
CSRCS = \
|
||||
sydney_audio_mac.c \
|
||||
$(NULL)
|
||||
|
||||
OS_LIBS += -framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),SunOS)
|
||||
CSRCS = \
|
||||
sydney_audio_sunaudio.c \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),AIX)
|
||||
CSRCS = \
|
||||
sydney_audio_aix.c \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifdef MOZ_ALSA
|
||||
CSRCS = \
|
||||
sydney_audio_alsa.c \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
OS_LIBS += winmm.lib
|
||||
endif
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
LOCAL_INCLUDES += -I$(srcdir)/../include
|
||||
|
||||
CFLAGS += $(MOZ_ALSA_CFLAGS)
|
@ -1,7 +0,0 @@
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
MODULE = 'sydneyaudio'
|
||||
|
@ -1,458 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stropts.h>
|
||||
#include <sys/audio.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include "sydney_audio.h"
|
||||
|
||||
#ifndef DEFAULT_AUDIO_DEVICE
|
||||
#define DEFAULT_AUDIO_DEVICE "/dev/paud0/1"
|
||||
#endif
|
||||
|
||||
#define LOOP_WHILE_EINTR(v,func) do { (v) = (func); } \
|
||||
while ((v) == -1 && errno == EINTR);
|
||||
|
||||
typedef struct sa_buf sa_buf;
|
||||
struct sa_buf {
|
||||
unsigned int size; /* the size of data */
|
||||
sa_buf *next;
|
||||
unsigned char data[]; /* sound data */
|
||||
};
|
||||
|
||||
struct sa_stream
|
||||
{
|
||||
int audio_fd;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_t thread_id;
|
||||
int playing;
|
||||
int64_t bytes_played;
|
||||
|
||||
/* audio format info */
|
||||
/* default setting */
|
||||
unsigned int default_n_channels;
|
||||
unsigned int default_rate;
|
||||
unsigned int default_precision;
|
||||
|
||||
/* used settings */
|
||||
unsigned int rate;
|
||||
unsigned int n_channels;
|
||||
unsigned int precision;
|
||||
|
||||
/* buffer list */
|
||||
sa_buf *bl_head;
|
||||
sa_buf *bl_tail;
|
||||
};
|
||||
|
||||
/* Use a default buffer size with enough room for one second of audio,
|
||||
* assuming stereo data at 44.1kHz with 32 bits per channel, and impose
|
||||
* a generous limit on the number of buffers.
|
||||
*/
|
||||
#define BUF_SIZE (2 * 44100 * 4)
|
||||
|
||||
static void* audio_callback(void* s);
|
||||
|
||||
static sa_buf *new_buffer(int size);
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Startup and shutdown functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_create_pcm(
|
||||
sa_stream_t ** _s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int n_channels
|
||||
)
|
||||
{
|
||||
sa_stream_t * s = 0;
|
||||
|
||||
/* Make sure we return a NULL stream pointer on failure. */
|
||||
if (_s == NULL)
|
||||
return SA_ERROR_INVALID;
|
||||
|
||||
*_s = NULL;
|
||||
|
||||
if (mode != SA_MODE_WRONLY)
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
if (format != SA_PCM_FORMAT_S16_LE)
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
/*
|
||||
* Allocate the instance and required resources.
|
||||
*/
|
||||
if ((s = malloc(sizeof(sa_stream_t))) == NULL)
|
||||
return SA_ERROR_OOM;
|
||||
|
||||
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
||||
free(s);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
s->audio_fd = NULL;
|
||||
s->rate = rate;
|
||||
s->n_channels = n_channels;
|
||||
s->precision = 16;
|
||||
|
||||
s->playing = 0;
|
||||
s->bytes_played = 0;
|
||||
s->bl_tail = s->bl_head = NULL;
|
||||
|
||||
*_s = s;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_open(sa_stream_t *s)
|
||||
{
|
||||
int fd,err;
|
||||
char *device_name;
|
||||
|
||||
audio_init init;
|
||||
audio_control control;
|
||||
audio_change change;
|
||||
|
||||
device_name = DEFAULT_AUDIO_DEVICE;
|
||||
|
||||
if (s == NULL)
|
||||
return SA_ERROR_NO_INIT;
|
||||
|
||||
if (s->audio_fd != NULL)
|
||||
return SA_ERROR_INVALID;
|
||||
|
||||
fd = open(device_name,O_WRONLY | O_NONBLOCK);
|
||||
if (fd >= 0)
|
||||
{
|
||||
close (fd);
|
||||
fd = open (device_name, O_WRONLY);
|
||||
}
|
||||
|
||||
if ( fd < 0 )
|
||||
{
|
||||
printf("Open %s failed:%s ",device_name,strerror(errno));
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
init.srate = s->rate;
|
||||
init.channels = s->n_channels;
|
||||
init.mode = AUDIO_PCM;
|
||||
init.flags = AUDIO_BIG_ENDIAN | AUDIO_TWOS_COMPLEMENT;
|
||||
init.operation = AUDIO_PLAY;
|
||||
|
||||
if (ioctl(s->audio_fd, AUDIO_INIT, &init) < 0) {
|
||||
close(s->audio_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
change.balance = 0x3fff0000;
|
||||
change.volume = 0x3fff0000;
|
||||
change.monitor = AUDIO_IGNORE;
|
||||
change.input = AUDIO_IGNORE;
|
||||
change.output = AUDIO_OUTPUT_1;
|
||||
|
||||
control.ioctl_request = AUDIO_CHANGE;
|
||||
control.position = 0;
|
||||
control.request_info = &change;
|
||||
|
||||
if (ioctl(s->audio_fd, AUDIO_CONTROL, &control) < 0) {
|
||||
close(s->audio_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
control.ioctl_request = AUDIO_START;
|
||||
control.request_info = NULL;
|
||||
|
||||
if (ioctl(s->audio_fd, AUDIO_CONTROL, &control) < 0) {
|
||||
close(s->audio_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_destroy(sa_stream_t *s)
|
||||
{
|
||||
int result;
|
||||
|
||||
if (s == NULL)
|
||||
return SA_SUCCESS;
|
||||
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
result = SA_SUCCESS;
|
||||
|
||||
/*
|
||||
* Shut down the audio output device.
|
||||
* and release resources
|
||||
*/
|
||||
if (s->audio_fd != NULL)
|
||||
{
|
||||
if (close(s->audio_fd) < 0)
|
||||
{
|
||||
perror("Close aix audio fd failed");
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
s->thread_id = 0;
|
||||
|
||||
while (s->bl_head != NULL) {
|
||||
sa_buf * next = s->bl_head->next;
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if (pthread_mutex_destroy(&s->mutex) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
free(s);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Data read and write functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes)
|
||||
{
|
||||
|
||||
int result;
|
||||
sa_buf *buf;
|
||||
|
||||
if (s == NULL || s->audio_fd == NULL)
|
||||
return SA_ERROR_NO_INIT;
|
||||
|
||||
if (nbytes == 0)
|
||||
return SA_SUCCESS;
|
||||
|
||||
|
||||
/*
|
||||
* Append the new data to the end of our buffer list.
|
||||
*/
|
||||
result = SA_SUCCESS;
|
||||
buf = new_buffer(nbytes);
|
||||
|
||||
if (buf == NULL)
|
||||
return SA_ERROR_OOM;
|
||||
|
||||
memcpy(buf->data,data, nbytes);
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (!s->bl_head)
|
||||
s->bl_head = buf;
|
||||
else
|
||||
s->bl_tail->next = buf;
|
||||
|
||||
s->bl_tail = buf;
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Once we have our first block of audio data, enable the audio callback
|
||||
* function. This doesn't need to be protected by the mutex, because
|
||||
* s->playing is not used in the audio callback thread, and it's probably
|
||||
* better not to be inside the lock when we enable the audio callback.
|
||||
*/
|
||||
if (!s->playing) {
|
||||
s->playing = 1;
|
||||
if (pthread_create(&s->thread_id, NULL, audio_callback, s) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void*
|
||||
audio_callback(void* data)
|
||||
{
|
||||
sa_stream_t* s = (sa_stream_t*)data;
|
||||
sa_buf *buf;
|
||||
int fd,nbytes_written,bytes,nbytes;
|
||||
|
||||
fd = s->audio_fd;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (s->thread_id == 0)
|
||||
break;
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
while (s->bl_head)
|
||||
{
|
||||
buf = s->bl_head;
|
||||
s->bl_head = s->bl_head->next;
|
||||
|
||||
nbytes_written = 0;
|
||||
nbytes = buf->size;
|
||||
|
||||
while (nbytes_written < nbytes)
|
||||
{
|
||||
LOOP_WHILE_EINTR(bytes,(write(fd, (void *)((buf->data)+nbytes_written), nbytes-nbytes_written)));
|
||||
|
||||
nbytes_written += bytes;
|
||||
if (nbytes_written != nbytes)
|
||||
printf("AixAudio\tWrite completed short - %d vs %d. Write more data\n",nbytes_written,nbytes);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
s->bytes_played += nbytes;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_write_size(sa_stream_t *s, size_t *size)
|
||||
{
|
||||
sa_buf * b;
|
||||
size_t used = 0;
|
||||
|
||||
if (s == NULL )
|
||||
return SA_ERROR_NO_INIT;
|
||||
|
||||
/* there is no interface to get the avaiable writing buffer size
|
||||
* in aix audio, we return max size here to force sa_stream_write() to
|
||||
* be called when there is data to be played
|
||||
*/
|
||||
*size = BUF_SIZE;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (position != SA_POSITION_WRITE_SOFTWARE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
*pos = s->bytes_played;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
static sa_buf *
|
||||
new_buffer(int size)
|
||||
{
|
||||
sa_buf * b = malloc(sizeof(sa_buf) + size);
|
||||
if (b != NULL) {
|
||||
b->size = size;
|
||||
b->next = NULL;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_pause(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_resume(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_drain(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
@ -1,549 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "sydney_audio.h"
|
||||
|
||||
#define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin"
|
||||
|
||||
/* ALSA implementation based heavily on sydney_audio_mac.c */
|
||||
|
||||
pthread_mutex_t sa_alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
struct sa_stream {
|
||||
snd_pcm_t* output_unit;
|
||||
int64_t bytes_written;
|
||||
int64_t last_position;
|
||||
|
||||
/* audio format info */
|
||||
unsigned int rate;
|
||||
unsigned int n_channels;
|
||||
|
||||
/* work around bug 573924 */
|
||||
int pulseaudio;
|
||||
int resumed;
|
||||
};
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Error Handler to prevent output to stderr
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
static void
|
||||
quiet_error_handler(const char* file,
|
||||
int line,
|
||||
const char* function,
|
||||
int err,
|
||||
const char* format,
|
||||
...)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Startup and shutdown functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_create_pcm(
|
||||
sa_stream_t ** _s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int n_channels
|
||||
) {
|
||||
sa_stream_t * s = 0;
|
||||
|
||||
/*
|
||||
* Make sure we return a NULL stream pointer on failure.
|
||||
*/
|
||||
if (_s == NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
*_s = NULL;
|
||||
|
||||
if (mode != SA_MODE_WRONLY) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (format != SA_PCM_FORMAT_S16_NE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the instance and required resources.
|
||||
*/
|
||||
if ((s = malloc(sizeof(sa_stream_t))) == NULL) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
s->output_unit = NULL;
|
||||
s->bytes_written = 0;
|
||||
s->last_position = 0;
|
||||
s->rate = rate;
|
||||
s->n_channels = n_channels;
|
||||
s->pulseaudio = 0;
|
||||
s->resumed = 0;
|
||||
|
||||
*_s = s;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_open(sa_stream_t *s) {
|
||||
snd_output_t* out;
|
||||
char* buf;
|
||||
size_t bufsz;
|
||||
snd_pcm_hw_params_t* hwparams;
|
||||
snd_pcm_sw_params_t* swparams;
|
||||
int dir;
|
||||
snd_pcm_uframes_t period;
|
||||
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (s->output_unit != NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&sa_alsa_mutex);
|
||||
|
||||
/* Turn off debug output to stderr */
|
||||
snd_lib_error_set_handler(quiet_error_handler);
|
||||
|
||||
if (snd_pcm_open(&s->output_unit,
|
||||
"default",
|
||||
SND_PCM_STREAM_PLAYBACK,
|
||||
0) < 0) {
|
||||
pthread_mutex_unlock(&sa_alsa_mutex);
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
if (snd_pcm_set_params(s->output_unit,
|
||||
#ifdef SA_LITTLE_ENDIAN
|
||||
SND_PCM_FORMAT_S16_LE,
|
||||
#else
|
||||
SND_PCM_FORMAT_S16_BE,
|
||||
#endif
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED,
|
||||
s->n_channels,
|
||||
s->rate,
|
||||
1,
|
||||
500000) < 0) {
|
||||
snd_pcm_close(s->output_unit);
|
||||
s->output_unit = NULL;
|
||||
pthread_mutex_unlock(&sa_alsa_mutex);
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* ugly alsa-pulse plugin detection */
|
||||
snd_output_buffer_open(&out);
|
||||
snd_pcm_dump(s->output_unit, out);
|
||||
bufsz = snd_output_buffer_string(out, &buf);
|
||||
s->pulseaudio = bufsz >= strlen(ALSA_PA_PLUGIN) &&
|
||||
strncmp(buf, ALSA_PA_PLUGIN, strlen(ALSA_PA_PLUGIN)) == 0;
|
||||
snd_output_close(out);
|
||||
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
snd_pcm_hw_params_current(s->output_unit, hwparams);
|
||||
snd_pcm_hw_params_get_period_size(hwparams, &period, &dir);
|
||||
|
||||
pthread_mutex_unlock(&sa_alsa_mutex);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_min_write(sa_stream_t *s, size_t *size) {
|
||||
int r;
|
||||
snd_pcm_uframes_t threshold;
|
||||
snd_pcm_sw_params_t* swparams;
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
snd_pcm_sw_params_current(s->output_unit, swparams);
|
||||
r = snd_pcm_sw_params_get_start_threshold(swparams, &threshold);
|
||||
if (r < 0) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
*size = snd_pcm_frames_to_bytes(s->output_unit, threshold);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_destroy(sa_stream_t *s) {
|
||||
int result = SA_SUCCESS;
|
||||
|
||||
if (s == NULL) {
|
||||
return result;
|
||||
}
|
||||
/*
|
||||
* Shut down the audio output device.
|
||||
*/
|
||||
if (s->output_unit != NULL) {
|
||||
pthread_mutex_lock(&sa_alsa_mutex);
|
||||
if (snd_pcm_close(s->output_unit) < 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
pthread_mutex_unlock(&sa_alsa_mutex);
|
||||
}
|
||||
free(s);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Data read and write functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
|
||||
snd_pcm_sframes_t frames, nframes, avail;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
nframes = snd_pcm_bytes_to_frames(s->output_unit, nbytes);
|
||||
while(nframes>0) {
|
||||
if (s->resumed) {
|
||||
avail = snd_pcm_avail_update(s->output_unit);
|
||||
frames = snd_pcm_writei(s->output_unit, data, nframes > avail ? avail : nframes);
|
||||
avail = snd_pcm_avail_update(s->output_unit);
|
||||
s->resumed = avail != 0;
|
||||
} else {
|
||||
avail = snd_pcm_avail_update(s->output_unit);
|
||||
avail = avail < 64 ? 64 : avail;
|
||||
frames = snd_pcm_writei(s->output_unit, data, nframes > avail ? avail : nframes);
|
||||
}
|
||||
if (frames < 0) {
|
||||
int r = snd_pcm_recover(s->output_unit, frames, 1);
|
||||
if (r < 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
} else {
|
||||
size_t bytes = snd_pcm_frames_to_bytes(s->output_unit, frames);
|
||||
nframes -= frames;
|
||||
data = ((unsigned char *)data) + bytes;
|
||||
s->bytes_written += bytes;
|
||||
}
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
|
||||
snd_pcm_sframes_t avail;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
do {
|
||||
avail = snd_pcm_avail_update(s->output_unit);
|
||||
if (avail < 0) {
|
||||
int r = snd_pcm_recover(s->output_unit, avail, 1);
|
||||
if (r < 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
} while (1);
|
||||
|
||||
*size = snd_pcm_frames_to_bytes(s->output_unit, avail);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (position != SA_POSITION_WRITE_SOFTWARE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (snd_pcm_state(s->output_unit) != SND_PCM_STATE_RUNNING) {
|
||||
*pos = s->last_position;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
if (snd_pcm_delay(s->output_unit, &delay) != 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
/* delay means audio is 'x' frames behind what we've written. We need to
|
||||
subtract the delay from the data written to return the actual bytes played.
|
||||
|
||||
due to buffering, the delay can be larger than the amount we've
|
||||
written--in this case, report position as zero. */
|
||||
*pos = s->bytes_written;
|
||||
if (*pos >= snd_pcm_frames_to_bytes(s->output_unit, delay)) {
|
||||
*pos -= snd_pcm_frames_to_bytes(s->output_unit, delay);
|
||||
} else {
|
||||
*pos = 0;
|
||||
}
|
||||
s->last_position = *pos;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_pause(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (snd_pcm_pause(s->output_unit, 1) != 0)
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_resume(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (s->pulseaudio) {
|
||||
s->resumed = 1;
|
||||
}
|
||||
|
||||
if (snd_pcm_pause(s->output_unit, 0) != 0)
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_drain(sa_stream_t *s)
|
||||
{
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (snd_pcm_state(s->output_unit) == SND_PCM_STATE_PREPARED) {
|
||||
size_t min_samples = 0;
|
||||
size_t min_bytes = 0;
|
||||
void *buf;
|
||||
|
||||
if (sa_stream_get_min_write(s, &min_samples) < 0)
|
||||
return SA_ERROR_SYSTEM;
|
||||
min_bytes = snd_pcm_frames_to_bytes(s->output_unit, min_samples);
|
||||
|
||||
buf = malloc(min_bytes);
|
||||
if (!buf)
|
||||
return SA_ERROR_SYSTEM;
|
||||
memset(buf, 0, min_bytes);
|
||||
sa_stream_write(s, buf, min_bytes);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
if (snd_pcm_state(s->output_unit) != SND_PCM_STATE_RUNNING) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
snd_pcm_drain(s->output_unit);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Extension functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
|
||||
snd_mixer_t* mixer = 0;
|
||||
snd_mixer_elem_t* elem = 0;
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (snd_mixer_open(&mixer, 0) < 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
if (snd_mixer_attach(mixer, "default") < 0) {
|
||||
snd_mixer_close(mixer);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
if (snd_mixer_selem_register(mixer, NULL, NULL) < 0) {
|
||||
snd_mixer_close(mixer);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
if (snd_mixer_load(mixer) < 0) {
|
||||
snd_mixer_close(mixer);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
#if 0
|
||||
snd_mixer_elem_t* elem = 0;
|
||||
for (elem = snd_mixer_first_elem(mixer); elem != NULL; elem = snd_mixer_elem_next(elem)) {
|
||||
if (snd_mixer_selem_has_playback_volume(elem)) {
|
||||
printf("Playback %s\n", snd_mixer_selem_get_name(elem));
|
||||
}
|
||||
else {
|
||||
printf("No Playback: %s\n", snd_mixer_selem_get_name(elem));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
elem = snd_mixer_first_elem(mixer);
|
||||
if (elem && snd_mixer_selem_has_playback_volume(elem)) {
|
||||
long min = 0;
|
||||
long max = 0;
|
||||
if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) >= 0) {
|
||||
snd_mixer_selem_set_playback_volume_all(elem, (max-min)*vol + min);
|
||||
}
|
||||
}
|
||||
snd_mixer_close(mixer);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_volume_abs(sa_stream_t *s, float *vol) {
|
||||
snd_mixer_t* mixer = 0;
|
||||
snd_mixer_elem_t* elem = 0;
|
||||
long value = 0;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (snd_mixer_open(&mixer, 0) < 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
if (snd_mixer_attach(mixer, "default") < 0) {
|
||||
snd_mixer_close(mixer);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
if (snd_mixer_selem_register(mixer, NULL, NULL) < 0) {
|
||||
snd_mixer_close(mixer);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
if (snd_mixer_load(mixer) < 0) {
|
||||
snd_mixer_close(mixer);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
elem = snd_mixer_first_elem(mixer);
|
||||
if (elem && snd_mixer_selem_get_playback_volume(elem, 0, &value) >= 0) {
|
||||
long min = 0;
|
||||
long max = 0;
|
||||
if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) >= 0) {
|
||||
*vol = (float)(value-min)/(float)(max-min);
|
||||
}
|
||||
}
|
||||
snd_mixer_close(mixer);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
@ -1,553 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <jni.h>
|
||||
#include "sydney_audio.h"
|
||||
|
||||
#include "android/log.h"
|
||||
|
||||
#ifndef ALOG
|
||||
#if defined(DEBUG) || defined(FORCE_ALOG)
|
||||
#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - SYDNEY_AUDIO" , ## args)
|
||||
#else
|
||||
#define ALOG(args...)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Android implementation based on sydney_audio_mac.c */
|
||||
|
||||
#define NANOSECONDS_PER_SECOND 1000000000
|
||||
#define NANOSECONDS_IN_MILLISECOND 1000000
|
||||
#define MILLISECONDS_PER_SECOND 1000
|
||||
|
||||
/* android.media.AudioTrack */
|
||||
struct AudioTrack {
|
||||
jclass class;
|
||||
jmethodID constructor;
|
||||
jmethodID flush;
|
||||
jmethodID getminbufsz;
|
||||
jmethodID pause;
|
||||
jmethodID play;
|
||||
jmethodID release;
|
||||
jmethodID setvol;
|
||||
jmethodID stop;
|
||||
jmethodID write;
|
||||
jmethodID getpos;
|
||||
};
|
||||
|
||||
enum AudioTrackMode {
|
||||
MODE_STATIC = 0,
|
||||
MODE_STREAM = 1
|
||||
};
|
||||
|
||||
/* android.media.AudioManager */
|
||||
enum AudioManagerStream {
|
||||
STREAM_VOICE_CALL = 0,
|
||||
STREAM_SYSTEM = 1,
|
||||
STREAM_RING = 2,
|
||||
STREAM_MUSIC = 3,
|
||||
STREAM_ALARM = 4,
|
||||
STREAM_NOTIFICATION = 5,
|
||||
STREAM_DTMF = 8
|
||||
};
|
||||
|
||||
/* android.media.AudioFormat */
|
||||
enum AudioFormatChannel {
|
||||
CHANNEL_OUT_MONO = 4,
|
||||
CHANNEL_OUT_STEREO = 12
|
||||
};
|
||||
|
||||
enum AudioFormatEncoding {
|
||||
ENCODING_PCM_16BIT = 2,
|
||||
ENCODING_PCM_8BIT = 3
|
||||
};
|
||||
|
||||
struct sa_stream {
|
||||
jobject output_unit;
|
||||
jbyteArray output_buf;
|
||||
unsigned int output_buf_size;
|
||||
|
||||
unsigned int rate;
|
||||
unsigned int channels;
|
||||
unsigned int isPaused;
|
||||
|
||||
int64_t amountWritten;
|
||||
unsigned int bufferSize;
|
||||
|
||||
jclass at_class;
|
||||
};
|
||||
|
||||
static struct AudioTrack at;
|
||||
extern JNIEnv * GetJNIForThread();
|
||||
|
||||
static jclass
|
||||
init_jni_bindings(JNIEnv *jenv) {
|
||||
jclass class = (*jenv)->FindClass(jenv, "android/media/AudioTrack");
|
||||
if (!class) {
|
||||
return NULL;
|
||||
}
|
||||
at.constructor = (*jenv)->GetMethodID(jenv, class, "<init>", "(IIIIII)V");
|
||||
at.flush = (*jenv)->GetMethodID(jenv, class, "flush", "()V");
|
||||
at.getminbufsz = (*jenv)->GetStaticMethodID(jenv, class, "getMinBufferSize", "(III)I");
|
||||
at.pause = (*jenv)->GetMethodID(jenv, class, "pause", "()V");
|
||||
at.play = (*jenv)->GetMethodID(jenv, class, "play", "()V");
|
||||
at.release = (*jenv)->GetMethodID(jenv, class, "release", "()V");
|
||||
at.setvol = (*jenv)->GetMethodID(jenv, class, "setStereoVolume", "(FF)I");
|
||||
at.stop = (*jenv)->GetMethodID(jenv, class, "stop", "()V");
|
||||
at.write = (*jenv)->GetMethodID(jenv, class, "write", "([BII)I");
|
||||
at.getpos = (*jenv)->GetMethodID(jenv, class, "getPlaybackHeadPosition", "()I");
|
||||
|
||||
return (*jenv)->NewGlobalRef(jenv, class);
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Startup and shutdown functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_create_pcm(
|
||||
sa_stream_t ** _s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int channels
|
||||
) {
|
||||
|
||||
/*
|
||||
* Make sure we return a NULL stream pointer on failure.
|
||||
*/
|
||||
if (_s == NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
*_s = NULL;
|
||||
|
||||
if (mode != SA_MODE_WRONLY) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (format != SA_PCM_FORMAT_S16_NE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (channels != 1 && channels != 2) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the instance and required resources.
|
||||
*/
|
||||
sa_stream_t *s;
|
||||
if ((s = malloc(sizeof(sa_stream_t))) == NULL) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
s->output_unit = NULL;
|
||||
s->output_buf = NULL;
|
||||
s->output_buf_size = 0;
|
||||
s->rate = rate;
|
||||
s->channels = channels;
|
||||
s->isPaused = 0;
|
||||
|
||||
s->amountWritten = 0;
|
||||
|
||||
s->bufferSize = 0;
|
||||
|
||||
*_s = s;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_open(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (s->output_unit != NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
if (!jenv) {
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
if ((*jenv)->PushLocalFrame(jenv, 4)) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
s->at_class = init_jni_bindings(jenv);
|
||||
if (!s->at_class) {
|
||||
(*jenv)->PopLocalFrame(jenv, NULL);
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
int32_t chanConfig = s->channels == 1 ?
|
||||
CHANNEL_OUT_MONO : CHANNEL_OUT_STEREO;
|
||||
|
||||
jint minsz = (*jenv)->CallStaticIntMethod(jenv, s->at_class, at.getminbufsz,
|
||||
s->rate, chanConfig, ENCODING_PCM_16BIT);
|
||||
if (minsz <= 0) {
|
||||
(*jenv)->PopLocalFrame(jenv, NULL);
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
s->bufferSize = s->rate * s->channels * sizeof(int16_t);
|
||||
if (s->bufferSize < minsz) {
|
||||
s->bufferSize = minsz;
|
||||
}
|
||||
|
||||
jobject obj =
|
||||
(*jenv)->NewObject(jenv, s->at_class, at.constructor,
|
||||
STREAM_MUSIC,
|
||||
s->rate,
|
||||
chanConfig,
|
||||
ENCODING_PCM_16BIT,
|
||||
s->bufferSize,
|
||||
MODE_STREAM);
|
||||
|
||||
jthrowable exception = (*jenv)->ExceptionOccurred(jenv);
|
||||
if (exception) {
|
||||
(*jenv)->ExceptionDescribe(jenv);
|
||||
(*jenv)->ExceptionClear(jenv);
|
||||
(*jenv)->PopLocalFrame(jenv, NULL);
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
if (!obj) {
|
||||
(*jenv)->PopLocalFrame(jenv, NULL);
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
s->output_unit = (*jenv)->NewGlobalRef(jenv, obj);
|
||||
|
||||
/* arbitrary buffer size. using a preallocated buffer avoids churning
|
||||
the GC every audio write. */
|
||||
s->output_buf_size = 4096 * s->channels * sizeof(int16_t);
|
||||
jbyteArray buf = (*jenv)->NewByteArray(jenv, s->output_buf_size);
|
||||
if (!buf) {
|
||||
(*jenv)->ExceptionClear(jenv);
|
||||
(*jenv)->DeleteGlobalRef(jenv, s->output_unit);
|
||||
(*jenv)->PopLocalFrame(jenv, NULL);
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
s->output_buf = (*jenv)->NewGlobalRef(jenv, buf);
|
||||
|
||||
(*jenv)->PopLocalFrame(jenv, NULL);
|
||||
|
||||
ALOG("%p - New stream %u %u bsz=%u min=%u obsz=%u", s, s->rate, s->channels,
|
||||
s->bufferSize, minsz, s->output_buf_size);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_destroy(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
if (!jenv) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
if (s->output_buf) {
|
||||
(*jenv)->DeleteGlobalRef(jenv, s->output_buf);
|
||||
}
|
||||
if (s->output_unit) {
|
||||
(*jenv)->CallVoidMethod(jenv, s->output_unit, at.stop);
|
||||
|
||||
jthrowable exception = (*jenv)->ExceptionOccurred(jenv);
|
||||
if (exception) {
|
||||
(*jenv)->ExceptionDescribe(jenv);
|
||||
(*jenv)->ExceptionClear(jenv);
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
(*jenv)->CallVoidMethod(jenv, s->output_unit, at.flush);
|
||||
(*jenv)->CallVoidMethod(jenv, s->output_unit, at.release);
|
||||
(*jenv)->DeleteGlobalRef(jenv, s->output_unit);
|
||||
}
|
||||
if (s->at_class) {
|
||||
(*jenv)->DeleteGlobalRef(jenv, s->at_class);
|
||||
}
|
||||
free(s);
|
||||
|
||||
ALOG("%p - Stream destroyed", s);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Data read and write functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
if ((*jenv)->PushLocalFrame(jenv, 2)) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
const jbyte *p = data;
|
||||
jint r = 0;
|
||||
size_t wrote = 0;
|
||||
do {
|
||||
size_t towrite = nbytes - wrote;
|
||||
if (towrite > s->output_buf_size) {
|
||||
towrite = s->output_buf_size;
|
||||
}
|
||||
(*jenv)->SetByteArrayRegion(jenv, s->output_buf, 0, towrite, p);
|
||||
|
||||
r = (*jenv)->CallIntMethod(jenv,
|
||||
s->output_unit,
|
||||
at.write,
|
||||
s->output_buf,
|
||||
0,
|
||||
towrite);
|
||||
if (r < 0) {
|
||||
ALOG("%p - Write failed %d", s, r);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
p += r;
|
||||
wrote += r;
|
||||
s->amountWritten += r;
|
||||
|
||||
sa_stream_resume(s);
|
||||
} while (wrote < nbytes);
|
||||
|
||||
(*jenv)->PopLocalFrame(jenv, NULL);
|
||||
|
||||
return r < 0 ? SA_ERROR_INVALID : SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
|
||||
/* No way to query the available buffer space directly, so calculate it from
|
||||
the amount we've written and the current playback position. */
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
int32_t framePosition = (*jenv)->CallIntMethod(jenv, s->output_unit, at.getpos);
|
||||
|
||||
int64_t bytePos = framePosition * s->channels * sizeof(int16_t);
|
||||
|
||||
*size = s->bufferSize - (s->amountWritten - bytePos);
|
||||
|
||||
/* Available buffer space can't exceed bufferSize. */
|
||||
if (*size > s->bufferSize) {
|
||||
*size = s->bufferSize;
|
||||
}
|
||||
|
||||
ALOG("%p - Write Size aw=%lld bufsz=%u pos=%lld sz=%zu",
|
||||
s, s->amountWritten, s->bufferSize, bytePos, *size);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
*pos = (*jenv)->CallIntMethod(jenv, s->output_unit, at.getpos);
|
||||
|
||||
/* android returns number of frames, so:
|
||||
position = frames * (PCM_16_BIT == 2 bytes) * channels
|
||||
*/
|
||||
*pos *= s->channels * sizeof(int16_t);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_pause(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
s->isPaused = 1;
|
||||
|
||||
/* Update stats */
|
||||
ALOG("%p - pause", s);
|
||||
|
||||
(*jenv)->CallVoidMethod(jenv, s->output_unit, at.pause);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_resume(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
ALOG("%p - resume", s);
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
s->isPaused = 0;
|
||||
|
||||
(*jenv)->CallVoidMethod(jenv, s->output_unit, at.play);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_drain(sa_stream_t *s)
|
||||
{
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
/* This is somewhat of a hack (see bug 693131). The AudioTrack documentation
|
||||
doesn't make it clear how much data must be written before a chunk of data is
|
||||
played, and experimentation with short streams required filling the entire
|
||||
allocated buffer. To guarantee that short streams (and the end of longer
|
||||
streams) are audible, write an entire bufferSize of silence before sleeping.
|
||||
This guarantees the short write logic in sa_stream_write is hit and the
|
||||
stream is playing before sleeping. Note that the sleep duration is
|
||||
calculated from the duration of audio written before writing silence. */
|
||||
size_t available;
|
||||
sa_stream_get_write_size(s, &available);
|
||||
|
||||
void *p = calloc(1, s->bufferSize);
|
||||
sa_stream_write(s, p, s->bufferSize);
|
||||
free(p);
|
||||
|
||||
/* There is no way with the Android SDK to determine exactly how
|
||||
long to playback. So estimate and sleep for that long. */
|
||||
unsigned long long x = (s->bufferSize - available) * 1000 / s->channels / s->rate /
|
||||
sizeof(int16_t) * NANOSECONDS_IN_MILLISECOND;
|
||||
ALOG("%p - Drain - flush %u, sleep for %llu ns", s, available, x);
|
||||
|
||||
struct timespec ts = {x / NANOSECONDS_PER_SECOND, x % NANOSECONDS_PER_SECOND};
|
||||
nanosleep(&ts, NULL);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Extension functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
(*jenv)->CallIntMethod(jenv, s->output_unit, at.setvol,
|
||||
(jfloat)vol, (jfloat)vol);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_volume_abs(sa_stream_t *s, float *vol))
|
||||
UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
@ -1,456 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
extern "C" {
|
||||
#include "sydney_audio.h"
|
||||
}
|
||||
|
||||
#include "gonk/AudioTrack.h"
|
||||
#include "android/log.h"
|
||||
|
||||
#if defined(DEBUG) || defined(FORCE_ALOG)
|
||||
#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - SYDNEY_AUDIO" , ## args)
|
||||
#else
|
||||
#define ALOG(args...)
|
||||
#endif
|
||||
|
||||
/* Gonk implementation based on sydney_audio_android.c */
|
||||
|
||||
#define NANOSECONDS_PER_SECOND 1000000000
|
||||
#define NANOSECONDS_IN_MILLISECOND 1000000
|
||||
#define MILLISECONDS_PER_SECOND 1000
|
||||
|
||||
using namespace android;
|
||||
|
||||
struct sa_stream {
|
||||
AudioTrack *output_unit;
|
||||
|
||||
unsigned int rate;
|
||||
unsigned int channels;
|
||||
unsigned int isPaused;
|
||||
|
||||
int64_t amountWritten;
|
||||
unsigned int bufferSize;
|
||||
|
||||
int streamType;
|
||||
};
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Startup and shutdown functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_create_pcm(
|
||||
sa_stream_t ** _s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int channels
|
||||
) {
|
||||
|
||||
/*
|
||||
* Make sure we return a NULL stream pointer on failure.
|
||||
*/
|
||||
if (_s == NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
*_s = NULL;
|
||||
|
||||
if (mode != SA_MODE_WRONLY) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (format != SA_PCM_FORMAT_S16_NE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (channels != 1 && channels != 2) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the instance and required resources.
|
||||
*/
|
||||
sa_stream_t *s;
|
||||
if ((s = (sa_stream_t *)malloc(sizeof(sa_stream_t))) == NULL) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
s->output_unit = NULL;
|
||||
s->rate = rate;
|
||||
s->channels = channels;
|
||||
s->isPaused = 0;
|
||||
|
||||
s->amountWritten = 0;
|
||||
|
||||
s->bufferSize = 0;
|
||||
|
||||
s->streamType = AudioSystem::SYSTEM;
|
||||
|
||||
*_s = s;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/* Assign audio stream type for argument used by AudioTrack class */
|
||||
int
|
||||
sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type)
|
||||
{
|
||||
if (s->output_unit != NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
switch (stream_type)
|
||||
{
|
||||
case SA_STREAM_TYPE_VOICE_CALL:
|
||||
s->streamType = AudioSystem::VOICE_CALL;
|
||||
break;
|
||||
case SA_STREAM_TYPE_SYSTEM:
|
||||
s->streamType = AudioSystem::SYSTEM;
|
||||
break;
|
||||
case SA_STREAM_TYPE_RING:
|
||||
s->streamType = AudioSystem::RING;
|
||||
break;
|
||||
case SA_STREAM_TYPE_MUSIC:
|
||||
s->streamType = AudioSystem::MUSIC;
|
||||
break;
|
||||
case SA_STREAM_TYPE_ALARM:
|
||||
s->streamType = AudioSystem::ALARM;
|
||||
break;
|
||||
case SA_STREAM_TYPE_NOTIFICATION:
|
||||
s->streamType = AudioSystem::NOTIFICATION;
|
||||
break;
|
||||
case SA_STREAM_TYPE_BLUETOOTH_SCO:
|
||||
s->streamType = AudioSystem::BLUETOOTH_SCO;
|
||||
break;
|
||||
case SA_STREAM_TYPE_ENFORCED_AUDIBLE:
|
||||
s->streamType = AudioSystem::ENFORCED_AUDIBLE;
|
||||
break;
|
||||
case SA_STREAM_TYPE_DTMF:
|
||||
s->streamType = AudioSystem::DTMF;
|
||||
break;
|
||||
case SA_STREAM_TYPE_FM:
|
||||
s->streamType = AudioSystem::FM;
|
||||
break;
|
||||
default:
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_open(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (s->output_unit != NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
int32_t chanConfig = s->channels == 1 ?
|
||||
AUDIO_CHANNEL_OUT_MONO : AUDIO_CHANNEL_OUT_STEREO;
|
||||
|
||||
int frameCount;
|
||||
if (AudioTrack::getMinFrameCount(&frameCount, s->streamType,
|
||||
s->rate) != NO_ERROR) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
s->bufferSize = frameCount * s->channels * sizeof(int16_t);
|
||||
|
||||
AudioTrack *track =
|
||||
new AudioTrack(s->streamType,
|
||||
s->rate,
|
||||
AudioSystem::PCM_16_BIT,
|
||||
chanConfig,
|
||||
s->bufferSize / s->channels / sizeof(int16_t),
|
||||
0,
|
||||
NULL, NULL,
|
||||
0,
|
||||
0);
|
||||
|
||||
if (track->initCheck() != NO_ERROR) {
|
||||
delete track;
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
s->output_unit = track;
|
||||
|
||||
ALOG("%p - New stream rate=%u chan=%u bsz=%u", s, s->rate, s->channels, s->bufferSize);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_destroy(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
static bool firstLeaked = 0;
|
||||
if (s->output_unit) {
|
||||
s->output_unit->stop();
|
||||
s->output_unit->flush();
|
||||
// XXX: Figure out why we crash if we don't leak the first AudioTrack
|
||||
if (firstLeaked)
|
||||
delete s->output_unit;
|
||||
else
|
||||
firstLeaked = true;
|
||||
}
|
||||
free(s);
|
||||
|
||||
ALOG("%p - Stream destroyed", s);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Data read and write functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
const char *p = (char *)data;
|
||||
ssize_t r = 0;
|
||||
size_t wrote = 0;
|
||||
do {
|
||||
size_t towrite = nbytes - wrote;
|
||||
|
||||
r = s->output_unit->write(p, towrite);
|
||||
if (r < 0) {
|
||||
ALOG("%p - Write failed %d", s, r);
|
||||
break;
|
||||
}
|
||||
|
||||
p += r;
|
||||
wrote += r;
|
||||
s->amountWritten += r;
|
||||
|
||||
sa_stream_resume(s);
|
||||
} while (wrote < nbytes);
|
||||
|
||||
return r < 0 ? SA_ERROR_INVALID : SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
/* No way to query the available buffer space directly, so calculate it from
|
||||
the amount we've written and the current playback position. */
|
||||
uint32_t framePosition = 0;
|
||||
s->output_unit->getPosition(&framePosition);
|
||||
|
||||
int64_t bytePos = framePosition * s->channels * sizeof(int16_t);
|
||||
|
||||
*size = s->bufferSize - (s->amountWritten - bytePos);
|
||||
|
||||
/* Available buffer space can't exceed bufferSize. */
|
||||
if (*size > s->bufferSize) {
|
||||
*size = s->bufferSize;
|
||||
}
|
||||
ALOG("%p - Write Size aw=%lld bufsz=%u pos=%lld sz=%u",
|
||||
s, s->amountWritten, s->bufferSize, bytePos, *size);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
uint32_t framePosition;
|
||||
if (s->output_unit->getPosition(&framePosition) != NO_ERROR)
|
||||
return SA_ERROR_INVALID;
|
||||
|
||||
/* android returns number of frames, so:
|
||||
position = frames * (PCM_16_BIT == 2 bytes) * channels
|
||||
*/
|
||||
*pos = framePosition * s->channels * sizeof(int16_t);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_pause(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
s->isPaused = 1;
|
||||
|
||||
ALOG("%p - pause", s);
|
||||
|
||||
s->output_unit->pause();
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_resume(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
ALOG("%p - resume", s);
|
||||
|
||||
s->isPaused = 0;
|
||||
|
||||
s->output_unit->start();
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_drain(sa_stream_t *s)
|
||||
{
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
/* This is somewhat of a hack (see bug 693131). The AudioTrack documentation
|
||||
doesn't make it clear how much data must be written before a chunk of data is
|
||||
played, and experimentation with short streams required filling the entire
|
||||
allocated buffer. To guarantee that short streams (and the end of longer
|
||||
streams) are audible, write an entire bufferSize of silence before sleeping.
|
||||
This guarantees the short write logic in sa_stream_write is hit and the
|
||||
stream is playing before sleeping. Note that the sleep duration is
|
||||
calculated from the duration of audio written before writing silence. */
|
||||
size_t available;
|
||||
sa_stream_get_write_size(s, &available);
|
||||
|
||||
void *p = calloc(1, s->bufferSize);
|
||||
sa_stream_write(s, p, s->bufferSize);
|
||||
free(p);
|
||||
|
||||
/* There is no way with the Android SDK to determine exactly how
|
||||
long to playback. So estimate and sleep for that long. */
|
||||
unsigned long long x = (s->bufferSize - available) * 1000 / s->channels / s->rate /
|
||||
sizeof(int16_t) * NANOSECONDS_IN_MILLISECOND;
|
||||
ALOG("%p - Drain - flush %u, sleep for %llu ns", s, available, x);
|
||||
|
||||
struct timespec ts = {(time_t)(x / NANOSECONDS_PER_SECOND),
|
||||
(time_t)(x % NANOSECONDS_PER_SECOND)};
|
||||
nanosleep(&ts, NULL);
|
||||
s->output_unit->flush();
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Extension functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
s->output_unit->setVolume(vol, vol);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_volume_abs(sa_stream_t *s, float *vol))
|
||||
UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
@ -1,736 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include "sydney_audio.h"
|
||||
|
||||
/*
|
||||
* The Mac's audio interface is based on a "pull" I/O model, which means you
|
||||
* can't just provide a data buffer and tell the audio device to play; you must
|
||||
* register a callback and provide data as the device asks for it. To support
|
||||
* sydney audio's "write-to-play" style interface, we have to buffer up the
|
||||
* data as it arrives and feed it to the callback as required.
|
||||
*
|
||||
* This is handled by a simple linked list of buffers; data is always written
|
||||
* to the tail and read from the head. Each buffer tracks the start and end
|
||||
* positions of its contained data. Buffers are allocated when the tail buffer
|
||||
* fills, and freed when the head buffer empties. There is always at least one
|
||||
* buffer allocated.
|
||||
*
|
||||
* s e s e s e + data read
|
||||
* +++##### -> ######## -> ####---- # data written
|
||||
* ^ ^ - empty
|
||||
* bl_head bl_tail
|
||||
*/
|
||||
|
||||
typedef struct sa_buf sa_buf;
|
||||
struct sa_buf {
|
||||
unsigned int size;
|
||||
unsigned int start;
|
||||
unsigned int end;
|
||||
sa_buf * next;
|
||||
unsigned char data[0];
|
||||
};
|
||||
|
||||
struct sa_stream {
|
||||
AudioUnit output_unit;
|
||||
pthread_mutex_t mutex;
|
||||
bool playing;
|
||||
int64_t bytes_played;
|
||||
int64_t bytes_played_last;
|
||||
|
||||
/* audio format info */
|
||||
unsigned int rate;
|
||||
unsigned int n_channels;
|
||||
unsigned int bytes_per_ch;
|
||||
|
||||
/* buffer list */
|
||||
sa_buf * bl_head;
|
||||
sa_buf * bl_tail;
|
||||
int n_bufs;
|
||||
|
||||
size_t buffer_size;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Allow up to a second of audio to be buffered.
|
||||
*/
|
||||
#define BUF_SIZE_MS 200
|
||||
#define BUF_LIMIT 5
|
||||
|
||||
#if BUF_LIMIT < 2
|
||||
#error BUF_LIMIT must be at least 2!
|
||||
#endif
|
||||
|
||||
|
||||
static OSStatus audio_callback(void *arg, AudioUnitRenderActionFlags *action_flags,
|
||||
const AudioTimeStamp *time_stamp, UInt32 bus_num, UInt32 n_frames, AudioBufferList *data);
|
||||
|
||||
static sa_buf *new_buffer(size_t bufsz);
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Startup and shutdown functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_create_pcm(
|
||||
sa_stream_t ** _s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int n_channels
|
||||
) {
|
||||
|
||||
sa_stream_t * s;
|
||||
|
||||
/*
|
||||
* Make sure we return a NULL stream pointer on failure.
|
||||
*/
|
||||
if (_s == NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
*_s = NULL;
|
||||
|
||||
if (mode != SA_MODE_WRONLY) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (format != SA_PCM_FORMAT_S16_NE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the instance and required resources.
|
||||
*/
|
||||
if ((s = malloc(sizeof(sa_stream_t))) == NULL) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
s->output_unit = NULL;
|
||||
s->playing = FALSE;
|
||||
s->bytes_played = 0;
|
||||
s->bytes_played_last = 0;
|
||||
s->rate = rate;
|
||||
s->n_channels = n_channels;
|
||||
s->bytes_per_ch = 2;
|
||||
s->buffer_size = s->bytes_per_ch *
|
||||
((rate * n_channels * BUF_SIZE_MS) / 1000);
|
||||
|
||||
/* round buffer_size up to ensure it's divisible by the number of bytes per frame. */
|
||||
if (s->buffer_size % (s->bytes_per_ch * n_channels) != 0) {
|
||||
s->buffer_size += (s->bytes_per_ch * n_channels) - (s->buffer_size % (s->bytes_per_ch * n_channels));
|
||||
}
|
||||
|
||||
if ((s->bl_head = new_buffer(s->buffer_size)) == NULL) {
|
||||
free(s);
|
||||
return SA_ERROR_OOM;
|
||||
|
||||
}
|
||||
|
||||
s->bl_tail = s->bl_head;
|
||||
s->n_bufs = 1;
|
||||
|
||||
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
||||
free(s->bl_head);
|
||||
free(s);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
*_s = s;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_open(sa_stream_t *s) {
|
||||
|
||||
ComponentDescription desc;
|
||||
Component comp;
|
||||
AURenderCallbackStruct input;
|
||||
AudioStreamBasicDescription fmt;
|
||||
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (s->output_unit != NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the default audio output unit.
|
||||
*/
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
comp = FindNextComponent(NULL, &desc);
|
||||
if (comp == NULL) {
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
if (OpenAComponent(comp, &s->output_unit) != noErr) {
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up the render callback used to feed audio data into the output unit.
|
||||
*/
|
||||
input.inputProc = audio_callback;
|
||||
input.inputProcRefCon = s;
|
||||
if (AudioUnitSetProperty(s->output_unit, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &input, sizeof(input)) != 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up the format description for our audio data. Apple uses the
|
||||
* following terminology:
|
||||
*
|
||||
* sample = a single data value for one channel
|
||||
* frame = a set of samples that includes one sample for each channel
|
||||
* packet = the smallest indivisible block of audio data; for uncompressed
|
||||
* audio (which is what we have), this is one frame
|
||||
* rate = the number of complete frames per second
|
||||
*
|
||||
* Note that this definition of frame differs from, well, pretty much everyone
|
||||
* else's. See this really long link for more info:
|
||||
*
|
||||
* http://developer.apple.com/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/Reference/reference.html#//apple_ref/c/tdef/AudioStreamBasicDescription
|
||||
*/
|
||||
fmt.mFormatID = kAudioFormatLinearPCM;
|
||||
fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
|
||||
#ifdef __BIG_ENDIAN__
|
||||
kLinearPCMFormatFlagIsBigEndian |
|
||||
#endif
|
||||
kLinearPCMFormatFlagIsPacked;
|
||||
fmt.mSampleRate = s->rate;
|
||||
fmt.mChannelsPerFrame = s->n_channels;
|
||||
fmt.mBitsPerChannel = s->bytes_per_ch * 8;
|
||||
fmt.mFramesPerPacket = 1; /* uncompressed audio */
|
||||
fmt.mBytesPerFrame = fmt.mChannelsPerFrame * fmt.mBitsPerChannel / 8;
|
||||
fmt.mBytesPerPacket = fmt.mBytesPerFrame * fmt.mFramesPerPacket;
|
||||
|
||||
/*
|
||||
* We're feeding data in to the output bus of the audio system, so we set
|
||||
* the format description on the input scope of the device, using the very
|
||||
* obvious element value of 0 to indicate the output bus.
|
||||
*
|
||||
* http://developer.apple.com/technotes/tn2002/tn2091.html
|
||||
*/
|
||||
if (AudioUnitSetProperty(s->output_unit, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &fmt, sizeof(AudioStreamBasicDescription)) != 0) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (AudioUnitInitialize(s->output_unit) != 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_destroy(sa_stream_t *s) {
|
||||
|
||||
int result = SA_SUCCESS;
|
||||
|
||||
if (s == NULL) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shut down the audio output device. Don't hold the mutex when stopping
|
||||
* the audio device, because it is possible to deadlock with this thread
|
||||
* holding mutex then waiting on an internal Core Audio lock, and with the
|
||||
* callback thread holding the Core Audio lock and waiting on the mutex.
|
||||
* This does not need to be protected by the mutex anyway because
|
||||
* AudioOutputUnitStop, when called from the non-callback thread, blocks
|
||||
* until in-flight callbacks complete and the HAL shuts down. See:
|
||||
* http://lists.apple.com/archives/coreaudio-api/2005/Dec/msg00055.html
|
||||
*/
|
||||
if (s->output_unit != NULL) {
|
||||
if (s->playing && AudioOutputUnitStop(s->output_unit) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
if (AudioUnitUninitialize(s->output_unit) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
if (CloseComponent(s->output_unit) != noErr) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Release resources.
|
||||
*/
|
||||
if (pthread_mutex_destroy(&s->mutex) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
while (s->bl_head != NULL) {
|
||||
sa_buf * next = s->bl_head->next;
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
}
|
||||
free(s);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Data read and write functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
|
||||
|
||||
int result = SA_SUCCESS;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Append the new data to the end of our buffer list.
|
||||
*/
|
||||
while (1) {
|
||||
unsigned int avail = s->bl_tail->size - s->bl_tail->end;
|
||||
|
||||
if (nbytes <= avail) {
|
||||
|
||||
/*
|
||||
* The new data will fit into the current tail buffer, so
|
||||
* just copy it in and we're done.
|
||||
*/
|
||||
memcpy(s->bl_tail->data + s->bl_tail->end, data, nbytes);
|
||||
s->bl_tail->end += nbytes;
|
||||
break;
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Copy what we can into the tail and allocate a new buffer
|
||||
* for the rest.
|
||||
*/
|
||||
memcpy(s->bl_tail->data + s->bl_tail->end, data, avail);
|
||||
s->bl_tail->end += avail;
|
||||
data = ((unsigned char *)data) + avail;
|
||||
nbytes -= avail;
|
||||
|
||||
/*
|
||||
* If we still have data left to copy but we've hit the limit of
|
||||
* allowable buffer allocations, we need to spin for a bit to allow
|
||||
* the audio callback function to slurp some more data up.
|
||||
*/
|
||||
if (nbytes > 0 && s->n_bufs == BUF_LIMIT) {
|
||||
#ifdef TIMING_TRACE
|
||||
printf("#"); /* too much audio data */
|
||||
#endif
|
||||
if (!s->playing) {
|
||||
/*
|
||||
* We haven't even started playing yet! That means the
|
||||
* BUF_SIZE_MS/BUF_LIMIT values are too low... Not much we can
|
||||
* do here; spinning won't help because the audio callback
|
||||
* hasn't been enabled yet. Oh well, error time.
|
||||
*/
|
||||
printf("Too much audio data received before audio device enabled!\n");
|
||||
result = SA_ERROR_SYSTEM;
|
||||
break;
|
||||
}
|
||||
while (s->n_bufs == BUF_LIMIT) {
|
||||
struct timespec ts = {0, 1000000};
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
nanosleep(&ts, NULL);
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new tail buffer, and go 'round again to fill it up.
|
||||
*/
|
||||
if ((s->bl_tail->next = new_buffer(s->buffer_size)) == NULL) {
|
||||
result = SA_ERROR_OOM;
|
||||
break;
|
||||
}
|
||||
s->n_bufs++;
|
||||
s->bl_tail = s->bl_tail->next;
|
||||
|
||||
} /* if (nbytes <= avail), else */
|
||||
|
||||
} /* while (1) */
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Once we have our first block of audio data, enable the audio callback
|
||||
* function. This doesn't need to be protected by the mutex, because
|
||||
* s->playing is not used in the audio callback thread, and it's probably
|
||||
* better not to be inside the lock when we enable the audio callback.
|
||||
*/
|
||||
if (!s->playing) {
|
||||
if (AudioOutputUnitStart(s->output_unit) != 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
s->playing = TRUE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static OSStatus
|
||||
audio_callback(
|
||||
void * arg,
|
||||
AudioUnitRenderActionFlags * action_flags,
|
||||
const AudioTimeStamp * time_stamp,
|
||||
UInt32 bus_num,
|
||||
UInt32 n_frames,
|
||||
AudioBufferList * data
|
||||
) {
|
||||
|
||||
sa_stream_t * s = arg;
|
||||
unsigned char * dst;
|
||||
unsigned int bytes_per_frame;
|
||||
unsigned int bytes_to_copy;
|
||||
|
||||
#ifdef TIMING_TRACE
|
||||
printf("."); /* audio read 'tick' */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We're dealing with interleaved data, so the system should only
|
||||
* have provided one buffer to be filled.
|
||||
*/
|
||||
assert(data->mNumberBuffers == 1);
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
dst = data->mBuffers[0].mData;
|
||||
bytes_per_frame = s->n_channels * s->bytes_per_ch;
|
||||
bytes_to_copy = n_frames * bytes_per_frame;
|
||||
|
||||
s->bytes_played += s->bytes_played_last;
|
||||
s->bytes_played_last = 0;
|
||||
|
||||
/*
|
||||
* Consume data from the start of the buffer list.
|
||||
*/
|
||||
while (1) {
|
||||
unsigned int avail = s->bl_head->end - s->bl_head->start;
|
||||
|
||||
assert(s->bl_head->start <= s->bl_head->end);
|
||||
|
||||
if (avail >= bytes_to_copy) {
|
||||
|
||||
/*
|
||||
* We have all we need in the head buffer, so just grab it and go.
|
||||
*/
|
||||
memcpy(dst, s->bl_head->data + s->bl_head->start, bytes_to_copy);
|
||||
s->bl_head->start += bytes_to_copy;
|
||||
s->bytes_played_last += bytes_to_copy;
|
||||
break;
|
||||
|
||||
} else {
|
||||
|
||||
sa_buf * next;
|
||||
|
||||
/*
|
||||
* Copy what we can from the head and move on to the next buffer.
|
||||
*/
|
||||
memcpy(dst, s->bl_head->data + s->bl_head->start, avail);
|
||||
s->bl_head->start += avail;
|
||||
dst += avail;
|
||||
bytes_to_copy -= avail;
|
||||
s->bytes_played_last += avail;
|
||||
|
||||
/*
|
||||
* We want to free the now-empty buffer, but not if it's also the
|
||||
* current tail. If it is the tail, we don't have enough data to fill
|
||||
* the destination buffer, so we'll just zero it out and give up.
|
||||
*/
|
||||
next = s->bl_head->next;
|
||||
if (next == NULL) {
|
||||
#ifdef TIMING_TRACE
|
||||
printf("!"); /* not enough audio data */
|
||||
#endif
|
||||
memset(dst, 0, bytes_to_copy);
|
||||
break;
|
||||
}
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
s->n_bufs--;
|
||||
|
||||
} /* if (avail >= bytes_to_copy), else */
|
||||
|
||||
} /* while (1) */
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
|
||||
|
||||
unsigned int avail;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* The sum of the free space in the tail buffer plus the size of any new
|
||||
* buffers represents the write space available before blocking.
|
||||
*/
|
||||
avail = s->bl_tail->size - s->bl_tail->end;
|
||||
avail += (BUF_LIMIT - s->n_bufs) * s->buffer_size;
|
||||
*size = avail;
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (position != SA_POSITION_WRITE_SOFTWARE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
*pos = s->bytes_played;
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_pause(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't hold the mutex when stopping the audio device, because it is
|
||||
* possible to deadlock with this thread holding mutex then waiting on an
|
||||
* internal Core Audio lock, and with the callback thread holding the Core
|
||||
* Audio lock and waiting on the mutex.
|
||||
*/
|
||||
if (AudioOutputUnitStop(s->output_unit) != 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
s->playing = FALSE;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_resume(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't hold the mutex when starting the audio device, because it is
|
||||
* possible to deadlock with this thread holding mutex then waiting on an
|
||||
* internal Core Audio lock, and with the callback thread holding the Core
|
||||
* Audio lock and waiting on the mutex.
|
||||
*/
|
||||
if (AudioOutputUnitStart(s->output_unit) != 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
s->playing = TRUE;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static sa_buf *
|
||||
new_buffer(size_t bufsz) {
|
||||
sa_buf * b = malloc(sizeof(sa_buf) + bufsz);
|
||||
if (b != NULL) {
|
||||
b->size = bufsz;
|
||||
b->start = 0;
|
||||
b->end = 0;
|
||||
b->next = NULL;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_drain(sa_stream_t *s)
|
||||
{
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (!s->playing) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
sa_buf * b;
|
||||
size_t used = 0;
|
||||
struct timespec ts = {0, 1000000};
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
for (b = s->bl_head; b != NULL; b = b->next) {
|
||||
used += b->end - b->start;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if (used == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Extension functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
AudioUnitSetParameter(s->output_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitParameterFlag_Output, 0, vol, 0);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_volume_abs(sa_stream_t *s, float *vol) {
|
||||
|
||||
Float32 local_vol = 0;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
AudioUnitGetParameter(s->output_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitParameterFlag_Output, 0, &local_vol);
|
||||
*vol = local_vol;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
@ -1,905 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/*****************************************************************************/
|
||||
/* OVERVIEW
|
||||
*
|
||||
* Unlike other DART implementations which pull data into the backend
|
||||
* as needed, this one relies on the upstream code to provide sufficient
|
||||
* data in a well regulated stream. If other activities in the system
|
||||
* interrupt that stream, the sound device may run out of data. While
|
||||
* it should simply pause until more data is available, on some machines
|
||||
* a buffer underrun causes the device to stop responding and to ignore
|
||||
* new data until an MCI_STOP or MCI_PAUSE command is issued.
|
||||
*
|
||||
* The solution used here is to track the number of buffers in use and
|
||||
* to pause the device when the count falls below a threshold. Writing
|
||||
* a new buffer to the device causes playback to resume automatically.
|
||||
* To support this scheme, the code uses atomic operations on 2 counters
|
||||
* to pass buffer counts between its two threads (the app's decode thread
|
||||
* and DART's event thread). It also has the event thread do as little
|
||||
* as possible to ensure it's not busy when a buffer-free event occurs.
|
||||
*
|
||||
*/
|
||||
/*****************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include "sydney_audio.h"
|
||||
|
||||
#define INCL_DOS
|
||||
#define INCL_MCIOS2
|
||||
#include <os2.h>
|
||||
#include <os2me.h>
|
||||
#include <386/builtin.h>
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* this will have to be changed to a variable
|
||||
* if other than 16-bit samples are ever supported */
|
||||
#define SAOS2_SAMPLE_SIZE 2
|
||||
|
||||
/* the number of buffers to allocate; each buffer requires
|
||||
* 64kb of linear address space in the low-mem private arena;
|
||||
* actual physical memory used depends on each buffer's size */
|
||||
#define SAOS2_BUF_CNT 40
|
||||
|
||||
/* the minimum number of milliseconds worth of data required before
|
||||
* a buffer is written to the device - the actual number of ms per
|
||||
* write will usually be greater; the size of each buffer is based
|
||||
* on this figure and the stream's rate & number of channels */
|
||||
#define SAOS2_MS_PER_WRITE 40
|
||||
|
||||
/* if the number of buffers in use is less than this value,
|
||||
* os2_mixer_event() will pause the device to prevent an underrun */
|
||||
#define SAOS2_UNDERRUN_CNT 2
|
||||
|
||||
/* wait 5 seconds for a buffer to become free -
|
||||
* an indefinite wait invites a hung thread */
|
||||
#define SAOS2_WAIT 5000
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Debug */
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifndef SAOS2_ERROR
|
||||
#define SAOS2_ERROR
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef SAOS2_ERROR
|
||||
static int os2_error_msg(int rtn, char * func, char * msg, uint32_t err);
|
||||
#define os2_error(rtn, func, msg, err) os2_error_msg(rtn, func, msg, err)
|
||||
#else
|
||||
#define os2_error(rtn, func, msg, err) rtn
|
||||
#endif
|
||||
|
||||
/*****************************************************************************/
|
||||
/* OS/2 implementation of sa_stream_t */
|
||||
|
||||
struct sa_stream {
|
||||
|
||||
/* audio format info */
|
||||
const char * client_name;
|
||||
sa_mode_t mode;
|
||||
sa_pcm_format_t format;
|
||||
uint32_t rate;
|
||||
uint32_t nchannels;
|
||||
uint32_t bps;
|
||||
|
||||
/* device info */
|
||||
uint16_t hwDeviceID;
|
||||
uint32_t hwMixHandle;
|
||||
PMIXERPROC hwWriteProc;
|
||||
|
||||
/* buffer allocations */
|
||||
int32_t bufCnt;
|
||||
size_t bufSize;
|
||||
size_t bufMin;
|
||||
PMCI_MIX_BUFFER bufList;
|
||||
|
||||
/* buffer usage tracking */
|
||||
volatile uint32_t freeNew;
|
||||
int32_t freeCnt;
|
||||
int32_t freeNdx;
|
||||
volatile uint32_t usedNew;
|
||||
int32_t usedCnt;
|
||||
int32_t usedMin;
|
||||
|
||||
/* miscellaneous */
|
||||
volatile uint32_t playing;
|
||||
volatile uint32_t writeTime;
|
||||
volatile uint32_t writeNew;
|
||||
int64_t writePos;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Private (static) Functions */
|
||||
|
||||
static int32_t os2_mixer_event(uint32_t ulStatus, PMCI_MIX_BUFFER pBuffer,
|
||||
uint32_t ulFlags);
|
||||
static void os2_stop_device(uint16_t hwDeviceID);
|
||||
static int os2_pause_device(uint16_t hwDeviceID, uint32_t release);
|
||||
static int os2_get_free_count(sa_stream_t *s, int32_t count);
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Mozilla-specific Additions */
|
||||
|
||||
/* load mdm.dll on demand */
|
||||
static int os2_load_mdm(void);
|
||||
|
||||
/* invoke mciSendCommand() via a static variable */
|
||||
typedef ULONG _System MCISENDCOMMAND(USHORT, USHORT, ULONG, PVOID, USHORT);
|
||||
static MCISENDCOMMAND * _mciSendCommand = 0;
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Sydney Audio Functions */
|
||||
/*****************************************************************************/
|
||||
|
||||
/** Normal way to open a PCM device */
|
||||
|
||||
int sa_stream_create_pcm(sa_stream_t ** s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int nchannels)
|
||||
{
|
||||
uint32_t status = SA_SUCCESS;
|
||||
uint32_t size;
|
||||
uint32_t rc;
|
||||
sa_stream_t * sTemp = 0;
|
||||
|
||||
/* this do{}while(0) "loop" makes it easy to ensure
|
||||
* resources are freed on exit if there's an error */
|
||||
do {
|
||||
/* load mdm.dll if it isn't already loaded */
|
||||
if (os2_load_mdm() != SA_SUCCESS)
|
||||
return SA_ERROR_SYSTEM;
|
||||
|
||||
if (mode != SA_MODE_WRONLY || format != SA_PCM_FORMAT_S16_LE)
|
||||
return os2_error(SA_ERROR_NOT_SUPPORTED, "sa_stream_create_pcm",
|
||||
"invalid mode or format", 0);
|
||||
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_INVALID, "sa_stream_create_pcm",
|
||||
"s is null", 0);
|
||||
*s = 0;
|
||||
|
||||
/* the MCI_MIX_BUFFERs must be in low memory or terrible things will
|
||||
* happen! - since there's extra space, put 'sa_stream' there too */
|
||||
size = sizeof(sa_stream_t) + sizeof(PMCI_MIX_BUFFER) * SAOS2_BUF_CNT;
|
||||
rc = DosAllocMem((void**)&sTemp, size,
|
||||
PAG_COMMIT | PAG_READ | PAG_WRITE);
|
||||
if (rc) {
|
||||
status = os2_error(SA_ERROR_OOM, "sa_stream_create_pcm",
|
||||
"DosAllocMem - rc=", rc);
|
||||
break;
|
||||
}
|
||||
|
||||
memset(sTemp, 0, size);
|
||||
sTemp->bufList = (PMCI_MIX_BUFFER)&sTemp[1];
|
||||
|
||||
/* fill in the miscellanea */
|
||||
sTemp->client_name = client_name;
|
||||
sTemp->mode = mode;
|
||||
sTemp->format = format;
|
||||
sTemp->rate = rate;
|
||||
sTemp->nchannels = nchannels;
|
||||
sTemp->bps = rate * nchannels * SAOS2_SAMPLE_SIZE;
|
||||
|
||||
/* each buffer requires 64k of linear address space;
|
||||
* the actual physical memory used is much less */
|
||||
sTemp->bufCnt = SAOS2_BUF_CNT;
|
||||
|
||||
/* a buffer must contain at least 'bufmin' bytes before it's written
|
||||
* to the device - this equates to SAOS2_MS_PER_WRITE worth of data */
|
||||
sTemp->bufMin = (sTemp->bps * SAOS2_MS_PER_WRITE) / 1000;
|
||||
|
||||
/* 'bufSize' is 150% of 'bufmin' rounded up to the nearest page
|
||||
* boundary, then rounded down to a multiple of the frame size;
|
||||
* this ensures that all data delivered to sa_stream_write() will
|
||||
* fit in a single buffer & that all committed memory can be used */
|
||||
sTemp->bufSize = (((3 * sTemp->bufMin) / 2) + 0xfff) & ~0xfff;
|
||||
sTemp->bufSize -= sTemp->bufSize % (SAOS2_SAMPLE_SIZE * nchannels);
|
||||
|
||||
*s = sTemp;
|
||||
|
||||
} while (0);
|
||||
|
||||
/* on error, free any allocations */
|
||||
if (status != SA_SUCCESS && sTemp) {
|
||||
if (sTemp)
|
||||
DosFreeMem(sTemp);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** Initialise the device */
|
||||
|
||||
int sa_stream_open(sa_stream_t *s)
|
||||
{
|
||||
int status = SA_SUCCESS;
|
||||
uint32_t rc;
|
||||
int32_t ctr;
|
||||
uint32_t bufCntRequested;
|
||||
MCI_AMP_OPEN_PARMS AmpOpenParms;
|
||||
MCI_MIXSETUP_PARMS MixSetupParms;
|
||||
MCI_BUFFER_PARMS BufferParms;
|
||||
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_open", "s is null", 0);
|
||||
|
||||
do {
|
||||
/* s->bufCnt will be restored after successfully allocating buffers */
|
||||
bufCntRequested = s->bufCnt;
|
||||
s->bufCnt = 0;
|
||||
|
||||
/* open the Amp-Mixer using the default device in shared mode */
|
||||
memset(&AmpOpenParms, 0, sizeof(MCI_AMP_OPEN_PARMS));
|
||||
AmpOpenParms.pszDeviceType = (PSZ)(MCI_DEVTYPE_AUDIO_AMPMIX | 0);
|
||||
|
||||
rc = _mciSendCommand(0, MCI_OPEN,
|
||||
MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE,
|
||||
(void*)&AmpOpenParms, 0);
|
||||
if (LOUSHORT(rc)) {
|
||||
status = os2_error(SA_ERROR_NO_DEVICE, "sa_stream_open",
|
||||
"MCI_OPEN - rc=", LOUSHORT(rc));
|
||||
break;
|
||||
}
|
||||
|
||||
/* save the device ID */
|
||||
s->hwDeviceID = AmpOpenParms.usDeviceID;
|
||||
|
||||
/* setup the Amp-Mixer to play wave data */
|
||||
memset(&MixSetupParms, 0, sizeof(MCI_MIXSETUP_PARMS));
|
||||
MixSetupParms.ulBitsPerSample = 16;
|
||||
MixSetupParms.ulFormatTag = MCI_WAVE_FORMAT_PCM;
|
||||
MixSetupParms.ulFormatMode = MCI_PLAY;
|
||||
MixSetupParms.ulSamplesPerSec = s->rate;
|
||||
MixSetupParms.ulChannels = s->nchannels;
|
||||
MixSetupParms.ulDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO;
|
||||
MixSetupParms.pmixEvent = (MIXEREVENT*)os2_mixer_event;
|
||||
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_MIXSETUP,
|
||||
MCI_WAIT | MCI_MIXSETUP_INIT,
|
||||
(void*)&MixSetupParms, 0);
|
||||
if (LOUSHORT(rc)) {
|
||||
status = os2_error(SA_ERROR_NOT_SUPPORTED, "sa_stream_open",
|
||||
"MCI_MIXSETUP - rc=", LOUSHORT(rc));
|
||||
break;
|
||||
}
|
||||
|
||||
/* save hw info we'll need later */
|
||||
s->hwMixHandle = MixSetupParms.ulMixHandle;
|
||||
s->hwWriteProc = MixSetupParms.pmixWrite;
|
||||
|
||||
/* allocate device buffers from the Amp-Mixer */
|
||||
BufferParms.ulStructLength = sizeof(MCI_BUFFER_PARMS);
|
||||
BufferParms.ulNumBuffers = bufCntRequested;
|
||||
BufferParms.ulBufferSize = s->bufSize;
|
||||
BufferParms.pBufList = s->bufList;
|
||||
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_BUFFER,
|
||||
MCI_WAIT | MCI_ALLOCATE_MEMORY,
|
||||
(void*)&BufferParms, 0);
|
||||
if (LOUSHORT(rc)) {
|
||||
status = os2_error(SA_ERROR_OOM, "sa_stream_open",
|
||||
"MCI_ALLOCATE_MEMORY - rc=", LOUSHORT(rc));
|
||||
break;
|
||||
}
|
||||
|
||||
/* MCI_ALLOCATE_MEMORY may have decreased the,
|
||||
* number of buffers, so update the counts */
|
||||
s->bufCnt = BufferParms.ulNumBuffers;
|
||||
s->freeCnt = BufferParms.ulNumBuffers;
|
||||
|
||||
/* sa_stream_write() & os2_mixer_event() require these initializations */
|
||||
s->usedMin = SAOS2_UNDERRUN_CNT;
|
||||
for (ctr = 0; ctr < s->bufCnt; ctr++) {
|
||||
s->bufList[ctr].ulStructLength = sizeof(MCI_MIX_BUFFER);
|
||||
s->bufList[ctr].ulBufferLength = 0;
|
||||
s->bufList[ctr].ulUserParm = (uint32_t)s;
|
||||
}
|
||||
|
||||
} while (0);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** Close/destroy everything */
|
||||
|
||||
int sa_stream_destroy(sa_stream_t *s)
|
||||
{
|
||||
int status = SA_SUCCESS;
|
||||
uint32_t rc;
|
||||
MCI_GENERIC_PARMS GenericParms = { 0 };
|
||||
MCI_BUFFER_PARMS BufferParms;
|
||||
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_destroy", "s is null", 0);
|
||||
|
||||
/* if the device was opened, close it */
|
||||
if (s->hwDeviceID) {
|
||||
|
||||
/* prevent os2_mixer_event() from reacting to a buffer under-run */
|
||||
s->bufMin = 0;
|
||||
s->playing = FALSE;
|
||||
|
||||
/* If another instance has already acquired the device the
|
||||
* MCI commands below will fail, so re-acquire it temporarily.
|
||||
* MCI_CLOSE will release the device to the previous owner. */
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_ACQUIREDEVICE,
|
||||
MCI_WAIT,
|
||||
(void*)&GenericParms, 0);
|
||||
if (LOUSHORT(rc))
|
||||
os2_error(0, "sa_stream_destroy",
|
||||
"MCI_ACQUIREDEVICE - rc=", LOUSHORT(rc));
|
||||
|
||||
/* stop the device (which may not actually be playing) */
|
||||
os2_stop_device(s->hwDeviceID);
|
||||
|
||||
/* if hardware buffers were allocated, free them */
|
||||
if (s->bufCnt) {
|
||||
BufferParms.hwndCallback = 0;
|
||||
BufferParms.ulStructLength = sizeof(MCI_BUFFER_PARMS);
|
||||
BufferParms.ulNumBuffers = s->bufCnt;
|
||||
BufferParms.ulBufferSize = s->bufSize;
|
||||
BufferParms.pBufList = s->bufList;
|
||||
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_BUFFER,
|
||||
MCI_WAIT | MCI_DEALLOCATE_MEMORY,
|
||||
(void*)&BufferParms, 0);
|
||||
if (LOUSHORT(rc))
|
||||
status = os2_error(SA_ERROR_SYSTEM, "sa_stream_destroy",
|
||||
"MCI_DEALLOCATE_MEMORY - rc=", LOUSHORT(rc));
|
||||
}
|
||||
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_CLOSE,
|
||||
MCI_WAIT,
|
||||
(void*)&GenericParms, 0);
|
||||
if (LOUSHORT(rc))
|
||||
status = os2_error(SA_ERROR_SYSTEM, "sa_stream_destroy",
|
||||
"MCI_CLOSE - rc=", LOUSHORT(rc));
|
||||
}
|
||||
|
||||
/* free other resources we allocated */
|
||||
DosFreeMem(s);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** Interleaved playback function */
|
||||
|
||||
int sa_stream_write(sa_stream_t * s, const void * data, size_t nbytes)
|
||||
{
|
||||
uint32_t rc;
|
||||
size_t cnt;
|
||||
PMCI_MIX_BUFFER pHW;
|
||||
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_write", "s is null", 0);
|
||||
if (!data)
|
||||
return os2_error(SA_ERROR_INVALID, "sa_stream_write", "data is null", 0);
|
||||
|
||||
/* exit if no data */
|
||||
if (!nbytes)
|
||||
return SA_SUCCESS;
|
||||
|
||||
/* This should only loop on the last write before sa_stream_drain()
|
||||
* is called; at other times, 'nbytes' won't exceed 'bufSize'. */
|
||||
while (nbytes) {
|
||||
size_t offs;
|
||||
size_t left;
|
||||
|
||||
/* get the count of free buffers, wait until at least one
|
||||
* is available (in practice, this should never block) */
|
||||
if (os2_get_free_count(s, 1))
|
||||
return SA_ERROR_SYSTEM;
|
||||
|
||||
/* copy as much as will fit into the buffer */
|
||||
pHW = &(s->bufList[s->freeNdx]);
|
||||
|
||||
offs = pHW->ulBufferLength;
|
||||
left = s->bufSize - offs;
|
||||
cnt = (nbytes > left) ? left : nbytes;
|
||||
memcpy(&((char*)pHW->pBuffer)[offs], (char*)data, cnt);
|
||||
|
||||
pHW->ulBufferLength += cnt;
|
||||
nbytes -= cnt;
|
||||
data = (char*)data + cnt;
|
||||
|
||||
/* don't dispatch the buffer until it has bufMin bytes */
|
||||
if (pHW->ulBufferLength < s->bufMin)
|
||||
continue;
|
||||
|
||||
/* write the buffer to the device */
|
||||
rc = s->hwWriteProc(s->hwMixHandle, pHW, 1);
|
||||
if (LOUSHORT(rc)) {
|
||||
pHW->ulBufferLength = 0;
|
||||
return os2_error(SA_ERROR_SYSTEM, "sa_stream_write",
|
||||
"mixWrite - rc=", LOUSHORT(rc));
|
||||
}
|
||||
|
||||
/* signal the event thread that a new buffer is now in use */
|
||||
__atomic_increment(&s->usedNew);
|
||||
s->playing = TRUE;
|
||||
|
||||
s->freeCnt--;
|
||||
s->freeNdx = (s->freeNdx + 1) % s->bufCnt;
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** sync/timing */
|
||||
|
||||
int sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos)
|
||||
{
|
||||
uint32_t rc;
|
||||
uint32_t then;
|
||||
uint32_t now;
|
||||
|
||||
if (!s || !pos)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_get_position",
|
||||
"s or pos is null", 0);
|
||||
|
||||
if (position != SA_POSITION_WRITE_SOFTWARE)
|
||||
return os2_error(SA_ERROR_NOT_SUPPORTED, "sa_stream_get_position",
|
||||
"unsupported postion type=", position);
|
||||
|
||||
/* Return the count of bytes that are known to have been played
|
||||
* already plus an adjustment for the number that may have been
|
||||
* played since the last mixer event. Since both 'writePos' and
|
||||
* 'writeTime' are volatile, the loop ensures both are in sync.
|
||||
* Note: the MCI command to get stream position isn't usable -
|
||||
* it returns a time value that resets when the stream is paused. */
|
||||
|
||||
do {
|
||||
then = s->writeTime;
|
||||
s->writePos += __atomic_xchg(&s->writeNew, 0);
|
||||
*pos = s->writePos;
|
||||
|
||||
/* adjust if device is playing & there's been at least one write */
|
||||
if (s->playing && s->writePos) {
|
||||
DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, &now, sizeof(now));
|
||||
*pos += ((now - then) * s->bps) / 1000;
|
||||
}
|
||||
} while (then != s->writeTime);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** Resume playing after a pause */
|
||||
|
||||
int sa_stream_resume(sa_stream_t *s)
|
||||
{
|
||||
uint32_t rc;
|
||||
MCI_GENERIC_PARMS GenericParms = { 0 };
|
||||
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_resume",
|
||||
"s is null", 0);
|
||||
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_ACQUIREDEVICE,
|
||||
MCI_WAIT,
|
||||
(void*)&GenericParms, 0);
|
||||
if (LOUSHORT(rc))
|
||||
return os2_error(SA_ERROR_SYSTEM, "sa_stream_resume",
|
||||
"MCI_ACQUIREDEVICE - rc=", LOUSHORT(rc));
|
||||
|
||||
/* this may produce a spurious error if the device
|
||||
* was just acquired, so report it but ignore it */
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_RESUME,
|
||||
MCI_WAIT,
|
||||
(void*)&GenericParms, 0);
|
||||
if (LOUSHORT(rc))
|
||||
os2_error(SA_ERROR_SYSTEM, "sa_stream_resume",
|
||||
"MCI_RESUME - rc=", LOUSHORT(rc));
|
||||
|
||||
/* reset the last write time so get_position() doesn't over-adjust */
|
||||
DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
|
||||
(void*)&s->writeTime, sizeof(s->writeTime));
|
||||
s->playing = TRUE;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** Pause audio playback (do not empty the buffer) */
|
||||
|
||||
int sa_stream_pause(sa_stream_t *s)
|
||||
{
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_pause", "s is null", 0);
|
||||
|
||||
/* pause & release device */
|
||||
s->playing = FALSE;
|
||||
return os2_pause_device(s->hwDeviceID, TRUE);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** Block until all audio has been played */
|
||||
|
||||
int sa_stream_drain(sa_stream_t *s)
|
||||
{
|
||||
int status = SA_SUCCESS;
|
||||
char buf[32];
|
||||
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_drain", "s is null", 0);
|
||||
|
||||
/* keep os2_mixer_event() from reacting to buffer under-runs */
|
||||
s->usedMin = 0;
|
||||
|
||||
/* perform the smallest possible write to force any
|
||||
* partially-filled buffer to be written to the device */
|
||||
memset(buf, 0, sizeof(buf));
|
||||
s->bufMin = 0;
|
||||
sa_stream_write(s, buf, s->nchannels * SAOS2_SAMPLE_SIZE);
|
||||
|
||||
/* DART won't start playing until 2 buffers have been written,
|
||||
* so write a dummy 2nd buffer if writePos is still zero */
|
||||
if (!s->writePos)
|
||||
s->writePos += __atomic_xchg(&s->writeNew, 0);
|
||||
if (!s->writePos)
|
||||
sa_stream_write(s, buf, s->nchannels * SAOS2_SAMPLE_SIZE);
|
||||
|
||||
/* wait for all buffers to become free */
|
||||
if (!status)
|
||||
status = os2_get_free_count(s, s->bufCnt);
|
||||
s->playing = FALSE;
|
||||
|
||||
/* stop the device so it doesn't misbehave due to an under-run */
|
||||
os2_stop_device(s->hwDeviceID);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** Query how much can be written without blocking */
|
||||
|
||||
int sa_stream_get_write_size(sa_stream_t *s, size_t *size)
|
||||
{
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_get_write_size",
|
||||
"s is null", 0);
|
||||
|
||||
/* return a non-zero value here in case the upstream code ignores
|
||||
* the return code - if so, sa_stream_write() will fail instead */
|
||||
if (os2_get_free_count(s, 0)) {
|
||||
*size = s->bufSize;
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
*size = s->freeCnt * s->bufSize;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** set absolute volume using a value ranging from 0.0 to 1.0 */
|
||||
|
||||
int sa_stream_set_volume_abs(sa_stream_t *s, float vol)
|
||||
{
|
||||
uint32_t rc;
|
||||
MCI_SET_PARMS SetParms;
|
||||
|
||||
if (!s)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_set_volume_abs",
|
||||
"s is null", 0);
|
||||
|
||||
/* convert f.p. value to an integer value ranging
|
||||
* from 0 to 100 and apply to both channels */
|
||||
SetParms.ulLevel = (vol * 100);
|
||||
SetParms.ulAudio = MCI_SET_AUDIO_ALL;
|
||||
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_SET,
|
||||
MCI_WAIT | MCI_SET_AUDIO | MCI_SET_VOLUME,
|
||||
(void*)&SetParms, 0);
|
||||
if (LOUSHORT(rc))
|
||||
return os2_error(SA_ERROR_SYSTEM, "sa_stream_set_volume_abs",
|
||||
"MCI_SET_VOLUME - rc=", LOUSHORT(rc));
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** get absolute volume as a value ranging from 0.0 to 1.0 */
|
||||
|
||||
int sa_stream_get_volume_abs(sa_stream_t *s, float *vol)
|
||||
{
|
||||
int status = SA_SUCCESS;
|
||||
uint32_t rc;
|
||||
MCI_STATUS_PARMS StatusParms;
|
||||
|
||||
if (!s || !vol)
|
||||
return os2_error(SA_ERROR_NO_INIT, "sa_stream_get_volume_abs",
|
||||
"s or vol is null", 0);
|
||||
|
||||
memset(&StatusParms, 0, sizeof(MCI_STATUS_PARMS));
|
||||
StatusParms.ulItem = MCI_STATUS_VOLUME;
|
||||
|
||||
rc = _mciSendCommand(s->hwDeviceID, MCI_STATUS,
|
||||
MCI_WAIT | MCI_STATUS_ITEM,
|
||||
(void*)&StatusParms, 0);
|
||||
if (LOUSHORT(rc)) {
|
||||
/* if there's an error, return a reasonable value */
|
||||
StatusParms.ulReturn = (50 | 50 << 16);
|
||||
status = os2_error(SA_ERROR_SYSTEM, "sa_stream_get_volume_abs",
|
||||
"MCI_STATUS_VOLUME - rc=", LOUSHORT(rc));
|
||||
}
|
||||
|
||||
/* left channel is the low-order word, right channel is the
|
||||
* high-order word - convert the average of the channels from
|
||||
* an integer (range 0 - 100) to a floating point value */
|
||||
|
||||
*vol = (LOUSHORT(StatusParms.ulReturn) +
|
||||
HIUSHORT(StatusParms.ulReturn)) / 200.0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Private (static) Functions */
|
||||
/*****************************************************************************/
|
||||
|
||||
/** signal the decode thread that a buffer is available -
|
||||
** this runs on a separate high-priority thread created by DART */
|
||||
|
||||
static int32_t os2_mixer_event(uint32_t ulStatus, PMCI_MIX_BUFFER pBuffer,
|
||||
uint32_t ulFlags)
|
||||
{
|
||||
sa_stream_t * s;
|
||||
|
||||
/* check for errors */
|
||||
if (ulFlags & MIX_STREAM_ERROR)
|
||||
os2_error(0, "os2_mixer_event", "MIX_STREAM_ERROR - status=", ulStatus);
|
||||
|
||||
if (!(ulFlags & MIX_WRITE_COMPLETE))
|
||||
return os2_error(TRUE, "os2_mixer_event",
|
||||
"unexpected event - flag=", ulFlags);
|
||||
|
||||
if (!pBuffer || !pBuffer->ulUserParm)
|
||||
return os2_error(TRUE, "os2_mixer_event", "null pointer", 0);
|
||||
|
||||
/* Note: this thread doesn't use a mutex to avoid a deadlock with the one
|
||||
* DART uses to prevent MCI operations while this function is running */
|
||||
s = (sa_stream_t *)pBuffer->ulUserParm;
|
||||
|
||||
/* update the number of buffers that are now in use */
|
||||
s->usedCnt += __atomic_xchg(&s->usedNew, 0);
|
||||
s->usedCnt--;
|
||||
|
||||
/* if fewer than 2 buffers are in use, enter recovery mode -
|
||||
* if we wait until they're all free, it's often too late; */
|
||||
if (s->usedCnt < s->usedMin) {
|
||||
s->playing = FALSE;
|
||||
os2_pause_device(s->hwDeviceID, FALSE);
|
||||
os2_error(0, "os2_mixer_event",
|
||||
"too few buffers in use - recovering", 0);
|
||||
}
|
||||
|
||||
/* pass the number of newly played bytes to the other thread;
|
||||
* get the time so the other thread can estimate how many
|
||||
* additional bytes have been consumed since this event */
|
||||
__atomic_add(&s->writeNew, pBuffer->ulBufferLength);
|
||||
pBuffer->ulBufferLength = 0;
|
||||
DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
|
||||
(void*)&s->writeTime, sizeof(s->writeTime));
|
||||
|
||||
/* signal the decode thread that a buffer is available */
|
||||
__atomic_increment(&s->freeNew);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** stop playback */
|
||||
|
||||
static void os2_stop_device(uint16_t hwDeviceID)
|
||||
{
|
||||
uint32_t rc;
|
||||
MCI_GENERIC_PARMS GenericParms = { 0 };
|
||||
|
||||
rc = _mciSendCommand(hwDeviceID, MCI_STOP,
|
||||
MCI_WAIT,
|
||||
(void*)&GenericParms, 0);
|
||||
if (LOUSHORT(rc))
|
||||
os2_error(0, "os2_stop_device", "MCI_STOP - rc=", LOUSHORT(rc));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** pause playback and optionally release device */
|
||||
|
||||
static int os2_pause_device(uint16_t hwDeviceID, uint32_t release)
|
||||
{
|
||||
uint32_t rc;
|
||||
MCI_GENERIC_PARMS GenericParms = { 0 };
|
||||
|
||||
rc = _mciSendCommand(hwDeviceID, MCI_PAUSE,
|
||||
MCI_WAIT,
|
||||
(void*)&GenericParms, 0);
|
||||
if (LOUSHORT(rc))
|
||||
return os2_error(SA_ERROR_SYSTEM, "os2_pause_device",
|
||||
"MCI_PAUSE - rc=", LOUSHORT(rc));
|
||||
|
||||
if (release)
|
||||
_mciSendCommand(hwDeviceID, MCI_RELEASEDEVICE,
|
||||
MCI_WAIT,
|
||||
(void*)&GenericParms, 0);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/** update the count of free buffers, returning when 'count' are available */
|
||||
|
||||
static int os2_get_free_count(sa_stream_t *s, int32_t count)
|
||||
{
|
||||
uint32_t timeout = 0;
|
||||
|
||||
while (1) {
|
||||
uint32_t now;
|
||||
|
||||
s->freeCnt += __atomic_xchg(&s->freeNew, 0);
|
||||
if (s->freeCnt >= count)
|
||||
break;
|
||||
|
||||
/* get the current time in milliseconds */
|
||||
DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, &now, sizeof(now));
|
||||
if (!timeout)
|
||||
timeout = now + SAOS2_WAIT;
|
||||
|
||||
if (now > timeout)
|
||||
return os2_error(SA_ERROR_SYSTEM, "os2_get_free_count",
|
||||
"timed-out waiting for free buffer(s)", 0);
|
||||
|
||||
DosSleep(1);
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
#ifdef SAOS2_ERROR
|
||||
|
||||
/** display an error message & return whatever value was passed in */
|
||||
|
||||
static int os2_error_msg(int rtn, char * func, char * msg, uint32_t err)
|
||||
{
|
||||
if (!err)
|
||||
fprintf(stderr, "sa_os2 error - %s: %s\n", func, msg);
|
||||
else
|
||||
fprintf(stderr, "sa_os2 error - %s: %s %u\n", func, msg, err);
|
||||
fflush(stderr);
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Mozilla-specific Function */
|
||||
/*****************************************************************************/
|
||||
|
||||
/** load mdm.dll & get the entrypoint for mciSendCommand() */
|
||||
|
||||
static int os2_load_mdm(void)
|
||||
{
|
||||
uint32_t rc;
|
||||
HMODULE hmod;
|
||||
char text[32];
|
||||
|
||||
if (_mciSendCommand)
|
||||
return SA_SUCCESS;
|
||||
|
||||
rc = DosLoadModule(text, sizeof(text), "MDM", &hmod);
|
||||
if (rc)
|
||||
return os2_error(SA_ERROR_SYSTEM, "os2_load_mdm",
|
||||
"DosLoadModule - rc=", rc);
|
||||
|
||||
/* the ordinal for mciSendCommand is '1' */
|
||||
rc = DosQueryProcAddr(hmod, 1, 0, (PFN*)&_mciSendCommand);
|
||||
if (rc) {
|
||||
_mciSendCommand = 0;
|
||||
return os2_error(SA_ERROR_SYSTEM, "os2_load_mdm",
|
||||
"DosQueryProcAddr - rc=", rc);
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Not Implemented / Not Supported */
|
||||
/*****************************************************************************/
|
||||
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
||||
/*****************************************************************************/
|
||||
|
@ -1,698 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#if defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
#include <soundcard.h>
|
||||
#else
|
||||
#include <sys/soundcard.h>
|
||||
#endif
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include "sydney_audio.h"
|
||||
|
||||
// for versions newer than 3.6.1
|
||||
#define OSS_VERSION(x, y, z) (x << 16 | y << 8 | z)
|
||||
// support only versions newer than 3.6.1
|
||||
#define SUPP_OSS_VERSION OSS_VERSION(3,0,1)
|
||||
|
||||
#if (SOUND_VERSION < SUPP_OSS_VERSION)
|
||||
#error Unsupported OSS Version
|
||||
#else
|
||||
|
||||
typedef struct sa_buf sa_buf;
|
||||
struct sa_buf {
|
||||
unsigned int size;
|
||||
unsigned int start;
|
||||
unsigned int end;
|
||||
sa_buf * next;
|
||||
unsigned char data[0];
|
||||
};
|
||||
|
||||
struct sa_stream {
|
||||
char* output_unit;
|
||||
int output_fd;
|
||||
pthread_t thread_id;
|
||||
pthread_mutex_t mutex;
|
||||
char playing;
|
||||
int64_t bytes_played;
|
||||
|
||||
/* audio format info */
|
||||
unsigned int rate;
|
||||
unsigned int channels;
|
||||
int format;
|
||||
|
||||
/* buffer list */
|
||||
sa_buf * bl_head;
|
||||
sa_buf * bl_tail;
|
||||
int n_bufs;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Use a default buffer size with enough room for one second of audio,
|
||||
* assuming stereo data at 44.1kHz with 32 bits per channel, and impose
|
||||
* a generous limit on the number of buffers.
|
||||
*/
|
||||
#define BUF_SIZE (2 * 44100 * 4)
|
||||
#define BUF_LIMIT 5
|
||||
|
||||
#if BUF_LIMIT < 2
|
||||
#error BUF_LIMIT must be at least 2!
|
||||
#endif
|
||||
|
||||
static void audio_callback(void* s);
|
||||
static sa_buf *new_buffer(void);
|
||||
|
||||
/** Private functions - implementation specific */
|
||||
|
||||
/*!
|
||||
* \brief private function mapping Sudney Audio format to OSS formats
|
||||
* \param format - Sydney Audio API specific format
|
||||
* \param - filled by the function with a value for corresponding OSS format
|
||||
* \return - Sydney API error value as in ::sa_pcm_format_t
|
||||
* */
|
||||
static int oss_audio_format(sa_pcm_format_t sa_format, int *fmt) {
|
||||
*fmt = -1;
|
||||
switch (sa_format) {
|
||||
case SA_PCM_FORMAT_U8:
|
||||
*fmt = AFMT_U8;
|
||||
break;
|
||||
case SA_PCM_FORMAT_ULAW:
|
||||
*fmt = AFMT_MU_LAW;
|
||||
break;
|
||||
case SA_PCM_FORMAT_ALAW:
|
||||
*fmt = AFMT_A_LAW;
|
||||
break;
|
||||
/* 16-bit little endian (LE) format */
|
||||
case SA_PCM_FORMAT_S16_LE:
|
||||
*fmt = AFMT_S16_LE;
|
||||
break;
|
||||
/* 16-bit big endian (BE) format */
|
||||
case SA_PCM_FORMAT_S16_BE:
|
||||
*fmt = AFMT_S16_BE;
|
||||
break;
|
||||
#if SOUND_VERSION >= OSS_VERSION(4,0,0)
|
||||
/* 24-bit formats (LSB aligned in 32 bit word) */
|
||||
case SA_PCM_FORMAT_S24_LE:
|
||||
*fmt = AFMT_S24_LE;
|
||||
break;
|
||||
/* 24-bit formats (LSB aligned in 32 bit word) */
|
||||
case SA_PCM_FORMAT_S24_BE:
|
||||
*fmt = AFMT_S24_BE;
|
||||
break;
|
||||
/* 32-bit format little endian */
|
||||
case SA_PCM_FORMAT_S32_LE:
|
||||
*fmt = AFMT_S32_LE;
|
||||
break;
|
||||
/* 32-bit format big endian */
|
||||
case SA_PCM_FORMAT_S32_BE:
|
||||
*fmt = AFMT_S32_BE;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Startup and shutdown functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_create_pcm(
|
||||
sa_stream_t ** _s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int channels
|
||||
) {
|
||||
sa_stream_t * s = 0;
|
||||
int fmt = 0;
|
||||
|
||||
/*
|
||||
* Make sure we return a NULL stream pointer on failure.
|
||||
*/
|
||||
if (_s == NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
*_s = NULL;
|
||||
|
||||
if (mode != SA_MODE_WRONLY) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (oss_audio_format(format, &fmt) != SA_SUCCESS) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the instance and required resources.
|
||||
*/
|
||||
if ((s = malloc(sizeof(sa_stream_t))) == NULL) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
if ((s->bl_head = new_buffer()) == NULL) {
|
||||
free(s);
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
||||
free(s->bl_head);
|
||||
free(s);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
s->output_unit = "/dev/dsp";
|
||||
s->output_fd = -1;
|
||||
s->thread_id = 0;
|
||||
s->playing = 0;
|
||||
s->bytes_played = 0;
|
||||
s->rate = rate;
|
||||
s->channels = channels;
|
||||
s->format = fmt;
|
||||
s->bl_tail = s->bl_head;
|
||||
s->n_bufs = 1;
|
||||
|
||||
*_s = s;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_open(sa_stream_t *s) {
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (s->output_unit == NULL || s->output_fd != -1) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
// open the default OSS device
|
||||
if ((s->output_fd = open(s->output_unit, O_WRONLY, 0)) == -1) {
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
// set the playback rate
|
||||
if (ioctl(s->output_fd, SNDCTL_DSP_SPEED, &(s->rate)) < 0) {
|
||||
close(s->output_fd);
|
||||
s->output_fd = -1;
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// set the channel numbers
|
||||
if (ioctl(s->output_fd, SNDCTL_DSP_CHANNELS, &(s->channels)) < 0) {
|
||||
close(s->output_fd);
|
||||
s->output_fd = -1;
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (ioctl(s->output_fd, SNDCTL_DSP_SETFMT, &(s->format)) < 0 ) {
|
||||
close(s->output_fd);
|
||||
s->output_fd = -1;
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_destroy(sa_stream_t *s) {
|
||||
int result = SA_SUCCESS;
|
||||
pthread_t thread_id;
|
||||
|
||||
if (s == NULL) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
thread_id = s->thread_id;
|
||||
|
||||
/*
|
||||
* This causes the thread sending data to OSS to stop
|
||||
*/
|
||||
s->thread_id = 0;
|
||||
|
||||
/*
|
||||
* Shut down the audio output device.
|
||||
*/
|
||||
if (s->output_fd != -1) {
|
||||
if (s->playing && close(s->output_fd) < 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
pthread_join(thread_id, NULL);
|
||||
|
||||
/*
|
||||
* Release resources.
|
||||
*/
|
||||
if (pthread_mutex_destroy(&s->mutex) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
while (s->bl_head != NULL) {
|
||||
sa_buf * next = s->bl_head->next;
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
}
|
||||
free(s);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Data read and write functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
|
||||
int result = SA_SUCCESS;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Append the new data to the end of our buffer list.
|
||||
*/
|
||||
while (1) {
|
||||
unsigned int avail = s->bl_tail->size - s->bl_tail->end;
|
||||
|
||||
if (nbytes <= avail) {
|
||||
|
||||
/*
|
||||
* The new data will fit into the current tail buffer, so
|
||||
* just copy it in and we're done.
|
||||
*/
|
||||
memcpy(s->bl_tail->data + s->bl_tail->end, data, nbytes);
|
||||
s->bl_tail->end += nbytes;
|
||||
break;
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Copy what we can into the tail and allocate a new buffer
|
||||
* for the rest.
|
||||
*/
|
||||
memcpy(s->bl_tail->data + s->bl_tail->end, data, avail);
|
||||
s->bl_tail->end += avail;
|
||||
data = ((unsigned char *)data) + avail;
|
||||
nbytes -= avail;
|
||||
|
||||
/*
|
||||
* If we still have data left to copy but we've hit the limit of
|
||||
* allowable buffer allocations, we need to spin for a bit to allow
|
||||
* the audio callback function to slurp some more data up.
|
||||
*/
|
||||
if (nbytes > 0 && s->n_bufs == BUF_LIMIT) {
|
||||
#ifdef TIMING_TRACE
|
||||
printf("#"); /* too much audio data */
|
||||
#endif
|
||||
if (!s->playing) {
|
||||
/*
|
||||
* We haven't even started playing yet! That means the
|
||||
* BUF_SIZE/BUF_LIMIT values are too low... Not much we can
|
||||
* do here; spinning won't help because the audio callback
|
||||
* hasn't been enabled yet. Oh well, error time.
|
||||
*/
|
||||
printf("Too much audio data received before audio device enabled!\n");
|
||||
result = SA_ERROR_SYSTEM;
|
||||
break;
|
||||
}
|
||||
while (s->n_bufs == BUF_LIMIT) {
|
||||
struct timespec ts = {0, 1000000};
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
nanosleep(&ts, NULL);
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new tail buffer, and go 'round again to fill it up.
|
||||
*/
|
||||
if ((s->bl_tail->next = new_buffer()) == NULL) {
|
||||
result = SA_ERROR_OOM;
|
||||
break;
|
||||
}
|
||||
s->n_bufs++;
|
||||
s->bl_tail = s->bl_tail->next;
|
||||
|
||||
} /* if (nbytes <= avail), else */
|
||||
|
||||
} /* while (1) */
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Once we have our first block of audio data, enable the audio callback
|
||||
* function. This doesn't need to be protected by the mutex, because
|
||||
* s->playing is not used in the audio callback thread, and it's probably
|
||||
* better not to be inside the lock when we enable the audio callback.
|
||||
*/
|
||||
if (!s->playing) {
|
||||
s->playing = 1;
|
||||
if (pthread_create(&s->thread_id, NULL, (void *)audio_callback, s) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void audio_callback(void* data)
|
||||
{
|
||||
sa_stream_t* s = (sa_stream_t*)data;
|
||||
audio_buf_info info;
|
||||
char* buffer = 0;
|
||||
unsigned int avail;
|
||||
int frames;
|
||||
|
||||
#ifdef TIMING_TRACE
|
||||
printf("."); /* audio read 'tick' */
|
||||
#endif
|
||||
|
||||
ioctl(s->output_fd, SNDCTL_DSP_GETOSPACE, &info);
|
||||
buffer = malloc(info.bytes);
|
||||
|
||||
while(1) {
|
||||
char* dst = buffer;
|
||||
unsigned int bytes_to_copy = info.bytes;
|
||||
int bytes = info.bytes;
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (!s->thread_id)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Consume data from the start of the buffer list.
|
||||
*/
|
||||
while (1) {
|
||||
assert(s->bl_head->start <= s->bl_head->end);
|
||||
avail = s->bl_head->end - s->bl_head->start;
|
||||
|
||||
if (avail >= bytes_to_copy) {
|
||||
/*
|
||||
* We have all we need in the head buffer, so just grab it and go.
|
||||
*/
|
||||
memcpy(dst, s->bl_head->data + s->bl_head->start, bytes_to_copy);
|
||||
s->bl_head->start += bytes_to_copy;
|
||||
s->bytes_played += bytes_to_copy;
|
||||
break;
|
||||
|
||||
} else {
|
||||
|
||||
sa_buf* next = 0;
|
||||
/*
|
||||
* Copy what we can from the head and move on to the next buffer.
|
||||
*/
|
||||
memcpy(dst, s->bl_head->data + s->bl_head->start, avail);
|
||||
s->bl_head->start += avail;
|
||||
dst += avail;
|
||||
bytes_to_copy -= avail;
|
||||
s->bytes_played += avail;
|
||||
|
||||
/*
|
||||
* We want to free the now-empty buffer, but not if it's also the
|
||||
* current tail. If it is the tail, we don't have enough data to fill
|
||||
* the destination buffer, so we write less and give up.
|
||||
*/
|
||||
next = s->bl_head->next;
|
||||
if (next == NULL) {
|
||||
#ifdef TIMING_TRACE
|
||||
printf("!"); /* not enough audio data */
|
||||
#endif
|
||||
bytes = bytes-bytes_to_copy;
|
||||
break;
|
||||
}
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
s->n_bufs--;
|
||||
|
||||
} /* if (avail >= bytes_to_copy), else */
|
||||
|
||||
} /* while (1) */
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if(bytes > 0) {
|
||||
frames = write(s->output_fd, buffer, bytes);
|
||||
if (frames < 0) {
|
||||
printf("error writing to sound device\n");
|
||||
}
|
||||
if (frames >= 0 && frames != bytes) {
|
||||
printf("short write (expected %d, wrote %d)\n", (int)bytes, (int)frames);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
|
||||
sa_buf * b;
|
||||
size_t used = 0;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Sum up the used portions of our buffers and subtract that from
|
||||
* the pre-defined max allowed allocation.
|
||||
*/
|
||||
for (b = s->bl_head; b != NULL; b = b->next) {
|
||||
used += b->end - b->start;
|
||||
}
|
||||
*size = BUF_SIZE * BUF_LIMIT - used;
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
|
||||
int err;
|
||||
count_info ptr;
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (position != SA_POSITION_WRITE_SOFTWARE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if ((err = ioctl(s->output_fd,
|
||||
SNDCTL_DSP_GETOPTR,
|
||||
&ptr)) <0) {
|
||||
fprintf(stderr, "Error reading playback position\n");
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
*pos = (int64_t)ptr.bytes;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_pause(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
#if 0 /* TODO */
|
||||
AudioOutputUnitStop(s->output_unit);
|
||||
#endif
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_resume(sa_stream_t *s) {
|
||||
|
||||
if (s == NULL || s->output_unit == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* The audio device resets its mSampleTime counter after pausing,
|
||||
* so we need to clear our tracking value to keep that in sync.
|
||||
*/
|
||||
s->bytes_played = 0;
|
||||
#if 0 /* TODO */
|
||||
AudioOutputUnitStart(s->output_unit);
|
||||
#endif
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static sa_buf *
|
||||
new_buffer(void) {
|
||||
sa_buf * b = malloc(sizeof(sa_buf) + BUF_SIZE);
|
||||
if (b != NULL) {
|
||||
b->size = BUF_SIZE;
|
||||
b->start = 0;
|
||||
b->end = 0;
|
||||
b->next = NULL;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Extension functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
#if SOUND_VERSION >= OSS_VERSION(4,0,0)
|
||||
int mvol = ((int)(100*vol)) | ((int)(100*vol) << 8);
|
||||
if (ioctl(s->output_fd, SNDCTL_DSP_SETPLAYVOL, &mvol) < 0){
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
return SA_SUCCESS;
|
||||
#else
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_volume_abs(sa_stream_t *s, float *vol) {
|
||||
|
||||
if (vol == NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
*vol = 0.0f;
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
#if SOUND_VERSION >= OSS_VERSION(4,0,0)
|
||||
int mvol;
|
||||
if (ioctl(s->output_fd, SNDCTL_DSP_SETPLAYVOL, &mvol) < 0){
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
*vol = ((mvol & 0xFF) + (mvol >> 8)) / 200.0f;
|
||||
return SA_SUCCESS;
|
||||
#else
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_drain(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
||||
#endif
|
@ -1,686 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include "sydney_audio.h"
|
||||
|
||||
/* Pulseaudio implementation based heavily on sydney_audio_alsa.c */
|
||||
|
||||
/*
|
||||
* The audio interface is based on a "pull" I/O model, which means you
|
||||
* can't just provide a data buffer and tell the audio device to play; you must
|
||||
* register a callback and provide data as the device asks for it. To support
|
||||
* sydney audio's "write-to-play" style interface, we have to buffer up the
|
||||
* data as it arrives and feed it to the callback as required.
|
||||
*
|
||||
* This is handled by a simple linked list of buffers; data is always written
|
||||
* to the tail and read from the head. Each buffer tracks the start and end
|
||||
* positions of its contained data. Buffers are allocated when the tail buffer
|
||||
* fills, and freed when the head buffer empties. There is always at least one
|
||||
* buffer allocated.
|
||||
*
|
||||
* s e s e s e + data read
|
||||
* +++##### -> ######## -> ####---- # data written
|
||||
* ^ ^ - empty
|
||||
* bl_head bl_tail
|
||||
*/
|
||||
|
||||
typedef struct sa_buf sa_buf;
|
||||
struct sa_buf {
|
||||
unsigned int size;
|
||||
unsigned int start;
|
||||
unsigned int end;
|
||||
sa_buf * next;
|
||||
unsigned char data[0];
|
||||
};
|
||||
|
||||
struct sa_stream {
|
||||
pa_context* context;
|
||||
pa_stream* stream;
|
||||
pa_sample_spec sample_spec;
|
||||
pa_threaded_mainloop* m;
|
||||
|
||||
pthread_t thread_id;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
char playing;
|
||||
int64_t bytes_written;
|
||||
char client_name[255];
|
||||
|
||||
/* buffer list */
|
||||
sa_buf * bl_head;
|
||||
sa_buf * bl_tail;
|
||||
int n_bufs;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Use a default buffer size with enough room for one second of audio,
|
||||
* assuming stereo data at 44.1kHz with 32 bits per channel, and impose
|
||||
* a generous limit on the number of buffers.
|
||||
*/
|
||||
#define BUF_SIZE (2 * 44100 * 4)
|
||||
#define BUF_LIMIT 5
|
||||
|
||||
#if BUF_LIMIT < 2
|
||||
#error BUF_LIMIT must be at least 2!
|
||||
#endif
|
||||
|
||||
static void audio_callback(void* data);
|
||||
static void stream_write_callback(pa_stream *stream, size_t length, void *userdata);
|
||||
static void stream_latency_update_callback(pa_stream *stream, void *userdata);
|
||||
static void context_state_callback(pa_context *c, void *userdata);
|
||||
static sa_buf *new_buffer(void);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Pulseaudio callback functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static void context_state_callback(pa_context *c, void *userdata) {
|
||||
sa_stream_t* s = (sa_stream_t*)userdata;
|
||||
switch (pa_context_get_state(c)) {
|
||||
case PA_CONTEXT_READY:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
case PA_CONTEXT_FAILED:
|
||||
pa_threaded_mainloop_signal(s->m, 0);
|
||||
break;
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stream_state_callback(pa_stream *stream, void *userdata) {
|
||||
sa_stream_t* s = (sa_stream_t*)userdata;
|
||||
switch (pa_stream_get_state(stream)) {
|
||||
|
||||
case PA_STREAM_READY:
|
||||
case PA_STREAM_FAILED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
pa_threaded_mainloop_signal(s->m, 0);
|
||||
break;
|
||||
case PA_STREAM_UNCONNECTED:
|
||||
case PA_STREAM_CREATING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stream_write_callback(pa_stream *stream, size_t length, void *userdata)
|
||||
{
|
||||
sa_stream_t* s = (sa_stream_t*)userdata;
|
||||
pa_threaded_mainloop_signal(s->m, 0);
|
||||
}
|
||||
|
||||
static void stream_latency_update_callback(pa_stream *stream, void *userdata)
|
||||
{
|
||||
sa_stream_t* s = (sa_stream_t*)userdata;
|
||||
pa_threaded_mainloop_signal(s->m, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Startup and shutdown functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_create_pcm(
|
||||
sa_stream_t ** _s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int n_channels
|
||||
) {
|
||||
sa_stream_t * s = 0;
|
||||
char *server = NULL;
|
||||
|
||||
/*
|
||||
* Make sure we return a NULL stream pointer on failure.
|
||||
*/
|
||||
if (_s == NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
*_s = NULL;
|
||||
|
||||
if (mode != SA_MODE_WRONLY) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (format != SA_PCM_FORMAT_S16_LE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the instance and required resources.
|
||||
*/
|
||||
if ((s = malloc(sizeof(sa_stream_t))) == NULL) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
if ((s->bl_head = new_buffer()) == NULL) {
|
||||
free(s);
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
||||
free(s->bl_head);
|
||||
free(s);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
s->stream = NULL;
|
||||
s->m = NULL;
|
||||
s->thread_id = 0;
|
||||
s->playing = 0;
|
||||
s->bytes_written = 0;
|
||||
|
||||
s->bl_tail = s->bl_head;
|
||||
s->n_bufs = 1;
|
||||
|
||||
s->sample_spec.format = PA_SAMPLE_S16LE;
|
||||
s->sample_spec.channels = n_channels;
|
||||
s->sample_spec.rate = rate;
|
||||
|
||||
strcpy(s->client_name, client_name);
|
||||
|
||||
/* Set up a new main loop */
|
||||
s->m = pa_threaded_mainloop_new();
|
||||
pa_threaded_mainloop_start(s->m);
|
||||
|
||||
pa_threaded_mainloop_lock(s->m);
|
||||
|
||||
/* Create a new connection context */
|
||||
if (!(s->context = pa_context_new(pa_threaded_mainloop_get_api(s->m), "OggPlay"))) {
|
||||
fprintf(stderr, "pa_context_new() failed.\n");
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
pa_context_set_state_callback(s->context, context_state_callback, s);
|
||||
|
||||
pa_context_connect(s->context, server, 0, NULL);
|
||||
|
||||
/* Wait until the context is ready */
|
||||
pa_threaded_mainloop_wait(s->m);
|
||||
if (pa_context_get_state(s->context) != PA_CONTEXT_READY) {
|
||||
fprintf(stderr, "creating Pulseaudio Context failed\n");
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
|
||||
*_s = s;
|
||||
return SA_SUCCESS;
|
||||
|
||||
unlock_and_fail:
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
free(s);
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_open(sa_stream_t *s) {
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (s->stream != NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(s->m);
|
||||
if (!(s->stream = pa_stream_new(s->context, s->client_name, &s->sample_spec, NULL))) {
|
||||
fprintf(stderr, "pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(s->context)));
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
|
||||
pa_stream_set_state_callback(s->stream, stream_state_callback, s);
|
||||
pa_stream_set_write_callback(s->stream, stream_write_callback, s);
|
||||
pa_stream_set_latency_update_callback(s->stream, stream_latency_update_callback, s);
|
||||
|
||||
if (pa_stream_connect_playback(s->stream, NULL, NULL, 0, NULL, NULL) < 0) {
|
||||
fprintf(stderr, "pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(s->context)));
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
|
||||
/* Wait until the stream is ready */
|
||||
pa_threaded_mainloop_wait(s->m);
|
||||
|
||||
if (pa_stream_get_state(s->stream) != PA_STREAM_READY) {
|
||||
fprintf(stderr, "Failed to connect stream: %s", pa_strerror(pa_context_errno(s->context)));
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
|
||||
if (!s->stream)
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
return SA_SUCCESS;
|
||||
|
||||
unlock_and_fail:
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_destroy(sa_stream_t *s) {
|
||||
if (s == NULL) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
s->thread_id = 0;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
pa_threaded_mainloop_lock(s->m);
|
||||
pa_stream_disconnect(s->stream);
|
||||
s->stream = NULL;
|
||||
pa_context_disconnect(s->context);
|
||||
pa_context_unref(s->context);
|
||||
s->context = NULL;
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
|
||||
pa_threaded_mainloop_stop(s->m);
|
||||
pa_threaded_mainloop_free(s->m);
|
||||
|
||||
pthread_mutex_destroy(&s->mutex);
|
||||
|
||||
while (s->bl_head != NULL) {
|
||||
sa_buf * next = s->bl_head->next;
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
}
|
||||
free(s);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Data read and write functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
|
||||
int result = SA_SUCCESS;
|
||||
|
||||
if (s == NULL || s->stream == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Append the new data to the end of our buffer list.
|
||||
*/
|
||||
while (1) {
|
||||
unsigned int avail = s->bl_tail->size - s->bl_tail->end;
|
||||
|
||||
if (nbytes <= avail) {
|
||||
|
||||
/*
|
||||
* The new data will fit into the current tail buffer, so
|
||||
* just copy it in and we're done.
|
||||
*/
|
||||
memcpy(s->bl_tail->data + s->bl_tail->end, data, nbytes);
|
||||
s->bl_tail->end += nbytes;
|
||||
break;
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Copy what we can into the tail and allocate a new buffer
|
||||
* for the rest.
|
||||
*/
|
||||
memcpy(s->bl_tail->data + s->bl_tail->end, data, avail);
|
||||
s->bl_tail->end += avail;
|
||||
data = ((unsigned char *)data) + avail;
|
||||
nbytes -= avail;
|
||||
|
||||
/*
|
||||
* If we still have data left to copy but we've hit the limit of
|
||||
* allowable buffer allocations, we need to spin for a bit to allow
|
||||
* the audio callback function to slurp some more data up.
|
||||
*/
|
||||
if (nbytes > 0 && s->n_bufs == BUF_LIMIT) {
|
||||
if (!s->playing) {
|
||||
/*
|
||||
* We haven't even started playing yet! That means the
|
||||
* BUF_SIZE/BUF_LIMIT values are too low... Not much we can
|
||||
* do here; spinning won't help because the audio callback
|
||||
* hasn't been enabled yet. Oh well, error time.
|
||||
*/
|
||||
printf("Too much audio data received before audio device enabled!\n");
|
||||
result = SA_ERROR_SYSTEM;
|
||||
break;
|
||||
}
|
||||
while (s->n_bufs == BUF_LIMIT) {
|
||||
struct timespec ts = {0, 1000000};
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
nanosleep(&ts, NULL);
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new tail buffer, and go 'round again to fill it up.
|
||||
*/
|
||||
if ((s->bl_tail->next = new_buffer()) == NULL) {
|
||||
result = SA_ERROR_OOM;
|
||||
break;
|
||||
}
|
||||
s->n_bufs++;
|
||||
s->bl_tail = s->bl_tail->next;
|
||||
|
||||
} /* if (nbytes <= avail), else */
|
||||
|
||||
} /* while (1) */
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
/*
|
||||
* Once we have our first block of audio data, enable the audio callback
|
||||
* function. This doesn't need to be protected by the mutex, because
|
||||
* s->playing is not used in the audio callback thread, and it's probably
|
||||
* better not to be inside the lock when we enable the audio callback.
|
||||
*/
|
||||
if (!s->playing) {
|
||||
s->playing = 1;
|
||||
if (pthread_create(&s->thread_id, NULL, (void *)audio_callback, s) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void audio_callback(void* data)
|
||||
{
|
||||
sa_stream_t* s = (sa_stream_t*)data;
|
||||
unsigned int bytes_per_frame = s->sample_spec.channels * pa_sample_size(&s->sample_spec);
|
||||
size_t buffer_size = s->sample_spec.rate * bytes_per_frame;
|
||||
char* buffer = malloc(buffer_size);
|
||||
|
||||
while(1) {
|
||||
char* dst = buffer;
|
||||
size_t bytes_to_copy, bytes;
|
||||
|
||||
pa_threaded_mainloop_lock(s->m);
|
||||
while(1) {
|
||||
if (s == NULL || s->stream == NULL) {
|
||||
if (s != NULL && s->m != NULL)
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
goto free_buffer;
|
||||
}
|
||||
if ((bytes_to_copy = pa_stream_writable_size(s->stream)) == (size_t) -1) {
|
||||
fprintf(stderr, "pa_stream_writable_size() failed: %s", pa_strerror(pa_context_errno(s->context)));
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
goto free_buffer;
|
||||
}
|
||||
if(bytes_to_copy > 0)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(s->m);
|
||||
}
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
if (bytes_to_copy > buffer_size)
|
||||
bytes_to_copy = buffer_size;
|
||||
bytes = bytes_to_copy;
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (!s->thread_id) {
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Consume data from the start of the buffer list.
|
||||
*/
|
||||
while (1) {
|
||||
unsigned int avail = s->bl_head->end - s->bl_head->start;
|
||||
assert(s->bl_head->start <= s->bl_head->end);
|
||||
|
||||
if (avail >= bytes_to_copy) {
|
||||
/*
|
||||
* We have all we need in the head buffer, so just grab it and go.
|
||||
*/
|
||||
memcpy(dst, s->bl_head->data + s->bl_head->start, bytes_to_copy);
|
||||
s->bl_head->start += bytes_to_copy;
|
||||
break;
|
||||
|
||||
} else {
|
||||
sa_buf* next = 0;
|
||||
/*
|
||||
* Copy what we can from the head and move on to the next buffer.
|
||||
*/
|
||||
memcpy(dst, s->bl_head->data + s->bl_head->start, avail);
|
||||
s->bl_head->start += avail;
|
||||
dst += avail;
|
||||
bytes_to_copy -= avail;
|
||||
/*
|
||||
* We want to free the now-empty buffer, but not if it's also the
|
||||
* current tail. If it is the tail, we don't have enough data to fill
|
||||
* the destination buffer, so we write less and give up.
|
||||
*/
|
||||
next = s->bl_head->next;
|
||||
if (next == NULL) {
|
||||
bytes = bytes-bytes_to_copy;
|
||||
break;
|
||||
}
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
s->n_bufs--;
|
||||
} /* if (avail >= bytes_to_copy), else */
|
||||
} /* while (1) */
|
||||
|
||||
if(bytes > 0) {
|
||||
pa_threaded_mainloop_lock(s->m);
|
||||
if (pa_stream_write(s->stream, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE) < 0) {
|
||||
fprintf(stderr, "pa_stream_write() failed: %s", pa_strerror(pa_context_errno(s->context)));
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
return;
|
||||
}
|
||||
pa_stream_update_timing_info(s->stream, NULL, NULL);
|
||||
s->bytes_written += bytes;
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
free_buffer:
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
|
||||
sa_buf * b;
|
||||
size_t used = 0;
|
||||
|
||||
if (s == NULL || s->stream == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Sum up the used portions of our buffers and subtract that from
|
||||
* the pre-defined max allowed allocation.
|
||||
*/
|
||||
for (b = s->bl_head; b != NULL; b = b->next) {
|
||||
used += b->end - b->start;
|
||||
}
|
||||
*size = BUF_SIZE * BUF_LIMIT - used;
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
|
||||
pa_usec_t usec;
|
||||
if (s == NULL || s->stream == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (position != SA_POSITION_WRITE_SOFTWARE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
pa_threaded_mainloop_lock(s->m);
|
||||
if(pa_stream_get_time(s->stream, &usec) != PA_ERR_NODATA) {
|
||||
*pos = pa_usec_to_bytes(usec, &s->sample_spec);
|
||||
}
|
||||
else {
|
||||
*pos = s->bytes_written;
|
||||
}
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_pause(sa_stream_t *s) {
|
||||
if (s == NULL || s->stream == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_resume(sa_stream_t *s) {
|
||||
if (s == NULL || s->stream == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(s->m);
|
||||
s->bytes_written = 0;
|
||||
pa_threaded_mainloop_unlock(s->m);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static sa_buf *
|
||||
new_buffer(void) {
|
||||
sa_buf * b = malloc(sizeof(sa_buf) + BUF_SIZE);
|
||||
if (b != NULL) {
|
||||
b->size = BUF_SIZE;
|
||||
b->start = 0;
|
||||
b->end = 0;
|
||||
b->next = NULL;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Extension functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
|
||||
pa_cvolume cv;
|
||||
|
||||
if (s == NULL || s->stream == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
pa_cvolume_set(&cv, s->sample_spec.channels, pa_sw_volume_from_dB(vol));
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_get_volume_abs(sa_stream_t *s, float *vol) {
|
||||
if (s == NULL || s->stream == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
printf("sa_stream_get_volume_abs not implemented\n");
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_drain(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
@ -1,723 +0,0 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stropts.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/audio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mixer.h>
|
||||
#include "sydney_audio.h"
|
||||
|
||||
/* Sun Audio implementation based heavily on sydney_audio_mac.c */
|
||||
|
||||
#define DEFAULT_AUDIO_DEVICE "/dev/audio"
|
||||
#define DEFAULT_DSP_DEVICE "/dev/dsp"
|
||||
|
||||
/* Macros copied from audio_oss.h */
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or http://www.opensolaris.org/os/licensing.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 4Front Technologies 1996-2008.
|
||||
*
|
||||
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
|
||||
* Use is subject to license terms.
|
||||
*/
|
||||
#define OSSIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */
|
||||
#define OSSIOC_VOID 0x00000000 /* no parameters */
|
||||
#define OSSIOC_OUT 0x20000000 /* copy out parameters */
|
||||
#define OSSIOC_IN 0x40000000 /* copy in parameters */
|
||||
#define OSSIOC_INOUT (OSSIOC_IN|OSSIOC_OUT)
|
||||
#define OSSIOC_SZ(t) ((sizeof (t) & OSSIOCPARM_MASK) << 16)
|
||||
#define __OSSIO(x, y) ((int)(OSSIOC_VOID|(x<<8)|y))
|
||||
#define __OSSIOR(x, y, t) ((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y))
|
||||
#define __OSSIOWR(x, y, t) ((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y))
|
||||
#define SNDCTL_DSP_SPEED __OSSIOWR('P', 2, int)
|
||||
#define SNDCTL_DSP_CHANNELS __OSSIOWR('P', 6, int)
|
||||
#define SNDCTL_DSP_SETFMT __OSSIOWR('P', 5, int) /* Selects ONE fmt */
|
||||
#define SNDCTL_DSP_GETPLAYVOL __OSSIOR('P', 24, int)
|
||||
#define SNDCTL_DSP_SETPLAYVOL __OSSIOWR('P', 24, int)
|
||||
#define SNDCTL_DSP_HALT_OUTPUT __OSSIO('P', 34)
|
||||
#define AFMT_S16_LE 0x00000010
|
||||
#define AFMT_S16_BE 0x00000020
|
||||
#ifdef SA_LITTLE_ENDIAN
|
||||
#define AFMT_S16_NE AFMT_S16_LE
|
||||
#else
|
||||
#define AFMT_S16_NE AFMT_S16_BE
|
||||
#endif
|
||||
|
||||
typedef struct sa_buf sa_buf;
|
||||
struct sa_buf {
|
||||
unsigned int size;
|
||||
unsigned int start;
|
||||
unsigned int end;
|
||||
sa_buf * next;
|
||||
unsigned char data[];
|
||||
};
|
||||
|
||||
struct sa_stream {
|
||||
bool using_oss;
|
||||
int output_fd;
|
||||
pthread_t thread_id;
|
||||
pthread_mutex_t mutex;
|
||||
bool playing;
|
||||
int64_t bytes_played;
|
||||
|
||||
/* audio format info */
|
||||
unsigned int rate;
|
||||
unsigned int n_channels;
|
||||
unsigned int bytes_per_ch;
|
||||
|
||||
/* buffer list */
|
||||
sa_buf * bl_head;
|
||||
sa_buf * bl_tail;
|
||||
int n_bufs;
|
||||
};
|
||||
|
||||
/* Use a default buffer size with enough room for one second of audio,
|
||||
* assuming stereo data at 44.1kHz with 32 bits per channel, and impose
|
||||
* a generous limit on the number of buffers.
|
||||
*/
|
||||
#define BUF_SIZE (2 * 44100 * 4)
|
||||
#define BUF_LIMIT 5
|
||||
|
||||
#if BUF_LIMIT < 2
|
||||
#error BUF_LIMIT must be at least 2!
|
||||
#endif
|
||||
|
||||
static void *audio_callback(void *s);
|
||||
static sa_buf *new_buffer(void);
|
||||
static int shutdown_device(sa_stream_t *s);
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Startup and shutdown functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_create_pcm(
|
||||
sa_stream_t ** _s,
|
||||
const char * client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int n_channels
|
||||
) {
|
||||
/*
|
||||
* Make sure we return a NULL stream pointer on failure.
|
||||
*/
|
||||
if (_s == NULL) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
*_s = NULL;
|
||||
|
||||
if (mode != SA_MODE_WRONLY) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (format != SA_PCM_FORMAT_S16_NE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the instance and required resources.
|
||||
*/
|
||||
sa_stream_t *s;
|
||||
if ((s = malloc(sizeof(sa_stream_t))) == NULL) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
if ((s->bl_head = new_buffer()) == NULL) {
|
||||
free(s);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
||||
free(s->bl_head);
|
||||
free(s);
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
s->output_fd = -1;
|
||||
s->playing = false;
|
||||
s->bytes_played = 0;
|
||||
s->rate = rate;
|
||||
s->n_channels = n_channels;
|
||||
s->bytes_per_ch = 2;
|
||||
s->bl_tail = s->bl_head;
|
||||
s->n_bufs = 1;
|
||||
|
||||
*_s = s;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_open(sa_stream_t *s) {
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (s->output_fd != -1) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the default audio output unit.
|
||||
*/
|
||||
|
||||
/* If UTAUDIODEV is set, use it with Sun Audio interface */
|
||||
char * sa_device_name = getenv("UTAUDIODEV");
|
||||
char * dsp_device_name = NULL;
|
||||
if (!sa_device_name) {
|
||||
dsp_device_name = getenv("AUDIODSP");
|
||||
if (!dsp_device_name) {
|
||||
dsp_device_name = DEFAULT_DSP_DEVICE;
|
||||
}
|
||||
sa_device_name = getenv("AUDIODEV");
|
||||
if (!sa_device_name) {
|
||||
sa_device_name = DEFAULT_AUDIO_DEVICE;
|
||||
}
|
||||
}
|
||||
|
||||
int fd = -1;
|
||||
s->using_oss = false;
|
||||
/* Try to use OSS if available */
|
||||
if (dsp_device_name) {
|
||||
fd = open(dsp_device_name, O_WRONLY | O_NONBLOCK);
|
||||
if (fd >= 0) {
|
||||
s->using_oss = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try Sun Audio */
|
||||
if (!s->using_oss) {
|
||||
fd = open(sa_device_name, O_WRONLY | O_NONBLOCK);
|
||||
}
|
||||
|
||||
if (fd < 0)
|
||||
{
|
||||
printf("Open %s failed:%s.\n", sa_device_name, strerror(errno));
|
||||
return SA_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
if (s->using_oss) {
|
||||
/* set the playback rate */
|
||||
if (ioctl(fd, SNDCTL_DSP_SPEED, &(s->rate)) < 0) {
|
||||
close(fd);
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* set the channel numbers */
|
||||
if (ioctl(fd, SNDCTL_DSP_CHANNELS, &(s->n_channels)) < 0) {
|
||||
close(fd);
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
int format = AFMT_S16_NE;
|
||||
if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0) {
|
||||
close(fd);
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
s->output_fd = fd;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
audio_info_t audio_info;
|
||||
AUDIO_INITINFO(&audio_info)
|
||||
audio_info.play.sample_rate = s->rate;
|
||||
audio_info.play.channels = s->n_channels;
|
||||
audio_info.play.precision = s->bytes_per_ch * 8;
|
||||
|
||||
/* Signed Linear PCM encoding */
|
||||
audio_info.play.encoding = AUDIO_ENCODING_LINEAR;
|
||||
|
||||
if (ioctl(fd, AUDIO_SETINFO, &audio_info) == -1) {
|
||||
printf("ioctl AUDIO_SETINFO failed.\n");
|
||||
close(fd);
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
s->output_fd = fd;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_destroy(sa_stream_t *s) {
|
||||
if (s == NULL) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Join the thread.
|
||||
*/
|
||||
bool thread_created = false;
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (s->playing) {
|
||||
thread_created = true;
|
||||
s->playing = false;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
if (thread_created) {
|
||||
pthread_join(s->thread_id, NULL);
|
||||
}
|
||||
|
||||
int result = SA_SUCCESS;
|
||||
|
||||
|
||||
/*
|
||||
* Shutdown the audio output device.
|
||||
*/
|
||||
result = shutdown_device(s);
|
||||
|
||||
/*
|
||||
* Release resouces.
|
||||
*/
|
||||
if (pthread_mutex_destroy(&s->mutex) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
while (s->bl_head != NULL) {
|
||||
sa_buf * next = s->bl_head->next;
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
}
|
||||
free(s);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Data read and write functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* Append the new data to the end of our buffer list.
|
||||
*/
|
||||
int result = SA_SUCCESS;
|
||||
while (1) {
|
||||
unsigned int avail = s->bl_tail->size - s->bl_tail->end;
|
||||
|
||||
if (nbytes <= avail) {
|
||||
/*
|
||||
* The new data will fit into the current tail buffer, so
|
||||
* just copy it in and we're done.
|
||||
*/
|
||||
memcpy(s->bl_tail->data + s->bl_tail->end, data, nbytes);
|
||||
s->bl_tail->end += nbytes;
|
||||
break;
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Copy what we can into the tail and allocate a new buffer
|
||||
* for the rest.
|
||||
*/
|
||||
memcpy(s->bl_tail->data + s->bl_tail->end, data, avail);
|
||||
s->bl_tail->end += avail;
|
||||
data = ((unsigned char *)data) + avail;
|
||||
nbytes -= avail;
|
||||
|
||||
/*
|
||||
* If we still have data left to copy but we've hit the limit of
|
||||
* allowable buffer allocations, we need to spin for a bit to allow
|
||||
* the audio callback function to slurp some more data up.
|
||||
*/
|
||||
if (nbytes > 0 && s->n_bufs == BUF_LIMIT) {
|
||||
#ifdef TIMING_TRACE
|
||||
printf("#"); /* too much audio data */
|
||||
#endif
|
||||
if (!s->playing) {
|
||||
/*
|
||||
* We haven't even started playing yet! That means the
|
||||
* BUF_SIZE/BUF_LIMIT values are too low... Not much we can
|
||||
* do here; spinning won't help because the audio callback
|
||||
* hasn't been enabled yet. Oh well, error time.
|
||||
*/
|
||||
printf("Too much audio data received before audio device enabled!\n");
|
||||
result = SA_ERROR_SYSTEM;
|
||||
break;
|
||||
}
|
||||
while (s->n_bufs == BUF_LIMIT) {
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
struct timespec ts = {0, 1000000};
|
||||
nanosleep(&ts, NULL);
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new tail buffer, and go 'round again to fill it up.
|
||||
*/
|
||||
if ((s->bl_tail->next = new_buffer()) == NULL) {
|
||||
result = SA_ERROR_OOM;
|
||||
break;
|
||||
}
|
||||
s->n_bufs++;
|
||||
s->bl_tail = s->bl_tail->next;
|
||||
|
||||
} /* if (nbytes <= avail), else */
|
||||
|
||||
} /* while (1) */
|
||||
|
||||
/*
|
||||
* Once we have our first block of audio data, enable the audio callback
|
||||
* function.
|
||||
*/
|
||||
if (!s->playing) {
|
||||
s->playing = true;
|
||||
if (pthread_create(&s->thread_id, NULL, audio_callback, s) != 0) {
|
||||
result = SA_ERROR_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void *
|
||||
audio_callback(void *data) {
|
||||
sa_stream_t *s = data;
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
while (s->playing) {
|
||||
/*
|
||||
* Consume data from the start of the buffer list.
|
||||
*/
|
||||
while (s->output_fd != -1) {
|
||||
unsigned int avail = s->bl_head->end - s->bl_head->start;
|
||||
if (avail > 0) {
|
||||
int written = write(s->output_fd, s->bl_head->data + s->bl_head->start, avail);
|
||||
if (written == -1) {
|
||||
break; /* Try again later. */
|
||||
}
|
||||
s->bl_head->start += written;
|
||||
s->bytes_played += written;
|
||||
if (written < avail) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sa_buf * next = s->bl_head->next;
|
||||
if (next == NULL) {
|
||||
#ifdef TIMING_TRACE
|
||||
printf("!"); /* not enough audio data */
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
free(s->bl_head);
|
||||
s->bl_head = next;
|
||||
s->n_bufs--;
|
||||
} /* while (s->output_fd != -1) */
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
struct timespec ts = {0, 1000000};
|
||||
nanosleep(&ts, NULL);
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
} /* s->playing */
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
|
||||
/*
|
||||
* The sum of the free space in the tail buffer plus the size of any new
|
||||
* buffers represents the write space available before blocking.
|
||||
*/
|
||||
|
||||
unsigned int avail = s->bl_tail->size - s->bl_tail->end;
|
||||
avail += (BUF_LIMIT - s->n_bufs) * BUF_SIZE;
|
||||
*size = avail;
|
||||
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* General query and support functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
if (position != SA_POSITION_WRITE_SOFTWARE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
*pos = s->bytes_played;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_drain(sa_stream_t *s) {
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
sa_buf * b;
|
||||
size_t used = 0;
|
||||
for (b = s->bl_head; b != NULL; b = b->next) {
|
||||
used += b->end - b->start;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if (used == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
struct timespec ts = {0, 1000000};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_pause(sa_stream_t *s) {
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
int result = shutdown_device(s);
|
||||
if (result == SA_SUCCESS) {
|
||||
s->output_fd = -1;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
sa_stream_resume(sa_stream_t *s) {
|
||||
if (s == NULL) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
int result = sa_stream_open(s);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static sa_buf *
|
||||
new_buffer(void) {
|
||||
sa_buf * b = malloc(sizeof(sa_buf) + BUF_SIZE);
|
||||
if (b != NULL) {
|
||||
b->size = BUF_SIZE;
|
||||
b->start = 0;
|
||||
b->end = 0;
|
||||
b->next = NULL;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static int
|
||||
shutdown_device(sa_stream_t *s) {
|
||||
if (s->output_fd != -1)
|
||||
{
|
||||
/* Flush buffer. */
|
||||
if (s->using_oss) {
|
||||
ioctl(s->output_fd, SNDCTL_DSP_HALT_OUTPUT);
|
||||
} else {
|
||||
ioctl(s->output_fd, I_FLUSH);
|
||||
}
|
||||
|
||||
if (close(s->output_fd) < 0)
|
||||
{
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
}
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Extension functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (s->using_oss) {
|
||||
int mvol = ((int)(100 * vol)) | ((int)(100 * vol) << 8);
|
||||
if (ioctl(s->output_fd, SNDCTL_DSP_SETPLAYVOL, &mvol) < 0) {
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
unsigned int newVolume = (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) * vol + AUDIO_MIN_GAIN;
|
||||
|
||||
/* Check if the new volume is valid or not */
|
||||
if ( newVolume < AUDIO_MIN_GAIN || newVolume > AUDIO_MAX_GAIN )
|
||||
return SA_ERROR_INVALID;
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
audio_info_t audio_info;
|
||||
AUDIO_INITINFO(&audio_info);
|
||||
audio_info.play.gain = newVolume;
|
||||
int err = ioctl(s->output_fd, AUDIO_SETINFO, &audio_info);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if (err == -1)
|
||||
{
|
||||
perror("sa_stream_set_volume_abs failed\n");
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
sa_stream_get_volume_abs(sa_stream_t *s, float *vol) {
|
||||
if (s == NULL || s->output_fd == -1) {
|
||||
return SA_ERROR_NO_INIT;
|
||||
}
|
||||
|
||||
if (s->using_oss) {
|
||||
int mvol;
|
||||
if (ioctl(s->output_fd, SNDCTL_DSP_GETPLAYVOL, &mvol) < 0){
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
*vol = ((mvol & 0xFF) + (mvol >> 8)) / 200.0f;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
audio_info_t audio_info;
|
||||
AUDIO_INITINFO(&audio_info);
|
||||
int err = ioctl(s->output_fd, AUDIO_GETINFO, &audio_info);
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if (err == -1)
|
||||
{
|
||||
perror("sa_stream_get_volume_abs failed\n");
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
*vol = (float)((audio_info.play.gain - AUDIO_MIN_GAIN))/(AUDIO_MAX_GAIN - AUDIO_MIN_GAIN);
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
UNSUPPORTED(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))
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_min_write(sa_stream_t *s, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
const char *sa_strerror(int code) { return NULL; }
|
||||
|
@ -1,759 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "sydney_audio.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmreg.h>
|
||||
#include <mmsystem.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define BLOCK_COUNT 10
|
||||
#define DEFAULT_DEVICE_NAME "Default WAVE Device"
|
||||
#define DEFAULT_DEVICE WAVE_MAPPER
|
||||
#define BLOCK_DURATION_MS 100
|
||||
#define BYTES_PER_SAMPLE 2
|
||||
|
||||
#define VERBOSE_OUTPUT 1
|
||||
|
||||
// INFO: if you get weird compile errors make sure there is no extra chars pass '\'
|
||||
#if defined(VERBOSE_OUTPUT)
|
||||
#define WAVE_ERROR_VERBOSE(error, message) \
|
||||
switch (error) { \
|
||||
case MMSYSERR_ALLOCATED: \
|
||||
printf("[WAVE API] Device allocation error returned while executing %s\n", message); \
|
||||
break; \
|
||||
case MMSYSERR_BADDEVICEID: \
|
||||
printf("[WAVE API] Wrong device ID error returned while executing %s\n", message); \
|
||||
break; \
|
||||
case MMSYSERR_NODRIVER: \
|
||||
printf("[WAVE API] System driver not present error returned while executing %s\n", message); \
|
||||
break; \
|
||||
case MMSYSERR_INVALHANDLE: \
|
||||
printf("[WAVE API] Invalid device handle error returned while executing %s\n", message); \
|
||||
break; \
|
||||
case MMSYSERR_NOMEM: \
|
||||
printf("[WAVE API] No memory error returned while executing %s\n", message); \
|
||||
break; \
|
||||
case MMSYSERR_NOTSUPPORTED: \
|
||||
printf("[WAVE API] Not supported error returned while executing %s\n", message); \
|
||||
break; \
|
||||
case WAVERR_BADFORMAT: \
|
||||
printf("[WAVE API] Not valid audio format returned while executing %s\n", message); \
|
||||
break; \
|
||||
case WAVERR_SYNC: \
|
||||
printf("[WAVE API] Device synchronous error returned while executing %s\n", message); \
|
||||
break; \
|
||||
default: \
|
||||
printf("[WAVE API] Error while executing %s\n", message); \
|
||||
break; \
|
||||
}
|
||||
#else
|
||||
#define WAVE_ERROR_VERBOSE(error, message) \
|
||||
do {} while(0)
|
||||
#endif
|
||||
|
||||
#define HANDLE_WAVE_ERROR(status, location) \
|
||||
if (status != MMSYSERR_NOERROR) { \
|
||||
WAVE_ERROR_VERBOSE(status, location); \
|
||||
return getSAErrorCode(status); \
|
||||
}
|
||||
|
||||
#define ERROR_IF_NO_INIT(handle) \
|
||||
if (handle == NULL) { \
|
||||
return SA_ERROR_NO_INIT; \
|
||||
}
|
||||
|
||||
/* local implementation of the sa_stream_t type */
|
||||
struct sa_stream {
|
||||
char* deviceName;
|
||||
UINT device;
|
||||
UINT channels;
|
||||
UINT rate;
|
||||
|
||||
sa_mode_t rwMode;
|
||||
sa_pcm_format_t format;
|
||||
|
||||
HWAVEOUT hWaveOut;
|
||||
HANDLE callbackEvent;
|
||||
CRITICAL_SECTION waveCriticalSection;
|
||||
WAVEHDR* waveBlocks;
|
||||
volatile int waveFreeBlockCount;
|
||||
int waveCurrentBlock;
|
||||
|
||||
int playing;
|
||||
size_t blockSize;
|
||||
};
|
||||
|
||||
|
||||
/** Forward definitions of audio api specific functions */
|
||||
int allocateBlocks(int size, int count, WAVEHDR** blocks);
|
||||
int freeBlocks(WAVEHDR* blocks);
|
||||
int openAudio(sa_stream_t *s);
|
||||
int closeAudio(sa_stream_t * s);
|
||||
int writeBlock(sa_stream_t *s, WAVEHDR* current);
|
||||
int writeAudio(sa_stream_t *s, LPSTR data, int bytes);
|
||||
int getSAErrorCode(int waveErrorCode);
|
||||
|
||||
void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2);
|
||||
|
||||
/** Normal way to open a PCM device */
|
||||
int sa_stream_create_pcm(sa_stream_t **s,
|
||||
const char *client_name,
|
||||
sa_mode_t mode,
|
||||
sa_pcm_format_t format,
|
||||
unsigned int rate,
|
||||
unsigned int nchannels) {
|
||||
sa_stream_t * _s = NULL;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
*s = NULL;
|
||||
|
||||
/* FIX ME: for formats different than PCM extend using WAVEFORMATEXTENSIBLE */
|
||||
if (format != SA_PCM_FORMAT_S16_NE) {
|
||||
/* If we ever support non 16bit sound formats, we need to change the use of
|
||||
* BYTES_PER_SAMPLE in the blockSize calculation below. */
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (mode != SA_MODE_WRONLY) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if ((_s = (sa_stream_t*)calloc(1, sizeof(sa_stream_t))) == NULL) {
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
_s->rwMode = mode;
|
||||
_s->format = format;
|
||||
_s->rate = rate;
|
||||
_s->channels = nchannels;
|
||||
_s->deviceName = DEFAULT_DEVICE_NAME;
|
||||
_s->device = DEFAULT_DEVICE;
|
||||
_s->playing = 0;
|
||||
_s->blockSize = BYTES_PER_SAMPLE * nchannels * ((rate * BLOCK_DURATION_MS) / 1000);
|
||||
/* Other parts of the code assumes that the block size is evenly
|
||||
divisible by 2. */
|
||||
assert((_s->blockSize & 1) != 1);
|
||||
assert((_s->blockSize % BYTES_PER_SAMPLE) == 0);
|
||||
assert(((_s->blockSize / BYTES_PER_SAMPLE) % nchannels) == 0);
|
||||
*s = _s;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/** Initialise the device */
|
||||
int sa_stream_open(sa_stream_t *s) {
|
||||
int status = SA_SUCCESS;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
switch (s->rwMode) {
|
||||
case SA_MODE_WRONLY:
|
||||
status = openAudio(s);
|
||||
break;
|
||||
default:
|
||||
status = SA_ERROR_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int sa_stream_get_min_write(sa_stream_t *s, size_t *size) {
|
||||
ERROR_IF_NO_INIT(s);
|
||||
*size = s->blockSize;
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/** Interleaved playback function */
|
||||
int sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
|
||||
int status = SA_SUCCESS;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
status = writeAudio(s, (LPSTR)data, nbytes);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/** Query how much can be written without blocking */
|
||||
int sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
|
||||
unsigned int avail;
|
||||
WAVEHDR* current;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
EnterCriticalSection(&(s->waveCriticalSection));
|
||||
avail = (s->waveFreeBlockCount-1) * s->blockSize;
|
||||
if (s->waveFreeBlockCount != BLOCK_COUNT) {
|
||||
current = &(s->waveBlocks[s->waveCurrentBlock]);
|
||||
avail += s->blockSize - current->dwUser;
|
||||
}
|
||||
LeaveCriticalSection(&(s->waveCriticalSection));
|
||||
|
||||
*size = avail;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/** Close/destroy everything */
|
||||
int sa_stream_destroy(sa_stream_t *s) {
|
||||
int status;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
/* close and release all allocated resources */
|
||||
status = closeAudio(s);
|
||||
|
||||
free(s);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#define LEFT_CHANNEL_MASK 0x0000FFFF
|
||||
#define RIGHT_CHANNEL_MASK 0xFFFF0000
|
||||
|
||||
/**
|
||||
* retrieved volume as an int in a scale from 0x0000 to 0xFFFF
|
||||
* only one value for all channels
|
||||
*/
|
||||
int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n) {
|
||||
int status;
|
||||
DWORD volume;
|
||||
WORD left;
|
||||
WORD right;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
status = waveOutGetVolume(s->hWaveOut, &volume);
|
||||
HANDLE_WAVE_ERROR(status, "reading audio volume level");
|
||||
|
||||
left = volume & LEFT_CHANNEL_MASK;
|
||||
right = (volume & RIGHT_CHANNEL_MASK) >> 16;
|
||||
vol[0] = (int32_t)(left + right /2);
|
||||
|
||||
return SA_SUCCESS;
|
||||
|
||||
}
|
||||
|
||||
/** changes volume as an int in a scale from 0x0000 to 0xFFFF*/
|
||||
int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n) {
|
||||
int status;
|
||||
DWORD volume;
|
||||
WORD left;
|
||||
WORD right;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
volume = (DWORD)vol[0];
|
||||
left = volume & LEFT_CHANNEL_MASK;
|
||||
right = left;
|
||||
volume = (left << 16) | right;
|
||||
|
||||
status = waveOutSetVolume(s->hWaveOut, volume);
|
||||
HANDLE_WAVE_ERROR(status, "setting new audio volume level");
|
||||
|
||||
return SA_SUCCESS;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/** sync/timing */
|
||||
int sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
|
||||
int status;
|
||||
MMTIME mm;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
if (position != SA_POSITION_WRITE_HARDWARE) {
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
// request playback progress in bytes
|
||||
mm.wType = TIME_BYTES;
|
||||
status = waveOutGetPosition(s->hWaveOut, &mm, sizeof(MMTIME));
|
||||
HANDLE_WAVE_ERROR(status, "reading audio buffer position");
|
||||
*pos = (int64_t)mm.u.cb;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/* Control/xrun */
|
||||
/** Resume playing after a pause */
|
||||
int sa_stream_resume(sa_stream_t *s) {
|
||||
int status;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
status = waveOutRestart(s->hWaveOut);
|
||||
HANDLE_WAVE_ERROR(status, "resuming audio playback");
|
||||
|
||||
s->playing = 1;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
/** Pause audio playback (do not empty the buffer) */
|
||||
int sa_stream_pause(sa_stream_t *s) {
|
||||
int status;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
status = waveOutPause(s->hWaveOut);
|
||||
HANDLE_WAVE_ERROR(status, "resuming audio playback");
|
||||
|
||||
s->playing = 0;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/** Block until all audio has been played */
|
||||
int sa_stream_drain(sa_stream_t *s) {
|
||||
int status;
|
||||
WAVEHDR* current;
|
||||
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
current = &(s->waveBlocks[s->waveCurrentBlock]);
|
||||
if (current->dwUser) {
|
||||
/* We've got pending audio which hasn't been written, we must write it to
|
||||
the hardware, else it will never be played. */
|
||||
status = writeBlock(s, current);
|
||||
HANDLE_WAVE_ERROR(status, "writing audio to audio device");
|
||||
}
|
||||
|
||||
if (!s->playing) {
|
||||
return SA_ERROR_INVALID;
|
||||
}
|
||||
|
||||
/* wait for all blocks to complete */
|
||||
EnterCriticalSection(&(s->waveCriticalSection));
|
||||
while(s->waveFreeBlockCount < BLOCK_COUNT) {
|
||||
LeaveCriticalSection(&(s->waveCriticalSection));
|
||||
Sleep(10);
|
||||
EnterCriticalSection(&(s->waveCriticalSection));
|
||||
}
|
||||
LeaveCriticalSection(&(s->waveCriticalSection));
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Private WAVE API specific functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* \brief - allocate buffer for writing to system WAVE audio device
|
||||
* \param size - size of each audio block
|
||||
* \param cound - number of blocks to be allocated
|
||||
* \param blocks - pointer to the blocks buffer to be allocated
|
||||
* \return - completion status
|
||||
*/
|
||||
int allocateBlocks(int size, int count, WAVEHDR** blocks)
|
||||
{
|
||||
unsigned char* buffer;
|
||||
int i;
|
||||
WAVEHDR* headers;
|
||||
DWORD totalBufferSize = (size + sizeof(WAVEHDR)) * count;
|
||||
|
||||
/* allocate memory on heap for the entire set in one go */
|
||||
if((buffer = HeapAlloc(
|
||||
GetProcessHeap(),
|
||||
HEAP_ZERO_MEMORY,
|
||||
totalBufferSize
|
||||
)) == NULL) {
|
||||
printf("Memory allocation error\n");
|
||||
return SA_ERROR_OOM;
|
||||
}
|
||||
|
||||
/* and set up the pointers to each bit */
|
||||
headers = *blocks = (WAVEHDR*)buffer;
|
||||
buffer += sizeof(WAVEHDR) * count;
|
||||
for(i = 0; i < count; i++) {
|
||||
headers[i].dwBufferLength = size;
|
||||
headers[i].lpData = buffer;
|
||||
buffer += size;
|
||||
}
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief - free allocated audio buffer
|
||||
* \param blocks - pointer to allocated the buffer of audio bloks
|
||||
* \return - completion status
|
||||
*/
|
||||
int freeBlocks(WAVEHDR* blocks)
|
||||
{
|
||||
if (blocks == NULL)
|
||||
return SA_ERROR_INVALID;
|
||||
|
||||
/* and this is why allocateBlocks works the way it does */
|
||||
HeapFree(GetProcessHeap(), 0, blocks);
|
||||
blocks = NULL;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief - open system default WAVE device
|
||||
* \param s - sydney audio stream handle
|
||||
* \return - completion status
|
||||
*/
|
||||
int openAudio(sa_stream_t *s) {
|
||||
int status;
|
||||
WAVEFORMATEX wfx;
|
||||
UINT supported = FALSE;
|
||||
|
||||
status = allocateBlocks(s->blockSize, BLOCK_COUNT, &(s->waveBlocks));
|
||||
HANDLE_WAVE_ERROR(status, "allocating audio buffer blocks");
|
||||
|
||||
s->waveFreeBlockCount = BLOCK_COUNT;
|
||||
s->waveCurrentBlock = 0;
|
||||
wfx.nSamplesPerSec = (DWORD)s->rate; /* sample rate */
|
||||
wfx.wBitsPerSample = 16; /* sample size */
|
||||
wfx.nChannels = s->channels; /* channels */
|
||||
wfx.cbSize = 0; /* size of _extra_ info */
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nBlockAlign = (wfx.wBitsPerSample * wfx.nChannels) >> 3;
|
||||
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
|
||||
|
||||
supported = waveOutOpen(NULL, WAVE_MAPPER, &wfx, (DWORD_PTR)0, (DWORD_PTR)0,
|
||||
WAVE_FORMAT_QUERY);
|
||||
if (supported == MMSYSERR_NOERROR) { // audio device opened sucessfully
|
||||
status = waveOutOpen((LPHWAVEOUT)&(s->hWaveOut), WAVE_MAPPER, &wfx,
|
||||
(DWORD_PTR)waveOutProc, (DWORD_PTR)s, CALLBACK_FUNCTION);
|
||||
if (status != MMSYSERR_NOERROR) {
|
||||
freeBlocks(s->waveBlocks);
|
||||
s->waveBlocks = NULL;
|
||||
HANDLE_WAVE_ERROR(status, "opening audio device for playback");
|
||||
}
|
||||
}
|
||||
else if (supported == WAVERR_BADFORMAT) {
|
||||
printf("Requested format not supported.\n");
|
||||
// clean up the memory
|
||||
freeBlocks(s->waveBlocks);
|
||||
s->waveBlocks = NULL;
|
||||
return SA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
else {
|
||||
printf("Error opening default audio device.\n");
|
||||
// clean up the memory
|
||||
freeBlocks(s->waveBlocks);
|
||||
s->waveBlocks = NULL;
|
||||
return SA_ERROR_SYSTEM;
|
||||
}
|
||||
// create notification for data written to a device
|
||||
s->callbackEvent = CreateEvent(0, FALSE, FALSE, 0);
|
||||
// initialise critical section for operations on waveFreeBlockCound variable
|
||||
InitializeCriticalSection(&(s->waveCriticalSection));
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
/**
|
||||
* \brief - closes opened audio device handle
|
||||
* \param s - sydney audio stream handle
|
||||
* \return - completion status
|
||||
*/
|
||||
int closeAudio(sa_stream_t * s) {
|
||||
int status, i, result;
|
||||
|
||||
result = SA_SUCCESS;
|
||||
|
||||
// reseting audio device and flushing buffers
|
||||
status = waveOutReset(s->hWaveOut);
|
||||
if (status != MMSYSERR_NOERROR) {
|
||||
result = getSAErrorCode(status);
|
||||
}
|
||||
|
||||
if (s->waveBlocks) {
|
||||
/* wait for all blocks to complete */
|
||||
while(s->waveFreeBlockCount < BLOCK_COUNT) {
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
/* unprepare any blocks that are still prepared */
|
||||
for(i = 0; i < s->waveFreeBlockCount; i++) {
|
||||
if(s->waveBlocks[i].dwFlags & WHDR_PREPARED) {
|
||||
status = waveOutUnprepareHeader(s->hWaveOut, &(s->waveBlocks[i]), sizeof(WAVEHDR));
|
||||
if (status != MMSYSERR_NOERROR) {
|
||||
result = getSAErrorCode(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeBlocks(s->waveBlocks);
|
||||
s->waveBlocks = NULL;
|
||||
}
|
||||
|
||||
status = waveOutClose(s->hWaveOut);
|
||||
if (status != MMSYSERR_NOERROR) {
|
||||
result = getSAErrorCode(status);
|
||||
}
|
||||
|
||||
s->playing = 0;
|
||||
|
||||
DeleteCriticalSection(&(s->waveCriticalSection));
|
||||
CloseHandle(s->callbackEvent);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief - writes a WAVEHDR block of PCM audio samples to hardware.
|
||||
* \param s - valid handle to opened sydney stream
|
||||
* \param current - pointer to WAVEHDR storing audio samples to be played
|
||||
* \return - completion status
|
||||
*/
|
||||
int writeBlock(sa_stream_t *s, WAVEHDR* current) {
|
||||
int status;
|
||||
ERROR_IF_NO_INIT(s);
|
||||
|
||||
current->dwBufferLength = current->dwUser;
|
||||
/* write to audio device */
|
||||
waveOutPrepareHeader(s->hWaveOut, current, sizeof(WAVEHDR));
|
||||
status = waveOutWrite(s->hWaveOut, current, sizeof(WAVEHDR));
|
||||
HANDLE_WAVE_ERROR(status, "writing audio to audio device");
|
||||
|
||||
EnterCriticalSection(&(s->waveCriticalSection));
|
||||
s->waveFreeBlockCount--;
|
||||
LeaveCriticalSection(&(s->waveCriticalSection));
|
||||
|
||||
/*
|
||||
* point to the next block
|
||||
*/
|
||||
(s->waveCurrentBlock)++;
|
||||
(s->waveCurrentBlock) %= BLOCK_COUNT;
|
||||
|
||||
s->playing = 1;
|
||||
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief - writes PCM audio samples to audio device
|
||||
* \param s - valid handle to opened sydney stream
|
||||
* \param data - pointer to memory storing audio samples to be played
|
||||
* \param nsamples - number of samples in the memory pointed by previous parameter
|
||||
* \return - completion status
|
||||
*/
|
||||
int writeAudio(sa_stream_t *s, LPSTR data, int bytes) {
|
||||
UINT status;
|
||||
WAVEHDR* current;
|
||||
int remain;
|
||||
|
||||
current = &(s->waveBlocks[s->waveCurrentBlock]);
|
||||
|
||||
while(bytes > 0) {
|
||||
/*
|
||||
* wait for a block to become free
|
||||
*/
|
||||
while (!(s->waveFreeBlockCount))
|
||||
WaitForSingleObject(s->callbackEvent, INFINITE);
|
||||
|
||||
/* first make sure the header we're going to use is unprepared */
|
||||
if(current->dwFlags & WHDR_PREPARED) {
|
||||
status = waveOutUnprepareHeader(s->hWaveOut, current, sizeof(WAVEHDR));
|
||||
HANDLE_WAVE_ERROR(status, "preparing audio headers for writing");
|
||||
}
|
||||
|
||||
if(bytes < (int)(s->blockSize - current->dwUser)) {
|
||||
memcpy(current->lpData + current->dwUser, data, bytes);
|
||||
current->dwUser += bytes;
|
||||
break;
|
||||
}
|
||||
|
||||
/* remain is even as s->blockSize and dwUser are even too */
|
||||
remain = s->blockSize - current->dwUser;
|
||||
memcpy(current->lpData + current->dwUser, data, remain);
|
||||
current->dwUser += remain;
|
||||
bytes -= remain;
|
||||
data += remain;
|
||||
|
||||
status = writeBlock(s, current);
|
||||
HANDLE_WAVE_ERROR(status, "writing audio to audio device");
|
||||
|
||||
current = &(s->waveBlocks[s->waveCurrentBlock]);
|
||||
current->dwUser = 0;
|
||||
}
|
||||
return SA_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief - audio callback function called when next WAVE header is played by audio device
|
||||
*/
|
||||
void CALLBACK waveOutProc(HWAVEOUT hWaveOut,
|
||||
UINT uMsg,
|
||||
DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1,
|
||||
DWORD_PTR dwParam2)
|
||||
{
|
||||
/*
|
||||
* pointer to free block counter
|
||||
*/
|
||||
sa_stream_t* handle = (sa_stream_t*)dwInstance;
|
||||
/*
|
||||
* ignore calls that occur due to openining and closing the
|
||||
* device.
|
||||
*/
|
||||
if(uMsg != WOM_DONE)
|
||||
return;
|
||||
|
||||
EnterCriticalSection(&(handle->waveCriticalSection));
|
||||
(handle->waveFreeBlockCount)++;
|
||||
if ((handle->waveFreeBlockCount) == 1)
|
||||
SetEvent(handle->callbackEvent);
|
||||
LeaveCriticalSection(&(handle->waveCriticalSection));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief - converts frequently reported WAVE error codes to Sydney audio API codes
|
||||
*/
|
||||
int getSAErrorCode(int waveErrorCode) {
|
||||
int error = SA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
switch (waveErrorCode) {
|
||||
case MMSYSERR_NOERROR:
|
||||
error = SA_SUCCESS;
|
||||
break;
|
||||
case MMSYSERR_ALLOCATED:
|
||||
error = SA_ERROR_SYSTEM;
|
||||
break;
|
||||
case MMSYSERR_BADDEVICEID:
|
||||
error = SA_ERROR_INVALID;
|
||||
break;
|
||||
case MMSYSERR_NODRIVER:
|
||||
error = SA_ERROR_NO_DRIVER;
|
||||
break;
|
||||
case MMSYSERR_NOTSUPPORTED:
|
||||
error = SA_ERROR_NOT_SUPPORTED;
|
||||
break;
|
||||
case MMSYSERR_NOMEM:
|
||||
error = SA_ERROR_OOM;
|
||||
break;
|
||||
case MMSYSERR_INVALHANDLE:
|
||||
error = SA_ERROR_INVALID;
|
||||
break;
|
||||
case WAVERR_BADFORMAT:
|
||||
error = SA_ERROR_NOT_SUPPORTED;
|
||||
break;
|
||||
case WAVERR_SYNC:
|
||||
error = SA_ERROR_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Functions to be implemented next
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#define NOT_IMPLEMENTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
/* "Soft" params */
|
||||
NOT_IMPLEMENTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
|
||||
NOT_IMPLEMENTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
|
||||
NOT_IMPLEMENTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
||||
NOT_IMPLEMENTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
||||
|
||||
/** Set the mapping between channels and the loudspeakers */
|
||||
NOT_IMPLEMENTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
||||
|
||||
/* Query functions */
|
||||
NOT_IMPLEMENTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
||||
NOT_IMPLEMENTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
||||
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Unsupported functions
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
#define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
|
||||
|
||||
/** Create an opaque (e.g. AC3) codec stream */
|
||||
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
|
||||
/** Whether xruns cause the card to reset */
|
||||
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
||||
/** Set the device to non-interleaved mode */
|
||||
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
||||
/** Require dynamic sample rate */
|
||||
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
||||
/** Select driver */
|
||||
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
||||
/** Start callback */
|
||||
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
||||
/** Stop callback */
|
||||
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
||||
/** Change the device connected to the stream */
|
||||
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
||||
/** volume in hundreths of dB*/
|
||||
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
||||
/** Change the sampling rate */
|
||||
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
||||
/** Change some meta data that is attached to the stream */
|
||||
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
||||
/** Associate opaque user data */
|
||||
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
||||
/* Hardware-related. This is implementation-specific and hardware specific. */
|
||||
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
||||
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
||||
/* Query functions */
|
||||
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
||||
|
||||
UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
||||
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
||||
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
||||
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
||||
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
||||
/** Get current state of the audio device */
|
||||
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
||||
/** Obtain the error code */
|
||||
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
||||
/** Obtain the notification code */
|
||||
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
||||
|
||||
/* Blocking IO calls */
|
||||
/** Interleaved capture function */
|
||||
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
||||
/** Non-interleaved capture function */
|
||||
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
||||
|
||||
/** Non-interleaved playback function */
|
||||
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
||||
/** Interleaved playback function with seek offset */
|
||||
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
||||
/** Non-interleaved playback function with seek offset */
|
||||
UNSUPPORTED(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))
|
||||
|
||||
/** Query how much can be read without blocking */
|
||||
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
||||
|
||||
UNSUPPORTED(int sa_stream_set_stream_type(sa_stream_t *s, const sa_stream_type_t stream_type))
|
||||
|
||||
/** Return a human readable error */
|
||||
const char *sa_strerror(int code);
|
@ -24,7 +24,7 @@ LIBS = \
|
||||
$(DEPTH)/netwerk/srtp/src/$(LIB_PREFIX)nksrtp_s.$(LIB_SUFFIX) \
|
||||
$(NULL)
|
||||
|
||||
ifneq (,$(MOZ_CUBEB)$(MOZ_SYDNEYAUDIO))
|
||||
ifdef MOZ_CUBEB
|
||||
ifdef MOZ_ALSA
|
||||
LIBS += \
|
||||
$(MOZ_ALSA_LIBS) \
|
||||
|
@ -425,7 +425,7 @@ endif
|
||||
endif
|
||||
|
||||
|
||||
ifneq (,$(MOZ_CUBEB)$(MOZ_SYDNEYAUDIO))
|
||||
ifdef MOZ_CUBEB
|
||||
ifdef MOZ_ALSA
|
||||
EXTRA_DSO_LDOPTS += $(MOZ_ALSA_LIBS)
|
||||
endif
|
||||
@ -519,27 +519,6 @@ OS_LIBS += \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),Darwin)
|
||||
ifdef MOZ_SYDNEYAUDIO
|
||||
OS_LIBS += \
|
||||
-framework Carbon \
|
||||
-framework CoreAudio \
|
||||
-framework AudioToolbox \
|
||||
-framework AudioUnit \
|
||||
-framework IOKit \
|
||||
-framework Foundation \
|
||||
-framework AppKit \
|
||||
-framework Security \
|
||||
$(NULL)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter NetBSD OpenBSD,$(OS_ARCH)))
|
||||
ifdef MOZ_SYDNEYAUDIO
|
||||
EXTRA_DSO_LDOPTS += -lossaudio
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq (OpenBSD,$(OS_ARCH))
|
||||
ifdef MOZ_CUBEB
|
||||
EXTRA_DSO_LDOPTS += -lsndio
|
||||
|
@ -85,9 +85,6 @@ if CONFIG['MOZ_VP8'] and not CONFIG['MOZ_NATIVE_LIBVPX']:
|
||||
if CONFIG['MOZ_OGG']:
|
||||
add_tier_dir('platform', ['media/libogg', 'media/libtheora'])
|
||||
|
||||
if CONFIG['MOZ_SYDNEYAUDIO']:
|
||||
add_tier_dir('platform', 'media/libsydneyaudio')
|
||||
|
||||
if CONFIG['MOZ_WEBRTC']:
|
||||
add_tier_dir('platform', [
|
||||
'media/webrtc',
|
||||
|
Loading…
Reference in New Issue
Block a user