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

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

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

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

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

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

ऐप्लिकेशन को एक 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 फ़्रेमवर्क ट्रांसकोड करेगा:

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 बनाना चाहिए. साथ ही, यह चुने गए कॉन्टेंट के लिए उपलब्ध होना चाहिए.

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

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

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