音频处理:调节音量水平

9
我想从应用程序捆绑包中读取声音文件,复制它,使用其最大音量级别(增益值或峰值功率,我不确定技术名称),然后将其作为另一个文件再次写入捆绑包。
我已经完成了复制和写入部分。生成的文件与输入文件相同。我使用AudioToolbox框架中的AudioFile服务的AudioFileReadBytes()和AudioFileWriteBytes()函数来完成这个任务。
因此,我有输入文件的字节以及其音频数据格式(通过使用带有kAudioFilePropertyDataFormat的AudioFileGetProperty()函数),但我找不到一个变量来处理原始文件的最大音量级别。
为了澄清我的目的,我正在尝试生成另一个声音文件,其音量水平相对于原始文件增加或减少,因此我不关心用户或iOS设置的系统音量水平。
使用我提到的框架能否实现这一点?如果不能,是否有其他替代建议?
谢谢
编辑: 在查看Sam的答案时,我决定通过另一种方式扩展问题。
我可以使用AudioQueue服务将现有声音文件(位于捆绑包中)录制到另一个文件中,并在录制阶段使用框架调整音量级别吗?
更新: 以下是我如何读取输入文件和写入输出的代码。下面的代码降低了“某些”幅度值的声音级别,但带有大量噪音。有趣的是,如果我选择0.5作为幅度值,它会增加声音级别而不是降低声音级别,但当我使用0.1作为幅度值时,它会降低声音。这两种情况都涉及到扰动噪音。我想这就是Art谈论规范化的原因,但我对规范化一无所知。
AudioFileID inFileID;

CFURLRef inURL = [self inSoundURL];

AudioFileOpenURL(inURL, kAudioFileReadPermission, kAudioFileWAVEType, &inFileID)

UInt32 fileSize = [self audioFileSize:inFileID];
Float32 *inData = malloc(fileSize * sizeof(Float32)); //I used Float32 type with jv42's suggestion
AudioFileReadBytes(inFileID, false, 0, &fileSize, inData);

Float32 *outData = malloc(fileSize * sizeof(Float32));

//Art's suggestion, if I've correctly understood him

float ampScale = 0.5f; //this will reduce the 'volume' by -6db
for (int i = 0; i < fileSize; i++) {
    outData[i] = (Float32)(inData[i] * ampScale);
}

AudioStreamBasicDescription outDataFormat = {0};
[self audioDataFormat:inFileID];

AudioFileID outFileID;

CFURLRef outURL = [self outSoundURL];
AudioFileCreateWithURL(outURL, kAudioFileWAVEType, &outDataFormat, kAudioFileFlags_EraseFile, &outFileID)

AudioFileWriteBytes(outFileID, false, 0, &fileSize, outData);

AudioFileClose(outFileID);
AudioFileClose(inFileID);
4个回答

16

在(Ext)AudioFile中,你不会找到幅度缩放操作,因为它是可以做的最简单的DSP。

假设您使用ExtAudioFile将读取的任何内容转换为32位浮点数。要更改振幅,只需进行乘法运算:

float ampScale = 0.5f; //this will reduce the 'volume' by -6db
for (int ii=0; ii<numSamples; ++ii) {
    *sampOut = *sampIn * ampScale;
    sampOut++; sampIn++;
}

要增加增益,您只需使用比1.f大的比例尺。例如,ampScale为2.f将使您获得+6dB的增益。

如果您想要标准化,则必须对音频进行两次处理:一次用于确定具有最大振幅的样本,然后另一次用于实际应用计算出的增益。

仅为了访问音量属性而使用AudioQueue服务是严重且过度的。

更新:

在您的更新代码中,您正在将每个字节乘以0.5,而不是每个样本。这是您代码的快速修复,但请参见下面的注释。个人不建议您做这件事。

...

// create short pointers to our byte data
int16_t *inDataShort = (int16_t *)inData;
int16_t *outDataShort = (int16_t *)inData;

int16_t ampScale = 2;
for (int i = 0; i < fileSize; i++) {
    outDataShort[i] = inDataShort[i] / ampScale;
}

...

当然,这不是最好的做法:它假定你的文件是小端16位带符号线性PCM。(大多数WAV文件是这样的,但AIFF、m4a、mp3等不是)。我会使用ExtAudioFile API而不是AudioFile API,因为它会将你读取的任何格式转换为你想在代码中使用的任何格式。通常,最简单的方法是将样本读入为32位浮点数。以下是使用ExtAudioAPI处理任何输入文件格式(包括立体声与单声道)的代码示例:

void ScaleAudioFileAmplitude(NSURL *theURL, float ampScale) {
    OSStatus err = noErr;

    ExtAudioFileRef audiofile;
    ExtAudioFileOpenURL((CFURLRef)theURL, &audiofile);
    assert(audiofile);

    // get some info about the file's format.
    AudioStreamBasicDescription fileFormat;
    UInt32 size = sizeof(fileFormat);
    err = ExtAudioFileGetProperty(audiofile, kExtAudioFileProperty_FileDataFormat, &size, &fileFormat);

    // we'll need to know what type of file it is later when we write 
    AudioFileID aFile;
    size = sizeof(aFile);
    err = ExtAudioFileGetProperty(audiofile, kExtAudioFileProperty_AudioFile, &size, &aFile);
    AudioFileTypeID fileType;
    size = sizeof(fileType);
    err = AudioFileGetProperty(aFile, kAudioFilePropertyFileFormat, &size, &fileType);


    // tell the ExtAudioFile API what format we want samples back in
    AudioStreamBasicDescription clientFormat;
    bzero(&clientFormat, sizeof(clientFormat));
    clientFormat.mChannelsPerFrame = fileFormat.mChannelsPerFrame;
    clientFormat.mBytesPerFrame = 4;
    clientFormat.mBytesPerPacket = clientFormat.mBytesPerFrame;
    clientFormat.mFramesPerPacket = 1;
    clientFormat.mBitsPerChannel = 32;
    clientFormat.mFormatID = kAudioFormatLinearPCM;
    clientFormat.mSampleRate = fileFormat.mSampleRate;
    clientFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved;
    err = ExtAudioFileSetProperty(audiofile, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);

    // find out how many frames we need to read
    SInt64 numFrames = 0;
    size = sizeof(numFrames);
    err = ExtAudioFileGetProperty(audiofile, kExtAudioFileProperty_FileLengthFrames, &size, &numFrames);

    // create the buffers for reading in data
    AudioBufferList *bufferList = malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer) * (clientFormat.mChannelsPerFrame - 1));
    bufferList->mNumberBuffers = clientFormat.mChannelsPerFrame;
    for (int ii=0; ii < bufferList->mNumberBuffers; ++ii) {
        bufferList->mBuffers[ii].mDataByteSize = sizeof(float) * numFrames;
        bufferList->mBuffers[ii].mNumberChannels = 1;
        bufferList->mBuffers[ii].mData = malloc(bufferList->mBuffers[ii].mDataByteSize);
    }

    // read in the data
    UInt32 rFrames = (UInt32)numFrames;
    err = ExtAudioFileRead(audiofile, &rFrames, bufferList);

    // close the file
    err = ExtAudioFileDispose(audiofile);

    // process the audio
    for (int ii=0; ii < bufferList->mNumberBuffers; ++ii) {
        float *fBuf = (float *)bufferList->mBuffers[ii].mData;
        for (int jj=0; jj < rFrames; ++jj) {
            *fBuf = *fBuf * ampScale;
            fBuf++;
        }
    }

    // open the file for writing
    err = ExtAudioFileCreateWithURL((CFURLRef)theURL, fileType, &fileFormat, NULL, kAudioFileFlags_EraseFile, &audiofile);

    // tell the ExtAudioFile API what format we'll be sending samples in
    err = ExtAudioFileSetProperty(audiofile, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);

    // write the data
    err = ExtAudioFileWrite(audiofile, rFrames, bufferList);

    // close the file
    ExtAudioFileDispose(audiofile);

    // destroy the buffers
    for (int ii=0; ii < bufferList->mNumberBuffers; ++ii) {
        free(bufferList->mBuffers[ii].mData);
    }
    free(bufferList);
    bufferList = NULL;

}

谢谢Art,我已经根据你的建议更新了我的代码和问题,但这引起了其他问题。也许我理解有误,如果您能查看带有代码片段的更新问题,那就太完美了。 - cocoatoucher
我已经编辑了我的回答,并解释了为什么你的代码无法工作,同时提供了一个将能够工作的代码示例。 - Art Gillespie
1
Art,非常感谢你的回答。你不仅提供了代码,还帮助我理解了其中的原理。非常感谢!请继续分享。也感谢其他人。 - cocoatoucher
@ArtGillespie 我正在做类似的事情;我正在尝试为视频播放(或录制)的音频缓冲区实现一个音量表。我正在寻找功率百分比,为此我需要知道AudioBufferList中mData的最大和最小值。你知道我怎么能做到这一点吗?当我打印出mData时,在静音时值会快速在-200和200之间反弹,在一些噪声中则在-3000和6000之间。你知道我怎么能将这些数字转换为用作音量功率百分比的值吗? - Sti
@ArtGillespie 如果我从远程服务器流式传输数据怎么办? - SPatel

1

我认为如果可以的话,你应该避免使用8位无符号字符来处理音频。 尽量获取16位或32位的数据,这样可以避免一些噪音/质量问题。


0
对于大多数常见的音频文件格式,不存在单一的主音量变量。相反,您需要获取(或转换为)PCM声音样本,并对每个样本执行至少一些最小的数字信号处理(乘法、饱和/限制/AGC、量化噪声整形等)。

那可不太好了 :) 如果需要的话,我更希望框架能够处理这样的科学运算 :) - cocoatoucher

0

如果声音文件已经被标准化,那么你几乎无法通过其他方法让声音文件更大声。除了在编码错误的情况下,音量基本上完全掌握在播放引擎的领域。

http://en.wikipedia.org/wiki/Audio_bit_depth

妥善存储的音频文件将在其位深度可用的最大值处或附近具有峰值音量。如果您尝试“降低音量”声音文件,则实质上只会降低声音质量。


谢谢,那很有道理。然后我需要详细阐述问题并分享我的其他选择。 - cocoatoucher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接