AAudio และ MMAP

AAudio เป็น Audio API ที่เปิดตัวใน Android 8.0 รุ่น Android 8.1 มีการปรับปรุงเพื่อลดเวลาแฝงเมื่อใช้ร่วมกับ HAL และไดรเวอร์ที่รองรับ MMAP เอกสารนี้อธิบายเกี่ยวกับ hardware 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 บัฟเฟอร์ MMAP ถูกใช้โดยมิกเซอร์ที่ทำงานอยู่ใน AudioServer ในโหมด EXCLUSIVE เวลาแฝงจะน้อยลงอย่างมากเนื่องจากข้อมูลข้ามตัวผสม

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

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

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

ในโหมด SHARED โมเดลเวลาก็ใช้เช่นกัน แต่อยู่ใน AAudioService

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

การเปลี่ยนแปลง HAL

สำหรับ 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);

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

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 ได้รับการสนับสนุนโดย 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

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

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

เพื่อรองรับโหมด EXCLUSIVE จำเป็นต้องแปลง descriptor /dev/snd/ เป็น anon_inode:dmabuf file descriptor 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/

การเปลี่ยนแปลง HAL

บริการ 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

คุณยังลบล้างค่าเหล่านี้ได้หลังจากที่อุปกรณ์บูตแล้ว คุณจะต้องรีสตาร์ทเซิร์ฟเวอร์เสียงเพื่อให้การเปลี่ยนแปลงมีผล ตัวอย่างเช่น ในการเปิดใช้งานโหมด AUTO สำหรับ 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);