Воспроизведение голоса из потока сервера nsdata с помощью AudioUnit IOS
Я пытаюсь создать какое-то приложение VoIP в iOS. До сих пор я был в состоянии успешно отправить данные микрофона в виде буфера от микрофона на сервер с помощью GCDAsyncSocket
, Теперь мне нужно воспроизвести данные, которые я получаю, что меня очень смущает. Я посмотрел онлайн, но все, что я вижу, это либо воспроизведение аудиофайла с удаленного компьютера, либо аудиопотока с URL-адреса. Я на самом деле получаю NSData
регулярно и нужно выяснить, как использовать эти NSData
заполнить список буферов аудиоустройств. Я новичок в C и мне трудно пройти через это. Это где я получаю NSData
с сервера.
- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
if (tag == 1 ){
//this is where I read password and stuff to authenticate
}
else{
[self setUpAQOutput:data];//this should somehow initialize AU and fill the buffer
}
и в моем AudioUnitProcessor
вот так я настроил AUnit
используя коды Стефана Поппа:
//
// AudioProcessor.m
// MicInput
//
// Created by Stefan Popp on 21.09.11.
//
#import "AudioProcessor.h"
#import "PTTClient.h"
#pragma mark Recording callback
static OSStatus recordingCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
// the data gets rendered here
AudioBuffer buffer;
// a variable where we check the status
OSStatus status;
/**
This is the reference to the object who owns the callback.
*/
AudioProcessor *audioProcessor = (AudioProcessor*) inRefCon;
/**
on this point we define the number of channels, which is mono
for the iphone. the number of frames is usally 512 or 1024.
*/
buffer.mDataByteSize = inNumberFrames * 2; // sample size
buffer.mNumberChannels = 1; // one channel
buffer.mData = malloc( inNumberFrames * 2 ); // buffer size
// we put our buffer into a bufferlist array for rendering
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0] = buffer;
// render input and check for error
status = AudioUnitRender([audioProcessor audioUnit], ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList);
// process the bufferlist in the audio processor
[audioProcessor processBuffer:&bufferList];
// clean up the buffer
free(bufferList.mBuffers[0].mData);
return noErr;
}
#pragma mark Playback callback
static OSStatus playbackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
//does nothing
return noErr;
}
#pragma mark objective-c class
@implementation AudioProcessor
@synthesize audioUnit, inAudioBuffer;
-(AudioProcessor*)init
{
self = [super init];
if (self) {
[self initializeAudio];
}
return self;
}
+ (OSStatus) playBytes:(NSArray*) byteArray {
/**
This is the reference to the object who owns the callback.
*/
// NSArray * byteArray = nil;
AudioProcessor *audioProcessor = [[AudioProcessor alloc] init];
// iterate over incoming stream an copy to output stream
for (int i=0; i < [byteArray count]; i++) {
// AudioBuffer buffer = ioData->mBuffers[i];
// find minimum size
UInt32 size = [audioProcessor inAudioBuffer].mDataByteSize;
// copy buffer to audio buffer which gets played after function return
memcpy(byteArray[i], [audioProcessor inAudioBuffer].mData, size);
// set data size
//buffer.mDataByteSize = size;
}
return noErr;
}
-(void)initializeAudio
{
OSStatus status;
// We define the audio component
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output; // we want to ouput
desc.componentSubType = kAudioUnitSubType_RemoteIO; // we want in and ouput
desc.componentFlags = 0; // must be zero
desc.componentFlagsMask = 0; // must be zero
desc.componentManufacturer = kAudioUnitManufacturer_Apple; // select provider
// find the AU component by description
AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
// create audio unit by component
status = AudioComponentInstanceNew(inputComponent, &audioUnit);
// define that we want record io on the input bus
UInt32 flag = 1;
status = AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_EnableIO, // use io
kAudioUnitScope_Input, // scope to input
kInputBus, // select input bus (1)
&flag, // set flag
sizeof(flag));
// define that we want play on io on the output bus
UInt32 stopFlag = 0;//stop flag 0 because we dont want to play audio back in device
status = AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_EnableIO, // use io
kAudioUnitScope_Output, // scope to output
kOutputBus, // select output bus (0)
&stopFlag, // set flag
sizeof(stopFlag));
/*
We need to specify our format on which we want to work.
We use Linear PCM cause its uncompressed and we work on raw data.
for more informations check.
We want 16 bits, 2 bytes per packet/frames at 44khz
*/
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = SAMPLE_RATE;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = audioFormat.mChannelsPerFrame * sizeof( SInt16);
audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * sizeof( SInt16);
// set the format on the output stream
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
kInputBus,
&audioFormat,
sizeof(audioFormat));
// set the format on the input stream
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
kOutputBus,
&audioFormat,
sizeof(audioFormat));
/**
We need to define a callback structure which holds
a pointer to the recordingCallback and a reference to
the audio processor object
*/
AURenderCallbackStruct callbackStruct;
// set recording callback
callbackStruct.inputProc = recordingCallback; // recordingCallback pointer
callbackStruct.inputProcRefCon = self;
// set input callback to recording callback on the input bus
status = AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
kInputBus,
&callbackStruct,
sizeof(callbackStruct));
/*
We do the same on the output stream to hear what is coming
from the input stream
*/
callbackStruct.inputProc = playbackCallback;
callbackStruct.inputProcRefCon = self;
// set playbackCallback as callback on our renderer for the output bus
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global,
kOutputBus,
&callbackStruct,
sizeof(callbackStruct));
// reset flag to 0
flag = 0;
/*
we need to tell the audio unit to allocate the render buffer,
that we can directly write into it.
*/
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output,
kInputBus,
&flag,
sizeof(flag));
/*
we set the number of channels to mono and allocate our block size to
1024 bytes.
*/
inAudioBuffer.mNumberChannels = 1;
inAudioBuffer.mDataByteSize = 512 * 2;
inAudioBuffer.mData = malloc( 512 * 2 );
// Initialize the Audio Unit and cross fingers =)
status = AudioUnitInitialize(audioUnit);
NSLog(@"Started");
}
#pragma mark controll stream
-(void)start;
{
// start the audio unit. You should hear something, hopefully :)
OSStatus status = AudioOutputUnitStart(audioUnit);
}
-(void)stop;
{
// stop the audio unit
OSStatus status = AudioOutputUnitStop(audioUnit);
}
#pragma mark processing
-(void)processBuffer: (AudioBufferList*) audioBufferList
{
AudioBuffer sourceBuffer = audioBufferList->mBuffers[0];
// we check here if the input data byte size has changed
if (inAudioBuffer.mDataByteSize != sourceBuffer.mDataByteSize) {
// clear old buffer
free(inAudioBuffer.mData);
// assing new byte size and allocate them on mData
inAudioBuffer.mDataByteSize = sourceBuffer.mDataByteSize;
inAudioBuffer.mData = malloc(sourceBuffer.mDataByteSize);
}
int currentBuffer =0;
int maxBuf = 800;
NSMutableData *data=[[NSMutableData alloc] init];
// CMBlockBufferRef blockBuffer;
// CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(ref, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, 0, &blockBuffer);
// NSLog(@"%@",blockBuffer);
// audioBufferList->mBuffers[0].mData, audioBufferList->mBuffers[0].mDataByteSize
for( int y=0; y<audioBufferList->mNumberBuffers; y++ )
{
if (currentBuffer < maxBuf){
AudioBuffer audioBuff = audioBufferList->mBuffers[y];
Float32 *frame = (Float32*)audioBuff.mData;
[data appendBytes:frame length:inAudioBuffer.mDataByteSize];
currentBuffer += audioBuff.mDataByteSize;
}
else{
break;
}
}
[[PTTClient getDefaultInstance] setAudioBufferData: data];//This is call to send buffer data to the server
// copy incoming audio data to the audio buffer (no need since we are not using playback)
//memcpy(inAudioBuffer.mData, audioBufferList->mBuffers[0].mData, audioBufferList->mBuffers[0].mDataByteSize);
}
@end
И, наконец, это способ отправки аудиоданных на сервер
-(void) setAudioBufferData: (NSData*) data{
[gcdSocket writeData:data withTimeout:timeout tag:tag];
}
Все это прекрасно работает, и я могу слушать звук на моем сервере, который работает на Java. Теперь мне нужно выяснить, как настроить этот аудиоустройство для воспроизведения NSData
пакеты, которые я постоянно получаю с сервера (я посмотрел несколько примеров воспроизведения удаленного файла, а это не то, что мне нужно. Мне нужно воспроизводить голос). Источник не файл, а кто-то говорит, так что я немного растерялся.
1 ответ
1) Ну, по-видимому, аудиоданные могут стать большими, поэтому я бы их буферизовал. Не уверен, что это более элегантный способ, но эй... методы грубой силы в вашем коде, работающие на логике уровня C++, меня немного удерживают
Использует
[NSData writeToFile:atomically:];
... полезно вообще? Возможно, затем использовать этот файл в качестве источника звука, передаваемого в более удобную платформу Core?
2) Единственное, что приходит на ум, это локальная розетка. т.е. откройте соединение с собой и предоставьте его как "удаленный источник"
Жаль, что я знал больше, чтобы быть более полезным.