অডিওর ক্ষমতা

Android TV-তে একাধিক অডিও আউটপুট একসাথে কাজ করে: TV স্পিকার, HDMI হোম সিনেমা, ব্লুটুথ এবং আরও অনেক কিছু। এইসব ডিভাইসে বিভিন্ন এনকোডিং (যেমন Dolby Digital+, DTS, PCM), স্যাম্পেল রেট ও চ্যানেল কাজ করে। HDMI-কানেক্টেড টিভিতে অনেক ফর্ম্যাট কাজ করে, কিন্তু ব্লুটুথে শুধু PCM কাজ করে।

প্লেব্যাক চলাকালীন উপলভ্য অডিও ডিভাইস এবং রাউটিং পরিবর্তন হতে পারে: ব্যবহারকারীরা হট-প্লাগ HDMI, ব্লুটুথ কানেক্ট বা সেটিংস পরিবর্তন করতে পারেন। নতুন ডিভাইসের ক্ষমতা ব্যবহার করে নিরবচ্ছিন্ন প্লেব্যাক নিশ্চিত করতে অ্যাপগুলিকে অবশ্যই মানিয়ে নিতে হবে। কাজ করে না এমন ফর্ম্যাটের কারণে সমস্যা হতে পারে বা কোনও সাউন্ড নাও শোনা যেতে পারে।

ডিভাইসের ক্ষমতার উপর ভিত্তি করে সেরা অভিজ্ঞতার জন্য একাধিক এনকোডিং অফার করুন। যেমন, Dolby Digital কাজ করলে সেটি ব্যবহার করা হয়; অন্যথায়, অ্যাপটি PCM-এ ফিরে যায়। Android ডিকোডারের জন্য কাজ করে এমন মিডিয়া ফর্ম্যাট দেখুন যা স্ট্রিমকে PCM-এ পরিবর্তন করে।

প্লেব্যাকের সময়, আপনার স্ট্রিমিং অ্যাপের উচিত আউটপুট অডিও ডিভাইসের মাধ্যমে কাজ করে এমন সেরা AudioFormat সহ AudioTrack তৈরি করা।

সঠিক ফর্ম্যাটে ট্র্যাক তৈরি করুন

অ্যাপকে একটি AudioTrack তৈরি করতে হবে, এটি চালানো শুরু করতে হবে এবং getRoutedDevice() কল করতে হবে যাতে ডিফল্ট অডিও ডিভাইস নির্ধারণ করা যায় যেখান থেকে সাউন্ড চালানো হবে। এটি হতে পারে, উদাহরণস্বরূপ, একটি নিরাপদ সংক্ষিপ্ত নীরবতা PCM এনকোড করা ট্র্যাক শুধুমাত্র রাউট করা ডিভাইস এবং এর অডিও ক্ষমতা নির্ধারণ করতে ব্যবহৃত হয়।

কাজ করে এমন এনকোডিং পান

ডিফল্ট অডিও ডিভাইসে উপলভ্য অডিও ফর্ম্যাট নির্ধারণ করতে getAudioProfiles() (API লেভেল ৩১ এবং তার পরবর্তী যেকোনও ভার্সন) অথবা getEncodings() (API লেভেল ২৩ এবং তার পরবর্তী যেকোনও ভার্সন) ব্যবহার করুন।

কাজ করে এমন অডিও প্রোফাইল ও ফর্ম্যাট চেক করা

ফর্ম্যাট, চ্যানেল সংখ্যা ও স্যাম্পেল রেটের কাজ করে এমন কম্বিনেশন চেক করতে AudioProfile (API লেভেল ৩১ ও তার চেয়ে বেশি) অথবা isDirectPlaybackSupported() (API লেভেল ২৯ ও তার চেয়ে বেশি) ব্যবহার করুন।

কিছু Android ডিভাইস আউটপুট অডিও ডিভাইসের মাধ্যমে কাজ করে এমন এনকোডিংয়ের বাইরেও এনকোডিংয়ে কাজ করতে পারে। এইসব অতিরিক্ত ফর্ম্যাট isDirectPlaybackSupported()-এর মাধ্যমে শনাক্ত করতে হবে। এইসব ক্ষেত্রে অডিও ডেটা এমন ফর্ম্যাটে রি-এনকোড করা হয় যা আউটপুট অডিও ডিভাইসে কাজ করে। getEncodings()-এর মাধ্যমে রিটার্ন করা তালিকায় না থাকলেও বেছে নেওয়া ফর্ম্যাটের জন্য সহায়তা সঠিকভাবে চেক করতে isDirectPlaybackSupported() ব্যবহার করুন।

প্রত্যাশিত অডিও রুট

Android 13 (API লেভেল ৩৩) আগাম অডিও রুট চালু করেছে। আপনি ডিভাইসের অডিও অ্যাট্রিবিউট সাপোর্ট সম্পর্কে অনুমান করতে এবং অ্যাক্টিভ অডিও ডিভাইসের জন্য ট্র্যাক প্রস্তুত করতে পারবেন। আপনি 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
}

জাভা

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()
}

জাভা

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 তৈরি করতে, অ্যাপের এই তথ্য ব্যবহার করা উচিত।

অডিও ডিভাইসের পরিবর্তন ইন্টারসেপ্ট করুন

অডিও ডিভাইসের পরিবর্তন ইন্টারসেপ্ট করতে এবং সেই অনুযায়ী কাজ করতে, অ্যাপকে এগুলি করতে হবে:

  • API লেভেল ২৪ বা তার বেশি হলে, অডিও ডিভাইসের পরিবর্তন (HDMI, ব্লুটুথ ইত্যাদি) মনিটর করতে একটি OnRoutingChangedListener যোগ করুন।
  • API লেভেল ২৩-এর জন্য, উপলভ্য অডিও ডিভাইসের তালিকায় পরিবর্তন পেতে AudioDeviceCallback রেজিস্টার করুন।
  • API লেভেল ২১ ও ২২-এর জন্য, HDMI প্লাগ ইভেন্ট মনিটর করুন এবং ব্রডকাস্ট থেকে অতিরিক্ত ডেটা ব্যবহার করুন।
  • এছাড়াও, API 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)

জাভা

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