You need to enable JavaScript to run this app.
导航

【C】实时音量均衡

最近更新时间2023.03.16 11:40:14

首次发布时间2023.03.16 11:40:14

使用流程

本功能是在单线程中使用,内部采用同步处理的策略即一帧输入处理后会返回一帧输出,不支持多线程调用

完整例子
#include "me_audio_encoder.h"
#include "sami_core.h"
#include "demo_helper.h"
#include <iostream>
#include <string>
#include <cfloat>
#include <chrono>

int main(int argc, char* argv[]){
    if(argc < 3) {
        std::cerr << "Usage: " << argv[0] << " input.wav output.wav\n";
        return -1;
    }
    const std::string input = argv[1];
    const std::string output = argv[2];

    std::shared_ptr<FileSource> input_src = FileSource::create(input);
    if(!input_src) {
        std::cerr << "cannot open " << input << std::endl;
        return -1;
    }
    int total_samples = input_src->getNumFrames();
    int num_channels = input_src->getNumChannel();
    int sample_rate = input_src->getSampleRate();
    int num_samples = sample_rate / 100;

    auto input_wav_data = loadWholeAudioFile(input_src);
    double signal_duration = static_cast<double>(total_samples) / static_cast<double>(sample_rate) * 1000;

    AudioEncoderSettings setting;
    setting.format = mammon::AudioEncoderFormat::kWav_F32;
    std::unique_ptr<AudioEncoder> audio_encoder = nullptr;
    AudioEncoderStatus status;
    std::tie(audio_encoder, status) = AudioEncoder::create(setting);
    if(audio_encoder == nullptr || status != mammon::AudioEncoderStatus::kOK) {
        std::cerr << "open output file failed\n";
        return -1;
    }
    int ret = audio_encoder->open(output, sample_rate, num_channels, 64000);
    if(ret != 0){
        std::abort();
    }
//  create handle
    SAMICoreAudioCommonParameter parameter;
    parameter.sampleRate = sample_rate;
    parameter.channels = num_channels;
    SAMICoreHandle handle;
    ret = SAMICoreCreateHandleByIdentify(&handle, SAMICoreIdentify_Loudnorm2, &parameter);
    if(ret != 0){
        std::abort();
    }

//  set property
    SAMICoreLoudnorm2Property loudnormProperty;
    loudnormProperty.target_lufs = -16;

    SAMICoreProperty coreProperty;
    coreProperty.id = SAMICorePropertyID_Processor_Loudnorm2;
    coreProperty.data = &loudnormProperty;
    coreProperty.writable = 0;
    coreProperty.dataLen = 1;
    coreProperty.type = SAMICoreDataType_LoudNorm2;
    ret = SAMICoreSetProperty(handle, SAMICorePropertyID_Processor_Loudnorm2, &coreProperty);
    if(ret != 0){
        std::abort();
    }

    SAMICoreAudioBuffer in_buffer;
    in_buffer.data = new float*[num_channels];
    in_buffer.isInterleave = 0;
    in_buffer.numberChannels = num_channels;
    for(int i = 0; i < num_channels; i++) { in_buffer.data[i] = new float[num_samples]; }

    SAMICoreAudioBuffer out_buffer;
    out_buffer.data = new float*[num_channels];
    out_buffer.isInterleave = 0;
    out_buffer.numberChannels = num_channels;
    for(int i = 0; i < num_channels; i++) { out_buffer.data[i] = new float[num_samples]; }


    SAMICoreBlock in_block;
    SAMICoreBlock out_block;
    in_block.numberAudioData = 1;
    in_block.dataType = SAMICoreDataType_AudioBuffer;
    in_block.audioData = &in_buffer;

    out_block.numberAudioData = 1;
    out_block.dataType = SAMICoreDataType_AudioBuffer;
    out_block.audioData = &out_buffer;
    auto start_time = std::chrono::steady_clock::now();
    int inx = 0;
    while(inx < total_samples - num_samples) {
        int actual_block_size = getNextBlockSize(inx, total_samples, num_samples);
        in_buffer.numberSamples = actual_block_size;
        out_buffer.numberSamples = actual_block_size;
        for(int i = 0; i < num_channels; i++) {
            memcpy(in_buffer.data[i], input_wav_data[i].data() + inx, actual_block_size * sizeof(float));
        }
        ret = SAMICoreProcess(handle, &in_block, &out_block);

        auto* outBuffer = (SAMICoreAudioBuffer*)out_block.audioData;
        if(ret != SAMI_OK  || outBuffer == nullptr) {
            std::cerr << "process error: " << ret;
            exit(-1);
        }
        // write output block to file
        audio_encoder->writePlanarData(outBuffer->data, outBuffer->numberChannels, actual_block_size);
        inx += actual_block_size;
    }
    auto end_time = std::chrono::steady_clock::now();
    auto cost_time = std::chrono::duration<double, std::milli>(end_time - start_time).count();
    if(signal_duration >= DBL_EPSILON) {
        double ratio = cost_time / signal_duration;
        std::cout << "Cast time " << cost_time << " ms "
                  << "signal duration " << signal_duration << " ms "
                  << " ratio " << ratio
                  << std::endl;
    }

    for(int i = 0; i < num_channels; i++) {
        delete[] in_buffer.data[i];
        delete[] out_buffer.data[i];
        in_buffer.data[i] = nullptr;
        out_buffer.data[i] = nullptr;
    }

    audio_encoder->close();
    SAMICoreDestroyHandle(handle);
    delete[] in_buffer.data;
    delete[] out_buffer.data;
    handle = nullptr;
    return 0;

}
接入步骤

一、创建算法句柄

传入采样率、声道数和 maxBlockSize,通过 SAMICoreCreateHandleByIdentify 创建 handle。

//  create handle
    SAMICoreAudioCommonParameter parameter;
    parameter.sampleRate = sample_rate;
    parameter.channels = num_channels;
    SAMICoreHandle handle;
    ret = SAMICoreCreateHandleByIdentify(&handle, SAMICoreIdentify_Loudnorm2, &parameter);
    assert(ret == SAMI_OK);

二、创建 SAMICoreBlock 用于存放输入和输出

SAMICoreAudioBuffer,用于存放音频数据,更多关于音频数据格式请参看【SAMI Core】名词解释 部分。SAMICoreBlock,用于存放需要处理的数据。

三、设置参数

通过 SAMICoreSetProperty 设置target_lufs参数。

SAMICoreLoudnorm2Property loudnormProperty;
loudnormProperty.target_lufs = -16;

SAMICoreProperty coreProperty;
coreProperty.id = SAMICorePropertyID_Processor_Loudnorm2;
coreProperty.data = &loudnormProperty;
coreProperty.writable = 0;
coreProperty.dataLen = 1;
coreProperty.type = SAMICoreDataType_LoudNorm;
ret = SAMICoreSetProperty(handle, SAMICorePropertyID_Processor_Loudnorm2, &coreProperty);
assert(ret == (SAMI_OK));

四、处理音频

准备内存空间

由于此算法输入和输出的大小是对等的,所以为了提高效率输入和输出的内存空间都由用户提前分配好

SAMICoreAudioBuffer in_buffer;
in_buffer.data = new float*[num_channels];
in_buffer.isInterleave = 0;
in_buffer.numberChannels = num_channels;
for(int i = 0; i < num_channels; i++) { in_buffer.data[i] = new float[num_samples]; }

SAMICoreAudioBuffer out_buffer;
out_buffer.data = new float*[num_channels];
out_buffer.isInterleave = 0;
out_buffer.numberChannels = num_channels;
for(int i = 0; i < num_channels; i++) { out_buffer.data[i] = new float[num_samples]; }

拷贝数据进行处理

将待处理的音频数据拷贝到 in_buffer 中,经过 SAMICoreProcess 处理后,结果将拷贝至 out_buffer 中。

int inx = 0;
while(inx < total_samples - num_samples) {
    int actual_block_size = getNextBlockSize(inx, total_samples, num_samples);
    in_buffer.numberSamples = actual_block_size;
    out_buffer.numberSamples = actual_block_size;
    for(int i = 0; i < num_channels; i++) {
        memcpy(in_buffer.data[i], input_wav_data[i].data() + inx, actual_block_size * sizeof(float));
    }
    ret = SAMICoreProcess(handle, &in_block, &out_block);

    auto* outBuffer = (SAMICoreAudioBuffer*)out_block.audioData;
    if(ret != SAMI_OK  || outBuffer == nullptr) {
        std::cerr << "process error: " << ret;
        exit(-1);
    }
    // write output block to file
    audio_encoder->writePlanarData(outBuffer->data, outBuffer->numberChannels, actual_block_size);
    inx += actual_block_size;
}

In-Place 进行处理

更新音频数据的指针,指向正确的内存即可,这样可以避免内存数据的拷贝。

甚至某些场景下,你可以将 input 和 output 的内存指向同一处,这样处理后的结果将以 overwrite 的形式立即生效。

五、释放资源

ret = SAMICoreDestroyHandle(handle);

此外,还要注意音频数据数据的内存释放(如果有)。例如:

for(int i = 0; i < num_channels; i++) {
    delete[] in_buffer.data[i];
    delete[] out_buffer.data[i];
    in_buffer.data[i] = nullptr;
    out_buffer.data[i] = nullptr;
}

delete[] in_buffer.data;
delete[] out_buffer.data;
注意事项
  1. 本功能输入的数据需要是交织类型的,非交织的不支持,请参考上面 语音格式部分