יכולות אודיו

במכשירי Android TV אפשר לחבר כמה יציאות אודיו בו-זמנית: רמקולים של טלוויזיה, קולנוע ביתי שמחובר ל-HDMI, אוזניות Bluetooth וכן הלאה. המכשירים האלה לפלט אודיו יכולים לתמוך ביכולות אודיו שונות, כמו קידודים (Dolby Digital+ , DTS ו-PCM), קצב דגימה וערוצים. לדוגמה, טלוויזיות שמחוברות ל-HDMI תומכות במגוון קידודים ואוזניות Bluetooth מחוברות תומכות בדרך כלל רק ב-PCM.

גם הרשימה של התקני האודיו הזמינים והתקן האודיו מנותב יכולים להשתנות על ידי חיבור חם של מכשירי HDMI, חיבור או ניתוק של אוזניות Bluetooth או על ידי המשתמש שמשנה את הגדרות האודיו. מאחר שהיכולות של פלט האודיו יכולות גם אם האפליקציות מפעילות מדיה, הן צריכות להתאים את עצמן באלגנטיות משתנה ולהמשיך את ההפעלה במכשיר האודיו החדש שמנותב יכולות. פלט בפורמט אודיו שגוי עלול לגרום לשגיאות או לא מושמע צליל.

לאפליקציות יש אפשרות להפיק פלט של אותו תוכן במספר קידודים כדי להציע למשתמשים את חוויית האודיו הטובה ביותר בהתאם למכשיר האודיו יכולות. לדוגמה, מופעל שידור אודיו בקידוד Dolby Digital אם הטלוויזיה תומכת בכך, ואילו סטרימינג אודיו של PCM עם תמיכה רחבה יותר הוא שנבחרת כשאין תמיכה ב-Dolby Digital. רשימת המכשירים המובנים של Android אפשר למצוא מפענחים שמשמשים לטרנספורמציה של זרם אודיו ל-PCM פורמטים נתמכים של מדיה.

בזמן ההפעלה, אפליקציית הסטרימינג צריכה ליצור AudioTrack עם הטובים ביותר AudioFormat נתמך בפלט התקן אודיו.

יצירת טראק בפורמט הנכון

האפליקציות צריכות ליצור AudioTrack, להתחיל להפעיל אותו ולהתקשר getRoutedDevice() כדי לקבוע את התקן האודיו שמוגדר כברירת מחדל שממנו יושמעו צלילים. זה יכול להיות, לדוגמה, טראק בטוח לשימוש קצר בקידוד PCM שמשמש להשתקה של ה-PCM, לדעת איזה מכשיר מנתב ואת יכולות האודיו שלו.

קבלת קידודים נתמכים

כדאי להשתמש getAudioProfiles() (API ברמה 31 ומעלה) או getEncodings() (API ברמה 23 ומעלה) כדי לקבוע את הפורמטים של האודיו הזמינים התקן האודיו שמוגדר כברירת מחדל.

איך בודקים פרופילים ופורמטים נתמכים של אודיו

שימוש ב-AudioProfile (API ברמה 31 ומעלה) או isDirectPlaybackSupported() (API ברמה 29 ומעלה) כדי לבדוק שילובים נתמכים של פורמטים, מספר הערוצים וקצב הדגימה.

מכשירי Android מסוימים יכולים לתמוך בקידודים מעבר לאלה שנתמך על ידי התקן הפלט. הפורמטים הנוספים האלה צריכים להיות זוהה באמצעות isDirectPlaybackSupported(). במקרים כאלה, נתוני האודיו מקודד מחדש לפורמט שנתמך על ידי התקן הפלט. כדאי להשתמש isDirectPlaybackSupported() כדי לבדוק כראוי את התמיכה בפורמט הרצוי גם אם הוא לא מופיע ברשימה שהוחזרה על ידי getEncodings().

מסלול האודיו הצפוי

ב-Android 13 (רמת API 33) נוספו נתיבי אודיו צפויים. אפשר לצפות את התמיכה במאפייני האודיו במכשיר ולהכין טראקים למצב פעיל התקן אודיו. אפשר להשתמש getDirectPlaybackSupport() כדי לבדוק אם יש תמיכה בהפעלה ישירה באודיו שמנותב כרגע למכשיר המתאים לפורמט ולמאפיינים נתונים:

Kotlin

val format = AudioFormat.Builder()
    .setEncoding(AudioFormat.ENCODING_E_AC3)
    .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
    .setSampleRate(48000)
    .build()
val attributes = AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_MEDIA)
    .build()

if (AudioManager.getDirectPlaybackSupport(format, attributes) !=
    AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED
) {
    // The format and attributes are supported for direct playback
    // on the currently active routed audio path
} else {
    // The format and attributes are NOT supported for direct playback
    // on the currently active routed audio path
}

Java

AudioFormat format = new AudioFormat.Builder()
        .setEncoding(AudioFormat.ENCODING_E_AC3)
        .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
        .setSampleRate(48000)
        .build();
AudioAttributes attributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build();

if (AudioManager.getDirectPlaybackSupport(format, attributes) !=
        AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) {
    // The format and attributes are supported for direct playback
    // on the currently active routed audio path
} else {
    // The format and attributes are NOT supported for direct playback
    // on the currently active routed audio path
}

לחלופין, אפשר להריץ שאילתה לגבי הפרופילים הנתמכים למדיה ישירה הפעלה דרך התקן האודיו מנותב כרגע. זה לא כולל פרופילים שהן לא נתמכות, או שהן, למשל, יומרו על ידי מערכת ה-Android framework:

Kotlin

private fun findBestAudioFormat(audioAttributes: AudioAttributes): AudioFormat {
    val preferredFormats = listOf(
        AudioFormat.ENCODING_E_AC3,
        AudioFormat.ENCODING_AC3,
        AudioFormat.ENCODING_PCM_16BIT,
        AudioFormat.ENCODING_DEFAULT
    )
    val audioProfiles = audioManager.getDirectProfilesForAttributes(audioAttributes)
    val bestAudioProfile = preferredFormats.firstNotNullOf { format ->
        audioProfiles.firstOrNull { it.format == format }
    }
    val sampleRate = findBestSampleRate(bestAudioProfile)
    val channelMask = findBestChannelMask(bestAudioProfile)
    return AudioFormat.Builder()
        .setEncoding(bestAudioProfile.format)
        .setSampleRate(sampleRate)
        .setChannelMask(channelMask)
        .build()
}

Java

private AudioFormat findBestAudioFormat(AudioAttributes audioAttributes) {
    Stream<Integer> preferredFormats = Stream.<Integer>builder()
            .add(AudioFormat.ENCODING_E_AC3)
            .add(AudioFormat.ENCODING_AC3)
            .add(AudioFormat.ENCODING_PCM_16BIT)
            .add(AudioFormat.ENCODING_DEFAULT)
            .build();
    Stream<AudioProfile> audioProfiles =
            audioManager.getDirectProfilesForAttributes(audioAttributes).stream();
    AudioProfile bestAudioProfile = (AudioProfile) preferredFormats.map(format ->
            audioProfiles.filter(profile -> profile.getFormat() == format)
                    .findFirst()
                    .orElseThrow(NoSuchElementException::new)
    );
    Integer sampleRate = findBestSampleRate(bestAudioProfile);
    Integer channelMask = findBestChannelMask(bestAudioProfile);
    return new AudioFormat.Builder()
            .setEncoding(bestAudioProfile.getFormat())
            .setSampleRate(sampleRate)
            .setChannelMask(channelMask)
            .build();
}

בדוגמה הזו, preferredFormats הוא רשימה של AudioFormat מופעים. ההזמנה בוצעה עם העדיפות הראשונה ברשימה והכי פחות מועדפת אחרונה. getDirectProfilesForAttributes() מחזירה רשימה של AudioProfile אובייקטים עבור התקן האודיו מנותב כרגע עם AudioAttributes. הרשימה של הפריט המועדף עבור AudioFormat עובר חזרה עד שיש תמיכה ב האפליקציה AudioProfile נמצאה. AudioProfile זה מאוחסן בתור bestAudioProfile. קצב הדגימה האופטימלי ומסכות הערוצים נקבעים לפי bestAudioProfile. בסוף, AudioFormat מתאים והמכונה נוצרת.

יצירת טראק של אודיו

האפליקציות צריכות להשתמש במידע הזה כדי ליצור AudioTrack עבור AudioFormat באיכות הגבוהה ביותר שנתמך על ידי התקן האודיו שמוגדר כברירת מחדל (וזמין עבור התוכן שנבחר).

יירוט שינויים במכשיר האודיו

כדי ליירט שינויים במכשיר האודיו ולהגיב אליהם, האפליקציות צריכות:

  • עבור רמות API שוות ל-24 או יותר, מוסיפים OnRoutingChangedListener כדי לעקוב אחר שינויים במכשירי האודיו (HDMI, Bluetooth וכו').
  • לרמת API 23, רושמים AudioDeviceCallback כדי לקבל שינויים ברשימת התקני האודיו הזמינים.
  • ברמות API 21 ו-22, בצעו מעקב אחר אירועים של חיבור HDMI ולהשתמש בנתונים הנוספים מהשידורים.
  • צריך לרשום גם BroadcastReceiver למעקב BluetoothDevice שינויים במצב למכשירים עם פחות מ-API 23, מאז AudioDeviceCallback אינו עדיין נתמך.

כשזוהה שינוי במכשיר אודיו עבור AudioTrack, האפליקציה לבדוק את יכולות האודיו המעודכנות, ואם יש צורך, ליצור מחדש AudioTrack עם AudioFormat שונה. עשו זאת אם או שהקידוד שהיה בשימוש בעבר שלא נתמכים יותר.

קוד לדוגמה

Kotlin

// audioPlayer is a wrapper around an AudioTrack
// which calls a callback for an AudioTrack write error
audioPlayer.addAudioTrackWriteErrorListener {
    // error code can be checked here,
    // in case of write error try to recreate the audio track
    restartAudioTrack(findDefaultAudioDeviceInfo())
}

audioPlayer.audioTrack.addOnRoutingChangedListener({ audioRouting ->
    audioRouting?.routedDevice?.let { audioDeviceInfo ->
        // use the updated audio routed device to determine
        // what audio format should be used
        if (needsAudioFormatChange(audioDeviceInfo)) {
            restartAudioTrack(audioDeviceInfo)
        }
    }
}, handler)

Java

// audioPlayer is a wrapper around an AudioTrack
// which calls a callback for an AudioTrack write error
audioPlayer.addAudioTrackWriteErrorListener(new AudioTrackPlayer.AudioTrackWriteError() {
    @Override
    public void audioTrackWriteError(int errorCode) {
        // error code can be checked here,
        // in case of write error try to recreate the audio track
        restartAudioTrack(findDefaultAudioDeviceInfo());
    }
});

audioPlayer.getAudioTrack().addOnRoutingChangedListener(new AudioRouting.OnRoutingChangedListener() {
    @Override
    public void onRoutingChanged(AudioRouting audioRouting) {
        if (audioRouting != null && audioRouting.getRoutedDevice() != null) {
            AudioDeviceInfo audioDeviceInfo = audioRouting.getRoutedDevice();
            // use the updated audio routed device to determine
            // what audio format should be used
            if (needsAudioFormatChange(audioDeviceInfo)) {
                restartAudioTrack(audioDeviceInfo);
            }
        }
    }
}, handler);