From 9d0daf68218be2a6e44b45b29294e96b761773e2 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Tue, 8 Sep 2009 22:06:07 +0000 Subject: [PATCH] Add OSS audio player Tested on OSS4 (4front), FreeBSD 7.2 OSS and with ALSA's OSS emulation. Bear with me, this is both my first serious C++ and OSS code. Originally committed to SVN as r3497. --- aegisub/configure.in | 23 +++ aegisub/src/Makefile.am | 7 + aegisub/src/audio_player.cpp | 6 + aegisub/src/audio_player_oss.cpp | 334 +++++++++++++++++++++++++++++++ aegisub/src/audio_player_oss.h | 173 ++++++++++++++++ aegisub/src/options.cpp | 1 + 6 files changed, 544 insertions(+) create mode 100644 aegisub/src/audio_player_oss.cpp create mode 100644 aegisub/src/audio_player_oss.h diff --git a/aegisub/configure.in b/aegisub/configure.in index c1afb0d2b..970c5b695 100644 --- a/aegisub/configure.in +++ b/aegisub/configure.in @@ -573,6 +573,28 @@ fi AM_CONDITIONAL([HAVE_OPENAL], [test "$with_openal" != "no"]) +###### +## OSS +###### +AC_ARG_WITH(oss,[ --without-oss build without OSS audio provider. + (default: auto)], oss_disabled="(disabled)") +if test "$with_oss" != "no"; then + if test -f "/etc/oss.conf"; then + . /etc/oss.conf + CPPFLAGS="$CPPFLAGS -I${OSSLIBDIR}/include/sys" + fi + AC_CHECK_HEADERS([soundcard.h sys/soundcard.h], [with_oss=yes], [with_oss=no]) + # XXX: maybe check if OSS works +fi + +if test "$with_oss" != "no"; then + found_audio_player="yes" + AC_DEFINE(WITH_OSS, 1, [Enable OSS support]) +fi + +AM_CONDITIONAL([HAVE_OSS], [test "$with_oss" != "no"]) + + ######################### # Video / Audio Providers ######################### @@ -1206,6 +1228,7 @@ Scripting Engines Audio Players ALSA: $with_alsa $alsa_disabled OpenAL: $with_openal $openal_disabled + OSS: $with_oss $oss_disabled PortAudio: $with_portaudio $portaudio_disabled PulseAudio: $with_pulseaudio $pulseaudio_disabled diff --git a/aegisub/src/Makefile.am b/aegisub/src/Makefile.am index af077a678..a9e5ede77 100644 --- a/aegisub/src/Makefile.am +++ b/aegisub/src/Makefile.am @@ -75,6 +75,12 @@ aegisub_2_2_LDFLAGS += @OPENAL_LIBS@ aegisub_2_2_LDADD += libaudio_openal.a endif +if HAVE_OSS +noinst_LIBRARIES += libaudio_oss.a +libaudio_oss_a_SOURCES = audio_player_oss.cpp +aegisub_2_2_LDADD += libaudio_oss.a +endif + if HAVE_FFMPEG aegisub_2_2_LDFLAGS += @LIBAVFORMAT_LIBS@ @LIBAVCODEC_LIBS@ @LIBSWSCALE_LIBS@ @LIBAVUTIL_LIBS@ endif @@ -153,6 +159,7 @@ EXTRA_aegisub_2_2_SOURCES = \ audio_player_dsound2.cpp \ audio_player_portaudio.cpp \ audio_player_pulse.cpp \ + audio_player_oss.cpp \ audio_provider_avs.cpp \ auto4_lua.cpp \ auto4_lua_assfile.cpp \ diff --git a/aegisub/src/audio_player.cpp b/aegisub/src/audio_player.cpp index 7cc35e0e2..db5e2d9a8 100644 --- a/aegisub/src/audio_player.cpp +++ b/aegisub/src/audio_player.cpp @@ -58,6 +58,9 @@ #ifdef WITH_PULSEAUDIO #include "audio_player_pulse.h" #endif +#ifdef WITH_OSS +#include "audio_player_oss.h" +#endif @@ -191,6 +194,9 @@ void AudioPlayerFactoryManager::RegisterProviders() { #ifdef WITH_PULSEAUDIO RegisterFactory(new PulseAudioPlayerFactory(),_T("PulseAudio")); #endif +#ifdef WITH_OSS + RegisterFactory(new OSSPlayerFactory(),_T("OSS")); +#endif } diff --git a/aegisub/src/audio_player_oss.cpp b/aegisub/src/audio_player_oss.cpp new file mode 100644 index 000000000..4db315160 --- /dev/null +++ b/aegisub/src/audio_player_oss.cpp @@ -0,0 +1,334 @@ +// Copyright (c) 2009, Grigori Goronzy +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_player_oss.cpp +/// @brief Open Sound System audio output +/// @ingroup audio_output +/// + + +#include "config.h" + +#ifdef WITH_OSS + +/////////// +// Headers +#include +#include "audio_player_manager.h" +#include "audio_provider_manager.h" +#include "utils.h" +#include "main.h" +#include "frame_main.h" +#include "audio_player_oss.h" +#include "options.h" + + + +/// @brief Constructor +/// +OSSPlayer::OSSPlayer() +{ + volume = 1.0f; + open = false; + playing = false; + start_frame = cur_frame = end_frame = bpf = 0; + provider = 0; + thread = 0; +} + + + +/// @brief Destructor +/// +OSSPlayer::~OSSPlayer() +{ + CloseStream(); +} + + + +/// @brief Open stream +/// +void OSSPlayer::OpenStream() +{ + CloseStream(); + + // Get provider + provider = GetProvider(); + bpf = provider->GetChannels() * provider->GetBytesPerSample(); + + // Open device + wxString device = Options.AsText(_T("Audio OSS Device")); + dspdev = ::open(device, O_WRONLY, 0); + if (dspdev < 0) { + throw _T("OSS player: opening device failed"); + } + + // Set number of channels + int channels = provider->GetChannels(); + if (ioctl(dspdev, SNDCTL_DSP_CHANNELS, &channels) < 0) { + throw _T("OSS player: setting channels failed"); + } + + // Set sample format + int sample_format; + switch (provider->GetBytesPerSample()) { + case 1: + sample_format = AFMT_S8; + break; + case 2: + sample_format = AFMT_S16_LE; + break; + default: + throw _T("OSS player: can only handle 8 and 16 bit sound"); + } + + if (ioctl(dspdev, SNDCTL_DSP_SETFMT, &sample_format) < 0) { + throw _T("OSS player: setting sample format failed"); + } + + // Set sample rate + rate = provider->GetSampleRate(); + if (ioctl(dspdev, SNDCTL_DSP_SPEED, &rate) < 0) { + throw("OSS player: setting samplerate failed"); + } + + // Now ready + open = true; +} + + + +/// @brief Close stream +/// @return +/// +void OSSPlayer::CloseStream() +{ + if (!open) return; + + Stop(); + ::close(dspdev); + + // No longer working + open = false; +} + + + +/// @brief Play +/// @param start +/// @param count +/// +void OSSPlayer::Play(int64_t start, int64_t count) +{ + Stop(); + + start_frame = cur_frame = start; + end_frame = start + count; + + thread = new OSSPlayerThread(this); + thread->Create(); + thread->Run(); + + // Update timer + if (displayTimer && !displayTimer->IsRunning()) displayTimer->Start(15); + playing = true; +} + + + +/// @brief Stop +/// @param timerToo +/// @return +/// +void OSSPlayer::Stop(bool timerToo) +{ + if (!open) return; + if (!playing) return; + + // Stop the thread + if (thread) { + if (thread->IsAlive()) { + thread->Delete(); + } + thread->Wait(); + delete thread; + } + + // errors can be ignored here + ioctl(dspdev, SNDCTL_DSP_RESET, NULL); + + // Reset data + playing = false; + start_frame = 0; + cur_frame = 0; + end_frame = 0; + + // Stop timer + if (timerToo && displayTimer) { + displayTimer->Stop(); + } +} + + + +/// @brief DOCME +/// @return +/// +bool OSSPlayer::IsPlaying() +{ + return playing; +} + + + +/// @brief Set end +/// @param pos +/// +void OSSPlayer::SetEndPosition(int64_t pos) +{ + end_frame = pos; +} + + + +/// @brief Set current position +/// @param pos +/// +void OSSPlayer::SetCurrentPosition(int64_t pos) +{ + cur_frame = start_frame = pos; +} + + + +/// @brief DOCME +/// @return +/// +int64_t OSSPlayer::GetStartPosition() +{ + return start_frame; +} + + + +/// @brief DOCME +/// @return +/// +int64_t OSSPlayer::GetEndPosition() +{ + return end_frame; +} + + + +/// @brief Get current position +/// @return +/// +int64_t OSSPlayer::GetCurrentPosition() +{ + if (!playing) + return 0; + +#ifdef SNDCTL_DSP_CURRENT_OPTR + // OSS4 + long played_frames = 0; + oss_count_t pos; + if (ioctl(dspdev, SNDCTL_DSP_CURRENT_OPTR, &pos) >= 0) { + // XXX: Apparently the semantics are different on FreeBSD... +#ifdef __FREEBSD__ + played_frames = MAX(0, pos.samples - pos.fifo_samples); +#else + played_frames = pos.samples + pos.fifo_samples; +#endif + wxLogDebug("OSS player: played_frames %d fifo %d", played_frames, + pos.fifo_samples); + if (start_frame + played_frames >= end_frame) { + if (displayTimer) + displayTimer->Stop(); + } + return start_frame + played_frames; + } +#endif + + // Fallback for old OSS versions + int delay = 0; + if (ioctl(dspdev, SNDCTL_DSP_GETODELAY, &delay) >= 0) { + delay /= bpf; + wxLogDebug("OSS player: cur_frame %d delay %d", cur_frame, delay); + // delay can jitter a bit at the end, detect that + if (cur_frame == end_frame && delay < rate / 20) { + if (displayTimer) + displayTimer->Stop(); + return cur_frame; + } + return MAX(0, (long) cur_frame - delay); + } + + // Maybe this still didn't work... + // Return the last written frame, timing will suffer + return cur_frame; +} + + + +/// @brief Thread constructor +/// @param par +/// +OSSPlayerThread::OSSPlayerThread(OSSPlayer *par) : wxThread(wxTHREAD_JOINABLE) +{ + parent = par; +} + +/// @brief Thread entry point +/// @return +/// +wxThread::ExitCode OSSPlayerThread::Entry() { + // Use small enough writes for good timing accuracy with all + // timing methods. + const int wsize = parent->rate / 25; + void *buf = malloc(wsize * parent->bpf); + + while (!TestDestroy() && parent->cur_frame < parent->end_frame) { + int rsize = MIN(wsize, parent->end_frame - parent->cur_frame); + parent->provider->GetAudioWithVolume(buf, parent->cur_frame, + rsize, parent->volume); + int written = ::write(parent->dspdev, buf, rsize * parent->bpf); + parent->cur_frame += written / parent->bpf; + } + free(buf); + parent->cur_frame = parent->end_frame; + + wxLogDebug(_T("OSS player thread dead")); + return 0; +} + + + +#endif // WITH_OSS diff --git a/aegisub/src/audio_player_oss.h b/aegisub/src/audio_player_oss.h new file mode 100644 index 000000000..ccf9398d7 --- /dev/null +++ b/aegisub/src/audio_player_oss.h @@ -0,0 +1,173 @@ +// Copyright (c) 2009, Grigori Goronzy +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_player_oss.h +/// @see audio_player_oss.cpp +/// @ingroup audio_output +/// + +#include "config.h" + +#ifdef WITH_OSS + + +/////////// +// Headers +#include +#include +#include +#ifdef HAVE_SOUNDCARD_H +# include +#else +# ifdef HAVE_SYS_SOUNDCARD_H +# include +# endif +#endif +#include "include/aegisub/audio_player.h" +#include "include/aegisub/audio_provider.h" +#include "utils.h" +#include "main.h" +#include "frame_main.h" +#include "options.h" + + +////////////// +// Prototypes +class OSSPlayer; + + + +/// DOCME +/// @class OSSPlayerThread +/// @brief DOCME +/// +/// DOCME +class OSSPlayerThread : public wxThread { +private: + + /// DOCME + OSSPlayer *parent; + +public: + OSSPlayerThread(OSSPlayer *parent); + + wxThread::ExitCode Entry(); +}; + + + +/// DOCME +/// @class OSSPlayer +/// @brief DOCME +/// +/// DOCME +class OSSPlayer : public AudioPlayer { +private: + friend class OSSPlayerThread; + + /// DOCME + bool open; + + /// DOCME + unsigned int rate; // sample rate of audio + + OSSPlayerThread *thread; + + /// DOCME + AudioProvider *provider; + + /// DOCME + volatile bool playing; + + /// DOCME + volatile float volume; + + /// DOCME + volatile unsigned long start_frame; // first frame of playback + + /// DOCME + volatile unsigned long cur_frame; // last written frame + 1 + + /// DOCME + volatile unsigned long end_frame; // last frame to play + + /// DOCME + unsigned long bpf; // bytes per frame + + // OSS audio device handle + volatile int dspdev; + +public: + OSSPlayer(); + ~OSSPlayer(); + + void OpenStream(); + void CloseStream(); + + void Play(int64_t start, int64_t count); + void Stop(bool timerToo=true); + bool IsPlaying(); + + int64_t GetStartPosition(); + int64_t GetEndPosition(); + int64_t GetCurrentPosition(); + void SetEndPosition(int64_t pos); + void SetCurrentPosition(int64_t pos); + + /// @brief DOCME + /// @param vol + /// @return + /// + void SetVolume(double vol) { volume = vol; } + + /// @brief DOCME + /// @return + /// + double GetVolume() { return volume; } +}; + + + + +/// DOCME +/// @class OSSPlayerFactory +/// @brief DOCME +/// +/// DOCME +class OSSPlayerFactory : public AudioPlayerFactory { +public: + + /// @brief DOCME + /// + AudioPlayer *CreatePlayer() { return new OSSPlayer(); } +}; + +#endif diff --git a/aegisub/src/options.cpp b/aegisub/src/options.cpp index b7864f6f2..ad66b106c 100644 --- a/aegisub/src/options.cpp +++ b/aegisub/src/options.cpp @@ -213,6 +213,7 @@ void OptionsManager::LoadDefaults(bool onlyDefaults,bool doOverride) { SetText(_T("Audio Downmixer"),_T("ConvertToMono"),1700); SetText(_T("Audio Alsa Device"), _T("default")); SetInt(_T("Audio PortAudio Device"), -1); + SetText(_T("Audio OSS Device"), _T("/dev/dsp")); SetText(_T("Audio HD Cache Location"),_T("default"),1700); SetText(_T("Audio HD Cache Name"),_T("audio%02i.tmp"),1700); SetBool(_T("Audio Disable PCM Provider"), false);