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:
parent
aae790e3fe
commit
999f02bd5f
@ -166,6 +166,8 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
|
|||||||
|
|
||||||
bool IsInterrupted();
|
bool IsInterrupted();
|
||||||
|
|
||||||
|
std::optional<AudioDeviceModule::Stats> GetStats() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Called by the relevant AudioSessionObserver methods on `thread_`.
|
// Called by the relevant AudioSessionObserver methods on `thread_`.
|
||||||
void HandleInterruptionBegin();
|
void HandleInterruptionBegin();
|
||||||
@ -173,7 +175,7 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
|
|||||||
void HandleValidRouteChange();
|
void HandleValidRouteChange();
|
||||||
void HandleCanPlayOrRecordChange(bool can_play_or_record);
|
void HandleCanPlayOrRecordChange(bool can_play_or_record);
|
||||||
void HandleSampleRateChange();
|
void HandleSampleRateChange();
|
||||||
void HandlePlayoutGlitchDetected();
|
void HandlePlayoutGlitchDetected(uint64_t glitch_duration_ms);
|
||||||
void HandleOutputVolumeChange();
|
void HandleOutputVolumeChange();
|
||||||
|
|
||||||
// Uses current `playout_parameters_` and `record_parameters_` to inform the
|
// 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_);
|
bool has_configured_session_ RTC_GUARDED_BY(thread_);
|
||||||
|
|
||||||
// Counts number of detected audio glitches on the playout side.
|
// 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_);
|
int64_t last_playout_time_ RTC_GUARDED_BY(io_thread_checker_);
|
||||||
|
|
||||||
// Counts number of playout callbacks per call.
|
// Counts number of playout callbacks per call.
|
||||||
@ -307,6 +310,12 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
|
|||||||
rtc::scoped_refptr<PendingTaskSafetyFlag> safety_ =
|
rtc::scoped_refptr<PendingTaskSafetyFlag> safety_ =
|
||||||
PendingTaskSafetyFlag::Create();
|
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
|
// Ratio between mach tick units and nanosecond. Used to change mach tick
|
||||||
// units to nanoseconds.
|
// units to nanoseconds.
|
||||||
double machTickUnitsToNanoseconds_;
|
double machTickUnitsToNanoseconds_;
|
||||||
|
|||||||
@ -62,6 +62,10 @@ namespace ios_adm {
|
|||||||
const UInt16 kFixedPlayoutDelayEstimate = 30;
|
const UInt16 kFixedPlayoutDelayEstimate = 30;
|
||||||
const UInt16 kFixedRecordDelayEstimate = 30;
|
const UInt16 kFixedRecordDelayEstimate = 30;
|
||||||
|
|
||||||
|
constexpr double kMsToSecond = 1.0 / 1000.0;
|
||||||
|
constexpr double kSecondToMs = 1e3;
|
||||||
|
constexpr int kHwLatencyUpdatePeriodSeconds = 5;
|
||||||
|
|
||||||
using ios::CheckAndLogError;
|
using ios::CheckAndLogError;
|
||||||
|
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
@ -105,9 +109,15 @@ AudioDeviceIOS::AudioDeviceIOS(
|
|||||||
is_interrupted_(false),
|
is_interrupted_(false),
|
||||||
has_configured_session_(false),
|
has_configured_session_(false),
|
||||||
num_detected_playout_glitches_(0),
|
num_detected_playout_glitches_(0),
|
||||||
|
total_playout_glitches_duration_ms_(0),
|
||||||
last_playout_time_(0),
|
last_playout_time_(0),
|
||||||
num_playout_callbacks_(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()
|
LOGI() << "ctor" << ios::GetCurrentThreadDescription()
|
||||||
<< ",bypass_voice_processing=" << bypass_voice_processing_;
|
<< ",bypass_voice_processing=" << bypass_voice_processing_;
|
||||||
io_thread_checker_.Detach();
|
io_thread_checker_.Detach();
|
||||||
@ -243,6 +253,7 @@ int32_t AudioDeviceIOS::StartPlayout() {
|
|||||||
playing_.store(1, std::memory_order_release);
|
playing_.store(1, std::memory_order_release);
|
||||||
num_playout_callbacks_ = 0;
|
num_playout_callbacks_ = 0;
|
||||||
num_detected_playout_glitches_ = 0;
|
num_detected_playout_glitches_ = 0;
|
||||||
|
total_playout_glitches_duration_ms_ = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,11 +271,13 @@ int32_t AudioDeviceIOS::StopPlayout() {
|
|||||||
|
|
||||||
// Derive average number of calls to OnGetPlayoutData() between detected
|
// Derive average number of calls to OnGetPlayoutData() between detected
|
||||||
// audio glitches and add the result to a histogram.
|
// 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;
|
int average_number_of_playout_callbacks_between_glitches = 100000;
|
||||||
RTC_DCHECK_GE(num_playout_callbacks_, num_detected_playout_glitches_);
|
RTC_DCHECK_GE(num_playout_callbacks_, num_detected_playout_glitches);
|
||||||
if (num_detected_playout_glitches_ > 0) {
|
if (num_detected_playout_glitches > 0) {
|
||||||
average_number_of_playout_callbacks_between_glitches =
|
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",
|
RTC_HISTOGRAM_COUNTS_100000("WebRTC.Audio.AveragePlayoutCallbacksBetweenGlitches",
|
||||||
average_number_of_playout_callbacks_between_glitches);
|
average_number_of_playout_callbacks_between_glitches);
|
||||||
@ -319,6 +332,7 @@ bool AudioDeviceIOS::Recording() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const {
|
int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const {
|
||||||
|
// TODO: Use the actual delay.
|
||||||
delayMS = kFixedPlayoutDelayEstimate;
|
delayMS = kFixedPlayoutDelayEstimate;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -465,18 +479,41 @@ OSStatus AudioDeviceIOS::OnGetPlayoutData(AudioUnitRenderActionFlags* flags,
|
|||||||
if (glitch_threshold < 120 && delta_time > 120) {
|
if (glitch_threshold < 120 && delta_time > 120) {
|
||||||
RTCLog(@"Glitch warning is ignored. Probably caused by device switch.");
|
RTCLog(@"Glitch warning is ignored. Probably caused by device switch.");
|
||||||
} else {
|
} 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;
|
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
|
// 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
|
// the native I/O audio unit) and copy the result to the audio buffer in the
|
||||||
// `io_data` destination.
|
// `io_data` destination.
|
||||||
fine_audio_buffer_->GetPlayoutData(
|
fine_audio_buffer_->GetPlayoutData(
|
||||||
rtc::ArrayView<int16_t>(static_cast<int16_t*>(audio_buffer->mData), num_frames),
|
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;
|
return noErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -572,6 +609,8 @@ void AudioDeviceIOS::HandleSampleRateChange() {
|
|||||||
current_sample_rate,
|
current_sample_rate,
|
||||||
(unsigned long)current_frames_per_buffer);
|
(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.
|
// Sample rate and buffer size are the same, no work to do.
|
||||||
if (std::abs(current_sample_rate - new_sample_rate) <= DBL_EPSILON &&
|
if (std::abs(current_sample_rate - new_sample_rate) <= DBL_EPSILON &&
|
||||||
current_frames_per_buffer == new_frames_per_buffer) {
|
current_frames_per_buffer == new_frames_per_buffer) {
|
||||||
@ -624,7 +663,7 @@ void AudioDeviceIOS::HandleSampleRateChange() {
|
|||||||
RTCLog(@"Successfully handled sample rate change.");
|
RTCLog(@"Successfully handled sample rate change.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioDeviceIOS::HandlePlayoutGlitchDetected() {
|
void AudioDeviceIOS::HandlePlayoutGlitchDetected(uint64_t glitch_duration_ms) {
|
||||||
RTC_DCHECK_RUN_ON(thread_);
|
RTC_DCHECK_RUN_ON(thread_);
|
||||||
// Don't update metrics if we're interrupted since a "glitch" is expected
|
// Don't update metrics if we're interrupted since a "glitch" is expected
|
||||||
// in this state.
|
// in this state.
|
||||||
@ -640,9 +679,10 @@ void AudioDeviceIOS::HandlePlayoutGlitchDetected() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
num_detected_playout_glitches_++;
|
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(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
|
RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
|
||||||
[session notifyDidDetectPlayoutGlitch:glitch_count];
|
[session notifyDidDetectPlayoutGlitch:glitch_count];
|
||||||
@ -1155,5 +1195,21 @@ int32_t AudioDeviceIOS::RecordingIsAvailable(bool& available) {
|
|||||||
return 0;
|
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 ios_adm
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -127,6 +127,8 @@ class AudioDeviceModuleIOS : public AudioDeviceModule {
|
|||||||
|
|
||||||
int32_t GetPlayoutUnderrunCount() const override;
|
int32_t GetPlayoutUnderrunCount() const override;
|
||||||
|
|
||||||
|
std::optional<Stats> GetStats() const override;
|
||||||
|
|
||||||
#if defined(WEBRTC_IOS)
|
#if defined(WEBRTC_IOS)
|
||||||
int GetPlayoutAudioParameters(AudioParameters* params) const override;
|
int GetPlayoutAudioParameters(AudioParameters* params) const override;
|
||||||
int GetRecordAudioParameters(AudioParameters* params) const override;
|
int GetRecordAudioParameters(AudioParameters* params) const override;
|
||||||
|
|||||||
@ -651,6 +651,13 @@ AudioDeviceModuleIOS::AudioDeviceModuleIOS(bool bypass_voice_processing,
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<AudioDeviceModule::Stats> AudioDeviceModuleIOS::GetStats() const {
|
||||||
|
if (audio_device_ == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
};
|
||||||
|
return audio_device_->GetStats();
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(WEBRTC_IOS)
|
#if defined(WEBRTC_IOS)
|
||||||
int AudioDeviceModuleIOS::GetPlayoutAudioParameters(
|
int AudioDeviceModuleIOS::GetPlayoutAudioParameters(
|
||||||
AudioParameters* params) const {
|
AudioParameters* params) const {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user