במכשירי 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);