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
diff --git a/media/codec2/components/opus/Android.bp b/media/codec2/components/opus/Android.bp
index 240cdb9..0ed141b 100644
--- a/media/codec2/components/opus/Android.bp
+++ b/media/codec2/components/opus/Android.bp
@@ -9,3 +9,14 @@
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"],
+}
diff --git a/media/codec2/components/opus/C2SoftOpusDec.cpp b/media/codec2/components/opus/C2SoftOpusDec.cpp
index 2439c3c..3ce1fd6 100644
--- a/media/codec2/components/opus/C2SoftOpusDec.cpp
+++ b/media/codec2/components/opus/C2SoftOpusDec.cpp
@@ -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 @@
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 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 @@
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 @@
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 @@
}
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;
diff --git a/media/codec2/components/opus/C2SoftOpusDec.h b/media/codec2/components/opus/C2SoftOpusDec.h
index 92b7426..b0715ac 100644
--- a/media/codec2/components/opus/C2SoftOpusDec.h
+++ b/media/codec2/components/opus/C2SoftOpusDec.h
@@ -24,16 +24,6 @@
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;
diff --git a/media/codec2/components/opus/C2SoftOpusEnc.cpp b/media/codec2/components/opus/C2SoftOpusEnc.cpp
new file mode 100644
index 0000000..d6ed5ff
--- /dev/null
+++ b/media/codec2/components/opus/C2SoftOpusEnc.cpp
@@ -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;
+}
diff --git a/media/codec2/components/opus/C2SoftOpusEnc.h b/media/codec2/components/opus/C2SoftOpusEnc.h
new file mode 100644
index 0000000..69e5240
--- /dev/null
+++ b/media/codec2/components/opus/C2SoftOpusEnc.h
@@ -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_
diff --git a/media/codec2/vndk/C2Store.cpp b/media/codec2/vndk/C2Store.cpp
index a5dd203..dc7e89c 100644
--- a/media/codec2/vndk/C2Store.cpp
+++ b/media/codec2/vndk/C2Store.cpp
@@ -817,6 +817,7 @@
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");
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index f45cc58..03eef48 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -160,7 +160,6 @@
"libstagefright_codecbase",
"libstagefright_foundation",
"libstagefright_omx_utils",
- "libstagefright_opus_common",
"libRScpp",
"libhidlallocatorutils",
"libhidlbase",
diff --git a/media/libstagefright/OggWriter.cpp b/media/libstagefright/OggWriter.cpp
index ad55c56..5c13983 100644
--- a/media/libstagefright/OggWriter.cpp
+++ b/media/libstagefright/OggWriter.cpp
@@ -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 @@
}
mSampleRate = sampleRate;
-
- 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");
+ 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;
}
- op.packet = header_data;
+
+ ogg_packet op;
+ ogg_page og;
+ op.packet = (unsigned char *)header_data;
op.bytes = packet_size;
op.b_o_s = 1;
op.e_o_s = 0;
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 49e485a..2e7da01 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -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 @@
} 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());
diff --git a/media/libstagefright/data/media_codecs_google_c2_audio.xml b/media/libstagefright/data/media_codecs_google_c2_audio.xml
index 0b554a2..88cd08d 100644
--- a/media/libstagefright/data/media_codecs_google_c2_audio.xml
+++ b/media/libstagefright/data/media_codecs_google_c2_audio.xml
@@ -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>
diff --git a/media/libstagefright/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index dd1d904..533cd72 100644
--- a/media/libstagefright/foundation/Android.bp
+++ b/media/libstagefright/foundation/Android.bp
@@ -72,6 +72,7 @@
"MediaKeys.cpp",
"MetaData.cpp",
"MetaDataBase.cpp",
+ "OpusHeader.cpp",
"avc_utils.cpp",
"base64.cpp",
"hexdump.cpp",
diff --git a/media/libstagefright/opus/OpusHeader.cpp b/media/libstagefright/foundation/OpusHeader.cpp
similarity index 69%
rename from media/libstagefright/opus/OpusHeader.cpp
rename to media/libstagefright/foundation/OpusHeader.cpp
index e4a460c..9faede1 100644
--- a/media/libstagefright/opus/OpusHeader.cpp
+++ b/media/libstagefright/foundation/OpusHeader.cpp
@@ -16,7 +16,7 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "SoftOpus"
-
+#include <algorithm>
#include <cstring>
#include <stdint.h>
@@ -43,9 +43,6 @@
{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 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 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
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/OpusHeader.h b/media/libstagefright/foundation/include/media/stagefright/foundation/OpusHeader.h
new file mode 100644
index 0000000..9bffccb
--- /dev/null
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/OpusHeader.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+/*
+ * The Opus specification is part of IETF RFC 6716:
+ * http://tools.ietf.org/html/rfc6716
+ */
+
+#ifndef OPUS_HEADER_H_
+#define OPUS_HEADER_H_
+
+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;
+ int num_streams;
+ int num_coupled;
+ int16_t gain_db;
+ int skip_samples;
+ uint8_t stream_map[8];
+};
+
+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_
diff --git a/media/libstagefright/opus/Android.bp b/media/libstagefright/opus/Android.bp
deleted file mode 100644
index c5086ec..0000000
--- a/media/libstagefright/opus/Android.bp
+++ /dev/null
@@ -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,
- },
- },
-}
\ No newline at end of file
diff --git a/media/libstagefright/opus/include/OpusHeader.h b/media/libstagefright/opus/include/OpusHeader.h
deleted file mode 100644
index f9f79cd..0000000
--- a/media/libstagefright/opus/include/OpusHeader.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * The Opus specification is part of IETF RFC 6716:
- * http://tools.ietf.org/html/rfc6716
- */
-
-#ifndef OPUS_HEADER_H_
-#define OPUS_HEADER_H_
-
-namespace android {
-
-struct OpusHeader {
- int channels;
- int channel_mapping;
- int num_streams;
- int num_coupled;
- int16_t gain_db;
- int skip_samples;
- uint8_t stream_map[8];
-};
-
-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);
-} // namespace android
-
-#endif // OPUS_HEADER_H_
diff --git a/media/libstagefright/webm/Android.bp b/media/libstagefright/webm/Android.bp
index 1f840b7..64ecc2d 100644
--- a/media/libstagefright/webm/Android.bp
+++ b/media/libstagefright/webm/Android.bp
@@ -28,7 +28,6 @@
shared_libs: [
"libstagefright_foundation",
- "libstagefright_opus_common",
"libutils",
"liblog",
],
diff --git a/media/libstagefright/webm/WebmWriter.cpp b/media/libstagefright/webm/WebmWriter.cpp
index 7b4b23a..b0a303e 100644
--- a/media/libstagefright/webm/WebmWriter.cpp
+++ b/media/libstagefright/webm/WebmWriter.cpp
@@ -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>
diff --git a/services/mediacodec/registrant/Android.bp b/services/mediacodec/registrant/Android.bp
index 8ae3376..80d3630 100644
--- a/services/mediacodec/registrant/Android.bp
+++ b/services/mediacodec/registrant/Android.bp
@@ -38,6 +38,7 @@
"libcodec2_soft_mp3dec",
"libcodec2_soft_vorbisdec",
"libcodec2_soft_opusdec",
+ "libcodec2_soft_opusenc",
"libcodec2_soft_vp8dec",
"libcodec2_soft_vp9dec",
"libcodec2_soft_av1dec",