ऑडियो की सुविधाएं

Android TV डिवाइसों में एक समय पर कई ऑडियो आउटपुट कनेक्ट किए जा सकते हैं: टीवी स्पीकर, एचडीएमआई से कनेक्ट किया गया होम सिनेमा, ब्लूटूथ हेडफ़ोन वगैरह. इन ऑडियो आउटपुट डिवाइस में, कोड में बदलने के तरीके (Dolby Digital+, DTS, और PCM), नमूना दर, और चैनल जैसी अलग-अलग ऑडियो सुविधाएं काम कर सकती हैं. उदाहरण के लिए, एचडीएमआई से कनेक्ट किए गए टीवी पर, कोड में बदलने के कई तरीके काम करते हैं. हालांकि, कनेक्ट किए गए ब्लूटूथ हेडफ़ोन पर आम तौर पर सिर्फ़ पीसीएम काम करते हैं.

एचडीएमआई डिवाइस को हॉट-प्लग करके, ब्लूटूथ हेडफ़ोन को कनेक्ट या डिसकनेक्ट करके या उपयोगकर्ता की ऑडियो सेटिंग में बदलाव करके भी, उपलब्ध ऑडियो डिवाइसों की सूची और रूट किए गए ऑडियो डिवाइस में बदलाव किया जा सकता है. ऐप्लिकेशन में मीडिया चलने के दौरान भी, ऑडियो आउटपुट की सुविधाओं में बदलाव हो सकते हैं. इसलिए, ऐप्लिकेशन को इन बदलावों के हिसाब से ढल जाना चाहिए और रूट किए गए नए ऑडियो डिवाइस और उसकी सुविधाओं पर वीडियो चलाना जारी रखना चाहिए. गलत ऑडियो फ़ॉर्मैट आउटपुट करने पर, गड़बड़ियां हो सकती हैं या आवाज़ नहीं चल सकती.

ऐप्लिकेशन में एक ही कॉन्टेंट को कई तरह के कोड में बदलने की सुविधा होती है. इससे, उपयोगकर्ता को ऑडियो डिवाइस की सुविधाओं के हिसाब से, बेहतर ऑडियो अनुभव मिलता है. उदाहरण के लिए, अगर टीवी पर YouTube TV की सुविधा काम करती है, तो Dolby Digital कोड में बदली गई ऑडियो स्ट्रीम चुनी जाती है. वहीं, Dolby Digital के लिए कोई सुविधा न होने पर, बड़े पैमाने पर काम करने वाली पीसीएम ऑडियो स्ट्रीम को चुना जाता है. ऑडियो स्ट्रीम को PCM में बदलने के लिए, Android में पहले से मौजूद डिकोडर का इस्तेमाल किया जाता है. इन डिकोडर की सूची, काम करने वाले मीडिया फ़ॉर्मैट में देखी जा सकती है.

वीडियो चलाने के दौरान, स्ट्रीमिंग ऐप्लिकेशन को सबसे बेहतर AudioTrack बनाना चाहिए. यह AudioFormat, आउटपुट ऑडियो डिवाइस के साथ काम करता है.

सही फ़ॉर्मैट में ट्रैक बनाएं

ऐप्लिकेशन को एक AudioTrack बनाना चाहिए, उसे चलाना चाहिए, और उस डिवाइस का पता लगाने के लिए getRoutedDevice() को कॉल करना चाहिए जिस पर ऑडियो चलाना है. उदाहरण के लिए, यह एक छोटा सा सुरक्षित PCM कोड वाला ट्रैक हो सकता है. इसका इस्तेमाल सिर्फ़ रूट किए गए डिवाइस और उसके ऑडियो की क्षमताओं का पता लगाने के लिए किया जाता है.

एन्कोडिंग के काम करने वाले फ़ॉर्मैट के बारे में जानकारी

डिफ़ॉल्ट ऑडियो डिवाइस पर उपलब्ध ऑडियो फ़ॉर्मैट का पता लगाने के लिए, getAudioProfiles() (एपीआई लेवल 31 और उसके बाद के वर्शन) या getEncodings() (एपीआई लेवल 23 और उसके बाद के वर्शन) का इस्तेमाल करें.

इस्तेमाल किए जा सकने वाले ऑडियो प्रोफ़ाइल और फ़ॉर्मैट देखना

फ़ॉर्मैट, चैनल की संख्या, और सैंपल रेट के काम करने वाले कॉम्बिनेशन देखने के लिए, AudioProfile (एपीआई लेवल 31 और इसके बाद के वर्शन) या isDirectPlaybackSupported() (एपीआई लेवल 29 और इसके बाद के वर्शन) का इस्तेमाल करें.

कुछ Android डिवाइसों पर, आउटपुट ऑडियो डिवाइस पर काम करने वाली एन्कोडिंग के अलावा, अन्य एन्कोडिंग भी काम करती हैं. इन अतिरिक्त फ़ॉर्मैट का पता isDirectPlaybackSupported() की मदद से लगाया जाना चाहिए. इन मामलों में ऑडियो डेटा को ऐसे फ़ॉर्मैट में फिर से एन्कोड किया जाता है जो आउटपुट ऑडियो डिवाइस के साथ काम करता है. isDirectPlaybackSupported() का इस्तेमाल करके, अपने पसंदीदा फ़ॉर्मैट के काम करने की जांच करें. भले ही, वह getEncodings() की दी गई सूची में मौजूद न हो.

आने वाले ऑडियो का रूट

Android 13 (एपीआई लेवल 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 फ़्रेमवर्क की मदद से ट्रांसकोड किया जाएगा:

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(), दिए गए AudioAttributes के साथ, फ़िलहाल रूट किए गए ऑडियो डिवाइस के लिए, काम करने वाले AudioProfile ऑब्जेक्ट की सूची दिखाता है. पसंदीदा AudioFormat आइटम की सूची में तब तक बदलाव किया जाता है, जब तक कि काम करने वाला कोई AudioProfile आइटम न मिल जाए. इस AudioProfile को bestAudioProfile के तौर पर सेव किया जाता है. ऑप्टिमाइज़ की गई सैंपल रेट और चैनल मास्क, bestAudioProfile से तय किए जाते हैं. आखिर में, AudioFormat का सही इंस्टेंस बन जाता है.

ऑडियो ट्रैक बनाएं

ऐप्लिकेशन को इस जानकारी का इस्तेमाल, डिफ़ॉल्ट ऑडियो डिवाइस पर काम करने वाली सबसे अच्छी क्वालिटी के AudioFormat के लिए AudioTrack बनाने के लिए करना चाहिए. यह AudioFormat, चुने गए कॉन्टेंट के लिए उपलब्ध होना चाहिए.

ऑडियो डिवाइस में होने वाले बदलावों को इंटरसेप्ट करना

ऑडियो डिवाइस में होने वाले बदलावों को इंटरसेप्ट करने और उन पर कार्रवाई करने के लिए, ऐप्लिकेशन को ये काम करने चाहिए:

  • अगर एपीआई लेवल 24 या उससे ज़्यादा के हैं, तो ऑडियो डिवाइस में हुए बदलावों (एचडीएमआई, ब्लूटूथ वगैरह) पर नज़र रखने के लिए OnRoutingChangedListener जोड़ें.
  • एपीआई लेवल 23 के लिए, उपलब्ध ऑडियो डिवाइस की सूची में बदलाव पाने के लिए, AudioDeviceCallback रजिस्टर करें.
  • एपीआई लेवल 21 और 22 के लिए, HDMI प्लग इवेंट को मॉनिटर करें और ब्रॉडकास्ट से मिले अतिरिक्त डेटा का इस्तेमाल करें.
  • एपीआई लेवल 23 से पहले के डिवाइसों के लिए, BluetoothDevice की स्थिति में होने वाले बदलावों को मॉनिटर करने के लिए, एक BroadcastReceiver भी रजिस्टर करें. ऐसा इसलिए, क्योंकि AudioDeviceCallback अभी काम नहीं करता.

जब AudioTrack के लिए ऑडियो डिवाइस में बदलाव का पता चलता है, तो ऐप्लिकेशन को ऑडियो की अपडेट की गई सुविधाओं की जांच करनी चाहिए. साथ ही, ज़रूरत पड़ने पर, किसी दूसरे AudioFormat के साथ AudioTrack को फिर से बनाना चाहिए. ऐसा तब करें, जब अब ज़्यादा क्वालिटी वाली एन्कोडिंग काम करती हो या पहले इस्तेमाल की गई एन्कोडिंग अब काम न करती हो.

नमूना कोड

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