AVAudioUnit host - PCM buffer output silent

Hi,

I just started to develop audio unit hosting support in my application.

Offline rendering seems to work except that I hear no output, but why?

I suspect with the player goes something wrong.

I connect to CoreAudio in a different location in the code.

Here are some error messages I faced so far:

2025-08-14 19:42:04.132930+0200 com.gsequencer.GSequencer[34358:18611871] [avae]     AVAudioEngineGraph.mm:4668  Can't retrieve source node to play sequence because there is no output node!
2025-08-14 19:42:04.151171+0200 com.gsequencer.GSequencer[34358:18611871] [avae]     AVAudioEngineGraph.mm:4668  Can't retrieve source node to play sequence because there is no output node!
2025-08-14 19:43:08.344530+0200 com.gsequencer.GSequencer[34358:18614927]            AUAudioUnit.mm:1417  Cannot set maximumFramesToRender while render resources allocated.
2025-08-14 19:43:08.346583+0200 com.gsequencer.GSequencer[34358:18614927] [avae]            AVAEInternal.h:104   [AVAudioSequencer.mm:121:-[AVAudioSequencer(AVAudioSequencer_Player) startAndReturnError:]: (impl->Start()): error -10852

** (<unknown>:34358): WARNING **: 19:43:08.346: error during audio sequencer start - -10852

I have implemented an AVAudioEngine based AudioUnit host. Here I instantiate player and effect:

/* audio engine */
audio_engine = [[AVAudioEngine alloc] init];
  
fx_audio_unit_audio->audio_engine = (gpointer) audio_engine;

av_format = (AVAudioFormat *) fx_audio_unit_audio->av_format;

/* av audio player node */
av_audio_player_node = [[AVAudioPlayerNode alloc] init];

/* av audio unit */
av_audio_unit_effect = [[AVAudioUnitEffect alloc] initWithAudioComponentDescription:[((AVAudioUnitComponent *) AGS_AUDIO_UNIT_PLUGIN(base_plugin)->component) audioComponentDescription]];

av_audio_unit = (AVAudioUnit *) av_audio_unit_effect;
  
fx_audio_unit_audio->av_audio_unit = av_audio_unit;
  
/* audio sequencer */
av_audio_sequencer = [[AVAudioSequencer alloc] initWithAudioEngine:audio_engine];
  
fx_audio_unit_audio->av_audio_sequencer = (gpointer) av_audio_sequencer;

/* output node */
[[AVAudioOutputNode alloc] init];
  
/* audio player and audio unit */
[audio_engine attachNode:av_audio_player_node];
[audio_engine attachNode:av_audio_unit];

[audio_engine connect:av_audio_player_node to:av_audio_unit format:av_format];
[audio_engine connect:av_audio_unit to:[audio_engine outputNode] format:av_format];

ns_error = NULL;
  
[audio_engine enableManualRenderingMode:AVAudioEngineManualRenderingModeOffline
   format:av_format
   maximumFrameCount:buffer_size error:&ns_error];

if(ns_error != NULL &&
   [ns_error code] != noErr){
  g_warning("enable manual rendering mode error - %d", [ns_error code]);
}

ns_error = NULL;
      
[[av_audio_unit AUAudioUnit] allocateRenderResourcesAndReturnError:&ns_error];

if(ns_error != NULL &&
   [ns_error code] != noErr){
  g_warning("Audio Unit allocate render resources returned error - ErrorCode %d", [ns_error code]);
}

Then I render in a dedicated thread.

ns_error = NULL;

[audio_engine startAndReturnError:&ns_error];

if(ns_error != NULL &&
   [ns_error code] != noErr){
  g_warning("error during audio engine start - %d", [ns_error code]);
}
  
[av_audio_sequencer prepareToPlay];

ns_error = NULL;
  
[av_audio_sequencer startAndReturnError:&ns_error];

if(ns_error != NULL &&
   [ns_error code] != noErr){
  g_warning("error during audio sequencer start - %d", [ns_error code]);
}

[av_audio_player_node play];

while(is_running){
  /* pre sync */

  /* IO buffers */
  av_output_buffer = (AVAudioPCMBuffer *) scope_data->av_output_buffer;
  av_input_buffer = (AVAudioPCMBuffer *) scope_data->av_input_buffer;

  /* fill input buffer */

  /* schedule av input buffer */
  frame_position = 0; // (gint64) ((note_offset * absolute_delay) + delay_counter) * buffer_size;

  av_audio_player_node = (AVAudioPlayerNode *) fx_audio_unit_audio->av_audio_player_node;

  AVAudioTime *av_audio_time = [[AVAudioTime alloc] initWithHostTime:frame_position sampleTime:frame_position atRate:((double) samplerate)];

  [av_audio_player_node scheduleBuffer:av_input_buffer atTime:av_audio_time options:0 completionHandler:nil];
	  
  /* render */
  ns_error = NULL;
	  
  status = [audio_engine renderOffline:AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE toBuffer:av_output_buffer error:&ns_error];
    
  if(ns_error != NULL &&
     [ns_error code] != noErr){
    g_warning("render offline error - %d", [ns_error code]);
  }
}

regards, Joël

Answered by joel2001k in 855221022

Hi,

Now, I have a working Audio Unit v3 host, using these objects:

AVAudioEngine *audio_engine;

AVAudioOutputNode *av_output_node;
AVAudioInputNode *av_input_node;
AVAudioUnit *av_audio_unit;
AVAudioSequencer *av_audio_sequencer;

AVAudioFormat *av_format;

You can make use of output and input node of AVAudioEngine while in offline rendering mode.

/* output node */
av_output_node = [audio_engine outputNode];

/* input node */
av_input_node = [audio_engine inputNode];

/* mixer node */
av_audio_mixer_node = [audio_engine mainMixerNode];
  
/* audio player and audio unit */
[audio_engine attachNode:av_audio_unit];

[audio_engine connect:av_input_node to:av_audio_unit format:av_format];
[audio_engine connect:av_audio_unit to:av_audio_mixer_node format:av_format];
[audio_engine connect:av_audio_mixer_node to:av_output_node format:av_format];

The thing with the input node is you have to provide a block before start AVAudioEngine.

input_success = [av_input_node setManualRenderingInputPCMFormat:av_format
inputBlock:^(AVAudioFrameCount inNumberOfFrames){
  AudioBufferList *audio_buffer_list;

  guint audio_channels;
  guint i;

  audio_channels = [av_format channelCount];

  audio_buffer_list = (AudioBufferList *) ags_fx_audio_unit_iterate_channel_data->audio_buffer_list;
				
  /* fill av input buffer */
  if(ags_fx_audio_unit_iterate_channel_data != NULL){
    if(AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE <= inNumberOfFrames){
      ags_audio_buffer_util_copy_buffer_to_buffer(NULL,
          audio_buffer_list->mBuffers[0].mData, 1, 0,
          ags_fx_audio_unit_iterate_channel_data->input + (ags_fx_audio_unit_iterate_sub_block * AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE), 1, 0,
                                                                                     AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE, AGS_AUDIO_BUFFER_UTIL_COPY_FLOAT_TO_FLOAT);
    }else{
	   ags_audio_buffer_util_copy_buffer_to_buffer(NULL,
          audio_buffer_list->mBuffers[0].mData, 1, 0,
          ags_fx_audio_unit_iterate_channel_data->input + (ags_fx_audio_unit_iterate_sub_block * AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE), 1, 0,
          inNumberOfFrames, AGS_AUDIO_BUFFER_UTIL_COPY_FLOAT_TO_FLOAT);
        }
      }
    
    return((const AudioBufferList *) audio_buffer_list);
  }];

if(input_success != YES){
  g_warning("set manual rendering input failed");
}

Update, I was able to remove the error messages by these 3 steps:

#1 don't call:

[[av_audio_unit AUAudioUnit] allocateRenderResourcesAndReturnError:&ns_error];

#2 add a AVMusicTrack to AVAudioSequencer

av_music_track = [av_audio_sequencer createAndAppendTrack];

av_music_track.destinationAudioUnit = av_audio_unit;

#3 call the outputNode singleton of AVAudioEngine

av_music_track = [av_audio_sequencer createAndAppendTrack];

av_music_track.destinationAudioUnit = av_audio_unit;

... but still no audible output.

Hi, I have limited knowledge here, but I'v been working on Core Audio recently so: from my understanding, offline rendering outputs to a file, i.e. you process offline your audio, it goes super fast, to then play the file.

Now, if you _really_want to hear the audio, disable manual rendering.

Accepted Answer

Hi,

Now, I have a working Audio Unit v3 host, using these objects:

AVAudioEngine *audio_engine;

AVAudioOutputNode *av_output_node;
AVAudioInputNode *av_input_node;
AVAudioUnit *av_audio_unit;
AVAudioSequencer *av_audio_sequencer;

AVAudioFormat *av_format;

You can make use of output and input node of AVAudioEngine while in offline rendering mode.

/* output node */
av_output_node = [audio_engine outputNode];

/* input node */
av_input_node = [audio_engine inputNode];

/* mixer node */
av_audio_mixer_node = [audio_engine mainMixerNode];
  
/* audio player and audio unit */
[audio_engine attachNode:av_audio_unit];

[audio_engine connect:av_input_node to:av_audio_unit format:av_format];
[audio_engine connect:av_audio_unit to:av_audio_mixer_node format:av_format];
[audio_engine connect:av_audio_mixer_node to:av_output_node format:av_format];

The thing with the input node is you have to provide a block before start AVAudioEngine.

input_success = [av_input_node setManualRenderingInputPCMFormat:av_format
inputBlock:^(AVAudioFrameCount inNumberOfFrames){
  AudioBufferList *audio_buffer_list;

  guint audio_channels;
  guint i;

  audio_channels = [av_format channelCount];

  audio_buffer_list = (AudioBufferList *) ags_fx_audio_unit_iterate_channel_data->audio_buffer_list;
				
  /* fill av input buffer */
  if(ags_fx_audio_unit_iterate_channel_data != NULL){
    if(AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE <= inNumberOfFrames){
      ags_audio_buffer_util_copy_buffer_to_buffer(NULL,
          audio_buffer_list->mBuffers[0].mData, 1, 0,
          ags_fx_audio_unit_iterate_channel_data->input + (ags_fx_audio_unit_iterate_sub_block * AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE), 1, 0,
                                                                                     AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE, AGS_AUDIO_BUFFER_UTIL_COPY_FLOAT_TO_FLOAT);
    }else{
	   ags_audio_buffer_util_copy_buffer_to_buffer(NULL,
          audio_buffer_list->mBuffers[0].mData, 1, 0,
          ags_fx_audio_unit_iterate_channel_data->input + (ags_fx_audio_unit_iterate_sub_block * AGS_FX_AUDIO_UNIT_AUDIO_FIXED_BUFFER_SIZE), 1, 0,
          inNumberOfFrames, AGS_AUDIO_BUFFER_UTIL_COPY_FLOAT_TO_FLOAT);
        }
      }
    
    return((const AudioBufferList *) audio_buffer_list);
  }];

if(input_success != YES){
  g_warning("set manual rendering input failed");
}
AVAudioUnit host - PCM buffer output silent
 
 
Q