เวลาในการตอบสนองของเสียง

เวลาในการตอบสนองคือระยะเวลาที่สัญญาณใช้ในการเดินทางผ่านระบบ ซึ่งมักเป็น ประเภทของเวลาในการตอบสนองที่เกี่ยวข้องกับแอปเสียง

  • เวลาในการตอบสนองของเอาต์พุตเสียงคือช่วงเวลาระหว่างที่ตัวอย่างเสียงสร้างขึ้นโดย แอปและตัวอย่างที่เล่นผ่านช่องเสียบหูฟังหรือลำโพงในตัว
  • เวลาในการตอบสนองของอินพุตเสียง คือช่วงเวลาระหว่างที่สัญญาณเสียงได้รับ อินพุตเสียงของอุปกรณ์ เช่น ไมโครโฟน และข้อมูลเสียงเดียวกันนั้น ก่อนที่จะพร้อมใช้งาน แอป
  • เวลาในการตอบสนองไป-กลับคือผลรวมของเวลาในการตอบสนองของอินพุต เวลาในการประมวลผลแอป และ เวลาในการตอบสนองเอาต์พุต

  • เวลาในการตอบสนองของการแตะ คือช่วงเวลาระหว่างที่ผู้ใช้แตะหน้าจอ แอปได้รับเหตุการณ์การแตะ
  • เวลาในการตอบสนองอุ่นเครื่องคือเวลาที่ใช้ในการเริ่มต้นไปป์ไลน์เสียงในช่วงแรก ข้อมูลเวลาจะอยู่ในคิวบัฟเฟอร์

หน้านี้อธิบายวิธีพัฒนาแอปเสียงด้วยอินพุตและเอาต์พุตที่มีเวลาในการตอบสนองต่ำ รวมถึงวิธีหลีกเลี่ยง เวลาในการตอบสนองของการอุ่นเครื่อง

วัดเวลาในการตอบสนอง

การวัดเวลาในการตอบสนองของอินพุตและเอาต์พุตเสียงด้วยตัวเองทำได้ยาก เนื่องจากจะต้องทราบข้อมูลที่แน่นอน เมื่อมีการส่งตัวอย่างแรกไปยังเส้นทางเสียง (แม้ว่าจะสามารถทำได้โดยใช้ วงจรทดสอบด้วยแสงและออสซิลโลสโคป) หากคุณทราบเวลาในการตอบสนองของเสียงไป-กลับ คุณสามารถ หลักทั่วไปก็คือ เวลาในการตอบสนองของอินพุตเสียง (และเอาต์พุต) คิดเป็นครึ่งหนึ่งของเวลาในการตอบสนองของเสียงไป-กลับ ในเส้นทางที่ไม่มีการประมวลผลสัญญาณ

เวลาในการตอบสนองของเสียงไป-กลับจะแตกต่างกันไป อย่างมากโดยขึ้นอยู่กับรุ่นอุปกรณ์และ บิลด์ของ Android คุณสามารถดูภาพรวมคร่าวๆ เกี่ยวกับการเดินทางไป-กลับ เวลาในการตอบสนองสำหรับอุปกรณ์ Nexus โดยการอ่านค่า การวัดที่เผยแพร่

คุณสามารถวัดเวลาในการตอบสนองของเสียงไป-กลับได้โดยการสร้างแอปที่สร้างสัญญาณเสียง คอยฟังสัญญาณนั้น และวัดเวลาตั้งแต่การส่งและรับสัญญาณ

และเนื่องจากเส้นทางเสียงจะมีเวลาในการตอบสนองต่ำที่สุด โดยมีการประมวลผลสัญญาณน้อยที่สุด คุณจึงอาจ และต้องการใช้ ดองเกิล Loopback ของเสียง ซึ่งช่วยให้สามารถทดสอบผ่านขั้วต่อชุดหูฟังได้

แนวทางปฏิบัติแนะนำในการลดเวลาในการตอบสนอง

ตรวจสอบประสิทธิภาพเสียง

เอกสารข้อกำหนดความเข้ากันได้ของ Android (CDD) จะแจกแจงฮาร์ดแวร์และซอฟต์แวร์ ข้อกำหนดของอุปกรณ์ Android ที่ใช้งานร่วมกันได้ โปรดดู ความเข้ากันได้กับ Android สำหรับข้อมูลเพิ่มเติมเกี่ยวกับโปรแกรมความเข้ากันได้โดยรวม และ CDD สำหรับเอกสาร CDD จริง

ใน CDD เวลาในการตอบสนองไป-กลับจะระบุเป็น 20 มิลลิวินาทีหรือน้อยกว่า (แม้ว่านักดนตรี โดยทั่วไปจะใช้เวลา 10 มิลลิวินาที) เนื่องจากมี Use Case สําคัญที่เปิดใช้โดย 20 มิลลิวินาที

ขณะนี้ยังไม่มี API ที่ใช้กำหนดเวลาในการตอบสนองของเสียงบนเส้นทางทั้งหมดในอุปกรณ์ 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 หรือในทางกลับกันก็ได้

ไม่ต้องคาดเดาเกี่ยวกับประสิทธิภาพเสียง

โปรดระวังสมมติฐานต่อไปนี้เพื่อหลีกเลี่ยงปัญหาเกี่ยวกับเวลาในการตอบสนอง

  • อย่าทึกทักเอาว่าลำโพงและไมโครโฟนที่ใช้ในอุปกรณ์เคลื่อนที่นั้น อะคูสติก เนื่องจากมีขนาดเล็ก คุณภาพเสียงโดยทั่วไปจะไม่ดีนัก การประมวลผลสัญญาณจึง เพิ่มเข้ามาเพื่อปรับปรุงคุณภาพเสียง การประมวลผลสัญญาณนี้จะทำให้เกิดเวลาในการตอบสนอง
  • อย่าคิดเอาเองว่าการเรียกกลับอินพุตและเอาต์พุตของคุณซิงค์กัน สําหรับการป้อนข้อมูลพร้อมกัน และเอาต์พุต ตัวแฮนเดิลคิวบัฟเฟอร์ ที่แยกต่างหากจะใช้สำหรับแต่ละฝั่ง ไม่มี รับประกันลำดับที่เกี่ยวข้องของ Callback เหล่านี้หรือการซิงค์นาฬิกาเสียง แม้ว่าทั้ง 2 ฝั่งจะใช้อัตราการสุ่มตัวอย่างเดียวกันก็ตาม แอปพลิเคชันควรบัฟเฟอร์ข้อมูลกับ การซิงค์บัฟเฟอร์ที่เหมาะสม
  • อย่าคิดเอาเองว่าอัตราตัวอย่างจริงตรงกับอัตราตัวอย่างค่ากลางทุกประการ สำหรับ ตัวอย่างเช่น หากอัตราการสุ่มตัวอย่างค่ากลางคือ 48,000 Hz ก็เป็นเรื่องปกติที่นาฬิกาเสียงจะเลื่อน ในอัตราที่แตกต่างจากระบบปฏิบัติการ CLOCK_MONOTONIC เล็กน้อย นั่นเป็นเพราะ เสียงและนาฬิกาของระบบอาจมาจากคริสตัลที่ต่างกัน
  • อย่าคาดเดาว่าอัตราการสุ่มตัวอย่างการเล่นจริงตรงกับตัวอย่างการบันทึกจริง โดยเฉพาะอย่างยิ่งหากปลายทางอยู่บนเส้นทางแยกกัน ตัวอย่างเช่น หากคุณกำลังจับภาพจาก ไมโครโฟนบนอุปกรณ์ที่มีอัตราการสุ่มตัวอย่างค่ากลาง 48,000 Hz และเล่นโดยใช้เสียง USB ที่อัตราการสุ่มตัวอย่างค่าปกติ 48,000 Hz อัตราการสุ่มตัวอย่างจริงจะมีแนวโน้มที่จะแตกต่างออกไปเล็กน้อย ได้

ผลของนาฬิกาเสียงที่อาจเป็นอิสระคือความต้องการอัตราการสุ่มตัวอย่างแบบไม่พร้อมกัน Conversion เทคนิคง่ายๆ (แต่ไม่เหมาะสำหรับคุณภาพเสียง) สำหรับอัตราการสุ่มตัวอย่างแบบไม่พร้อมกัน Conversion คือ การทำซ้ำหรือปล่อยตัวอย่างตามความจำเป็นใกล้กับจุดตัดขวางที่เป็นศูนย์ เพิ่มเติม จึงมีโอกาสทำ Conversion ที่ซับซ้อนได้

ลดเวลาในการตอบสนองของอินพุต

ส่วนนี้จะมีคำแนะนำที่จะช่วยลดเวลาในการตอบสนองของอินพุตเสียงเมื่อบันทึกด้วย ไมโครโฟนในตัวหรือไมโครโฟนของชุดหูฟังภายนอก

  • หากแอปกำลังตรวจสอบอินพุต โปรดแจ้งให้ผู้ใช้ใช้ชุดหูฟัง (เช่น โดยการแสดงหน้าจอดีที่สุดเมื่อใช้หูฟังเมื่อเรียกใช้ครั้งแรก) หมายเหตุ การใช้เพียงชุดหูฟังไม่ได้รับประกันว่า จะมีเวลาในการตอบสนองต่ำที่สุด คุณอาจต้องทำดังนี้ ดำเนินขั้นตอนอื่นๆ เพื่อลบการประมวลผลสัญญาณที่ไม่ต้องการออกจากเส้นทางเสียง เช่น โดยใช้ VOICE_RECOGNITION ที่กำหนดล่วงหน้าเมื่อบันทึก
  • เตรียมตัวรับมือกับอัตราการสุ่มตัวอย่างขั้นต่ำ 44,100 และ 48,000 Hz ตามที่รายงาน getProperty(String) สำหรับ PROPERTY_OUTPUT_SAMPLE_RATE อาจมีอัตราตัวอย่างอื่นๆ ด้วย แต่หายาก
  • เตรียมรับมือกับขนาดบัฟเฟอร์ที่รายงานโดย getProperty(String) สำหรับ PROPERTY_OUTPUT_FRAMES_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 Hz = 1000 mHz)

ใช้ขนาดบัฟเฟอร์ที่เหมาะสมที่สุดเพื่อกำหนดคิวข้อมูลเสียง

คุณสามารถหาขนาดบัฟเฟอร์ที่เหมาะสมที่สุดด้วยวิธีเดียวกันกับอัตราการสุ่มตัวอย่างที่เหมาะสมที่สุดโดยใช้ API ของ 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 (ฮาร์ดแวร์ Abstraction Layer) เก็บได้ คุณควรสร้างเสียง บัฟเฟอร์ เพื่อให้มีการคูณตัวเลขนี้ที่ตรงกัน หากคุณใช้หมายเลขที่ถูกต้อง ของเฟรมเสียง การเรียกกลับจะเกิดขึ้นในช่วงเวลาที่สม่ำเสมอ ซึ่งลดเสียงรบกวน

คุณจะต้องใช้ API เพื่อระบุขนาดบัฟเฟอร์แทนการใช้ค่าฮาร์ดโค้ด เนื่องจากขนาดบัฟเฟอร์ HAL แตกต่างกันไปตามอุปกรณ์และแต่ละบิลด์ของ Android

ไม่ต้องเพิ่มอินเทอร์เฟซเอาต์พุต ที่เกี่ยวข้องกับการประมวลผลสัญญาณ

โปรแกรมผสมแบบรวดเร็วรองรับเฉพาะอินเทอร์เฟซเหล่านี้เท่านั้น

  • SL_IID_ANDROIDSIMPLEBUFFERQUEUE
  • SL_IID_VOLUME
  • SL_IID_MUTESOLO

อินเทอร์เฟซเหล่านี้ไม่ได้รับอนุญาต เนื่องจากเกี่ยวข้องกับการประมวลผลสัญญาณและจะทำให้ คำขอสำหรับช่องทางด่วนที่จะถูกปฏิเสธ:

  • SL_IID_BASSBOOST
  • SL_IID_EFFECTSEND
  • SL_IID_สภาพแวดล้อม
  • 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. ตอนนี้ให้เล่นเสียงจากแอป คุณมีเวลาประมาณ 3 วินาทีในการเรียกใช้ คำสั่งต่อไปนี้จากเทอร์มินัล:
  5. adb shell dumpsys media.audio_flinger
    
  6. สแกนหารหัสกระบวนการของคุณ หากคุณเห็น F ในคอลัมน์ชื่อ แสดงว่าอยู่บน การติดตามเวลาในการตอบสนองต่ำ (F ย่อมาจาก fast Track)

ลดเวลาในการตอบสนองของการอุ่นเครื่อง

เมื่อคุณสร้างการจัดคิวข้อมูลเสียงเป็นครั้งแรก จะกินเวลาเพียงเล็กน้อยแต่ก็มีความหมาย ระยะเวลาที่วงจรเสียงของอุปกรณ์จะร้อนขึ้น เพื่อหลีกเลี่ยงเวลาในการตอบสนองของการอุ่นเครื่อง คุณสามารถ บัฟเฟอร์ย่อยของข้อมูลเสียงที่มีช่วงที่ไม่มีเสียง ดังที่แสดงในตัวอย่างโค้ดต่อไปนี้

#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. ระยะหน่วงเวลาไป-กลับ