অডিও ক্ষমতা

অ্যান্ড্রয়েড টিভি ডিভাইসগুলিতে একই সাথে একাধিক অডিও আউটপুট সংযুক্ত থাকতে পারে: টিভি স্পিকার, HDMI-সংযুক্ত হোম সিনেমা, ব্লুটুথ হেডফোন ইত্যাদি। এই অডিও আউটপুট ডিভাইসগুলি এনকোডিং (ডলবি ডিজিটাল+, DTS, এবং PCM), নমুনা হার এবং চ্যানেলের মতো বিভিন্ন অডিও ক্ষমতা সমর্থন করতে পারে। উদাহরণস্বরূপ, HDMI-সংযুক্ত টিভিগুলিতে প্রচুর এনকোডিং সমর্থন থাকে যেখানে সংযুক্ত ব্লুটুথ হেডফোনগুলি সাধারণত কেবল PCM সমর্থন করে।

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

অ্যাপগুলিতে অডিও ডিভাইসের ক্ষমতার উপর নির্ভর করে ব্যবহারকারীকে সর্বোত্তম অডিও অভিজ্ঞতা প্রদানের জন্য একাধিক এনকোডিংয়ে একই বিষয়বস্তু আউটপুট করার ক্ষমতা রয়েছে। উদাহরণস্বরূপ, টিভিতে যদি ডলবি ডিজিটাল এনকোডেড অডিও স্ট্রিম চালানো হয়, তবে এটি চালানো হয়, যখন ডলবি ডিজিটালের জন্য কোনও সমর্থন না থাকলে আরও ব্যাপকভাবে সমর্থিত PCM অডিও স্ট্রিম বেছে নেওয়া হয়। একটি অডিও স্ট্রিমকে PCM-এ রূপান্তর করতে ব্যবহৃত বিল্ট-ইন অ্যান্ড্রয়েড ডিকোডারের তালিকা সমর্থিত মিডিয়া ফর্ম্যাটে পাওয়া যাবে।

প্লেব্যাকের সময়, স্ট্রিমিং অ্যাপটির আউটপুট অডিও ডিভাইস দ্বারা সমর্থিত সেরা 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 তৈরি করতে হবে (এবং নির্বাচিত সামগ্রীর জন্য উপলব্ধ)।

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

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

  • ২৪ এর সমান বা তার বেশি API লেভেলের জন্য, অডিও ডিভাইসের পরিবর্তনগুলি (HDMI, Bluetooth, ইত্যাদি) নিরীক্ষণ করতে একটি OnRoutingChangedListener যোগ করুন।
  • API লেভেল ২৩-এর জন্য, উপলব্ধ অডিও ডিভাইস তালিকার পরিবর্তনগুলি পেতে একটি AudioDeviceCallback নিবন্ধন করুন।
  • API লেভেল ২১ এবং ২২ এর জন্য, HDMI প্লাগ ইভেন্টগুলির জন্য নজর রাখুন এবং সম্প্রচার থেকে অতিরিক্ত ডেটা ব্যবহার করুন।
  • AudioDeviceCallback এখনও সমর্থিত না হওয়ায়, API 23 এর চেয়ে কম ডিভাইসের জন্য BluetoothDevice অবস্থার পরিবর্তনগুলি পর্যবেক্ষণ করতে একটি BroadcastReceiver নিবন্ধন করুন।

যখন 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);