الصوت

AAudio هي واجهة برمجة تطبيقات جديدة لنظام التشغيل Android C تم تقديمها في إصدار Android O. وهو مصمّم لتطبيقات الصوت العالية الأداء التي تتطلّب وقت استجابة منخفضًا. تتواصل التطبيقات مع AAudio من خلال قراءة البيانات وكتابتها في مصادر البيانات.

واجهة برمجة التطبيقات AAudio API هي واجهة برمجة تطبيقات بسيطة من حيث التصميم، ولا تؤدي هذه الوظائف:

  • تعداد الأجهزة الصوتية
  • التوجيه الآلي بين نقاط نهاية الصوت
  • إدخال/إخراج الملفات
  • فك ترميز الصوت المضغوط
  • عرض تلقائي لجميع عمليات الإدخال أو عمليات البث في ردّ واحد

خطوات البدء:

يمكنك استدعاء AAudio من رمز C++. لإضافة مجموعة ميزات AAudio إلى تطبيقك، أدرِج ملف الرأس AAudio.h:

#include <aaudio/AAudio.h>

البث الصوتي

تنقل AAudio بيانات الصوت بين تطبيقك ومدخلات الصوت ومخارجه على جهاز Android. ينقل تطبيقك البيانات من وإلى الجهاز من خلال القراءة من ملفات صوتية وكتابتها، ويتم تمثيلها من خلال البنية AAudioStream. يمكن أن تكون طلبات القراءة/الكتابة حظرًا أو غير حظر.

يتم تعريف البثّ بما يلي:

  • جهاز الصوت الذي يمثّل مصدر البيانات في البث أو وجهة البيانات
  • وضع المشاركة الذي يحدِّد ما إذا كان لبثّ معيّن إذن وصول حصري إلى جهاز صوتي يمكن مشاركته مع مجموعات بث متعددة
  • تنسيق بيانات الصوت في البث

جهاز سماعي

يتم ربط كل بث بجهاز صوت واحد.

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

يمكنك استخدام AudioManager الطريقة getDevices() لاكتشاف أجهزة الصوت المتاحة على جهاز Android. تُعرِض الطريقة معلومات عن type كل جهاز.

يمتلك كل جهاز صوتي معرّفًا فريدًا على جهاز Android. يمكنك استخدام رقم التعريف لربط مصدر صوت بجهاز صوتي معيّن. ومع ذلك، في معظم الحالات، يمكنك السماح لـ AAudio باختيار الجهاز الأساسي التلقائي بدلاً من تحديد جهاز بنفسك.

يحدِّد جهاز الصوت المرتبط بالبث ما إذا كان البث مخصّصًا للإدخال أو الإخراج. يمكن لمصدر البيانات نقل البيانات في اتجاه واحد فقط. عند تحديد مصدر بيانات، يمكنك أيضًا تحديد اتجاهه. عند فتح بث، يتحقّق Android من توافق جهاز الصوت واتجاه البث.

وضع المشاركة

يتضمّن البث وضع مشاركة:

  • يشير الرمز AAUDIO_SHARING_MODE_EXCLUSIVE إلى أنّ البث الصوتي لديه إذن وصول حصري إلى جهاز الصوت، ولا يمكن لأي بث صوتي آخر استخدام الجهاز. إذا كان جهاز الصوت قيد الاستخدام حاليًا، قد لا يكون بإمكان البث الوصول إليه بشكل حصري. من المرجّح أن يكون وقت استجابة أحداث البث الحصري أقل، ولكن من المرجّح أيضًا أن ينقطع الاتصال. يجب إغلاق عمليات البث الحصرية فور الانتهاء منها كي تتمكّن التطبيقات الأخرى من الوصول إلى الجهاز. توفّر أحداث البث الحصرية أقل وقت استجابة ممكن.
  • يسمح AAUDIO_SHARING_MODE_SHARED لـ AAudio بمزج الصوت. يمزج AAudio جميع مصادر البث المشتركة التي تم تعيينها للجهاز نفسه.

يمكنك ضبط وضع المشاركة صراحةً عند إنشاء بث. يكون وضع المشاركة تلقائيًا هو SHARED.

صيغة الصوت

تحتوي البيانات التي يتم تمريرها عبر مصدر بيانات على سمات الصوت الرقمي المعتادة. وهي كما يلي:

  • نموذج البيانات
  • عدد القنوات (عيّنات لكل لقطة)
  • معدل العينة

يسمح AAudio بتنسيقات العيّنات التالية:

aaudio_format_t نوع البيانات C الملاحظات
AAUDIO_FORMAT_PCM_I16 int16_t عيّنات 16 بت الشائعة، بتنسيق Q0.15
AAUDIO_FORMAT_PCM_FLOAT عائم من -1.0 إلى +1.0
AAUDIO_FORMAT_PCM_I24_PACKED uint8_t في مجموعات من 3 عيّنات مُجمَّعة بسعة 24 بت، بتنسيق Q0.23
AAUDIO_FORMAT_PCM_I32 int32_t عيّنات 32 بت الشائعة بتنسيق Q0.31
AAUDIO_FORMAT_IEC61937 uint8_t صوت مضغوط مُغلف بتنسيق IEC61937 لنقل البيانات عبر HDMI أو S/PDIF

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

aaudio_format_t dataFormat = AAudioStream_getDataFormat(stream);
//... later
if (dataFormat == AAUDIO_FORMAT_PCM_I16) {
     convertFloatToPcm16(...)
}

إنشاء بث صوتي

تتّبع مكتبة AAudio نمط تصميم المُنشئ وتوفّر AAudioStreamBuilder.

  1. أنشئ AAudioStreamBuilder:

    AAudioStreamBuilder *builder;
    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
    

  2. اضبط إعدادات البث الصوتي في أداة الإنشاء باستخدام دوالّ أداة الإنشاء التي تتوافق مع مَعلمات البث. تتوفّر دوالّ الإعدادات الاختيارية التالية:

    AAudioStreamBuilder_setDeviceId(builder, deviceId);
    AAudioStreamBuilder_setDirection(builder, direction);
    AAudioStreamBuilder_setSharingMode(builder, mode);
    AAudioStreamBuilder_setSampleRate(builder, sampleRate);
    AAudioStreamBuilder_setChannelCount(builder, channelCount);
    AAudioStreamBuilder_setFormat(builder, format);
    AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);
    

    يُرجى العِلم أنّ هذه الطرق لا تُبلغ عن الأخطاء، مثل الثابت غير المحدّد أو القيمة خارج النطاق.

    إذا لم تحدِّد deviceId، سيكون الجهاز الافتراضي هو جهاز الإخراج الأساسي. إذا لم تحدِّد اتجاه البث، يكون الإعداد التلقائي هو بث الإخراج. بالنسبة إلى جميع المَعلمات الأخرى، يمكنك ضبط قيمة بشكل صريح أو السماح للنظام بتحديد القيمة المثلى من خلال عدم تحديد المَعلمة على الإطلاق أو ضبطها على AAUDIO_UNSPECIFIED.

    للتأكّد من أمان البث الصوتي، تحقّق من حالته بعد إنشائه، كما هو موضّح في الخطوة 4 أدناه.

  3. عند ضبط AAudioStreamBuilder، استخدِمه لإنشاء بث:

    AAudioStream *stream;
    result = AAudioStreamBuilder_openStream(builder, &stream);
    

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

    AAudioStreamBuilder_setDeviceId() AAudioStream_getDeviceId()
    AAudioStreamBuilder_setDirection() AAudioStream_getDirection()
    AAudioStreamBuilder_setSharingMode() AAudioStream_getSharingMode()
    AAudioStreamBuilder_setSampleRate() AAudioStream_getSampleRate()
    AAudioStreamBuilder_setChannelCount() AAudioStream_getChannelCount()
    AAudioStreamBuilder_setFormat() AAudioStream_getFormat()
    AAudioStreamBuilder_setBufferCapacityInFrames() AAudioStream_getBufferCapacityInFrames()

  5. يمكنك حفظ أداة الإنشاء وإعادة استخدامها في المستقبل لإنشاء المزيد من أحداث البث. ولكن إذا لم تعُد تخطّط لاستخدامه، عليك حذفه.

    AAudioStreamBuilder_delete(builder);
    

استخدام بث صوتي

عمليات النقل بين الحالات

يكون بث AAudio عادةً في إحدى الحالات الثابتة الخمس (يتم وصف حالة الخطأ "غير متّصل" في نهاية هذا القسم):

  • فتح
  • قيد التشغيل
  • متوقف مؤقتًا
  • وجه محمّر الخدود
  • متوقفة

لا تتدفق البيانات من خلال مصدر بيانات إلا عندما يكون مصدر البيانات في الحالة "مُشغّل". لنقل البث بين الحالات، استخدِم إحدى الدوالّ التي تطلب انتقالًا في الحالة:

aaudio_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);

يُرجى العلم أنّه يمكنك طلب إيقاف مؤقت أو تفريغ ذاكرة التخزين المؤقت في بث الإخراج فقط:

هذه الدوال غير متزامنة، ولا يحدث تغيير الحالة على الفور. عند طلب تغيير الحالة، ينقل البث أحد الحالات المؤقتة المقابلة:

  • جارٍ البدء
  • الإيقاف المؤقت
  • تدفق المياه
  • جارٍ الإيقاف
  • الخاتمة

يعرض مخطّط الحالة أدناه الحالات الثابتة على شكل مستطيلات مستديرة، والحالات العابرة على شكل مستطيلات منقطة. على الرغم من عدم ظهورها، يمكنك الاتصال برقم close() من أي ولاية.

مراحل AAudio

لا توفّر AAudio وظائف استدعاء لتنبيهك بتغييرات الحالة. يمكن استخدام دالة خاصة واحدة، AAudioStream_waitForStateChange(stream, inputState, nextState, timeout) للانتظار إلى أن يحدث تغيير في الحالة.

لا ترصد الدالة تغيير الحالة من تلقاء نفسها، ولا تنتظر حالة معيّنة. وينتظر إلى أن تصبح الحالة الحالية مختلفة عن inputState التي تحدّدها.

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

aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;
aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;
int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;
result = AAudioStream_requestPause(stream);
result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);

إذا لم تكن حالة البث هي "إيقاف مؤقت" (inputState، والتي افترضنا أنّها الحالة الحالية في وقت الاستدعاء)، تُرجع الدالة القيمة على الفور. بخلاف ذلك، يتم حظره إلى أن تنتهي حالة "الإيقاف المؤقت" أو تنتهي مهلة الانتظار. عند عرض الدالة، تعرض المَعلمة nextState الحالة الحالية للبث.

يمكنك استخدام هذه الطريقة نفسها بعد طلب بدء أو إيقاف أو تفريغ، باستخدام الحالة المؤقتة المقابلة كحالة الإدخال. لا تتصل بـ waitForStateChange() بعد الاتصال بـ AAudioStream_close() لأنّه سيتم حذف البث فور إغلاقه. ولا تستدعي AAudioStream_close() عندما يكون waitForStateChange() قيد التشغيل في سلسلة محادثات أخرى.

القراءة والكتابة في بث صوتي

هناك طريقتان لمعالجة البيانات في بث بعد بدئه:

لإجراء قراءة أو كتابة حظرتين لنقل العدد المحدّد من اللقطات، اضبط timeoutNanos على قيمة أكبر من صفر. بالنسبة إلى طلب غير حظر، اضبط timeoutNanos على صفر. في هذه الحالة، تكون النتيجة هي العدد الفعلي للّقطات التي تم نقلها.

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

aaudio_result_t result =
    AAudioStream_read(stream, audioData, numFrames, timeout);
if (result < 0) {
  // Error!
}
if (result != numFrames) {
  // pad the buffer with zeros
  memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
      sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
}

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

يجب أن تتطابق البيانات في المخزن المؤقت مع تنسيق البيانات الذي يعرضه AAudioStream_getDataFormat().

إغلاق بث صوتي

عند الانتهاء من استخدام مصدر بيانات، يمكنك إغلاقه باتّباع الخطوات التالية:

AAudioStream_close(stream);

بعد إغلاق مصدر بيانات، لا يمكنك استخدام مؤشر مصدر البيانات مع أي دالة AAudio تستند إلى مصدر البيانات.

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

انقطاع البث الصوتي

يمكن أن ينقطع بث الصوت في أي وقت في حال حدوث أحد هذه الأحداث:

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

عندما يتم إيقاف بث، تصبح حالته "متوقف"، وستؤدي أي محاولات لتنفيذ AAudioStream_write()‎ أو وظائف أخرى إلى ظهور خطأ. يجب دائمًا إيقاف البث غير المتصل بالإنترنت وإغلاقه، بغض النظر عن رمز الخطأ.

إذا كنت تستخدِم دالة استدعاء للبيانات (بدلاً من إحدى طرق القراءة/الكتابة المباشرة)، لن تتلقّى أي رمز إرجاع عند انقطاع الاتصال بالبث. للاطّلاع على إشعار عند حدوث ذلك، اكتب دالة AAudioStream_errorCallback واسجِّلها باستخدام AAudioStreamBuilder_setErrorCallback()‎.

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

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

void errorCallback(AAudioStream *stream,
                   void *userData,
                   aaudio_result_t error) {
    // Launch a new thread to handle the disconnect.
    std::thread myThread(my_error_thread_proc, stream, userData);
    myThread.detach(); // Don't wait for the thread to finish.
}

تحسين الأداء

يمكنك تحسين أداء تطبيق الصوت من خلال تعديل وحدات التخزين المؤقتة الداخلية واستخدام سلاسل محادثات خاصة ذات أولوية عالية.

ضبط وحدات التخزين المؤقت لتقليل وقت الاستجابة

تُرسِل AAudio البيانات إلى وحدات التخزين المؤقتة الداخلية التي تديرها، ويكون هناك وحدة واحدة لكل جهاز صوت.

سعة المخزن المؤقت هي إجمالي كمية البيانات التي يمكن أن يحتفظ بها المخزن المؤقت. يمكنك الاتصال برقم AAudioStreamBuilder_setBufferCapacityInFrames() لضبط السعة. تحدّ الطريقة من السعة التي يمكنك تخصيصها إلى الحد الأقصى المسموح به على الجهاز. استخدِم AAudioStream_getBufferCapacityInFrames() للتحقّق من السعة الفعلية للذاكرة المؤقتة.

ليس على التطبيق استخدام السعة الكاملة للمخزّن المؤقت. تملأ AAudio ذاكرة تخزين مؤقت حتى الحجم الذي يمكنك ضبطه. لا يمكن أن يكون حجم المخزن المؤقت أكبر من سعتها، وغالبًا ما يكون أصغر. من خلال التحكّم في حجم المخزن المؤقت، يمكنك تحديد عدد عمليات النقل المفاجئة المطلوبة لملئه، وبالتالي التحكّم في وقت الاستجابة. استخدِم الطريقتَين AAudioStreamBuilder_setBufferSizeInFrames() و AAudioStreamBuilder_getBufferSizeInFrames() لضبط حجم ذاكرة التخزين المؤقت.

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

      التخزين المؤقت في AAudio

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

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

في ما يلي مثال على حلقة تحسين المخزن المؤقت:

int32_t previousUnderrunCount = 0;
int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);
int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);

int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);

while (go) {
    result = writeSomeData();
    if (result < 0) break;

    // Are we getting underruns?
    if (bufferSize < bufferCapacity) {
        int32_t underrunCount = AAudioStream_getXRunCount(stream);
        if (underrunCount > previousUnderrunCount) {
            previousUnderrunCount = underrunCount;
            // Try increasing the buffer size by one burst
            bufferSize += framesPerBurst;
            bufferSize = AAudioStream_setBufferSize(stream, bufferSize);
        }
    }
}

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

استخدام طلب معاودة الاتصال ذو الأولوية العالية

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

تحتوي دالة ردّ الاتصال على النموذج الأوّلي التالي:

typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames);

استخدِم ميزة إنشاء البث لتسجيل طلب إعادة الاتصال:

AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

في أبسط الحالات، ينفِّذ البث بشكل دوري وظيفة الاستدعاء لجمع البيانات اللازمة للدفعة التالية.

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

على سبيل المثال، يمكنك استخدام دالة استدعاء لإنشاء ناتج موجة جيبية بشكل مستمر على النحو التالي:

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    int64_t timeout = 0;

    // Write samples directly into the audioData array.
    generateSineWave(static_cast<float *>(audioData), numFrames);
    return AAUDIO_CALLABCK_RESULT_CONTINUE;
}

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

تُجري وظيفة الاستدعاء عملية قراءة غير محظورة من تدفق الإدخال وتضع البيانات في المخزن المؤقت لتدفق الإخراج:

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    AAudioStream *inputStream = (AAudioStream *) userData;
    int64_t timeout = 0;
    aaudio_result_t result =
        AAudioStream_read(inputStream, audioData, numFrames, timeout);

  if (result == numFrames)
      return AAUDIO_CALLABCK_RESULT_CONTINUE;
  if (result >= 0) {
      memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
          sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
      return AAUDIO_CALLBACK_RESULT_CONTINUE;
  }
  return AAUDIO_CALLBACK_RESULT_STOP;
}

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

ضبط وضع الأداء

يحتوي كل عنصر AAudioStream على وضع أداء يؤثر بشكل كبير في سلوك تطبيقك. هناك ثلاثة أوضاع:

  • AAUDIO_PERFORMANCE_MODE_NONE هو الوضع التلقائي. ويستخدم هذا الوضع بثًا أساسيًا يوازن بين وقت الاستجابة وتوفير الطاقة.
  • يستخدم AAUDIO_PERFORMANCE_MODE_LOW_LATENCY وحدات تخزين مؤقت أصغر ومسار بيانات محسَّنًا لتقليل وقت الاستجابة.
  • يستخدم AAUDIO_PERFORMANCE_MODE_POWER_SAVING وحدات تخزين مؤقت داخلية أكبر ومسار بيانات يوازن بين وقت الاستجابة واستهلاك الطاقة الأقل.

يمكنك اختيار وضع الأداء من خلال استدعاء setPerformanceMode()‎، ومعرفة الوضع الحالي من خلال استدعاء getPerformanceMode()‎.

إذا كان وقت الاستجابة المنخفض أكثر أهمية من توفير الطاقة في تطبيقك، استخدِم AAUDIO_PERFORMANCE_MODE_LOW_LATENCY. يكون هذا الإجراء مفيدًا للتطبيقات التفاعلية للغاية، مثل الألعاب أو أدوات تركيب الأصوات باستخدام لوحة المفاتيح.

إذا كان توفير الطاقة أكثر أهمية من وقت الاستجابة المنخفض في تطبيقك، استخدِم AAUDIO_PERFORMANCE_MODE_POWER_SAVING. ويحدث ذلك عادةً في التطبيقات التي تشغّل الموسيقى التي تم إنشاؤها سابقًا، مثل تطبيقات بث الصوت أو تشغيل ملفات MIDI.

في الإصدار الحالي من AAudio، لتحقيق أقل وقت ممكن للاستجابة، يجب استخدام وضع الأداء AAUDIO_PERFORMANCE_MODE_LOW_LATENCY مع طلب استدعاء ذو أولوية عالية. اتّبِع هذا المثال:

// Create a stream builder
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);

// Use it to create the stream
AAudioStream *stream;
AAudioStreamBuilder_openStream(streamBuilder, &stream);

أمان مؤشرات الترابط

واجهة برمجة التطبيقات AAudio API ليست آمنة من حيث مؤشرات الترابط بالكامل. لا يمكنك استدعاء بعض دوال AAudio بشكل متزامن من أكثر من سلسلة محادثات واحدة في الوقت نفسه. ويرجع ذلك إلى أنّ AAudio يتجنب استخدام عمليات قفل المتغيّرات، ما قد يؤدي إلى استبدال مؤشرات الترابط وظهور مشاكل.

للاحتفاظ بالأمان، لا تستدعي AAudioStream_waitForStateChange() أو تقرأ من أو تكتب إلى البث نفسه من سلسلة محادثات مختلفتَين. وبالمثل، لا تغلق مصدر بيانات في سلسلة محادثات واحدة أثناء القراءة منه أو الكتابة إليه في سلسلة محادثات أخرى.

إنّ الطلبات التي تعرض إعدادات البث، مثل AAudioStream_getSampleRate() وAAudioStream_getChannelCount()، آمنة في مؤشرات الترابط.

وهذه الطلبات آمنة أيضًا في مؤشر التسلسل:

  • AAudio_convert*ToText()
  • AAudio_createStreamBuilder()
  • AAudioStream_get*() باستثناء AAudioStream_getTimestamp()

المشاكل المعروفة

  • وقت استجابة الصوت مرتفع لحظر write() لأنّ إصدار Android O DP2 لا يستخدم مسار FAST. استخدِم طلب معاودة الاتصال للحصول على وقت استجابة أقل.

مصادر إضافية

لمزيد من المعلومات، يمكنك الاطّلاع على المراجع التالية:

مرجع حول API

الدروس التطبيقية حول الترميز

الفيديوهات