AAudio และ MMAP

AAudio เป็น API เสียงที่เปิดตัวใน Android 8.0 Android 8.1 มีการปรับปรุงเพื่อลดเวลาแฝงเมื่อใช้ร่วมกับ HAL และไดรเวอร์ที่รองรับ MMAP เอกสารนี้อธิบายฮาร์ดแวร์ Abstraction Layer (HAL) และการเปลี่ยนแปลงไดรเวอร์ที่จำเป็นเพื่อรองรับฟีเจอร์ MMAP ของ AAudio ใน Android

การรองรับ AAudio MMAP ต้องการ:

  • รายงานความสามารถ MMAP ของ HAL
  • การใช้ฟังก์ชันใหม่ใน HAL
  • เลือกใช้ ioctl() แบบกำหนดเองสำหรับบัฟเฟอร์โหมด EXCLUSIVE หรือไม่ก็ได้
  • ให้เส้นทางข้อมูลฮาร์ดแวร์เพิ่มเติม
  • การตั้งค่าคุณสมบัติของระบบที่เปิดใช้งานคุณสมบัติ MMAP

สถาปัตยกรรม AAudio

AAudio คือ C API เนทิฟตัวใหม่ที่เป็นทางเลือกแทน Open SL ES ใช้รูปแบบการออกแบบ Builder เพื่อสร้างสตรีมเสียง

AAudio ให้เส้นทางข้อมูลที่มีความหน่วงต่ำ ในโหมด EXCLUSIVE คุณลักษณะนี้อนุญาตให้โค้ดแอปพลิเคชันไคลเอ็นต์เขียนโดยตรงลงในบัฟเฟอร์ที่แมปหน่วยความจำซึ่งแชร์กับไดรเวอร์ ALSA ในโหมด SHARED มิกเซอร์ที่ทำงานใน AudioServer จะใช้บัฟเฟอร์ MMAP ในโหมด EXCLUSIVE เวลาแฝงจะน้อยลงอย่างมากเนื่องจากข้อมูลข้ามมิกเซอร์

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

ในแผนภาพด้านล่าง เราจะเห็นข้อมูล Pulse-code modulation (PCM) ที่ไหลลงมาผ่าน MMAP FIFO ไปยังไดรเวอร์ ALSA การประทับเวลาจะถูกร้องขอเป็นระยะโดยบริการ AAudio จากนั้นส่งต่อไปยังโมเดลการกำหนดเวลาของไคลเอนต์ผ่านคิวข้อความอะตอมมิก

แผนภาพการไหลของข้อมูล PCM
รูปที่ 1 ข้อมูล PCM ไหลผ่าน FIFO ไปยัง ALSA

ในโหมด SHARED จะใช้โมเดลการกำหนดเวลาด้วย แต่จะอยู่ใน AAudioService

สำหรับการจับเสียงจะใช้รูปแบบที่คล้ายกัน แต่ข้อมูล PCM จะไหลไปในทิศทางตรงกันข้าม

ฮาลเปลี่ยนแปลง

สำหรับ TinyALSA ดู:

external/tinyalsa/include/tinyalsa/asoundlib.h
external/tinyalsa/include/tinyalsa/pcm.c
int pcm_start(struct pcm *pcm);
int pcm_stop(struct pcm *pcm);
int pcm_mmap_begin(struct pcm *pcm, void **areas,
           unsigned int *offset,
           unsigned int *frames);
int pcm_get_poll_fd(struct pcm *pcm);
int pcm_mmap_commit(struct pcm *pcm, unsigned int offset,
           unsigned int frames);
int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr,
           struct timespec *tstamp);

สำหรับ HAL เดิม โปรดดู:

hardware/libhardware/include/hardware/audio.h
hardware/qcom/audio/hal/audio_hw.c
int start(const struct audio_stream_out* stream);
int stop(const struct audio_stream_out* stream);
int create_mmap_buffer(const struct audio_stream_out *stream,
                        int32_t min_size_frames,
                        struct audio_mmap_buffer_info *info);
int get_mmap_position(const struct audio_stream_out *stream,
                        struct audio_mmap_position *position);

สำหรับ HAL เสียง HIDL:

hardware/interfaces/audio/2.0/IStream.hal
hardware/interfaces/audio/2.0/types.hal
hardware/interfaces/audio/2.0/default/Stream.h
start() generates (Result retval);
stop() generates (Result retval) ;
createMmapBuffer(int32_t minSizeFrames)
       generates (Result retval, MmapBufferInfo info);
getMmapPosition()
       generates (Result retval, MmapPosition position);

รายงานการสนับสนุน MMAP

คุณสมบัติระบบ "aaudio.mmap_policy" ควรตั้งค่าเป็น 2 (AAUDIO_POLICY_AUTO) เพื่อให้เฟรมเวิร์กเสียงรู้ว่าโหมด MMAP ได้รับการสนับสนุนโดย audio HAL (ดู "การเปิดใช้งานเส้นทางข้อมูล AAudio MMAP" ด้านล่าง)

ไฟล์ audio_policy_configuration.xml ต้องมีโปรไฟล์เอาต์พุตและอินพุตเฉพาะสำหรับโหมด MMAP/NO IRQ เพื่อให้ตัวจัดการนโยบายเสียงทราบว่าจะเปิดสตรีมใดเมื่อสร้างไคลเอ็นต์ MMAP:

<mixPort name="mmap_no_irq_out" role="source"
            flags="AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_MMAP_NOIRQ">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                                samplingRates="48000"
                                channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>

<mixPort name="mmap_no_irq_in" role="sink" flags="AUDIO_INPUT_FLAG_MMAP_NOIRQ">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                                samplingRates="48000"
                                channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</mixPort>

เปิดและปิดสตรีม MMAP

createMmapBuffer(int32_t minSizeFrames)
            generates (Result retval, MmapBufferInfo info);

สตรีม MMAP สามารถเปิดและปิดได้โดยการเรียกใช้ฟังก์ชัน Tinyalsa

ค้นหาตำแหน่ง MMAP

การประทับเวลาที่ส่งกลับไปยัง Timing Model จะมีตำแหน่งเฟรมและเวลาแบบ MONOTONIC ในหน่วยนาโนวินาที:

getMmapPosition()
        generates (Result retval, MmapPosition position);

HAL สามารถรับข้อมูลนี้จากโปรแกรมควบคุม ALSA โดยการเรียกฟังก์ชัน Tinyalsa ใหม่:

int pcm_mmap_get_hw_ptr(struct pcm* pcm,
                        unsigned int *hw_ptr,
                        struct timespec *tstamp);

ตัวอธิบายไฟล์สำหรับหน่วยความจำที่ใช้ร่วมกัน

เส้นทางข้อมูล AAudio MMAP ใช้ขอบเขตหน่วยความจำที่แชร์ระหว่างฮาร์ดแวร์และบริการเสียง หน่วยความจำที่ใช้ร่วมกันถูกอ้างอิงโดยใช้ตัวอธิบายไฟล์ที่สร้างขึ้นโดยไดรเวอร์ ALSA

การเปลี่ยนแปลงเคอร์เนล

หากตัวอธิบายไฟล์เชื่อมโยงโดยตรงกับไฟล์ไดรเวอร์ /dev/snd/ บริการ AAudio จะสามารถใช้งานได้ในโหมด SHARED แต่ไม่สามารถส่ง descriptor ไปยังรหัสไคลเอ็นต์สำหรับโหมด EXCLUSIVE ได้ /dev/snd/ file descriptor จะให้การเข้าถึงไคลเอ็นต์ที่กว้างเกินไป ดังนั้นจึงถูกบล็อกโดย SELinux

เพื่อสนับสนุนโหมด EXCLUSIVE จำเป็นต้องแปลง /dev/snd/ descriptor เป็นตัวอธิบายไฟล์ anon_inode:dmabuf SELinux อนุญาตให้ส่งตัวอธิบายไฟล์นั้นไปยังไคลเอนต์ นอกจากนี้ยังสามารถใช้งานได้โดย AAudioService

ตัวอธิบายไฟล์ anon_inode:dmabuf สามารถสร้างขึ้นได้โดยใช้ไลบรารีหน่วยความจำ Android Ion

สำหรับข้อมูลเพิ่มเติม โปรดดูแหล่งข้อมูลภายนอกเหล่านี้:

  1. "ตัวจัดสรรหน่วยความจำ Android ION" https://lwn.net/Articles/480055/
  2. "ภาพรวม Android ION" https://wiki.linaro.org/BenjaminGaignard/ion
  3. "การรวมตัวจัดสรรหน่วยความจำ ION" https://lwn.net/Articles/565469/

ฮาลเปลี่ยนแปลง

บริการ AAudio จำเป็นต้องทราบว่ารองรับ anon_inode:dmabuf หรือไม่ ก่อน Android 10.0 วิธีเดียวที่จะทำได้คือส่งขนาดของบัฟเฟอร์ MMAP เป็นจำนวนลบ เช่น -2048 แทนที่จะเป็น 2048 หากรองรับ ใน Android 10.0 และใหม่กว่า คุณสามารถตั้งค่าสถานะ AUDIO_MMAP_APPLICATION_SHAREABLE ได้

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

การเปลี่ยนแปลงระบบย่อยเสียง

AAudio ต้องการเส้นทางข้อมูลเพิ่มเติมที่ส่วนหน้าของระบบเสียงย่อยเพื่อให้สามารถทำงานคู่ขนานกับเส้นทาง AudioFlinger ดั้งเดิมได้ เส้นทางดั้งเดิมนั้นใช้สำหรับเสียงของระบบและเสียงแอปพลิเคชันอื่นๆ ทั้งหมด ฟังก์ชันนี้อาจมีให้โดยตัวผสมซอฟต์แวร์ใน DSP หรือตัวผสมฮาร์ดแวร์ใน SOC

เปิดใช้งานเส้นทางข้อมูล AAudio MMAP

AAudio จะใช้เส้นทางข้อมูล AudioFlinger เดิม หาก MMAP ไม่ได้รับการสนับสนุนหรือไม่สามารถเปิดสตรีมได้ ดังนั้น AAudio จะทำงานร่วมกับอุปกรณ์เสียงที่ไม่รองรับเส้นทาง MMAP/NOIRQ

เมื่อทดสอบการรองรับ MMAP สำหรับ AAudio สิ่งสำคัญคือต้องทราบว่าคุณกำลังทดสอบเส้นทางข้อมูล MMAP จริง ๆ หรือเพียงทดสอบเส้นทางข้อมูลเดิมเท่านั้น ข้อมูลต่อไปนี้จะอธิบายวิธีการเปิดใช้งานหรือบังคับเส้นทางข้อมูลเฉพาะ และวิธีการสืบค้นเส้นทางที่ใช้โดยสตรีม

คุณสมบัติของระบบ

คุณสามารถตั้งค่านโยบาย MMAP ผ่านคุณสมบัติของระบบ:

  • 1 = AAUDIO_POLICY_NEVER - ใช้เฉพาะเส้นทางเดิมเท่านั้น อย่าแม้แต่จะลองใช้ MMAP
  • 2 = AAUDIO_POLICY_AUTO - ลองใช้ MMAP หากล้มเหลวหรือไม่พร้อมใช้งาน ให้ใช้เส้นทางเดิม
  • 3 = AAUDIO_POLICY_ALWAYS - ใช้เส้นทาง MMAP เท่านั้น อย่าถอยกลับไปสู่เส้นทางเดิม

สิ่งเหล่านี้อาจถูกตั้งค่าในอุปกรณ์ Makefile เช่น:

# Enable AAudio MMAP/NOIRQ data path.
# 2 is AAUDIO_POLICY_AUTO so it will try MMAP then fallback to Legacy path.
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_policy=2
# Allow EXCLUSIVE then fall back to SHARED.
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_exclusive_policy=2

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

adb root
adb shell setprop aaudio.mmap_policy 2
adb shell killall audioserver

มีฟังก์ชันที่ให้ไว้ใน ndk/sysroot/usr/include/aaudio/AAudioTesting.h ที่อนุญาตให้คุณแทนที่นโยบายสำหรับการใช้เส้นทาง MMAP:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

หากต้องการทราบว่าสตรีมใช้เส้นทาง MMAP หรือไม่ ให้โทร:

bool AAudioStream_isMMapUsed(AAudioStream* stream);