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