【问题标题】:iOS UI are causing a glitch in my audio streamiOS UI 导致我的音频流出现故障
【发布时间】:2013-01-04 17:39:54
【问题描述】:

我正在为 iPhone 编写一个基于 VOIP 的应用程序。我遇到了一个奇怪的问题,当用户按下屏幕时,音频出现故障,当您按下手机本身的音量增大/减小按钮时也会发生这种情况。经过几天的调试,我发现它与我的循环缓冲区有关。我在这里换了一个:

http://atastypixel.com/blog/a-simple-fast-circular-buffer-implementation-for-audio-processing/

这不会导致故障,但延迟比我的要长近 4 倍,我必须有最小的延迟并且无法弄清楚我的应用程序发生了什么。

设置:

我跟着:http://www.stefanpopp.de/2011/capture-iphone-microphone/ 有点制作基本应用程序,但我有不同的设置/功能等。我有一个视图控制器,它有这个 audioProcessor 类的属性,这个类有一个用于循环缓冲区的变量。在录制回调中我发送数据,这一切都很好。在 CFSocket 回调中,我将来自网络的数据添加到此缓冲区,然后播放回调从此缓冲区中提取数据并将其传递给系统。

在播放过程中的某个时刻,如果用户按下会触发 UI 事件,这一切都会变得很糟糕,并且会出现这个奇怪的数据。我猜这是某种线程问题,但我在这方面几乎没有经验。我会很感激任何帮助。下面是相关代码:

网络回调 - 将数据添加到缓冲区:

static void addDataToBuffer(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
    AudioUnitCBufferProduce(&audioProcessor->auCBuffer, (uint8_t*)[(__bridge NSData *)data bytes], [(__bridge NSData *)data length]);
}

音频单元播放 - 从缓冲区复制数据并放入指向 ioData 的“targetBuffer”:

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

    uint8_t *targetBuffer = (uint8_t*)ioData->mBuffers[0].mData;
    AudioUnitCBufferConsume(&audioProcessor->auCBuffer, targetBuffer, inNumberFrames);

    return noErr;
}

缓冲区初始化:

void AudioUnitCBufferInit(AudioUnitCBuffer *b)
{
  // create array of bytes of length specified, fill with silence
  uint8_t buffer[2048];

  for(int i = 0; i < 2048; i++)
  {
      buffer[i] = 0xd5;
  }

  // init buffer elements
  b->buffer = buffer;
  b->consumer = buffer;
  b->producer = buffer;
  b->length = 2048;
}

缓冲区生产者/消费者:

这样写是为了传递一个指向函数的指针,然后用数据填充这个指针,如果没有数据,指针将用 ALAW 十六进制值填充以保持静音。这使音频单元代码保持较小,因为缓冲区确保它始终向其提供数据。这也比复制到某个临时位置然后 memcpy 将其放入上面链接使用的缓冲区中要快得多,而且对于我的需要来说速度很慢。

inline static void AudioUnitCBufferProduce(AudioUnitCBuffer *b, uint8_t *bytes, int32_t len)
{   
//printf("\n\ninside producer: len %i \n\n", len);
while(len--)
{
    // if producer catches up with consumer, skip a byte
    if (b->producer+1 == b->consumer)
    {
        //printf("b->producer+1 == b->consumer == continue \n");
        continue;
    }
    else
    {
        //printf("b->producer+1 != b->consumer == add byte \n");
        *b->producer = *bytes++;
        b->producer++;

        if(b->producer == &b->buffer[b->length-1])
        {
            //printf("\n\nproducer == end, skipping \n\n");
            b->producer = b->buffer;
        }
    }
}
}

inline static void AudioUnitCBufferConsume(AudioUnitCBuffer *b, uint8_t *bytes, int32_t len)
{
while(len--)
{
    // if producer catches up with consumer, skip a byte
    if (b->consumer == b->producer)
    {
        *bytes++ = 0xd5;
    }
    else
    {
        *bytes++ = *b->consumer;
        b->consumer++;

        if(b->consumer == &b->buffer[b->length-1])
        {
            b->consumer = b->buffer;
        }
    }
}
}

【问题讨论】:

  • 您的套接字回调安排在哪个线程上?试试后台线程。
  • 使用 GCDAsync 我让它在后台线程上运行,没有任何变化。使用 cf 它在 main 上运行。如果我在链接中使用循环缓冲区,这意味着它不是网络,它工作正常。只是那个缓冲区有将近 4 倍的延迟
  • 你是如何确定你的循环缓冲区大小的?它是否大于 N 个传入数据包(对于某些 N),并且已预先填充以允许到达时间抖动?您如何处理接收音频采样率与发送音频采样率或网络数据率略有不同?更快或更慢,因为音频采样率时钟不同步?
  • 这个尺寸是我测试看到的问题。它曾经更小,允许 10 个数据包。发送和接收同步到 8k ALAW 流。系统需要尽可能实时。所以一旦有足够的数据进入播放,如果没有可用的数据,返回 0xd5 的例程会通过确保始终重新调整数据来进行补偿
  • @void 抱歉,不,这是我为一家公司编写的专有代码。我得到了发布这个的许可,因为它并没有真正泄露任何东西。你首先需要找出问题所在。您需要花费大量时间来进行一些测试。使用wireshark / 控制台打印输出验证正在发送/接收的数据量。验证所有这些数据是否有机会输入音频单元,确保循环缓冲区不必经常丢弃数据包/返回静音。修复它的方法会因实际问题而有很大差异

标签: objective-c multithreading core-audio circular-buffer


【解决方案1】:

Ok 编写了一种不同风格的 Circular 缓冲区,它似乎已经成功了,延迟非常相似并且没有故障。我仍然不完全明白为什么这会更好,请有这方面经验的人分享。

由于苹果发布的这些东西很少,下面是我的循环缓冲区实现,它适用于我的 VOIP 设置,请随意使用它,欢迎任何建议,如果它不要来找我不适合你。这次它是一个objective-c 类。

请注意,这是为 ALAW 格式而非线性 PCM 设计的,“0xd5”是 ALAW 中的静音字节,不确定 PCM 中的内容,但预计会是噪音。

CircularBuffer.h:

//
//  CircularBuffer.h
//  clevercall
//
//  Created by Simon Mcloughlin on 10/1/2013.
//
//

#import <Foundation/Foundation.h>

@interface CircularBuffer : NSObject

-(int) availableBytes;
-(id) initWithLength:(int)length;
-(void) produceToBuffer:(const void*)data ofLength:(int)length;
-(void) consumeBytesTo:(void *)buf OfLength:(int)length;

@end

CircularBuffer.m:

//
//  CircularBuffer.m
//  clevercall
//
//  Created by Simon Mcloughlin on 10/1/2013.
//
//

#import "CircularBuffer.h"

@implementation CircularBuffer
{
    unsigned int gBufferLength;
    unsigned int gAvailableBytes;
    unsigned int gHead;
    unsigned int gTail;
    void *gBuffer;
}

// Init instance with a certain length and alloc the space
-(id)initWithLength:(int)length
{
    self = [super init];

    if (self != nil)
    {
        gBufferLength = length;
        gBuffer = malloc(length);
        memset(gBuffer, 0xd5, length);

        gAvailableBytes = 0;
        gHead = 0;
        gTail = 0;
    }

    return self;
}

// return the number of bytes stored in the buffer
-(int) availableBytes
{
    return gAvailableBytes;
}

-(void) produceToBuffer:(const void*)data ofLength:(int)length
{
    // if the number of bytes to add to the buffer will go past the end.
    // copy enough to fill to the end
    // go back to the start
    // fill the remaining
    if((gHead + length) > gBufferLength-1)
    {
        int remainder = ((gBufferLength-1) - gHead);
        memcpy(gBuffer + gHead, data, remainder);
        gHead = 0;
        memcpy(gBuffer + gHead, data + remainder, (length - remainder));
        gHead += (length - remainder);
        gAvailableBytes += length;
    }
    // if there is room in the buffer for these bytes add them
    else if((gAvailableBytes + length) <= gBufferLength-1)
    {
        memcpy(gBuffer + gHead, data, length);
        gAvailableBytes += length;
        gHead += length;
    }
    else
    {
        //NSLog(@"--- Discarded ---");
    }
}

-(void) consumeBytesTo:(void *)buf OfLength:(int)length
{
    // if the tail is at a point where there is not enough between it and the end to fill the buffer.
    // copy out whats left
    // move back to the start
    // copy out the rest
    if((gTail + length) > gBufferLength-1 && length <= gAvailableBytes)
    {
        int remainder = ((gBufferLength-1) - gTail);
        memcpy(buf, gBuffer + gTail, remainder);
        gTail = 0;
        memcpy(buf + remainder, gBuffer, (length -remainder));
        gAvailableBytes-=length;
        gTail += (length -remainder);
    }
    // if there is enough bytes in the buffer
    else if(length <= gAvailableBytes)
    {
        memcpy(buf, gBuffer + gTail, length);
        gAvailableBytes-=length;
        gTail+=length;
    }
    // else play silence
    else
    {
        memset(buf, 0xd5, length);
    }
}

@end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-25
    • 1970-01-01
    • 2013-03-14
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    相关资源
    最近更新 更多