audio-streams.lisp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: AUDIO-STREAMS; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:audio-streams)
  4. (log5:defcategory cat-log-stream)
  5. (defmacro log-stream (&rest log-stuff) `(log5:log-for (cat-log-stream) ,@log-stuff))
  6. (deftype octet () '(unsigned-byte 8))
  7. (defmacro make-octets (len) `(make-array ,len :element-type 'octet))
  8. (defclass mem-stream ()
  9. ((stream-filename :accessor stream-filename :initform nil :initarg :stream-filename :documentation "if set, then MMAP file")
  10. (index :accessor index :initform 0)
  11. (stream-size :accessor stream-size :initform 0)
  12. (vect :accessor vect :initform nil :initarg :vect :documentation "if set, then the vector we want STREAM-ize"))
  13. (:documentation "A thin-wrapper class over mmaped-files and/or vectors."))
  14. (defmacro with-mem-stream-slots ((instance) &body body)
  15. `(with-slots (stream-filename index stream-size vect) ,instance
  16. (declare (fixnum index stream-size)
  17. (type (or (array (unsigned-byte 8) 1) null) vect))
  18. ,@body))
  19. (defun make-mem-stream (v) (make-instance 'mem-stream :vect v))
  20. (defun make-mmap-stream (f) (make-instance 'mem-stream :stream-filename f))
  21. (defmethod initialize-instance :after ((stream mem-stream) &key)
  22. "Stream initializer. If STREAM-FILENAME is set, MMAP a the file. Else, we assume VECT was set."
  23. (with-mem-stream-slots (stream)
  24. (when stream-filename
  25. #+CCL (setf vect (ccl:map-file-to-octet-vector stream-filename))
  26. #-CCL (error "Not Yet!")
  27. )
  28. (setf stream-size (length vect))))
  29. (defmethod stream-close ((stream mem-stream))
  30. "Close a stream, making the underlying object (file or vector) inaccessible."
  31. (declare #.utils:*standard-optimize-settings*)
  32. (with-mem-stream-slots (stream)
  33. (when stream-filename
  34. #+CCL (ccl:unmap-octet-vector vect)
  35. #-CCL (error "Not Yet")
  36. )
  37. (setf vect nil)))
  38. ;;; finding out current file position is so common, we also
  39. ;;; provide a macro
  40. (defmacro stream-here (stream) `(index ,stream))
  41. (defmethod stream-seek ((stream mem-stream) &optional (offset 0) (from :current))
  42. "Set INDEX to requested value. No error checking done here, but subsequent reads will fail if INDEX is out-of-bounds.
  43. As a convenience, OFFSET and FROM are optional, so (STREAM-SEEK stream) returns the current read-offset in stream."
  44. (declare #.utils:*standard-optimize-settings*)
  45. (declare (fixnum offset))
  46. (with-mem-stream-slots (stream)
  47. (ecase from
  48. (:start ; INDEX set to OFFSET from start of stream
  49. (setf index offset))
  50. (:current ; INDEX set relative to current INDEX, by OFFSET bytes
  51. (if (zerop offset)
  52. index
  53. (incf index offset)))
  54. (:end ; INDEX set to OFFSET from end of stream
  55. (setf index (- stream-size offset))))))
  56. (declaim (inline read-n-bytes))
  57. (defun read-n-bytes (stream n-bytes &key (bits-per-byte 8) (endian :little-endian))
  58. "Returns a FIXNUM constructed by reading N-BYTES. BITS-PER-BYTE contols how many bits should be used from each read byte."
  59. (declare #.utils:*standard-optimize-settings*)
  60. (declare (fixnum n-bytes))
  61. (with-mem-stream-slots (stream)
  62. (when (<= (+ index n-bytes) stream-size)
  63. (ecase endian
  64. (:little-endian
  65. (loop with value = 0
  66. for low-bit downfrom (* bits-per-byte (1- n-bytes)) to 0 by bits-per-byte do
  67. (setf (ldb (byte bits-per-byte low-bit) value) (aref vect index))
  68. (incf index)
  69. finally (return-from read-n-bytes value)))
  70. (:big-endian
  71. (loop with value = 0
  72. for low-bit upfrom 0 to (* bits-per-byte (1- n-bytes)) by bits-per-byte do
  73. (setf (ldb (byte bits-per-byte low-bit) value) (aref vect index))
  74. (incf index)
  75. finally (return-from read-n-bytes value))))))
  76. nil)
  77. (defun stream-read-u8 (stream)
  78. (declare #.utils:*standard-optimize-settings*)
  79. (with-mem-stream-slots (stream)
  80. (if (<= (+ index 1) stream-size)
  81. (let ((val (aref vect index)))
  82. (incf index)
  83. val)
  84. nil)))
  85. (defun stream-read-u16 (stream &key (bits-per-byte 8) (endian :little-endian)) (read-n-bytes stream 2 :bits-per-byte bits-per-byte :endian endian))
  86. (defun stream-read-u24 (stream &key (bits-per-byte 8) (endian :little-endian)) (read-n-bytes stream 3 :bits-per-byte bits-per-byte :endian endian))
  87. (defun stream-read-u32 (stream &key (bits-per-byte 8) (endian :little-endian)) (read-n-bytes stream 4 :bits-per-byte bits-per-byte :endian endian))
  88. (defun stream-read-u64 (stream &key (bits-per-byte 8) (endian :little-endian)) (read-n-bytes stream 8 :bits-per-byte bits-per-byte :endian endian))
  89. (defun stream-read-u128 (stream &key (bits-per-byte 8) (endian :little-endian)) (read-n-bytes stream 16 :bits-per-byte bits-per-byte :endian endian))
  90. (defmethod stream-read-sequence ((stream mem-stream) size &key (bits-per-byte 8))
  91. "Read in a sequence of octets at BITS-PER-BYTE. If BITS-PER-BYTE == 8, then simply return
  92. a displaced array from STREAMs underlying vector. If it is == 7, then we have to create a new vector and read into that."
  93. (declare #.utils:*standard-optimize-settings*)
  94. (with-mem-stream-slots (stream)
  95. (when (> (+ index size) stream-size)
  96. (setf size (- stream-size index)))
  97. (ecase bits-per-byte
  98. (8 (let ((octets (make-array size :element-type 'octet :displaced-to vect :displaced-index-offset index :adjustable nil)))
  99. (incf index size)
  100. (values octets size)))
  101. (7
  102. (let* ((last-byte-was-FF nil)
  103. (byte nil)
  104. (octets
  105. #-CCL (error "Not yet")
  106. #+CCL (ccl:with-output-to-vector (out)
  107. (dotimes (i size)
  108. (setf byte (stream-read-u8 stream))
  109. (if last-byte-was-FF
  110. (if (not (zerop byte))
  111. (write-byte byte out))
  112. (write-byte byte out))
  113. (setf last-byte-was-FF (= byte #xFF))))
  114. ))
  115. (values octets size))))))
  116. (defclass mp3-file-stream (mem-stream)
  117. ((id3-header :accessor id3-header :initform nil :documentation "holds all the ID3 info")
  118. (audio-info :accessor audio-info :initform nil :documentation "holds the bit-rate, etc info"))
  119. (:documentation "Stream for parsing MP3 files"))
  120. (defclass mp4-file-stream (mem-stream)
  121. ((mp4-atoms :accessor mp4-atoms :initform nil :documentation "holds tree of parsed MP4 atoms/boxes")
  122. (audio-info :accessor audio-info :initform nil :documentation "holds the bit-rate, etc info"))
  123. (:documentation "Stream for parsing MP4 audio files"))
  124. (defclass flac-file-stream (mem-stream)
  125. ((flac-headers :accessor flac-headers :initform nil :documentation "holds all the flac headers in file")
  126. (audio-info :accessor audio-info :initform nil :documentation "parsed audio info")
  127. (flac-tags :accessor flac-tags :initform nil :documentation "parsed comment tags."))
  128. (:documentation "Stream for parsing flac files"))
  129. (defun make-file-stream (filename)
  130. "Convenience function for creating a file stream. Detects file type and returns proper type stream."
  131. (declare #.utils:*standard-optimize-settings*)
  132. (log5:with-context "make-file-stream"
  133. (let* ((new-stream (make-mmap-stream filename))
  134. (ret-stream))
  135. (log-stream "Looking at ~a" filename)
  136. ;; detect file type and make RET-STREAM. if we don't recognize stream, RET-STREAM will be NULL
  137. (cond ((mp4-atom:is-valid-m4-file new-stream)
  138. (log-stream "~a is an MP4 file" filename)
  139. (setf ret-stream (make-instance 'mp4-file-stream :vect (vect new-stream) :stream-filename (stream-filename new-stream))))
  140. ((flac-frame:is-valid-flac-file new-stream)
  141. (log-stream "~a is a FLAC file" filename)
  142. (setf ret-stream (make-instance 'flac-file-stream :vect (vect new-stream) :stream-filename (stream-filename new-stream))))
  143. ((id3-frame:is-valid-mp3-file new-stream)
  144. (log-stream "~a is an ID3 file" filename)
  145. (setf ret-stream (make-instance 'mp3-file-stream :vect (vect new-stream) :stream-filename (stream-filename new-stream))))
  146. (t
  147. (log-stream "Unkown file type")))
  148. (stream-close new-stream)
  149. ret-stream)))
  150. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Strings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  151. ;;; Decode octets as an iso-8859-1 string (encoding == 0)
  152. (defun stream-decode-iso-string (octets &key (start 0) (end nil))
  153. (declare #.utils:*standard-optimize-settings*)
  154. #+CCL (ccl:decode-string-from-octets octets :start start :end end :external-format :iso-8859-1)
  155. #-CCL (error "Not Yet")
  156. )
  157. ;;;
  158. ;;; XXX: Coded this way because I can't seem to get a simple :external-format :ucs-2 to work correctly
  159. ;;; AND some taggers encode a UCS-2 empty string w/o a byte-order mark (i.e. null strings are
  160. ;;; sometimes encoded as #(00 00))
  161. (defun stream-decode-ucs-string (octets &key (start 0) (end nil))
  162. "Decode octets as a UCS string with a BOM (encoding == 1)"
  163. (declare #.utils:*standard-optimize-settings*)
  164. (labels ((get-byte-order-mark (octets)
  165. (let ((retval 0))
  166. (setf (ldb (byte 8 0) retval) (aref octets 1)
  167. (ldb (byte 8 8) retval) (aref octets 0))
  168. (when (not (or (= #xfffe retval) (= #xfeff retval)))
  169. (error "Got invalid byte-order mark of ~x in STREAM-DECODE-UCS-STRING" retval))
  170. retval)))
  171. ;; special case: empty (and mis-coded) string
  172. (cond ((zerop (length octets))
  173. (make-string 0))
  174. (t
  175. ;;
  176. ;; else, we have a (hopefully) properly encoded string
  177. (let ((bom (get-byte-order-mark octets)))
  178. (ecase (the fixnum bom)
  179. (#xfffe #+CCL (ccl:decode-string-from-octets octets :start (+ 2 start) :end end :external-format :ucs-2le)
  180. #-CCL (error "Not Yet")
  181. )
  182. (#xfeff #+CCL (ccl:decode-string-from-octets octets :start (+ 2 start) :end end :external-format :ucs-2be)
  183. #-CCL (error "Not Yet")
  184. )
  185. (0 (make-string 0))))))))
  186. (defun stream-decode-ucs-be-string (octets &key (start 0) (end nil))
  187. "Decode octets as a UCS-BE string (encoding == 2)"
  188. (declare #.utils:*standard-optimize-settings*)
  189. #+CCL (ccl:decode-string-from-octets octets :start start :end end :external-format :ucs-2be)
  190. #-CCL (error "Not Yet")
  191. )
  192. (defun stream-decode-utf-8-string (octets &key (start 0) (end nil))
  193. "Decode octets as a utf-8 string"
  194. (declare #.utils:*standard-optimize-settings*)
  195. #+CCL (ccl:decode-string-from-octets octets :start start :end end :external-format :utf-8)
  196. #-CCL (error "Not Yet")
  197. )
  198. (defun stream-decode-string (octets &key (start 0) (end nil) (encoding 0))
  199. "Decode octets depending on encoding"
  200. (declare #.utils:*standard-optimize-settings*)
  201. (ecase encoding
  202. (0 (stream-decode-iso-string octets :start start :end end))
  203. (1 (stream-decode-ucs-string octets :start start :end end))
  204. (2 (stream-decode-ucs-be-string octets :start start :end end))
  205. (3 (stream-decode-utf-8-string octets :start start :end end))))
  206. (defmethod stream-read-iso-string-with-len ((instream mem-stream) len)
  207. "Read an iso-8859-1 string of length 'len' (encoding = 0)"
  208. (declare #.utils:*standard-optimize-settings*)
  209. (stream-decode-iso-string (stream-read-sequence instream len)))
  210. (defmethod stream-read-ucs-string-with-len ((instream mem-stream) len)
  211. "Read an ucs-2 string of length 'len' (encoding = 1)"
  212. (declare #.utils:*standard-optimize-settings*)
  213. (stream-decode-ucs-string (stream-read-sequence instream len)))
  214. (defmethod stream-read-ucs-be-string-with-len ((instream mem-stream) len)
  215. "Read an ucs-2-be string of length 'len' (encoding = 2)"
  216. (declare #.utils:*standard-optimize-settings*)
  217. (stream-decode-ucs-be-string (stream-read-sequence instream len)))
  218. (defmethod stream-read-utf-8-string-with-len ((instream mem-stream) len)
  219. "Read an utf-8 string of length 'len' (encoding = 3)"
  220. (declare #.utils:*standard-optimize-settings*)
  221. (stream-decode-utf-8-string (stream-read-sequence instream len)))
  222. (defmethod stream-read-string-with-len ((instream mem-stream) len &key (encoding 0))
  223. "Read in a string of a given encoding of length 'len'"
  224. (declare #.utils:*standard-optimize-settings*)
  225. (ecase encoding
  226. (0 (stream-read-iso-string-with-len instream len))
  227. (1 (stream-read-ucs-string-with-len instream len))
  228. (2 (stream-read-ucs-be-string-with-len instream len))
  229. (3 (stream-read-utf-8-string-with-len instream len))))
  230. (defmethod stream-read-iso-string ((instream mem-stream))
  231. "Read in a null terminated iso-8859-1 string"
  232. (declare #.utils:*standard-optimize-settings*)
  233. (let ((octets #+CCL (ccl:with-output-to-vector (out)
  234. (do ((b (stream-read-u8 instream) (stream-read-u8 instream)))
  235. (nil)
  236. (when (zerop b)
  237. (return)) ; leave loop w/o writing
  238. (write-byte b out)))
  239. #-CCL (error "Not Yet")
  240. ))
  241. (stream-decode-iso-string octets)))
  242. (defmethod stream-read-ucs-string ((instream mem-stream))
  243. "Read in a null terminated UCS string."
  244. (declare #.utils:*standard-optimize-settings*)
  245. (let ((octets #+CCL (ccl:with-output-to-vector (out)
  246. (do* ((b0 (stream-read-u8 instream)
  247. (stream-read-u8 instream))
  248. (b1 (stream-read-u8 instream)
  249. (stream-read-u8 instream)))
  250. (nil)
  251. (when (and (zerop b0) (zerop b1))
  252. (return))
  253. (write-byte b0 out)
  254. (write-byte b1 out)))
  255. #-CCL (error "Not Yet")
  256. ))
  257. (stream-decode-ucs-string octets)))
  258. (defmethod stream-read-ucs-be-string ((instream mem-stream))
  259. "Read in a null terminated UCS-BE string."
  260. (declare #.utils:*standard-optimize-settings*)
  261. (let ((octets #+CCL (ccl:with-output-to-vector (out)
  262. (do* ((b0 (stream-read-u8 instream)
  263. (stream-read-u8 instream))
  264. (b1 (stream-read-u8 instream)
  265. (stream-read-u8 instream)))
  266. (nil)
  267. (when (and (zerop b0) (zerop b1))
  268. (return))
  269. (write-byte b0 out)
  270. (write-byte b1 out)))
  271. #-CCL (error "Not Yet")
  272. ))
  273. (stream-decode-ucs-be-string octets)))
  274. (defmethod stream-read-utf-8-string ((instream mem-stream))
  275. "Read in a null terminated utf-8 string (encoding == 3)"
  276. (declare #.utils:*standard-optimize-settings*)
  277. (let ((octets #+CCL (ccl:with-output-to-vector (out)
  278. (do ((b (stream-read-u8 instream)
  279. (stream-read-u8 instream)))
  280. (nil)
  281. (when (zerop b)
  282. (return))
  283. (write-byte b out)))
  284. #-CCL (error "Not Yet")
  285. ))
  286. (stream-decode-utf-8-string octets)))
  287. (defmethod stream-read-string ((instream mem-stream) &key (encoding 0))
  288. "Read in a null terminated string of a given encoding."
  289. (declare #.utils:*standard-optimize-settings*)
  290. (ecase encoding
  291. (0 (stream-read-iso-string instream))
  292. (1 (stream-read-ucs-string instream))
  293. (2 (stream-read-ucs-be-string instream))
  294. (3 (stream-read-utf-8-string instream))))
  295. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Files ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  296. (defvar *get-audio-info* t "controls whether the parsing functions also parse audio info like bit-rate, etc")
  297. (defmethod parse-audio-file ((stream mp4-file-stream) &key (get-audio-info *get-audio-info*) &allow-other-keys)
  298. "Parse an MP4A file by reading its ATOMS and decoding them."
  299. (declare #.utils:*standard-optimize-settings*)
  300. (handler-case
  301. (progn
  302. (mp4-atom:find-mp4-atoms stream)
  303. (when get-audio-info
  304. (setf (audio-info stream) (mp4-atom:get-mp4-audio-info stream))))
  305. (condition (c)
  306. (utils:warn-user "make-mp4-stream got condition: ~a" c))))
  307. (defmethod parse-audio-file ((stream flac-file-stream) &key (get-audio-info *get-audio-info*) &allow-other-keys)
  308. "Parse a flac file by reading its headers and decoding them."
  309. (declare #.utils:*standard-optimize-settings*)
  310. (declare (ignore get-audio-info)) ; audio info comes for "free" by parsing headers
  311. (handler-case
  312. (flac-frame:find-flac-frames stream)
  313. (condition (c)
  314. (utils:warn-user "make-flac-stream got condition: ~a" c))))
  315. (defmethod parse-audio-file ((stream mp3-file-stream) &key (get-audio-info *get-audio-info*) &allow-other-keys)
  316. "Parse an MP3 file by reading its FRAMES and decoding them."
  317. (declare #.utils:*standard-optimize-settings*)
  318. (handler-case
  319. (progn
  320. (id3-frame:find-id3-frames stream)
  321. (when get-audio-info
  322. (setf (audio-info stream) (mpeg:get-mpeg-audio-info stream))))
  323. (condition (c)
  324. (utils:warn-user "make-mp3-stream got condition: ~a" c))))