Newer
Older
Import / applications / HighwayDash / ports / Platform / iOS / AudioRenderer.mm
//  BlockyFroggy
//  Copyright © 2017 John Ryland.
//  All rights reserved.
#import "AudioRenderer.h"
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#include "Log.h"
#include "Utilities.h"
#include <TargetConditionals.h>


#if TARGET_IPHONE_SIMULATOR == 1
// Provide dummy implmentations for the simulator where the AudioToolbox isn't available
OSStatus AudioUnitInitialize(AudioUnit)   { return 0; }
OSStatus AudioUnitUninitialize(AudioUnit) { return 0; }
OSStatus AudioOutputUnitStart(AudioUnit)  { return 0; }
OSStatus AudioOutputUnitStop(AudioUnit)   { return 0; }
OSStatus AudioComponentInstanceNew(AudioComponent, AudioComponentInstance*) { return 0; }
OSStatus AudioUnitSetProperty(AudioUnit, AudioUnitPropertyID, AudioUnitScope, AudioUnitElement, const void*, UInt32) { return 0; }
AudioComponent AudioComponentFindNext(AudioComponent, AudioComponentDescription*) { return AudioComponent(); }
#endif


struct AudioRenderer::Pimpl
{
    // private implementation details here
    AudioComponentInstance  m_audioUnit;
    int                     m_channels;
    int                     m_bytesPerChannel;
    int                     m_sampleRate;
    //AudioBuffer           m_buffer;
};


AudioRenderer::AudioRenderer()
  : m_pimpl(std::make_unique<Pimpl>())
{
}


AudioRenderer::~AudioRenderer()
{
    AudioUnitUninitialize(m_pimpl->m_audioUnit);
}


static void checkStatus(int status)
{
    if (status)
        LogTag(LL_Error, "AUD", "Audio Error %d", status);
}


// Callback when the audioUnit needs to fill buffers with audio data
static OSStatus playbackCallback(void *inRefCon,
                                 AudioUnitRenderActionFlags *ioActionFlags,
                                 const AudioTimeStamp *inTimeStamp,
                                 UInt32 inBusNumber,
                                 UInt32 inNumberFrames,
                                 AudioBufferList *ioData) {
    //! @todo check what happens for stereo or other cases
    for (int i=0; i < ioData->mNumberBuffers; i++)
    {
        AudioBuffer buffer = ioData->mBuffers[i];
        AudioRenderer* _this = static_cast<AudioRenderer*>(inRefCon);
        _this->renderSamples(buffer.mData, inNumberFrames * 2); // _this->m_pimpl->m_channels);
    }
    return noErr;
}


void AudioRenderer::initialize(SampleRate a_rate, BitsPerChannel a_bitsPerChannel, Channels a_channels)
{
    OSStatus status;

    // Save parameters
    m_pimpl->m_bytesPerChannel = a_bitsPerChannel / 8;
    m_pimpl->m_channels = a_channels;
    m_pimpl->m_sampleRate = a_rate;
    
    // Describe audio component
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    
    // Get component
    AudioComponent audioComponent = AudioComponentFindNext(NULL, &desc);
    
    // Get audio units
    status = AudioComponentInstanceNew(audioComponent, &m_pimpl->m_audioUnit);
    checkStatus(status);
    
    const int kOutputBus = 0;
    
    // Enable IO for playback
    UInt32 flag = 1;
    status = AudioUnitSetProperty(m_pimpl->m_audioUnit,
                                  kAudioOutputUnitProperty_EnableIO,
                                  kAudioUnitScope_Output,
                                  kOutputBus,
                                  &flag,
                                  sizeof(flag));
    checkStatus(status);
    
    // Describe format
    AudioStreamBasicDescription audioFormat;
    audioFormat.mSampleRate			= float(a_rate);
    audioFormat.mFormatID			= kAudioFormatLinearPCM;
    audioFormat.mFormatFlags		= kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    audioFormat.mFramesPerPacket	= 1;
    audioFormat.mChannelsPerFrame	= a_channels;
    audioFormat.mBitsPerChannel		= a_bitsPerChannel;
    audioFormat.mBytesPerPacket		= m_pimpl->m_bytesPerChannel * a_channels;
    audioFormat.mBytesPerFrame		= m_pimpl->m_bytesPerChannel * a_channels;
    
    // Apply format
    status = AudioUnitSetProperty(m_pimpl->m_audioUnit,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Input,
                                  kOutputBus,
                                  &audioFormat,
                                  sizeof(audioFormat));
    checkStatus(status);
    
    // Set output callback
    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = playbackCallback;
    callbackStruct.inputProcRefCon = this;
    status = AudioUnitSetProperty(m_pimpl->m_audioUnit,
                                  kAudioUnitProperty_SetRenderCallback,
                                  kAudioUnitScope_Global,
                                  kOutputBus,
                                  &callbackStruct,
                                  sizeof(callbackStruct));
    checkStatus(status);
    
    // Initialise
    status = AudioUnitInitialize(m_pimpl->m_audioUnit);
    checkStatus(status);
}


void AudioRenderer::start()
{
    checkStatus(AudioOutputUnitStart(m_pimpl->m_audioUnit));
}



void AudioRenderer::stop()
{
    checkStatus(AudioOutputUnitStop(m_pimpl->m_audioUnit));
}