Skip to content

Commit

Permalink
Mac: Add CoreAudio player
Browse files Browse the repository at this point in the history
  • Loading branch information
IbarakiKasen committed May 15, 2022
1 parent 5b0d015 commit aa1e63f
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 5 deletions.
18 changes: 16 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ target_include_directories(luajit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/vendor/lu
if(WIN32)
target_sources(luajit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/vendor/luajit/src/lj_vm.obj")
else()
enable_language(ASM-ATT)
enable_language(ASM)
target_sources(luajit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/vendor/luajit/src/lj_vm.s")
target_link_libraries(luajit ${CMAKE_DL_LIBS})
endif()
Expand Down Expand Up @@ -646,6 +646,17 @@ else()
set(WITH_XAUDIO2 OFF)
endif()

if(APPLE)
option(WITH_COREAUDIO "Enable CoreAudio support" ON)
if(WITH_COREAUDIO)
target_compile_definitions(Aegisub PRIVATE "WITH_COREAUDIO")
target_link_libraries(Aegisub PRIVATE "-framework AudioToolbox")
target_sources(Aegisub PRIVATE src/audio_player_coreaudio.cpp)
endif()
else()
set(WITH_COREAUDIO OFF)
endif()

find_package(FFMS2)
option(WITH_FFMS2 "Enable FFMS2 support" ${FFMS2_FOUND})
if(WITH_FFMS2)
Expand Down Expand Up @@ -765,6 +776,8 @@ else()
set(DEFAULT_PLAYER_AUDIO "PulseAudio" CACHE STRING "Default audio player")
elseif(WITH_ALSA)
set(DEFAULT_PLAYER_AUDIO "ALSA" CACHE STRING "Default audio player")
elseif(WITH_COREAUDIO)
set(DEFAULT_PLAYER_AUDIO "CoreAudio" CACHE STRING "Default audio player")
elseif(WITH_OPENAL)
set(DEFAULT_PLAYER_AUDIO "OpenAL" CACHE STRING "Default audio player")
elseif(WITH_PORTAUDIO)
Expand Down Expand Up @@ -847,13 +860,14 @@ message(STATUS "\n"
"\n"
"Audio Players\n"
" ALSA: ${WITH_ALSA}\n"
" CoreAudio: ${WITH_COREAUDIO}\n"
" DirectSound: ${WITH_DIRECTSOUND}\n"
" DirectSound-old: ${WITH_DIRECTSOUND}\n"
" XAudio2: ${WITH_XAUDIO2}\n"
" OpenAL: ${WITH_OPENAL}\n"
" OSS: ${WITH_OSS}\n"
" PortAudio: ${WITH_PORTAUDIO}\n"
" PulseAudio: ${WITH_LIBPULSE}\n"
" XAudio2: ${WITH_XAUDIO2}\n"
"\n"
"Misc Packages\n"
" AviSynth: ${WITH_AVISYNTH}\n"
Expand Down
1 change: 1 addition & 0 deletions Makefile.inc.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ HAVE_ALSA = @with_alsa@
HAVE_FFMS2 = @with_ffms2@
HAVE_HUNSPELL = @with_hunspell@
HAVE_LIBPULSE = @with_libpulse@
HAVE_COREAUDIO = @with_coreaudio@
HAVE_OPENAL = @with_openal@
HAVE_OSS = @with_oss@
HAVE_PORTAUDIO = @with_portaudio@
Expand Down
25 changes: 22 additions & 3 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,23 @@ AC_SUBST(ALSA_CFLAGS)
AC_SUBST(ALSA_LIBS)
AC_SUBST(with_alsa)


#######
## CoreAudio
#######
AC_ARG_WITH(coreaudio,
AS_HELP_STRING([--without-coreaudio],
[build without CoreAudio audio player [auto]]))
AS_IF([test x$build_darwin = xyes], [
COREAUDIO_LIBS="-framework AudioToolbox"
AC_SUBST(COREAUDIO_LIBS)
with_coreaudio="yes"
], [with_coreaudio="no"])

AS_IF([test x$with_coreaudio = xyes], AC_DEFINE(WITH_COREAUDIO, 1, [Enable CoreAudio Support]))

AC_SUBST(with_coreaudio)

#########
## OpenAL
#########
Expand Down Expand Up @@ -532,16 +549,17 @@ AC_DEFINE_UNQUOTED([UPDATE_CHECKER_BASE_URL], ["$with_update_url"],
# it above.
####################################################################
AC_ARG_WITH(player-audio,
AS_HELP_STRING([--with-player-audio=(ALSA|OpenAL|PortAudio|PulseAudio|OSS)],
[Default Audio Player [Linux/ALSA, Darwin/OpenAL, 1:*/OSS, 2:*/PortAudio]]))
AS_HELP_STRING([--with-player-audio=(ALSA|CoreAudio|OpenAL|PortAudio|PulseAudio|OSS)],
[Default Audio Player [Linux/ALSA, Darwin/CoreAudio, 1:*/OSS, 2:*/PortAudio]]))

# Default audio player.
AS_IF([test -z "$with_player_audio"], [
AS_IF([test x$build_linux = xyes && test x$with_alsa = xyes], [DEFAULT_PLAYER_AUDIO="ALSA"],
[test x$build_darwin = xyes && test x$with_openal = xyes], [DEFAULT_PLAYER_AUDIO="OpenAL"],
[test x$build_darwin = xyes && test x$with_coreaudio = xyes], [DEFAULT_PLAYER_AUDIO="CoreAudio"],
[test x$with_portaudio = xyes], [DEFAULT_PLAYER_AUDIO="PortAudio"],
[test x$with_oss = xyes], [DEFAULT_PLAYER_AUDIO="OSS"],
[test x$with_alsa = xyes], [DEFAULT_PLAYER_AUDIO="ALSA"],
[test x$with_coreaudio = xyes], [DEFAULT_PLAYER_AUDIO="CoreAudio"],
[test x$with_openal = xyes], [DEFAULT_PLAYER_AUDIO="OpenAL"],
[test x$with_libpulse = xyes], [DEFAULT_PLAYER_AUDIO="PulseAudio"])],
[DEFAULT_PLAYER_AUDIO="$with_player_audio"])
Expand Down Expand Up @@ -610,6 +628,7 @@ Default Settings
Audio Players
ALSA: $with_alsa $alsa_disabled
CoreAudio: $with_coreaudio
OpenAL: $with_openal $openal_disabled
OSS: $with_oss $oss_disabled
PortAudio: $with_portaudio $portaudio_disabled
Expand Down
6 changes: 6 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ endif
$(d)audio_player_alsa.o_FLAGS := $(CFLAGS_ALSA)
$(d)audio_player_portaudio.o_FLAGS := $(CFLAGS_PORTAUDIO)
$(d)audio_player_pulse.o_FLAGS := $(CFLAGS_LIBPULSE)
$(d)audio_player_coreaudio.o_FLAGS := $(CFLAGS_COREAUDIO)
$(d)audio_player_openal.o_FLAGS := $(CFLAGS_OPENAL)
$(d)audio_player_oss.o_FLAGS := $(CFLAGS_OSS)

Expand All @@ -150,6 +151,11 @@ src_LIBS += $(LIBS_LIBPULSE)
src_OBJ += $(d)audio_player_pulse.o
endif

ifeq (yes, $(HAVE_COREAUDIO))
src_LIBS += $(LIBS_COREAUDIO)
src_OBJ += $(d)audio_player_coreaudio.o
endif

ifeq (yes, $(HAVE_OPENAL))
src_LIBS += $(LIBS_OPENAL)
src_OBJ += $(d)audio_player_openal.o
Expand Down
6 changes: 6 additions & 0 deletions src/audio_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ std::unique_ptr<AudioPlayer> CreateXAudio2Player(agi::AudioProvider* providers,
#ifdef WITH_OPENAL
std::unique_ptr<AudioPlayer> CreateOpenALPlayer(agi::AudioProvider *providers, wxWindow *window);
#endif
#ifdef WITH_COREAUDIO
std::unique_ptr<AudioPlayer> CreateCoreAudioPlayer(agi::AudioProvider *providers, wxWindow *window);
#endif
#ifdef WITH_PORTAUDIO
std::unique_ptr<AudioPlayer> CreatePortAudioPlayer(agi::AudioProvider *providers, wxWindow *window);
#endif
Expand Down Expand Up @@ -84,6 +87,9 @@ namespace {
#ifdef WITH_OPENAL
{"OpenAL", CreateOpenALPlayer, false},
#endif
#ifdef WITH_COREAUDIO
{"CoreAudio", CreateCoreAudioPlayer, false},
#endif
#ifdef WITH_PORTAUDIO
{"PortAudio", CreatePortAudioPlayer, false},
#endif
Expand Down
215 changes: 215 additions & 0 deletions src/audio_player_coreaudio.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// Copyright (c) 2022, Qirui Wang
// 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/

#ifdef WITH_COREAUDIO
#include "include/aegisub/audio_player.h"

#include <mutex>
#include <AudioToolbox/AudioFormat.h>
#include <AudioToolbox/AudioQueue.h>

#include <libaegisub/audio/provider.h>
#include <libaegisub/log.h>
#include <libaegisub/make_unique.h>

namespace {
class CoreAudioPlayer final : public AudioPlayer {
static const int num_buffers = 8;
AudioQueueRef aq = nullptr;
AudioQueueBufferRef buffer[num_buffers];
AudioStreamBasicDescription format;

int64_t samples_per_buffer;
int64_t next_input_frame = 0, start_frame = 0, end_frame = 0;
Float64 start_sample, sample_rate = 0;
bool original = true;
bool playing = false;
std::mutex mutex;
static void Callback(void *self, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
void FillBuffer(AudioQueueRef inAQ, AudioQueueBufferRef buffer);
Float64 GetSampleTime();
public:
CoreAudioPlayer(agi::AudioProvider *provider);
~CoreAudioPlayer();

void Play(int64_t start, int64_t count) override;
void Stop() override;
bool IsPlaying() override { return playing; }

void SetVolume(double vol) override { AudioQueueSetParameter(aq, kAudioQueueParam_Volume, vol); }

int64_t GetEndPosition() override { return end_frame; }
int64_t GetCurrentPosition() override;
void SetEndPosition(int64_t pos) override;
};

CoreAudioPlayer::CoreAudioPlayer(agi::AudioProvider *provider)
: AudioPlayer(provider)
{
format.mSampleRate = provider->GetSampleRate();
format.mFormatID = kAudioFormatLinearPCM;
if (provider->AreSamplesFloat()) {
format.mFormatFlags = kAudioFormatFlagIsFloat;
} else if (provider->GetBytesPerSample() == sizeof(uint8_t)) {
format.mFormatFlags = kAudioFormatFlagsAreAllClear;
} else {
format.mFormatFlags = kAudioFormatFlagIsSignedInteger;
}
format.mBytesPerPacket = provider->GetChannels() * provider->GetBytesPerSample();
format.mFramesPerPacket = 1;
format.mBytesPerFrame = format.mBytesPerPacket;
format.mChannelsPerFrame = provider->GetChannels();
format.mBitsPerChannel = provider->GetBytesPerSample() * 8;
format.mReserved = 0;
OSStatus ret = AudioQueueNewOutput(&format, Callback, this, NULL, NULL, 0, &aq);
if (ret) {
if (ret == kAudioFormatUnsupportedDataFormatError) {
original = false;
format.mFormatFlags = kAudioFormatFlagIsSignedInteger;
format.mBytesPerPacket = sizeof(int16_t);
format.mFramesPerPacket = 1;
format.mBytesPerFrame = sizeof(int16_t);
format.mChannelsPerFrame = 1;
format.mBitsPerChannel = sizeof(int16_t) * 8;
ret = AudioQueueNewOutput(&format, Callback, this, NULL, NULL, 0, &aq);
if (ret) {
throw AudioPlayerOpenError("AudioQueueNewOutput failed");
}
} else {
throw AudioPlayerOpenError("AudioQueueNewOutput failed");
}
}
samples_per_buffer = provider->GetSampleRate() / num_buffers / 2;
for (int i = 0; i < num_buffers; ++i) {
AudioQueueAllocateBuffer(aq, samples_per_buffer * format.mBytesPerFrame, buffer + i);
}
}

CoreAudioPlayer::~CoreAudioPlayer()
{
std::lock_guard<std::mutex> lock(mutex);
Stop();
AudioQueueDispose(aq, false);
aq = nullptr;
}

void CoreAudioPlayer::Callback(void *self, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
{
CoreAudioPlayer *player = static_cast<CoreAudioPlayer*>(self);
std::lock_guard<std::mutex> lock(player->mutex);
player->FillBuffer(inAQ, inBuffer);
}

void CoreAudioPlayer::FillBuffer(AudioQueueRef inAQ, AudioQueueBufferRef buffer)
{
if (!playing) {
LOG_D("audio/player/coreaudio") << "FillBuffer quit: not playing";
return;
}
int fill_len = std::min<int>(end_frame - next_input_frame, samples_per_buffer);
if (fill_len <= 0) {
LOG_D("audio/player/coreaudio") << "FillBuffer quit: zero fill_len";
return;
}
if (original) {
provider->GetAudio(buffer->mAudioData, next_input_frame, fill_len);
} else {
provider->GetInt16MonoAudio(reinterpret_cast<int16_t*>(buffer->mAudioData), next_input_frame, fill_len);
}
next_input_frame += fill_len;
buffer->mAudioDataByteSize = fill_len * format.mBytesPerFrame;
buffer->mPacketDescriptionCount = 0;
buffer->mUserData = NULL;
AudioQueueEnqueueBuffer(inAQ, buffer, 0, NULL);
}

Float64 CoreAudioPlayer::GetSampleTime()
{
AudioTimeStamp ts;
AudioQueueDeviceGetCurrentTime(aq, &ts);
if (ts.mFlags & kAudioTimeStampSampleTimeValid)
return ts.mSampleTime;
return 0;
}

void CoreAudioPlayer::Play(int64_t start, int64_t count)
{
playing = false;
AudioQueueReset(aq);

start_frame = start;
end_frame = start + count;
next_input_frame = start;
playing = true;
OSStatus ret = AudioQueueStart(aq, NULL);
if (ret) {
throw AudioPlayerOpenError("AudioQueueStart failed");
}
for (int i = 0; i < num_buffers; ++i) {
FillBuffer(aq, buffer[i]);
}
start_sample = GetSampleTime();
LOG_D("audio/player/coreaudio") << "Playback begun";
}

void CoreAudioPlayer::Stop()
{
LOG_D("audio/player/coreaudio") << "Stop";
playing = false;
AudioQueueStop(aq, true);
}

int64_t CoreAudioPlayer::GetCurrentPosition()
{
Float64 samples = GetSampleTime() - start_sample;
if (sample_rate == 0) {
UInt32 size = sizeof(sample_rate);
AudioQueueGetProperty(aq, kAudioQueueDeviceProperty_SampleRate, &sample_rate, &size);
}
if (sample_rate == 0) {
return start_frame;
}
return samples / sample_rate * provider->GetSampleRate() + start_frame;
}

void CoreAudioPlayer::SetEndPosition(int64_t pos)
{
end_frame = pos;
if (end_frame <= GetCurrentPosition())
Stop();
}

}

std::unique_ptr<AudioPlayer> CreateCoreAudioPlayer(agi::AudioProvider *provider, wxWindow *)
{
return agi::make_unique<CoreAudioPlayer>(provider);
}

#endif // WITH_COREAUDIO

0 comments on commit aa1e63f

Please sign in to comment.