Merge changes from topic "opus_encoder_0124"

* changes:
  libstagefright: Add support for muxing Opus files to Ogg format with unified CSD
  codec2: add C2SoftOpusEnc
  C2SoftOpusDec: Add support for decoding single CSD
  OpusHeader: Add support for unified CSD
  libstagefright: Move OpusHeader files to libstagefright_foundation
gugelfrei
Ray Essick 6 years ago committed by Android (Google) Code Review
commit 3fdf34f243

@ -9,3 +9,14 @@ cc_library_shared {
shared_libs: ["libopus"],
}
cc_library_shared {
name: "libcodec2_soft_opusenc",
defaults: [
"libcodec2_soft-defaults",
"libcodec2_soft_sanitize_all-defaults",
],
srcs: ["C2SoftOpusEnc.cpp"],
shared_libs: ["libopus"],
}

@ -19,10 +19,9 @@
#include <log/log.h>
#include <media/stagefright/foundation/MediaDefs.h>
#include <media/stagefright/foundation/OpusHeader.h>
#include <C2PlatformSupport.h>
#include <SimpleC2Interface.h>
#include "C2SoftOpusDec.h"
extern "C" {
@ -188,16 +187,6 @@ static void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
work->workletsProcessed = 1u;
}
static uint16_t ReadLE16(const uint8_t *data, size_t data_size,
uint32_t read_offset) {
if (read_offset + 1 > data_size)
return 0;
uint16_t val;
val = data[read_offset];
val |= data[read_offset + 1] << 8;
return val;
}
static const int kRate = 48000;
// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
@ -216,81 +205,6 @@ static const int kMaxOpusOutputPacketSizeSamples = 960 * 6;
static const int kMaxChannelsWithDefaultLayout = 2;
static const uint8_t kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = { 0, 1 };
// Parses Opus Header. Header spec: http://wiki.xiph.org/OggOpus#ID_Header
static bool ParseOpusHeader(const uint8_t *data, size_t data_size,
OpusHeader* header) {
// Size of the Opus header excluding optional mapping information.
const size_t kOpusHeaderSize = 19;
// Offset to the channel count byte in the Opus header.
const size_t kOpusHeaderChannelsOffset = 9;
// Offset to the pre-skip value in the Opus header.
const size_t kOpusHeaderSkipSamplesOffset = 10;
// Offset to the gain value in the Opus header.
const size_t kOpusHeaderGainOffset = 16;
// Offset to the channel mapping byte in the Opus header.
const 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.
const size_t kOpusHeaderNumStreamsOffset = kOpusHeaderSize;
const size_t kOpusHeaderNumCoupledOffset = kOpusHeaderNumStreamsOffset + 1;
const size_t kOpusHeaderStreamMapOffset = kOpusHeaderNumStreamsOffset + 2;
if (data_size < kOpusHeaderSize) {
ALOGE("Header size is too small.");
return false;
}
header->channels = *(data + kOpusHeaderChannelsOffset);
if (header->channels <= 0 || header->channels > kMaxChannels) {
ALOGE("Invalid Header, wrong 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) {
ALOGE("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) {
ALOGE("Invalid stream map; insufficient data for current channel "
"count: %d", header->channels);
return false;
}
header->num_streams = *(data + kOpusHeaderNumStreamsOffset);
header->num_coupled = *(data + kOpusHeaderNumCoupledOffset);
if (header->num_streams + header->num_coupled != header->channels) {
ALOGE("Inconsistent channel mapping.");
return false;
}
for (int i = 0; i < header->channels; ++i)
header->stream_map[i] = *(data + kOpusHeaderStreamMapOffset + i);
return true;
}
// Convert nanoseconds to number of samples.
static uint64_t ns_to_samples(uint64_t ns, int rate) {
@ -338,7 +252,19 @@ void C2SoftOpusDec::process(
const uint8_t *data = rView.data() + inOffset;
if (mInputBufferCount < 3) {
if (mInputBufferCount == 0) {
if (!ParseOpusHeader(data, inSize, &mHeader)) {
size_t opusHeadSize = inSize;
size_t codecDelayBufSize = 0;
size_t seekPreRollBufSize = 0;
void *opusHeadBuf = (void *)data;
void *codecDelayBuf = NULL;
void *seekPreRollBuf = NULL;
GetOpusHeaderBuffers(data, inSize, &opusHeadBuf,
&opusHeadSize, &codecDelayBuf,
&codecDelayBufSize, &seekPreRollBuf,
&seekPreRollBufSize);
if (!ParseOpusHeader((uint8_t *)opusHeadBuf, opusHeadSize, &mHeader)) {
ALOGE("Encountered error while Parsing Opus Header.");
mSignalledError = true;
work->result = C2_CORRUPTED;
@ -377,6 +303,20 @@ void C2SoftOpusDec::process(
work->result = C2_CORRUPTED;
return;
}
if (codecDelayBuf && codecDelayBufSize == 8) {
uint64_t value;
memcpy(&value, codecDelayBuf, sizeof(uint64_t));
mCodecDelay = ns_to_samples(value, kRate);
mSamplesToDiscard = mCodecDelay;
++mInputBufferCount;
}
if (seekPreRollBuf && seekPreRollBufSize == 8) {
uint64_t value;
memcpy(&value, codecDelayBuf, sizeof(uint64_t));
mSeekPreRoll = ns_to_samples(value, kRate);
++mInputBufferCount;
}
} else {
if (inSize < 8) {
ALOGE("Input sample size is too small.");
@ -392,29 +332,30 @@ void C2SoftOpusDec::process(
}
else {
mSeekPreRoll = samples;
ALOGI("Configuring decoder: %d Hz, %d channels",
kRate, mHeader.channels);
C2StreamSampleRateInfo::output sampleRateInfo(0u, kRate);
C2StreamChannelCountInfo::output channelCountInfo(0u, mHeader.channels);
std::vector<std::unique_ptr<C2SettingResult>> failures;
c2_status_t err = mIntf->config(
{ &sampleRateInfo, &channelCountInfo },
C2_MAY_BLOCK,
&failures);
if (err == OK) {
work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(sampleRateInfo));
work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(channelCountInfo));
} else {
ALOGE("Config Update failed");
mSignalledError = true;
work->result = C2_CORRUPTED;
return;
}
}
}
++mInputBufferCount;
if (mInputBufferCount == 3) {
ALOGI("Configuring decoder: %d Hz, %d channels",
kRate, mHeader.channels);
C2StreamSampleRateInfo::output sampleRateInfo(0u, kRate);
C2StreamChannelCountInfo::output channelCountInfo(0u, mHeader.channels);
std::vector<std::unique_ptr<C2SettingResult>> failures;
c2_status_t err = mIntf->config(
{ &sampleRateInfo, &channelCountInfo },
C2_MAY_BLOCK,
&failures);
if (err == OK) {
work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(sampleRateInfo));
work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(channelCountInfo));
} else {
ALOGE("Config Update failed");
mSignalledError = true;
work->result = C2_CORRUPTED;
return;
}
}
fillEmptyWork(work);
if (eos) {
mSignalledOutputEos = true;

@ -24,16 +24,6 @@ struct OpusMSDecoder;
namespace android {
struct OpusHeader {
int channels;
int skip_samples;
int channel_mapping;
int num_streams;
int num_coupled;
int16_t gain_db;
uint8_t stream_map[8];
};
struct C2SoftOpusDec : public SimpleC2Component {
class IntfImpl;

@ -0,0 +1,638 @@
/*
* Copyright (C) 2019 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 "C2SoftOpusEnc"
#include <utils/Log.h>
#include <C2PlatformSupport.h>
#include <SimpleC2Interface.h>
#include <media/stagefright/foundation/MediaDefs.h>
#include <media/stagefright/foundation/OpusHeader.h>
#include "C2SoftOpusEnc.h"
extern "C" {
#include <opus.h>
#include <opus_multistream.h>
}
#define DEFAULT_FRAME_DURATION_MS 20
namespace android {
constexpr char COMPONENT_NAME[] = "c2.android.opus.encoder";
class C2SoftOpusEnc::IntfImpl : public C2InterfaceHelper {
public:
explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper)
: C2InterfaceHelper(helper) {
setDerivedInstance(this);
addParameter(
DefineParam(mInputFormat, C2_NAME_INPUT_STREAM_FORMAT_SETTING)
.withConstValue(new C2StreamFormatConfig::input(0u, C2FormatAudio))
.build());
addParameter(
DefineParam(mOutputFormat, C2_NAME_OUTPUT_STREAM_FORMAT_SETTING)
.withConstValue(new C2StreamFormatConfig::output(0u, C2FormatCompressed))
.build());
addParameter(
DefineParam(mInputMediaType, C2_NAME_INPUT_PORT_MIME_SETTING)
.withConstValue(AllocSharedString<C2PortMimeConfig::input>(
MEDIA_MIMETYPE_AUDIO_RAW))
.build());
addParameter(
DefineParam(mOutputMediaType, C2_NAME_OUTPUT_PORT_MIME_SETTING)
.withConstValue(AllocSharedString<C2PortMimeConfig::output>(
MEDIA_MIMETYPE_AUDIO_OPUS))
.build());
addParameter(
DefineParam(mSampleRate, C2_NAME_STREAM_SAMPLE_RATE_SETTING)
.withDefault(new C2StreamSampleRateInfo::input(0u, 48000))
.withFields({C2F(mSampleRate, value).oneOf({
8000, 12000, 16000, 24000, 48000})})
.withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps))
.build());
addParameter(
DefineParam(mChannelCount, C2_NAME_STREAM_CHANNEL_COUNT_SETTING)
.withDefault(new C2StreamChannelCountInfo::input(0u, 1))
.withFields({C2F(mChannelCount, value).inRange(1, 8)})
.withSetter((Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps))
.build());
addParameter(
DefineParam(mBitrate, C2_NAME_STREAM_BITRATE_SETTING)
.withDefault(new C2BitrateTuning::output(0u, 128000))
.withFields({C2F(mBitrate, value).inRange(500, 512000)})
.withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps)
.build());
addParameter(
DefineParam(mComplexity, C2_PARAMKEY_COMPLEXITY)
.withDefault(new C2StreamComplexityTuning::output(0u, 10))
.withFields({C2F(mComplexity, value).inRange(1, 10)})
.withSetter(Setter<decltype(*mComplexity)>::NonStrictValueWithNoDeps)
.build());
addParameter(
DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE)
.withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 3840))
.build());
}
uint32_t getSampleRate() const { return mSampleRate->value; }
uint32_t getChannelCount() const { return mChannelCount->value; }
uint32_t getBitrate() const { return mBitrate->value; }
uint32_t getComplexity() const { return mComplexity->value; }
private:
std::shared_ptr<C2StreamFormatConfig::input> mInputFormat;
std::shared_ptr<C2StreamFormatConfig::output> mOutputFormat;
std::shared_ptr<C2PortMimeConfig::input> mInputMediaType;
std::shared_ptr<C2PortMimeConfig::output> mOutputMediaType;
std::shared_ptr<C2StreamSampleRateInfo::input> mSampleRate;
std::shared_ptr<C2StreamChannelCountInfo::input> mChannelCount;
std::shared_ptr<C2BitrateTuning::output> mBitrate;
std::shared_ptr<C2StreamComplexityTuning::output> mComplexity;
std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize;
};
C2SoftOpusEnc::C2SoftOpusEnc(const char* name, c2_node_id_t id,
const std::shared_ptr<IntfImpl>& intfImpl)
: SimpleC2Component(
std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)),
mIntf(intfImpl),
mOutputBlock(nullptr),
mEncoder(nullptr),
mInputBufferPcm16(nullptr),
mOutIndex(0u) {
}
C2SoftOpusEnc::~C2SoftOpusEnc() {
onRelease();
}
c2_status_t C2SoftOpusEnc::onInit() {
return initEncoder();
}
c2_status_t C2SoftOpusEnc::configureEncoder() {
unsigned char mono_mapping[256] = {0};
unsigned char stereo_mapping[256] = {0, 1};
unsigned char surround_mapping[256] = {0, 1, 255};
mSampleRate = mIntf->getSampleRate();
mChannelCount = mIntf->getChannelCount();
uint32_t bitrate = mIntf->getBitrate();
int complexity = mIntf->getComplexity();
mNumSamplesPerFrame = mSampleRate / (1000 / mFrameDurationMs);
mNumPcmBytesPerInputFrame =
mChannelCount * mNumSamplesPerFrame * sizeof(int16_t);
int err = C2_OK;
unsigned char* mapping;
if (mChannelCount < 2) {
mapping = mono_mapping;
} else if (mChannelCount == 2) {
mapping = stereo_mapping;
} else {
mapping = surround_mapping;
}
if (mEncoder != nullptr) {
opus_multistream_encoder_destroy(mEncoder);
}
mEncoder = opus_multistream_encoder_create(mSampleRate, mChannelCount,
1, 1, mapping, OPUS_APPLICATION_AUDIO, &err);
if (err) {
ALOGE("Could not create libopus encoder. Error code: %i", err);
return C2_CORRUPTED;
}
// Complexity
if (opus_multistream_encoder_ctl(
mEncoder, OPUS_SET_COMPLEXITY(complexity)) != OPUS_OK) {
ALOGE("failed to set complexity");
return C2_BAD_VALUE;
}
// DTX
if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_DTX(0) != OPUS_OK)) {
ALOGE("failed to set dtx");
return C2_BAD_VALUE;
}
// Application
if (opus_multistream_encoder_ctl(mEncoder,
OPUS_SET_APPLICATION(OPUS_APPLICATION_AUDIO)) != OPUS_OK) {
ALOGE("failed to set application");
return C2_BAD_VALUE;
}
// Signal type
if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_SIGNAL(OPUS_AUTO)) !=
OPUS_OK) {
ALOGE("failed to set signal");
return C2_BAD_VALUE;
}
// Unconstrained VBR
if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_VBR(0) != OPUS_OK)) {
ALOGE("failed to set vbr type");
return C2_BAD_VALUE;
}
if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_VBR_CONSTRAINT(0) !=
OPUS_OK)) {
ALOGE("failed to set vbr constraint");
return C2_BAD_VALUE;
}
// Bitrate
if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_BITRATE(bitrate)) !=
OPUS_OK) {
ALOGE("failed to set bitrate");
return C2_BAD_VALUE;
}
// Get codecDelay
int32_t lookahead;
if (opus_multistream_encoder_ctl(mEncoder, OPUS_GET_LOOKAHEAD(&lookahead)) !=
OPUS_OK) {
ALOGE("failed to get lookahead");
return C2_BAD_VALUE;
}
mCodecDelay = lookahead * 1000000000ll / mSampleRate;
// Set seek preroll to 80 ms
mSeekPreRoll = 80000000;
return C2_OK;
}
c2_status_t C2SoftOpusEnc::initEncoder() {
mSignalledEos = false;
mSignalledError = false;
mHeaderGenerated = false;
mIsFirstFrame = true;
mEncoderFlushed = false;
mBufferAvailable = false;
mAnchorTimeStamp = 0ull;
mProcessedSamples = 0;
mFilledLen = 0;
mFrameDurationMs = DEFAULT_FRAME_DURATION_MS;
if (!mInputBufferPcm16) {
mInputBufferPcm16 =
(int16_t*)malloc(kFrameSize * kMaxNumChannels * sizeof(int16_t));
}
if (!mInputBufferPcm16) return C2_NO_MEMORY;
/* Default Configurations */
c2_status_t status = configureEncoder();
return status;
}
c2_status_t C2SoftOpusEnc::onStop() {
mSignalledEos = false;
mSignalledError = false;
mIsFirstFrame = true;
mEncoderFlushed = false;
mBufferAvailable = false;
mAnchorTimeStamp = 0ull;
mProcessedSamples = 0u;
mFilledLen = 0;
if (mEncoder) {
int status = opus_multistream_encoder_ctl(mEncoder, OPUS_RESET_STATE);
if (status != OPUS_OK) {
ALOGE("OPUS_RESET_STATE failed status = %s", opus_strerror(status));
mSignalledError = true;
return C2_CORRUPTED;
}
}
if (mOutputBlock) mOutputBlock.reset();
mOutputBlock = nullptr;
return C2_OK;
}
void C2SoftOpusEnc::onReset() {
(void)onStop();
}
void C2SoftOpusEnc::onRelease() {
(void)onStop();
if (mInputBufferPcm16) {
free(mInputBufferPcm16);
mInputBufferPcm16 = nullptr;
}
if (mEncoder) {
opus_multistream_encoder_destroy(mEncoder);
mEncoder = nullptr;
}
}
c2_status_t C2SoftOpusEnc::onFlush_sm() {
return onStop();
}
// Drain the encoder to get last frames (if any)
int C2SoftOpusEnc::drainEncoder(uint8_t* outPtr) {
memset((uint8_t *)mInputBufferPcm16 + mFilledLen, 0,
(mNumPcmBytesPerInputFrame - mFilledLen));
int encodedBytes = opus_multistream_encode(
mEncoder, mInputBufferPcm16, mNumSamplesPerFrame, outPtr, kMaxPayload);
if (encodedBytes > mOutputBlock->capacity()) {
ALOGE("not enough space left to write encoded data, dropping %d bytes",
mBytesEncoded);
// a fatal error would stop the encoding
return -1;
}
ALOGV("encoded %i Opus bytes from %zu PCM bytes", encodedBytes,
mNumPcmBytesPerInputFrame);
mEncoderFlushed = true;
mFilledLen = 0;
return encodedBytes;
}
void C2SoftOpusEnc::process(const std::unique_ptr<C2Work>& work,
const std::shared_ptr<C2BlockPool>& pool) {
// Initialize output work
work->result = C2_OK;
work->workletsProcessed = 1u;
work->worklets.front()->output.flags = work->input.flags;
if (mSignalledError || mSignalledEos) {
work->result = C2_BAD_VALUE;
return;
}
bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
C2ReadView rView = mDummyReadView;
size_t inOffset = 0u;
size_t inSize = 0u;
c2_status_t err = C2_OK;
if (!work->input.buffers.empty()) {
rView =
work->input.buffers[0]->data().linearBlocks().front().map().get();
inSize = rView.capacity();
if (inSize && rView.error()) {
ALOGE("read view map failed %d", rView.error());
work->result = C2_CORRUPTED;
return;
}
}
ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
inSize, (int)work->input.ordinal.timestamp.peeku(),
(int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
if (!mEncoder) {
if (initEncoder() != C2_OK) {
ALOGE("initEncoder failed with status %d", err);
work->result = err;
mSignalledError = true;
return;
}
}
if (mIsFirstFrame) {
mAnchorTimeStamp = work->input.ordinal.timestamp.peekull();
mIsFirstFrame = false;
}
C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE};
err = pool->fetchLinearBlock(kMaxPayload, usage, &mOutputBlock);
if (err != C2_OK) {
ALOGE("fetchLinearBlock for Output failed with status %d", err);
work->result = C2_NO_MEMORY;
return;
}
C2WriteView wView = mOutputBlock->map().get();
if (wView.error()) {
ALOGE("write view map failed %d", wView.error());
work->result = C2_CORRUPTED;
mOutputBlock.reset();
return;
}
size_t inPos = 0;
size_t processSize = 0;
mBytesEncoded = 0;
uint64_t outTimeStamp = 0u;
std::shared_ptr<C2Buffer> buffer;
uint64_t inputIndex = work->input.ordinal.frameIndex.peeku();
const uint8_t* inPtr = rView.data() + inOffset;
class FillWork {
public:
FillWork(uint32_t flags, C2WorkOrdinalStruct ordinal,
const std::shared_ptr<C2Buffer> &buffer)
: mFlags(flags), mOrdinal(ordinal), mBuffer(buffer) {
}
~FillWork() = default;
void operator()(const std::unique_ptr<C2Work>& work) {
work->worklets.front()->output.flags = (C2FrameData::flags_t)mFlags;
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.ordinal = mOrdinal;
work->workletsProcessed = 1u;
work->result = C2_OK;
if (mBuffer) {
work->worklets.front()->output.buffers.push_back(mBuffer);
}
ALOGV("timestamp = %lld, index = %lld, w/%s buffer",
mOrdinal.timestamp.peekll(),
mOrdinal.frameIndex.peekll(),
mBuffer ? "" : "o");
}
private:
const uint32_t mFlags;
const C2WorkOrdinalStruct mOrdinal;
const std::shared_ptr<C2Buffer> mBuffer;
};
C2WorkOrdinalStruct outOrdinal = work->input.ordinal;
if (!mHeaderGenerated) {
uint8_t header[AOPUS_UNIFIED_CSD_MAXSIZE];
memset(header, 0, sizeof(header));
OpusHeader opusHeader;
opusHeader.channels = mChannelCount;
opusHeader.num_streams = mChannelCount;
opusHeader.num_coupled = 0;
opusHeader.channel_mapping = ((mChannelCount > 8) ? 255 : (mChannelCount > 2));
opusHeader.gain_db = 0;
opusHeader.skip_samples = 0;
int headerLen = WriteOpusHeaders(opusHeader, mSampleRate, header,
sizeof(header), mCodecDelay, mSeekPreRoll);
std::unique_ptr<C2StreamCsdInfo::output> csd =
C2StreamCsdInfo::output::AllocUnique(headerLen, 0u);
if (!csd) {
ALOGE("CSD allocation failed");
mSignalledError = true;
work->result = C2_NO_MEMORY;
return;
}
ALOGV("put csd, %d bytes", headerLen);
memcpy(csd->m.value, header, headerLen);
work->worklets.front()->output.configUpdate.push_back(std::move(csd));
mHeaderGenerated = true;
}
/*
* For buffer size which is not a multiple of mNumPcmBytesPerInputFrame, we will
* accumulate the input and keep it. Once the input is filled with expected number
* of bytes, we will send it to encoder. mFilledLen manages the bytes of input yet
* to be processed. The next call will fill mNumPcmBytesPerInputFrame - mFilledLen
* bytes to input and send it to the encoder.
*/
while (inPos < inSize) {
const uint8_t* pcmBytes = inPtr + inPos;
int filledSamples = mFilledLen / sizeof(int16_t);
if ((inPos + (mNumPcmBytesPerInputFrame - mFilledLen)) <= inSize) {
processSize = mNumPcmBytesPerInputFrame - mFilledLen;
mBufferAvailable = true;
} else {
processSize = inSize - inPos;
mBufferAvailable = false;
if (eos) {
memset(mInputBufferPcm16 + filledSamples, 0,
(mNumPcmBytesPerInputFrame - mFilledLen));
mBufferAvailable = true;
}
}
const unsigned nInputSamples = processSize / sizeof(int16_t);
for (unsigned i = 0; i < nInputSamples; i++) {
int32_t data = pcmBytes[2 * i + 1] << 8 | pcmBytes[2 * i];
data = ((data & 0xFFFF) ^ 0x8000) - 0x8000;
mInputBufferPcm16[i + filledSamples] = data;
}
inPos += processSize;
mFilledLen += processSize;
if (!mBufferAvailable) break;
uint8_t* outPtr = wView.data() + mBytesEncoded;
int encodedBytes =
opus_multistream_encode(mEncoder, mInputBufferPcm16,
mNumSamplesPerFrame, outPtr, kMaxPayload);
ALOGV("encoded %i Opus bytes from %zu PCM bytes", encodedBytes,
processSize);
if (encodedBytes < 0 || encodedBytes > kMaxPayload) {
ALOGE("opus_encode failed, encodedBytes : %d", encodedBytes);
mSignalledError = true;
work->result = C2_CORRUPTED;
return;
}
if (buffer) {
outOrdinal.frameIndex = mOutIndex++;
outOrdinal.timestamp = mAnchorTimeStamp + outTimeStamp;
cloneAndSend(
inputIndex, work,
FillWork(C2FrameData::FLAG_INCOMPLETE, outOrdinal, buffer));
buffer.reset();
}
if (encodedBytes > 0) {
buffer =
createLinearBuffer(mOutputBlock, mBytesEncoded, encodedBytes);
}
mBytesEncoded += encodedBytes;
mProcessedSamples += (filledSamples + nInputSamples);
outTimeStamp =
mProcessedSamples * 1000000ll / mChannelCount / mSampleRate;
if ((processSize + mFilledLen) < mNumPcmBytesPerInputFrame)
mEncoderFlushed = true;
mFilledLen = 0;
}
uint32_t flags = 0;
if (eos) {
ALOGV("signalled eos");
mSignalledEos = true;
if (!mEncoderFlushed) {
if (buffer) {
outOrdinal.frameIndex = mOutIndex++;
outOrdinal.timestamp = mAnchorTimeStamp + outTimeStamp;
cloneAndSend(
inputIndex, work,
FillWork(C2FrameData::FLAG_INCOMPLETE, outOrdinal, buffer));
buffer.reset();
}
// drain the encoder for last buffer
drainInternal(pool, work);
}
flags = C2FrameData::FLAG_END_OF_STREAM;
}
if (buffer) {
outOrdinal.frameIndex = mOutIndex++;
outOrdinal.timestamp = mAnchorTimeStamp + outTimeStamp;
FillWork((C2FrameData::flags_t)(flags), outOrdinal, buffer)(work);
buffer.reset();
}
mOutputBlock = nullptr;
}
c2_status_t C2SoftOpusEnc::drainInternal(
const std::shared_ptr<C2BlockPool>& pool,
const std::unique_ptr<C2Work>& work) {
mBytesEncoded = 0;
std::shared_ptr<C2Buffer> buffer = nullptr;
C2WorkOrdinalStruct outOrdinal = work->input.ordinal;
bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE};
c2_status_t err = pool->fetchLinearBlock(kMaxPayload, usage, &mOutputBlock);
if (err != C2_OK) {
ALOGE("fetchLinearBlock for Output failed with status %d", err);
return C2_NO_MEMORY;
}
C2WriteView wView = mOutputBlock->map().get();
if (wView.error()) {
ALOGE("write view map failed %d", wView.error());
mOutputBlock.reset();
return C2_CORRUPTED;
}
int encBytes = drainEncoder(wView.data());
if (encBytes > 0) mBytesEncoded += encBytes;
if (mBytesEncoded > 0) {
buffer = createLinearBuffer(mOutputBlock, 0, mBytesEncoded);
mOutputBlock.reset();
}
mProcessedSamples += (mNumPcmBytesPerInputFrame / sizeof(int16_t));
uint64_t outTimeStamp =
mProcessedSamples * 1000000ll / mChannelCount / mSampleRate;
outOrdinal.frameIndex = mOutIndex++;
outOrdinal.timestamp = mAnchorTimeStamp + outTimeStamp;
work->worklets.front()->output.flags =
(C2FrameData::flags_t)(eos ? C2FrameData::FLAG_END_OF_STREAM : 0);
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.ordinal = outOrdinal;
work->workletsProcessed = 1u;
work->result = C2_OK;
if (buffer) {
work->worklets.front()->output.buffers.push_back(buffer);
}
mOutputBlock = nullptr;
return C2_OK;
}
c2_status_t C2SoftOpusEnc::drain(uint32_t drainMode,
const std::shared_ptr<C2BlockPool>& pool) {
if (drainMode == NO_DRAIN) {
ALOGW("drain with NO_DRAIN: no-op");
return C2_OK;
}
if (drainMode == DRAIN_CHAIN) {
ALOGW("DRAIN_CHAIN not supported");
return C2_OMITTED;
}
mIsFirstFrame = true;
mAnchorTimeStamp = 0ull;
mProcessedSamples = 0u;
return drainInternal(pool, nullptr);
}
class C2SoftOpusEncFactory : public C2ComponentFactory {
public:
C2SoftOpusEncFactory()
: mHelper(std::static_pointer_cast<C2ReflectorHelper>(
GetCodec2PlatformComponentStore()->getParamReflector())) {}
virtual c2_status_t createComponent(
c2_node_id_t id, std::shared_ptr<C2Component>* const component,
std::function<void(C2Component*)> deleter) override {
*component = std::shared_ptr<C2Component>(
new C2SoftOpusEnc(
COMPONENT_NAME, id,
std::make_shared<C2SoftOpusEnc::IntfImpl>(mHelper)),
deleter);
return C2_OK;
}
virtual c2_status_t createInterface(
c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface,
std::function<void(C2ComponentInterface*)> deleter) override {
*interface = std::shared_ptr<C2ComponentInterface>(
new SimpleInterface<C2SoftOpusEnc::IntfImpl>(
COMPONENT_NAME, id,
std::make_shared<C2SoftOpusEnc::IntfImpl>(mHelper)),
deleter);
return C2_OK;
}
virtual ~C2SoftOpusEncFactory() override = default;
private:
std::shared_ptr<C2ReflectorHelper> mHelper;
};
} // namespace android
extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
ALOGV("in %s", __func__);
return new ::android::C2SoftOpusEncFactory();
}
extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
ALOGV("in %s", __func__);
delete factory;
}

@ -0,0 +1,90 @@
/*
* Copyright (C) 2019 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.
*/
#ifndef ANDROID_C2_SOFT_OPUS_ENC_H_
#define ANDROID_C2_SOFT_OPUS_ENC_H_
#include <atomic>
#include <SimpleC2Component.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
struct OpusMSEncoder;
namespace android {
struct C2SoftOpusEnc : public SimpleC2Component {
class IntfImpl;
C2SoftOpusEnc(const char *name, c2_node_id_t id,
const std::shared_ptr<IntfImpl> &intfImpl);
virtual ~C2SoftOpusEnc();
// From SimpleC2Component
c2_status_t onInit() override;
c2_status_t onStop() override;
void onReset() override;
void onRelease() override;
c2_status_t onFlush_sm() override;
void process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) override;
c2_status_t drain(
uint32_t drainMode,
const std::shared_ptr<C2BlockPool> &pool) override;
private:
/* OPUS_FRAMESIZE_20_MS */
const int kFrameSize = 960;
const int kMaxPayload = 4000;
const int kMaxNumChannels = 8;
std::shared_ptr<IntfImpl> mIntf;
std::shared_ptr<C2LinearBlock> mOutputBlock;
OpusMSEncoder* mEncoder;
int16_t* mInputBufferPcm16;
bool mHeaderGenerated;
bool mIsFirstFrame;
bool mEncoderFlushed;
bool mBufferAvailable;
bool mSignalledEos;
bool mSignalledError;
uint32_t mSampleRate;
uint32_t mChannelCount;
uint32_t mFrameDurationMs;
uint64_t mAnchorTimeStamp;
uint64_t mProcessedSamples;
// Codec delay in ns
uint64_t mCodecDelay;
// Seek pre-roll in ns
uint64_t mSeekPreRoll;
int mNumSamplesPerFrame;
int mBytesEncoded;
int32_t mFilledLen;
size_t mNumPcmBytesPerInputFrame;
std::atomic_uint64_t mOutIndex;
c2_status_t initEncoder();
c2_status_t configureEncoder();
int drainEncoder(uint8_t* outPtr);
c2_status_t drainInternal(const std::shared_ptr<C2BlockPool>& pool,
const std::unique_ptr<C2Work>& work);
C2_DO_NOT_COPY(C2SoftOpusEnc);
};
} // namespace android
#endif // ANDROID_C2_SOFT_OPUS_ENC_H_

@ -817,6 +817,7 @@ C2PlatformComponentStore::C2PlatformComponentStore()
emplace("c2.android.mp3.decoder", "libcodec2_soft_mp3dec.so");
emplace("c2.android.vorbis.decoder", "libcodec2_soft_vorbisdec.so");
emplace("c2.android.opus.decoder", "libcodec2_soft_opusdec.so");
emplace("c2.android.opus.encoder", "libcodec2_soft_opusenc.so");
emplace("c2.android.vp8.decoder", "libcodec2_soft_vp8dec.so");
emplace("c2.android.vp9.decoder", "libcodec2_soft_vp9dec.so");
emplace("c2.android.vp8.encoder", "libcodec2_soft_vp8enc.so");

@ -160,7 +160,6 @@ cc_library {
"libstagefright_codecbase",
"libstagefright_foundation",
"libstagefright_omx_utils",
"libstagefright_opus_common",
"libRScpp",
"libhidlallocatorutils",
"libhidlbase",

@ -30,7 +30,7 @@
#include <media/stagefright/MetaData.h>
#include <media/stagefright/OggWriter.h>
#include <media/stagefright/foundation/ADebug.h>
#include "OpusHeader.h"
#include <media/stagefright/foundation/OpusHeader.h>
extern "C" {
#include <ogg/ogg.h>
@ -114,30 +114,17 @@ status_t OggWriter::addSource(const sp<MediaSource>& source) {
}
mSampleRate = sampleRate;
uint32_t type;
const void *header_data;
size_t packet_size;
if (!source->getFormat()->findData(kKeyOpusHeader, &type, &header_data, &packet_size)) {
ALOGE("opus header not found");
return UNKNOWN_ERROR;
}
OpusHeader header;
header.channels = nChannels;
header.num_streams = nChannels;
header.num_coupled = 0;
header.channel_mapping = ((nChannels > 8) ? 255 : (nChannels > 2));
header.gain_db = 0;
header.skip_samples = 0;
// headers are 21-bytes + something driven by channel count
// expect numbers in the low 30's here. WriteOpusHeader() will tell us
// if things are bad.
unsigned char header_data[100];
ogg_packet op;
ogg_page og;
const int packet_size = WriteOpusHeader(header, mSampleRate, (uint8_t*)header_data,
sizeof(header_data));
if (packet_size < 0) {
ALOGE("opus header writing failed");
return UNKNOWN_ERROR;
}
op.packet = header_data;
op.packet = (unsigned char *)header_data;
op.bytes = packet_size;
op.b_o_s = 1;
op.e_o_s = 0;

@ -37,6 +37,7 @@
#include <media/stagefright/foundation/ALookup.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/ByteUtils.h>
#include <media/stagefright/foundation/OpusHeader.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/MediaDefs.h>
#include <media/AudioSystem.h>
@ -1745,12 +1746,34 @@ void convertMessageToMetaData(const sp<AMessage> &msg, sp<MetaData> &meta) {
} else if (mime == MEDIA_MIMETYPE_VIDEO_VP9) {
meta->setData(kKeyVp9CodecPrivate, 0, csd0->data(), csd0->size());
} else if (mime == MEDIA_MIMETYPE_AUDIO_OPUS) {
meta->setData(kKeyOpusHeader, 0, csd0->data(), csd0->size());
size_t opusHeadSize = csd0->size();
size_t codecDelayBufSize = 0;
size_t seekPreRollBufSize = 0;
void *opusHeadBuf = csd0->data();
void *codecDelayBuf = NULL;
void *seekPreRollBuf = NULL;
if (msg->findBuffer("csd-1", &csd1)) {
meta->setData(kKeyOpusCodecDelay, 0, csd1->data(), csd1->size());
codecDelayBufSize = csd1->size();
codecDelayBuf = csd1->data();
}
if (msg->findBuffer("csd-2", &csd2)) {
meta->setData(kKeyOpusSeekPreRoll, 0, csd2->data(), csd2->size());
seekPreRollBufSize = csd2->size();
seekPreRollBuf = csd2->data();
}
/* Extract codec delay and seek pre roll from csd-0,
* if csd-1 and csd-2 are not present */
if (!codecDelayBuf && !seekPreRollBuf) {
GetOpusHeaderBuffers(csd0->data(), csd0->size(), &opusHeadBuf,
&opusHeadSize, &codecDelayBuf,
&codecDelayBufSize, &seekPreRollBuf,
&seekPreRollBufSize);
}
meta->setData(kKeyOpusHeader, 0, opusHeadBuf, opusHeadSize);
if (codecDelayBuf) {
meta->setData(kKeyOpusCodecDelay, 0, codecDelayBuf, codecDelayBufSize);
}
if (seekPreRollBuf) {
meta->setData(kKeyOpusSeekPreRoll, 0, seekPreRollBuf, seekPreRollBufSize);
}
} else if (mime == MEDIA_MIMETYPE_AUDIO_VORBIS) {
meta->setData(kKeyVorbisInfo, 0, csd0->data(), csd0->size());

@ -93,5 +93,12 @@
<Limit name="complexity" range="0-8" default="5" />
<Feature name="bitrate-modes" value="CQ" />
</MediaCodec>
<MediaCodec name="c2.android.opus.encoder" type="audio/opus">
<Limit name="channel-count" max="2" />
<Limit name="sample-rate" ranges="8000,12000,16000,24000,48000" />
<Limit name="bitrate" range="500-512000" />
<Limit name="complexity" range="0-10" default="5" />
<Feature name="bitrate-modes" value="CQ" />
</MediaCodec>
</Encoders>
</Included>

@ -72,6 +72,7 @@ cc_defaults {
"MediaKeys.cpp",
"MetaData.cpp",
"MetaDataBase.cpp",
"OpusHeader.cpp",
"avc_utils.cpp",
"base64.cpp",
"hexdump.cpp",

@ -16,7 +16,7 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "SoftOpus"
#include <algorithm>
#include <cstring>
#include <stdint.h>
@ -43,9 +43,6 @@ constexpr uint8_t kOpusChannelMap[kMaxChannels][kMaxChannels] = {
{0, 6, 1, 2, 3, 4, 5, 7},
};
// Opus always has a 48kHz output rate. This is true for all Opus, not just this
// implementation.
constexpr int kRate = 48000;
// Size of the Opus header excluding optional mapping information.
constexpr size_t kOpusHeaderSize = 19;
// Offset to magic string that starts Opus header.
@ -76,15 +73,12 @@ constexpr size_t kOpusHeaderNumStreamsOffset = 19;
constexpr size_t kOpusHeaderNumCoupledStreamsOffset = 20;
// Offset to the stream to channel mapping in the Opus header.
constexpr size_t kOpusHeaderStreamMapOffset = 21;
// Maximum packet size used in Xiph's opusdec.
constexpr int kMaxOpusOutputPacketSizeSamples = 960 * 6;
// 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;
constexpr uint8_t kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = {0, 1};
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
@ -182,4 +176,88 @@ int WriteOpusHeader(const OpusHeader &header, int input_sample_rate,
}
}
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 = WriteOpusHeader(header, inputSampleRate, output,
outputSize);
if (headerLen < 0) {
ALOGD("WriteOpusHeader failed");
return -1;
}
if (headerLen >= (outputSize - 2 * AOPUS_TOTAL_CSD_SIZE)) {
ALOGD("Buffer not large enough to hold codec delay and seek pre roll");
return -1;
}
uint64_t length = AOPUS_LENGTH;
/*
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 (8 bytes)
Markers supported:
AOPUSDLY - Signals Codec Delay
AOPUSPRL - Signals seek pre roll
Length should be 8.
*/
// 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, AOPUS_CSD_SIZE);
headerLen += AOPUS_CSD_SIZE;
// 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, AOPUS_CSD_SIZE);
headerLen += AOPUS_CSD_SIZE;
return headerLen;
}
void 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 = (void *)data;
*opusHeadSize = data_size;
if (data_size >= AOPUS_UNIFIED_CSD_MINSIZE) {
size_t i = 0;
while (i < data_size - AOPUS_TOTAL_CSD_SIZE) {
uint8_t *csdBuf = (uint8_t *)data + i;
if (!memcmp(csdBuf, AOPUS_CSD_CODEC_DELAY_MARKER, AOPUS_MARKER_SIZE)) {
*opusHeadSize = std::min(*opusHeadSize, i);
*codecDelayBuf = csdBuf + AOPUS_MARKER_SIZE + AOPUS_LENGTH_SIZE;
*codecDelaySize = AOPUS_CSD_SIZE;
i += AOPUS_TOTAL_CSD_SIZE;
} else if (!memcmp(csdBuf, AOPUS_CSD_SEEK_PREROLL_MARKER, AOPUS_MARKER_SIZE)) {
*opusHeadSize = std::min(*opusHeadSize, i);
*seekPreRollBuf = csdBuf + AOPUS_MARKER_SIZE + AOPUS_LENGTH_SIZE;
*seekPreRollSize = AOPUS_CSD_SIZE;
i += AOPUS_TOTAL_CSD_SIZE;
} else {
i++;
}
}
}
}
} // namespace android

@ -24,6 +24,24 @@
namespace android {
/* Constants used for delimiting Opus CSD */
#define AOPUS_CSD_CODEC_DELAY_MARKER "AOPUSDLY"
#define AOPUS_CSD_SEEK_PREROLL_MARKER "AOPUSPRL"
#define AOPUS_CSD_SIZE 8
#define AOPUS_LENGTH 8
#define AOPUS_MARKER_SIZE 8
#define AOPUS_LENGTH_SIZE 8
#define AOPUS_TOTAL_CSD_SIZE \
((AOPUS_MARKER_SIZE) + (AOPUS_LENGTH_SIZE) + (AOPUS_CSD_SIZE))
#define AOPUS_CSD0_MINSIZE 19
#define AOPUS_UNIFIED_CSD_MINSIZE \
((AOPUS_CSD0_MINSIZE) + 2 * (AOPUS_TOTAL_CSD_SIZE))
/* CSD0 at max can be 22 bytes + max number of channels (255) */
#define AOPUS_CSD0_MAXSIZE 277
#define AOPUS_UNIFIED_CSD_MAXSIZE \
((AOPUS_CSD0_MAXSIZE) + 2 * (AOPUS_TOTAL_CSD_SIZE))
struct OpusHeader {
int channels;
int channel_mapping;
@ -36,6 +54,13 @@ struct OpusHeader {
bool ParseOpusHeader(const uint8_t* data, size_t data_size, OpusHeader* header);
int WriteOpusHeader(const OpusHeader &header, int input_sample_rate, uint8_t* output, size_t output_size);
void GetOpusHeaderBuffers(const uint8_t *data, size_t data_size,
void **opusHeadBuf, size_t *opusHeadSize,
void **codecDelayBuf, size_t *codecDelaySize,
void **seekPreRollBuf, size_t *seekPreRollSize);
int WriteOpusHeaders(const OpusHeader &header, int inputSampleRate,
uint8_t* output, size_t outputSize, uint64_t codecDelay,
uint64_t seekPreRoll);
} // namespace android
#endif // OPUS_HEADER_H_

@ -1,21 +0,0 @@
cc_library_shared {
name: "libstagefright_opus_common",
vendor_available: true,
export_include_dirs: ["include"],
srcs: ["OpusHeader.cpp"],
shared_libs: ["liblog"],
cflags: ["-Werror"],
sanitize: {
integer_overflow: true,
cfi: true,
diag: {
integer_overflow: true,
cfi: true,
},
},
}

@ -28,7 +28,6 @@ cc_library_static {
shared_libs: [
"libstagefright_foundation",
"libstagefright_opus_common",
"libutils",
"liblog",
],

@ -24,7 +24,7 @@
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/hexdump.h>
#include <OpusHeader.h>
#include <media/stagefright/foundation/OpusHeader.h>
#include <utils/Errors.h>

@ -38,6 +38,7 @@ cc_library_shared {
"libcodec2_soft_mp3dec",
"libcodec2_soft_vorbisdec",
"libcodec2_soft_opusdec",
"libcodec2_soft_opusenc",
"libcodec2_soft_vp8dec",
"libcodec2_soft_vp9dec",
"libcodec2_soft_av1dec",

Loading…
Cancel
Save