Implement playout stats for ios AudioDeviceModule

Bug: webrtc:378966976
Change-Id: I30169b43f7fc8aba4832a77043566129d5b087a3
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/368320
Reviewed-by: Jakob Ivarsson‎ <jakobi@webrtc.org>
Commit-Queue: Lionel Koenig <lionelk@webrtc.org>
Reviewed-by: Peter Hanspers <peterhanspers@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43422}
This commit is contained in:
Lionel Koenig Gélas 2024-11-14 14:37:28 +01:00 committed by WebRTC LUCI CQ
parent aae790e3fe
commit 999f02bd5f
4 changed files with 85 additions and 11 deletions

View File

@ -166,6 +166,8 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
bool IsInterrupted();
std::optional<AudioDeviceModule::Stats> GetStats() const;
private:
// Called by the relevant AudioSessionObserver methods on `thread_`.
void HandleInterruptionBegin();
@ -173,7 +175,7 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
void HandleValidRouteChange();
void HandleCanPlayOrRecordChange(bool can_play_or_record);
void HandleSampleRateChange();
void HandlePlayoutGlitchDetected();
void HandlePlayoutGlitchDetected(uint64_t glitch_duration_ms);
void HandleOutputVolumeChange();
// Uses current `playout_parameters_` and `record_parameters_` to inform the
@ -291,7 +293,8 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
bool has_configured_session_ RTC_GUARDED_BY(thread_);
// Counts number of detected audio glitches on the playout side.
int64_t num_detected_playout_glitches_ RTC_GUARDED_BY(thread_);
std::atomic<uint64_t> num_detected_playout_glitches_;
std::atomic<uint64_t> total_playout_glitches_duration_ms_;
int64_t last_playout_time_ RTC_GUARDED_BY(io_thread_checker_);
// Counts number of playout callbacks per call.
@ -307,6 +310,12 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
rtc::scoped_refptr<PendingTaskSafetyFlag> safety_ =
PendingTaskSafetyFlag::Create();
// Playout stats.
std::atomic<uint64_t> total_playout_samples_count_;
std::atomic<uint64_t> total_playout_samples_duration_ms_;
std::atomic<uint64_t> total_playout_delay_ms_;
std::atomic<double> hw_output_latency_;
int last_hw_output_latency_update_sample_count_;
// Ratio between mach tick units and nanosecond. Used to change mach tick
// units to nanoseconds.
double machTickUnitsToNanoseconds_;

View File

@ -62,6 +62,10 @@ namespace ios_adm {
const UInt16 kFixedPlayoutDelayEstimate = 30;
const UInt16 kFixedRecordDelayEstimate = 30;
constexpr double kMsToSecond = 1.0 / 1000.0;
constexpr double kSecondToMs = 1e3;
constexpr int kHwLatencyUpdatePeriodSeconds = 5;
using ios::CheckAndLogError;
#if !defined(NDEBUG)
@ -105,9 +109,15 @@ AudioDeviceIOS::AudioDeviceIOS(
is_interrupted_(false),
has_configured_session_(false),
num_detected_playout_glitches_(0),
total_playout_glitches_duration_ms_(0),
last_playout_time_(0),
num_playout_callbacks_(0),
last_output_volume_change_time_(0) {
last_output_volume_change_time_(0),
total_playout_samples_count_(0),
total_playout_samples_duration_ms_(0),
total_playout_delay_ms_(0),
hw_output_latency_(0),
last_hw_output_latency_update_sample_count_(0) {
LOGI() << "ctor" << ios::GetCurrentThreadDescription()
<< ",bypass_voice_processing=" << bypass_voice_processing_;
io_thread_checker_.Detach();
@ -243,6 +253,7 @@ int32_t AudioDeviceIOS::StartPlayout() {
playing_.store(1, std::memory_order_release);
num_playout_callbacks_ = 0;
num_detected_playout_glitches_ = 0;
total_playout_glitches_duration_ms_ = 0;
return 0;
}
@ -260,11 +271,13 @@ int32_t AudioDeviceIOS::StopPlayout() {
// Derive average number of calls to OnGetPlayoutData() between detected
// audio glitches and add the result to a histogram.
int64_t num_detected_playout_glitches =
num_detected_playout_glitches_.load(std::memory_order_acquire);
int average_number_of_playout_callbacks_between_glitches = 100000;
RTC_DCHECK_GE(num_playout_callbacks_, num_detected_playout_glitches_);
if (num_detected_playout_glitches_ > 0) {
RTC_DCHECK_GE(num_playout_callbacks_, num_detected_playout_glitches);
if (num_detected_playout_glitches > 0) {
average_number_of_playout_callbacks_between_glitches =
num_playout_callbacks_ / num_detected_playout_glitches_;
num_playout_callbacks_ / num_detected_playout_glitches;
}
RTC_HISTOGRAM_COUNTS_100000("WebRTC.Audio.AveragePlayoutCallbacksBetweenGlitches",
average_number_of_playout_callbacks_between_glitches);
@ -319,6 +332,7 @@ bool AudioDeviceIOS::Recording() const {
}
int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const {
// TODO: Use the actual delay.
delayMS = kFixedPlayoutDelayEstimate;
return 0;
}
@ -465,18 +479,41 @@ OSStatus AudioDeviceIOS::OnGetPlayoutData(AudioUnitRenderActionFlags* flags,
if (glitch_threshold < 120 && delta_time > 120) {
RTCLog(@"Glitch warning is ignored. Probably caused by device switch.");
} else {
thread_->PostTask(SafeTask(safety_, [this] { HandlePlayoutGlitchDetected(); }));
int64_t glitch_duration_ms = now_time - last_playout_time_;
thread_->PostTask(SafeTask(safety_, [this, glitch_duration_ms] {
HandlePlayoutGlitchDetected(glitch_duration_ms);
}));
}
}
}
last_playout_time_ = now_time;
uint16_t playout_delay_ms;
PlayoutDelay(playout_delay_ms);
if (last_hw_output_latency_update_sample_count_ >=
playout_parameters_.sample_rate() * kHwLatencyUpdatePeriodSeconds) {
// We update the hardware output latency every kHwLatencyUpdatePeriodSeconds seconds.
hw_output_latency_.store([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance].outputLatency,
std::memory_order_relaxed);
last_hw_output_latency_update_sample_count_ = 0;
}
double output_latency_ = hw_output_latency_.load(std::memory_order_relaxed) +
kMsToSecond * playout_parameters_.GetBufferSizeInMilliseconds();
// Read decoded 16-bit PCM samples from WebRTC (using a size that matches
// the native I/O audio unit) and copy the result to the audio buffer in the
// `io_data` destination.
fine_audio_buffer_->GetPlayoutData(
rtc::ArrayView<int16_t>(static_cast<int16_t*>(audio_buffer->mData), num_frames),
kFixedPlayoutDelayEstimate);
playout_delay_ms);
last_hw_output_latency_update_sample_count_ += num_frames;
total_playout_samples_count_.fetch_add(num_frames, std::memory_order_relaxed);
total_playout_samples_duration_ms_.fetch_add(
num_frames * 1000 / playout_parameters_.sample_rate(), std::memory_order_relaxed);
total_playout_delay_ms_.fetch_add(output_latency_ * kSecondToMs * num_frames,
std::memory_order_relaxed);
return noErr;
}
@ -572,6 +609,8 @@ void AudioDeviceIOS::HandleSampleRateChange() {
current_sample_rate,
(unsigned long)current_frames_per_buffer);
hw_output_latency_.store(session.outputLatency, std::memory_order_relaxed);
// Sample rate and buffer size are the same, no work to do.
if (std::abs(current_sample_rate - new_sample_rate) <= DBL_EPSILON &&
current_frames_per_buffer == new_frames_per_buffer) {
@ -624,7 +663,7 @@ void AudioDeviceIOS::HandleSampleRateChange() {
RTCLog(@"Successfully handled sample rate change.");
}
void AudioDeviceIOS::HandlePlayoutGlitchDetected() {
void AudioDeviceIOS::HandlePlayoutGlitchDetected(uint64_t glitch_duration_ms) {
RTC_DCHECK_RUN_ON(thread_);
// Don't update metrics if we're interrupted since a "glitch" is expected
// in this state.
@ -640,9 +679,10 @@ void AudioDeviceIOS::HandlePlayoutGlitchDetected() {
return;
}
num_detected_playout_glitches_++;
RTCLog(@"Number of detected playout glitches: %lld", num_detected_playout_glitches_);
total_playout_glitches_duration_ms_.fetch_add(glitch_duration_ms, std::memory_order_relaxed);
uint64_t glitch_count = num_detected_playout_glitches_.load(std::memory_order_acquire);
RTCLog(@"Number of detected playout glitches: %lld", glitch_count);
int64_t glitch_count = num_detected_playout_glitches_;
dispatch_async(dispatch_get_main_queue(), ^{
RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
[session notifyDidDetectPlayoutGlitch:glitch_count];
@ -1155,5 +1195,21 @@ int32_t AudioDeviceIOS::RecordingIsAvailable(bool& available) {
return 0;
}
std::optional<AudioDeviceModule::Stats> AudioDeviceIOS::GetStats() const {
const uint64_t total_samples_count = total_playout_samples_count_.load(std::memory_order_acquire);
AudioDeviceModule::Stats playout_stats = {
.synthesized_samples_duration_s =
kMsToSecond * total_playout_glitches_duration_ms_.load(std::memory_order_acquire),
.synthesized_samples_events = num_detected_playout_glitches_.load(std::memory_order_acquire),
.total_samples_duration_s =
kMsToSecond * total_playout_samples_duration_ms_.load(std::memory_order_acquire),
.total_playout_delay_s =
kMsToSecond * total_playout_delay_ms_.load(std::memory_order_acquire),
.total_samples_count = total_samples_count,
};
return playout_stats;
} // namespace ios_adm
} // namespace ios_adm
} // namespace webrtc

View File

@ -127,6 +127,8 @@ class AudioDeviceModuleIOS : public AudioDeviceModule {
int32_t GetPlayoutUnderrunCount() const override;
std::optional<Stats> GetStats() const override;
#if defined(WEBRTC_IOS)
int GetPlayoutAudioParameters(AudioParameters* params) const override;
int GetRecordAudioParameters(AudioParameters* params) const override;

View File

@ -651,6 +651,13 @@ AudioDeviceModuleIOS::AudioDeviceModuleIOS(bool bypass_voice_processing,
return ok;
}
std::optional<AudioDeviceModule::Stats> AudioDeviceModuleIOS::GetStats() const {
if (audio_device_ == nullptr) {
return std::nullopt;
};
return audio_device_->GetStats();
}
#if defined(WEBRTC_IOS)
int AudioDeviceModuleIOS::GetPlayoutAudioParameters(
AudioParameters* params) const {