Воспроизведение аудиоданных Raw PCM, поступающих из NSStream

Я пытаюсь воспроизвести данные PCM из NSInputStream. Может ли кто-нибудь предоставить мне правильный подход или код для этого.

Я получил событие Audio in StreamHasData со следующим кодом.

uint8_t bytes[self.audioStreamReadMaxLength];
        UInt32 length = [audioStream readData:bytes maxLength:self.audioStreamReadMaxLength];

Теперь, как я могу воспроизводить байты аудиоданных в iphone?


person Tornado    schedule 05.02.2015    source источник


Ответы (2)


Я работал над похожей проблемой, и я в конце концов решил ее.

Вот основное, что я сделал. Я использую библиотеку для сокетов

Класс ниже отвечает за получение звука и его доступность для подключенных клиентов.

#import <Foundation/Foundation.h>
#import "GCDAsyncSocket.h"
#import  <AudioToolbox/AudioToolbox.h>

@interface AudioServer : NSObject <GCDAsyncSocketDelegate>

@property (nonatomic, strong)GCDAsyncSocket * serverSocket;

@property (nonatomic, strong)NSMutableArray *connectedClients;

@property (nonatomic) AudioComponentInstance audioUnit;

-(void) start;
-(void) stop;
-(void) writeDataToClients:(NSData*)data;

@end

#define kOutputBus 0
#define kInputBus 1

#import "AudioServer.h"
#import "SM_Utils.h"

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

    // TODO: Use inRefCon to access our interface object to do stuff
    // Then, use inNumberFrames to figure out how much data is available, and make
    // that much space available in buffers in an AudioBufferList.

    AudioServer *server = (__bridge AudioServer*)inRefCon;

    AudioBufferList bufferList;

    SInt16 samples[inNumberFrames]; // A large enough size to not have to worry about buffer overrun
    memset (&samples, 0, sizeof (samples));

    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mData = samples;
    bufferList.mBuffers[0].mNumberChannels = 1;
    bufferList.mBuffers[0].mDataByteSize = inNumberFrames*sizeof(SInt16);

    // Then:
    // Obtain recorded samples

    OSStatus status;

    status = AudioUnitRender(server.audioUnit,
                             ioActionFlags,
                             inTimeStamp,
                             inBusNumber,
                             inNumberFrames,
                             &bufferList);

    NSData *dataToSend = [NSData dataWithBytes:bufferList.mBuffers[0].mData length:bufferList.mBuffers[0].mDataByteSize];
    [server writeDataToClients:dataToSend];

    return noErr;
}

@implementation AudioServer

-(id) init
{
    return [super init];
}

-(void) start
{

    [UIApplication sharedApplication].idleTimerDisabled = YES;
    // Create a new instance of AURemoteIO

    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;

    AudioComponent comp = AudioComponentFindNext(NULL, &desc);
    AudioComponentInstanceNew(comp, &_audioUnit);

    //  Enable input and output on AURemoteIO
    //  Input is enabled on the input scope of the input element
    //  Output is enabled on the output scope of the output element

    UInt32 one = 1;
    AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof(one));

    AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &one, sizeof(one));

    // Explicitly set the input and output client formats
    // sample rate = 44100, num channels = 1, format = 32 bit floating point

    AudioStreamBasicDescription audioFormat = [self getAudioDescription];
    AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &audioFormat, sizeof(audioFormat));
    AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat));

    // Set the MaximumFramesPerSlice property. This property is used to describe to an audio unit the maximum number
    // of samples it will be asked to produce on any single given call to AudioUnitRender
    UInt32 maxFramesPerSlice = 4096;
    AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(UInt32));

    // Get the property value back from AURemoteIO. We are going to use this value to allocate buffers accordingly
    UInt32 propSize = sizeof(UInt32);
    AudioUnitGetProperty(_audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, &propSize);


    AURenderCallbackStruct renderCallback;
    renderCallback.inputProc = recordingCallback;
    renderCallback.inputProcRefCon = (__bridge void *)(self);

    AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &renderCallback, sizeof(renderCallback));


    // Initialize the AURemoteIO instance
    AudioUnitInitialize(_audioUnit);

    AudioOutputUnitStart(_audioUnit);

    _connectedClients = [[NSMutableArray alloc] init];
    _serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

    [self startAcceptingConnections];
}

- (AudioStreamBasicDescription)getAudioDescription {
    AudioStreamBasicDescription audioDescription = {0};
    audioDescription.mFormatID          = kAudioFormatLinearPCM;
    audioDescription.mFormatFlags       = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
    audioDescription.mChannelsPerFrame  = 1;
    audioDescription.mBytesPerPacket    = sizeof(SInt16)*audioDescription.mChannelsPerFrame;
    audioDescription.mFramesPerPacket   = 1;
    audioDescription.mBytesPerFrame     = sizeof(SInt16)*audioDescription.mChannelsPerFrame;
    audioDescription.mBitsPerChannel    = 8 * sizeof(SInt16);
    audioDescription.mSampleRate        = 44100.0;
    return audioDescription;
}

-(void) startAcceptingConnections
{
    NSError *error = nil;
    if(_serverSocket)
        [_serverSocket acceptOnPort:[SM_Utils serverPort] error:&error];
}


-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    if(_connectedClients)
        [_connectedClients removeObject:sock];
}

- (void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {

    NSLog(@"Accepted New Socket from %@:%hu", [newSocket connectedHost], [newSocket connectedPort]);

    @synchronized(_connectedClients)
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            if(_connectedClients)
                [_connectedClients addObject:newSocket];
        });
    }

    NSError *error = nil;
    if(_serverSocket)
        [_serverSocket acceptOnPort:[SM_Utils serverPort] error:&error];
}

-(void) writeDataToClients:(NSData *)data
{
    if(_connectedClients)
    {
        for (GCDAsyncSocket *socket in _connectedClients) {
            if([socket isConnected])
            {
                [socket writeData:data withTimeout:-1 tag:0];
            }
            else{
                if([_connectedClients containsObject:socket])
                    [_connectedClients removeObject:socket];
            }
        }
    }
}

-(void) stop
{
    if(_serverSocket)
    {
        _serverSocket = nil;
    }
    [UIApplication sharedApplication].idleTimerDisabled = NO;
    AudioOutputUnitStop(_audioUnit);
}

-(void) dealloc
{
    if(_serverSocket)
    {
        _serverSocket = nil;
    }
    [UIApplication sharedApplication].idleTimerDisabled = NO;
    AudioOutputUnitStop(_audioUnit);
}

@end

Затем следующий класс отвечает за получение звука с сервера и его воспроизведение.

#import <Foundation/Foundation.h>
#import "GCDAsyncSocket.h"
#import  <AudioToolbox/AudioToolbox.h>

#import "TPCircularBuffer.h"

@protocol AudioClientDelegate <NSObject>

-(void) connected;
-(void) animateSoundIndicator:(float) rms;

@end

@interface AudioClient : NSObject<GCDAsyncSocketDelegate>
{
    NSString *ipAddress;
    BOOL stopped;
}

@property (nonatomic) TPCircularBuffer circularBuffer;
@property (nonatomic) AudioComponentInstance audioUnit;
@property (nonatomic, strong) GCDAsyncSocket *socket;
@property (nonatomic, strong) id<AudioClientDelegate> delegate;

-(id) initWithDelegate:(id)delegate;
-(void) start:(NSString *)ip;
-(void) stop;
-(TPCircularBuffer *) outputShouldUseCircularBuffer;

@end


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


    AudioClient *output = (__bridge AudioClient*)inRefCon;


    TPCircularBuffer *circularBuffer = [output outputShouldUseCircularBuffer];
    if( !circularBuffer ){
        AudioUnitSampleType *left  = (AudioUnitSampleType*)ioData->mBuffers[0].mData;
        for(int i = 0; i < inNumberFrames; i++ ){
            left[  i ] = 0.0f;
        }
        return noErr;
    };

    int32_t bytesToCopy = ioData->mBuffers[0].mDataByteSize;
    SInt16* outputBuffer = ioData->mBuffers[0].mData;

    int32_t availableBytes;
    SInt16 *sourceBuffer = TPCircularBufferTail(circularBuffer, &availableBytes);

    int32_t amount = MIN(bytesToCopy,availableBytes);
    memcpy(outputBuffer, sourceBuffer, amount);

    TPCircularBufferConsume(circularBuffer,amount);

    return noErr;
}

-(id) initWithDelegate:(id)delegate
{
    if(!self)
    {
        self = [super init];
    }

    [self circularBuffer:&_circularBuffer withSize:24576*5];
    _delegate = delegate;
    stopped = NO;
    return self;
}

-(void) start:(NSString *)ip
{
    _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue: dispatch_get_main_queue()];

    NSError *err;

    ipAddress = ip;

    [UIApplication sharedApplication].idleTimerDisabled = YES;

    if(![_socket connectToHost:ipAddress onPort:[SM_Utils serverPort] error:&err])
    {

    }

    [self setupAudioUnit];
}

-(void) setupAudioUnit
{
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;

    AudioComponent comp = AudioComponentFindNext(NULL, &desc);

    OSStatus status;

    status = AudioComponentInstanceNew(comp, &_audioUnit);

    if(status != noErr)
    {
        NSLog(@"Error creating AudioUnit instance");
    }

    //  Enable input and output on AURemoteIO
    //  Input is enabled on the input scope of the input element
    //  Output is enabled on the output scope of the output element

    UInt32 one = 1;

    status = AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &one, sizeof(one));


    if(status != noErr)
    {
        NSLog(@"Error enableling AudioUnit output bus");
    }

    // Explicitly set the input and output client formats
    // sample rate = 44100, num channels = 1, format = 16 bit int point

    AudioStreamBasicDescription audioFormat = [self getAudioDescription];

    status = AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));

    if(status != noErr)
    {
        NSLog(@"Error setting audio format");
    }

    AURenderCallbackStruct renderCallback;
    renderCallback.inputProc = OutputRenderCallback;
    renderCallback.inputProcRefCon = (__bridge void *)(self);

    status = AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &renderCallback, sizeof(renderCallback));

    if(status != noErr)
    {
        NSLog(@"Error setting rendering callback");
    }

    // Initialize the AURemoteIO instance
    status = AudioUnitInitialize(_audioUnit);

    if(status != noErr)
    {
        NSLog(@"Error initializing audio unit");
    }
}

- (AudioStreamBasicDescription)getAudioDescription {
    AudioStreamBasicDescription audioDescription = {0};
    audioDescription.mFormatID          = kAudioFormatLinearPCM;
    audioDescription.mFormatFlags       = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
    audioDescription.mChannelsPerFrame  = 1;
    audioDescription.mBytesPerPacket    = sizeof(SInt16)*audioDescription.mChannelsPerFrame;
    audioDescription.mFramesPerPacket   = 1;
    audioDescription.mBytesPerFrame     = sizeof(SInt16)*audioDescription.mChannelsPerFrame;
    audioDescription.mBitsPerChannel    = 8 * sizeof(SInt16);
    audioDescription.mSampleRate        = 44100.0;
    return audioDescription;
}

-(void) socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    if(!stopped)
    if(![_socket connectToHost:ipAddress onPort:[SM_Utils serverPort] error:&err])
    {

    }
}

-(void) socket:(GCDAsyncSocket *)socket didReadData:(NSData *)data withTag:(long)tag
{
    if(data.length > 0)
    {
        unsigned long len = [data length];

        SInt16* byteData = (SInt16*)malloc(len);
        memcpy(byteData, [data bytes], len);

        double sum = 0.0;
        for(int i = 0; i < len/2; i++) {
            sum += byteData[i] * byteData[i];
        }

        double average = sum / len;
        double rms = sqrt(average);

        [_delegate animateSoundIndicator:rms];

        Byte* soundData = (Byte*)malloc(len);
        memcpy(soundData, [data bytes], len);

        if(soundData)
        {
            AudioBufferList *theDataBuffer = (AudioBufferList*) malloc(sizeof(AudioBufferList) *1);
            theDataBuffer->mNumberBuffers = 1;
            theDataBuffer->mBuffers[0].mDataByteSize = (UInt32)len;
            theDataBuffer->mBuffers[0].mNumberChannels = 1;
            theDataBuffer->mBuffers[0].mData = (SInt16*)soundData;

            [self appendDataToCircularBuffer:&_circularBuffer fromAudioBufferList:theDataBuffer];
        }
    }

    [socket readDataToLength:18432 withTimeout:-1 tag:0];
}

-(void)circularBuffer:(TPCircularBuffer *)circularBuffer withSize:(int)size {
    TPCircularBufferInit(circularBuffer,size);
}

-(void)appendDataToCircularBuffer:(TPCircularBuffer*)circularBuffer
              fromAudioBufferList:(AudioBufferList*)audioBufferList {
    TPCircularBufferProduceBytes(circularBuffer,
                                 audioBufferList->mBuffers[0].mData,
                                 audioBufferList->mBuffers[0].mDataByteSize);
}

-(void)freeCircularBuffer:(TPCircularBuffer *)circularBuffer {
    TPCircularBufferClear(circularBuffer);
    TPCircularBufferCleanup(circularBuffer);
}

-(void) socket:(GCDAsyncSocket *)socket didConnectToHost:(NSString *)host port:(uint16_t)port
{
    OSStatus status = AudioOutputUnitStart(_audioUnit);

    if(status != noErr)
    {
        NSLog(@"Error starting audio unit");
    }

    [socket readDataToLength:18432 withTimeout:-1 tag:0];

    [_delegate connected];
}

-(TPCircularBuffer *) outputShouldUseCircularBuffer
{
    return &_circularBuffer;
}

-(void) stop
{

    OSStatus status = AudioOutputUnitStop(_audioUnit);

    if(status != noErr)
    {
        NSLog(@"Error stopping audio unit");
    }

    [UIApplication sharedApplication].idleTimerDisabled = NO;

    TPCircularBufferClear(&_circularBuffer);
    _audioUnit = nil;
    stopped = YES;
}

-(void) dealloc {
    OSStatus status = AudioOutputUnitStop(_audioUnit);

    if(status != noErr)
    {
       NSLog(@"Error stopping audio unit");
    }

    [UIApplication sharedApplication].idleTimerDisabled = NO;

    TPCircularBufferClear(&_circularBuffer);
    _audioUnit = nil;
    stopped = YES;
}

@end

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

person Armand    schedule 19.05.2015
comment
Привет, Арманд, это действительно хорошо, спасибо. Вы знаете, почему звук искажается? у меня проблемы с этим - person Pablo Martinez; 22.01.2016
comment
Пожалуйста, может кто-нибудь указать мне строку кода, где аудиоданные наконец попадают в динамик? - person Vincenzo; 12.06.2016
comment
@user3211074 memcpy(outputBuffer, sourceBuffer, amount); - person Armand; 23.06.2016
comment
@nullforlife Это класс, который содержит некоторые статические свойства, такие как порт сервера. - person Armand; 12.10.2016
comment
@Armand Спасибо за ваш ответ. Также мне интересно, метод writeDataToClients, похоже, записывает данные на все подключенные клиенты. Разве сервер не должен обрабатывать это? Или я что-то здесь упускаю? - person nullforlife; 12.10.2016
comment
@nullforlife Эти два класса являются серверными и клиентскими классами, они также обрабатывают аудиозапись и воспроизведение, по сути, это можно разделить на отдельные классы. - person Armand; 12.10.2016
comment
@Armand Хорошо, теперь это имеет больше смысла. Большое спасибо за то, что поделились всем этим. - person nullforlife; 12.10.2016
comment
@VitaliyA Извините, к сожалению, нет, но преобразовать его должно быть очень сложно :) - person Armand; 19.10.2018
comment
@Armand Спасибо, приятель. Вы знаете, почему по какой-то причине OutputRenderCallback не вызывается в клиенте? я всё скопировал - person Oz Shabat; 13.02.2020
comment
@OzShabat извини, чувак, точно не помню, это было так давно - person Armand; 13.02.2020

У Apple есть пример, делающий то же самое: -

void* MyGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei*    outSampleRate)
{
OSStatus                        err = noErr;    
SInt64                          theFileLengthInFrames = 0;
AudioStreamBasicDescription     theFileFormat;
UInt32                          thePropertySize = sizeof(theFileFormat);
ExtAudioFileRef                 extRef = NULL;
void*                           theData = NULL;
AudioStreamBasicDescription     theOutputFormat;

// Open a file with ExtAudioFileOpen()
err = ExtAudioFileOpenURL(inFileURL, &extRef);
if(err) { printf("MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", err); goto Exit; }

// Get the audio data format
err = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat);
if(err) { printf("MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld\n", err); goto Exit; }
if (theFileFormat.mChannelsPerFrame > 2)  { printf("MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); goto Exit;}

// Set the client format to 16 bit signed integer (native-endian) data
// Maintain the channel count and sample rate of the original source format
theOutputFormat.mSampleRate = theFileFormat.mSampleRate;
theOutputFormat.mChannelsPerFrame = theFileFormat.mChannelsPerFrame;

theOutputFormat.mFormatID = kAudioFormatLinearPCM;
theOutputFormat.mBytesPerPacket = 2 * theOutputFormat.mChannelsPerFrame;
theOutputFormat.mFramesPerPacket = 1;
theOutputFormat.mBytesPerFrame = 2 * theOutputFormat.mChannelsPerFrame;
theOutputFormat.mBitsPerChannel = 16;
theOutputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;

// Set the desired client (output) data format
err = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(theOutputFormat), &theOutputFormat);
if(err) { printf("MyGetOpenALAudioData: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) FAILED, Error = %ld\n", err); goto Exit; }

// Get the total frame count
thePropertySize = sizeof(theFileLengthInFrames);
err = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames);
if(err) { printf("MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld\n", err); goto Exit; }

// Read all the data into memory
UInt32 theFramesToRead = (UInt32)theFileLengthInFrames;     
UInt32 dataSize = theFramesToRead * theOutputFormat.mBytesPerFrame;;
theData = malloc(dataSize);
if (theData)
{
    AudioBufferList     theDataBuffer;
    theDataBuffer.mNumberBuffers = 1;
    theDataBuffer.mBuffers[0].mDataByteSize = dataSize;
    theDataBuffer.mBuffers[0].mNumberChannels = theOutputFormat.mChannelsPerFrame;
    theDataBuffer.mBuffers[0].mData = theData;

    // Read the data into an AudioBufferList
    err = ExtAudioFileRead(extRef, &theFramesToRead, &theDataBuffer);
    if(err == noErr)
    {
        // success
        *outDataSize = (ALsizei)dataSize;
        *outDataFormat = (theOutputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
        *outSampleRate = (ALsizei)theOutputFormat.mSampleRate;
    }
    else 
    { 
        // failure
        free (theData);
        theData = NULL; // make sure to return NULL
        printf("MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", err); goto Exit;
    }   
}

Exit:
// Dispose the ExtAudioFileRef, it is no longer needed
if (extRef) ExtAudioFileDispose(extRef);
return theData;
}

Найдите здесь пример кода. Надеюсь, это поможет.

person Mohd Prophet    schedule 25.02.2015
comment
Это больше соответствует тому, что необходимо: stackoverflow.com/questions/28712887/ проблема в том, что он еще не работает. - person Armand; 25.02.2015