تأخیر صدا

تأخیر زمانی است که طول می‌کشد تا سیگنال در سیستم حرکت کند. این‌ها انواع رایج تأخیر مربوط به برنامه‌های صوتی هستند:

  • تأخیر خروجی صدا عبارت است از زمان بین تولید نمونه صدا توسط برنامه و پخش آن نمونه ازطریق فیش هدفون یا بلندگوی داخلی.
  • تأخیر ورودی صدا مدت زمان بین دریافت سیگنال صوتی توسط ورودی صدای دستگاه، مانند میکروفون، و دردسترس قرار گرفتن همان داده‌های صوتی برای برنامه است.
  • تأخیر رفت‌وبرگشت مجموع تأخیر ورودی، زمان پردازش برنامه، و تأخیر خروجی است.

  • تأخیر لمس عبارت است از زمان بین لمس کردن صفحه‌نمایش توسط کاربر و دریافت آن رویداد لمسی توسط برنامه.
  • تأخیر آماده‌سازی مدت‌زمانی است که طول می‌کشد تا خط لوله صوتی برای اولین‌بار که داده‌ها در میان‌گیر صف‌بندی می‌شوند، راه‌اندازی شود.

این صفحه نحوه توسعه برنامه صوتی با ورودی و خروجی با تأخیر کم و نحوه اجتناب از تأخیر آماده‌سازی را شرح می‌دهد.

اندازه‌گیری تأخیر

اندازه‌گیری تأخیر ورودی و خروجی صدا به‌صورت مجزا دشوار است زیرا مستلزم دانستن دقیق زمان ارسال اولین نمونه به مسیر صدا است (اگرچه این کار را می‌توان بااستفاده از مدار آزمایش نور و اسیلوسکوپ انجام داد). اگر تأخیر صوتی رفت‌وبرگشت را می‌دانید، می‌توانید از این قاعده سرانگشتی استفاده کنید: تأخیر ورودی (و خروجی) صوتی نصف تأخیر صوتی رفت‌وبرگشت در مسیرهای بدون پردازش سیگنال است.

تأخیر صوتی رفت‌وبرگشت بسته به مدل دستگاه و نسخه Android بسیار متفاوت است. با خواندن اندازه‌گیری‌های منتشرشده می‌توانید ایده کلی از تأخیر رفت‌وبرگشت برای دستگاه‌های Nexus به‌دست آورید.

می‌توانید تأخیر صوتی رفت‌وبرگشت را با ایجاد برنامه‌ای که سیگنال صوتی تولید می‌کند، به آن سیگنال گوش می‌دهد، و زمان بین ارسال و دریافت آن را اندازه‌گیری می‌کند، اندازه‌گیری کنید.

ازآنجایی‌که کمترین تأخیر در مسیرهای صوتی با حداقل پردازش سیگنال به‌دست می‌آید، ممکن است بخواهید از دانگل حلقه برگشتی صوتی نیز استفاده کنید که امکان اجرای آزمایش را ازطریق اتصال هدست فراهم می‌کند.

روال‌های مطلوب برای به‌حداقل رساندن تأخیر

اعتبارسنجی عملکرد صوتی

«سند تعریف سازگاری Android»‏ (CDD) الزامات سخت‌افزاری و نرم‌افزاری دستگاه Android سازگار را برمی‌شمارد. برای اطلاعات بیشتر درباره برنامه کلی سازگاری، به سازگاری Android مراجعه کنید و برای سند CDD واقعی، به CDD مراجعه کنید.

در CDD، تأخیر رفت‌وبرگشت ۲۰ میلی‌ثانیه یا کمتر مشخص شده است (هرچند موسیقی‌دانان عموماً به ۱۰ میلی‌ثانیه نیاز دارند). دلیل آن این است که موارد استفاده مهمی وجود دارد که با ۲۰ میلی‌ثانیه فعال می‌شود.

درحال‌حاضر هیچ میانای برنامه‌سازی کاربردی‌ای برای تعیین تأخیر صوتی در هیچ مسیری در دستگاه Android در زمان اجرا وجود ندارد. بااین‌حال، می‌توانید از پرچم‌های ویژگی سخت‌افزاری زیر استفاده کنید تا متوجه شوید که آیا دستگاه تضمینی برای تأخیر ارائه می‌دهد یا نه:

معیارهای گزارش کردن این پرچم‌ها در بخش‌های ۵.۶ تأخیر صوتی و ۵.۱۰ صدای حرفه‌ای در CDD تعریف شده است.

در اینجا نحوه بررسی این ویژگی‌ها در Java آورده شده است:

کاتلین

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

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

جاوا

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، اما برعکس آن ممکن نیست.

هیچ فرضی درباره عملکرد صوتی نداشته باشید

برای کمک به جلوگیری از مشکلات تأخیر، مراقب فرض‌های زیر باشید:

  • فرض نکنید که بلندگوها و میکروفون‌های مورداستفاده در دستگاه‌های همراه عموماً آکوستیک خوبی دارند. به‌دلیل اندازه کوچک آن‌ها، آکوستیک معمولاً ضعیف است، بنابراین پردازش سیگنال برای بهبود کیفیت صدا اضافه می‌شود. این پردازش سیگنال باعث تأخیر می‌شود.
  • فرض نکنید که فراخوانی‌های برگشتی ورودی و خروجی شما همگام‌سازی شده‌اند. برای ورودی و خروجی هم‌زمان، از گرداننده‌های تکمیل صف میان‌گیری جداگانه برای هر طرف استفاده می‌شود. هیچ تضمینی برای ترتیب نسبی این فراخوانی‌های برگشتی یا همگام‌سازی ساعت‌های صوتی وجود ندارد، حتی زمانی که هر دو طرف از یک نرخ نمونه‌برداری استفاده می‌کنند. برنامه شما باید داده‌ها را با همگام‌سازی میان‌گیر مناسب میان‌گیری کند.
  • فرض نکنید که نرخ نمونه‌برداری واقعی دقیقاً با نرخ نمونه‌برداری اسمی مطابقت دارد. برای مثال، اگر نرخ نمونه اسمی ۴۸٬۰۰۰ هرتز باشد، طبیعی است که ساعت صوتی با نرخی کمی متفاوت از CLOCK_MONOTONIC سیستم‌عامل پیش برود. دلیل این امر این است که ساعت‌های صوتی و سیستمی ممکن است از کریستال‌های مختلفی مشتق شوند.
  • فرض نکنید که بسامد نمونه بازپخش واقعی دقیقاً با بسامد نمونه ضبط واقعی مطابقت دارد، به‌ویژه اگر نقاط پایانی در مسیرهای جداگانه‌ای باشند. برای مثال، اگر از میکروفون روی دستگاه با نرخ نمونه اسمی ۴۸٬۰۰۰ هرتز ضبط می‌کنید و در صدای USB با نرخ نمونه اسمی ۴۸٬۰۰۰ هرتز پخش می‌کنید، نرخ‌های نمونه واقعی احتمالاً کمی با یکدیگر متفاوت خواهند بود.

پیامد ساعت‌های صوتی بالقوه مستقل، نیاز به تبدیل نرخ نمونه‌برداری ناهمگام است. یک تکنیک ساده (اگرچه برای کیفیت صدا ایده‌آل نیست) برای تبدیل نرخ نمونه ناهمگام، تکرار یا حذف نمونه‌ها درصورت نیاز در نزدیکی نقطه عبور از صفر است. تبدیل‌های پیچیده‌تر امکان‌پذیر است.

به‌حداقل رساندن تأخیر ورودی

این بخش پیشنهادهایی ارائه می‌دهد تا به شما کمک کند تأخیر ورودی صدا را هنگام ضبط با میکروفون داخلی یا میکروفون هدست خارجی کاهش دهید.

  • اگر برنامه‌تان ورودی را پایش می‌کند، به کاربران پیشنهاد دهید از هدست استفاده کنند (برای مثال، با نمایش صفحه بهترین تجربه با هدفون در اولین اجرا). توجه داشته باشید که صرفاً استفاده از هدست تضمین نمی‌کند که کمترین تأخیر ممکن را داشته باشید. ممکن است لازم باشد مراحل دیگری را برای حذف هرگونه پردازش سیگنال ناخواسته از مسیر صوتی انجام دهید، مثلاً با استفاده از پیش‌تنظیم VOICE_RECOGNITION هنگام ضبط.
  • برای مدیریت بسامدهای نمونه اسمی ۴۴٬۱۰۰ و ۴۸٬۰۰۰ هرتز که توسط getProperty(String) برای PROPERTY_OUTPUT_SAMPLE_RATE گزارش شده است، آماده باشید. نرخ‌های نمونه‌برداری دیگر ممکن است اما نادر است.
  • برای مدیریت اندازه میان‌گیر گزارش‌شده توسط getProperty(String) برای PROPERTY_OUTPUT_FRAMES_PER_BUFFER آماده باشید. اندازه‌های میان‌گیر معمول شامل ۹۶، ۱۲۸، ۱۶۰، ۱۹۲، ۲۴۰، ۲۵۶، یا ۵۱۲ فریم است، اما مقادیر دیگر نیز ممکن است.

به حداقل رساندن تأخیر برونداد

هنگام ایجاد پخش‌کننده صوتی، از نرخ نمونه‌برداری بهینه استفاده کنید

برای دستیابی به کمترین تأخیر، باید داده‌های صوتی‌ای ارائه دهید که با بسامد نمونه و اندازه میان‌گیر بهینه دستگاه مطابقت داشته باشد. برای اطلاعات بیشتر، طراحی برای کاهش تأخیر را ببینید.

در Java، می‌توانید نرخ نمونه‌برداری بهینه را از AudioManager دریافت کنید، همان‌طور که در نمونه کد زیر نشان داده شده است:

کاتلین

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

جاوا

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 به نرخ نمونه‌برداری در هر کانال برحسب میلی‌هرتز اشاره دارد (۱ هرتز = ۱۰۰۰ میلی‌هرتز).

استفاده از اندازه میان‌گیر بهینه برای صف‌بندی داده‌های صوتی

می‌توانید اندازه میان‌گیر بهینه را به روشی مشابه با نرخ نمونه بهینه، بااستفاده از AudioManager API به‌دست آورید:

کاتلین

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

جاوا

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_ANDROIDEFFECT
  • SL_IID_ANDROIDEFFECTSEND

وقتی بازیکن خود را ایجاد می‌کنید، مطمئن شوید که فقط میاناهای سریع را اضافه می‌کنید، همان‌طور که در مثال زیر نشان داده شده است:

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. زمان تأخیر رفت‌وبرگشت