Воспроизведение звука из массива только один раз без зацикливания

#ios #core-audio #audiounit

#iOS #core-audio #audiounit

Вопрос:

Я полностью новичок, когда дело доходит до аудиопрограммирования, и прямо сейчас я играю с AudioUnit. Я следую http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html и я перенес код для работы с iOS7. Проблема в том, что я хочу, чтобы он воспроизводил сгенерированную синусоидальную волну только один раз, а не продолжал воспроизводить звуковую волну. Я не уверен, как это сделать.

Генерация звуковых сэмплов:

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

    {
        // Fixed amplitude is good enough for our purposes
        const double amplitude = 0.25;

        // Get the tone parameters out of the view controller
        ToneGeneratorViewController *viewController =
            (ToneGeneratorViewController *)inRefCon;
        double theta = viewController->theta;
        double theta_increment =
            2.0 * M_PI * viewController->frequency / viewController->sampleRate;

        // This is a mono tone generator so we only need the first buffer
        const int channel = 0;
        Float32 *buffer = (Float32 *)ioData->mBuffers[channel].mData;

        // Generate the samples
        for (UInt32 frame = 0; frame < inNumberFrames; frame  ) 
        {
            buffer[frame] = sin(theta) * amplitude;

            theta  = theta_increment;
            if (theta > 2.0 * M_PI)
            {
                theta -= 2.0 * M_PI;
            }
        }

        // Store the updated theta back in the view controller
        viewController->theta = theta;

        return noErr;
    }
 

Создание AudioUnit:

 // Configure the search parameters to find the default playback output unit
// (called the kAudioUnitSubType_RemoteIO on iOS but
// kAudioUnitSubType_DefaultOutput on Mac OS X)
AudioComponentDescription defaultOutputDescription;
defaultOutputDescription.componentType = kAudioUnitType_Output;
defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO;
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
defaultOutputDescription.componentFlags = 0;
defaultOutputDescription.componentFlagsMask = 0;

// Get the default playback output unit
AudioComponent defaultOutput = AudioComponentFindNext(NULL, amp;defaultOutputDescription);
NSAssert(defaultOutput, @"Can't find default output");

// Create a new unit based on this that we'll use for output
OSErr err = AudioComponentInstanceNew(defaultOutput, amp;toneUnit);
NSAssert1(toneUnit, @"Error creating unit: %ld", err);

// Set our tone rendering function on the unit
AURenderCallbackStruct input;
input.inputProc = RenderTone;
input.inputProcRefCon = self;
err = AudioUnitSetProperty(toneUnit, 
    kAudioUnitProperty_SetRenderCallback, 
    kAudioUnitScope_Input,
    0, 
    amp;input, 
    sizeof(input));
NSAssert1(err == noErr, @"Error setting callback: %ld", err);

// Set the format to 32 bit, single channel, floating point, linear PCM
const int four_bytes_per_float = 4;
const int eight_bits_per_byte = 8;
AudioStreamBasicDescription streamFormat;
streamFormat.mSampleRate = sampleRate;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags =
    kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
streamFormat.mBytesPerPacket = four_bytes_per_float;
streamFormat.mFramesPerPacket = 1;    
streamFormat.mBytesPerFrame = four_bytes_per_float;        
streamFormat.mChannelsPerFrame = 1;    
streamFormat.mBitsPerChannel = four_bytes_per_float * eight_bits_per_byte;
err = AudioUnitSetProperty (toneUnit,
    kAudioUnitProperty_StreamFormat,
    kAudioUnitScope_Input,
    0,
    amp;streamFormat,
    sizeof(AudioStreamBasicDescription));
NSAssert1(err == noErr, @"Error setting stream format: %ld", err);
 

Спасибо!

Ответ №1:

Проблема в том, что я хочу, чтобы он воспроизводил сгенерированную синусоидальную волну только один раз

Что вам следует сделать, так это остановить аудиоустройство через определенное время.

Вы могли бы, например, установить NSTimer , когда вы звоните AudioOutputUnitStart , а затем, когда срабатывает таймер, вы звоните AudioOutputUnitStop (фактически, код удаления вашего аудиоустройства). Еще проще, вы можете использовать performSelector:withObject:afterDelay: и вызвать метод удаления аудиоустройства.

Надеюсь, это поможет.

Комментарии:

1. Спасибо, Серджио! Итак, у меня нет возможности передать ему массив с плавающей точкой и сказать, чтобы он остановился после этого? Лучший способ — рассчитать количество времени, которое должно пройти, а затем вызвать AudioOutputUnitStop по истечении этого времени?

2. ну, вы могли бы изменить свой RenderTone обратный вызов так, чтобы он просто создавал желаемое количество пакетов (мне кажется, что теперь он создает бесконечную синусоидальную волну), а затем предоставляет аудиоустройству буфер, заполненный НУЛЕМ: вы услышите сгенерированную синусоидальную волну один раз и ничего больше после этого, но звук будетвсе еще включен, процессор и т. Д. Я думаю, что это намного чище (и проще), если вы рассчитаете, как долго должна длиться ваша синусоидальная волна, а затем остановите звук. Приятным дополнением было бы также отключение звука вместо его внезапной остановки…

3. Ах .. так у меня была бы какая-то переменная для отслеживания, если RenderTone она была вызвана ранее? Если это так, то я бы сделал mBuffers null дополненным? Поправьте меня, если я здесь ошибаюсь, но причина, по которой он повторяется снова и снова, заключается в том, что я регистрируюсь RenderTone как обратный вызов, и он просто вызывает себя повторно? Большое спасибо @sergio!

4. как вы говорите, RenderTone вызывается повторно, как того требует аудиоустройство. Это не тот случай, когда один вызов соответствует одной полной синусоидальной волне — абсолютно нет. Действительно, одна полная синусоидальная волна не будет слышна. Вам нужно будет изменить RenderTone так, чтобы он создавал синусоидальную волну, как вам нравится, а затем возвращал ноль. Но, как я и предполагал, намного проще просто остановить аудиоустройство — попробуйте performSelector…:afterDelay: , это будет несложно…