You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
666 lines
23 KiB
666 lines
23 KiB
4 months ago
|
/*
|
||
|
* 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 {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
constexpr char COMPONENT_NAME[] = "c2.android.opus.encoder";
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
static const int kMaxNumChannelsSupported = 2;
|
||
|
|
||
|
class C2SoftOpusEnc::IntfImpl : public SimpleInterface<void>::BaseParams {
|
||
|
public:
|
||
|
explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper)
|
||
|
: SimpleInterface<void>::BaseParams(
|
||
|
helper,
|
||
|
COMPONENT_NAME,
|
||
|
C2Component::KIND_ENCODER,
|
||
|
C2Component::DOMAIN_AUDIO,
|
||
|
MEDIA_MIMETYPE_AUDIO_OPUS) {
|
||
|
noPrivateBuffers();
|
||
|
noInputReferences();
|
||
|
noOutputReferences();
|
||
|
noInputLatency();
|
||
|
noTimeStretch();
|
||
|
setDerivedInstance(this);
|
||
|
|
||
|
addParameter(
|
||
|
DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES)
|
||
|
.withConstValue(new C2ComponentAttributesSetting(
|
||
|
C2Component::ATTRIB_IS_TEMPORAL))
|
||
|
.build());
|
||
|
|
||
|
addParameter(
|
||
|
DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE)
|
||
|
.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_PARAMKEY_CHANNEL_COUNT)
|
||
|
.withDefault(new C2StreamChannelCountInfo::input(0u, 1))
|
||
|
.withFields({C2F(mChannelCount, value).inRange(1, kMaxNumChannelsSupported)})
|
||
|
.withSetter((Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps))
|
||
|
.build());
|
||
|
|
||
|
addParameter(
|
||
|
DefineParam(mBitrateMode, C2_PARAMKEY_BITRATE_MODE)
|
||
|
.withDefault(new C2StreamBitrateModeTuning::output(
|
||
|
0u, C2Config::BITRATE_VARIABLE))
|
||
|
.withFields({
|
||
|
C2F(mBitrateMode, value).oneOf({
|
||
|
C2Config::BITRATE_CONST,
|
||
|
C2Config::BITRATE_VARIABLE})
|
||
|
})
|
||
|
.withSetter(
|
||
|
Setter<decltype(*mBitrateMode)>::StrictValueWithNoDeps)
|
||
|
.build());
|
||
|
|
||
|
addParameter(
|
||
|
DefineParam(mBitrate, C2_PARAMKEY_BITRATE)
|
||
|
.withDefault(new C2StreamBitrateInfo::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 getBitrateMode() const { return mBitrateMode->value; }
|
||
|
uint32_t getComplexity() const { return mComplexity->value; }
|
||
|
|
||
|
private:
|
||
|
std::shared_ptr<C2StreamSampleRateInfo::input> mSampleRate;
|
||
|
std::shared_ptr<C2StreamChannelCountInfo::input> mChannelCount;
|
||
|
std::shared_ptr<C2StreamBitrateInfo::output> mBitrate;
|
||
|
std::shared_ptr<C2StreamBitrateModeTuning::output> mBitrateMode;
|
||
|
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() {
|
||
|
static const unsigned char mono_mapping[256] = {0};
|
||
|
static const unsigned char stereo_mapping[256] = {0, 1};
|
||
|
mSampleRate = mIntf->getSampleRate();
|
||
|
mChannelCount = mIntf->getChannelCount();
|
||
|
uint32_t bitrate = mIntf->getBitrate();
|
||
|
uint32_t bitrateMode = mIntf->getBitrateMode();
|
||
|
int complexity = mIntf->getComplexity();
|
||
|
mNumSamplesPerFrame = mSampleRate / (1000 / mFrameDurationMs);
|
||
|
mNumPcmBytesPerInputFrame =
|
||
|
mChannelCount * mNumSamplesPerFrame * sizeof(int16_t);
|
||
|
int err = C2_OK;
|
||
|
|
||
|
const unsigned char* mapping;
|
||
|
if (mChannelCount == 1) {
|
||
|
mapping = mono_mapping;
|
||
|
} else if (mChannelCount == 2) {
|
||
|
mapping = stereo_mapping;
|
||
|
} else {
|
||
|
ALOGE("Number of channels (%d) is not supported", mChannelCount);
|
||
|
return C2_BAD_VALUE;
|
||
|
}
|
||
|
|
||
|
if (mEncoder != nullptr) {
|
||
|
opus_multistream_encoder_destroy(mEncoder);
|
||
|
}
|
||
|
|
||
|
mEncoder = opus_multistream_encoder_create(mSampleRate, mChannelCount,
|
||
|
1, mChannelCount - 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;
|
||
|
}
|
||
|
|
||
|
if (bitrateMode == C2Config::BITRATE_VARIABLE) {
|
||
|
// Constrained VBR
|
||
|
if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_VBR(1) != OPUS_OK)) {
|
||
|
ALOGE("failed to set vbr type");
|
||
|
return C2_BAD_VALUE;
|
||
|
}
|
||
|
if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_VBR_CONSTRAINT(1) !=
|
||
|
OPUS_OK)) {
|
||
|
ALOGE("failed to set vbr constraint");
|
||
|
return C2_BAD_VALUE;
|
||
|
}
|
||
|
} else if (bitrateMode == C2Config::BITRATE_CONST) {
|
||
|
if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_VBR(0) != OPUS_OK)) {
|
||
|
ALOGE("failed to set cbr type");
|
||
|
return C2_BAD_VALUE;
|
||
|
}
|
||
|
} else {
|
||
|
ALOGE("unknown bitrate mode");
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// 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 && inSize > 0) {
|
||
|
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));
|
||
|
|
||
|
// Get codecDelay
|
||
|
int32_t lookahead;
|
||
|
if (opus_multistream_encoder_ctl(mEncoder, OPUS_GET_LOOKAHEAD(&lookahead)) !=
|
||
|
OPUS_OK) {
|
||
|
ALOGE("failed to get lookahead");
|
||
|
mSignalledError = true;
|
||
|
work->result = C2_CORRUPTED;
|
||
|
return;
|
||
|
}
|
||
|
mCodecDelay = lookahead * 1000000000ll / mSampleRate;
|
||
|
|
||
|
OpusHeader opusHeader;
|
||
|
memset(&opusHeader, 0, sizeof(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 = lookahead;
|
||
|
int headerLen = WriteOpusHeaders(opusHeader, mSampleRate, header,
|
||
|
sizeof(header), mCodecDelay, mSeekPreRoll);
|
||
|
|
||
|
std::unique_ptr<C2StreamInitDataInfo::output> csd =
|
||
|
C2StreamInitDataInfo::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 - mBytesEncoded);
|
||
|
ALOGV("encoded %i Opus bytes from %zu PCM bytes", encodedBytes,
|
||
|
processSize);
|
||
|
|
||
|
if (encodedBytes < 0 || encodedBytes > (kMaxPayload - mBytesEncoded)) {
|
||
|
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
|
||
|
|
||
|
__attribute__((cfi_canonical_jump_table))
|
||
|
extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
|
||
|
ALOGV("in %s", __func__);
|
||
|
return new ::android::C2SoftOpusEncFactory();
|
||
|
}
|
||
|
|
||
|
__attribute__((cfi_canonical_jump_table))
|
||
|
extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
|
||
|
ALOGV("in %s", __func__);
|
||
|
delete factory;
|
||
|
}
|