ความสามารถด้านเสียง

อุปกรณ์ Android TV สามารถเชื่อมต่อเอาต์พุตเสียงหลายรายการพร้อมกันได้ ดังนี้ ลำโพงทีวี, โรงภาพยนตร์ในบ้านที่เชื่อมต่อ HDMI, หูฟังบลูทูธ และอื่นๆ อุปกรณ์เอาต์พุตเสียงเหล่านี้จะรองรับความสามารถด้านเสียงที่แตกต่างกัน เช่น การเข้ารหัส (Dolby Digital+, DTS และ PCM) อัตราตัวอย่าง และแชแนล ตัวอย่างเช่น ทีวีที่เชื่อมต่อ HDMI รองรับการเข้ารหัสที่หลากหลาย ขณะที่หูฟังบลูทูธที่เชื่อมต่อมักจะรองรับ PCM เท่านั้น

รายการอุปกรณ์เสียงที่พร้อมใช้งานและอุปกรณ์เสียงที่มีการกำหนดเส้นทางอาจเปลี่ยนแปลงได้ด้วย ด้วยการเสียบอุปกรณ์ HDMI ด้วยความร้อน เชื่อมต่อหรือยกเลิกการเชื่อมต่อหูฟังบลูทูธ หรือผู้ใช้เปลี่ยนการตั้งค่าเสียง เนื่องจากเอาต์พุตเสียงสามารถ เปลี่ยนแม้ในขณะที่แอปกำลังเล่นสื่อ แอปจำเป็นต้องปรับเปลี่ยนอย่างเหมาะสม เปลี่ยนและเล่นต่อบนอุปกรณ์เสียงที่มีการกำหนดเส้นทางใหม่และ ความสามารถ เอาต์พุตเสียงผิดรูปแบบอาจทําให้เกิดข้อผิดพลาดหรือ ไม่มีการเปิดเสียง

แอปสามารถแสดงผลเนื้อหาเดียวกันในการเข้ารหัสหลายรายการได้ เพื่อมอบประสบการณ์เสียงที่ดีที่สุดให้แก่ผู้ใช้ ตามอุปกรณ์เสียง ความสามารถ ตัวอย่างเช่น เมื่อมีการเล่นสตรีมเสียงที่เข้ารหัส Dolby Digital หากทีวีรองรับ ในขณะที่สตรีมเสียง PCM ที่สนับสนุนอย่างกว้างขวางกว่า เมื่อไม่มีการรองรับ Dolby Digital รายการ Android ในตัว ตัวถอดรหัสที่ใช้เปลี่ยนรูปแบบสตรีมเสียงเป็น PCM อยู่ใน รูปแบบสื่อที่รองรับ

ขณะเล่น แอปสตรีมมิงควรสร้าง AudioTrack กับรูปภาพที่ดีที่สุด เอาต์พุตนี้รองรับ AudioFormat อุปกรณ์เสียง

สร้างแทร็กด้วยรูปแบบที่ถูกต้อง

แอปควรสร้าง AudioTrack เริ่มเล่น และโทร getRoutedDevice() เพื่อกำหนดอุปกรณ์เสียงเริ่มต้นที่จะใช้เล่นเสียง ตัวอย่างเช่น แทร็กที่เข้ารหัส PCM แบบเงียบสั้นๆ ที่ปลอดภัยซึ่งใช้เพื่อ ระบุอุปกรณ์ที่กำหนดเส้นทางและความสามารถด้านเสียง

รับการเข้ารหัสที่รองรับ

ใช้ getAudioProfiles() (API ระดับ 31 ขึ้นไป) หรือ getEncodings() (API ระดับ 23 ขึ้นไป) เพื่อกำหนดรูปแบบเสียงที่มีใน อุปกรณ์เสียงเริ่มต้น

ตรวจสอบโปรไฟล์เสียงและรูปแบบที่รองรับ

ใช้ AudioProfile (API ระดับ 31 ขึ้นไป) หรือ isDirectPlaybackSupported() (API ระดับ 29 ขึ้นไป) เพื่อตรวจสอบชุดค่าผสมที่รองรับของรูปแบบ จำนวนช่อง และอัตราการสุ่มตัวอย่าง

อุปกรณ์ Android บางรุ่นสามารถรองรับการเข้ารหัสอื่นๆ นอกเหนือจาก ที่สนับสนุนโดยอุปกรณ์เสียงเอาต์พุต รูปแบบเพิ่มเติมเหล่านี้ควรเป็น ตรวจพบผ่านทาง isDirectPlaybackSupported() ในกรณีเหล่านี้ ข้อมูลเสียง มีการเข้ารหัสใหม่ให้อยู่ในรูปแบบที่อุปกรณ์เสียงเอาต์พุตรองรับ ใช้ isDirectPlaybackSupported()เพื่อตรวจสอบการรองรับรูปแบบที่ต้องการอย่างเหมาะสม แม้ว่าจะไม่ปรากฏในรายการที่ getEncodings() แสดงผลก็ตาม

เส้นทางเสียงที่คาดหวัง

Android 13 (API ระดับ 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() จะแสดงรายการที่สนับสนุน AudioProfile ออบเจ็กต์สำหรับ อุปกรณ์เสียงที่จัดเส้นทางในปัจจุบันโดยใช้ AudioAttributes รายการ ระบบจะทำซ้ำรายการ AudioFormat ที่ต้องการจนกว่าจะรองรับการจับคู่ที่ตรงกัน พบ AudioProfile AudioProfile นี้จัดเก็บเป็น bestAudioProfile อัตราการสุ่มตัวอย่างและมาสก์ของช่องที่เหมาะสมที่สุดจะกำหนดจาก bestAudioProfile สุดท้ายคือ AudioFormat ที่เหมาะสม มีการสร้างอินสแตนซ์แล้ว

สร้างแทร็กเสียง

แอปควรใช้ข้อมูลนี้เพื่อสร้าง AudioTrack สำหรับ AudioFormat คุณภาพสูงสุดที่อุปกรณ์เสียงเริ่มต้นรองรับ (และใช้ได้กับเนื้อหาที่เลือก)

สกัดกั้นการเปลี่ยนแปลงอุปกรณ์เสียง

หากต้องการสกัดกั้นและตอบสนองต่อการเปลี่ยนแปลงของอุปกรณ์เสียง แอปควรทำดังนี้

  • สำหรับ API ระดับเท่ากับหรือมากกว่า 24 ให้เพิ่มแอตทริบิวต์ OnRoutingChangedListener เพื่อตรวจสอบการเปลี่ยนแปลงของอุปกรณ์เสียง (HDMI, บลูทูธ เป็นต้น)
  • สำหรับ API ระดับ 23 ให้ลงทะเบียน AudioDeviceCallback เพื่อรับการเปลี่ยนแปลงในรายการอุปกรณ์เสียงที่พร้อมใช้งาน
  • สำหรับ API ระดับ 21 และ 22 ให้ตรวจสอบ เหตุการณ์เกี่ยวกับปลั๊ก HDMI และใช้ข้อมูลเพิ่มเติมจากการออกอากาศ
  • ลงทะเบียน BroadcastReceiver เพื่อตรวจสอบด้วย การเปลี่ยนแปลงสถานะของ BluetoothDevice สำหรับอุปกรณ์ที่ต่ำกว่า API 23 เนื่องจาก AudioDeviceCallback ไม่ใช่ ยังไม่รองรับ

เมื่อตรวจพบการเปลี่ยนแปลงอุปกรณ์เสียงสำหรับ AudioTrack แอป ควรตรวจสอบความสามารถด้านเสียงที่อัปเดตใหม่ และหากจำเป็น AudioTrack ที่มี AudioFormat ที่แตกต่างกัน ดำเนินการนี้หากคุณภาพสูงกว่า รองรับการเข้ารหัสแล้ว หรือการเข้ารหัสที่ใช้ก่อนหน้านี้คือ ไม่รองรับอีกต่อไป

โค้ดตัวอย่าง

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