/* * 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 #include #include #include #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(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