音视频开发直播技术专题

基于iOS平台的最简单的FFmpeg音频播放器(一)

2018-05-23  本文已影响458人  Aiewing

基于iOS平台的最简单的FFmpeg音频播放器(一)
基于iOS平台的最简单的FFmpeg音频播放器(二)
基于iOS平台的最简单的FFmpeg音频播放器(三)

正式开始

音频的基本特性

1.初始化音频播放器

1.1 创建音频组件描述

AudioComponentDescription description = {0};
description.componentType = kAudioUnitType_Output;
description.componentSubType = kAudioUnitSubType_RemoteIO;
description.componentManufacturer = kAudioUnitManufacturer_Apple;

1.2 创建音频输出单元

AudioComponent component = AudioComponentFindNext(NULL, &description);
status = AudioComponentInstanceNew(component, &_audioUnit);
if (status != noErr) {
    NSLog(@"无法创建音频输出单元");
    return false;
}

1.3 获取设备音频的基本信息

// 获取硬件的输出信息
    size = sizeof(AudioStreamBasicDescription);
    status = AudioUnitGetProperty(_audioUnit,
                                           kAudioUnitProperty_StreamFormat,
                                           kAudioUnitScope_Input,
                                           0,
                                           &_outputFormat,
                                           &size);
    if (status != noErr) {
        NSLog(@"无法获取硬件的输出流格式");
        return false;
    }
    
    _numBytesPerSample = _outputFormat.mBitsPerChannel / 8;
    _numOutputChannels = _outputFormat.mChannelsPerFrame;

1.4 设置Audio Unit回调

// 设置回调
    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = renderCallback;
    callbackStruct.inputProcRefCon = (__bridge void *)(self);
    
    status = AudioUnitSetProperty(_audioUnit,
                                  kAudioUnitProperty_SetRenderCallback,
                                  kAudioUnitScope_Input,
                                  0,
                                  &callbackStruct,
                                  sizeof(callbackStruct));
    if (status != noErr) {
        NSLog(@"无法设置音频输出单元的回调");
        return false;
    }

1.5 初始化Audio Unit

// 初始化音频输出单元
    status = AudioUnitInitialize(_audioUnit);
    if (status != noErr) {
        NSLog(@"无法初始化音频输出单元");
        return false;
    }

2.启动Audio Unit

// 启动音频输出单元
    OSStatus status = AudioOutputUnitStart(_audioUnit);
    if (status == noErr) {
        _playing = true;
    } else {
        _playing = false;
    }

3.音频播放回调解析

#pragma mark - CallBack
static OSStatus renderCallback (void                        *inRefCon,
                                AudioUnitRenderActionFlags    * ioActionFlags,
                                const AudioTimeStamp         * inTimeStamp,
                                UInt32                        inOutputBusNumber,
                                UInt32                        inNumberFrames,
                                AudioBufferList                * ioData)
{
    AieAudioManager * aam = (__bridge AieAudioManager *)inRefCon;
    return [aam renderFrames:inNumberFrames ioData:ioData];
}
- (BOOL)renderFrames:(UInt32)numFrames ioData:(AudioBufferList *)ioData
{
    // 第一步
    for (int iBuffer = 0; iBuffer < ioData->mNumberBuffers; iBuffer++) {
        memset(ioData->mBuffers[iBuffer].mData, 0, ioData->mBuffers[iBuffer].mDataByteSize);
    }
    
    if (_playing && _outputBlock) {
        // 第二步
        _outputBlock(_outData, numFrames, _numOutputChannels);
        
        if (_numBytesPerSample == 2) {
            // 第三步
            float scale = (float)INT16_MAX;
            vDSP_vsmul(_outData,
                       1,
                       &scale,
                       _outData,
                       1,
                       numFrames * _numOutputChannels);
            // 第四步
            for (int iBuffer = 0; iBuffer < ioData->mNumberBuffers; ++iBuffer) {
                int thisNumChannels = ioData->mBuffers[iBuffer].mNumberChannels;
                for (int iChannel = 0; iChannel < thisNumChannels; ++iChannel) {
                    vDSP_vfix16(_outData+iChannel,
                                _numOutputChannels,
                                (SInt16 *)ioData->mBuffers[iBuffer].mData+iChannel,
                                thisNumChannels, numFrames);
                }
            }
        }
    }
    
    return noErr;
}

1. 第一步是初始化ioData,这个算是属于一个C语言范畴的函数,就是把ioData列表中所有的数据都用0来填充。
2. 第二步是使用block从外界,把需要的数据(实际上就是解码后的PCM数据)填充到_outData中来。
3. 第三步是利用vDSP_vsmul()对数据做了一个乘法的运算。
4. 第四步是对所有ioData数据,根据不同的声道,使用vDSP_vfix16()将非交错的16位带符号整数转化成单精度浮点型(因为苹果设备都是每个采样点16bit量化),如果是每个采样点32bit量化,那就需要使用vDSP_vflt32()

4.拓展方法

结尾

上一篇 下一篇

猜你喜欢

热点阅读