/* Audio File Library Copyright (C) 2011-2013, Michael Pruett This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "CAF.h" #include "Buffer.h" #include "File.h" #include "PacketTable.h" #include "Setup.h" #include "Tag.h" #include "Track.h" #include "byteorder.h" #include "util.h" #include #include #include #include const int _af_caf_compression_types[_AF_CAF_NUM_COMPTYPES] = { AF_COMPRESSION_G711_ULAW, AF_COMPRESSION_G711_ALAW, AF_COMPRESSION_IMA, AF_COMPRESSION_ALAC }; enum { kCAFLinearPCMFormatFlagIsFloat = (1L << 0), kCAFLinearPCMFormatFlagIsLittleEndian = (1L << 1) }; enum { kALACFormatFlag_16BitSourceData = 1, kALACFormatFlag_20BitSourceData = 2, kALACFormatFlag_24BitSourceData = 3, kALACFormatFlag_32BitSourceData = 4 }; static const unsigned kALACDefaultFramesPerPacket = 4096; static const _AFfilesetup cafDefaultFileSetup = { _AF_VALID_FILESETUP, // valid AF_FILE_CAF, // fileFormat true, // trackSet true, // instrumentSet true, // miscellaneousSet 1, // trackCount NULL, // tracks 1, // instrumentCount NULL, // instruments 0, // miscellaneousCount NULL // miscellaneous }; CAFFile::CAFFile() : m_dataOffset(-1), m_cookieDataOffset(-1) { setFormatByteOrder(AF_BYTEORDER_BIGENDIAN); } CAFFile::~CAFFile() { } bool CAFFile::recognize(File *file) { file->seek(0, File::SeekFromBeginning); uint8_t buffer[8]; if (file->read(buffer, 8) != 8 || memcmp(buffer, "caff", 4) != 0) return false; const uint8_t versionAndFlags[4] = { 0, 1, 0, 0 }; if (memcmp(buffer + 4, versionAndFlags, 4) != 0) return false; return true; } status CAFFile::readInit(AFfilesetup setup) { m_fh->seek(8, File::SeekFromBeginning); if (!allocateTrack()) return AF_FAIL; off_t currentOffset = m_fh->tell(); off_t fileLength = m_fh->length(); while (currentOffset < fileLength) { Tag chunkType; int64_t chunkLength; if (!readTag(&chunkType) || !readS64(&chunkLength)) return AF_FAIL; currentOffset += 12; if (chunkType == "data" && chunkLength == -1) chunkLength = fileLength - currentOffset; else if (chunkLength < 0) _af_error(AF_BAD_HEADER, "invalid chunk length %jd for chunk type %s\n", static_cast(chunkLength), chunkType.name().c_str()); if (chunkType == "desc") { if (parseDescription(chunkType, chunkLength) == AF_FAIL) return AF_FAIL; } else if (chunkType == "data") { if (parseData(chunkType, chunkLength) == AF_FAIL) return AF_FAIL; } else if (chunkType == "pakt") { if (parsePacketTable(chunkType, chunkLength) == AF_FAIL) return AF_FAIL; } else if (chunkType == "kuki") { if (parseCookieData(chunkType, chunkLength) == AF_FAIL) return AF_FAIL; } currentOffset = m_fh->seek(currentOffset + chunkLength, File::SeekFromBeginning); } return AF_SUCCEED; } status CAFFile::writeInit(AFfilesetup setup) { if (initFromSetup(setup) == AF_FAIL) return AF_FAIL; initCompressionParams(); Tag caff("caff"); if (!writeTag(&caff)) return AF_FAIL; const uint8_t versionAndFlags[4] = { 0, 1, 0, 0 }; if (m_fh->write(versionAndFlags, 4) != 4) return AF_FAIL; if (writeDescription() == AF_FAIL) return AF_FAIL; if (writeCookieData() == AF_FAIL) return AF_FAIL; if (writeData(false) == AF_FAIL) return AF_FAIL; return AF_SUCCEED; } AFfilesetup CAFFile::completeSetup(AFfilesetup setup) { if (setup->trackSet && setup->trackCount != 1) { _af_error(AF_BAD_NUMTRACKS, "CAF file must have 1 track"); return AF_NULL_FILESETUP; } TrackSetup *track = &setup->tracks[0]; if (track->sampleFormatSet) { if (track->f.isUnsigned()) { _af_error(AF_BAD_FILEFMT, "CAF format does not support unsigned data"); return AF_NULL_FILESETUP; } } else _af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, track->f.sampleWidth); if (track->f.isSigned() && (track->f.sampleWidth < 1 || track->f.sampleWidth > 32)) { _af_error(AF_BAD_WIDTH, "invalid sample width %d for CAF file (must be 1-32)", track->f.sampleWidth); return AF_NULL_FILESETUP; } if (!track->byteOrderSet) track->f.byteOrder = _AF_BYTEORDER_NATIVE; if (track->f.compressionType != AF_COMPRESSION_NONE && track->f.compressionType != AF_COMPRESSION_G711_ULAW && track->f.compressionType != AF_COMPRESSION_G711_ALAW && track->f.compressionType != AF_COMPRESSION_IMA && track->f.compressionType != AF_COMPRESSION_ALAC) { _af_error(AF_BAD_COMPTYPE, "compression format %d not supported in CAF file", track->f.compressionType); return AF_NULL_FILESETUP; } if (track->markersSet && track->markerCount) { _af_error(AF_BAD_NOT_IMPLEMENTED, "CAF does not yet support markers"); return AF_NULL_FILESETUP; } if (track->aesDataSet) { _af_error(AF_BAD_FILESETUP, "CAF does not support AES data"); return AF_NULL_FILESETUP; } return _af_filesetup_copy(setup, &cafDefaultFileSetup, true); } status CAFFile::update() { if (writeCookieData() == AF_FAIL) return AF_FAIL; if (writeData(true) == AF_FAIL) return AF_FAIL; if (writePacketTable() == AF_FAIL) return AF_FAIL; return AF_SUCCEED; } status CAFFile::parseDescription(const Tag &, int64_t) { double sampleRate; Tag formatID; uint32_t formatFlags; uint32_t bytesPerPacket; uint32_t framesPerPacket; uint32_t channelsPerFrame; uint32_t bitsPerChannel; if (!readDouble(&sampleRate) || !readTag(&formatID) || !readU32(&formatFlags) || !readU32(&bytesPerPacket) || !readU32(&framesPerPacket) || !readU32(&channelsPerFrame) || !readU32(&bitsPerChannel)) return AF_FAIL; if (!channelsPerFrame) { _af_error(AF_BAD_CHANNELS, "invalid file with 0 channels"); return AF_FAIL; } Track *track = getTrack(); track->f.channelCount = channelsPerFrame; track->f.sampleWidth = bitsPerChannel; track->f.sampleRate = sampleRate; track->f.framesPerPacket = 1; if (formatID == "lpcm") { track->f.compressionType = AF_COMPRESSION_NONE; if (formatFlags & kCAFLinearPCMFormatFlagIsFloat) { if (bitsPerChannel != 32 && bitsPerChannel != 64) { _af_error(AF_BAD_WIDTH, "invalid bits per sample %d for floating-point audio data", bitsPerChannel); return AF_FAIL; } track->f.sampleFormat = bitsPerChannel == 32 ? AF_SAMPFMT_FLOAT : AF_SAMPFMT_DOUBLE; } else { track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP; } track->f.byteOrder = (formatFlags & kCAFLinearPCMFormatFlagIsLittleEndian) ? AF_BYTEORDER_LITTLEENDIAN : AF_BYTEORDER_BIGENDIAN; if (_af_set_sample_format(&track->f, track->f.sampleFormat, track->f.sampleWidth) == AF_FAIL) return AF_FAIL; track->f.computeBytesPerPacketPCM(); return AF_SUCCEED; } else if (formatID == "ulaw") { track->f.compressionType = AF_COMPRESSION_G711_ULAW; track->f.byteOrder = _AF_BYTEORDER_NATIVE; _af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 16); track->f.bytesPerPacket = channelsPerFrame; return AF_SUCCEED; } else if (formatID == "alaw") { track->f.compressionType = AF_COMPRESSION_G711_ALAW; track->f.byteOrder = _AF_BYTEORDER_NATIVE; _af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 16); track->f.bytesPerPacket = channelsPerFrame; return AF_SUCCEED; } else if (formatID == "ima4") { track->f.compressionType = AF_COMPRESSION_IMA; track->f.byteOrder = _AF_BYTEORDER_NATIVE; _af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 16); initIMACompressionParams(); return AF_SUCCEED; } else if (formatID == "alac") { track->f.compressionType = AF_COMPRESSION_ALAC; track->f.byteOrder = _AF_BYTEORDER_NATIVE; switch (formatFlags) { case kALACFormatFlag_16BitSourceData: track->f.sampleWidth = 16; break; case kALACFormatFlag_20BitSourceData: track->f.sampleWidth = 20; break; case kALACFormatFlag_24BitSourceData: track->f.sampleWidth = 24; break; case kALACFormatFlag_32BitSourceData: track->f.sampleWidth = 32; break; default: _af_error(AF_BAD_CODEC_TYPE, "unsupported format flags for ALAC: %u", formatFlags); return AF_FAIL; } _af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, track->f.sampleWidth); track->f.framesPerPacket = framesPerPacket; track->f.bytesPerPacket = 0; return AF_SUCCEED; } else { _af_error(AF_BAD_NOT_IMPLEMENTED, "Compression type %s not supported", formatID.name().c_str()); return AF_FAIL; } } status CAFFile::parseData(const Tag &tag, int64_t length) { uint32_t editCount; if (!readU32(&editCount)) return AF_FAIL; Track *track = getTrack(); if (length == -1) track->data_size = m_fh->length() - m_fh->tell(); else track->data_size = length - 4; track->fpos_first_frame = m_fh->tell(); track->computeTotalFileFrames(); return AF_SUCCEED; } static uint32_t readBERInteger(const uint8_t *input, size_t *numBytes) { uint32_t result = 0; uint8_t data; size_t size = 0; do { data = input[size]; result = (result << 7) | (data & 0x7f); if (++size > 5) return 0; } while ((data & 0x80) && size < *numBytes); *numBytes = size; return result; } static void encodeBERInteger(uint32_t value, uint8_t *buffer, size_t *numBytes) { if ((value & 0x7f) == value) { *numBytes = 1; buffer[0] = value; } else if ((value & 0x3fff) == value) { *numBytes = 2; buffer[0] = (value >> 7) | 0x80; buffer[1] = value & 0x7f; } else if ((value & 0x1fffff) == value) { *numBytes = 3; buffer[0] = (value >> 14) | 0x80; buffer[1] = ((value >> 7) & 0x7f) | 0x80; buffer[2] = value & 0x7f; } else if ((value & 0x0fffffff) == value) { *numBytes = 4; buffer[0] = (value >> 21) | 0x80; buffer[1] = ((value >> 14) & 0x7f) | 0x80; buffer[2] = ((value >> 7) & 0x7f) | 0x80; buffer[3] = value & 0x7f; } else { *numBytes = 5; buffer[0] = (value >> 28) | 0x80; buffer[1] = ((value >> 21) & 0x7f) | 0x80; buffer[2] = ((value >> 14) & 0x7f) | 0x80; buffer[3] = ((value >> 7) & 0x7f) | 0x80; buffer[4] = value & 0x7f; } } status CAFFile::parsePacketTable(const Tag &tag, int64_t length) { if (length < 24) return AF_FAIL; int64_t numPackets; int64_t numValidFrames; int32_t primingFrames; int32_t remainderFrames; if (!readS64(&numPackets) || !readS64(&numValidFrames) || !readS32(&primingFrames) || !readS32(&remainderFrames)) { return AF_FAIL; } if (!numPackets) return AF_SUCCEED; int64_t tableLength = length - 24; SharedPtr buffer = new Buffer(tableLength); if (m_fh->read(buffer->data(), tableLength) != tableLength) return AF_FAIL; SharedPtr packetTable = new PacketTable(numValidFrames, primingFrames, remainderFrames); const uint8_t *data = static_cast(buffer->data()); size_t position = 0; while (position < buffer->size()) { size_t sizeRemaining = buffer->size() - position; uint32_t bytesPerPacket = readBERInteger(data + position, &sizeRemaining); if (bytesPerPacket == 0) break; packetTable->append(bytesPerPacket); position += sizeRemaining; } assert(numPackets == packetTable->numPackets()); Track *track = getTrack(); track->m_packetTable = packetTable; track->totalfframes = numValidFrames; return AF_SUCCEED; } status CAFFile::parseCookieData(const Tag &tag, int64_t length) { m_codecData = new Buffer(length); if (m_fh->read(m_codecData->data(), length) != length) return AF_FAIL; AUpvlist pv = AUpvnew(2); AUpvsetparam(pv, 0, _AF_CODEC_DATA_SIZE); AUpvsetvaltype(pv, 0, AU_PVTYPE_LONG); long l = length; AUpvsetval(pv, 0, &l); AUpvsetparam(pv, 1, _AF_CODEC_DATA); AUpvsetvaltype(pv, 1, AU_PVTYPE_PTR); void *v = m_codecData->data(); AUpvsetval(pv, 1, &v); Track *track = getTrack(); track->f.compressionParams = pv; return AF_SUCCEED; } status CAFFile::writeDescription() { Track *track = getTrack(); Tag desc("desc"); int64_t chunkLength = 32; double sampleRate = track->f.sampleRate; Tag formatID("lpcm"); uint32_t formatFlags = 0; if (track->f.byteOrder == AF_BYTEORDER_LITTLEENDIAN) formatFlags |= kCAFLinearPCMFormatFlagIsLittleEndian; if (track->f.isFloat()) formatFlags |= kCAFLinearPCMFormatFlagIsFloat; uint32_t bytesPerPacket = track->f.bytesPerFrame(false); uint32_t framesPerPacket = 1; uint32_t channelsPerFrame = track->f.channelCount; uint32_t bitsPerChannel = track->f.sampleWidth; if (track->f.compressionType == AF_COMPRESSION_G711_ULAW) { formatID = "ulaw"; formatFlags = 0; bytesPerPacket = channelsPerFrame; bitsPerChannel = 8; } else if (track->f.compressionType == AF_COMPRESSION_G711_ALAW) { formatID = "alaw"; formatFlags = 0; bytesPerPacket = channelsPerFrame; bitsPerChannel = 8; } else if (track->f.compressionType == AF_COMPRESSION_IMA) { formatID = "ima4"; formatFlags = 0; bytesPerPacket = track->f.bytesPerPacket; framesPerPacket = track->f.framesPerPacket; bitsPerChannel = 16; } else if (track->f.compressionType == AF_COMPRESSION_ALAC) { formatID = "alac"; switch (track->f.sampleWidth) { case 16: formatFlags = kALACFormatFlag_16BitSourceData; break; case 20: formatFlags = kALACFormatFlag_20BitSourceData; break; case 24: formatFlags = kALACFormatFlag_24BitSourceData; break; case 32: formatFlags = kALACFormatFlag_32BitSourceData; break; } bytesPerPacket = track->f.bytesPerPacket; framesPerPacket = track->f.framesPerPacket; } if (!writeTag(&desc) || !writeS64(&chunkLength) || !writeDouble(&sampleRate) || !writeTag(&formatID) || !writeU32(&formatFlags) || !writeU32(&bytesPerPacket) || !writeU32(&framesPerPacket) || !writeU32(&channelsPerFrame) || !writeU32(&bitsPerChannel)) return AF_FAIL; return AF_SUCCEED; } status CAFFile::writeData(bool update) { Track *track = getTrack(); if (m_dataOffset == -1) m_dataOffset = m_fh->tell(); else m_fh->seek(m_dataOffset, File::SeekFromBeginning); Tag data("data"); int64_t dataLength = -1; uint32_t editCount = 0; if (update) dataLength = track->data_size + 4; if (!writeTag(&data) || !writeS64(&dataLength) || !writeU32(&editCount)) return AF_FAIL; if (track->fpos_first_frame == 0) track->fpos_first_frame = m_fh->tell(); return AF_SUCCEED; } status CAFFile::writePacketTable() { Track *track = getTrack(); m_fh->seek(track->fpos_after_data, File::SeekFromBeginning); SharedPtr packetTable = track->m_packetTable; if (!packetTable) return AF_SUCCEED; int64_t numPackets = packetTable->numPackets(); int64_t numValidFrames = packetTable->numValidFrames(); int32_t primingFrames = packetTable->primingFrames(); int32_t remainderFrames = packetTable->remainderFrames(); SharedPtr buffer = new Buffer(packetTable->numPackets() * 5); uint8_t *data = static_cast(buffer->data()); size_t position = 0; for (unsigned i=0; inumPackets(); i++) { uint32_t bytesPerPacket = packetTable->bytesPerPacket(i); size_t numBytes = 0; encodeBERInteger(bytesPerPacket, data + position, &numBytes); position += numBytes; } Tag pakt("pakt"); int64_t packetTableLength = 24 + position; if (!writeTag(&pakt) || !writeS64(&packetTableLength) || !writeS64(&numPackets) || !writeS64(&numValidFrames) || !writeS32(&primingFrames) || !writeS32(&remainderFrames) || m_fh->write(buffer->data(), position) != static_cast(position)) { return AF_FAIL; } return AF_SUCCEED; } status CAFFile::writeCookieData() { if (!m_codecData) return AF_SUCCEED; if (m_cookieDataOffset == -1) m_cookieDataOffset = m_fh->tell(); else m_fh->seek(m_cookieDataOffset, File::SeekFromBeginning); Tag kuki("kuki"); int64_t cookieDataLength = m_codecData->size(); if (!writeTag(&kuki) || !writeS64(&cookieDataLength) || m_fh->write(m_codecData->data(), m_codecData->size()) != static_cast(m_codecData->size())) { return AF_FAIL; } return AF_SUCCEED; } void CAFFile::initCompressionParams() { Track *track = getTrack(); if (track->f.compressionType == AF_COMPRESSION_IMA) initIMACompressionParams(); else if (track->f.compressionType == AF_COMPRESSION_ALAC) initALACCompressionParams(); } void CAFFile::initIMACompressionParams() { Track *track = getTrack(); track->f.bytesPerPacket = 34 * track->f.channelCount; track->f.framesPerPacket = 64; AUpvlist pv = AUpvnew(1); AUpvsetparam(pv, 0, _AF_IMA_ADPCM_TYPE); AUpvsetvaltype(pv, 0, AU_PVTYPE_LONG); long l = _AF_IMA_ADPCM_TYPE_QT; AUpvsetval(pv, 0, &l); track->f.compressionParams = pv; } void CAFFile::initALACCompressionParams() { if (m_access == _AF_READ_ACCESS) return; Track *track = getTrack(); track->f.bytesPerPacket = 0; track->f.framesPerPacket = kALACDefaultFramesPerPacket; const unsigned kALACSpecificConfigSize = 24; const unsigned kChannelAtomSize = 12; const unsigned kALACAudioChannelLayoutSize = 12; unsigned codecDataSize = kALACSpecificConfigSize; if (track->f.channelCount > 2) codecDataSize += kChannelAtomSize + kALACAudioChannelLayoutSize; m_codecData = new Buffer(codecDataSize); memset(m_codecData->data(), 0, m_codecData->size()); AUpvlist pv = AUpvnew(2); AUpvsetparam(pv, 0, _AF_CODEC_DATA_SIZE); AUpvsetvaltype(pv, 0, AU_PVTYPE_LONG); long l = codecDataSize; AUpvsetval(pv, 0, &l); AUpvsetparam(pv, 1, _AF_CODEC_DATA); AUpvsetvaltype(pv, 1, AU_PVTYPE_PTR); void *v = m_codecData->data(); AUpvsetval(pv, 1, &v); track->f.compressionParams = pv; track->m_packetTable = new PacketTable(); }