【问题标题】:Audio Processing: Playing with volume level音频处理:播放音量
【发布时间】:2010-10-20 22:04:37
【问题描述】:

我想从应用程序包中读取声音文件,复制它,以最大音量播放(增益值或峰值功率,我不确定它的技术名称),然后将其写入另一个文件再次到捆绑包。

我做了复制和写作部分。结果文件与输入文件相同。我在 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);

【问题讨论】:

    标签: iphone audio volume audioqueueservices audiotoolbox


    【解决方案1】:

    您不会在 (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 的刻度即可。例如,2.f 的 ampScale 将为您提供 +6dB 的增益。

    如果要进行归一化,则必须对音频进行两次遍历:一次确定幅度最大的样本。然后另一个实际应用您计算的增益。

    使用 AudioQueue 服务只是为了访问音量属性是严重的、严重的矫枉过正。

    更新:

    在您更新的代码中,您将每个 byte 乘以 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;
    }
    
    ...
    

    当然,这不是最好的处理方式:它假定您的文件是 little-endian 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,我已根据您的建议更新了我的代码和问题,但这导致了其他问题。也许我误会了你,但如果你能用代码 sn-p 查看更新的问题,那就完美了。
    • 我已经编辑了我的答案,解释了为什么您的代码不起作用以及一个代码示例。
    • 艺术,我很感激这个答案。您不仅为我提供了代码,还帮助我了解了发生了什么。非常感谢!请继续分享。也感谢其他人。
    • @ArtGillespie 我也在做同样的事情;我正在尝试为视频播放(或录制)的音频缓冲区实现音量计。我正在寻找功率的百分比,为此我需要知道 AudioBufferList 中 mData 的最大值和最小值。你知道我怎么能做到这一点吗?当我打印出 mData 时,我得到的值在 -200 和 200 之间无声地快速反弹,而在 -3000 和 6000 之间则有一些噪音。你知道我如何将这些数字转化为作为音量功率百分比的值吗?
    • @ArtGillespie 如果我从远程服务器流式传输数据会怎样?
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 那是个坏消息 :) 如果需要,我希望框架能够处理此类科学操作 :)
      【解决方案4】:

      如果声音文件被规范化,则无法使文件更响亮。除了音频编码不佳的情况外,音量几乎完全是播放引擎的领域。

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

      正确存储的音频文件的峰值音量将等于或接近文件位深度可用的最大值。如果您试图“降低声音文件的音量”,您实际上只会降低音质。

      【讨论】:

      • 谢谢,这是有道理的。然后我必须扩展问题并分享我自己的替代方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-01
      • 1970-01-01
      相关资源
      最近更新 更多