369 lines
14 KiB
C++
369 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
//#define LOG_NDEBUG 0
|
|
#define LOG_TAG "OpusHeader"
|
|
#include <cstring>
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
|
|
#include <log/log.h>
|
|
|
|
#include "OpusHeader.h"
|
|
|
|
namespace android {
|
|
|
|
// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
|
|
// mappings for up to 8 channels. This information is part of the Vorbis I
|
|
// Specification:
|
|
// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
|
|
constexpr int kMaxChannels = 8;
|
|
|
|
constexpr uint8_t kOpusChannelMap[kMaxChannels][kMaxChannels] = {
|
|
{0},
|
|
{0, 1},
|
|
{0, 2, 1},
|
|
{0, 1, 2, 3},
|
|
{0, 4, 1, 2, 3},
|
|
{0, 4, 1, 2, 3, 5},
|
|
{0, 4, 1, 2, 3, 5, 6},
|
|
{0, 6, 1, 2, 3, 4, 5, 7},
|
|
};
|
|
|
|
// Size of the Opus header excluding optional mapping information.
|
|
constexpr size_t kOpusHeaderSize = 19;
|
|
// Offset to magic string that starts Opus header.
|
|
constexpr size_t kOpusHeaderLabelOffset = 0;
|
|
// Offset to Opus version in the Opus header.
|
|
constexpr size_t kOpusHeaderVersionOffset = 8;
|
|
// Offset to the channel count byte in the Opus header.
|
|
constexpr size_t kOpusHeaderChannelsOffset = 9;
|
|
// Offset to the pre-skip value in the Opus header.
|
|
constexpr size_t kOpusHeaderSkipSamplesOffset = 10;
|
|
// Offset to sample rate in the Opus header.
|
|
constexpr size_t kOpusHeaderSampleRateOffset = 12;
|
|
// Offset to the gain value in the Opus header.
|
|
constexpr size_t kOpusHeaderGainOffset = 16;
|
|
// Offset to the channel mapping byte in the Opus header.
|
|
constexpr size_t kOpusHeaderChannelMappingOffset = 18;
|
|
// Opus Header contains a stream map. The mapping values are in the header
|
|
// beyond the always present |kOpusHeaderSize| bytes of data. The mapping
|
|
// data contains stream count, coupling information, and per channel mapping
|
|
// values:
|
|
// - Byte 0: Number of streams.
|
|
// - Byte 1: Number coupled.
|
|
// - Byte 2: Starting at byte 2 are |header->channels| uint8 mapping
|
|
// values.
|
|
// Offset to the number of streams in the Opus header.
|
|
constexpr size_t kOpusHeaderNumStreamsOffset = 19;
|
|
// Offset to the number of streams that are coupled in the Opus header.
|
|
constexpr size_t kOpusHeaderNumCoupledStreamsOffset = 20;
|
|
// Offset to the stream to channel mapping in the Opus header.
|
|
constexpr size_t kOpusHeaderStreamMapOffset = 21;
|
|
|
|
// Default audio output channel layout. Used to initialize |stream_map| in
|
|
// OpusHeader, and passed to opus_multistream_decoder_create() when the header
|
|
// does not contain mapping information. The values are valid only for mono and
|
|
// stereo output: Opus streams with more than 2 channels require a stream map.
|
|
constexpr int kMaxChannelsWithDefaultLayout = 2;
|
|
|
|
static uint16_t ReadLE16(const uint8_t* data, size_t data_size, uint32_t read_offset) {
|
|
// check whether the 2nd byte is within the buffer
|
|
if (read_offset + 1 >= data_size) return 0;
|
|
uint16_t val;
|
|
val = data[read_offset];
|
|
val |= data[read_offset + 1] << 8;
|
|
return val;
|
|
}
|
|
|
|
// Parses Opus Header. Header spec: http://wiki.xiph.org/OggOpus#ID_Header
|
|
bool ParseOpusHeader(const uint8_t* data, size_t data_size, OpusHeader* header) {
|
|
if (data == NULL) {
|
|
return false;
|
|
}
|
|
if (data_size < kOpusHeaderSize) {
|
|
ALOGV("Header size is too small.");
|
|
return false;
|
|
}
|
|
header->channels = data[kOpusHeaderChannelsOffset];
|
|
|
|
if (header->channels < 1 || header->channels > kMaxChannels) {
|
|
ALOGV("Invalid Header, bad channel count: %d", header->channels);
|
|
return false;
|
|
}
|
|
header->skip_samples = ReadLE16(data, data_size, kOpusHeaderSkipSamplesOffset);
|
|
header->gain_db = static_cast<int16_t>(ReadLE16(data, data_size, kOpusHeaderGainOffset));
|
|
header->channel_mapping = data[kOpusHeaderChannelMappingOffset];
|
|
if (!header->channel_mapping) {
|
|
if (header->channels > kMaxChannelsWithDefaultLayout) {
|
|
ALOGV("Invalid Header, missing stream map.");
|
|
return false;
|
|
}
|
|
header->num_streams = 1;
|
|
header->num_coupled = header->channels > 1;
|
|
header->stream_map[0] = 0;
|
|
header->stream_map[1] = 1;
|
|
return true;
|
|
}
|
|
if (data_size < kOpusHeaderStreamMapOffset + header->channels) {
|
|
ALOGV("Invalid stream map; insufficient data for current channel "
|
|
"count: %d",
|
|
header->channels);
|
|
return false;
|
|
}
|
|
header->num_streams = data[kOpusHeaderNumStreamsOffset];
|
|
header->num_coupled = data[kOpusHeaderNumCoupledStreamsOffset];
|
|
if (header->num_coupled > header->num_streams ||
|
|
header->num_streams + header->num_coupled != header->channels) {
|
|
ALOGV("Inconsistent channel mapping, streams: %d coupled: %d channels: %d",
|
|
header->num_streams, header->num_coupled, header->channels);
|
|
return false;
|
|
}
|
|
for (int i = 0; i < header->channels; ++i) {
|
|
uint8_t value = data[kOpusHeaderStreamMapOffset + i];
|
|
if (value != 255 && value >= header->channels) {
|
|
ALOGV("Invalid channel mapping for index %i : %d", i, value);
|
|
return false;
|
|
}
|
|
header->stream_map[i] = value;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int WriteOpusHeader(const OpusHeader &header, int input_sample_rate,
|
|
uint8_t* output, size_t output_size) {
|
|
// See https://wiki.xiph.org/OggOpus#ID_Header.
|
|
if (header.channels < 1 || header.channels > kMaxChannels) {
|
|
ALOGE("Invalid channel count: %d", header.channels);
|
|
return -1;
|
|
}
|
|
const size_t total_size = kOpusHeaderStreamMapOffset + header.channels;
|
|
if (output_size < total_size) {
|
|
ALOGE("Output buffer too small for header.");
|
|
return -1;
|
|
}
|
|
|
|
// ensure entire header is cleared, even though we overwrite much of it below
|
|
memset(output, 0, output_size);
|
|
|
|
// Set magic signature.
|
|
memcpy(output + kOpusHeaderLabelOffset, "OpusHead", 8);
|
|
// Set Opus version.
|
|
output[kOpusHeaderVersionOffset] = 1;
|
|
// Set channel count.
|
|
output[kOpusHeaderChannelsOffset] = (uint8_t)header.channels;
|
|
// Set pre-skip
|
|
memcpy(output + kOpusHeaderSkipSamplesOffset, &header.skip_samples, sizeof(uint16_t));
|
|
// Set original input sample rate in Hz.
|
|
memcpy(output + kOpusHeaderSampleRateOffset, &input_sample_rate, sizeof(uint32_t));
|
|
// Set output gain in dB.
|
|
memcpy(output + kOpusHeaderGainOffset, &header.gain_db, sizeof(uint16_t));
|
|
|
|
if (header.channels > 2) {
|
|
// Set channel mapping
|
|
output[kOpusHeaderChannelMappingOffset] = 1;
|
|
// Assuming no coupled streams. This should actually be
|
|
// channels() - |coupled_streams|.
|
|
output[kOpusHeaderNumStreamsOffset] = header.channels;
|
|
output[kOpusHeaderNumCoupledStreamsOffset] = 0;
|
|
|
|
// Set the actual stream map.
|
|
for (int i = 0; i < header.channels; ++i) {
|
|
output[kOpusHeaderStreamMapOffset + i] = kOpusChannelMap[header.channels - 1][i];
|
|
}
|
|
return kOpusHeaderStreamMapOffset + header.channels + 1;
|
|
} else {
|
|
output[kOpusHeaderChannelMappingOffset] = 0;
|
|
return kOpusHeaderChannelMappingOffset + 1;
|
|
}
|
|
}
|
|
|
|
int WriteOpusHeaders(const OpusHeader &header, int inputSampleRate,
|
|
uint8_t* output, size_t outputSize, uint64_t codecDelay,
|
|
uint64_t seekPreRoll) {
|
|
if (outputSize < AOPUS_UNIFIED_CSD_MINSIZE) {
|
|
ALOGD("Buffer not large enough to hold unified OPUS CSD");
|
|
return -1;
|
|
}
|
|
int headerLen = 0;
|
|
|
|
// Add opus header
|
|
/*
|
|
Following is the CSD syntax for signalling OpusHeader
|
|
(http://wiki.xiph.org/OggOpus#ID_Header)
|
|
|
|
Marker (8 bytes) | Length (8 bytes) | OpusHeader
|
|
|
|
Markers supported:
|
|
AOPUS_CSD_OPUS_HEADER_MARKER - Signals Opus Header
|
|
|
|
Length should be a value within AOPUS_OPUSHEAD_MINSIZE and AOPUS_OPUSHEAD_MAXSIZE.
|
|
*/
|
|
|
|
memcpy(output + headerLen, AOPUS_CSD_OPUS_HEADER_MARKER, AOPUS_MARKER_SIZE);
|
|
headerLen += AOPUS_MARKER_SIZE;
|
|
|
|
// Place holder for opusHeader Size
|
|
headerLen += AOPUS_LENGTH_SIZE;
|
|
|
|
int headerSize = WriteOpusHeader(header, inputSampleRate, output + headerLen,
|
|
outputSize - headerLen);
|
|
if (headerSize < 0) {
|
|
ALOGD("%s: WriteOpusHeader failed", __func__);
|
|
return -1;
|
|
}
|
|
headerLen += headerSize;
|
|
|
|
// Update opus headerSize after AOPUS_CSD_OPUS_HEADER_MARKER
|
|
uint64_t length = headerSize;
|
|
memcpy(output + AOPUS_MARKER_SIZE, &length, AOPUS_LENGTH_SIZE);
|
|
|
|
/*
|
|
Following is the CSD syntax for signalling codec delay and
|
|
seek pre-roll which is to be appended after OpusHeader
|
|
|
|
Marker (8 bytes) | Length (8 bytes) | Samples in ns (8 bytes)
|
|
|
|
Markers supported:
|
|
AOPUS_CSD_CODEC_DELAY_MARKER - codec delay as samples in ns, represented in 8 bytes
|
|
AOPUS_CSD_SEEK_PREROLL_MARKER - preroll adjustment as samples in ns, represented in 8 bytes
|
|
|
|
*/
|
|
length = sizeof(codecDelay);
|
|
if (headerLen > (outputSize - AOPUS_MARKER_SIZE - AOPUS_LENGTH_SIZE - length)) {
|
|
ALOGD("Buffer not large enough to hold codec delay");
|
|
return -1;
|
|
}
|
|
// Add codec delay
|
|
memcpy(output + headerLen, AOPUS_CSD_CODEC_DELAY_MARKER, AOPUS_MARKER_SIZE);
|
|
headerLen += AOPUS_MARKER_SIZE;
|
|
memcpy(output + headerLen, &length, AOPUS_LENGTH_SIZE);
|
|
headerLen += AOPUS_LENGTH_SIZE;
|
|
memcpy(output + headerLen, &codecDelay, length);
|
|
headerLen += length;
|
|
|
|
length = sizeof(seekPreRoll);
|
|
if (headerLen > (outputSize - AOPUS_MARKER_SIZE - AOPUS_LENGTH_SIZE - length)) {
|
|
ALOGD("Buffer not large enough to hold seek pre roll");
|
|
return -1;
|
|
}
|
|
// Add skip pre roll
|
|
memcpy(output + headerLen, AOPUS_CSD_SEEK_PREROLL_MARKER, AOPUS_MARKER_SIZE);
|
|
headerLen += AOPUS_MARKER_SIZE;
|
|
memcpy(output + headerLen, &length, AOPUS_LENGTH_SIZE);
|
|
headerLen += AOPUS_LENGTH_SIZE;
|
|
memcpy(output + headerLen, &seekPreRoll, length);
|
|
headerLen += length;
|
|
|
|
return headerLen;
|
|
}
|
|
|
|
bool IsOpusHeader(const uint8_t *data, size_t data_size) {
|
|
if (data_size < AOPUS_MARKER_SIZE) {
|
|
return false;
|
|
}
|
|
|
|
return !memcmp(data, AOPUS_CSD_OPUS_HEADER_MARKER, AOPUS_MARKER_SIZE);
|
|
}
|
|
|
|
bool GetOpusHeaderBuffers(const uint8_t *data, size_t data_size,
|
|
void **opusHeadBuf, size_t *opusHeadSize,
|
|
void **codecDelayBuf, size_t *codecDelaySize,
|
|
void **seekPreRollBuf, size_t *seekPreRollSize) {
|
|
*codecDelayBuf = NULL;
|
|
*codecDelaySize = 0;
|
|
*seekPreRollBuf = NULL;
|
|
*seekPreRollSize = 0;
|
|
*opusHeadBuf = NULL;
|
|
*opusHeadSize = 0;
|
|
|
|
// AOPUS_MARKER_SIZE is 8 "OpusHead" is of size 8
|
|
if (data_size < 8)
|
|
return false;
|
|
|
|
// Check if the CSD is in legacy format
|
|
if (!memcmp("OpusHead", data, 8)) {
|
|
if (data_size < AOPUS_OPUSHEAD_MINSIZE || data_size > AOPUS_OPUSHEAD_MAXSIZE) {
|
|
ALOGD("Unexpected size for opusHeadSize %zu", data_size);
|
|
return false;
|
|
}
|
|
*opusHeadBuf = (void *)data;
|
|
*opusHeadSize = data_size;
|
|
return true;
|
|
} else if (memcmp(AOPUS_CSD_MARKER_PREFIX, data, AOPUS_CSD_MARKER_PREFIX_SIZE) == 0) {
|
|
if (data_size < AOPUS_UNIFIED_CSD_MINSIZE || data_size > AOPUS_UNIFIED_CSD_MAXSIZE) {
|
|
ALOGD("Unexpected size for unified opus csd %zu", data_size);
|
|
return false;
|
|
}
|
|
size_t i = 0;
|
|
bool found = false;
|
|
while (i <= data_size - AOPUS_MARKER_SIZE - AOPUS_LENGTH_SIZE) {
|
|
uint8_t *csdBuf = (uint8_t *)data + i;
|
|
if (!memcmp(csdBuf, AOPUS_CSD_OPUS_HEADER_MARKER, AOPUS_MARKER_SIZE)) {
|
|
uint64_t value;
|
|
memcpy(&value, csdBuf + AOPUS_MARKER_SIZE, sizeof(value));
|
|
if (value < AOPUS_OPUSHEAD_MINSIZE || value > AOPUS_OPUSHEAD_MAXSIZE) {
|
|
ALOGD("Unexpected size for opusHeadSize %" PRIu64, value);
|
|
return false;
|
|
}
|
|
i += AOPUS_MARKER_SIZE + AOPUS_LENGTH_SIZE + value;
|
|
if (i > data_size) {
|
|
ALOGD("Marker signals a header that is larger than input");
|
|
return false;
|
|
}
|
|
*opusHeadBuf = csdBuf + AOPUS_MARKER_SIZE + AOPUS_LENGTH_SIZE;
|
|
*opusHeadSize = value;
|
|
found = true;
|
|
} else if (!memcmp(csdBuf, AOPUS_CSD_CODEC_DELAY_MARKER, AOPUS_MARKER_SIZE)) {
|
|
uint64_t value;
|
|
memcpy(&value, csdBuf + AOPUS_MARKER_SIZE, sizeof(value));
|
|
if (value != sizeof(uint64_t)) {
|
|
ALOGD("Unexpected size for codecDelay %" PRIu64, value);
|
|
return false;
|
|
}
|
|
i += AOPUS_MARKER_SIZE + AOPUS_LENGTH_SIZE + value;
|
|
if (i > data_size) {
|
|
ALOGD("Marker signals a header that is larger than input");
|
|
return false;
|
|
}
|
|
*codecDelayBuf = csdBuf + AOPUS_MARKER_SIZE + AOPUS_LENGTH_SIZE;
|
|
*codecDelaySize = value;
|
|
} else if (!memcmp(csdBuf, AOPUS_CSD_SEEK_PREROLL_MARKER, AOPUS_MARKER_SIZE)) {
|
|
uint64_t value;
|
|
memcpy(&value, csdBuf + AOPUS_MARKER_SIZE, sizeof(value));
|
|
if (value != sizeof(uint64_t)) {
|
|
ALOGD("Unexpected size for seekPreRollSize %" PRIu64, value);
|
|
return false;
|
|
}
|
|
i += AOPUS_MARKER_SIZE + AOPUS_LENGTH_SIZE + value;
|
|
if (i > data_size) {
|
|
ALOGD("Marker signals a header that is larger than input");
|
|
return false;
|
|
}
|
|
*seekPreRollBuf = csdBuf + AOPUS_MARKER_SIZE + AOPUS_LENGTH_SIZE;
|
|
*seekPreRollSize = value;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
return found;
|
|
} else {
|
|
return false; // it isn't in either format
|
|
}
|
|
}
|
|
|
|
} // namespace android
|