OLD | NEW |
(Empty) | |
| 1 // |
| 2 // Minimized test case for audio playback issue where AudioUnits are started |
| 3 // "successfully" during system resume, but never receive AURenderCallbacks. |
| 4 // |
| 5 // Date: Fri, Nov 15, 2013 |
| 6 // Author: Dale Curtis <dalecurtis@google.com> |
| 7 // Original Chrome issue: http://crbug.com/160920 |
| 8 // |
| 9 // Code based on Technical Note TN2091: |
| 10 // https://developer.apple.com/library/mac/technotes/tn2091/_index.html |
| 11 // |
| 12 // Compiles with the following command line: |
| 13 // clang -framework CoreAudio -framework AudioUnit -lstdc++ audio-hang.cc |
| 14 // |
| 15 |
| 16 #include <cstring> |
| 17 #include <pthread.h> |
| 18 |
| 19 #include <AudioUnit/AudioUnit.h> |
| 20 #include <CoreAudio/CoreAudio.h> |
| 21 #include <CoreServices/CoreServices.h> |
| 22 |
| 23 // Create an AUHAL based AudioUnit. |
| 24 static AudioUnit CreateAudioUnit() { |
| 25 AudioComponentDescription desc = { |
| 26 kAudioUnitType_Output, |
| 27 kAudioUnitSubType_HALOutput, |
| 28 kAudioUnitManufacturer_Apple, |
| 29 0, |
| 30 0 |
| 31 }; |
| 32 |
| 33 AudioComponent comp = AudioComponentFindNext(0, &desc); |
| 34 assert(comp); |
| 35 |
| 36 AudioUnit audio_unit; |
| 37 OSStatus result = AudioComponentInstanceNew(comp, &audio_unit); |
| 38 assert(result == noErr); |
| 39 |
| 40 return audio_unit; |
| 41 } |
| 42 |
| 43 // Configure an AudioUnit to use the default output device. |
| 44 static void ConfigureAudioUnitForDefaultDevice(AudioUnit audio_unit) { |
| 45 const AudioObjectPropertyAddress property_address = { |
| 46 kAudioHardwarePropertyDefaultOutputDevice, |
| 47 kAudioObjectPropertyScopeGlobal, |
| 48 kAudioObjectPropertyElementMaster |
| 49 }; |
| 50 |
| 51 AudioDeviceID device_id = 0; |
| 52 UInt32 size = sizeof(device_id); |
| 53 OSStatus result = AudioObjectGetPropertyData( |
| 54 kAudioObjectSystemObject, &property_address, 0, 0, &size, &device_id); |
| 55 assert(result == kAudioHardwareNoError && device_id != kAudioDeviceUnknown); |
| 56 |
| 57 result = AudioUnitSetProperty(audio_unit, |
| 58 kAudioOutputUnitProperty_CurrentDevice, |
| 59 kAudioUnitScope_Global, |
| 60 0, |
| 61 &device_id, |
| 62 sizeof(device_id)); |
| 63 assert(result == noErr); |
| 64 } |
| 65 |
| 66 // Configure an AudioUnit for output only. |
| 67 static void ConfigureAudioUnitForOutputOnly(AudioUnit audio_unit) { |
| 68 // Disable input. |
| 69 UInt32 enabled = 0; |
| 70 OSStatus result = AudioUnitSetProperty(audio_unit, |
| 71 kAudioOutputUnitProperty_EnableIO, |
| 72 kAudioUnitScope_Input, |
| 73 1, |
| 74 &enabled, |
| 75 sizeof(enabled)); |
| 76 assert (result == noErr); |
| 77 |
| 78 // Enable output. |
| 79 enabled = 1; |
| 80 result = AudioUnitSetProperty(audio_unit, |
| 81 kAudioOutputUnitProperty_EnableIO, |
| 82 kAudioUnitScope_Output, |
| 83 0, |
| 84 &enabled, |
| 85 sizeof(enabled)); |
| 86 assert (result == noErr); |
| 87 } |
| 88 |
| 89 // Configure an AudioUnit for float planar w/ native channel count and sample |
| 90 // rate. |
| 91 static void ConfigureAudioUnitStreamFormat(AudioUnit audio_unit) { |
| 92 AudioStreamBasicDescription device_format = {0}; |
| 93 UInt32 size = sizeof(device_format); |
| 94 OSStatus result = AudioUnitGetProperty(audio_unit, |
| 95 kAudioUnitProperty_StreamFormat, |
| 96 kAudioUnitScope_Input, |
| 97 0, |
| 98 &device_format, |
| 99 &size); |
| 100 assert(result == noErr); |
| 101 |
| 102 AudioStreamBasicDescription desired_format = { |
| 103 device_format.mSampleRate, |
| 104 kAudioFormatLinearPCM, |
| 105 kAudioFormatFlagsNativeFloatPacked | kLinearPCMFormatFlagIsNonInterleaved, |
| 106 sizeof(Float32), |
| 107 1, |
| 108 sizeof(Float32), |
| 109 device_format.mChannelsPerFrame, |
| 110 sizeof(Float32) * 8, |
| 111 0 |
| 112 }; |
| 113 |
| 114 result = AudioUnitSetProperty(audio_unit, |
| 115 kAudioUnitProperty_StreamFormat, |
| 116 kAudioUnitScope_Input, |
| 117 0, |
| 118 &desired_format, |
| 119 size); |
| 120 assert(result == noErr); |
| 121 } |
| 122 |
| 123 class AudioOutputStream { |
| 124 public: |
| 125 AudioOutputStream() |
| 126 : audio_unit_(CreateAudioUnit()), |
| 127 time_state_(0), |
| 128 render_signal_(NULL) { |
| 129 ConfigureAudioUnitForOutputOnly(audio_unit_); |
| 130 ConfigureAudioUnitForDefaultDevice(audio_unit_); |
| 131 ConfigureAudioUnitStreamFormat(audio_unit_); |
| 132 ConfigureAudioUnitRenderCallback(audio_unit_, this); |
| 133 |
| 134 OSStatus result = AudioUnitInitialize(audio_unit_); |
| 135 assert(result == noErr); |
| 136 |
| 137 printf("AudioOutputStream(%p) created.\n", this); |
| 138 } |
| 139 |
| 140 ~AudioOutputStream() { |
| 141 assert(!render_signal_); |
| 142 |
| 143 OSStatus result = AudioUnitUninitialize(audio_unit_); |
| 144 assert(result == noErr); |
| 145 |
| 146 result = AudioComponentInstanceDispose(audio_unit_); |
| 147 assert(result == noErr); |
| 148 |
| 149 printf("AudioOutputStream(%p) destroyed.\n", this); |
| 150 } |
| 151 |
| 152 void Start(pthread_cond_t* render_signal) { |
| 153 assert(render_signal); |
| 154 assert(!render_signal_); |
| 155 render_signal_ = render_signal; |
| 156 |
| 157 OSStatus result = AudioOutputUnitStart(audio_unit_); |
| 158 assert(result == noErr); |
| 159 |
| 160 printf(" AudioOutputStream(%p) started.\n", this); |
| 161 } |
| 162 |
| 163 void Stop() { |
| 164 OSStatus result = AudioOutputUnitStop(audio_unit_); |
| 165 assert(result == noErr); |
| 166 |
| 167 assert(render_signal_); |
| 168 render_signal_ = NULL; |
| 169 |
| 170 printf(" AudioOutputStream(%p) stopped.\n", this); |
| 171 } |
| 172 |
| 173 void Render(UInt32 number_of_frames, AudioBufferList* io_data) { |
| 174 assert(io_data->mNumberBuffers > 0); |
| 175 assert(io_data->mBuffers[0].mDataByteSize == |
| 176 number_of_frames * sizeof(float)); |
| 177 |
| 178 // Fill the first AudioBuffer with a sine wave and copy to other channels. |
| 179 float* output = reinterpret_cast<float*>(io_data->mBuffers[0].mData); |
| 180 for (UInt32 i = 0; i < number_of_frames; ++i) |
| 181 output[i] = sin(2.0 * M_PI * 600.0 / 48000.0 * time_state_++); |
| 182 for (UInt32 i = 1; i < io_data->mNumberBuffers; ++i) { |
| 183 memcpy(io_data->mBuffers[i].mData, |
| 184 io_data->mBuffers[0].mData, |
| 185 io_data->mBuffers[i].mDataByteSize); |
| 186 } |
| 187 |
| 188 // Signal the main loop that we've recieved the first Render() call. |
| 189 assert(render_signal_); |
| 190 pthread_cond_signal(render_signal_); |
| 191 } |
| 192 |
| 193 static OSStatus AURenderCallbackProc(void* user_data, |
| 194 AudioUnitRenderActionFlags* flags, |
| 195 const AudioTimeStamp* output_time_stamp, |
| 196 UInt32 bus_number, |
| 197 UInt32 number_of_frames, |
| 198 AudioBufferList* io_data) { |
| 199 assert(user_data); |
| 200 static_cast<AudioOutputStream*>(user_data)->Render( |
| 201 number_of_frames, io_data); |
| 202 return noErr; |
| 203 } |
| 204 |
| 205 void ConfigureAudioUnitRenderCallback(AudioUnit audio_unit, |
| 206 void* user_data) { |
| 207 AURenderCallbackStruct callback = { AURenderCallbackProc, user_data }; |
| 208 OSStatus result = AudioUnitSetProperty(audio_unit, |
| 209 kAudioUnitProperty_SetRenderCallback, |
| 210 kAudioUnitScope_Input, |
| 211 0, |
| 212 &callback, |
| 213 sizeof(callback)); |
| 214 assert(result == noErr); |
| 215 } |
| 216 |
| 217 private: |
| 218 const AudioUnit audio_unit_; |
| 219 size_t time_state_; |
| 220 pthread_cond_t* render_signal_; |
| 221 }; |
| 222 |
| 223 int main(int argc, char* argv[]) { |
| 224 pthread_mutex_t stream_mutex = PTHREAD_MUTEX_INITIALIZER; |
| 225 pthread_cond_t stream_1_render_called = PTHREAD_COND_INITIALIZER; |
| 226 pthread_cond_t stream_2_render_called = PTHREAD_COND_INITIALIZER; |
| 227 |
| 228 // Repeatedly configure and start two streams, wait for the first callback on |
| 229 // each stream, and then close and destroy the streams. To reproduce the bug, |
| 230 // manually suspend (close the lid) and resume (open the lid) the device until |
| 231 // the beeping noise stops. At this point you should see the loop hung while |
| 232 // waiting for the first callback; e.g., |
| 233 // |
| 234 // AudioOutputStream(0x00000001) created. |
| 235 // AudioOutputStream(0x00000002) created. |
| 236 // AudioOutputStream(0x00000001) started. |
| 237 // AudioOutputStream(0x00000002) started. |
| 238 // |
| 239 // Usually only a few suspend and resume cycles are necessary to reproduce the |
| 240 // issue. |
| 241 while (true) { |
| 242 printf("\n"); |
| 243 AudioOutputStream stream_1; |
| 244 AudioOutputStream stream_2; |
| 245 |
| 246 stream_1.Start(&stream_1_render_called); |
| 247 stream_2.Start(&stream_2_render_called); |
| 248 |
| 249 pthread_mutex_lock(&stream_mutex); |
| 250 pthread_cond_wait(&stream_1_render_called, &stream_mutex); |
| 251 pthread_cond_wait(&stream_2_render_called, &stream_mutex); |
| 252 pthread_mutex_unlock(&stream_mutex); |
| 253 |
| 254 stream_2.Stop(); |
| 255 stream_1.Stop(); |
| 256 } |
| 257 |
| 258 return 0; |
| 259 } |
OLD | NEW |