Audio Unit和ExtendedAudioFile播放音频

0.124字数 1013阅读 2445

前言

相关文章:
使用VideoToolbox硬编码H.264
使用VideoToolbox硬解码H.264
使用AudioToolbox编码AAC
使用AudioToolbox播放AAC
HLS点播实现(H.264和AAC码流)
HLS推流的实现(iOS和OS X系统)
iOS在线音频流播放
Audio Unit播放PCM文件
Audio Unit录音(播放伴奏+耳返)
Audio Unit播放aac/m4a/mp3等文件
前文介绍了AudioUnit的录音/播放功能,也介绍了通过AudioConvert进行音频的转换,但是AudioConvert的API使用起来较为麻烦,除了需要调用AudioFileGetProperty获取许多信息之外,还要调用AudioConverterFillComplexBuffer进行ConvertBuffer的填充,并在其数据输入回调中调用AudioFileReadPacketData,且要考虑AudioStreamPacketDescription的赋值。
本文尝试使用更为简单的方法 Extended Audio File Services
Extended Audio File Services是high-level的API,提供音频文件的读/写,是Audio File Services 和 Audio Converter Services 的结合,在AudioFile和AudioConvert的基础上提供统一的接口进行读写操作。

正文

概念储备

  • ExtAudioFileOpenURL是新建一个ExtAudioFileRef,用于读取音频文件;
  • ExtAudioFileWrapAudioFileID是通过一个已有的AudioFileID,创建一个ExtAudioFileRef;
    开发者必须保证在ExtAudioFileRef被销毁前,AudioFileID是处于打开的状态,并且在ExtAudioFileRef被销毁后,手动关闭AudioFileID;
  • ExtAudioFileGetProperty 获取对应PropertyID的属性;
  • ExtAudioFileGetProperty 获取设置PropertyID的属性;
    ExtAudioFile对应的PropertyID如下
CF_ENUM(ExtAudioFilePropertyID) {
    kExtAudioFileProperty_FileDataFormat        = 'ffmt',   // AudioStreamBasicDescription
    kExtAudioFileProperty_FileChannelLayout     = 'fclo',   // AudioChannelLayout
    kExtAudioFileProperty_ClientDataFormat      = 'cfmt',   // AudioStreamBasicDescription
    kExtAudioFileProperty_ClientChannelLayout   = 'cclo',   // AudioChannelLayout
    kExtAudioFileProperty_CodecManufacturer     = 'cman',   // UInt32
    
    // read-only:
    kExtAudioFileProperty_AudioConverter        = 'acnv',   // AudioConverterRef
    kExtAudioFileProperty_AudioFile             = 'afil',   // AudioFileID
    kExtAudioFileProperty_FileMaxPacketSize     = 'fmps',   // UInt32
    kExtAudioFileProperty_ClientMaxPacketSize   = 'cmps',   // UInt32
    kExtAudioFileProperty_FileLengthFrames      = '#frm',   // SInt64
    
    // writable:
    kExtAudioFileProperty_ConverterConfig       = 'accf',   // CFPropertyListRef
    kExtAudioFileProperty_IOBufferSizeBytes     = 'iobs',   // UInt32
    kExtAudioFileProperty_IOBuffer              = 'iobf',   // void *
    kExtAudioFileProperty_PacketTable           = 'xpti'    // AudioFilePacketTableInfo
};

介绍其中常用的属性:

  • kExtAudioFileProperty_FileDataFormat:读取文件格式,只读,返回文件的ASBD;

  • kAudioFormatProperty_FormatInfo:根据给定的格式,尽可能填充格式的其他信息。

  • kExtAudioFileProperty_ClientDataFormat:设置这个属性,才能进行对非pcm格式的文件进行编解码,这个格式也是ExtAudioFileRead 和 ExtAudioFileWrite 时的格式。

  • kExtAudioFileProperty_FileLengthFrames:文件的长度,单位是sample frames,获取前需要先设置好输入和输出的格式;

  • kExtAudioFileProperty_AudioConverter,是获取系统的AudioConverterRef,如果在获取之后,手动修改converter的属性,比如说码率,必须通过kExtAudioFileProperty_ConverterConfig设置ExtAudioFileRef;

  • kExtAudioFileError_CodecUnavailableInputConsumed:当ExtAudioFileWrite被打断的时候会返回这个错误,需要先停止调用ExtAudioFileWrite,等待audioSession恢复,并调用AudioSessionSetActive,再进行resuming;与kExtAudioFileError_CodecUnavailableInputNotConsumed的区别是,前者的buffer已经被使用,下次调用需要赋值新的buffer,后者需要再次提供相同的buffer;

具体细节

  • 1、初始化AVAudioSession和AudioBufferList;

  • 2、通过url打开ExtAudioFileRef,并通过ExtAudioFileGetProperty获取文件格式;初始化读取的格式,并通过ExtAudioFileSetProperty设置给ExtAudioFileRef;输入和输出格式设置,类似初始化AudioConvert的过程。

  • 3、初始化AudioUnit,并设置输入的格式与ExtAudioFileRef的输出格式一致;

  • 4、在AudioUnit的播放回调中调用ExtAudioFileRead读取ExtAudioFileRef的数据,如果读取返回的数组长度是0表示播放结束;

demo播放

遇到的问题

1、获取的音频frame帧数不正常

如果在未设置好输入输出格式前,就通过kExtAudioFileProperty_FileLengthFrames获取的总frame数,此时获取的frame是不准确的,并且会导致后续的操作错误。
正确的做法是先设置好 kExtAudioFileProperty_ClientDataFormat属性的值,再获取总的frame数。

2、播放进度不准确

播放的进度=当前播放的帧数/音频文件的总帧数;
进度不准确问题是因为获取的是frame数,之前在计算已播放的帧数时没有正确的把读取的字节长度除以输出格式的mBytesPerFrame。
当前已播放的帧数 += 读取的字节长度 / ASBD.mBytesPerFrame。

总结

ExtendedAudioFile相对Audio File Services 和 Audio Converter Services ,API调用非常简单和明确,并且不需要去处理AudioStreamPacketDescription,在实际开发中逻辑更为清晰。
demo 的代码在这里,可以看到ExtendedAudioFile具体使用方式。

推荐阅读更多精彩内容