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;
        }
}

cke_263980.png


更多关于HarmonyOS鸿蒙Next中opus音频编码,编码后的数据长度不固定的实战教程也可以访问 https://www.itying.com/category-93-b0.html

6 回复

方便问下楼主你这个opus是否正常播放?

opus输出结果本身buffersize就不是固定的。

更多关于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);

}

方案一二添加后,会出现以下提示报错:

  1. Use of undeclared identifier ‘OH_MD_KEY_AUD_BITRATE_MODE’
  2. Use of undeclared identifier ‘AUDIO_BITRATE_CBR’
  3. 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
  4. 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字节可能是基于理论计算,但实际编码中,静音或简单音频段落会被压缩得更小。

建议检查音频内容是否包含静音段或简单波形,这些会导致输出尺寸显著减小。只要编码后的音频能正常解码且音质无损,这种长度变化是正常的,无需调整代码。

回到顶部