--- /dev/null
+#import "PcmSound.h"
+#import <AudioToolbox/AudioToolbox.h>
+
+#if TARGET_OS_IOS
+#import <AVFoundation/AVFoundation.h>
+#endif
+
+#define kOutputBus 0
+#define NAMESPACE @"pcm_sound" // Assuming this is the namespace you want
+
+typedef NS_ENUM(NSUInteger, LogLevel) {
+ none = 0,
+ error = 1,
+ standard = 2,
+ verbose = 3,
+};
+
+@interface PcmSound ()
+@property(nonatomic) LogLevel mLogLevel;
+@property(nonatomic) AudioComponentInstance mAudioUnit;
+@property(nonatomic) NSMutableData *mSamples;
+@property(nonatomic) int mNumChannels;
+@property(nonatomic) int mFeedThreshold;
+@property(nonatomic) bool mDidInvokeFeedCallback;
+@end
+
+@implementation PcmSound
+
++ (instancetype)sharedInstance {
+ static PcmSound *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ sharedInstance = [[self alloc] init];
+ sharedInstance.mLogLevel = verbose;
+ sharedInstance.mSamples = [NSMutableData new];
+ sharedInstance.mFeedThreshold = 0;
+ sharedInstance.mDidInvokeFeedCallback = false;
+ });
+ return sharedInstance;
+}
+
+- (instancetype)init
+{
+ self = [super init];
+ if (self) {
+
+ }
+ return self;
+}
+
+-(void)dealloc {
+
+}
+
+-(void)setLogLevel:(NSNumber*)logLevelNumber {
+ self.mLogLevel = (LogLevel)[logLevelNumber integerValue];
+}
+
+-(void)setup:(int)sampleRate withchannels:(int)numChannels {
+
+ self.mNumChannels = numChannels;
+
+ // cleanup
+ if (_mAudioUnit != nil) {
+ [self cleanup];
+ }
+
+ // create
+ AudioComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+#if TARGET_OS_IOS
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#else // MacOS
+ desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+#endif
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+
+ AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
+ OSStatus status = AudioComponentInstanceNew(inputComponent, &_mAudioUnit);
+ if (status != noErr) {
+ //NSString* message = [NSString stringWithFormat:@"AudioComponentInstanceNew failed. OSStatus: %@", @(status)];
+ return;
+ }
+
+ // set stream format
+ AudioStreamBasicDescription audioFormat;
+ audioFormat.mSampleRate = sampleRate;
+ audioFormat.mFormatID = kAudioFormatLinearPCM;
+ audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+ audioFormat.mFramesPerPacket = 1;
+ audioFormat.mChannelsPerFrame = self.mNumChannels;
+ audioFormat.mBitsPerChannel = 16;
+ audioFormat.mBytesPerFrame = self.mNumChannels * (audioFormat.mBitsPerChannel / 8);
+ audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
+
+ status = AudioUnitSetProperty(_mAudioUnit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ kOutputBus,
+ &audioFormat,
+ sizeof(audioFormat));
+ if (status != noErr) {
+ //NSString* message = [NSString stringWithFormat:@"AudioUnitSetProperty StreamFormat failed. OSStatus: %@", @(status)];
+ return;
+ }
+
+ // set callback
+ AURenderCallbackStruct callback;
+ callback.inputProc = RenderCallback;
+ callback.inputProcRefCon = (__bridge void *)(self);
+
+ status = AudioUnitSetProperty(_mAudioUnit,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Global,
+ kOutputBus,
+ &callback,
+ sizeof(callback));
+ if (status != noErr) {
+ //NSString* message = [NSString stringWithFormat:@"AudioUnitSetProperty SetRenderCallback failed. OSStatus: %@", @(status)];
+ return;
+ }
+
+ // initialize
+ status = AudioUnitInitialize(_mAudioUnit);
+ if (status != noErr) {
+ //NSString* message = [NSString stringWithFormat:@"AudioUnitInitialize failed. OSStatus: %@", @(status)];
+ return;
+ }
+}
+
+-(void)feed:(NSData*)buffer {
+
+ @synchronized (self.mSamples) {
+ [self.mSamples appendData:buffer];
+ }
+
+ // reset
+ self.mDidInvokeFeedCallback = false;
+
+ // start playback if we can
+ if (_mAudioUnit != nil) {
+ UInt32 isPlaying = 0;
+ UInt32 size = sizeof(isPlaying);
+ OSStatus status = AudioUnitGetProperty(_mAudioUnit,
+ kAudioOutputUnitProperty_IsRunning,
+ kAudioUnitScope_Global,
+ 0,
+ &isPlaying,
+ &size);
+ if (status != noErr) {
+ //NSString* message = [NSString stringWithFormat:@"AudioUnitGetProperty IsRunning failed. OSStatus: %@", @(status)];
+ return;
+ }
+ if (!isPlaying) {
+ status = AudioOutputUnitStart(_mAudioUnit);
+ if (status != noErr) {
+ //NSString* message = [NSString stringWithFormat:@"AudioOutputUnitStart failed. OSStatus: %@", @(status)];
+ return;
+ }
+ }
+ }
+}
+
+-(void)setFeedThreshold:(NSNumber*)feedThreshold {
+ self.mFeedThreshold = [feedThreshold intValue];
+}
+
+-(BOOL)isPlaying {
+ return self.mSamples.length > 0;
+}
+
+
+- (void)cleanup
+{
+ if (_mAudioUnit != nil) {
+ AudioUnitUninitialize(_mAudioUnit);
+ AudioComponentInstanceDispose(_mAudioUnit);
+ _mAudioUnit = nil;
+ }
+ @synchronized (self.mSamples) {
+ self.mSamples = [NSMutableData new];
+ }
+}
+
+- (void)stopAudioUnit
+{
+ if (_mAudioUnit != nil) {
+ UInt32 isRunning = 0;
+ UInt32 size = sizeof(isRunning);
+ OSStatus status = AudioUnitGetProperty(_mAudioUnit,
+ kAudioOutputUnitProperty_IsRunning,
+ kAudioUnitScope_Global,
+ 0,
+ &isRunning,
+ &size);
+ if (status != noErr) {
+ NSLog(@"AudioUnitGetProperty IsRunning failed. OSStatus: %@", @(status));
+ return;
+ }
+ if (isRunning) {
+ status = AudioOutputUnitStop(_mAudioUnit);
+ if (status != noErr) {
+ NSLog(@"AudioOutputUnitStop failed. OSStatus: %@", @(status));
+ } else {
+ NSLog(@"AudioUnit stopped because no more samples");
+ }
+ }
+ }
+}
+
+
+static OSStatus RenderCallback(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ PcmSound *instance = (__bridge PcmSound *)(inRefCon);
+
+ NSUInteger remainingFrames;
+ BOOL shouldRequestMore = false;
+
+ @synchronized (instance.mSamples) {
+
+ // clear
+ memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
+
+ NSUInteger bytesToCopy = MIN(ioData->mBuffers[0].mDataByteSize, [instance.mSamples length]);
+
+ // provide samples
+ memcpy(ioData->mBuffers[0].mData, [instance.mSamples bytes], bytesToCopy);
+
+ // pop front bytes
+ NSRange range = NSMakeRange(0, bytesToCopy);
+ [instance.mSamples replaceBytesInRange:range withBytes:NULL length:0];
+
+ remainingFrames = [instance.mSamples length] / (instance.mNumChannels * sizeof(short));
+
+ // should request more frames?
+ shouldRequestMore = remainingFrames <= instance.mFeedThreshold && !instance.mDidInvokeFeedCallback;
+ }
+
+ // stop running, if needed
+ if (remainingFrames == 0) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [instance stopAudioUnit];
+ });
+ }
+
+ if (shouldRequestMore) {
+ instance.mDidInvokeFeedCallback = true;
+ //NSDictionary *response = @{@"remaining_frames": @(remainingFrames)};
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if ([instance.delegate respondsToSelector:@selector(remaining:)]) {
+ [instance.delegate remaining:remainingFrames];
+ }
+ });
+ }
+
+ return noErr;
+}
+
+
+@end