Streams in Audio Vivid format can store information such as the positions and gains of audio objects. For the audio source in Audio Vivid format that can provide the experience of changing the position and volume gain of the audio object, the Audio Vivid encoding technology can be used to implement HD spatial audio encoding since API version 26.0.0. The audio herein refers to a sound that is perceived as a whole or a sound that is independent of an ambient sound and that is emitted by a sound source.
For details about the APIs, see AudioCodec API Reference.
The following table lists the specifications supported in Audio Vivid encoding.
| Specification Item | Supported Scope |
|---|---|
| Sampling rate (Hz) | 32000, 44100, 48000 |
| Bit rate range (bit/s) | 32000–832000 |
| Audio channel count | 1–16 |
| Bit depth | SAMPLE_S16LE, SAMPLE_S24LE |
The following table lists the bit rates supported by different audio channel layouts.
| Audio Channel Layout | Supported Bit Rate (bit/s) |
|---|---|
| CH_LAYOUT_MONO | 32000, 44000, 56000, 64000, 72000, 80000, 96000, 128000, 144000, 164000, 192000 |
| CH_LAYOUT_STEREO | 32000, 48000, 64000, 80000, 96000, 128000, 144000, 192000, 256000, 320000 |
| CH_LAYOUT_5POINT1 | 96000, 128000, 144000, 160000, 192000, 256000, 320000, 384000, 448000, 512000, 640000, 720000 |
| CH_LAYOUT_5POINT1POINT2 | 152000, 320000, 480000, 576000 |
| CH_LAYOUT_5POINT1POINT4 | 176000, 256000, 384000, 448000, 576000, 704000 |
| CH_LAYOUT_7POINT1 | 128000, 160000, 192000, 256000, 384000, 480000, 576000, 640000 |
| CH_LAYOUT_7POINT1POINT2 | 216000, 384000, 480000, 576000, 768000 |
| CH_LAYOUT_7POINT1POINT4 | 240000, 384000, 512000, 608000, 832000 |
If the input bit rate is different from that in the table, the input bit rate is automatically adapted to the bit rate in the table. If the bit rate is lower than the minimum bit rate, the minimum bit rate is used.
target_link_libraries(sample PUBLIC libnative_media_codecbase.so libnative_media_core.so libnative_media_acodec.so libnative_media_avdemuxer.so libnative_media_avsource.so )
// Header files. #include <multimedia/player_framework/native_avcodec_audiocodec.h> #include <multimedia/native_audio_channel_layout.h> #include <multimedia/player_framework/native_avcapability.h> #include <multimedia/player_framework/native_avcodec_base.h> #include <multimedia/player_framework/native_avformat.h> #include <multimedia/player_framework/native_avbuffer.h>
class AudioVividEncoder {
public:
AudioVividEncoder() = default;
~AudioVividEncoder();
int32_t Create();
int32_t Config(int32_t sampleRate, int32_t channelCount, int64_t channelLayout, int32_t bitrate);
int32_t Start();
int32_t PushInputBuffer(
uint8_t *pcmData, int32_t pcmSize, uint8_t *metadata, int32_t metadataSize, int64_t presentationTimeUs);
AudioVividEncoderOutputData *GetOutputBuffer();
void FreeOutputBuffer();
int32_t Stop();
int32_t Release();
AudioVividEncoderContext *GetContext()
{
return &context_;
}
void UpdateMetadata(uint8_t *metadata, int32_t metadataSize);
private:
int32_t SetCallback();
int32_t Configure();
void AttachMetadataToBuffer(OH_AVBuffer *buffer, uint8_t *metadata, int32_t metadataSize);
OH_AVCodec *encoder_ = nullptr;
AudioVividEncoderContext context_;
int32_t sampleRate_ = AudioConfig::SAMPLE_RATE;
int32_t channelCount_ = AudioConfig::CHANNEL_COUNT;
int64_t channelLayout_ = AudioConfig::CHANNEL_LAYOUT;
int32_t bitrate_ = AudioConfig::BITRATE;
uint8_t *currentMetadata_ = nullptr;
int32_t currentMetadataSize_ = 0;
std::mutex metadataMutex_;
}; 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_);
// Set the bit rate of the sound bed to 128,000 bit/s. This is an example. Set it according to the actual situation.
OH_AVFormat_SetLongValue(format, OH_MD_KEY_AUDIO_SOUNDBED_BITRATE, 128000);
// Set the number of object formats to 2.
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_OBJECT_NUMBER, 2);
// Set the bit rate of the sound bed to 64,000 bit/s. This is an example. Set it according to the actual situation.
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;
} // In mono mode, the following keys are mandatory.
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_VIVID_SIGNAL_FORMAT, OH_AUDIO_VIVID_SIGNAL_FORMAT_MONO);
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_BITRATE, 128000);
// In stero mode, the following keys are mandatory.
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_VIVID_SIGNAL_FORMAT, OH_AUDIO_VIVID_SIGNAL_FORMAT_STEREO);
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_BITRATE, 128000);
// In mc mode (the number of audio channels is greater than 2 and less than or equal to 16), the following keys are mandatory.
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_VIVID_SIGNAL_FORMAT, OH_AUDIO_VIVID_SIGNAL_FORMAT_MC);
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_CHANNEL_LAYOUT, channelLayout_);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, 128000);
// Mix mode (number of objects > 0 and number of audio channels + number of objects <= 16)
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_VIVID_SIGNAL_FORMAT, OH_AUDIO_VIVID_SIGNAL_FORMAT_MIX); // Mandatory
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_S24LE); // Mandatory
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, sampleRate_); // Mandatory
OH_AVFormat_SetLongValue(format, OH_MD_KEY_AUDIO_SOUNDBED_LAYOUT, channelLayout_); // Optional. Set it when there is a sound bed.
OH_AVFormat_SetLongValue(format, OH_MD_KEY_AUDIO_SOUNDBED_BITRATE, 128000); // Optional. Set it when there is a sound bed.
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_OBJECT_NUMBER, 2); // Optional. Set it when there is an object.
OH_AVFormat_SetLongValue(format, OH_MD_KEY_AUDIO_OBJECT_BITRATE, 64000); // Optional. Set it when there is an object. 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) { // 10485760: 10 MB input size that is too large.
return;
}
currentMetadata_ = new uint8_t[metadataSize];
memcpy(currentMetadata_, metadata, metadataSize);
currentMetadataSize_ = metadataSize;
} 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] { // Wait for 100 ms to input.
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;
}
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);
}
} AudioVividEncoderOutputData *AudioVividEncoder::GetOutputBuffer()
{
std::unique_lock<std::mutex> lock(context_.outputMutex);
// Wait for 100 ms.
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::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_Flush(encoder_);
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;
} Create an Audio Vivid encoder.
// Create an encoder.
int32_t ret = 0;
AudioVividEncoder encoder_;
ret = encoder_.Create();
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to create encoder");
return -1;
}
ret = encoder_.Config(SAMPLE_RATE, PCM_CHANNEL_COUNT, CHANNEL_LAYOUT, BITRATE);
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to config encoder");
return -1;
}
ret = encoder_.Start();
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to start encoder");
return -1;
}
// Create the input and output threads for encoding.
encoderInputThread_ = std::thread(&AudioVividPlaybackManager::EncoderInputThread, this);
encoderOutputThread_ = std::thread(&AudioVividPlaybackManager::EncoderOutputThread, this); Set the Audio Vivid encoding parameters.
ret = encoder_.Config(SAMPLE_RATE, PCM_CHANNEL_COUNT, CHANNEL_LAYOUT, BITRATE);
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to config encoder");
return -1;
} Start the Audio vivid encoding.
ret = encoder_.Start();
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to start encoder");
return -1;
} Create an input thread for Audio Vivid encoding.
// Create the input and output threads for encoding.
encoderInputThread_ = std::thread(&AudioVividPlaybackManager::EncoderInputThread, this); Implementation of the encoding input process: This thread reads PCM data from the file, saves the data to the pcmBuffer, and then sends the data to the Audio Vivid encoder. In actual use, the data written to the pcmBuffer can be changed to the required data.
void AudioVividPlaybackManager::EncoderInputThread()
{
uint8_t *pcmBuffer = new uint8_t[BYTES_PER_PCM_FRAME];
uint8_t *encoderBuffer = new uint8_t[BYTES_PER_FRAME];
while (!shouldStop_.load()) {
size_t queueSize = 0;
{
std::lock_guard<std::mutex> lock(decodedQueueMutex_);
queueSize = decodedDataQueue_.size();
}
if (queueSize >= MAX_DECODED_QUEUE_SIZE) {
std::this_thread::sleep_for(std::chrono::milliseconds(20)); // Wait for 20 ms before reading data. A large number of buffers are not consumed.
continue;
}
int32_t bytesRead = pcmReader_.Read(pcmBuffer, BYTES_PER_PCM_FRAME);
if (bytesRead <= 0) {
if (pcmReader_.IsEOF()) {
pcmReader_.Reset();
AVCODEC_SAMPLE_LOGI("PCM file looped, restarting from beginning");
}
continue;
}
memset(encoderBuffer, 0, BYTES_PER_FRAME);
memcpy(encoderBuffer, pcmBuffer, bytesRead);
int64_t pts = presentationTimeUs_.fetch_add(FRAME_SIZE * 1000000 / SAMPLE_RATE); // 1000000: 1s is converted to μs.
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 ret = encoder_.PushInputBuffer(encoderBuffer, BYTES_PER_FRAME, metadata, metadataSize, pts);
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to push input buffer to encoder");
}
}
delete[] pcmBuffer;
delete[] encoderBuffer;
AVCODEC_SAMPLE_LOGI("EncoderInputThread exited");
} Create an output thread for Audio Vivid encoding.
encoderOutputThread_ = std::thread(&AudioVividPlaybackManager::EncoderOutputThread, this);
Implementation of the encoding output thread: When the Audio Vivid has encoding output, this thread obtains the encoding output data and stores it in outputData.
void AudioVividPlaybackManager::EncoderOutputThread()
{
while (!shouldStop_.load()) {
AudioVividEncoderOutputData *outputData = encoder_.GetOutputBuffer();
if (outputData == nullptr) {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Wait for 10 ms to input.
continue;
}
if (outputData->eos) {
delete outputData;
break;
}
int32_t ret = decoder_.PushInputBuffer(outputData->encodedData, outputData->encodedSize);
if (ret != 0) {
AVCODEC_SAMPLE_LOGE("Failed to push input buffer to decoder");
}
delete outputData;
encoder_.FreeOutputBuffer();
}
AVCODEC_SAMPLE_LOGI("EncoderOutputThread exited");
} Stop the encoder, and release the encoder instance.
encoder_.GetContext()->inputCond.notify_all();
encoder_.GetContext()->outputCond.notify_all();
if (encoderInputThread_.joinable()) {
encoderInputThread_.join();
}
if (encoderOutputThread_.joinable()) {
encoderOutputThread_.join();
}
encoder_.Stop();