অ্যান্ড্রয়েড টিভি ডিভাইসগুলোতে একই সাথে একাধিক অডিও আউটপুট সংযুক্ত করা যায়: যেমন টিভি স্পিকার, HDMI-সংযুক্ত হোম সিনেমা, ব্লুটুথ হেডফোন ইত্যাদি। এই অডিও আউটপুট ডিভাইসগুলো বিভিন্ন অডিও সক্ষমতা সমর্থন করতে পারে, যেমন এনকোডিং (ডলবি ডিজিটাল+, ডিটিএস, এবং পিসিএম), স্যাম্পল রেট এবং চ্যানেল। উদাহরণস্বরূপ, HDMI-সংযুক্ত টিভিগুলো বহু এনকোডিং সমর্থন করে, যেখানে সংযুক্ত ব্লুটুথ হেডফোনগুলো সাধারণত শুধু পিসিএম সমর্থন করে।
এইচডিএমআই ডিভাইস হট-প্লাগিং, ব্লুটুথ হেডফোন সংযোগ বা বিচ্ছিন্ন করা, অথবা ব্যবহারকারীর অডিও সেটিংস পরিবর্তনের মাধ্যমেও উপলব্ধ অডিও ডিভাইসের তালিকা এবং রাউটেড অডিও ডিভাইস পরিবর্তিত হতে পারে। যেহেতু অ্যাপগুলো মিডিয়া প্লে করার সময়েও অডিও আউটপুটের সক্ষমতা পরিবর্তিত হতে পারে, তাই অ্যাপগুলোকে এই পরিবর্তনগুলোর সাথে সাবলীলভাবে মানিয়ে নিতে হবে এবং নতুন রাউটেড অডিও ডিভাইস ও তার সক্ষমতা অনুযায়ী প্লেব্যাক চালিয়ে যেতে হবে। ভুল অডিও ফরম্যাট আউটপুট করলে ত্রুটি দেখা দিতে পারে বা কোনো শব্দ নাও বাজতে পারে।
অডিও ডিভাইসের সক্ষমতার উপর নির্ভর করে ব্যবহারকারীকে সেরা অডিও অভিজ্ঞতা দেওয়ার জন্য অ্যাপগুলো একই কন্টেন্ট একাধিক এনকোডিংয়ে আউটপুট করতে পারে। উদাহরণস্বরূপ, টিভি সমর্থন করলে একটি ডলবি ডিজিটাল এনকোডেড অডিও স্ট্রিম প্লে করা হয়, অন্যদিকে ডলবি ডিজিটালের সমর্থন না থাকলে আরও ব্যাপকভাবে সমর্থিত একটি পিসিএম অডিও স্ট্রিম বেছে নেওয়া হয়। একটি অডিও স্ট্রিমকে পিসিএম-এ রূপান্তর করতে ব্যবহৃত বিল্ট-ইন অ্যান্ড্রয়েড ডিকোডারগুলোর তালিকা ‘সমর্থিত মিডিয়া ফরম্যাট’ অংশে পাওয়া যাবে।
প্লেব্যাকের সময়, স্ট্রিমিং অ্যাপটি আউটপুট অডিও ডিভাইস দ্বারা সমর্থিত সেরা AudioFormat ব্যবহার করে একটি AudioTrack তৈরি করবে।
সঠিক ফরম্যাট ব্যবহার করে একটি ট্র্যাক তৈরি করুন
অ্যাপগুলোর উচিত একটি AudioTrack তৈরি করে সেটি বাজানো শুরু করা এবং শব্দ বাজানোর জন্য ডিফল্ট অডিও ডিভাইস নির্ধারণ করতে getRoutedDevice() কল করা। উদাহরণস্বরূপ, এটি একটি নিরাপদ, স্বল্প নীরবতার PCM এনকোডেড ট্র্যাক হতে পারে, যা শুধুমাত্র রাউটেড ডিভাইস এবং তার অডিও সক্ষমতা নির্ধারণের জন্য ব্যবহৃত হয়।
সমর্থিত এনকোডিংগুলি পান
ডিফল্ট অডিও ডিভাইসে উপলব্ধ অডিও ফরম্যাটগুলো জানতে getAudioProfiles() (API লেভেল 31 ও তার বেশি) অথবা getEncodings() (API লেভেল 23 ও তার বেশি) ব্যবহার করুন।
সমর্থিত অডিও প্রোফাইল এবং ফরম্যাটগুলি পরীক্ষা করুন
ফরম্যাট, চ্যানেল সংখ্যা এবং স্যাম্পল রেটের সমর্থিত সমন্বয়গুলো পরীক্ষা করতে AudioProfile (API লেভেল 31 এবং তার বেশি) অথবা isDirectPlaybackSupported() (API লেভেল 29 এবং তার বেশি) ব্যবহার করুন।
কিছু অ্যান্ড্রয়েড ডিভাইস আউটপুট অডিও ডিভাইস দ্বারা সমর্থিত এনকোডিং ছাড়াও অন্যান্য এনকোডিং সমর্থন করতে সক্ষম। এই অতিরিক্ত ফরম্যাটগুলো isDirectPlaybackSupported() এর মাধ্যমে শনাক্ত করা উচিত। এই ক্ষেত্রে, অডিও ডেটা আউটপুট অডিও ডিভাইস দ্বারা সমর্থিত একটি ফরম্যাটে পুনরায় এনকোড করা হয়। getEncodings() দ্বারা ফেরত আসা তালিকায় কাঙ্ক্ষিত ফরম্যাটটি উপস্থিত না থাকলেও, সেটির সমর্থন সঠিকভাবে পরীক্ষা করার জন্য isDirectPlaybackSupported() ব্যবহার করুন।
প্রত্যাশিত অডিও রুট
অ্যান্ড্রয়েড ১৩ (এপিআই লেভেল ৩৩) অ্যান্টিসিপেটরি অডিও রাউট চালু করেছে। আপনি ডিভাইসের অডিও অ্যাট্রিবিউট সাপোর্ট আগে থেকে অনুমান করতে এবং সক্রিয় অডিও ডিভাইসের জন্য ট্র্যাক প্রস্তুত করতে পারেন। একটি নির্দিষ্ট ফরম্যাট এবং অ্যাট্রিবিউটের জন্য বর্তমানে রাউট করা অডিও ডিভাইসে ডিরেক্ট প্লেব্যাক সমর্থিত কিনা তা পরীক্ষা করতে আপনি getDirectPlaybackSupport() ব্যবহার করতে পারেন:
কোটলিন
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 }
বিকল্পভাবে, আপনি বর্তমানে রাউট করা অডিও ডিভাইসের মাধ্যমে সরাসরি মিডিয়া প্লেব্যাকের জন্য কোন প্রোফাইলগুলি সমর্থিত তা জিজ্ঞাসা করতে পারেন। এর ফলে সেইসব প্রোফাইল বাদ পড়ে যায় যেগুলি অসমর্থিত অথবা, উদাহরণস্বরূপ, অ্যান্ড্রয়েড ফ্রেমওয়ার্ক দ্বারা ট্রান্সকোড করা হবে:
কোটলিন
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() ফাংশনটি সরবরাহকৃত AudioAttributes সহ বর্তমানে রাউট করা অডিও ডিভাইসের জন্য সমর্থিত AudioProfile অবজেক্টগুলোর একটি তালিকা ফেরত দেয়। পছন্দের AudioFormat আইটেমগুলোর তালিকাটি ততক্ষণ পর্যন্ত পুনরাবৃত্তি করা হয় যতক্ষণ না একটি মিলে যাওয়া সমর্থিত AudioProfile খুঁজে পাওয়া যায়। এই AudioProfile bestAudioProfile হিসেবে সংরক্ষণ করা হয়। bestAudioProfile থেকে সর্বোত্তম স্যাম্পল রেট এবং চ্যানেল মাস্ক নির্ধারণ করা হয়। সবশেষে, একটি উপযুক্ত AudioFormat ইনস্ট্যান্স তৈরি করা হয়।
অডিও ট্র্যাক তৈরি করুন
অ্যাপগুলোর উচিত এই তথ্য ব্যবহার করে ডিফল্ট অডিও ডিভাইস দ্বারা সমর্থিত (এবং নির্বাচিত কন্টেন্টের জন্য উপলব্ধ) সর্বোচ্চ মানের AudioFormat জন্য একটি AudioTrack তৈরি করা।
অডিও ডিভাইসের পরিবর্তনগুলি আটকান
অডিও ডিভাইসের পরিবর্তন শনাক্ত করতে এবং সে অনুযায়ী প্রতিক্রিয়া জানাতে, অ্যাপগুলোর উচিত:
- ২৪ বা তার বেশি এপিআই লেভেলের জন্য, অডিও ডিভাইসের পরিবর্তন (এইচডিএমআই, ব্লুটুথ, ইত্যাদি) নিরীক্ষণ করতে একটি
OnRoutingChangedListenerযোগ করুন। - এপিআই লেভেল ২৩-এর জন্য, উপলব্ধ অডিও ডিভাইস তালিকার পরিবর্তনগুলি পেতে একটি
AudioDeviceCallbackনিবন্ধন করুন। - এপিআই লেভেল ২১ এবং ২২-এর জন্য, এইচডিএমআই প্লাগ ইভেন্টগুলো পর্যবেক্ষণ করুন এবং ব্রডকাস্ট থেকে প্রাপ্ত অতিরিক্ত ডেটা ব্যবহার করুন।
- এছাড়াও, API 23-এর চেয়ে নিম্ন স্তরের ডিভাইসগুলির জন্য
BluetoothDeviceঅবস্থার পরিবর্তন নিরীক্ষণ করতে একটিBroadcastReceiverনিবন্ধন করুন, কারণAudioDeviceCallbackএখনও সমর্থিত নয়।
যখন AudioTrack এর জন্য কোনো অডিও ডিভাইস পরিবর্তনের বিষয়টি শনাক্ত করা হয়, তখন অ্যাপটির উচিত আপডেট হওয়া অডিও সক্ষমতাগুলো পরীক্ষা করা এবং প্রয়োজনে একটি ভিন্ন AudioFormat ব্যবহার করে AudioTrack টি পুনরায় তৈরি করা। যদি এখন কোনো উচ্চ-মানের এনকোডিং সমর্থিত হয় অথবা পূর্বে ব্যবহৃত এনকোডিংটি আর সমর্থিত না থাকে, তবে এটি করুন।
নমুনা কোড
কোটলিন
// 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);