* Copyright (c) 2026 Huawei Device Co., Ltd.
* 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.
*/
#include "AudioVividEncoder.h"
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_avbuffer.h>
#include <multimedia/player_framework/native_audio_vivid.h>
#include <cstring>
AudioVividEncoder::~AudioVividEncoder()
{
Release();
}
int32_t AudioVividEncoder::Create()
{
encoder_ = OH_AudioCodec_CreateByMime(OH_AVCODEC_MIMETYPE_AUDIO_VIVID, true);
if (encoder_ == nullptr) {
AVCODEC_SAMPLE_LOGE("Create AudioVivid encoder failed");
return -1;
}
AVCODEC_SAMPLE_LOGI("Create AudioVivid encoder success");
return 0;
}
int32_t AudioVividEncoder::Config(int32_t sampleRate, int32_t channelCount, int64_t channelLayout, int32_t bitrate)
{
sampleRate_ = sampleRate;
channelCount_ = channelCount;
channelLayout_ = channelLayout;
bitrate_ = bitrate;
int32_t ret = Configure();
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Configure failed");
return ret;
}
ret = SetCallback();
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("SetCallback failed");
return ret;
}
ret = OH_AudioCodec_Prepare(encoder_);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("Prepare failed, ret: %{public}d", ret);
return -1;
}
AVCODEC_SAMPLE_LOGI("Config AudioVivid encoder: sampleRate=%{public}d, channelCount=%{public}d, bitrate=%{public}d",
sampleRate_,
channelCount_,
bitrate_);
return 0;
}
int32_t AudioVividEncoder::Configure()
{
OH_AVFormat *format = OH_AVFormat_Create();
if (format == nullptr) {
AVCODEC_SAMPLE_LOGE("AVFormat create failed");
return -1;
}
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_VIVID_SIGNAL_FORMAT, OH_AUDIO_VIVID_SIGNAL_FORMAT_MIX);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_S24LE);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, sampleRate_);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_AUDIO_SOUNDBED_LAYOUT, channelLayout_);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_AUDIO_SOUNDBED_BITRATE, 128000);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_OBJECT_NUMBER, 2);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_AUDIO_OBJECT_BITRATE, 64000);
int32_t ret = OH_AudioCodec_Configure(encoder_, format);
OH_AVFormat_Destroy(format);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("Configure encoder failed, ret: %{public}d", ret);
return -1;
}
return 0;
}
int32_t AudioVividEncoder::SetCallback()
{
auto onError = [](OH_AVCodec *codec, int32_t errorCode, void *userData) {
AudioVividEncoderContext *context = static_cast<AudioVividEncoderContext *>(userData);
context->errorCode = errorCode;
context->outputCond.notify_all();
AVCODEC_SAMPLE_LOGE("Encoder error: %{public}d", errorCode);
};
auto onFormatChange = [](OH_AVCodec *codec, OH_AVFormat *format, void *userData) {
AVCODEC_SAMPLE_LOGI("Encoder format change");
};
auto onNeedInputBuffer = [](OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
AudioVividEncoderContext *context = static_cast<AudioVividEncoderContext *>(userData);
std::unique_lock<std::mutex> lock(context->inputMutex);
context->inputBufferIndices.push(index);
context->inputBufferQueue.push(buffer);
context->inputCond.notify_all();
};
auto onNewOutputBuffer = [](OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
AudioVividEncoderContext *context = static_cast<AudioVividEncoderContext *>(userData);
AudioVividEncoderOutputData outputData;
OH_AVCodecBufferAttr attr;
OH_AVBuffer_GetBufferAttr(buffer, &attr);
outputData.encodedSize = attr.size;
outputData.presentationTimeUs = attr.pts;
outputData.eos = (attr.flags & AVCODEC_BUFFER_FLAGS_EOS) != 0;
outputData.encodedData = nullptr;
uint8_t *encodedAddr = OH_AVBuffer_GetAddr(buffer);
if (encodedAddr && attr.size > 0) {
outputData.encodedData = new uint8_t[attr.size];
memcpy(outputData.encodedData, encodedAddr, attr.size);
}
{
std::unique_lock<std::mutex> lock(context->outputMutex);
context->outputQueue.push(outputData);
context->outputCond.notify_all();
}
OH_AudioCodec_FreeOutputBuffer(codec, index);
};
int32_t ret = OH_AudioCodec_RegisterCallback(
encoder_, {onError, onFormatChange, onNeedInputBuffer, onNewOutputBuffer}, &context_);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("RegisterCallback failed, ret: %{public}d", ret);
return -1;
}
return 0;
}
int32_t AudioVividEncoder::Start()
{
if (encoder_ == nullptr) {
AVCODEC_SAMPLE_LOGE("Encoder is null");
return -1;
}
int32_t ret = OH_AudioCodec_Start(encoder_);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("Start encoder failed, ret: %{public}d", ret);
return -1;
}
AVCODEC_SAMPLE_LOGI("AudioVivid encoder started");
return 0;
}
void AudioVividEncoder::UpdateMetadata(uint8_t *metadata, int32_t metadataSize)
{
std::lock_guard<std::mutex> lock(metadataMutex_);
if (currentMetadata_) {
delete[] currentMetadata_;
}
if (metadataSize > 10485760) {
return;
}
currentMetadata_ = new uint8_t[metadataSize];
memcpy(currentMetadata_, metadata, metadataSize);
currentMetadataSize_ = metadataSize;
}
void AudioVividEncoder::AttachMetadataToBuffer(OH_AVBuffer *buffer, uint8_t *metadata, int32_t metadataSize)
{
if (metadata && metadataSize > 0) {
OH_AVFormat *meta = OH_AVFormat_Create();
if (meta) {
OH_AVFormat_SetBuffer(meta, OH_MD_KEY_AUDIO_VIVID_METADATA, metadata, metadataSize);
OH_AVBuffer_SetParameter(buffer, meta);
}
OH_AVFormat_Destroy(meta);
}
}
int32_t AudioVividEncoder::PushInputBuffer(
uint8_t *pcmData, int32_t pcmSize, uint8_t *metadata, int32_t metadataSize, int64_t presentationTimeUs)
{
if (encoder_ == nullptr || pcmData == nullptr || pcmSize <= 0) {
AVCODEC_SAMPLE_LOGE("Invalid parameters: encoder=%{public}s, pcmData=%{public}s, pcmSize=%{public}d",
encoder_ ? "valid" : "null",
pcmData ? "valid" : "null",
pcmSize);
return -1;
}
std::unique_lock<std::mutex> lock(context_.inputMutex);
if (context_.inputBufferIndices.empty()) {
context_.inputCond.wait_for(lock, std::chrono::milliseconds(100), [this] {
return !context_.inputBufferIndices.empty() || context_.eos;
});
}
if (context_.inputBufferIndices.empty()) {
AVCODEC_SAMPLE_LOGE("No input buffer available");
return -1;
}
uint32_t index = context_.inputBufferIndices.front();
context_.inputBufferIndices.pop();
OH_AVBuffer *buffer = context_.inputBufferQueue.front();
context_.inputBufferQueue.pop();
lock.unlock();
uint8_t *bufferAddr = OH_AVBuffer_GetAddr(buffer);
int32_t capacity = OH_AVBuffer_GetCapacity(buffer);
if (pcmSize > capacity) {
AVCODEC_SAMPLE_LOGW("PCM size %{public}d exceeds capacity %{public}d, truncating", pcmSize, capacity);
pcmSize = capacity;
}
memcpy(bufferAddr, pcmData, pcmSize);
OH_AVCodecBufferAttr attr = {static_cast<int32_t>(presentationTimeUs), pcmSize, 0, AVCODEC_BUFFER_FLAGS_NONE};
OH_AVBuffer_SetBufferAttr(buffer, &attr);
if (metadata && metadataSize > 0) {
AttachMetadataToBuffer(buffer, metadata, metadataSize);
} else {
AVCODEC_SAMPLE_LOGE("no meta");
}
int32_t ret = OH_AudioCodec_PushInputBuffer(encoder_, index);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("Push input buffer failed, ret: %{public}d", ret);
return -1;
}
return 0;
}
AudioVividEncoderOutputData *AudioVividEncoder::GetOutputBuffer()
{
std::unique_lock<std::mutex> lock(context_.outputMutex);
context_.outputCond.wait_for(lock, std::chrono::milliseconds(100),
[this] { return !context_.outputQueue.empty() || context_.eos; });
if (context_.outputQueue.empty()) {
return nullptr;
}
AudioVividEncoderOutputData *data = new AudioVividEncoderOutputData(context_.outputQueue.front());
return data;
}
void AudioVividEncoder::FreeOutputBuffer()
{
std::unique_lock<std::mutex> lock(context_.outputMutex);
if (!context_.outputQueue.empty()) {
AudioVividEncoderOutputData &data = context_.outputQueue.front();
if (data.encodedData) {
delete[] data.encodedData;
data.encodedData = nullptr;
}
context_.outputQueue.pop();
}
}
int32_t AudioVividEncoder::NotifyEndOfStream()
{
if (encoder_ == nullptr) {
return -1;
}
std::unique_lock<std::mutex> lock(context_.inputMutex);
if (context_.inputBufferIndices.empty()) {
context_.inputCond.wait_for(lock, std::chrono::milliseconds(1000), [this] {
return !context_.inputBufferIndices.empty() || context_.eos;
});
}
if (context_.inputBufferIndices.empty()) {
AVCODEC_SAMPLE_LOGE("No input buffer available for EOS");
context_.eos = true;
context_.outputCond.notify_all();
return -1;
}
uint32_t index = context_.inputBufferIndices.front();
context_.inputBufferIndices.pop();
OH_AVBuffer *buffer = context_.inputBufferQueue.front();
context_.inputBufferQueue.pop();
lock.unlock();
OH_AVCodecBufferAttr attr = {0, 0, 0, AVCODEC_BUFFER_FLAGS_EOS};
OH_AVBuffer_SetBufferAttr(buffer, &attr);
int32_t ret = OH_AudioCodec_PushInputBuffer(encoder_, index);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("Push EOS buffer failed, ret: %{public}d", ret);
return -1;
}
AVCODEC_SAMPLE_LOGI("AudioVivid encoder notified EOS via buffer flag");
return 0;
}
int32_t AudioVividEncoder::Stop()
{
if (encoder_ == nullptr) {
return -1;
}
context_.eos = true;
context_.inputCond.notify_all();
context_.outputCond.notify_all();
int32_t ret = OH_AudioCodec_Stop(encoder_);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("Stop encoder failed, ret: %{public}d", ret);
return -1;
}
context_.inputBufferIndices = std::queue<uint32_t>();
context_.inputBufferQueue = std::queue<OH_AVBuffer *>();
AVCODEC_SAMPLE_LOGI("AudioVivid encoder stopped");
return 0;
}
int32_t AudioVividEncoder::Release()
{
if (encoder_ != nullptr) {
OH_AudioCodec_Destroy(encoder_);
encoder_ = nullptr;
}
{
std::unique_lock<std::mutex> lock(context_.outputMutex);
while (!context_.outputQueue.empty()) {
AudioVividEncoderOutputData &data = context_.outputQueue.front();
if (data.encodedData) {
delete[] data.encodedData;
}
context_.outputQueue.pop();
}
}
if (currentMetadata_) {
delete[] currentMetadata_;
currentMetadata_ = nullptr;
}
currentMetadataSize_ = 0;
context_.eos = false;
context_.errorCode = 0;
return 0;
}