michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/Endian.h" michael@0: #include michael@0: michael@0: #include "OpusParser.h" michael@0: michael@0: #include "nsDebug.h" michael@0: #include "MediaDecoderReader.h" michael@0: #include "VideoUtils.h" michael@0: #include michael@0: michael@0: #include "opus/opus.h" michael@0: extern "C" { michael@0: #include "opus/opus_multistream.h" michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gMediaDecoderLog; michael@0: #define OPUS_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) michael@0: #else michael@0: #define OPUS_LOG(type, msg) michael@0: #endif michael@0: michael@0: OpusParser::OpusParser(): michael@0: mRate(0), michael@0: mNominalRate(0), michael@0: mChannels(0), michael@0: mPreSkip(0), michael@0: #ifdef MOZ_SAMPLE_TYPE_FLOAT32 michael@0: mGain(1.0f), michael@0: #else michael@0: mGain_Q16(65536), michael@0: #endif michael@0: mChannelMapping(0), michael@0: mStreams(0), michael@0: mCoupledStreams(0) michael@0: { } michael@0: michael@0: bool OpusParser::DecodeHeader(unsigned char* aData, size_t aLength) michael@0: { michael@0: if (aLength < 19 || memcmp(aData, "OpusHead", 8)) { michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: unrecognized header")); michael@0: return false; michael@0: } michael@0: michael@0: mRate = 48000; // The Opus decoder runs at 48 kHz regardless. michael@0: michael@0: int version = aData[8]; michael@0: // Accept file format versions 0.x. michael@0: if ((version & 0xf0) != 0) { michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Rejecting unknown Opus file version %d", version)); michael@0: return false; michael@0: } michael@0: michael@0: mChannels = aData[9]; michael@0: if (mChannels<1) { michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: Number of channels %d", mChannels)); michael@0: return false; michael@0: } michael@0: michael@0: mPreSkip = LittleEndian::readUint16(aData + 10); michael@0: mNominalRate = LittleEndian::readUint32(aData + 12); michael@0: double gain_dB = LittleEndian::readInt16(aData + 16) / 256.0; michael@0: #ifdef MOZ_SAMPLE_TYPE_FLOAT32 michael@0: mGain = static_cast(pow(10,0.05*gain_dB)); michael@0: #else michael@0: mGain_Q16 = static_cast(std::min(65536*pow(10,0.05*gain_dB)+0.5, michael@0: static_cast(INT32_MAX))); michael@0: #endif michael@0: mChannelMapping = aData[18]; michael@0: michael@0: if (mChannelMapping == 0) { michael@0: // Mapping family 0 only allows two channels michael@0: if (mChannels>2) { michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: too many channels (%d) for" michael@0: " mapping family 0.", mChannels)); michael@0: return false; michael@0: } michael@0: mStreams = 1; michael@0: mCoupledStreams = mChannels - 1; michael@0: mMappingTable[0] = 0; michael@0: mMappingTable[1] = 1; michael@0: } else if (mChannelMapping == 1) { michael@0: // Currently only up to 8 channels are defined for mapping family 1 michael@0: if (mChannels>8) { michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: too many channels (%d) for" michael@0: " mapping family 1.", mChannels)); michael@0: return false; michael@0: } michael@0: if (aLength>static_cast(20+mChannels)) { michael@0: mStreams = aData[19]; michael@0: mCoupledStreams = aData[20]; michael@0: int i; michael@0: for (i=0; i mStreams) { michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: more coupled streams (%d) than " michael@0: "total streams (%d)", mCoupledStreams, mStreams)); michael@0: return false; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Opus stream header:")); michael@0: OPUS_LOG(PR_LOG_DEBUG, (" channels: %d", mChannels)); michael@0: OPUS_LOG(PR_LOG_DEBUG, (" preskip: %d", mPreSkip)); michael@0: OPUS_LOG(PR_LOG_DEBUG, (" original: %d Hz", mNominalRate)); michael@0: OPUS_LOG(PR_LOG_DEBUG, (" gain: %.2f dB", gain_dB)); michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Channel Mapping:")); michael@0: OPUS_LOG(PR_LOG_DEBUG, (" family: %d", mChannelMapping)); michael@0: OPUS_LOG(PR_LOG_DEBUG, (" streams: %d", mStreams)); michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: bool OpusParser::DecodeTags(unsigned char* aData, size_t aLength) michael@0: { michael@0: if (aLength < 16 || memcmp(aData, "OpusTags", 8)) michael@0: return false; michael@0: michael@0: // Copy out the raw comment lines, but only do basic validation michael@0: // checks against the string packing: too little data, too many michael@0: // comments, or comments that are too long. Rejecting these cases michael@0: // helps reduce the propagation of broken files. michael@0: // We do not ensure they are valid UTF-8 here, nor do we validate michael@0: // the required ASCII_TAG=value format of the user comments. michael@0: const unsigned char* buf = aData + 8; michael@0: uint32_t bytes = aLength - 8; michael@0: uint32_t len; michael@0: // Read the vendor string. michael@0: len = LittleEndian::readUint32(buf); michael@0: buf += 4; michael@0: bytes -= 4; michael@0: if (len > bytes) michael@0: return false; michael@0: mVendorString = nsCString(reinterpret_cast(buf), len); michael@0: buf += len; michael@0: bytes -= len; michael@0: // Read the user comments. michael@0: if (bytes < 4) michael@0: return false; michael@0: uint32_t ncomments = LittleEndian::readUint32(buf); michael@0: buf += 4; michael@0: bytes -= 4; michael@0: // If there are so many comments even their length fields michael@0: // won't fit in the packet, stop reading now. michael@0: if (ncomments > (bytes>>2)) michael@0: return false; michael@0: uint32_t i; michael@0: for (i = 0; i < ncomments; i++) { michael@0: if (bytes < 4) michael@0: return false; michael@0: len = LittleEndian::readUint32(buf); michael@0: buf += 4; michael@0: bytes -= 4; michael@0: if (len > bytes) michael@0: return false; michael@0: mTags.AppendElement(nsCString(reinterpret_cast(buf), len)); michael@0: buf += len; michael@0: bytes -= len; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: OPUS_LOG(PR_LOG_DEBUG, ("Opus metadata header:")); michael@0: OPUS_LOG(PR_LOG_DEBUG, (" vendor: %s", mVendorString.get())); michael@0: for (uint32_t i = 0; i < mTags.Length(); i++) { michael@0: OPUS_LOG(PR_LOG_DEBUG, (" %s", mTags[i].get())); michael@0: } michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: } // namespace mozilla michael@0: