HarmonyOS鸿蒙Next中opus音频编码,编码后的数据长度不固定
HarmonyOS鸿蒙Next中opus音频编码,编码后的数据长度不固定 我看我已经设置了固定码率的,并且确实在正常执行,编码前音频长度固定为640,但是编码后的attr.size为8-11左右,理论上应该为40。不知道具体什么原因
以下是主要逻辑代码:
// OH_AVCodecOnNeedInputBuffer回调函数的实现。
static void OnInputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *data, void *userData) {
if (userData == nullptr) {
return;
}
AEncBufferSignal *EnSignal_ = static_cast<AEncBufferSignal *>(userData);
std::unique_lock<std::mutex> lock(EnSignal_->inMutex_);
EnSignal_->inputBufferInfoQueue.emplace(index, data);
EnSignal_->inCond_.notify_all();
}
// OH_AVCodecOnNewOutputBuffer回调函数的实现。
static void OnOutputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *data, void *userData) {
(void)codec;
OH_AVCodecBufferAttr attr = {0};
int32_t ret = OH_AVBuffer_GetBufferAttr(data, &attr);
if (ret != AV_ERR_OK) {
OH_LOG_INFO(LOG_APP, "OH_AVBuffer_GetBufferAttr: %{public}d", ret);
return;
}
unique_lock<mutex> lock(EnSignal_->outMutex_);
OH_LOG_INFO(LOG_APP, "编码后attr.size: %{public}d", attr.size);
EnCodeOutFile_->write(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(data)), attr.size);
if (data == nullptr) {
OH_AudioCodec_FreeOutputBuffer(audioEnc_, index);
return;
}
EnSignal_->outBufferQueue_.push(data);
EnSignal_->outCond_.notify_all();
ret = OH_AudioCodec_FreeOutputBuffer(audioEnc_, index);
if (ret != AV_ERR_OK) {
OH_LOG_INFO(LOG_APP, "OH_AudioCodec_FreeOutputBuffer: %{public}d", ret);
}
}
编码器配置:
napi_value CustomAudioRender::Start(napi_env env, napi_callback_info info) { // 开启编码器
if (isstartEncode) {
return nullptr;
}
size_t argc = 2;
napi_value args[2] = {nullptr};
// napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
// 获取参数
napi_get_cb_info(env, info, &argc, args, NULL, NULL);
if (argc < 2) {
napi_throw_type_error(env, NULL, "没有获取输出地址");
return nullptr;
}
OH_AVCodecCallback cb_ = {&OnError, &OnOutputFormatChanged, &OnInputBufferAvailable, &OnOutputBufferAvailable};
int32_t ret = OH_AudioCodec_RegisterCallback(audioEnc_, cb_, EnSignal_);
if (ret != AV_ERR_OK) {
// 异常处理。
return nullptr;
}
// 设置编码分辨率。
// 配置音频采样率(必须)。
constexpr uint32_t DEFAULT_SAMPLERATE = 16000;
// 配置音频码率(必须)。
constexpr uint32_t DEFAULT_BITRATE = 16000;
// 配置音频声道数(必须)。
constexpr uint32_t DEFAULT_CHANNEL_COUNT = 1;
// 配置音频位深(必须)
constexpr OH_BitsPerSample SAMPLE_FORMAT = OH_BitsPerSample::SAMPLE_S16LE;
OH_AVFormat *format = OH_AVFormat_Create();
// 写入format。
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, DEFAULT_CHANNEL_COUNT);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, DEFAULT_SAMPLERATE);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, DEFAULT_BITRATE);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_FORMAT);
// 配置编码器。
ret = OH_AudioCodec_Configure(audioEnc_, format);
if (ret != AV_ERR_OK) {
OH_LOG_INFO(LOG_APP, "OH_AudioCodec_Configure: %{public}d", ret);
return nullptr;
}
ret = OH_AudioCodec_Prepare(audioEnc_);
if (ret != AV_ERR_OK) {
OH_LOG_INFO(LOG_APP, "OH_AudioCodec_Prepare: %{public}d", ret);
return nullptr;
}
char inputPath[4094] = {0};
size_t inputFilePathLength;
napi_get_value_string_utf8(env, args[0], nullptr, 0, &inputFilePathLength);
size_t copied;
napi_get_value_string_utf8(env, args[0], inputPath, inputFilePathLength + 1, &copied);
char outputPath[4094] = {0};
size_t outputFilePathLength;
napi_get_value_string_utf8(env, args[1], nullptr, 0, &outputFilePathLength);
size_t copied_output;
napi_get_value_string_utf8(env, args[1], outputPath, outputFilePathLength + 1, &copied_output);
std::string inputFilePath(inputPath);
std::string outputFilePath(outputPath);
EnCodeOutFile_->open(outputFilePath.data(), ios::out | ios::binary);
EnCodeInputFile_->open(inputFilePath.data(), ios::out | ios::binary);
// 开始编码。
ret = OH_AudioCodec_Start(audioEnc_);
if (ret != AV_ERR_OK) {
OH_LOG_INFO(LOG_APP, "OH_AudioCodec_Start: %{public}d", ret);
} else {
OH_LOG_INFO(LOG_APP, "1:开始编码success");
ret = OH_AudioCodec_IsValid(audioEnc_, &isValid); // 验证编码器实例有效性
if (ret != AV_ERR_OK) {
OH_LOG_INFO(LOG_APP, "编码器实例无效");
}
}
isstartEncode = true;
return nullptr;
}
编码前获取数据,推送到buffer缓冲区:
bool CustomAudioRender::encode(CustomAudioRender::AudioFrame &audioFrame,napi_env env) {
size_t buffer_size = audioFrame.bytesPerSample * audioFrame.channels * audioFrame.samplesPerChannel;
OH_LOG_INFO(LOG_APP, "Buffer size: %{public}d, bytesPerSample: %{public}d, Channels: %{public}d, samplesPerChannel: %{public}d",
buffer_size, audioFrame.bytesPerSample, audioFrame.channels, audioFrame.samplesPerChannel);
// 创建新的 OH_AVBuffer
OH_AVBuffer *buffer = OH_AVBuffer_Create(buffer_size);
memcpy((void *)OH_AVBuffer_GetAddr(buffer), audioFrame.buffer, buffer_size);
if (buffer == nullptr) {
return false;
}
if (!EnSignal_->inputBufferInfoQueue.empty()) {
std::lock_guard<std::mutex> lock(EnSignal_->inMutex_); // 确保线程安全
CodecBufferInfo &frontInfo = EnSignal_->inputBufferInfoQueue.front();
memcpy((void *)OH_AVBuffer_GetAddr(frontInfo.buffer), (void *)OH_AVBuffer_GetAddr(buffer), buffer_size);
OH_AVCodecBufferAttr attr = {0};
attr.size = buffer_size;
attr.flags = AVCODEC_BUFFER_FLAGS_NONE;
OH_AVBuffer_SetBufferAttr(reinterpret_cast<OH_AVBuffer *>(frontInfo.buffer), &attr);
// OH_LOG_INFO(LOG_APP, "编码前attr.size: %{public}d", attr.size);
EnCodeInputFile_->write(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(frontInfo.buffer)), attr.size);
int32_t ret = OH_AudioCodec_PushInputBuffer(audioEnc_, frontInfo.bufferIndex);
if (ret != AV_ERR_OK) {
OH_LOG_INFO(LOG_APP, "5:推入buffer缓冲区编码失败", ret);
}
EnSignal_->inputBufferInfoQueue.pop();
return false;
}
}
更多关于HarmonyOS鸿蒙Next中opus音频编码,编码后的数据长度不固定的实战教程也可以访问 https://www.itying.com/category-93-b0.html
不能正常播放,但是按照设置的编码参数应该为40左右,而不是8-11这么短吧,
从代码逻辑和配置来看,Opus编码器默认采用**VBR(可变码率)**模式,即使设置了固定码率参数,实际输出数据量仍会根据音频内容动态调整。需显式指定CBR(恒定码率)模式
Opus编码以动态帧大小处理音频数据,即使输入的PCM帧长度固定,编码后的数据帧可能因压缩算法特性导致输出长度波动。需检查编码器是否支持严格帧对齐。
解决方案与代码调整
1/ 在OH_AVFormat中添加码率控制模式参数:
// 在Start函数中添加以下配置
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_BITRATE_MODE, AUDIO_BITRATE_CBR);
2/确认设置的帧长度是否符合Opus规范:
// 在OH_AVFormat中设置帧大小
constexpr uint32_t FRAME_SIZE = 320; // 16kHz采样率下对应20ms帧
OH_AVFormat_SetIntValue(format, OH_MD_KEY_MAX_FRAME_SIZE, FRAME_SIZE);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_REQ_FRAME_SIZE, FRAME_SIZE);
3/在OnOutputBufferAvailable中增加数据校验与缓存机制:
static void OnOutputBufferAvailable(...) {
// 检查编码后的数据有效性
if (attr.size <= 0 || attr.offset != 0) {
OH_LOG_ERROR(LOG_APP, "Invalid buffer attr: size=%d, offset=%d", attr.size, attr.offset);
return;
}
// 按Opus帧格式处理数据
uint8_t *dataAddr = OH_AVBuffer_GetAddr(data);
int validSize = attr.size - 1; // 根据实际封装格式调整
EnCodeOutFile_->write(reinterpret_cast<char *>(dataAddr + 1), validSize);
}
方案一二添加后,会出现以下提示报错:
- Use of undeclared identifier ‘OH_MD_KEY_AUD_BITRATE_MODE’
- Use of undeclared identifier ‘AUDIO_BITRATE_CBR’
- Use of undeclared identifier ‘OH_MD_KEY_MAX_FRAME_SIZE’; did you mean ‘OH_MD_KEY_MAX_INPUT_SIZE’? (fix available) /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/sysroot/usr/include/multimedia/player_framework/native_avcodec_base.h:409:20: note: ‘OH_MD_KEY_MAX_INPUT_SIZE’ declared here
- Use of undeclared identifier ‘OH_MD_KEY_REQ_FRAME_SIZE’; did you mean ‘OH_MD_KEY_FRAME_RATE’? (fix available) /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/sysroot/usr/include/multimedia/player_framework/native_avcodec_base.h:444:20: note: ‘OH_MD_KEY_FRAME_RATE’ declared here
HarmonyOS鸿蒙Next中Opus编码数据长度可变是正常现象。Opus采用动态比特率编码,根据音频内容复杂度自动调整帧大小,导致输出数据包长度不一致。这属于编码器设计特性,不影响解码兼容性。可通过解析Opus帧头获取每帧实际长度进行正确处理。
在HarmonyOS Next中使用Opus编码时,即使设置了固定码率,编码后的数据长度不固定是正常现象。Opus编码器采用可变帧长设计,根据音频内容的复杂度动态调整输出数据大小,以实现更好的压缩效率。
从你的代码来看,配置参数(16k采样率、16kbps码率、单声道)是合理的。编码前输入帧长度固定为640字节(对应PCM数据),但编码后输出长度在8-11字节范围内波动,这符合Opus的编码特性。你预期的40字节可能是基于理论计算,但实际编码中,静音或简单音频段落会被压缩得更小。
建议检查音频内容是否包含静音段或简单波形,这些会导致输出尺寸显著减小。只要编码后的音频能正常解码且音质无损,这种长度变化是正常的,无需调整代码。