AAudio และ MMAP

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

การสนับสนุน AAudio MMAP ต้องใช้สิ่งต่อไปนี้

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

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

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

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

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

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

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

ในโหมด "แชร์" ระบบจะใช้รูปแบบการจับเวลาด้วย แต่รูปแบบดังกล่าวจะอยู่ใน 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);

สำหรับ 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) เพื่อให้เฟรมเวิร์กเสียงทราบว่า HAL เสียงรองรับโหมด MMAP (ดู "การเปิดใช้เส้นทางข้อมูล MMAP ของ AAudio" ด้านล่าง)

ไฟล์ 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 ของคำค้นหา

การประทับเวลาที่ส่งกลับไปยังโมเดลการกําหนดเวลาประกอบด้วยตําแหน่งเฟรมและเวลาแบบต่อเนื่องเป็นนาโนวินาที

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);

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

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

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

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

หากต้องการรองรับโหมด EXCLUSIVE คุณต้องแปลงข้อบ่งชี้ /dev/snd/ เป็นข้อบ่งชี้ไฟล์ anon_inode:dmabuf SELinux อนุญาตให้ส่งตัวระบุไฟล์นั้นไปยังไคลเอ็นต์ นอกจากนี้ AAudioService ยังใช้ AAudioFormat ได้ด้วย

คุณสามารถสร้างตัวระบุไฟล์ anon_inode:dmabuf โดยใช้คลังหน่วยความจำ Android Ion

ดูข้อมูลเพิ่มเติมได้ที่แหล่งข้อมูลภายนอกต่อไปนี้

  1. "The Android ION memory allocator" https://lwn.net/Articles/480055/
  2. "ภาพรวม ION ของ Android" 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 ขึ้นไป คุณตั้งค่า Flag AUDIO_MMAP_APPLICATION_SHAREABLE ได้

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

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

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

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

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);