PCM5122 DAC 具有 Android 事物

PCM5122 DAC with Android Things

我有一个 Raspberry Pi 3B 和 Suptronics X920 Expansion Board 使用 PCM5122 DAC。所以我无法通过该板播放声音。

配置文件是默认的,除了显示配置部分:

kernel=u-boot-dtok.bin
framebuffer_depth=16

# Prevent the firmware from loading HAT overlays now that we handle pin muxing.
# ourselves. See:
# https://www.raspberrypi.org/documentation/configuration/device-tree.md#part3.4
dtoverlay=

dtparam=i2c_arm=on
dtparam=spi=on
dtparam=audio=on

# pwm and I2S are mutually-exclusive since they share hardware clocks.
dtoverlay=pwm-2chan-with-clk,pin=18,func=2,pin2=13,func2=4
dtoverlay=generic-i2s

start_x=1

# Tell U-boot to always use the "serial0" interface for the console, which is
# set to whichever uart (uart0 or uart1) is set to the header pins. This doesn't
# interfere with the uart selected for Bluetooth.
dtoverlay=chosen-serial0

# Enable skip-init on the UART interfaces, so U-Boot doesn't attempt to
# re-initialize them.
dtoverlay=rpi-uart-skip-init

# Add pin devices to the system for use by the runtime pin configuration driver.
dtoverlay=runtimepinconfig
dtoverlay=uart1
dtoverlay=bcm2710-rpi-3-b-spi0-pin-reorder

# Tell the I2S driver to use the cprman clock.
dtoverlay=bcm2710-rpi-3-b-i2s-use-cprman

# Uncomment to disable serial port on headers, use GPIO14 and GPIO15
# as gpios and to allow the core_freq to change at runtime.
enable_uart=1
core_freq=400

# Support official RPi display.
dtoverlay=i2c-rtc,ds3231
dtoverlay=rpi-ft5406
hdmi_force_hotplug=1

# Set framebuffer to support RGBA colors.
framebuffer_swap=0

# Waveshare display settings
max_usb_current=1
hdmi_group=2
hdmi_mode=87
hdmi_cvt 1024 600 60 6 0 0 0
hdmi_drive=1

这是播放声音文件的代码:

fun playSound(file: File) {
    val audioEncoding = AudioFormat.ENCODING_PCM_16BIT
    val sampleRate = 16000

    val audioOutputFormat = AudioFormat.Builder()
            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
            .setEncoding(audioEncoding)
            .setSampleRate(16000)
            .build()

    val audioOutputBufferSize = AudioTrack.getMinBufferSize(sampleRate, audioOutputFormat.channelMask, audioEncoding)

    val audioOutputDevice = findAudioDevice(AudioManager.GET_DEVICES_OUTPUTS, AudioDeviceInfo.TYPE_BUS)

    val audioTrack = AudioTrack.Builder()
            .setAudioFormat(audioOutputFormat)
            .setBufferSizeInBytes(audioOutputBufferSize)
            .setTransferMode(AudioTrack.MODE_STREAM)
            .build()

    audioTrack.preferredDevice = audioOutputDevice

    val buffer = ByteArray(audioOutputBufferSize)

    audioTrack.play()
    audioTrack.setVolume(1f)

    val stream = file.inputStream().buffered()
    try {
        while (stream.read(buffer) > 0) {
            val out = audioTrack.write(buffer, 0, buffer.size, AudioTrack.WRITE_BLOCKING)
            d { "audioTrack.write = $out" }
        }
    } catch (error: Throwable) {
        e(error) { "Error playing audio $file" }
    } finally {
        stream.close()
    }

    audioTrack.stop()
    audioTrack.release()
}

private fun findAudioDevice(deviceFlag: Int, deviceType: Int): AudioDeviceInfo? {
    val manager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    val adis = manager.getDevices(deviceFlag)
    for (adi in adis) {
        if (adi.type == deviceType) {
            return adi
        }
    }
    return null
}

我用常规 Raspberry Pi 音频输出(即 AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)测试了代码,它工作正常。但是对于 AudioDeviceInfo.TYPE_BUS 它只是没有任何错误地产生任何声音。

我尝试了各种配置选项,例如 dtoverlay=hifiberrydtoverlay=hifiberry-dacplus,但没有成功。

请帮忙。

看起来您可能正在使用 Google Assistant sample 的一些代码,并且您假设 TYPE_BUS 是启用音频路由以使用 I2S 总线所需的是正确的而不是内置音频插孔。

然而,这可能不是全部。 DAC 可能需要额外的配置命令 and/or 外部触发器。查看 similar HAT with the same DAC, for example, there is an I2C bus connection as well for DAC setup commands. Our Assistant sample uses the VoiceHAT driver 以完成 DAC 在该外设上所需的额外触发。

在 Raspbian 中,您通过 dtoverlay 启用的驱动程序可能会处理这两部分。在这里,您的代码将需要手动管理设置位。以 VoiceHAT 驱动程序在 Assistant 示例中的使用为例。

此外,请确保您没有将任何 I2S 引脚启用为 GPIO 或 PWM,因为这将禁用音频路由 per the documentation

旁注: Android Things 不支持通过 config.txt 进行内核更改,因此添加驱动程序预计不会有任何影响。

我已经有一段时间没弄明白了,所以我正在 post 编写适合我的代码,这样其他人就可以减少花在手册上的时间。

我花了几个小时通读 manual 并皱着眉头看着电路板的原理图,我发现 PCM5122 芯片需要一些预配置。

原来这个芯片有一个复杂的时钟方案。来自数据表:

The serial audio interface typically has 4 connections: SCK (system master clock), BCK (bit clock), LRCK (left right word clock), and DIN (data). The device has an internal PLL that is used to take either SCK or BCK and create the higher rate clocks required by the interpolating processor and the DAC clock. This allows the device to operate with or without an external SCK.

所以,长话短说,芯片的 PLL 操作取决于物理连接到 Raspberry 板上的引脚 - SCK、BCK 或两者:

在我的例子中是 BCK。我们需要 select PLL 时钟源与第 13 个寄存器:

解释完所有内容后,我将 post 我使用的完整驱动程序和一些额外的配置。您可以在链接的手册中找到所有信息。希望对你有帮助。

class SuptronicsX920AudioDevice private constructor(
        private val busDevice: AudioDeviceInfo,
        private val i2cDevice: I2cDevice) : AudioDevice {

    private var audioTrack: AudioTrack? = null
    private var leftVolume = 1f
    private var rightVolume = 1f

    companion object {
        private const val ERROR_DETECT_REG = 37
        private const val ERROR_DETECT_IDCM_BIT = 3
        private const val PLL_SOURCE_REG = 13
        private const val PLL_SOURCE_BCK_BIT = 4
        private const val AUTO_MUTE_REG = 65
        private const val DIGITAL_VOLUME_LEFT_REG = 61
        private const val DIGITAL_VOLUME_RIGHT_REG = 62

        fun create(busDevice: AudioDeviceInfo, i2cDevice: I2cDevice): Either<IOException, SuptronicsX920AudioDevice> {
            return try {
                // Ignore BCK\SCK missing errors as they turn device into Power down mode
                riseRegBit(i2cDevice, ERROR_DETECT_REG, ERROR_DETECT_IDCM_BIT)

                // Select BCK as the source for PLL
                riseRegBit(i2cDevice, PLL_SOURCE_REG, PLL_SOURCE_BCK_BIT)

                // Disable auto mute for both channels
                i2cDevice.writeRegByte(AUTO_MUTE_REG, 0)

                // Set the maximum gain for both channels
                i2cDevice.writeRegByte(DIGITAL_VOLUME_LEFT_REG, 0)
                i2cDevice.writeRegByte(DIGITAL_VOLUME_RIGHT_REG, 0)

                SuptronicsX920AudioDevice(busDevice, i2cDevice).right()
            } catch (ioe: IOException) {
                e(ioe) { "Unable to configure PCM512x for Suptronics x920" }
                ioe.left()
            }
        }

        private fun riseRegBit(i2cDevice: I2cDevice, regAddress: Int, bitAddress: Int) {
            val value = i2cDevice.readRegByte(regAddress)
            i2cDevice.writeRegByte(regAddress, value or (1 shl bitAddress).toByte())
        }
    }

    override fun play(stream: InputStream, audioFormat: AudioFormat) {
        val audioOutputBufferSize = AudioTrack.getMinBufferSize(
                audioFormat.sampleRate,
                audioFormat.channelMask,
                audioFormat.encoding)
        val buffer = ByteArray(audioOutputBufferSize)

        audioTrack = AudioTrack.Builder()
                .setAudioFormat(audioFormat)
                .setBufferSizeInBytes(audioOutputBufferSize)
                .setTransferMode(AudioTrack.MODE_STREAM)
                .build()
        audioTrack?.apply {
            preferredDevice = busDevice

            setStereoVolume(leftVolume, rightVolume)
            play()

            var bytes = 0
            try {
                while (stream.read(buffer) > 0) {
                    bytes += write(buffer, 0, buffer.size, AudioTrack.WRITE_BLOCKING)
                }
            } catch (error: Throwable) {
                e(error) { "Error playing audio" }
            }
            d { "$bytes of audio track written" }
        }
        stop()
        audioTrack = null
    }

    override fun stop() {
        audioTrack?.apply {
            if (state != AudioTrack.STATE_UNINITIALIZED) {
                try {
                    pause()
                    flush()
                    release()
                    d { "Audio stopped" }
                } catch (error: Throwable) {
                    e(error) { "Can't stop track properly" }
                }
            }
        }
    }

    override fun setVolume(leftVolume: Float, rightVolume: Float) {
        this.leftVolume = leftVolume
        this.rightVolume = rightVolume
        audioTrack?.apply { setStereoVolume(leftVolume, rightVolume) }
    }

    override fun close() {
        stop()
        i2cDevice.close()
    }
}