وقت استجابة الصوت

وقت الاستجابة هو الوقت الذي تستغرقه الإشارة للانتقال خلال النظام. في ما يلي الأنواع الشائعة من وقت الاستجابة المتعلقة بتطبيقات الصوت:

  • وقت استجابة إخراج الصوت هو الوقت الفاصل بين إنشاء عيّنة صوتية من تطبيق وتشغيل العيّنة من خلال مقبس سمّاعة الرأس أو مكبّر الصوت المُدمَج.
  • وقت استجابة إدخال الصوت هو الوقت الفاصل بين استقبال إشارة صوتية من خلال إدخال صوت على الجهاز، مثل الميكروفون، وإتاحة البيانات الصوتية نفسها لأحد التطبيقات.
  • وقت استجابة البيانات واستقبالها هو مجموع وقت استجابة الإدخال ووقت معالجة التطبيق ووقت استجابة الإخراج.

  • وقت استجابة اللمس هو الوقت الذي يفصل بين لمس المستخدم للشاشة وحدث اللمس الذي يتلقاه أحد التطبيقات.
  • وقت استجابة عملية إعادة التحميل هو الوقت الذي يستغرقه بدء مسار الصوت عندما تضاف البيانات الأولى إلى قائمة الانتظار في مخزن مؤقت.

توضّح هذه الصفحة كيفية تطوير تطبيق الصوت لديك من خلال إدخال وإخراج بطيء في وقت الاستجابة وكيفية تجنُّب وقت الاستجابة البطيء.

قياس وقت الاستجابة

من الصعب قياس وقت استجابة إدخال وإخراج الصوت بشكلٍ منفصل لأنّه يتطلّب معرفة الوقت الدقيق لإرسال العيّنة الأولى في المسار الصوتي (على الرغم من أنّه يمكن إجراء ذلك باستخدام دائرة اختبار ضوئي ومذبذب ذبذبة). إذا كنت تعرف وقت استجابة الصوت لإرساله واستقباله، يمكنك اتّباع القاعدة العملية التقريبية: أنّ وقت استجابة الإدخال الصوتي (والإخراج) هو نصف وقت استجابة الصوت لإرساله واستقباله على المسارات بدون معالجة الإشارات.

يختلف وقت استجابة الصوت واستقباله بشكل كبير باختلاف طراز الجهاز وإصدار Android. يمكنك الحصول على فكرة تقريبية عن وقت الاستجابة لإرسال الرسائل واستقبالها على أجهزة Nexus من خلال قراءة القياسات المنشورة.

يمكنك قياس وقت استجابة الصوت ذهابًا وإيابًا من خلال إنشاء تطبيق ينشئ إشارة صوتية، ويستمع إلى هذه الإشارة، ويقيس الوقت بين إرسالها واستلامها.

وبما أنّ أقل وقت استجابة يتم تحقيقه على المسارات الصوتية بأقلّ معالجة للإشارات، يمكنك أيضًا استخدام مفتاح إلكتروني لتكرار الصوت، ما يسمح بإجراء الاختبار عبر موصّل سماعة الرأس.

أفضل الممارسات لتقليل وقت الاستجابة

التحقّق من أداء الصوت

يسرد مستند تعريف التوافق مع Android (CDD) متطلبات الأجهزة والبرامج الخاصة بجهاز Android متوافق. يمكنك مراجعة القسم التوافق مع Android للاطّلاع على مزيد من المعلومات حول برنامج التوافق بشكل عام، والاطّلاع على التوافق مع نظام التشغيل Android (CDD) للاطّلاع على مستند CDD الفعلي.

في CDD، يتم تحديد وقت استجابة إرسال البيانات واستقبالها بمقدار 20 ملي ثانية أو أقل (على الرغم من أنّ الموسيقيين يطلبون عمومًا 10 ملي ثانية). ويرجع ذلك إلى أنّ هناك حالات استخدام مهمة يتم تفعيلها بحلول 20 ملّي ثانية.

ولا تتوفر حاليًا أي واجهة برمجة تطبيقات لتحديد وقت استجابة الصوت في أي مسار على جهاز Android في وقت التشغيل. ومع ذلك، يمكنك استخدام علامات ميزات الأجهزة التالية لمعرفة ما إذا كان الجهاز يقدم أي ضمانات بشأن وقت الاستجابة:

  • android.hardware.audio.low_latency يشير إلى وقت استجابة متواصل للإخراج يبلغ 45 ملي ثانية أو أقل.
  • android.hardware.audio.pro يشير إلى وقت استجابة متواصل لإرسال البيانات واستقبالها بمقدار 20 ملي ثانية أو أقل.

يتم تحديد معايير الإبلاغ عن هذه البلاغات في CDD ضمن القسمين 5.6 وقت استجابة الصوت و5.10 الصوت الاحترافي.

إليك كيفية التحقق من هذه الميزات في Java:

Kotlin

val hasLowLatencyFeature: Boolean =
        packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY)

val hasProFeature: Boolean =
        packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO)

Java

boolean hasLowLatencyFeature =
    getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);

boolean hasProFeature =
    getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO);

في ما يتعلّق بالعلاقة بين ميزات الصوت، إنّ ميزة android.hardware.audio.low_latency هي شرط أساسي لـ android.hardware.audio.pro. يمكن لأي جهاز تنفيذ android.hardware.audio.low_latency وليس android.hardware.audio.pro، ولكن ليس العكس صحيح.

عدم وضع افتراضات حول أداء الصوت

انتبه إلى الافتراضات التالية للمساعدة في تجنب مشاكل وقت الاستجابة:

  • لا تفترض أن مكبرات الصوت والميكروفونات المستخدمة في الأجهزة المحمولة تتميز بخصائص صوتية جيدة بشكل عام. نظرًا لصغر حجمها، فإن الخصائص الصوتية بشكل عام رديئة، لذلك تتم إضافة معالجة الإشارات لتحسين جودة الصوت. تؤدي معالجة الإشارات هذه إلى توفير وقت الاستجابة.
  • لا تفترض مزامنة استدعاءات الإدخال والمخرجات. للإدخال والإخراج المتزامنين، يتم استخدام معالجات منفصلة لإكمال قائمة انتظار المخزن المؤقت لكل جانب. ليس هناك ما يضمن الترتيب النسبي لرد الاتصالات هذه أو مزامنة الساعات الصوتية، حتى عندما يستخدم كلا الجانبين معدّل العينة نفسه. يجب أن يخزّن تطبيقك البيانات باستخدام المزامنة المناسبة للمخزن المؤقت.
  • لا تفترض أنّ معدّل العيّنة الفعلي يتطابق تمامًا مع معدّل العيّنة الاسمي. على سبيل المثال، إذا كان معدّل العيّنة الاسمي هو 48,000 هرتز، من الطبيعي أن تتقدّم الساعة الصوتية بمعدل مختلف قليلاً عن معدّل التشغيل CLOCK_MONOTONIC لنظام التشغيل. هذا لأن ساعة الصوت وساعات النظام قد تأتي من بلورات مختلفة.
  • لا تفترض أنّ معدّل عيّنة التشغيل الفعلي يتطابق تمامًا مع معدّل الالتقاط الفعلي، خاصةً إذا كانت نقاط النهاية على مسارات منفصلة. على سبيل المثال، إذا كنت تلتقط من ميكروفون الجهاز وبمعدل عينة اسمية يبلغ 48,000 هرتز، وتشغّل التسجيل على صوت USB بمعدل عينة اسمية يبلغ 48,000 هرتز، فمن المحتمل أن تكون معدلات العينات الفعلية مختلفة قليلاً عن بعضها البعض.

ينتج عن الساعات الصوتية التي يُحتمل أن تكون مستقلة الحاجة إلى تحويل معدّل العيّنة غير المتزامن. هناك أسلوب بسيط (ولكنه ليس مثاليًا لجودة الصوت) لتحويل معدّل العيّنة غير المتزامن هو تكرار العيّنات أو إسقاطها حسب الحاجة بالقرب من نقطة تقاطع صفرية. من الممكن إجراء إحالات ناجحة أكثر تعقيدًا.

تقليل وقت استجابة الإدخال

يقدّم هذا القسم اقتراحات لمساعدتك في تقليل وقت استجابة إدخال الصوت عند التسجيل باستخدام ميكروفون مضمّن أو ميكروفون سماعة رأس خارجية.

  • إذا كان تطبيقك يراقب الإدخال، اقترِح على المستخدمين استخدام سماعة رأس (على سبيل المثال، من خلال عرض شاشة الأفضل مع سماعات الرأس عند التشغيل لأول مرة). تجدر الإشارة إلى أنّ استخدام سماعة الرأس فقط لا يضمن أدنى وقت استجابة ممكن. قد تحتاج إلى تنفيذ خطوات أخرى لإزالة أي معالجة إشارات غير مرغوب فيها من المسار الصوتي، كاستخدام الإعداد المسبق VOICE_RECOGNITION أثناء التسجيل.
  • كن مستعدًا للتعامل مع معدلات العينة الاسمية البالغ عددها 44,100 و48,000 هرتز كما تم الإبلاغ عن ذلك من خلال getProperty(String) لـ property_OUTPUT_نموذج_RATE. معدلات العينات الأخرى ممكنة، ولكنها نادرة.
  • كن مستعدًا للتعامل مع حجم المخزن المؤقت الذي تم الإبلاغ عنه من خلال getProperty(String) لـ property_OUTPUT_iframeS_PER_BUFFER. تتضمن أحجام المخزن المؤقت النموذجية 96 أو 128 أو 160 أو 192 أو 240 أو 256 أو 512 إطارًا، ولكن يمكن استخدام قيم أخرى.

تقليل وقت استجابة الإخراج

استخدام معدّل البيانات المثالي عند إنشاء مشغِّل الصوت

للحصول على أقل وقت استجابة، يجب توفير بيانات صوتية تتطابق مع معدّل العينة وحجم المخزن المؤقت الأمثل للجهاز. لمزيد من المعلومات، راجع تصميم وقت الاستجابة لتقليل وقت الاستجابة.

في Java، يمكنك الحصول على معدّل العينة الأمثل من AudioManager كما هو موضّح في مثال الرمز التالي:

Kotlin

val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val sampleRateStr: String? = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
var sampleRate: Int = sampleRateStr?.let { str ->
    Integer.parseInt(str).takeUnless { it == 0 }
} ?: 44100 // Use a default value if property not found

Java

AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String sampleRateStr = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
int sampleRate = Integer.parseInt(sampleRateStr);
if (sampleRate == 0) sampleRate = 44100; // Use a default value if property not found

بمجرد معرفة معدل العينة الأمثل، يمكنك تقديمه عند إنشاء المشغل الخاص بك. يستخدم هذا المثال OpenSL ES:

// create buffer queue audio player
void Java_com_example_audio_generatetone_MainActivity_createBufferQueueAudioPlayer
        (JNIEnv* env, jclass clazz, jint sampleRate, jint framesPerBuffer)
{
   ...
   // specify the audio source format
   SLDataFormat_PCM format_pcm;
   format_pcm.numChannels = 2;
   format_pcm.samplesPerSec = (SLuint32) sampleRate * 1000;
   ...
}

ملاحظة: يشير العمود samplesPerSec إلى معدّل البيانات في الملف الصوتي لكل قناة بالمللي هرتز (1 هرتز = 1000 ميغاهرتز).

استخدام الحجم الأمثل للمخزن المؤقت لإدراج البيانات الصوتية في قائمة الانتظار

يمكنك الحصول على الحجم الأمثل للمخزن المؤقت بطريقة مشابهة لمعدل العينة الأمثل، باستخدام واجهة برمجة تطبيقات AudioManager:

Kotlin

val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val framesPerBuffer: String? = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
var framesPerBufferInt: Int = framesPerBuffer?.let { str ->
    Integer.parseInt(str).takeUnless { it == 0 }
} ?: 256 // Use default

Java

AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
int framesPerBufferInt = Integer.parseInt(framesPerBuffer);
if (framesPerBufferInt == 0) framesPerBufferInt = 256; // Use default

تشير السمة PROPERTY_OUTPUT_FRAMES_PER_BUFFER إلى عدد الإطارات الصوتية التي يمكن أن يخزّنها المخزن المؤقت لطبقة التجريد (HAL) (HAL). يجب إنشاء المخازن المؤقتة للصوت بحيث تحتوي على مضاعف دقيق لهذا العدد. إذا استخدمت العدد الصحيح من الإطارات الصوتية، تتم عمليات معاودة الاتصال على فترات منتظمة، ما يقلل من عدم الاستقرار.

من المهم استخدام واجهة برمجة التطبيقات لتحديد حجم المخزن المؤقت بدلاً من استخدام قيمة غير قابلة للتغيير في ترميز ثابت، لأنّ أحجام المخزن المؤقت لـ HAL تختلف باختلاف الأجهزة وإصدارات Android.

لا تضِف واجهات إخراج تتضمّن معالجة الإشارات

لا تتوافق أداة المزج السريع إلا مع هذه الواجهات:

  • SL_IID_ANDROIDSIMPLEBUFFERQUEUE
  • SL_IID_VOLUME
  • SL_IID_MUTESOLO

غير مسموح بهذه الواجهات لأنها تتضمن معالجة الإشارات وستؤدي إلى رفض طلبك لمسار سريع:

  • SL_IID_BASSBOOST
  • SL_IID_مؤثرSEND
  • SL_IID_ENVIRONMENTALREVERB
  • SL_IID_EQUALIZER
  • SL_IID_PLAYBACKRATE
  • SL_IID_PRESETREVERB
  • SL_IID_VIRTUALIZER
  • SL_IID_ANDROIDمؤثر
  • SL_IID_ANDROIDمؤثرSEND

عند إنشاء المشغّل، تأكّد من إضافة واجهات سريعة فقط، كما هو موضّح في المثال التالي:

const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };

التأكّد من استخدام مسار وقت استجابة سريع

أكمِل هذه الخطوات للتحقّق من حصولك بنجاح على مسار وقت استجابة سريع:

  1. شغِّل تطبيقك ثم شغِّل الأمر التالي:
  2. adb shell ps | grep your_app_name
    
  3. دوِّن معرّف العملية لتطبيقك.
  4. يمكنك الآن تشغيل الصوت من التطبيق. أمامك ثلاث ثوانٍ تقريبًا لتشغيل الأمر التالي من الوحدة الطرفية:
  5. adb shell dumpsys media.audio_flinger
    
  6. ابحث عن رقم تعريف العملية. إذا رأيت الحرف F في عمود الاسم، فهذا يعني أنّه في مسار زمن انتقال منخفض (يرمز الحرف F إلى المسار السريع).

تقليل وقت استجابة الإحماء

عند إدراج البيانات الصوتية للمرة الأولى في قائمة المحتوى التالي، يستغرق تجهيز الدائرة الصوتية للجهاز وقتًا قليلاً، لكنّه لا يزال مهمًا. لتجنُّب وقت استجابة الإحماء هذا، يمكنك إدراج مخازن مؤقتة للبيانات الصوتية التي تحتوي على كتم الصوت في قائمة الانتظار، كما هو موضّح في مثال الرمز البرمجي التالي:

#define CHANNELS 1
static short* silenceBuffer;
int numSamples = frames * CHANNELS;
silenceBuffer = malloc(sizeof(*silenceBuffer) * numSamples);
    for (i = 0; i<numSamples; i++) {
        silenceBuffer[i] = 0;
    }

عند النقطة التي يجب أن يتم فيها إنتاج الصوت، يمكنك التبديل إلى إضافة مخازن مؤقتة إلى قائمة الانتظار تتضمّن بيانات صوتية حقيقية.

ملاحظة: يؤدي إخراج الصوت باستمرار إلى استهلاك كبير للطاقة. احرص على إيقاف الناتج باستخدام طريقة onPause(). يمكنك أيضًا إيقاف الاستماع الصامت مؤقتًا بعد مرور فترة من عدم نشاط المستخدم.

رمز نموذجي إضافي

لتنزيل نموذج تطبيق يعرض وقت استجابة الصوت، يُرجى الاطّلاع على عيّنات NDK.

للحصول على معلومات إضافية

  1. وقت استجابة الصوت لمطوّري التطبيقات
  2. مساهمون في وقت استجابة الصوت
  3. قياس وقت استجابة الصوت
  4. تحسين الصوت
  5. وقت الاستجابة (الصوت)
  6. مدة تأخير إرسال الرسائل واستقبالها