Bug 776137 - Apply latency correction to audio stream position on macOS. r=padenot
authorMatthew Gregan <kinetik@flim.org>
Wed, 28 Mar 2018 11:44:15 +1300
changeset 773622 5cf87a14afb5ecb5e9b38608312a9a32d4edaa84
parent 773621 54b5c168cc34fb0f8793fcca70174b8effb3d734
child 773623 b09e7b25bcbeb5ece5ee280d2037983f98267d75
push id104266
push userbmo:hsivonen@hsivonen.fi
push dateWed, 28 Mar 2018 07:33:03 +0000
reviewerspadenot
bugs776137
milestone61.0a1
Bug 776137 - Apply latency correction to audio stream position on macOS. r=padenot
media/libcubeb/README_MOZILLA
media/libcubeb/gtest/test_duplex.cpp
media/libcubeb/gtest/test_loopback.cpp
media/libcubeb/src/cubeb_audiounit.cpp
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was 7401fc25a9d87ed57d49648883e3e5658305c1cb (2018-03-26 16:54:01 +0200)
+The git commit ID used was f34f392de5158feb46202e8ae58528bfb42f2759 (2018-03-27 17:45:10 -0400)
--- a/media/libcubeb/gtest/test_duplex.cpp
+++ b/media/libcubeb/gtest/test_duplex.cpp
@@ -154,20 +154,22 @@ TEST(cubeb, duplex_collection_change)
   std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
     cleanup_cubeb_at_exit(ctx, cubeb_destroy);
 
   /* typical user-case: mono input, stereo output, low latency. */
   input_params.format = STREAM_FORMAT;
   input_params.rate = 48000;
   input_params.channels = 1;
   input_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.prefs = CUBEB_STREAM_PREF_NONE;
   output_params.format = STREAM_FORMAT;
   output_params.rate = 48000;
   output_params.channels = 2;
   output_params.layout = CUBEB_LAYOUT_STEREO;
+  output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
                         NULL, &input_params, NULL, &output_params,
                         latency_frames, data_cb_duplex, state_cb_duplex, nullptr);
   ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
--- a/media/libcubeb/gtest/test_loopback.cpp
+++ b/media/libcubeb/gtest/test_loopback.cpp
@@ -343,16 +343,17 @@ void run_loopback_separate_streams_test(
   input_params.rate = SAMPLE_FREQUENCY;
   input_params.channels = 1;
   input_params.layout = CUBEB_LAYOUT_MONO;
   input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
   output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
   output_params.rate = SAMPLE_FREQUENCY;
   output_params.channels = 1;
   output_params.layout = CUBEB_LAYOUT_MONO;
+  output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
   std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
   ASSERT_TRUE(!!user_data) << "Error allocating user data";
 
   r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
 
   /* setup an input stream with loopback */
@@ -510,16 +511,17 @@ void run_loopback_device_selection_test(
   input_params.rate = SAMPLE_FREQUENCY;
   input_params.channels = 1;
   input_params.layout = CUBEB_LAYOUT_MONO;
   input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
   output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
   output_params.rate = SAMPLE_FREQUENCY;
   output_params.channels = 1;
   output_params.layout = CUBEB_LAYOUT_MONO;
+  output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
   std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
   ASSERT_TRUE(!!user_data) << "Error allocating user data";
 
   r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
 
   /* setup an input stream with loopback */
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -65,16 +65,17 @@ const uint32_t SAFE_MIN_LATENCY_FRAMES =
 const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
 
 void audiounit_stream_stop_internal(cubeb_stream * stm);
 void audiounit_stream_start_internal(cubeb_stream * stm);
 static void audiounit_close_stream(cubeb_stream *stm);
 static int audiounit_setup_stream(cubeb_stream *stm);
 static vector<AudioObjectID>
 audiounit_get_devices_of_type(cubeb_device_type devtype);
+static UInt32 audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope);
 
 extern cubeb_ops const audiounit_ops;
 
 struct cubeb {
   cubeb_ops const * ops = &audiounit_ops;
   owned_critical_section mutex;
   atomic<int> active_streams{ 0 };
   uint32_t global_latency_frames = 0;
@@ -173,18 +174,17 @@ struct cubeb_stream {
   /* Frame counters */
   atomic<uint64_t> frames_played{ 0 };
   uint64_t frames_queued = 0;
   atomic<int64_t> frames_read{ 0 };
   atomic<bool> shutdown{ true };
   atomic<bool> draining{ false };
   /* Latency requested by the user. */
   uint32_t latency_frames = 0;
-  atomic<uint64_t> current_latency_frames{ 0 };
-  uint64_t hw_latency_frames = UINT64_MAX;
+  atomic<uint32_t> current_latency_frames{ 0 };
   atomic<float> panning{ 0 };
   unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
   /* This is true if a device change callback is currently running.  */
   atomic<bool> switching_device{ false };
   atomic<bool> buffer_size_change_state{ false };
   AudioDeviceID aggregate_device_id = 0;    // the aggregate device id
   AudioObjectID plugin_id = 0;              // used to create aggregate device
   /* Mixer interface */
@@ -315,29 +315,16 @@ AudioConvertHostTimeToNanos(uint64_t hos
   if (timebase_info.numer != timebase_info.denom) {
     answer *= timebase_info.numer;
     answer /= timebase_info.denom;
   }
   return (uint64_t)answer;
 }
 #endif
 
-static int64_t
-audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream)
-{
-  if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) {
-    return 0;
-  }
-
-  uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
-  uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
-
-  return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
-}
-
 static void
 audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames)
 {
   stm->mutex.assert_current_thread_owns();
   assert(stm->context->active_streams == 1);
   stm->context->global_latency_frames = latency_frames;
 }
 
@@ -521,17 +508,16 @@ audiounit_output_callback(void * user_pt
   void * output_buffer = NULL, * input_buffer = NULL;
 
   if (stm->shutdown) {
     ALOG("(%p) output shutdown.", stm);
     audiounit_make_silent(&outBufferList->mBuffers[0]);
     return noErr;
   }
 
-  stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm);
   if (stm->draining) {
     OSStatus r = AudioOutputUnitStop(stm->output_unit);
     assert(r == 0);
     if (stm->input_unit) {
       r = AudioOutputUnitStop(stm->input_unit);
       assert(r == 0);
     }
     stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
@@ -2536,16 +2522,24 @@ audiounit_setup_stream(cubeb_stream * st
   }
 
   if (stm->output_unit != NULL) {
     r = AudioUnitInitialize(stm->output_unit);
     if (r != noErr) {
       LOG("AudioUnitInitialize/output rv=%d", r);
       return CUBEB_ERROR;
     }
+
+    stm->current_latency_frames = audiounit_get_device_presentation_latency(stm->output_device.id, kAudioDevicePropertyScopeOutput);
+
+    Float64 unit_s;
+    UInt32 size = sizeof(unit_s);
+    if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &unit_s, &size) == noErr) {
+      stm->current_latency_frames += static_cast<uint32_t>(unit_s * stm->output_desc.mSampleRate);
+    }
   }
 
   if (stm->input_unit && stm->output_unit) {
     // According to the I/O hardware rate it is expected a specific pattern of callbacks
     // for example is input is 44100 and output is 48000 we expected no more than 2
     // out callback in a row.
     stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate);
   }
@@ -2759,95 +2753,32 @@ audiounit_stream_stop(cubeb_stream * stm
   LOG("Cubeb stream (%p) stopped successfully.", stm);
   return CUBEB_OK;
 }
 
 static int
 audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position)
 {
   assert(stm);
-  *position = stm->frames_played;
+  if (stm->current_latency_frames > stm->frames_played) {
+    *position = 0;
+  } else {
+    *position = stm->frames_played - stm->current_latency_frames;
+  }
   return CUBEB_OK;
 }
 
 int
 audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
 {
 #if TARGET_OS_IPHONE
   //TODO
   return CUBEB_ERROR_NOT_SUPPORTED;
 #else
-  auto_lock lock(stm->mutex);
-  if (stm->hw_latency_frames == UINT64_MAX) {
-    UInt32 size;
-    uint32_t device_latency_frames, device_safety_offset;
-    double unit_latency_sec;
-    AudioDeviceID output_device_id;
-    OSStatus r;
-    AudioObjectPropertyAddress latency_address = {
-      kAudioDevicePropertyLatency,
-      kAudioDevicePropertyScopeOutput,
-      kAudioObjectPropertyElementMaster
-    };
-    AudioObjectPropertyAddress safety_offset_address = {
-      kAudioDevicePropertySafetyOffset,
-      kAudioDevicePropertyScopeOutput,
-      kAudioObjectPropertyElementMaster
-    };
-
-    output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
-    if (output_device_id == kAudioObjectUnknown) {
-      return CUBEB_ERROR;
-    }
-
-    size = sizeof(unit_latency_sec);
-    r = AudioUnitGetProperty(stm->output_unit,
-                             kAudioUnitProperty_Latency,
-                             kAudioUnitScope_Global,
-                             0,
-                             &unit_latency_sec,
-                             &size);
-    if (r != noErr) {
-      LOG("AudioUnitGetProperty/kAudioUnitProperty_Latency rv=%d", r);
-      return CUBEB_ERROR;
-    }
-
-    size = sizeof(device_latency_frames);
-    r = AudioObjectGetPropertyData(output_device_id,
-                                   &latency_address,
-                                   0,
-                                   NULL,
-                                   &size,
-                                   &device_latency_frames);
-    if (r != noErr) {
-      LOG("AudioUnitGetPropertyData/latency_frames rv=%d", r);
-      return CUBEB_ERROR;
-    }
-
-    size = sizeof(device_safety_offset);
-    r = AudioObjectGetPropertyData(output_device_id,
-                                   &safety_offset_address,
-                                   0,
-                                   NULL,
-                                   &size,
-                                   &device_safety_offset);
-    if (r != noErr) {
-      LOG("AudioUnitGetPropertyData/safety_offset rv=%d", r);
-      return CUBEB_ERROR;
-    }
-
-    /* This part is fixed and depend on the stream parameter and the hardware. */
-    stm->hw_latency_frames =
-      static_cast<uint32_t>(unit_latency_sec * stm->output_desc.mSampleRate)
-      + device_latency_frames
-      + device_safety_offset;
-  }
-
-  *latency = stm->hw_latency_frames + stm->current_latency_frames;
-
+  *latency = stm->current_latency_frames;
   return CUBEB_OK;
 #endif
 }
 
 static int
 audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
 {
   assert(stm->output_unit);
@@ -3084,40 +3015,34 @@ audiounit_get_available_samplerate(Audio
   }
 
 }
 
 static UInt32
 audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope)
 {
   AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
-  UInt32 size, dev, stream = 0, offset;
+  UInt32 size, dev, stream = 0;
   AudioStreamID sid[1];
 
   adr.mSelector = kAudioDevicePropertyLatency;
   size = sizeof(UInt32);
   if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &dev) != noErr) {
     dev = 0;
   }
 
   adr.mSelector = kAudioDevicePropertyStreams;
   size = sizeof(sid);
   if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, sid) == noErr) {
     adr.mSelector = kAudioStreamPropertyLatency;
     size = sizeof(UInt32);
     AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream);
   }
 
-  adr.mSelector = kAudioDevicePropertySafetyOffset;
-  size = sizeof(UInt32);
-  if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr) {
-    offset = 0;
-  }
-
-  return dev + stream + offset;
+  return dev + stream;
 }
 
 static int
 audiounit_create_device_from_hwdev(cubeb_device_info * ret, AudioObjectID devid, cubeb_device_type type)
 {
   AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster };
   UInt32 size, ch, latency;
   CFStringRef str = NULL;