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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

يتم تحديد معايير الإبلاغ عن هذه العلامات في مستند 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، ولكن ليس والعكس صحيح.

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

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

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

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

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

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

  • إذا كان التطبيق يراقب الإدخال، نقترح أن يستخدم المستخدمون سماعة رأس. (على سبيل المثال، من خلال عرض شاشة الأفضل مع سماعات الرأس عند التشغيل لأول مرة). ملاحظة أن استخدام سماعة الرأس فقط لا يضمن أقل وقت استجابة ممكن. قد تحتاج إلى إجراء خطوات أخرى لإزالة أي معالجة إشارات غير مرغوب فيها من المسار الصوتي، مثلاً من خلال باستخدام يتم ضبط الإعداد المسبق VOICE_RECOGNITION أثناء التسجيل.
  • الاستعداد للتعامل مع معدلات العينات الاسمية 44,100 و48,000 هرتز كما ذكرت getProperty(String) لطلب البحث property_OUTPUT_SAMPLE_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 تختلف باختلاف الأجهزة وبين إصدارات Android.

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

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

  • SL_IID_ANDROIDSIMPLEBUFFERQUEUE
  • SL_IID_VOLUME
  • SL_IID_MUTESOLO

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

  • SL_IID_BASSBOOST
  • SL_IID_EffectSEND
  • 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. مدة تأخير رحلات الذهاب والعودة