iOS - 音频: Core Audio - 播放

探索iOS之CoreAudio核心音频框架 iOS的CoreAudio分为三层:应用服务层、驱动层、硬件层。其中,应用服务层包括:AudioQueue Service、AudioPlayer Service、AudioSession Service、AudioFile Service、AudioUnit等。 阅读详情

环境

iOS 18

Xcode 16.3

swift-driver version: 1.120.5 Apple Swift version 6.1 (swiftlang-6.1.0.110.21 clang-1700.0.13.3)
Target: x86_64-apple-macosx15.0

Core Audio 的架构

17457393790996.jpg

声音数据的描述

sample: 一个声道采样的值,采样率定义了每秒从连续信号中提取并组成离散信号的采样个数,它用赫兹(Hz)来表示。

17458195551416.jpg

17458196263841.jpg

frame: 一个时间常量内的sample的集合
packet: 一个或多个连续的帧

AudioStreamBasicDescription
// CoreAudioBaseTypes.h
// 音频的格式信息
struct AudioStreamBasicDescription
{
    Float64             mSampleRate;  // 采样率
    AudioFormatID       mFormatID;    // 采样格式类型
    AudioFormatFlags    mFormatFlags; // 采样格式标志位
    UInt32              mBytesPerPacket;   // 每个packet的字节数
    // 每个packet的frame数,在未压缩的音频中 mFramesPerPacket == 1 
    UInt32              mFramesPerPacket;
    UInt32              mBytesPerFrame;    // 每个frame的字节数
    UInt32              mChannelsPerFrame; // 每个frame的声道数
    UInt32              mBitsPerChannel;   // 每个通道的比特数
    UInt32              mReserved;         // 保留
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;
CF_ENUM(AudioFormatID)
{
    kAudioFormatLinearPCM               = 'lpcm',
    kAudioFormatAC3                     = 'ac-3',
    kAudioFormat60958AC3                = 'cac3',
    kAudioFormatAppleIMA4               = 'ima4',
    kAudioFormatMPEG4AAC                = 'aac',
    kAudioFormatMPEG4CELP               = 'celp',
    kAudioFormatMPEG4HVXC               = 'hvxc',
    kAudioFormatMPEG4TwinVQ             = 'twvq',
    kAudioFormatMACE3                   = 'MAC3',
    kAudioFormatMACE6                   = 'MAC6',
    kAudioFormatULaw                    = 'ulaw',
    kAudioFormatALaw                    = 'alaw',
    kAudioFormatQDesign                 = 'QDMC',
    kAudioFormatQDesign2                = 'QDM2',
    kAudioFormatQUALCOMM                = 'Qclp',
    kAudioFormatMPEGLayer1              = '.mp1',
    kAudioFormatMPEGLayer2              = '.mp2',
    kAudioFormatMPEGLayer3              = '.mp3',
    kAudioFormatTimeCode                = 'time',
    kAudioFormatMIDIStream              = 'midi',
    kAudioFormatParameterValueStream    = 'apvs',
    kAudioFormatAppleLossless           = 'alac',
    kAudioFormatMPEG4AAC_HE             = 'aach',
    kAudioFormatMPEG4AAC_LD             = 'aacl',
    kAudioFormatMPEG4AAC_ELD            = 'aace',
    kAudioFormatMPEG4AAC_ELD_SBR        = 'aacf',
    kAudioFormatMPEG4AAC_ELD_V2         = 'aacg',
    kAudioFormatMPEG4AAC_HE_V2          = 'aacp',
    kAudioFormatMPEG4AAC_Spatial        = 'aacs',
    kAudioFormatMPEGD_USAC              = 'usac',
    kAudioFormatAMR                     = 'samr',
    kAudioFormatAMR_WB                  = 'sawb',
    kAudioFormatAudible                 = 'AUDB',
    kAudioFormatiLBC                    = 'ilbc',
    kAudioFormatDVIIntelIMA             = 0x6D730011,
    kAudioFormatMicrosoftGSM            = 0x6D730031,
    kAudioFormatAES3                    = 'aes3',
    kAudioFormatEnhancedAC3             = 'ec-3',
    kAudioFormatFLAC                    = 'flac',
    kAudioFormatOpus                    = 'opus',
    kAudioFormatAPAC                    = 'apac',
};

CF_ENUM(AudioFormatFlags)
{
    kAudioFormatFlagIsFloat                     = (1U << 0),     // 0x1
    kAudioFormatFlagIsBigEndian                 = (1U << 1),     // 0x2
    kAudioFormatFlagIsSignedInteger             = (1U << 2),     // 0x4
    kAudioFormatFlagIsPacked                    = (1U << 3),     // 0x8
    kAudioFormatFlagIsAlignedHigh               = (1U << 4),     // 0x10
    kAudioFormatFlagIsNonInterleaved            = (1U << 5),     // 0x20
    kAudioFormatFlagIsNonMixable                = (1U << 6),     // 0x40
    kAudioFormatFlagsAreAllClear                = 0x80000000,
	
    kLinearPCMFormatFlagIsFloat                 = kAudioFormatFlagIsFloat,
    kLinearPCMFormatFlagIsBigEndian             = kAudioFormatFlagIsBigEndian,
    kLinearPCMFormatFlagIsSignedInteger         = kAudioFormatFlagIsSignedInteger,
    kLinearPCMFormatFlagIsPacked                = kAudioFormatFlagIsPacked,
    kLinearPCMFormatFlagIsAlignedHigh           = kAudioFormatFlagIsAlignedHigh,
    kLinearPCMFormatFlagIsNonInterleaved        = kAudioFormatFlagIsNonInterleaved,
    kLinearPCMFormatFlagIsNonMixable            = kAudioFormatFlagIsNonMixable,
    kLinearPCMFormatFlagsSampleFractionShift    = 7,
    kLinearPCMFormatFlagsSampleFractionMask     = (0x3F << kLinearPCMFormatFlagsSampleFractionShift),
    kLinearPCMFormatFlagsAreAllClear            = kAudioFormatFlagsAreAllClear,
	
    kAppleLosslessFormatFlag_16BitSourceData    = 1,
    kAppleLosslessFormatFlag_20BitSourceData    = 2,
    kAppleLosslessFormatFlag_24BitSourceData    = 3,
    kAppleLosslessFormatFlag_32BitSourceData    = 4
};

About the name: Although this data type has “stream” in its name, you use it in every instance where you need to represent an audio data format in Core Audio—including in non-streamed, standard files. You could think of it as the “audio format basic description” data type. The “stream” in the name refers to the fact that audio formats come into play whenever you need to move (that is, stream) audio data around in hardware or software.

类型中的stream只是表示播放的概念,这里定义的就是iOS音频格式的描述

获取声音的格式信息
public func audioFormat() -> AudioStreamBasicDescription? {
        var size: UInt32 = 0
        var audioFile: AudioFileID? // 使用可选类型
        var status = AudioFileOpenURL(self.path as CFURL, .readPermission, 0, &audioFile)
        guard status == noErr else {
            print("*** Error *** filePath: \(self.path) -- code: \(status)")
            return nil
        }
        
        // 获取音频数据格式
        var dataFormat = AudioStreamBasicDescription()
        size = UInt32(MemoryLayout<AudioStreamBasicDescription>.size)
        
        status = AudioFileGetProperty(
            audioFile!,
            kAudioFilePropertyDataFormat,
            &size,
            &dataFormat
        )
        
        // 可选:检查属性获取是否成功
        guard status == noErr else {
            print("Failed to get data format: \(status)")
            AudioFileClose(audioFile!) // 关闭文件防止泄漏
            return nil
        }
        
        return dataFormat
}

17457511066160.jpg

我加载的是一个44.1kHz,双声道的mp3文件
778924083 = 0x2E6D7033 => “.mp3”

AudioStreamPacketDescription
// CoreAudioBaseTypes.h
// 表示每个音频packet的数据
struct  AudioStreamPacketDescription {
    // For example, if the data buffer contains 5 bytes of data, with one byte per packet, then the start offset for the last packet is 4. There are 4 bytes in the buffer before the start of the last packet.
    // 偏移量
    // 比如缓冲区有5字节的数据,当每个packet包含1个字节,那最后一个包的这个值就是4
    SInt64  mStartOffset;
    UInt32  mVariableFramesInPacket; // frame的个数,如果是格式是CBR按常量编码的,这个值设置为0
    UInt32  mDataByteSize; // 字节数
};
typedef struct AudioStreamPacketDescription AudioStreamPacketDescription;
Proxy Objects,Properties, Scopes, Elements
名称说明
Proxy Objects音频文件,流的代码抽象
Properties管理对象的状态或行为
Scopes作用域:输入/输出
Elements具体的设备,比如蓝牙,有线…

Properties的key一般是个常量值,比如 kAudioFilePropertyFileFormat或者kAudioQueueDeviceProperty_NumberChannels.

回调函数: 与Core Audio交互

Core Audio接口通过回调函数与应用交互,一般有下面几种场景

  • 音频数据传送到应用程序(例如用于录制;然后通过回调将新数据写入磁盘)
  • 从应用程序请求音频数据(例如用于播放;回调从磁盘/内存读取并提供数据)。
  • 通知应用程序音频对象已更改状态(回调会执行适当操作,比如更新界面)。

回调函数实现分两步: 注册 && 回调

相关框架

名称说明
AVFoundation提供AVAudioPlayer类,这是一个用于音频播放的简化的原生接口,还提供AVAudioEngine类,用于更复杂的音频处理
AudioToolbox为Core Audio中的高级服务提供接口,包括Audio Session服务,用于在iOS管理应用程序的音频行为,允许应用程序使用音频插件,包括音频单元和编解码器
OpenAL提供了与OpenAL配合使用的接口
CoreAudioTypes提供Core Audio使用的数据类型以及低级服务的接口。

2025-04-29 08.40.08.png

示例

播放音乐

使用AudioToolbox.framework

func play() {
        var format = self.audioFormat()
        guard format != nil else {
            return;
        }
        
        // 打开音频文件
        var status = AudioFileOpenURL(path as CFURL, .readPermission, 0, &audioFile)
        guard status == noErr else {
            return
        }
        
        let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
        /**
        Audio Queue(音频队列)是 iOS 或 Mac OS X 中用来 录音 或 播放音频 的软件对象。它由 AudioQueueRef 数据类型表示,在 AudioQueue.h 中声明
        音频队列的工作是:

        * 连接音频硬件
        * 管理内存
        * 根据需要,为压缩音频格式使用编解码器
        * 调解录音或回放
        */
        // 创建输出队列,注册回调函数
        status = AudioQueueNewOutput(&format!, playbackCallback, context, nil, nil, 0, &queue)
        guard status == noErr else {
            print("Create new output queue error")
            return
        }
        
        status = AudioFileGetProperty(audioFile!, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize)
        guard status == noErr else {
            print("Get maxPacketSize property error")
            return
        }
        
        // 计算缓冲区大小
        // For uncompressed audio, the value is 1. For variable bit-rate formats, the value is a larger fixed number, such as 1024 for AAC. For formats with a variable number of frames per packet, such as Ogg Vorbis, set this field to 0.
        // MP3的值是1152
        if format!.mFramesPerPacket != 0 {
            // 每秒的包的数量 = 采样率/每个包的帧数
            // 比如MP3每秒采样44100次,每个包有1152帧,两者相除就是每秒的包数量
            let numPacketsPerSecond = format!.mSampleRate / Double(format!.mFramesPerPacket)
            // 每秒的包的数量 * 包大小的理论峰值, 即1s内占用内存的峰值
            outBufferSize = UInt32(numPacketsPerSecond * Double(maxPacketSize))
        } else {
            outBufferSize = max(maxBufferSize, maxPacketSize)
        }
        
        // 调整缓冲区大小
        if outBufferSize > maxBufferSize && outBufferSize > maxPacketSize {
            outBufferSize = maxBufferSize
        } else if outBufferSize < minBufferSize {
            outBufferSize = minBufferSize
        }
        
        // 计算包数量并分配包描述数组
        // 每次读取的包的数量 = 缓冲区大小 / 包大小的理论峰值
        numPacketsToRead = outBufferSize / maxPacketSize
        packetDescs = UnsafeMutablePointer<AudioStreamPacketDescription>.allocate(capacity: Int(numPacketsToRead))
        packetDescs?.initialize(repeating: AudioStreamPacketDescription(), count: Int(numPacketsToRead))
        
        // 分配3个缓冲区
        for i in 0..<maxBufferNum {
            var buffer: AudioQueueBufferRef?
            /**
             Once allocated, the pointer to the buffer and the buffer's size are fixed and cannot be
             changed. The mAudioDataByteSize field in the audio queue buffer structure,
             AudioQueueBuffer, is initially set to 0.
             
             @param      inAQ
             // 分配缓冲区的音频队列
             The audio queue you want to allocate a buffer.
             @param      inBufferByteSize
             // 缓冲区大小
             The desired size of the new buffer, in bytes. An appropriate buffer size depends on the
             processing you will perform on the data as well as on the audio data format.
             @param      outBuffer
             // 指向新创建的缓冲区
             On return, points to the newly created audio buffer. The mAudioDataByteSize field in the
             audio queue buffer structure, AudioQueueBuffer, is initially set to 0.
             @result     An OSStatus result code.
             */
            AudioQueueAllocateBuffer(queue!, outBufferSize, &buffer)
            buffers[i] = buffer
            // 预先读maxBufferNum个缓冲区
            audioQueueOutput(audioQueue: queue!, queueBuffer: buffer!)
        }
        
        // 启动队列
        AudioQueueStart(queue!, nil)
    }

在回调方法中读取音频文件并将数据入队供系统的队列播放

private let playbackCallback: AudioQueueOutputCallback = {
        inUserData,          // UnsafeMutableRawPointer?
        inAQ,                // AudioQueueRef
        buffer               // AudioQueueBufferRef
        in
        
        // 1. 保护
        guard let inUserData = inUserData else { return }
        
        // 2. 将指针转换为对象实例
        let player:AudioToolboxTest = Unmanaged<AudioToolboxTest>.fromOpaque(inUserData).takeUnretainedValue()
        
        // 3. 实际处理逻辑
        // 处理音频缓冲区
        player.audioQueueOutput(audioQueue: inAQ, queueBuffer: buffer)
    }
public func audioQueueOutput(audioQueue: AudioQueueRef, queueBuffer audioQueueBuffer: AudioQueueBufferRef) {
        var ioNumBytes = outBufferSize
        var ioNumPackets = numPacketsToRead
        
        // 读取音频文件
        let status = AudioFileReadPacketData(
            audioFile!,  // 音频文件
            false, // 是否缓存
            // on input the size of outBuffer in bytes.
            // on output, the number of bytes actually returned.
            // 输入场景等于缓存大小
            // 输出场景:比如播放根据文件的实际情况
            &ioNumBytes, // 包的长度
            packetDescs, // 音频包信息
            packetIndex, // 索引值
            &ioNumPackets, // 包的个数
            audioQueueBuffer.pointee.mAudioData // 读取到的数据
        )
        
        guard status == noErr else {
            print("Error reading packet data: \(status)")
            return
        }
        
        if ioNumPackets > 0 {
            // 设置音频缓冲区数据大小
            audioQueueBuffer.pointee.mAudioDataByteSize = ioNumBytes
            
            // 将音频数据入队列让系统去播放
            AudioQueueEnqueueBuffer(
                audioQueue,
                audioQueueBuffer,
                ioNumPackets,
                packetDescs
            )
            
            // 更新包的索引值
            self.packetIndex += Int64(ioNumPackets)
        }
    }

示例工程

参考

  1. Apple Core Audio Doc
  2. AudioFileOpenURL(:::😃
  3. iOS音频(1)——AudioToolbox
Core Audio音频基础概述 大多情况下,我们无法直接与HAL进行交互,Apple提供了一个特别的audio unit,即OS X中的AUHAL, iOS中的AURemoteIO, 我们可以通过它们让音频与硬件交互. 阅读详情

相关推荐

掌握音频核心:Swift 中 Core Audio 的深度应用与实践

Core Audio是Apple公司提供的一个底层音频处理框架,它包括了音频录制、播放、处理和文件格式转换等功能。Core Audio适用于macOS和iOS平台,为开发者提供了直接硬件访问的能力。

2401_85339615的博客 1127

基于 CoreAudio 的音频编解码(一):音频解码

系列文章目录 基于 CoreAudio 的音频编解码(一):音频解码 基于 CoreAudio 的音频编解码(二):音频编码 前言 Core Audio 是iOS和MAC系统中的关于数字音频处理的基础,它是应用程序用来处理音频的一组软件框架,所有关于iOS音频开发的接口都是由Core Audio来提供或者经过它提供的接口来进行封装的。 下图是 Core Audio 框架结构,其功能可谓是丰富且强大,几乎涵盖了所有与音频处理相关的内容。 这篇文章中,我们关注 Core Audio 中编解码能力,也就

记录学习的过程 1854

初代iPad音频接口缺失分析:从硬件限制到移动音频创作演变

在数字音频技术领域,音频接口是连接数字世界与模拟信号的关键桥梁,其核心原理是通过数模转换器(DAC)和模数转换器(ADC)实现信号的高保真转换。这项技术的价值在于为专业音频创作提供了低延迟、高质量的输入输出通道,广泛应用于音乐制作、播客录制等场景。然而,早期移动设备在追求集成化时往往牺牲了接口扩展性,初代iPad正是典型案例——它缺失了关键的音频线路输入输出功能,且缺乏通用USB端口,这直接限制了其作为移动音频工作站的潜力。尽管用户通过相机连接套件等方案尝试突破限制,但兼容性和稳定性问题凸显了封闭生态与专业

weixin_33709609的博客 264

macOS coreAudio 之 AudioQueue 播放本地音频文件

macOS CoreAudio 使用记录 之 使用AudioQueue配合AudioFile进行播放macOS 本地音频文件本文打仓库代码为:CoreAudio作为AppleAudioQueueAudioFileBufferAudioQueue。

goldWave的博客 1375

iOS平台网络PCM音频流播放实现详解

现在终于到了动手写播放器的时候了!为什么不直接用AVPlayer?因为它无法处理裸PCM流。我们必须借助更底层的框架,特别是其中的AudioQueue服务。[self.delegate playbackDidFailWithReason:@"无网络"];break;[self.delegate playbackDidFailWithReason:@"超时"];break;

weixin_29997223的博客 1268

Ubuntu 24.04.3 LTS 安装 OpenClaw 图文版

OpenClow安装指南

qq_17204557的博客 2552

Taskflow性能调优指南:TFProf可视化剖析工具从入门到精通

Taskflow 是一个 C++ 通用任务并行编程系统,而它内置的 **TFProf 可视化剖析工具**正是进行任务并行性能调优的核心利器:无需重新编译、无需安装任何外部工具,只需设置一个环境变量,就能捕获每一次任务执行,并在浏览器里以交互图形的方式看到全部细节。本文面向新手,带你用 5 个步骤掌握"生成追踪 → 可视化分析 → 定位瓶颈 → 完成调优"的完整流程。 ## 一、为什么需要 Tas

gitblog_01093的博客 829

433M遥控器无线解码1527协议

433M遥控器无线解码1527协议,定时器轮询法

weixin_58595117的博客 1万+

如何给SFBAudioEngine加自定义音效:播放器音频处理图完整指南

想让 App 播放的音乐带上教堂混响?不用重新编码文件,SFBAudioEngine 的播放器允许你把一个效果单元直接接进音频处理图的中间位置,之后的每一次播放都会经过你的自定义音效。本文先给出一段能跑的 10 行示例,再依次讲图结构、接线红线与切换采样率时的回调,带你给播放器装上音效。 ## 🎧 10 行代码给 SFBAudioEngine 加混响 先不聊原理。下面这段最小示例:创建一个系

gitblog_00705的博客 836

nRF52382低功耗蓝牙应用开发之入门教程-软件烧录

准备工作在上一篇文章讲过了,这里就讲讲nRFgo studio的使用 硬件准备: J-link:2线 供电:3.3V 如下图打开nRFgo studio界面介绍, 2、如果J-link正常,如下图 3、如果蓝牙芯片正常 4、烧录过程,如果蓝牙芯片之前有烧录过程序,需先点擦除,再点烧录 5、烧录代码路劲,例如 F:\nRF\SDK\nRF5_SDK_15.2.0_...

LinQiPing233的博客 1946

5分钟搞定阿里云千问大模型API申请:手把手教你获取API Key

本文提供了一份高效的阿里云通义千问大模型API Key获取实战指南。通过清晰的步骤,帮助开发者在五分钟内完成从账号准备、精准导航控制台到创建并验证API Key的全过程,并强调了密钥安全管理和基础调用测试的重要性,助力快速集成AI能力。

ol789012345的博客 1352

使用Core Audio实现VoIP通用音频模块

最近一直在做iOS音频技术相关的项目,由于单项直播SDK,互动直播SDK(iOS/Mac),短视频SDK,都会用到音频技术,因此在这里收集三个SDK的音频技术需求,开发一个通用的音频模块用于三个SDK,同时支持iOS和Mac。   想要阅读更多技术干货、行业洞察,欢迎关注网易云信博客。 了解网易云信,来自网易核心架构的通信与视频云服务。   需求实现 主要包括音频采集,音频格式转换,音...

netease_im的博客 625

EL6751实战指南:从协议解析到工业现场部署

本文详细介绍了EL6751网关在工业通信协议转换中的应用,从CANopen协议解析到EtherCAT系统部署的实战指南。通过协议栈位置、数据封装方式和通信模型差异的深入分析,帮助工程师高效实现EtherCAT主站与CANopen从站设备间的无缝对接,提升工业现场通信效率。

weixin_30338481的博客 547

EZAudioPlayer深度解析:iOS与macOS音频播放的终极解决方案

想要在iOS和macOS应用中实现专业级的音频播放功能吗?🎵 EZAudioPlayer框架为您提供了完整的音频文件播放与控制解决方案。作为EZAudio音频可视化框架的核心组件,EZAudioPlayer将EZAudioFile和EZOutput完美结合,让您轻松实现高质量、低延迟的音频播放体验。 ## 🎯 为什么选择EZAudioPlayer? ### 跨平台兼容性 EZAudioPl

gitblog_00751的博客 717

Qt Installer Framework4.6.1的最基础使用1(win10+离线安装包 )

因工作需要,win程序发布后,打包成一可执行exe文件,且兼容win7系统笔者记录了一些关键要点,供有需要的小伙伴使用。

aggs1990的专栏 904

我用 Python 监听了自己的键盘,生成击键热力图,看看每天到底在“摸鱼”还是“搬砖”?

数据采集(监听)、持久化(I/O)与可视化展示,必须是三个完全独立的模块,这符合单一职责原则。在多线程读写 Python 字典时,锁住的时间要尽可能短,防止 I/O 线程把高频的监听线程阻塞住。这类涉及底层的代码与“键盘记录木马”只有一线之隔。永远不要把击键记录通过网络上传,所有数据必须localhost消化。人脑对枯燥的 JSON 日志是不敏感的,但对颜色和空间极其敏感。这也是为什么 Prometheus + Grafana 是标配。使用ctypes。

BUG猿的博客 193

你真的懂Swift音频流处理吗?深入Core Audio与AVFoundation底层机制

掌握Swift音频处理核心技巧,深入解析Core Audio与AVFoundation底层机制,涵盖实时音频流处理、低延迟播放与录制等关键应用场景。揭示iOS音频开发难点与优化策略,提升开发效率与音质表现,值得收藏。

ProceChat的博客 1130

PyCharm 2024最新版图文安装教程(python环境搭建+PyCharm安装+运行测试+汉化+背景图设置)

PyCharm 2024新版图文安装教程(python环境搭建+PyCharm安装+运行测试+汉化+背景图设置)

Code_流苏:在代码中寻诗意,在实践中觅真知 4万+

iOS Audio Unit (一)

最近一直在做iOS音频相关技术的项目,期间在官方及网上的资料文档也学习了很多,当然,iOS平台中音频相关技术还是有很多方面的,这里我先总体概述下,然后以I/O Audio Unit为例对其概念,基本用法和思路进行讲解,可能不够全面,一些细节需要自行查找相关文档。后面我会对github上一个开源的音频引擎框架进行源码分析,来展现在更复杂的音频技术应用场景下可能的设计及实现方式。本文图片及大部分技术概念阐述均来自apple官网。

irainsa的博客 479

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

OR-Tools 路由解析器全景指南:TSPLIB、Solomon、CARPLIB、NEARPLIB 等标准问题格式的统一解析与解序列化

OR-Tools 的 `ortools/routing/parsers` 目录为经典车辆路径问题(VRP)及其变体提供了一整套标准文件格式解析器与解序列化工具,覆盖 TSP、CVRP、VRPTW、PDPTW、CARP、NEARP 与 MCND 等九类问题。阅读本文后,你将掌握各解析器的对应关系、文件格式要点、加载方式与输出约定,能够把学术界与工业界常用的基准测试数据直接接入 OR-Tools 的路

gitblog_00166的博客 369

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

用SFBAudioEngine构建iOS高保真音频播放器:SwiftUI完整开发指南

想要在iOS上打造一款支持FLAC、DSD、WavPack等高保真音频格式的播放器,却苦于系统原生框架支持有限?SFBAudioEngine正是解决这一痛点的终极利器。作为macOS、iOS和tvOS平台上的音频功能引擎,SFBAudioEngine集解码、播放、编码、格式转换、属性读取与元数据编辑于一身,让你用少量Swift代码就能构建出专业级的高保真音频播放器。本指南将从零开始,手把手带你完成

gitblog_00099的博客 435

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

EZAudio录音回放系统:构建完整的音频录制、播放与可视化应用

EZAudio是一个功能强大的iOS和macOS音频可视化框架,基于Core Audio构建,专为需要实时、低延迟音频处理和可视化的开发者设计。这个开源项目提供了一套完整的音频组件,让你能够快速构建专业的录音、播放和可视化应用。🚀 ## 为什么选择EZAudio框架? EZAudio框架通过简化的API封装了复杂的Core Audio底层操作,让开发者能够专注于应用逻辑而不是音频技术细节。该

gitblog_00805的博客 550

音频队列服务(Audio Queue Services)

介绍     本文档介绍了如何使用音频队列服务(Audio Queue Services),这是Core Audio Toolbox框架中的一个C语言编程接口。 什么是音频队列服务(Audio Queue Services)     在iOS和Mac OS X中,音频队列服务提供了一种直接、低开销的的方式来录制和播放音频。这也是向你的iOS和Mac OS X程序中添加

david21984的专栏 1580

SFBAudioEngine是什么?macOS/iOS/tvOS音频开发的全能引擎完整指南(解码、播放、编码、转换、元数据5大能力)

SFBAudioEngine 是一个面向 macOS、iOS 和 tvOS 的开源音频开发引擎,被官方称为"音频功能的强者"(a powerhouse of audio functionality)。无论你是想为 App 加入本地音乐播放、实现音频格式转换,还是批量编辑歌曲元数据,这个 Swift/Objective-C 双语言支持的全能引擎都能用极简代码帮你搞定。本文将用一份完整指南,带你了解它

gitblog_00384的博客 1135

突破Core Audio复杂性:Novocaine让iOS/OS X音频处理效率提升10倍的实战指南

你是否还在为Core Audio的底层复杂性而头疼?是否因Audio Unit配置耗费数周却收效甚微?本文将带你掌握Novocaine这个高性能音频处理库,用20%的代码实现80%的音频功能,彻底解决iOS与macOS平台的音频开发痛点。 读完本文你将获得: - 3分钟快速集成Novocaine的完整流程 - 5个核心API的实战应用模板(含完整代码) - 7种音频处理场景的最佳实践(录音/播放...

gitblog_00041的博客 440
上一篇: Flutter 环境搭建 (Android)
下一篇: Flutter - 概览
忘川三
博客等级 码龄12年 208粉丝 45原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值