* 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 "AudioVividFlvMuxerManager.h"
#include "AudioConfig.h"
#include <multimedia/player_framework/native_avbuffer.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_audio_vivid.h>
#include <cstring>
#include <chrono>
namespace {
using AudioConfig::SAMPLE_RATE;
using AudioConfig::CHANNEL_COUNT;
using AudioConfig::CHANNEL_LAYOUT;
using AudioConfig::BITRATE;
using AudioConfig::FRAME_SIZE;
using AudioConfig::BYTES_PER_INPUT_FRAME;
constexpr int32_t PCM_BIT_DEPTH = 24;
constexpr int32_t MAX_METADATA_SIZE = 10 * 1024 * 1024;
constexpr int32_t US_PER_SECOND = 1000000;
constexpr int32_t MAX_PUSH_RETRY_COUNT = 100;
constexpr int32_t POLL_INTERVAL_MS = 10;
}
AudioVividFlvMuxerManager::AudioVividFlvMuxerManager() {}
AudioVividFlvMuxerManager::~AudioVividFlvMuxerManager()
{
Release();
}
int32_t AudioVividFlvMuxerManager::Initialize(int32_t fd)
{
int32_t ret = pcmReader_.Open("/data/storage/el2/base/haps/entry/files/resfile/2ch_2obj_48k_24bit.pcm",
SAMPLE_RATE, CHANNEL_COUNT, PCM_BIT_DEPTH);
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to open PCM file");
return 1;
}
ret = encoder_.Create();
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to create encoder");
return 2;
}
ret = encoder_.Config(SAMPLE_RATE, CHANNEL_COUNT, CHANNEL_LAYOUT, BITRATE);
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to config encoder");
return 3;
}
muxer_ = OH_AVMuxer_Create(fd, AV_OUTPUT_FORMAT_FLV);
if (muxer_ == nullptr) {
AVCODEC_SAMPLE_LOGE("Failed to create OH_AVMuxer");
return 4;
}
OH_AVFormat *format = OH_AVFormat_CreateAudioFormat(OH_AVCODEC_MIMETYPE_AUDIO_VIVID, SAMPLE_RATE, CHANNEL_COUNT);
if (format == nullptr) {
AVCODEC_SAMPLE_LOGE("Failed to create audio format");
OH_AVMuxer_Destroy(muxer_);
muxer_ = nullptr;
return 5;
}
OH_AVFormat_SetIntValue(format, OH_MD_KEY_BITRATE, BITRATE);
ret = OH_AVMuxer_AddTrack(muxer_, &trackIndex_, format);
OH_AVFormat_Destroy(format);
if (ret != AV_ERR_OK || trackIndex_ < 0) {
AVCODEC_SAMPLE_LOGE("Failed to add audio track, ret=%{public}d", ret);
OH_AVMuxer_Destroy(muxer_);
muxer_ = nullptr;
return 6;
}
ret = OH_AVMuxer_Start(muxer_);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("Failed to start muxer, ret=%{public}d", ret);
OH_AVMuxer_Destroy(muxer_);
muxer_ = nullptr;
return 7;
}
GenerateInitialMetadata();
AVCODEC_SAMPLE_LOGI("AudioVividFlvMuxerManager initialized, trackIndex=%{public}d", trackIndex_);
return 0;
}
int32_t AudioVividFlvMuxerManager::Start()
{
if (isRunning_.load()) {
AVCODEC_SAMPLE_LOGW("Muxing is already running");
return 0;
}
shouldStop_.store(false);
presentationTimeUs_.store(0);
int32_t ret = encoder_.Start();
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to start encoder");
return -1;
}
encoderInputThread_ = std::thread(&AudioVividFlvMuxerManager::EncoderInputThread, this);
encoderOutputThread_ = std::thread(&AudioVividFlvMuxerManager::EncoderOutputThread, this);
isRunning_.store(true);
AVCODEC_SAMPLE_LOGI("AudioVividFlvMuxerManager started");
return 0;
}
int32_t AudioVividFlvMuxerManager::Stop()
{
if (isRunning_.load()) {
shouldStop_.store(true);
encoder_.GetContext()->inputCond.notify_all();
encoder_.GetContext()->outputCond.notify_all();
if (encoderInputThread_.joinable()) {
encoderInputThread_.join();
}
if (encoderOutputThread_.joinable()) {
encoderOutputThread_.join();
}
encoder_.Stop();
isRunning_.store(false);
}
if (muxer_) {
OH_AVMuxer_Stop(muxer_);
OH_AVMuxer_Destroy(muxer_);
muxer_ = nullptr;
}
AVCODEC_SAMPLE_LOGI("AudioVividFlvMuxerManager stopped");
return 0;
}
int32_t AudioVividFlvMuxerManager::Release()
{
Stop();
encoder_.Release();
pcmReader_.Close();
if (currentMetadata_) {
delete[] currentMetadata_;
currentMetadata_ = nullptr;
}
currentMetadataSize_ = 0;
AVCODEC_SAMPLE_LOGI("AudioVividFlvMuxerManager released");
return 0;
}
void AudioVividFlvMuxerManager::GenerateInitialMetadata()
{
vividWrapper_.Initialize();
vividWrapper_.UpdateObjectPosition(0, 0.3f, 0.0f, 0.5f);
vividWrapper_.UpdateObjectPosition(1, -0.3f, 0.0f, 0.5f);
vividWrapper_.UpdateObjectGain(0, 1.0f);
vividWrapper_.UpdateObjectGain(1, 1.0f);
std::vector<uint8_t> metadata = vividWrapper_.GetMetadata(true);
{
std::lock_guard<std::mutex> lock(metadataMutex_);
if (currentMetadata_) {
delete[] currentMetadata_;
}
currentMetadata_ = new uint8_t[metadata.size()];
memcpy(currentMetadata_, metadata.data(), metadata.size());
currentMetadataSize_ = metadata.size();
}
encoder_.UpdateMetadata(currentMetadata_, currentMetadataSize_);
}
void AudioVividFlvMuxerManager::UpdateMetadata(uint8_t *metadata, int32_t metadataSize)
{
std::lock_guard<std::mutex> lock(metadataMutex_);
if (currentMetadata_) {
delete[] currentMetadata_;
}
if (metadataSize > MAX_METADATA_SIZE) {
return;
}
currentMetadata_ = new uint8_t[metadataSize];
memcpy(currentMetadata_, metadata, metadataSize);
currentMetadataSize_ = metadataSize;
encoder_.UpdateMetadata(metadata, metadataSize);
}
void AudioVividFlvMuxerManager::EncoderInputThread()
{
uint8_t *pcmBuffer = new uint8_t[BYTES_PER_INPUT_FRAME];
uint8_t *encoderBuffer = new uint8_t[BYTES_PER_INPUT_FRAME];
while (!shouldStop_.load()) {
int32_t bytesRead = pcmReader_.Read(pcmBuffer, BYTES_PER_INPUT_FRAME);
if (bytesRead <= 0) {
if (pcmReader_.IsEOF()) {
AVCODEC_SAMPLE_LOGI("PCM file read complete, notifying EOS");
encoder_.NotifyEndOfStream();
break;
}
continue;
}
memset(encoderBuffer, 0, BYTES_PER_INPUT_FRAME);
memcpy(encoderBuffer, pcmBuffer, bytesRead);
int64_t pts = presentationTimeUs_.fetch_add(FRAME_SIZE * US_PER_SECOND / SAMPLE_RATE);
uint8_t *metadata = nullptr;
int32_t metadataSize = 0;
{
std::lock_guard<std::mutex> lock(metadataMutex_);
if (currentMetadata_ && currentMetadataSize_ > 0) {
metadata = currentMetadata_;
metadataSize = currentMetadataSize_;
}
}
int32_t retryCount = 0;
while (!shouldStop_.load()) {
int32_t ret = encoder_.PushInputBuffer(encoderBuffer, BYTES_PER_INPUT_FRAME, metadata, metadataSize, pts);
if (ret == 0) {
break;
}
retryCount++;
if (retryCount > MAX_PUSH_RETRY_COUNT) {
AVCODEC_SAMPLE_LOGE("Failed to push input buffer after retries");
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(POLL_INTERVAL_MS));
}
}
delete[] pcmBuffer;
delete[] encoderBuffer;
AVCODEC_SAMPLE_LOGI("EncoderInputThread exited");
}
void AudioVividFlvMuxerManager::WriteEncodedSample(AudioVividEncoderOutputData *outputData)
{
if (outputData->encodedData == nullptr || outputData->encodedSize <= 0) {
return;
}
std::lock_guard<std::mutex> lock(writeMutex_);
OH_AVBuffer *buffer = OH_AVBuffer_Create(outputData->encodedSize);
if (buffer == nullptr) {
return;
}
memcpy(OH_AVBuffer_GetAddr(buffer), outputData->encodedData, outputData->encodedSize);
OH_AVCodecBufferAttr attr;
attr.pts = outputData->presentationTimeUs;
attr.size = outputData->encodedSize;
attr.offset = 0;
attr.flags = AVCODEC_BUFFER_FLAGS_NONE;
OH_AVBuffer_SetBufferAttr(buffer, &attr);
int32_t ret = OH_AVMuxer_WriteSampleBuffer(muxer_, trackIndex_, buffer);
if (ret != AV_ERR_OK) {
AVCODEC_SAMPLE_LOGE("WriteSampleBuffer failed, ret=%{public}d", ret);
}
OH_AVBuffer_Destroy(buffer);
}
void AudioVividFlvMuxerManager::EncoderOutputThread()
{
while (!shouldStop_.load()) {
AudioVividEncoderOutputData *outputData = encoder_.GetOutputBuffer();
if (outputData == nullptr) {
std::this_thread::sleep_for(std::chrono::milliseconds(POLL_INTERVAL_MS));
continue;
}
if (outputData->eos) {
delete outputData;
encoder_.FreeOutputBuffer();
AVCODEC_SAMPLE_LOGI("EncoderOutputThread received EOS");
break;
}
WriteEncodedSample(outputData);
delete outputData;
encoder_.FreeOutputBuffer();
}
while (true) {
AudioVividEncoderOutputData *outputData = encoder_.GetOutputBuffer();
if (outputData == nullptr) {
break;
}
if (outputData->eos) {
delete outputData;
encoder_.FreeOutputBuffer();
break;
}
WriteEncodedSample(outputData);
delete outputData;
encoder_.FreeOutputBuffer();
}
AVCODEC_SAMPLE_LOGI("EncoderOutputThread exited");
}