在iOS应用程序中,

播放来自BLE的音频流,使用TPCircularBufferAudio Unit.

声音播放良好,但当缓冲区为空且没有字节可播放时,它会导致实例中的爆裂声.

这是我的音频流配置,

  AudioStreamBasicDescription audioFormat;
        audioFormat.mSampleRate         = 8000.00;
        audioFormat.mFormatID           = kAudioFormatLinearPCM;
        audioFormat.mFormatFlags        = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsPacked;
        // || kAudioFormatFlagsNativeFloatPacked || kAudioFormatFlagIsSignedInteger
        //kAudioFormatFlagIsSignedInteger
        audioFormat.mFramesPerPacket    = 1;
        audioFormat.mChannelsPerFrame   = 1;
        audioFormat.mBitsPerChannel     = 32;// Update when required
        audioFormat.mBytesPerPacket     = 4; // Update when required
        audioFormat.mBytesPerFrame      = 4; // Update when required
    

Below is a Playback function of Audio Unit

static OSStatus playbackCallback(void *inRefCon, 
                                 AudioUnitRenderActionFlags *ioActionFlags, 
                             const AudioTimeStamp *inTimeStamp, 
                             UInt32 inBusNumber, 
                             UInt32 inNumberFrames, 
                             AudioBufferList *ioData) {

//1
for (int i=0; i < ioData->mNumberBuffers; i++) { 

    IosAudioController *THIS = (__bridge IosAudioController *)inRefCon;

    int bytesAskingByPlayback = ioData->mBuffers[i].mDataByteSize;

    SInt16 *targetBuffer = (SInt16*)ioData->mBuffers[i].mData;

    // Pull audio from playthrough buffer
    int32_t availableBytesFromBuffer;

    SInt16 *sBuffer = TPCircularBufferTail(iosAudio.addressOfTPBuffer, &availableBytesFromBuffer);
    
    int willRemainBytes = availableBytesFromBuffer - bytesAskingByPlayback;

    if (willRemainBytes > 0) {
        memcpy(targetBuffer, sBuffer, bytesAskingByPlayback);
        TPCircularBufferConsume(iosAudio.addressOfTPBuffer,bytesAskingByPlayback);
        } else {


        //Note: Mostly need to update code here

                memcpy(targetBuffer, sBuffer, availableBytesFromBuffer);

            TPCircularBufferConsume(iosAudio.addressOfTPBuffer, availableBytesFromBuffer);

    }
}
    return noErr;
}

缓冲区大小为16384

一些解决方案说我会用0填充目标缓冲区以静音,但它不工作.

Some Solution说我可以用以前的值填充目标缓冲区来填补空白.

推荐答案

但是当缓冲区是空的并且没有字节播放时.

你需要从这里开始,并确定为什么会发生这种情况.理想情况下,这种情况永远不会发生.如果真的发生了,它应该是罕见的,并且有明确的原因.如果upstream 已经暂停,那么您需要暂停下游.如果存在网络延迟,则需要增加缓冲区的大小.

任何时候你注入一个常数值,无论是零还是最后一个值,你都会注入高频噪声.这听起来像是一个小爆裂声.如果你经常这样做,它会爆裂.有一些技术可以解决这个问题,但它们相当复杂,在解决潜在的欠载问题之前,你不应该go 解决.

如果你的upstream 有可变的延迟,那么你需要缓冲一个比特(10ms,50ms,2000ms,这取决于它的可变程度),然后你启动你的下游.如果缓冲区耗尽,则需要暂停下游,直到可以再次建立缓冲区.

有时候,偶尔的"弹出"是值得的,以避免暂停下游,这就是当"填充零"或"填充最后一个值"这样的建议出现的时候.但是,如果你一秒钟收到多次爆裂声(这就是"爆裂声"通常的意思),这可能意味着你在启动下游之前没有足够的缓冲.

根据音频的性质,您还需要考虑在您的upstream 有一个长时间的暂停,然后您突然得到了很多过go 的数据的情况.在这一点上,你必须决定是保留它并增加延迟,还是放弃它以接近"实时"."这方面的策略完全取决于您的用例,并且是设计任何实时系统的主要部分.

请注意TPCircularBuffer和Audio Unit都是非常低级的工具,因此需要大量的工作.就我个人而言,我更喜欢用一个更高级的工具来构建这类系统,比如AVSampleBuffer.它仍然具有挑战性,您需要了解实时系统,但AVFoundation将为您做更多的工作.(我个人经常需要担心暂停、倒带、跳过等问题,所以我的问题可能与你的问题完全不同,所以这个建议不适用.

Ios相关问答推荐

Fastlane,iOS Provisioning Profile推送通知权限(通过示例)&

SwiftUI@Observable不跟踪父类属性中的更改以更新视图

有没有办法观察滚动的SwiftUI图表的内容偏移量?

如何通过S定义的对象设置删除基于块的观察者的测试?

Xcode -Google Mobile Ads SDK在没有AppMeasurement的情况下初始化

SWIFT-仅解码感兴趣的元素

flutter中实现自定义底部导航栏

在 React Native iOS 中加载多个 JS 包

为什么这个 init 被分派到一个内部扩展 init?

Swift Combine:prepend() before share() 阻止初始接收器执行

如何擦除 Swift 中的 Core Graphics Context 绘图?

使用 AVCaptureDeviceTypeBuiltInTripleCamera 时 Select 合适的相机进行条码扫描

卡在 Apple 的 SwiftUI 教程第 5 章(更新应用数据)

如何在 SwiftUI 的 TabView 中添加底部曲线?

我可以在 Apple-Watch 上使用 iPhone 的摄像头扫描 SwiftUi 中的二维码(例如用于登录)吗

搜索在 ios xamarin.forms 中不起作用

文件是为存档而构建的,它不是被链接的体系 struct (i386)

如何在 iOS 中获取正在运行的应用程序的名称

如何为 UILabel 的背景 colored颜色 设置动画?

类 AMSupportURLConnectionDelegate 在两者中都实现