32.FFmpeg学习笔记 - iOS上播放PCM

本文介绍了如何使用iOS的Audio Unit框架播放PCM音频文件,重点关注了Remote I/O Unit的配置和回调函数的实现,通过设置音频会话类别、音频单元属性以及读取文件数据来实现音频播放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文打算用iOS上的Audio Unit来播放PCM文件。

文章当中用的Remote I/O Unit类型,下面是官方的一张示意图:

从图中可以看到,两个element都包含输入输出两个端。element0的输出端(output scope)对应的是扬声器,输入端用于输入数据,可以从element1的输出端获取,也可以从其他Unit或文件获取。

element1的输入端表示麦克风,element的输出端表示从麦克风采集到的声音数据。

下面是代码流程:

本文输入的pcm数据,声道数2,格式是浮点数存储,小端,采样率48000,交错存储(也就是LRLR....)。

首先打开pcm文件,用于提供数据:

    //打开文件
    const char *in_filename = [[[NSBundle mainBundle] pathForResource:@"sintel_f32le_2_48000.pcm" ofType:nil] UTF8String];
    _inFile = fopen(in_filename, "rb");
    if (!_inFile) {
        printf("open file %s failed\n", in_filename);
        return;
    }

设置audioSession,由于只是播放,所以采用AVAudioSessionCategoryPlayback,

    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryPlayback error:&error];

下面创建一个Audio Unit实例,选择RemoteIO类型,

    //设置类型RemoteIO,用于播放
    AudioComponentDescription audioDesc;
    audioDesc.componentType = kAudioUnitType_Output;
    audioDesc.componentSubType = kAudioUnitSubType_RemoteIO;
    audioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
    audioDesc.componentFlags = 0;
    audioDesc.componentFlagsMask = 0;
    
    AudioComponent inputComponent = AudioComponentFindNext(NULL, &audioDesc);
    AudioComponentInstanceNew(inputComponent, &_audioUnit);

通过调用AudioUnitSetProperty,开启扬声器,默认是关闭的,

    
#define INPUT_BUS 1
#define OUTPUT_BUS 0

    UInt32 flag = 1;
    //打开element0(OUTPUT_BUS=0)的输出scope,也就是开启扬声器
    status = AudioUnitSetProperty(_audioUnit,
                                  kAudioOutputUnitProperty_EnableIO,
                                  kAudioUnitScope_Output,
                                  OUTPUT_BUS,
                                  &flag,
                                  sizeof(flag));
    if (status != noErr) {
        NSLog(@"AudioUnitSetProperty error : %d", status);
        return;
    }

设置输入的pcm音频格式:

    //设置输入的音频格式
    AudioStreamBasicDescription outputFormat;
    memset(&outputFormat, 0, sizeof(outputFormat));
    outputFormat.mSampleRate       = 48000; // 采样率
    outputFormat.mFormatID         = kAudioFormatLinearPCM; // PCM格式
    /*kAudioFormatFlagIsNonInterleaved表示音频源是非交错的,在回调函数中需要对
      ioData->mBuffers数组分别填充各声道的数据。
      kAudioFormatFlagIsBigEndian可以指定大端存储的数据。
    */
    outputFormat.mFormatFlags      = kLinearPCMFormatFlagIsFloat; // 浮点数
    outputFormat.mFramesPerPacket  = 1; // 非压缩数据,固定填1
    outputFormat.mChannelsPerFrame = 2; // 声道数
    /*对于交错声音格式,mBytesPerFrame = 声道数 * 每个采样点占的字节数(浮点数是4),
      非交错格式,mBytesPerFrame = 每个采样点占的字节数(浮点数是4)
     */
    outputFormat.mBytesPerFrame    = 2 * 4;
    outputFormat.mBytesPerPacket   = outputFormat.mBytesPerFrame;
    outputFormat.mBitsPerChannel   = 8 * 4; //每声道占的位数

    //设置element0的输入scope
    status = AudioUnitSetProperty(_audioUnit,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Input,
                                  OUTPUT_BUS,
                                  &outputFormat,
                                  sizeof(outputFormat));
    if (status != noErr) {
        NSLog(@"AudioUnitSetProperty error : %d", status);
        return;
    }

设置回调函数,作用是:每当Audio Unit需要数据进行播放时,就会调用回调函数,这样开发人员可以从文件或上一级Unit获得数据,进行填充。

    //设置回调函数
    AURenderCallbackStruct playCallback;
    playCallback.inputProc = PlayCallback;
    playCallback.inputProcRefCon = (__bridge void *)self;
    AudioUnitSetProperty(_audioUnit,
                         kAudioUnitProperty_SetRenderCallback,
                         kAudioUnitScope_Input,
                         OUTPUT_BUS,
                         &playCallback,
                         sizeof(playCallback));

初始化、开启Unit:

    
    status = AudioUnitInitialize(_audioUnit);
    if (status != noErr) {
        NSLog(@"AudioUnitInitialize error : %d", status);
        return;
    }
    
    status =  AudioOutputUnitStart(_audioUnit);
    if (status != noErr) {
        NSLog(@"AudioOutputUnitStart error : %d", status);
        return;
    }

下面是回调函数,从文件中读取数据,填充到ioData->mBuffers[0].mData中,进行播放。

static OSStatus PlayCallback(void *inRefCon,
                             AudioUnitRenderActionFlags *ioActionFlags,
                             const AudioTimeStamp *inTimeStamp,
                             UInt32 inBusNumber,
                             UInt32 inNumberFrames,
                             AudioBufferList *ioData) {
    
    ViewController *vc = (__bridge ViewController *)inRefCon;

    ioData->mBuffers[0].mDataByteSize = (UInt32)fread(ioData->mBuffers[0].mData, 1, ioData->mBuffers[0].mDataByteSize, vc->_inFile);
    NSLog(@"out size: %d", ioData->mBuffers[0].mDataByteSize);
    
    if (ioData->mBuffers[0].mDataByteSize <= 0) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [vc stop];
        });
    }
    return noErr;
}
- (void)stop {
    AudioOutputUnitStop(_audioUnit);
    AudioUnitUninitialize(_audioUnit);
    AudioComponentInstanceDispose(_audioUnit);
    fclose(_inFile);
}

下载地址:

https://github.com/whoyouare888/Note/tree/master/FFmpegPlayer_1/AudioUnitPlayPCM

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值