הקלטת אודיו באיכות גבוהה ובקצב נמוך של העברת נתונים באמצעות xHE-AAC

החל מ-Android 17 QPR1 (רמת API ‏37.1 או Build.VERSION_CODES_FULL.CINNAMON_BUN_1), ‏ Android מציגה תמיכה בפלטפורמה בקידוד Extended High Efficiency Advanced Audio Coding (xHE-AAC).

‫xHE-AAC, שמוגדר ב-ISO/IEC 23003-3 (MPEG-D USAC) וב-ISO/IEC 23003-4 (MPEG-D DRC), משלב קידוד אודיו ודיבור מאוחד עם נורמליזציה חובה של עוצמת הקול ובקרת טווח דינמי מובנית. למרות שפענוח של xHE-AAC נתמך ונדרש על ידי מסמך הגדרת התאימות (CDD) של Android במספר גרסאות של הפלטפורמה, הוספה של מקודד xHE-AAC (הטמעות חומרה של c2.android.xheaac.encoder ו-OEM) מאפשרת להקליט הודעות קוליות והודעות אודיו עם קצב העברת נתונים נמוך, שידורי אודיו חיים ומטענים של תקשורת עם בהירות דיבור מעולה ועוצמת קול עקבית בכל המכשירים.

היתרונות של xHE-AAC

הקלטת קול רגילה באפליקציות להודעות ב-Android בדרך כלל משתמשת ב-AAC-LC (AAC Low Complexity) הישן יותר בקצב של 28kbps. בביטרייטים נמוכים (<32 kbps), ב-AAC-LC יש ארטיפקטים בולטים של הגבלת פס, איכות דיבור מטושטשת ויעילות דחיסה לא אופטימלית. משתמשי הקצה שומעים אודיו מעומעם, עם צליל מתכתי, שקשה להבחין בו בין העיצורים. לכן הם צריכים להפעיל מחדש את ההודעות או להתאמץ כדי להבין את הדיבור בסביבות רועשות. בנוסף, רמות עוצמת קול לא עקביות בקטעי אודיו מאלצות את המשתמשים לשנות את עוצמת הקול במכשיר לעיתים קרובות.

‫xHE-AAC מתגבר על המגבלות האלה באמצעות יישום של אלגוריתמים מתקדמים יותר של קידוד אודיו, למשל מעבר דינמי בין קידוד דיבור ייעודי לבין כלים כלליים לקידוד אודיו, תוך הרחבת טווח התדרים באמצעות שיפור של שכפול פס ספקטרלי (eSBR) ומתן יכולת קידוד סטריאו פרמטרי עם תקורה נוספת מינימלית של קידוד.

ה-DRC המובנה של MPEG-D מספק בקרת עוצמת קול חובה ל-xHE-AAC כדי להפעיל תוכן בעוצמת קול עקבית, ומציע עיבוד של בקרת טווח דינמי כדי לספק את חוויית המשתמש הטובה ביותר להאזנה בכל פלטפורמה ובכל סביבה.

תרחישים לדוגמה לטירגוט

  • שירותי תקשורת מגוונים (RCS) והודעות קוליות: עומד בדרישות של GSMA RCC.71 Universal Profile גרסה 3.1 ואילך, שבהן נדרש xHE-AAC להודעות אודיו עם קצב העברת נתונים נמוך.
  • סטרימינג של אודיו בשידור חי ופודקאסטים: מספק איכות אודיו טובה גם בחיבורים לרשתות סלולריות או לווייניות חלשות.

זמינות בפלטפורמות השונות (רמת API‏ 37.1 ומעלה)

כדי לתמוך בקידוד xHE-AAC בפלטפורמה, נדרשת רמת API‏ 37.1 (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_1 / SDK 3700001 ומעלה).

שאילתה לגבי תמיכה במקודד xHE-AAC

אסור להניח שקידוד xHE-AAC זמין בכל מכשיר על ידי קריאה ל-MediaCodec.createByCodecName(...) עם "c2.android.xheaac.encoder".

  • יכול להיות שמכשירים מסוימים יספקו מקודד xHE-AAC עם האצת חומרה מהספק של ערכת השבבים, ולא מקודד תוכנה של הפלטפורמה.
  • בגרסאות ישנות יותר של מערכת ההפעלה או ב-ROM בהתאמה אישית אין תמיכה בקידוד xHE-AAC.
  • לפני שמקודדים קובץ מצורף להעברת הודעות ישירות בין משתמשים (כמו RCS או צ'אט), האפליקציות צריכות גם לבדוק אם הלקוח המקבל מפרסם תמיכה בפענוח USAC/xHE-AAC (לדוגמה, באמצעות גילוי יכולות SDP‏ profile-level-id=55 / object=42) כדי למנוע כשלים בפעולה הדדית במקלטים מדור קודם.

בדיקת היכולות של MediaCodecList

כדי לבדוק אם המכשיר הנוכחי תומך בקידוד xHE-AAC, בודקים את profileLevels של מקודדים שתומכים ב-MediaFormat.MIMETYPE_AUDIO_AAC:

Kotlin

import android.media.MediaCodecInfo.CodecProfileLevel
import android.media.MediaCodecList
import android.media.MediaFormat
import android.os.Build

/**
 * Checks if the current device supports xHE-AAC audio encoding.
 */
fun isXheAacEncodingSupported(): Boolean {
    // Verify API Level >= 37.1 (CINNAMON_BUN_1 / SDK 3700001)
    if (Build.VERSION.SDK_INT_FULL < Build.VERSION_CODES_FULL.CINNAMON_BUN_1) {
        return false
    }

    val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
    for (codecInfo in codecList.codecInfos) {
        if (!codecInfo.isEncoder) continue

        if (MediaFormat.MIMETYPE_AUDIO_AAC in codecInfo.supportedTypes) {
            val capabilities =
                codecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC)
            // Check if AACObjectXHE (42) is present in profileLevels
            val supportsXhe = capabilities.profileLevels.any { profileLevel ->
                profileLevel.profile == CodecProfileLevel.AACObjectXHE
            }
            if (supportsXhe) {
                return true
            }
        }
    }
    return false
}

Java

import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.os.Build;

/**
 * Checks if the current device supports xHE-AAC audio encoding.
 */
public boolean isXheAacEncodingSupported() {
    // Verify API Level >= 37.1 (CINNAMON_BUN_1 / SDK 3700001)
    if (Build.VERSION.SDK_INT_FULL < Build.VERSION_CODES_FULL.CINNAMON_BUN_1) {
        return false;
    }

    MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
    for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
        if (!codecInfo.isEncoder()) continue;

        for (String type : codecInfo.getSupportedTypes()) {
            if (MediaFormat.MIMETYPE_AUDIO_AAC.equals(type)) {
                MediaCodecInfo.CodecCapabilities capabilities =
                        codecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC);
                for (CodecProfileLevel profileLevel : capabilities.profileLevels) {
                    if (profileLevel.profile == CodecProfileLevel.AACObjectXHE) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

הגדרת המקודד xHE-AAC והפעלתו

אחרי שמאמתים את התמיכה ב-xHE-AAC, מגדירים את MediaFormat עם KEY_AAC_PROFILE שמוגדר ל-MediaCodecInfo.CodecProfileLevel.AACObjectXHE (הערך 42).

Kotlin

fun setupXheAacEncoder(): MediaCodec? {
    if (!isXheAacEncodingSupported()) {
        return null
    }

    val mimeType = MediaFormat.MIMETYPE_AUDIO_AAC
    val sampleRate = 48000
    val channelCount = 1 // Mono voice recording
    val bitRate = 20000  // 20 kbps delivers superior speech clarity

    val format = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount).apply {
        setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
        setInteger(MediaFormat.KEY_AAC_PROFILE, CodecProfileLevel.AACObjectXHE)
    }

    // Find the hardware or software encoder supporting this format and profile
    val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
    val encoderName = codecList.findEncoderForFormat(format) ?: return null
    val encoder = MediaCodec.createByCodecName(encoderName)
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    return encoder
}

Java

public MediaCodec setupXheAacEncoder() throws IOException {
    if (!isXheAacEncodingSupported()) {
        return null;
    }

    String mimeType = MediaFormat.MIMETYPE_AUDIO_AAC;
    int sampleRate = 48000;
    int channelCount = 1; // Mono voice recording
    int bitRate = 20000;  // 20 kbps delivers superior speech clarity

    MediaFormat format = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount);
    format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
    format.setInteger(MediaFormat.KEY_AAC_PROFILE, CodecProfileLevel.AACObjectXHE);

    // Find the hardware or software encoder supporting this format and profile
    MediaCodecList codecList =
            new MediaCodecList(MediaCodecList.REGULAR_CODECS);
    String encoderName = codecList.findEncoderForFormat(format);
    if (encoderName == null) {
        return null;
    }

    MediaCodec encoder = MediaCodec.createByCodecName(encoderName);
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    return encoder;
}

מגבלות נתמכות של מקודדי תוכנה (c2.android.xheaac.encoder)

כשמשתמשים בתוכנת המקודד xHE-AAC שסופקה על ידי המערכת (c2.android.xheaac.encoder), הרכיב מפרסם את מגבלות היכולת הבאות:

  • מספר הערוצים: עד 2 ערוצים (מונו / סטריאו).
  • תדרי דגימה: 44100 Hz, 48000 Hz.
  • טווח קצב העברת הנתונים: 12,000 bps עד 400,000 bps (‎12 kbps – 400 kbps).
  • שילוב של מאגרי תגים: תואם לזרמי נתונים בסיסיים של MP4A ולשילוב של מאגרי תגים מסוג MP4/M4A בפלטפורמה (MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4).