streams.lisp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: 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 base-stream ()
  9. ((filename :accessor filename :initarg :filename)
  10. (instream :accessor instream :initform nil)
  11. (endian :accessor endian :initarg :endian :initform nil) ; controls endian-ness of read/writes
  12. (modified :accessor modified :initform nil) ; for when we implement writing tags
  13. (file-size :accessor file-size))
  14. (:documentation "Base class for all audio file types"))
  15. (defmethod initialize-instance :after ((me base-stream) &key read-only &allow-other-keys)
  16. (log5:with-context "base-stream-initializer"
  17. (with-slots (instream filename file-size endian) me
  18. (setf instream (if read-only
  19. (open filename :direction :input :element-type 'octet)
  20. (open filename :direction :io :if-exists :overwrite :element-type 'octet)))
  21. (setf binary-types:*endian* endian)
  22. (setf file-size (file-length instream))
  23. (log-stream "stream = ~a, name = ~a, size = ~:d~%endian = ~a"
  24. instream filename file-size endian))))
  25. (defmethod stream-close ((me base-stream))
  26. "Close an open stream."
  27. (with-slots (instream modified) me
  28. (when modified
  29. (warn "at some point, should I add code to auto-write modified audio-files?")
  30. (setf modified nil))
  31. (when instream
  32. (close instream)
  33. (setf instream nil))))
  34. (defmethod stream-seek ((me base-stream) offset from)
  35. "C-library like seek function. from can be one of :current, :start, :end.
  36. Returns the current offset into the stream"
  37. (assert (member from '(:current :start :end)) () "seek takes one of :current, :start, :end")
  38. (with-slots (instream file-size) me
  39. (ecase from
  40. (:start (file-position instream offset))
  41. (:current
  42. (let ((current (file-position instream)))
  43. (file-position instream (+ current offset))))
  44. (:end
  45. (file-position instream (- file-size offset))))))
  46. (defmethod stream-read-u8 ((me base-stream))
  47. "read 1 byte from file"
  48. (multiple-value-bind (value size) (binary-types:read-binary 'u8 (slot-value me 'instream))
  49. (assert (= size 1) () "Expected to read 1 byte, got ~d instead" size)
  50. value))
  51. (defmethod stream-read-u16 ((me base-stream))
  52. "read 2 bytes from file"
  53. (multiple-value-bind (value size) (binary-types:read-binary 'u16 (slot-value me 'instream))
  54. (assert (= size 2) () "Expected to read 2 bytes, got ~d instead" size)
  55. value))
  56. ;;; read 3-bytes
  57. (binary-types:define-unsigned u24 3)
  58. (defmethod stream-read-u24 ((me base-stream))
  59. "read 3 bytes from file"
  60. (multiple-value-bind (value size) (binary-types:read-binary 'u24 (slot-value me 'instream))
  61. (assert (= size 3) () "Expected to read 3 bytes, got ~d instead" size)
  62. value))
  63. (defmethod stream-read-u32 ((me base-stream))
  64. "read 4 bytes from file"
  65. (multiple-value-bind (value size) (binary-types:read-binary 'u32 (slot-value me 'instream))
  66. (assert (= size 4) () "Expected to read 4 bytes, got ~d instead" size)
  67. value))
  68. (defmethod stream-read-string ((me base-stream) &key size (terminators nil))
  69. "Read normal string from file. If size is provided, read exactly that many octets.
  70. If terminators is supplied, it is a list of characters that can terminate a string (and hence stop read)"
  71. (multiple-value-bind (value read-size)
  72. (binary-types:read-binary-string (slot-value me 'instream) :size size :terminators terminators)
  73. (declare (ignore read-size))
  74. ;; what checks should happen here?
  75. value))
  76. (defmethod stream-read-octets ((me base-stream) size)
  77. "Read SIZE octets from input-file"
  78. (let* ((octets (make-octets size))
  79. (read-len (read-sequence octets (slot-value me 'instream))))
  80. (assert (= read-len size))
  81. octets))
  82. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; MP4 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  83. (log5:defcategory cat-log-mp4-stream)
  84. (defmacro log-mp4-stream (&rest log-stuff) `(log5:log-for (cat-log-mp4-stream) ,@log-stuff))
  85. (defclass mp4-stream (base-stream)
  86. ((mp4-atoms :accessor mp4-atoms :initform nil))
  87. (:documentation "Class to access m4a/mp4 files"))
  88. (defun make-mp4-stream (filename read-only &key)
  89. "Convenience function to create an instance of MP4-FILE with appropriate init args"
  90. (log5:with-context "make-mp4-stream"
  91. (log-mp4-stream "opening ~a" filename)
  92. (let (handle)
  93. (handler-case
  94. (progn
  95. (setf handle (make-instance 'mp4-stream :filename filename :endian :big-endian :read-only read-only))
  96. (with-slots (mp4-atoms) handle
  97. (log-mp4-stream "getting atoms")
  98. (setf mp4-atoms (mp4-atom:find-mp4-atoms handle))))
  99. (condition (c)
  100. (warn "make-mp4-stream got condition: ~a" c)
  101. (when handle (stream-close handle))
  102. (setf handle nil)))
  103. handle)))
  104. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; MP3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  105. (log5:defcategory cat-log-mp3-stream)
  106. (defmacro log-mp3-stream (&rest log-stuff) `(log5:log-for (cat-log-mp3-stream) ,@log-stuff))
  107. (defclass mp3-stream (base-stream)
  108. ((mp3-header :accessor mp3-header :initform nil))
  109. (:documentation "Class to access mp3 files"))
  110. (defun make-mp3-stream (filename read-only &key)
  111. "Convenience function to create an instance of MP3-FILE with appropriate init args.
  112. NB: we assume non-syncsafe as default"
  113. (log5:with-context "make-mp3-stream"
  114. (log-mp3-stream "opening ~a" filename)
  115. (let (handle)
  116. (handler-case
  117. (progn
  118. (setf handle (make-instance 'mp3-stream :filename filename :endian :little-endian :read-only read-only))
  119. (with-slots (mp3-header) handle
  120. (log-mp3-stream "getting frames")
  121. (setf mp3-header (mp3-frame:find-mp3-frames handle))))
  122. (condition (c)
  123. (warn "make-mp3-stream got condition: ~a" c)
  124. (when handle (stream-close handle))
  125. (setf handle nil)))
  126. handle)))
  127. (defmethod stream-read-sync-safe-u32 ((me mp3-stream))
  128. "Read a sync-safe integer from file. Used by mp3 files"
  129. (let* ((ret 0))
  130. (setf (ldb (byte 7 21) ret) (stream-read-u8 me))
  131. (setf (ldb (byte 7 14) ret) (stream-read-u8 me))
  132. (setf (ldb (byte 7 7) ret) (stream-read-u8 me))
  133. (setf (ldb (byte 7 0) ret) (stream-read-u8 me))
  134. ret))
  135. (defmethod stream-read-sync-safe-octets ((me mp3-stream) len)
  136. "Used to undo sync-safe read of file"
  137. (let* ((last-byte-was-FF nil)
  138. (byte nil)
  139. (de-synced-data (binary-types:with-binary-output-to-vector (out)
  140. (dotimes (i len)
  141. (setf byte (stream-read-u8 me))
  142. (if last-byte-was-FF
  143. (if (not (zerop byte))
  144. (write-byte byte out))
  145. (write-byte byte out))
  146. (setf last-byte-was-FF (= byte #xFF))))))
  147. de-synced-data))