PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Zend/Media/Mpeg/Abs.php

http://php-reader.googlecode.com/
PHP | 456 lines | 222 code | 47 blank | 187 comment | 49 complexity | 9fc166f93547dcabebd623314720d55b MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Media
  17. * @subpackage MPEG
  18. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: Abs.php 261 2012-03-05 20:43:15Z svollbehr $
  21. */
  22. /**#@+ @ignore */
  23. require_once 'Zend/Media/Mpeg/Abs/Object.php';
  24. require_once 'Zend/Media/Mpeg/Abs/Frame.php';
  25. /**#@-*/
  26. /**
  27. * This class represents an MPEG Audio Bit Stream as described in
  28. * ISO/IEC 11172-3 and ISO/IEC 13818-3 standards.
  29. *
  30. * Non-standard VBR header extensions or namely XING, VBRI and LAME headers are
  31. * supported.
  32. *
  33. * This class is optimized for fast determination of the play duration of the
  34. * file and hence uses lazy data reading mode by default. In this mode the
  35. * actual frames and frame data are only read when referenced directly. You may
  36. * change this behaviour by giving an appropriate option to the constructor.
  37. *
  38. * @category Zend
  39. * @package Zend_Media
  40. * @subpackage MPEG
  41. * @author Ryan Butterfield <buttza@gmail.com>
  42. * @author Sven Vollbehr <sven@vollbehr.eu>
  43. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  44. * @license http://framework.zend.com/license/new-bsd New BSD License
  45. * @version $Id: Abs.php 261 2012-03-05 20:43:15Z svollbehr $
  46. * @todo Implement validation routines
  47. */
  48. final class Zend_Media_Mpeg_Abs extends Zend_Media_Mpeg_Abs_Object
  49. {
  50. /** @var integer */
  51. private $_bytes;
  52. /** @var Array */
  53. private $_frames = array();
  54. /** @var Zend_Media_Mpeg_Abs_XingHeader */
  55. private $_xingHeader = null;
  56. /** @var Zend_Media_Mpeg_Abs_LameHeader */
  57. private $_lameHeader = null;
  58. /** @var Zend_Media_Mpeg_Abs_VbriHeader */
  59. private $_vbriHeader = null;
  60. /** @var integer */
  61. private $_cumulativeBitrate = 0;
  62. /** @var integer */
  63. private $_cumulativePlayDuration = 0;
  64. /** @var integer */
  65. private $_estimatedBitrate = 0;
  66. /** @var integer */
  67. private $_estimatedPlayDuration = 0;
  68. /** @var integer */
  69. private $_lastFrameOffset = false;
  70. /**
  71. * Constructs the Zend_Media_Mpeg_ABS class with given file and options.
  72. *
  73. * The following options are currently recognized:
  74. * o readmode -- Can be one of full or lazy and determines when the read
  75. * of frames and their data happens. When in full mode the data is read
  76. * automatically during the instantiation of the frame and all the
  77. * frames are read during the instantiation of this class. While this
  78. * allows faster validation and data fetching, it is unnecessary in
  79. * terms of determining just the play duration of the file. Defaults to
  80. * lazy.
  81. *
  82. * o estimatePrecision -- Only applicaple with lazy read mode to determine
  83. * the precision of play duration estimate. This precision is equal to
  84. * how many frames are read before fixing the average bitrate that is
  85. * used to calculate the play duration estimate of the whole file. Each
  86. * frame adds about 0.1-0.2ms to the processing of the file. Defaults to
  87. * 1000.
  88. *
  89. * When in lazy data reading mode it is first checked whether a VBR header
  90. * is found in a file. If so, the play duration is calculated based no its
  91. * data and no further frames are read from the file. If no VBR header is
  92. * found, frames up to estimatePrecision are read to calculate an average
  93. * bitrate.
  94. *
  95. * Hence, only zero or <var>estimatePrecision</var> number of frames are
  96. * read in lazy data reading mode. The rest of the frames are read
  97. * automatically when directly referenced, ie the data is read when it is
  98. * needed.
  99. *
  100. * @param string|resource|Zend_Io_Reader $filename The path to the file,
  101. * file descriptor of an opened file, or a {@link Zend_Io_Reader} instance.
  102. * @param Array $options The options array.
  103. */
  104. public function __construct($filename, $options = array())
  105. {
  106. if ($filename instanceof Zend_Io_Reader) {
  107. $this->_reader = &$filename;
  108. } else {
  109. require_once 'Zend/Io/FileReader.php';
  110. try {
  111. $this->_reader = new Zend_Io_FileReader($filename);
  112. } catch (Zend_Io_Exception $e) {
  113. $this->_reader = null;
  114. require_once 'Zend/Media/Mpeg/Exception.php';
  115. throw new Zend_Media_Mpeg_Exception($e->getMessage());
  116. }
  117. }
  118. $this->setOptions($options);
  119. $offset = $this->_reader->getOffset();
  120. $this->_bytes = $this->_reader->getSize();
  121. /* Skip ID3v1 tag */
  122. $this->_reader->setOffset(-128);
  123. if ($this->_reader->read(3) == 'TAG') {
  124. $this->_bytes -= 128;
  125. }
  126. $this->_reader->setOffset($offset);
  127. /* Skip ID3v2 tags (some files errorneusly contain multiple tags) */
  128. while ($this->_reader->readString8(3) == 'ID3') {
  129. require_once 'Zend/Media/Id3/Header.php';
  130. $header = new Zend_Media_Id3_Header($this->_reader);
  131. $this->_reader->skip
  132. ($header->getSize() +
  133. ($header->hasFlag(Zend_Media_Id3_Header::FOOTER) ? 10 : 0));
  134. $offset = $this->_reader->getOffset();
  135. }
  136. $this->_reader->setOffset($offset);
  137. /* Check whether the ABS is contained within a RIFF chunk */
  138. $offset = $this->_reader->getOffset();
  139. if ($this->_reader->readString8(4) == 'RIFF') {
  140. $riffSize = $this->_reader->readUInt32LE();
  141. $riffType = $this->_reader->read(4); // WAVE
  142. while ($this->_reader->getOffset() < $offset + 8 + $riffSize - 1) {
  143. $chunkId = $this->_reader->read(4);
  144. $chunkSize = $this->_reader->readUInt32LE();
  145. if ($chunkId == 'fmt ') {
  146. if ($this->_reader->readInt16LE() != 85) { // 85: MPEG-1 Layer 3 Codec
  147. require_once 'Zend/Media/Mpeg/Exception.php';
  148. throw new Zend_Media_Mpeg_Exception
  149. ('File does not contain a valid MPEG Audio Bit Stream (Contains RIFF with no MPEG ABS)');
  150. } else {
  151. $this->_reader->skip($chunkSize - 2);
  152. }
  153. } else if ($chunkId == 'data') {
  154. $offset = $this->_reader->getOffset();
  155. break;
  156. } else {
  157. $this->_reader->skip($chunkSize);
  158. }
  159. }
  160. } else {
  161. $this->_reader->setOffset($offset);
  162. }
  163. /* Check for VBR headers */
  164. $this->_frames[] = $firstFrame =
  165. new Zend_Media_Mpeg_Abs_Frame($this->_reader, $options);
  166. $offset = $this->_reader->getOffset();
  167. $this->_reader->setOffset
  168. ($firstFrame->getOffset() + 4 + self::$sidesizes
  169. [$firstFrame->getFrequencyType()][$firstFrame->getMode()]);
  170. if (($xing = $this->_reader->readString8(4)) == 'Xing' ||
  171. $xing == 'Info') {
  172. require_once 'Zend/Media/Mpeg/Abs/XingHeader.php';
  173. $this->_xingHeader =
  174. new Zend_Media_Mpeg_Abs_XingHeader($this->_reader, $options);
  175. if ($this->_reader->readString8(4) == 'LAME') {
  176. require_once 'Zend/Media/Mpeg/Abs/LameHeader.php';
  177. $this->_lameHeader =
  178. new Zend_Media_Mpeg_Abs_LameHeader
  179. ($this->_reader, $options);
  180. }
  181. // A header frame is not counted as an audio frame
  182. array_pop($this->_frames);
  183. }
  184. $this->_reader->setOffset($firstFrame->getOffset() + 4 + 32);
  185. if ($this->_reader->readString8(4) == 'VBRI') {
  186. require_once 'Zend/Media/Mpeg/Abs/VbriHeader.php';
  187. $this->_vbriHeader =
  188. new Zend_Media_Mpeg_Abs_VbriHeader($this->_reader, $options);
  189. // A header frame is not counted as an audio frame
  190. array_pop($this->_frames);
  191. }
  192. $this->_reader->setOffset($offset);
  193. // Ensure we always have read at least one frame
  194. if (empty($this->_frames)) {
  195. $this->_readFrames(1);
  196. }
  197. /* Read necessary frames */
  198. if ($this->getOption('readmode', 'lazy') == 'lazy') {
  199. if ((($header = $this->_xingHeader) !== null ||
  200. ($header = $this->_vbriHeader) !== null) &&
  201. $header->getFrames() != 0) {
  202. $this->_estimatedPlayDuration = $header->getFrames() *
  203. $firstFrame->getSamples() /
  204. $firstFrame->getSamplingFrequency();
  205. if ($this->_lameHeader !== null) {
  206. $this->_estimatedBitrate = $this->_lameHeader->getBitrate();
  207. if ($this->_estimatedBitrate == 255) {
  208. $this->_estimatedBitrate = round
  209. (($this->_lameHeader->getMusicLength()) /
  210. (($header->getFrames() *
  211. $firstFrame->getSamples()) /
  212. $firstFrame->getSamplingFrequency()) / 1000 * 8);
  213. }
  214. } else {
  215. $this->_estimatedBitrate = ($this->_bytes - $firstFrame->getOffset()) /
  216. $this->_estimatedPlayDuration / 1000 * 8;
  217. }
  218. } else {
  219. $this->_readFrames($this->getOption('estimatePrecision', 1000));
  220. $this->_estimatedBitrate =
  221. $this->_cumulativeBitrate / count($this->_frames);
  222. $this->_estimatedPlayDuration =
  223. ($this->_bytes - $firstFrame->getOffset()) /
  224. ($this->_estimatedBitrate * 1000 / 8);
  225. }
  226. } else {
  227. $this->_readFrames();
  228. $this->_estimatedBitrate =
  229. $this->_cumulativeBitrate / count($this->_frames);
  230. $this->_estimatedPlayDuration = $this->_cumulativePlayDuration;
  231. }
  232. }
  233. /**
  234. * Returns <var>true</var> if the audio bitstream contains the Xing VBR
  235. * header, or <var>false</var> otherwise.
  236. *
  237. * @return boolean
  238. */
  239. public function hasXingHeader()
  240. {
  241. return $this->_xingHeader !== null;
  242. }
  243. /**
  244. * Returns the Xing VBR header, or <var>null</var> if not found in the audio
  245. * bitstream.
  246. *
  247. * @return Zend_Media_Mpeg_Abs_XingHeader
  248. */
  249. public function getXingHeader()
  250. {
  251. return $this->_xingHeader;
  252. }
  253. /**
  254. * Returns <var>true</var> if the audio bitstream contains the LAME VBR
  255. * header, or <var>false</var> otherwise.
  256. *
  257. * @return boolean
  258. */
  259. public function hasLameHeader()
  260. {
  261. return $this->_lameHeader !== null;
  262. }
  263. /**
  264. * Returns the LAME VBR header, or <var>null</var> if not found in the audio
  265. * bitstream.
  266. *
  267. * @return Zend_Media_Mpeg_Abs_LameHeader
  268. */
  269. public function getLameHeader()
  270. {
  271. return $this->_lameHeader;
  272. }
  273. /**
  274. * Returns <var>true</var> if the audio bitstream contains the Fraunhofer
  275. * IIS VBR header, or <var>false</var> otherwise.
  276. *
  277. * @return boolean
  278. */
  279. public function hasVbriHeader()
  280. {
  281. return $this->_vbriHeader !== null;
  282. }
  283. /**
  284. * Returns the Fraunhofer IIS VBR header, or <var>null</var> if not found in
  285. * the audio bitstream.
  286. *
  287. * @return Zend_Media_Mpeg_Abs_VbriHeader
  288. */
  289. public function getVbriHeader()
  290. {
  291. return $this->_vbriHeader;
  292. }
  293. /**
  294. * Returns the bitrate estimate. This value is either fetched from one of
  295. * the headers or calculated based on the read frames.
  296. *
  297. * @return integer
  298. */
  299. public function getBitrateEstimate()
  300. {
  301. return $this->_estimatedBitrate;
  302. }
  303. /**
  304. * For variable bitrate files this method returns the exact average bitrate
  305. * of the whole file.
  306. *
  307. * @return integer
  308. */
  309. public function getBitrate()
  310. {
  311. if ($this->getOption('readmode', 'lazy') == 'lazy') {
  312. $this->_readFrames();
  313. }
  314. return $this->_cumulativeBitrate / count($this->_frames);
  315. }
  316. /**
  317. * Returns the playtime estimate, in seconds.
  318. *
  319. * @return integer
  320. */
  321. public function getLengthEstimate()
  322. {
  323. return $this->_estimatedPlayDuration;
  324. }
  325. /**
  326. * Returns the exact playtime in seconds. In lazy reading mode the frames
  327. * are read from the file the first time you call this method to get the
  328. * exact playtime of the file.
  329. *
  330. * @return integer
  331. */
  332. public function getLength()
  333. {
  334. if ($this->getOption('readmode', 'lazy') == 'lazy') {
  335. $this->_readFrames();
  336. }
  337. return $this->_cumulativePlayDuration;
  338. }
  339. /**
  340. * Returns the playtime estimate as a string in the form of
  341. * [hours:]minutes:seconds.milliseconds.
  342. *
  343. * @param integer $seconds The playtime in seconds.
  344. * @return string
  345. */
  346. public function getFormattedLengthEstimate()
  347. {
  348. return $this->formatTime($this->getLengthEstimate());
  349. }
  350. /**
  351. * Returns the exact playtime given in seconds as a string in the form of
  352. * [hours:]minutes:seconds.milliseconds. In lazy reading mode the frames are
  353. * read from the file the first time you call this method to get the exact
  354. * playtime of the file.
  355. *
  356. * @param integer $seconds The playtime in seconds.
  357. * @return string
  358. */
  359. public function getFormattedLength()
  360. {
  361. return $this->formatTime($this->getLength());
  362. }
  363. /**
  364. * Returns all the frames of the audio bitstream as an array. In lazy
  365. * reading mode the frames are read from the file the first time you call
  366. * this method.
  367. *
  368. * @return Array
  369. */
  370. public function getFrames()
  371. {
  372. if ($this->getOption('readmode', 'lazy') == 'lazy') {
  373. $this->_readFrames();
  374. }
  375. return $this->_frames;
  376. }
  377. /**
  378. * Reads frames up to given limit. If called subsequently the method
  379. * continues after the last frame read in the last call, again to read up
  380. * to the limit or just the rest of the frames.
  381. *
  382. * @param integer $limit The maximum number of frames read from the
  383. * bitstream
  384. */
  385. private function _readFrames($limit = null)
  386. {
  387. if ($this->_lastFrameOffset !== false) {
  388. $this->_reader->setOffset($this->_lastFrameOffset);
  389. }
  390. for ($i = 0; ($j = $this->_reader->getOffset()) < $this->_bytes; $i++) {
  391. $options = $this->getOptions();
  392. $frame = new Zend_Media_Mpeg_Abs_Frame($this->_reader, $options);
  393. $this->_cumulativePlayDuration +=
  394. (double)($frame->getLength() /
  395. ($frame->getBitrate() * 1000 / 8));
  396. $this->_cumulativeBitrate += $frame->getBitrate();
  397. $this->_frames[] = $frame;
  398. if ($limit === null) {
  399. $this->_lastFrameOffset = $this->_reader->getOffset();
  400. }
  401. if (($limit !== null && (($i + 1) == $limit)) ||
  402. ($limit !== null &&
  403. ($j + $frame->getLength() >= $this->_bytes))) {
  404. $this->_lastFrameOffset = $this->_reader->getOffset();
  405. break;
  406. }
  407. }
  408. }
  409. }