【发布时间】:2014-02-11 15:48:38
【问题描述】:
我一直在为我的 Raspberry Pi 编写一个简单的 Clojure 框架来播放音乐(以及后来的一些其他东西)。程序解析给定的音乐目录中的歌曲,然后通过 TCP 接口开始监听控制命令(例如开始、停止、下一首歌曲)。
代码可通过 GitHub 获得: https://github.com/jvnn/raspi-framework
当前版本在我的笔记本电脑上运行良好,它会在收到指示时开始播放音乐(使用 JLayer 库)、更改歌曲并按应有的方式停止。 uberjar 在笔记本电脑上也需要几秒钟才能启动,但是当我尝试在 Raspberry Pi 上运行它时,速度变得异常缓慢。
启动程序以加载所有类并开始执行实际的程序代码需要一分钟多的时间。我尝试使用 -verbose:class 开关运行它,似乎 jvm 整个时间都在加载大量类(用于 Clojure 和其他所有类)。
当程序最终启动时,它确实会对给定的命令做出反应,但播放非常滞后。有一个亚秒级的短声,然后是几乎一秒钟的停顿,然后是另一个声音,另一个停顿等等......所以程序正在尝试播放一些东西,但它做的不够快。 CPU 使用率接近 98%。
现在,有了 Android 手机和所有设备,我确信 Java 可以在这样的硬件上很好地执行,可以毫无问题地播放一些 mp3 文件。而且我知道 JLayer(或其中的一部分)用于 gdx 游戏开发框架(也可以在 Android 上运行),所以它也不应该成为问题。
所以一切都表明我是问题所在。我可以用 leiningen(已经为所有文件启用 aot)、Raspberry Pi 或我的代码做些什么来让事情变得更快?
感谢您的宝贵时间!
更新: 我做了一个小测试用例来排除一些可能性,但以下 Clojure 代码仍然存在问题:
(ns test.core
(:import [javazoom.jl.player.advanced AdvancedPlayer])
(:gen-class))
(defn -main
[]
(let [filename "/path/to/a/music/file.mp3"
fis (java.io.FileInputStream. filename)
bis (java.io.BufferedInputStream. fis)
player (AdvancedPlayer. bis)]
(doto player (.play) (.close))))
project.clj:
(defproject test "0.0.1-SNAPSHOT"
:description "FIXME: write description"
:dependencies [[org.clojure/clojure "1.5.1"]
[javazoom/jlayer "1.0.1"]]
:javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"]
:aot :all
:main test.core)
所以,没有 core.async 也没有线程。播放确实变得更流畅了,但仍然是大约 200 毫秒的音乐和 200 毫秒的暂停。
【问题讨论】:
-
我认为这可能与 pi 的 CPU 上较小的缓存大小有关。 Clojure 很棒,但惯用的 Clojure 在缓存改动方面比大多数其他语言要求更高(特别是因为它大量使用不可变数据结构)。
-
我认为这不能解释播放问题(我猜也不是真正的缓慢启动时间)。因为(至少如果代码按照我的意思工作)在回放期间,由 java 库处理,所有 Clojure 代码都应该停放并等待一些异步通道输入。
-
停车可能涉及一些上下文切换。上下文切换会破坏您的缓存,这很昂贵(尤其是在总线速度较慢的情况下)。一旦您有一个媒体流直接从 RAM(甚至磁盘)传输到输出设备而无需修改,使其跳过的事情就是上下文切换,并且上下文切换需要的缓存越多,跳过问题就越极端会的。
-
我用最小的测试用例更新了这个问题。我添加的代码现在是唯一发生的事情,但它仍然远非顺利。我还通过使用 gstreamer 播放音乐文件检查了声音在 Pi 上是否正常工作,并且没有问题。
-
如果该代码跳过,那么问题要么是 javazoom 类,要么是 JVM。我正在写一个答案,但我查看了 javazoom 文档,发现没有关于输出设备缓冲区大小的选项。如果它不允许您调整缓冲区大小,您应该只是使用不同的库(考虑到代码的精简程度,除了更改输出缓冲区大小之外,我看不出还有什么可以提高性能)。
标签: audio clojure raspberry-pi