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

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

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

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

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

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

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

इस्तेमाल किए जा सकने वाले एन्कोडिंग के बारे में जानकारी पाना

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

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

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

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

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

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