草庐IT

ios - 如何在 iOS 上播放现场音频?

coder 2024-01-23 原文

我有一个 IPCamera,需要使用自定义库进行连接和通信。我已经处理好所有视频,但我还想让用户可以选择收听相机录制的音频。

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

由于我没有从文件中读取数据或没有可以连接到的 URL,我想我必须使用 AudioUnitsopenAL 之类的东西来播放我的音频。

我尝试根据我在网上找到的示例使用 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 上播放字节流中的音频?

最佳答案

我决定以全新的眼光看待这个项目。我摆脱了大部分代码,现在可以使用了。它不是很漂亮,但至少它现在可以运行。例如:我必须将我的采样率设置为 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 找到了其中的大部分内容

关于ios - 如何在 iOS 上播放现场音频?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34023843/

有关ios - 如何在 iOS 上播放现场音频?的更多相关文章

  1. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  5. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  6. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  7. ruby - 如何在 Lion 上安装 Xcode 4.6,需要用 RVM 升级 ruby - 2

    我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121

  8. ruby-on-rails - 如何在 ruby​​ 交互式 shell 中有多行? - 2

    这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式ruby​​shell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f

  9. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  10. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

随机推荐