153a5a1b3Sopenharmony_ci/*** 253a5a1b3Sopenharmony_ci This file is part of PulseAudio. 353a5a1b3Sopenharmony_ci 453a5a1b3Sopenharmony_ci Copyright 2013 Collabora Ltd. 553a5a1b3Sopenharmony_ci Author: Arun Raghavan <arun.raghavan@collabora.co.uk> 653a5a1b3Sopenharmony_ci 753a5a1b3Sopenharmony_ci PulseAudio is free software; you can redistribute it and/or modify 853a5a1b3Sopenharmony_ci it under the terms of the GNU Lesser General Public License as published 953a5a1b3Sopenharmony_ci by the Free Software Foundation; either version 2.1 of the License, 1053a5a1b3Sopenharmony_ci or (at your option) any later version. 1153a5a1b3Sopenharmony_ci 1253a5a1b3Sopenharmony_ci PulseAudio is distributed in the hope that it will be useful, but 1353a5a1b3Sopenharmony_ci WITHOUT ANY WARRANTY; without even the implied warranty of 1453a5a1b3Sopenharmony_ci MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1553a5a1b3Sopenharmony_ci General Public License for more details. 1653a5a1b3Sopenharmony_ci 1753a5a1b3Sopenharmony_ci You should have received a copy of the GNU Lesser General Public License 1853a5a1b3Sopenharmony_ci along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 1953a5a1b3Sopenharmony_ci***/ 2053a5a1b3Sopenharmony_ci 2153a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H 2253a5a1b3Sopenharmony_ci#include <config.h> 2353a5a1b3Sopenharmony_ci#endif 2453a5a1b3Sopenharmony_ci 2553a5a1b3Sopenharmony_ci#include <math.h> 2653a5a1b3Sopenharmony_ci 2753a5a1b3Sopenharmony_ci#include <pulsecore/log.h> 2853a5a1b3Sopenharmony_ci#include <pulsecore/macro.h> 2953a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h> 3053a5a1b3Sopenharmony_ci 3153a5a1b3Sopenharmony_ci#include "lo-test-util.h" 3253a5a1b3Sopenharmony_ci 3353a5a1b3Sopenharmony_ci/* Keep the frequency high so RMS over ranges of a few ms remains relatively 3453a5a1b3Sopenharmony_ci * high as well */ 3553a5a1b3Sopenharmony_ci#define TONE_HZ 4410 3653a5a1b3Sopenharmony_ci 3753a5a1b3Sopenharmony_cistatic void nop_free_cb(void *p) { 3853a5a1b3Sopenharmony_ci} 3953a5a1b3Sopenharmony_ci 4053a5a1b3Sopenharmony_cistatic void underflow_cb(struct pa_stream *s, void *userdata) { 4153a5a1b3Sopenharmony_ci pa_log_warn("Underflow"); 4253a5a1b3Sopenharmony_ci} 4353a5a1b3Sopenharmony_ci 4453a5a1b3Sopenharmony_cistatic void overflow_cb(struct pa_stream *s, void *userdata) { 4553a5a1b3Sopenharmony_ci pa_log_warn("Overlow"); 4653a5a1b3Sopenharmony_ci} 4753a5a1b3Sopenharmony_ci 4853a5a1b3Sopenharmony_ci/* 4953a5a1b3Sopenharmony_ci * We run a simple volume calibration so that we know we can detect the signal 5053a5a1b3Sopenharmony_ci * being played back. We start with the playback stream at 100% volume, and 5153a5a1b3Sopenharmony_ci * capture at 0. 5253a5a1b3Sopenharmony_ci * 5353a5a1b3Sopenharmony_ci * First, we then play a sine wave and increase the capture volume till the 5453a5a1b3Sopenharmony_ci * signal is clearly received. 5553a5a1b3Sopenharmony_ci * 5653a5a1b3Sopenharmony_ci * Next, we play back silence and make sure that the level is low enough to 5753a5a1b3Sopenharmony_ci * distinguish from when playback is happening. 5853a5a1b3Sopenharmony_ci * 5953a5a1b3Sopenharmony_ci * Finally, we hand off to the real read/write callbacks to run the actual 6053a5a1b3Sopenharmony_ci * test. 6153a5a1b3Sopenharmony_ci */ 6253a5a1b3Sopenharmony_ci 6353a5a1b3Sopenharmony_cienum { 6453a5a1b3Sopenharmony_ci CALIBRATION_ONE, 6553a5a1b3Sopenharmony_ci CALIBRATION_ZERO, 6653a5a1b3Sopenharmony_ci CALIBRATION_DONE, 6753a5a1b3Sopenharmony_ci}; 6853a5a1b3Sopenharmony_ci 6953a5a1b3Sopenharmony_cistatic int cal_state = CALIBRATION_ONE; 7053a5a1b3Sopenharmony_ci 7153a5a1b3Sopenharmony_cistatic void calibrate_write_cb(pa_stream *s, size_t nbytes, void *userdata) { 7253a5a1b3Sopenharmony_ci pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; 7353a5a1b3Sopenharmony_ci int i, nsamp = nbytes / ctx->fs; 7453a5a1b3Sopenharmony_ci float tmp[nsamp][2]; 7553a5a1b3Sopenharmony_ci static int count = 0; 7653a5a1b3Sopenharmony_ci 7753a5a1b3Sopenharmony_ci /* Write out a sine tone */ 7853a5a1b3Sopenharmony_ci for (i = 0; i < nsamp; i++) 7953a5a1b3Sopenharmony_ci tmp[i][0] = tmp[i][1] = cal_state == CALIBRATION_ONE ? sinf(count++ * TONE_HZ * 2 * M_PI / ctx->sample_spec.rate) : 0.0f; 8053a5a1b3Sopenharmony_ci 8153a5a1b3Sopenharmony_ci pa_assert_se(pa_stream_write(s, &tmp, nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE) == 0); 8253a5a1b3Sopenharmony_ci 8353a5a1b3Sopenharmony_ci if (cal_state == CALIBRATION_DONE) 8453a5a1b3Sopenharmony_ci pa_stream_set_write_callback(s, ctx->write_cb, ctx); 8553a5a1b3Sopenharmony_ci} 8653a5a1b3Sopenharmony_ci 8753a5a1b3Sopenharmony_cistatic void calibrate_read_cb(pa_stream *s, size_t nbytes, void *userdata) { 8853a5a1b3Sopenharmony_ci pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; 8953a5a1b3Sopenharmony_ci static double v = 0; 9053a5a1b3Sopenharmony_ci static int skip = 0, confirm; 9153a5a1b3Sopenharmony_ci 9253a5a1b3Sopenharmony_ci pa_cvolume vol; 9353a5a1b3Sopenharmony_ci pa_operation *o; 9453a5a1b3Sopenharmony_ci int nsamp; 9553a5a1b3Sopenharmony_ci float *in; 9653a5a1b3Sopenharmony_ci size_t l; 9753a5a1b3Sopenharmony_ci 9853a5a1b3Sopenharmony_ci pa_assert_se(pa_stream_peek(s, (const void **)&in, &l) == 0); 9953a5a1b3Sopenharmony_ci 10053a5a1b3Sopenharmony_ci nsamp = l / ctx->fs; 10153a5a1b3Sopenharmony_ci 10253a5a1b3Sopenharmony_ci /* For each state or volume step change, throw out a few samples so we know 10353a5a1b3Sopenharmony_ci * we're seeing the changed samples. */ 10453a5a1b3Sopenharmony_ci if (skip++ < 100) 10553a5a1b3Sopenharmony_ci goto out; 10653a5a1b3Sopenharmony_ci else 10753a5a1b3Sopenharmony_ci skip = 0; 10853a5a1b3Sopenharmony_ci 10953a5a1b3Sopenharmony_ci switch (cal_state) { 11053a5a1b3Sopenharmony_ci case CALIBRATION_ONE: 11153a5a1b3Sopenharmony_ci /* Try to detect the sine wave. RMS is 0.5, */ 11253a5a1b3Sopenharmony_ci if (pa_rms(in, nsamp) < 0.40f) { 11353a5a1b3Sopenharmony_ci confirm = 0; 11453a5a1b3Sopenharmony_ci v += 0.02f; 11553a5a1b3Sopenharmony_ci 11653a5a1b3Sopenharmony_ci if (v > 1.0) { 11753a5a1b3Sopenharmony_ci pa_log_error("Capture signal too weak at 100%% volume (%g). Giving up.", pa_rms(in, nsamp)); 11853a5a1b3Sopenharmony_ci pa_assert_not_reached(); 11953a5a1b3Sopenharmony_ci } 12053a5a1b3Sopenharmony_ci 12153a5a1b3Sopenharmony_ci pa_cvolume_set(&vol, ctx->sample_spec.channels, v * PA_VOLUME_NORM); 12253a5a1b3Sopenharmony_ci o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL); 12353a5a1b3Sopenharmony_ci pa_assert(o != NULL); 12453a5a1b3Sopenharmony_ci pa_operation_unref(o); 12553a5a1b3Sopenharmony_ci } else { 12653a5a1b3Sopenharmony_ci /* Make sure the signal strength is steadily above our threshold */ 12753a5a1b3Sopenharmony_ci if (++confirm > 5) { 12853a5a1b3Sopenharmony_ci#if 0 12953a5a1b3Sopenharmony_ci pa_log_debug(stderr, "Capture volume = %g (%g)", v, pa_rms(in, nsamp)); 13053a5a1b3Sopenharmony_ci#endif 13153a5a1b3Sopenharmony_ci cal_state = CALIBRATION_ZERO; 13253a5a1b3Sopenharmony_ci } 13353a5a1b3Sopenharmony_ci } 13453a5a1b3Sopenharmony_ci 13553a5a1b3Sopenharmony_ci break; 13653a5a1b3Sopenharmony_ci 13753a5a1b3Sopenharmony_ci case CALIBRATION_ZERO: 13853a5a1b3Sopenharmony_ci /* Now make sure silence doesn't trigger a false positive because 13953a5a1b3Sopenharmony_ci * of noise. */ 14053a5a1b3Sopenharmony_ci if (pa_rms(in, nsamp) > 0.1f) { 14153a5a1b3Sopenharmony_ci pa_log_warn("Too much noise on capture (%g). Giving up.", pa_rms(in, nsamp)); 14253a5a1b3Sopenharmony_ci pa_assert_not_reached(); 14353a5a1b3Sopenharmony_ci } 14453a5a1b3Sopenharmony_ci 14553a5a1b3Sopenharmony_ci cal_state = CALIBRATION_DONE; 14653a5a1b3Sopenharmony_ci pa_stream_set_read_callback(s, ctx->read_cb, ctx); 14753a5a1b3Sopenharmony_ci 14853a5a1b3Sopenharmony_ci break; 14953a5a1b3Sopenharmony_ci 15053a5a1b3Sopenharmony_ci default: 15153a5a1b3Sopenharmony_ci break; 15253a5a1b3Sopenharmony_ci } 15353a5a1b3Sopenharmony_ci 15453a5a1b3Sopenharmony_ciout: 15553a5a1b3Sopenharmony_ci pa_stream_drop(s); 15653a5a1b3Sopenharmony_ci} 15753a5a1b3Sopenharmony_ci 15853a5a1b3Sopenharmony_ci/* This routine is called whenever the stream state changes */ 15953a5a1b3Sopenharmony_cistatic void stream_state_callback(pa_stream *s, void *userdata) { 16053a5a1b3Sopenharmony_ci pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; 16153a5a1b3Sopenharmony_ci 16253a5a1b3Sopenharmony_ci switch (pa_stream_get_state(s)) { 16353a5a1b3Sopenharmony_ci case PA_STREAM_UNCONNECTED: 16453a5a1b3Sopenharmony_ci case PA_STREAM_CREATING: 16553a5a1b3Sopenharmony_ci case PA_STREAM_TERMINATED: 16653a5a1b3Sopenharmony_ci break; 16753a5a1b3Sopenharmony_ci 16853a5a1b3Sopenharmony_ci case PA_STREAM_READY: { 16953a5a1b3Sopenharmony_ci pa_cvolume vol; 17053a5a1b3Sopenharmony_ci pa_operation *o; 17153a5a1b3Sopenharmony_ci 17253a5a1b3Sopenharmony_ci /* Set volumes for calibration */ 17353a5a1b3Sopenharmony_ci if (s == ctx->play_stream) { 17453a5a1b3Sopenharmony_ci pa_cvolume_set(&vol, ctx->sample_spec.channels, PA_VOLUME_NORM); 17553a5a1b3Sopenharmony_ci o = pa_context_set_sink_input_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL); 17653a5a1b3Sopenharmony_ci } else { 17753a5a1b3Sopenharmony_ci pa_cvolume_set(&vol, ctx->sample_spec.channels, pa_sw_volume_from_linear(0.0)); 17853a5a1b3Sopenharmony_ci o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL); 17953a5a1b3Sopenharmony_ci } 18053a5a1b3Sopenharmony_ci 18153a5a1b3Sopenharmony_ci if (!o) { 18253a5a1b3Sopenharmony_ci pa_log_error("Could not set stream volume: %s", pa_strerror(pa_context_errno(ctx->context))); 18353a5a1b3Sopenharmony_ci pa_assert_not_reached(); 18453a5a1b3Sopenharmony_ci } else 18553a5a1b3Sopenharmony_ci pa_operation_unref(o); 18653a5a1b3Sopenharmony_ci 18753a5a1b3Sopenharmony_ci break; 18853a5a1b3Sopenharmony_ci } 18953a5a1b3Sopenharmony_ci 19053a5a1b3Sopenharmony_ci case PA_STREAM_FAILED: 19153a5a1b3Sopenharmony_ci default: 19253a5a1b3Sopenharmony_ci pa_log_error("Stream error: %s", pa_strerror(pa_context_errno(ctx->context))); 19353a5a1b3Sopenharmony_ci pa_assert_not_reached(); 19453a5a1b3Sopenharmony_ci } 19553a5a1b3Sopenharmony_ci} 19653a5a1b3Sopenharmony_ci 19753a5a1b3Sopenharmony_ci/* This is called whenever the context status changes */ 19853a5a1b3Sopenharmony_cistatic void context_state_callback(pa_context *c, void *userdata) { 19953a5a1b3Sopenharmony_ci pa_lo_test_context *ctx = (pa_lo_test_context *) userdata; 20053a5a1b3Sopenharmony_ci pa_mainloop_api *api; 20153a5a1b3Sopenharmony_ci 20253a5a1b3Sopenharmony_ci switch (pa_context_get_state(c)) { 20353a5a1b3Sopenharmony_ci case PA_CONTEXT_CONNECTING: 20453a5a1b3Sopenharmony_ci case PA_CONTEXT_AUTHORIZING: 20553a5a1b3Sopenharmony_ci case PA_CONTEXT_SETTING_NAME: 20653a5a1b3Sopenharmony_ci break; 20753a5a1b3Sopenharmony_ci 20853a5a1b3Sopenharmony_ci case PA_CONTEXT_READY: { 20953a5a1b3Sopenharmony_ci pa_buffer_attr buffer_attr; 21053a5a1b3Sopenharmony_ci 21153a5a1b3Sopenharmony_ci pa_thread_make_realtime(4); 21253a5a1b3Sopenharmony_ci 21353a5a1b3Sopenharmony_ci /* Create playback stream */ 21453a5a1b3Sopenharmony_ci buffer_attr.maxlength = -1; 21553a5a1b3Sopenharmony_ci buffer_attr.tlength = ctx->sample_spec.rate * ctx->fs * ctx->play_latency / 1000; 21653a5a1b3Sopenharmony_ci buffer_attr.prebuf = 0; /* Setting prebuf to 0 guarantees us the stream will run synchronously, no matter what */ 21753a5a1b3Sopenharmony_ci buffer_attr.minreq = -1; 21853a5a1b3Sopenharmony_ci buffer_attr.fragsize = -1; 21953a5a1b3Sopenharmony_ci 22053a5a1b3Sopenharmony_ci ctx->play_stream = pa_stream_new(c, "loopback: play", &ctx->sample_spec, NULL); 22153a5a1b3Sopenharmony_ci pa_assert(ctx->play_stream != NULL); 22253a5a1b3Sopenharmony_ci pa_stream_set_state_callback(ctx->play_stream, stream_state_callback, ctx); 22353a5a1b3Sopenharmony_ci pa_stream_set_write_callback(ctx->play_stream, calibrate_write_cb, ctx); 22453a5a1b3Sopenharmony_ci pa_stream_set_underflow_callback(ctx->play_stream, underflow_cb, userdata); 22553a5a1b3Sopenharmony_ci 22653a5a1b3Sopenharmony_ci pa_stream_connect_playback(ctx->play_stream, getenv("TEST_SINK"), &buffer_attr, 22753a5a1b3Sopenharmony_ci PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); 22853a5a1b3Sopenharmony_ci 22953a5a1b3Sopenharmony_ci /* Create capture stream */ 23053a5a1b3Sopenharmony_ci buffer_attr.maxlength = -1; 23153a5a1b3Sopenharmony_ci buffer_attr.tlength = (uint32_t) -1; 23253a5a1b3Sopenharmony_ci buffer_attr.prebuf = 0; 23353a5a1b3Sopenharmony_ci buffer_attr.minreq = (uint32_t) -1; 23453a5a1b3Sopenharmony_ci buffer_attr.fragsize = ctx->sample_spec.rate * ctx->fs * ctx->rec_latency / 1000; 23553a5a1b3Sopenharmony_ci 23653a5a1b3Sopenharmony_ci ctx->rec_stream = pa_stream_new(c, "loopback: rec", &ctx->sample_spec, NULL); 23753a5a1b3Sopenharmony_ci pa_assert(ctx->rec_stream != NULL); 23853a5a1b3Sopenharmony_ci pa_stream_set_state_callback(ctx->rec_stream, stream_state_callback, ctx); 23953a5a1b3Sopenharmony_ci pa_stream_set_read_callback(ctx->rec_stream, calibrate_read_cb, ctx); 24053a5a1b3Sopenharmony_ci pa_stream_set_overflow_callback(ctx->rec_stream, overflow_cb, userdata); 24153a5a1b3Sopenharmony_ci 24253a5a1b3Sopenharmony_ci pa_stream_connect_record(ctx->rec_stream, getenv("TEST_SOURCE"), &buffer_attr, 24353a5a1b3Sopenharmony_ci PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); 24453a5a1b3Sopenharmony_ci 24553a5a1b3Sopenharmony_ci break; 24653a5a1b3Sopenharmony_ci } 24753a5a1b3Sopenharmony_ci 24853a5a1b3Sopenharmony_ci case PA_CONTEXT_TERMINATED: 24953a5a1b3Sopenharmony_ci api = pa_mainloop_get_api(ctx->mainloop); 25053a5a1b3Sopenharmony_ci api->quit(api, 0); 25153a5a1b3Sopenharmony_ci break; 25253a5a1b3Sopenharmony_ci 25353a5a1b3Sopenharmony_ci case PA_CONTEXT_FAILED: 25453a5a1b3Sopenharmony_ci default: 25553a5a1b3Sopenharmony_ci pa_log_error("Context error: %s", pa_strerror(pa_context_errno(c))); 25653a5a1b3Sopenharmony_ci pa_assert_not_reached(); 25753a5a1b3Sopenharmony_ci } 25853a5a1b3Sopenharmony_ci} 25953a5a1b3Sopenharmony_ci 26053a5a1b3Sopenharmony_ciint pa_lo_test_init(pa_lo_test_context *ctx) { 26153a5a1b3Sopenharmony_ci /* FIXME: need to deal with non-float samples at some point */ 26253a5a1b3Sopenharmony_ci pa_assert(ctx->sample_spec.format == PA_SAMPLE_FLOAT32); 26353a5a1b3Sopenharmony_ci 26453a5a1b3Sopenharmony_ci ctx->ss = pa_sample_size(&ctx->sample_spec); 26553a5a1b3Sopenharmony_ci ctx->fs = pa_frame_size(&ctx->sample_spec); 26653a5a1b3Sopenharmony_ci 26753a5a1b3Sopenharmony_ci ctx->mainloop = pa_mainloop_new(); 26853a5a1b3Sopenharmony_ci ctx->context = pa_context_new(pa_mainloop_get_api(ctx->mainloop), ctx->context_name); 26953a5a1b3Sopenharmony_ci 27053a5a1b3Sopenharmony_ci pa_context_set_state_callback(ctx->context, context_state_callback, ctx); 27153a5a1b3Sopenharmony_ci 27253a5a1b3Sopenharmony_ci /* Connect the context */ 27353a5a1b3Sopenharmony_ci if (pa_context_connect(ctx->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { 27453a5a1b3Sopenharmony_ci pa_log_error("pa_context_connect() failed."); 27553a5a1b3Sopenharmony_ci goto quit; 27653a5a1b3Sopenharmony_ci } 27753a5a1b3Sopenharmony_ci 27853a5a1b3Sopenharmony_ci return 0; 27953a5a1b3Sopenharmony_ci 28053a5a1b3Sopenharmony_ciquit: 28153a5a1b3Sopenharmony_ci pa_context_unref(ctx->context); 28253a5a1b3Sopenharmony_ci pa_mainloop_free(ctx->mainloop); 28353a5a1b3Sopenharmony_ci 28453a5a1b3Sopenharmony_ci return -1; 28553a5a1b3Sopenharmony_ci} 28653a5a1b3Sopenharmony_ci 28753a5a1b3Sopenharmony_ciint pa_lo_test_run(pa_lo_test_context *ctx) { 28853a5a1b3Sopenharmony_ci int ret; 28953a5a1b3Sopenharmony_ci 29053a5a1b3Sopenharmony_ci if (pa_mainloop_run(ctx->mainloop, &ret) < 0) { 29153a5a1b3Sopenharmony_ci pa_log_error("pa_mainloop_run() failed."); 29253a5a1b3Sopenharmony_ci return -1; 29353a5a1b3Sopenharmony_ci } 29453a5a1b3Sopenharmony_ci 29553a5a1b3Sopenharmony_ci return 0; 29653a5a1b3Sopenharmony_ci} 29753a5a1b3Sopenharmony_ci 29853a5a1b3Sopenharmony_civoid pa_lo_test_deinit(pa_lo_test_context *ctx) { 29953a5a1b3Sopenharmony_ci if (ctx->play_stream) { 30053a5a1b3Sopenharmony_ci pa_stream_disconnect(ctx->play_stream); 30153a5a1b3Sopenharmony_ci pa_stream_unref(ctx->play_stream); 30253a5a1b3Sopenharmony_ci } 30353a5a1b3Sopenharmony_ci 30453a5a1b3Sopenharmony_ci if (ctx->rec_stream) { 30553a5a1b3Sopenharmony_ci pa_stream_disconnect(ctx->rec_stream); 30653a5a1b3Sopenharmony_ci pa_stream_unref(ctx->rec_stream); 30753a5a1b3Sopenharmony_ci } 30853a5a1b3Sopenharmony_ci 30953a5a1b3Sopenharmony_ci if (ctx->context) 31053a5a1b3Sopenharmony_ci pa_context_unref(ctx->context); 31153a5a1b3Sopenharmony_ci 31253a5a1b3Sopenharmony_ci if (ctx->mainloop) 31353a5a1b3Sopenharmony_ci pa_mainloop_free(ctx->mainloop); 31453a5a1b3Sopenharmony_ci} 31553a5a1b3Sopenharmony_ci 31653a5a1b3Sopenharmony_cifloat pa_rms(const float *s, int n) { 31753a5a1b3Sopenharmony_ci float sq = 0; 31853a5a1b3Sopenharmony_ci int i; 31953a5a1b3Sopenharmony_ci 32053a5a1b3Sopenharmony_ci for (i = 0; i < n; i++) 32153a5a1b3Sopenharmony_ci sq += s[i] * s[i]; 32253a5a1b3Sopenharmony_ci 32353a5a1b3Sopenharmony_ci return sqrtf(sq / n); 32453a5a1b3Sopenharmony_ci} 325