Android 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 লেভেল 33) anticipatory অডিও রুট চালু করেছে। আপনি
ডিভাইস অডিও অ্যাট্রিবিউট সাপোর্ট অনুমান করতে এবং অ্যাক্টিভ
অডিও ডিভাইসের জন্য ট্র্যাক প্রস্তুত করতে পারবেন। কোনও নির্দিষ্ট
ফর্ম্যাট ও অ্যাট্রিবিউটের জন্য বর্তমানে রাউট করা অডিও ডিভাইসে
ডাইরেক্ট প্লেব্যাক কাজ করে কিনা তা চেক করতে আপনি 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 লেভেল 23-এর জন্য, উপলভ্য অডিও ডিভাইসের তালিকায় পরিবর্তন
সম্পর্কিত তথ্য পেতে
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);