如何在iOS上播放实时音频?

时间:2015-12-01 15:22:31

标签: ios audio core-audio audiounit openal

我有一个IPCamera,需要使用自定义库进行连接和通信。我有视频全部照顾,但我也想让用户选择收听相机录制的音频。

我以字节流的形式接收音频(音频 PCM u-law )。

由于我没有从文件中读取数据或者有我可以连接的网址,我想我必须使用 AudioUnits openAL 之类的内容播放我的音频。

我尝试使用AudioUnits根据我在网上找到的示例实现它,这是我到目前为止所拥有的:

-(void) audioThread
{
    char buffer[1024];
    int size = 0;
    boolean audioConfigured = false;
    AudioComponentInstance audioUnit;

    while (running) {
        getAudioData(buffer,size);    //fill buffer with my audio

        int16_t* tempChar = (int16_t *)calloc(ret, sizeof(int16_t));
        for (int i = 0; i < ret; i++) {
            tempChar[i] = MuLaw_Decode(buf[i]);
        }

        uint8_t *data = NULL;
        data = malloc(size);
        data = memcpy(data, &tempChar, size);

        CMBlockBufferRef blockBuffer = NULL;
        OSStatus status = CMBlockBufferCreateWithMemoryBlock(NULL, data, 
                                                    size,  
                                                    kCFAllocatorNull, NULL,
                                                    0,    
                                                    size,  
                                                    0, &blockBuffer);

         CMSampleBufferRef sampleBuffer = NULL;
        // now I create my samplebuffer from the block buffer
        if(status == noErr)
        {
            const size_t sampleSize = size;
            status = CMSampleBufferCreate(kCFAllocatorDefault,
                                          blockBuffer, true, NULL, NULL,
                                          formatDesc, 1, 0, NULL, 1,
                                          &sampleSize, &sampleBuffer);
        }

        AudioStreamBasicDescription audioBasic;
        audioBasic.mBitsPerChannel = 16;
        audioBasic.mBytesPerPacket = 2;
        audioBasic.mBytesPerFrame = 2;
        audioBasic.mChannelsPerFrame = 1;
        audioBasic.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
        audioBasic.mFormatID = kAudioFormatLinearPCM;
        audioBasic.mFramesPerPacket = 1;
        audioBasic.mSampleRate = 48000;
        audioBasic.mReserved = 0;

        if(!audioConfigured)
        {
            //initialize the circular buffer
            if(instance.decodingBuffer == NULL)
                instance.decodingBuffer = malloc(sizeof(TPCircularBuffer));
            if(!TPCircularBufferInit(instance.decodingBuffer, 1024))
                continue;

            AudioComponentDescription componentDescription;
            componentDescription.componentType = kAudioUnitType_Output;
            componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
            componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
            componentDescription.componentFlags = 0;
            componentDescription.componentFlagsMask = 0;

            AudioComponent component = AudioComponentFindNext(NULL, &componentDescription);
            if(AudioComponentInstanceNew(component, &audioUnit) != noErr) {
                NSLog(@"Failed to initialize the AudioComponent");
                continue;
            }

            //enable IO for playback
            UInt32 flag = 1;
            if(AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &flag, sizeof(flag)) != noErr) {
                NSLog(@"Failed to enable IO for playback");
                continue;
            }

            // set the format for the outputstream
            if(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat,
                                    kAudioUnitScope_Output, 1, &audioBasic, sizeof(audioBasic)) != noErr) {
                NSLog(@"Failed to set the format for the outputstream");
                continue;
            }

            // set output callback
            AURenderCallbackStruct callbackStruct;
            callbackStruct.inputProc = playbackCallback;
            callbackStruct.inputProcRefCon = (__bridge void*) self;
            if(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &callbackStruct, sizeof(callbackStruct))!= noErr) {
                NSLog(@"Failed to Set output callback");
                continue;
            }

            // Disable buffer allocation for the recorder (optional - do this if we want to pass in our own)
            flag = 0;
            status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ShouldAllocateBuffer, kAudioUnitScope_Output, 1, &flag, sizeof(flag));

            if(AudioUnitInitialize(audioUnit) != noErr) {
                NSLog(@"Failed to initialize audioUnits");
            }

            if(AudioOutputUnitStart(audioUnit)!= noErr) {
                NSLog(@"[thread_ReceiveAudio] Failed to start audio");
            }
            audioConfigured = true;
        }

        AudioBufferList bufferList ;
       if (sampleBuffer!=NULL) {
            CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &bufferList, sizeof(bufferList), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
            UInt64 size = CMSampleBufferGetTotalSampleSize(sampleBuffer);

            // Put audio into circular buffer
            TPCircularBufferProduceBytes(self.decodingBuffer, bufferList.mBuffers[0].mData, size);
            //TPCircularBufferCopyAudioBufferList(self.decodingBuffer, &bufferList, NULL, kTPCircularBufferCopyAll, NULL);
            CFRelease(sampleBuffer);
            CFRelease(blockBuffer);
        }
    }

    //stop playing audio
    if(audioConfigured){
        if(AudioOutputUnitStop(audioUnit)!= noErr) {
            NSLog(@"[thread_ReceiveAudio] Failed to stop audio");
        }
        else{
            //clean up audio
            AudioComponentInstanceDispose(audioUnit);
        }
    }
}

int16_t MuLaw_Decode(int8_t number)
{
    const uint16_t MULAW_BIAS = 33;
    uint8_t sign = 0, position = 0;
    int16_t decoded = 0;
    number = ~number;
    if (number & 0x80)
    {
        number &= ~(1 << 7);
        sign = -1;
    }
    position = ((number & 0xF0) >> 4) + 5;
    decoded = ((1 << position) | ((number & 0x0F) << (position - 4))
               | (1 << (position - 5))) - MULAW_BIAS;
    return (sign == 0) ? (decoded) : (-(decoded));
}

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

    int bytesToCopy = ioData->mBuffers[0].mDataByteSize;
    SInt16 *targetBuffer = (SInt16*)ioData->mBuffers[0].mData;

    int32_t availableBytes;
    SInt16 *buffer = TPCircularBufferTail(instance.decodingBuffer, &availableBytes);
    int sampleCount = MIN(bytesToCopy, availableBytes);
    memcpy(targetBuffer, buffer, MIN(bytesToCopy, availableBytes));
    TPCircularBufferConsume(self.decodingBuffer, sampleCount);

    return noErr;
}

上面的代码不会产生任何错误,但不会播放任何声音。我虽然可以通过recordCallback中的bufferList设置音频,但它永远不会被调用。

所以我的问题是:如何从iOS上的字节流播放音频?

2 个答案:

答案 0 :(得分:1)

我决定以新的眼光看待这个项目。我摆脱了大部分代码并立即开始工作。它不漂亮,但至少它现在运行。例如:我必须将我的采样率设置为4000,否则它会很快发挥,我仍然会遇到性能问题。无论如何,这就是我提出的:

#define BUFFER_SIZE 1024
#define NUM_CHANNELS 2
#define kOutputBus 0
#define kInputBus 1

-(void) main
{
    char buf[BUFFER_SIZE];
    int size;

runloop: while (self.running) {
        getAudioData(&buf, size);

        if(!self.configured) {

            if(![self activateAudioSession])
                continue;

            self.configured = true;
        }

        TPCircularBufferProduceBytes(self.decodingBuffer, buf, size);
    }
    //stop audiounits
    AudioOutputUnitStop(self.audioUnit);
    AudioComponentInstanceDispose(self.audioUnit);
    if (self.decodingBuffer != NULL) {
        TPCircularBufferCleanup(self.decodingBuffer);
    }
}

static void audioSessionInterruptionCallback(void *inUserData, UInt32 interruptionState) {
    if (interruptionState == kAudioSessionEndInterruption) {
        AudioSessionSetActive(YES);
        AudioOutputUnitStart(self.audioUnit);
    }

    if (interruptionState == kAudioSessionBeginInterruption) {
        AudioOutputUnitStop(self.audioUnit);
    }
}

static OSStatus playbackCallback(void *inRefCon,
                                 AudioUnitRenderActionFlags *ioActionFlags,
                                 const AudioTimeStamp *inTimeStamp,
                                 UInt32 inBusNumber,
                                 UInt32 inNumberFrames,
                                 AudioBufferList *ioData) {
    // Notes: ioData contains buffers (may be more than one!)
    // Fill them up as much as you can. Remember to set the size value in each buffer to match how much data is in the buffer.
    if (!self.running ) {

        return -1;
    }

    int bytesToCopy = ioData->mBuffers[0].mDataByteSize;
    SInt16 *targetBuffer = (SInt16*)ioData->mBuffers[0].mData;

    // Pull audio from playthrough buffer
    int32_t availableBytes;
    if(self.decodingBuffer == NULL || self.decodingBuffer->length < 1) {
        NSLog(@"buffer is empty");
        return 0;
    }
    SInt16 *buffer = TPCircularBufferTail(self.decodingBuffer, &availableBytes);
    int sampleCount = MIN(bytesToCopy, availableBytes);
    memcpy(targetBuffer, buffer, sampleCount);
    TPCircularBufferConsume(self.decodingBuffer, sampleCount);

    return noErr;
}

- (BOOL) activateAudioSession {

    if (!self.activated_) {

        OSStatus result;

        result = AudioSessionInitialize(NULL,
                                        NULL,
                                        audioSessionInterruptionCallback,
                                        (__bridge void *)(self));
        if (kAudioSessionAlreadyInitialized != result)
            [self checkError:result message:@"Couldn't initialize audio session"];

        [self setupAudio]
        self.activated_ = YES;

    }
    return self.activated_;
}

- (void) setupAudio
{

    OSStatus status;

    // Describe audio component
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;

    // Get component
    AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);

    // Get audio units
    AudioComponentInstanceNew(inputComponent, &_audioUnit);

    //    // Enable IO for recording
    //    UInt32 flag = 1;
    //    status = AudioUnitSetProperty(audioUnit,
    //                                  kAudioOutputUnitProperty_EnableIO,
    //                                  kAudioUnitScope_Input,
    //                                  kInputBus,
    //                                  &flag,
    //                                  sizeof(flag));


    // Enable IO for playback
    UInt32 flag = 1;
    AudioUnitSetProperty(_audioUnit,
                                  kAudioOutputUnitProperty_EnableIO,
                                  kAudioUnitScope_Output,
                                  kOutputBus,
                                  &flag,
                                  sizeof(flag));

    // Describe format
    AudioStreamBasicDescription format;
    format.mSampleRate       = 4000;
    format.mFormatID         = kAudioFormatULaw; //kAudioFormatULaw
    format.mFormatFlags      = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;//
    format.mBitsPerChannel   = 8 * sizeof(char);
    format.mChannelsPerFrame = NUM_CHANNELS;
    format.mBytesPerFrame    = sizeof(char) * NUM_CHANNELS;
    format.mFramesPerPacket  = 1;
    format.mBytesPerPacket   = format.mBytesPerFrame * format.mFramesPerPacket;
    format.mReserved         = 0;
    self.audioFormat = format;

    // Apply format
    AudioUnitSetProperty(_audioUnit,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Output,
                                  kInputBus,
                                  &_audioFormat,
                                  sizeof(_audioFormat));

    AudioUnitSetProperty(_audioUnit,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Input,
                                  kOutputBus,
                                  &_audioFormat,
                                  sizeof(_audioFormat));

    //    // Set input callback
    //    AURenderCallbackStruct callbackStruct;
    //    callbackStruct.inputProc = recordingCallback;
    //    callbackStruct.inputProcRefCon = self;
    //    status = AudioUnitSetProperty(audioUnit,
    //                                  kAudioOutputUnitProperty_SetInputCallback,
    //                                  kAudioUnitScope_Global,
    //                                  kInputBus,
    //                                  &callbackStruct,
    //                                  sizeof(callbackStruct));
    //    checkStatus(status);

    // Set output callback
    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = playbackCallback;
    callbackStruct.inputProcRefCon = (__bridge void * _Nullable)(self);
    AudioUnitSetProperty(_audioUnit,
                                  kAudioUnitProperty_SetRenderCallback,
                                  kAudioUnitScope_Global,
                                  kOutputBus,
                                  &callbackStruct,
                                  sizeof(callbackStruct));

    // Disable buffer allocation for the recorder (optional - do this if we want to pass in our own)
    flag = 0;
    status = AudioUnitSetProperty(_audioUnit,
                                  kAudioUnitProperty_ShouldAllocateBuffer,
                                  kAudioUnitScope_Output,
                                  kInputBus,
                                  &flag,
                                  sizeof(flag));

    //initialize the circular buffer
    if(self.decodingBuffer == NULL)
        self.decodingBuffer = malloc(sizeof(TPCircularBuffer));

        if(!TPCircularBufferInit(self.decodingBuffer, 512*1024))
            return NO;

    // Initialise
    status = AudioUnitInitialize(self.audioUnit);

    AudioOutputUnitStart(self.audioUnit);
}

我通过查看github并从a tasty pixel

查找了大部分内容

答案 1 :(得分:0)

如果AVAudioSession配置为使用短缓冲区,则可以使用RemoteIO音频单元以较低的额外延迟播放接收到的音频。

在音频配置期间检查错误。某些iOS设备仅支持48 kHz采样率,因此您可能需要将音频PCM数据从8 kHz重新采样到另一种速率。

RemoteIO仅支持线性PCM,因此您需要先将所有传入的8位u-law PCM样本转换为16位线性PCM格式,然后再将其存储在无锁循环缓冲区中。

您需要调用AudioOutputUnitStart来启动操作系统调用的音频回调。您的代码不应该调用这些回调。它们将由操作系统调用。

AudioUnitRender用于录制回调,而不是用于播放音频。所以你不需要使用它。只需在播放回调中使用请求的帧数填充AudioBufferList缓冲区。

然后,您可以使用播放音频回调来检查循环缓冲区并提取所需数量的样本(如果有足够的可用)。您不应该在此回调中进行任何内存管理(例如free()调用)。