I am doing a transcription app in iOS. So, I have to record the audio in buffer and stream them to the server through socket. So, I have used AudioQueue to record the audio in buffer.
The Audio is being recorded properly in local file. For streaming, I converted audio data to NSData and send it through socket. But, The Audio quality is not good in the server especially the voice is not clear at all. It contains lots of noise in the place of voice. The same logic works properly in Android. So, The server side code is working properly. But, the iOS streaming conversion is a problem. I used two different sockets (SocketRocket/PockSocket). The problem remains the same in both the sockets.
I have attached my code here. Please let me know if you can help me.
ViewController.h
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>
#import <SocketRocket/SocketRocket.h>
#define NUM_BUFFERS 3
#define SAMPLERATE 16000
//Struct defining recording state
typedef struct {
AudioStreamBasicDescription dataFormat;
AudioQueueRef queue;
AudioQueueBufferRef buffers[NUM_BUFFERS];
AudioFileID audioFile;
SInt64 currentPacket;
bool recording;
} RecordState;
//Struct defining playback state
typedef struct {
AudioStreamBasicDescription dataFormat;
AudioQueueRef queue;
AudioQueueBufferRef buffers[NUM_BUFFERS];
AudioFileID audioFile;
SInt64 currentPacket;
bool playing;
} PlayState;
#interface ViewController : UIViewController <SRWebSocketDelegate> {
RecordState recordState;
PlayState playState;
CFURLRef fileURL;
}
#property (nonatomic, strong) SRWebSocket * webSocket;
#property (weak, nonatomic) IBOutlet UITextView *textView;
#end
ViewController.m
#import "ViewController.h"
id thisClass;
//Declare C callback functions
void AudioInputCallback(void * inUserData, // Custom audio metada
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp * inStartTime,
UInt32 isNumberPacketDescriptions,
const AudioStreamPacketDescription * inPacketDescs);
void AudioOutputCallback(void * inUserData,
AudioQueueRef outAQ,
AudioQueueBufferRef outBuffer);
#interface ViewController ()
#end
#implementation ViewController
#synthesize webSocket;
#synthesize textView;
// Takes a filled buffer and writes it to disk, "emptying" the buffer
void AudioInputCallback(void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp * inStartTime,
UInt32 inNumberPacketDescriptions,
const AudioStreamPacketDescription * inPacketDescs)
{
RecordState * recordState = (RecordState*)inUserData;
if (!recordState->recording)
{
printf("Not recording, returning\n");
}
printf("Writing buffer %lld\n", recordState->currentPacket);
OSStatus status = AudioFileWritePackets(recordState->audioFile,
false,
inBuffer->mAudioDataByteSize,
inPacketDescs,
recordState->currentPacket,
&inNumberPacketDescriptions,
inBuffer->mAudioData);
if (status == 0)
{
recordState->currentPacket += inNumberPacketDescriptions;
NSData * audioData = [NSData dataWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize * NUM_BUFFERS];
[thisClass sendAudioToSocketAsData:audioData];
}
AudioQueueEnqueueBuffer(recordState->queue, inBuffer, 0, NULL);
}
// Fills an empty buffer with data and sends it to the speaker
void AudioOutputCallback(void * inUserData,
AudioQueueRef outAQ,
AudioQueueBufferRef outBuffer) {
PlayState * playState = (PlayState *) inUserData;
if(!playState -> playing) {
printf("Not playing, returning\n");
return;
}
printf("Queuing buffer %lld for playback\n", playState -> currentPacket);
AudioStreamPacketDescription * packetDescs;
UInt32 bytesRead;
UInt32 numPackets = SAMPLERATE * NUM_BUFFERS;
OSStatus status;
status = AudioFileReadPackets(playState -> audioFile, false, &bytesRead, packetDescs, playState -> currentPacket, &numPackets, outBuffer -> mAudioData);
if (numPackets) {
outBuffer -> mAudioDataByteSize = bytesRead;
status = AudioQueueEnqueueBuffer(playState -> queue, outBuffer, 0, packetDescs);
playState -> currentPacket += numPackets;
}else {
if (playState -> playing) {
AudioQueueStop(playState -> queue, false);
AudioFileClose(playState -> audioFile);
playState -> playing = false;
}
AudioQueueFreeBuffer(playState -> queue, outBuffer);
}
}
- (void) setupAudioFormat:(AudioStreamBasicDescription *) format {
format -> mSampleRate = SAMPLERATE;
format -> mFormatID = kAudioFormatLinearPCM;
format -> mFramesPerPacket = 1;
format -> mChannelsPerFrame = 1;
format -> mBytesPerFrame = 2;
format -> mBytesPerPacket = 2;
format -> mBitsPerChannel = 16;
format -> mReserved = 0;
format -> mFormatFlags = kLinearPCMFormatFlagIsBigEndian |kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
char path[256];
[self getFilename:path maxLength:sizeof path];
fileURL = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8*)path, strlen(path), false);
// Init state variables
recordState.recording = false;
thisClass = self;
}
- (void) startRecordingInQueue {
[self setupAudioFormat:&recordState.dataFormat];
recordState.currentPacket = 0;
OSStatus status;
status = AudioQueueNewInput(&recordState.dataFormat, AudioInputCallback, &recordState, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &recordState.queue);
if(status == 0) {
//Prime recording buffers with empty data
for (int i=0; i < NUM_BUFFERS; i++) {
AudioQueueAllocateBuffer(recordState.queue, SAMPLERATE, &recordState.buffers[i]);
AudioQueueEnqueueBuffer(recordState.queue, recordState.buffers[i], 0, NULL);
}
status = AudioFileCreateWithURL(fileURL, kAudioFileAIFFType, &recordState.dataFormat, kAudioFileFlags_EraseFile, &recordState.audioFile);
if (status == 0) {
recordState.recording = true;
status = AudioQueueStart(recordState.queue, NULL);
if(status == 0) {
NSLog(#"-----------Recording--------------");
NSLog(#"File URL : %#", fileURL);
}
}
}
if (status != 0) {
[self stopRecordingInQueue];
}
}
- (void) stopRecordingInQueue {
recordState.recording = false;
AudioQueueStop(recordState.queue, true);
for (int i=0; i < NUM_BUFFERS; i++) {
AudioQueueFreeBuffer(recordState.queue, recordState.buffers[i]);
}
AudioQueueDispose(recordState.queue, true);
AudioFileClose(recordState.audioFile);
NSLog(#"---Idle------");
NSLog(#"File URL : %#", fileURL);
}
- (void) startPlaybackInQueue {
playState.currentPacket = 0;
[self setupAudioFormat:&playState.dataFormat];
OSStatus status;
status = AudioFileOpenURL(fileURL, kAudioFileReadPermission, kAudioFileAIFFType, &playState.audioFile);
if (status == 0) {
status = AudioQueueNewOutput(&playState.dataFormat, AudioOutputCallback, &playState, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &playState.queue);
if( status == 0) {
//Allocate and prime playback buffers
playState.playing = true;
for (int i=0; i < NUM_BUFFERS && playState.playing; i++) {
AudioQueueAllocateBuffer(playState.queue, SAMPLERATE, &playState.buffers[i]);
AudioOutputCallback(&playState, playState.queue, playState.buffers[i]);
}
status = AudioQueueStart(playState.queue, NULL);
if (status == 0) {
NSLog(#"-------Playing Audio---------");
}
}
}
if (status != 0) {
[self stopPlaybackInQueue];
NSLog(#"---Playing Audio Failed ------");
}
}
- (void) stopPlaybackInQueue {
playState.playing = false;
for (int i=0; i < NUM_BUFFERS; i++) {
AudioQueueFreeBuffer(playState.queue, playState.buffers[i]);
}
AudioQueueDispose(playState.queue, true);
AudioFileClose(playState.audioFile);
}
- (IBAction)startRecordingAudio:(id)sender {
NSLog(#"starting recording tapped");
[self startRecordingInQueue];
}
- (IBAction)stopRecordingAudio:(id)sender {
NSLog(#"stop recording tapped");
[self stopRecordingInQueue];
}
- (IBAction)startPlayingAudio:(id)sender {
NSLog(#"start playing audio tapped");
[self startPlaybackInQueue];
}
- (IBAction)stopPlayingAudio:(id)sender {
NSLog(#"stop playing audio tapped");
[self stopPlaybackInQueue];
}
- (BOOL) getFilename:(char *) buffer maxLength:(int) maxBufferLength {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * docDir = [paths objectAtIndex:0];
NSString * file = [docDir stringByAppendingString:#"recording.aif"];
return [file getCString:buffer maxLength:maxBufferLength encoding:NSUTF8StringEncoding];
}
- (void) sendAudioToSocketAsData:(NSData *) audioData {
[self.webSocket send:audioData];
}
- (IBAction)connectToSocketTapped:(id)sender {
[self startStreaming];
}
- (void) startStreaming {
[self connectToSocket];
}
- (void) connectToSocket {
//Socket Connection Intiliazation
// create the NSURLRequest that will be sent as the handshake
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"${url}"]];
// create the socket and assign delegate
self.webSocket = [[SRWebSocket alloc] initWithURLRequest:request];
self.webSocket.delegate = self;
// open socket
[self.webSocket open];
}
///--------------------------------------
#pragma mark - SRWebSocketDelegate
///--------------------------------------
- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
{
NSLog(#"Websocket Connected");
}
- (void) webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
NSLog(#":( Websocket Failed With Error %#", error);
self.webSocket = nil;
}
- (void) webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSLog(#"Received \"%#\"", message);
textView.text = message;
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
{
NSLog(#"WebSocket closed");
self.webSocket = nil;
}
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload;
{
NSLog(#"WebSocket received pong");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
Thanks in Advance
I made it work. It was the audio format set up which was causing the problem. I set the audio properly by checking the server side documentation. The Big-Endian was causing problem. If you specify it as big-endian, it is big endian. If you do not specify it, then, it is little-endian. I was in need of little-endian.
- (void) setupAudioFormat:(AudioStreamBasicDescription *) format {
format -> mSampleRate = 16000.0; //
format -> mFormatID = kAudioFormatLinearPCM; //
format -> mFramesPerPacket = 1;
format -> mChannelsPerFrame = 1; //
format -> mBytesPerFrame = 2;
format -> mBytesPerPacket = 2;
format -> mBitsPerChannel = 16; //
// format -> mReserved = 0;
format -> mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
}
Related
I'm trying to simply playback an audio file in my bundle with Audio Queue.
I've got this code from a tutorial where audio was first recorded and then played back, which worked fine.
After modifying the sound file path to point at my sound file there is only white noise when playing back. I've tried different formats and played around with my audio format settings but I'm obviously missing something.
Also, I've been learning iOS with Swift, and I failed to translate this code to Swift, so I bridged it in my Swift app.
There seem to be not many examples online, the Apple example project links linked to are dead.
Any advice much appreciated!
#import "ViewController.h"
typedef NS_ENUM(NSUInteger, AudioQueueState) {
AudioQueueState_Idle,
AudioQueueState_Recording,
AudioQueueState_Playing,
};
#import AVFoundation;
#interface ViewController ()
#property AudioQueueState currentState;
#property (strong, nonatomic) NSURL *audioFileURL;
#end
#define NUM_BUFFERS 10
#implementation ViewController
void AudioOutputCallback(void *inUserData,
AudioQueueRef outAQ, // a reference to the audio queue
AudioQueueBufferRef outBuffer) { // the buffers
ViewController *viewController = (__bridge ViewController*)inUserData;
if (viewController.currentState != AudioQueueState_Playing) {
return;
}
// Read the data out of the audio file in order to fill the buffers with it.
UInt32 numBytes = 16000;
OSStatus status = AudioFileReadBytes(audioFileID, false, currentByte, &numBytes, outBuffer->mAudioData);
if (status != noErr && status != kAudioFileEndOfFileError) {
printf("Error\n");
return;
}
// If data has been read successfully tell the audio queue that the buffer is ready to play.
if (numBytes > 0) {
outBuffer->mAudioDataByteSize = numBytes;
OSStatus statusOfEnqueue = AudioQueueEnqueueBuffer(queue, outBuffer, 0, NULL);
if (statusOfEnqueue != noErr) {
printf("Error\n");
return;
}
currentByte += numBytes;
}
// Check if it's at the end of the file.
if (numBytes == 0 || status == kAudioFileEndOfFileError) {
AudioQueueStop(queue, false);
AudioFileClose(audioFileID);
viewController.currentState = AudioQueueState_Idle;
}
}
static SInt64 currentByte;
static AudioStreamBasicDescription audioFormat;
static AudioQueueRef queue;
static AudioQueueBufferRef buffers [NUM_BUFFERS];
static AudioFileID audioFileID;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self setupAudio];
}
- (void) setupAudio {
audioFormat.mSampleRate = 44100.00;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * sizeof(SInt16);
audioFormat.mBytesPerPacket = audioFormat.mFramesPerPacket * audioFormat.mBytesPerFrame;
self.currentState = AudioQueueState_Idle;
}
- (IBAction)playButtonPressed:(id)sender {
// Set up the audio session.
[[AVAudioSession sharedInstance] setActive:YES error:&error];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
[self startPlayback];
}
- (void) startPlayback {
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"MyAudioFileName" ofType:#"wav"];
NSLog(#"path: %#", resourcePath);
self.audioFileURL = [NSURL fileURLWithPath:resourcePath];
currentByte = 0;
OSStatus status = AudioFileOpenURL((__bridge CFURLRef) (self.audioFileURL), kAudioFileReadPermission, kAudioFileWAVEType, &audioFileID);
status = AudioQueueNewOutput(&audioFormat, AudioOutputCallback, (__bridge void*)self, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &queue);
self.currentState = AudioQueueState_Playing;
for (int i = 0; i < NUM_BUFFERS && self.currentState == AudioQueueState_Playing; i++) {
status = AudioQueueAllocateBuffer(queue, 16000, &buffers[i]);
AudioOutputCallback((__bridge void*)self, queue, buffers[i]);
}
status = AudioQueueStart(queue, NULL);
}
- (void) stopPlayback {
self.currentState = AudioQueueState_Idle;
for (int i = 0; i < NUM_BUFFERS; i++) {
AudioQueueFreeBuffer(queue, buffers[i]);
}
AudioQueueDispose(queue, true);
AudioFileClose(audioFileID);
}
#end
I get audio data from RTMPPacket, and I use AudioQueue to play it in IPAD.
First, the voice is fine. But After about 15mins, there is no voice.
Then the data is ok, and the queue is playing.
I don't know why. Can anyone help me? Thanks.
This is my class used to play audio.
AudioPlayer.h
#import <Foundation/Foundation.h>
#import <CoreAudio/CoreAudioTypes.h>
#import <CoreFoundation/CoreFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#include <unistd.h>
#define kNumberBuffers 3
#interface AudioPlayer : NSObject
{
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
AudioStreamBasicDescription mPlayFormat;
int mIndex;
#public
Boolean mIsRunning;
Boolean mIsInitialized;
int mBufferByteSize;
int pip_fd[2];
UInt32 mNumPacketsToRead;
}
#property AudioQueueRef mQueue;
-(id)init;
-(id)initWithSampleRate:(int)sampleRate;
-(void)startPlayWithBufferByteSize:(int)bufferByteSize;
-(void)stopPlay;
-(void)putAudioData:(short*)pcmData;
#end
AudioPlayer.m
#import "AudioPlayer.h"
#implementation AudioPlayer
#synthesize mQueue;
void AQBufferCallback(void * inUserData ,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer)
{
AudioPlayer *THIS = (__bridge AudioPlayer *)(inUserData);
if(THIS->mIsRunning)
{
inBuffer->mPacketDescriptionCount = THIS->mBufferByteSize/2;
inBuffer->mAudioDataByteSize =THIS->mBufferByteSize;
if(read(THIS->pip_fd[0], inBuffer->mAudioData, THIS->mBufferByteSize) > 0 ){
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
}
}
-(id)init
{
return [self initWithSampleRate:16000];
}
-(id)initWithSampleRate:(int)sampleRate
{
self = [super init];
if(self)
{
memset(&mPlayFormat, 0, sizeof(mPlayFormat));
mPlayFormat.mFormatID = kAudioFormatLinearPCM;
mPlayFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
mPlayFormat.mBitsPerChannel = 16;
mPlayFormat.mChannelsPerFrame = 1;
mPlayFormat.mBytesPerPacket = mPlayFormat.mBytesPerFrame = (mPlayFormat.mBitsPerChannel / 8) * mPlayFormat.mChannelsPerFrame;
mPlayFormat.mFramesPerPacket = 1;
mPlayFormat.mSampleRate = sampleRate;
mIsRunning = false;
mIsInitialized = false;
}
return self;
}
-(void)startPlayWithBufferByteSize:(int)bufferByteSize
{
if (mIsInitialized) return;
mBufferByteSize = bufferByteSize;
AudioQueueNewOutput(&mPlayFormat, AQBufferCallback, (__bridge void *)(self), nil, nil, 0, &mQueue);
for (int i=0; i<kNumberBuffers; i++) {
AudioQueueAllocateBuffer(mQueue, mBufferByteSize, &mBuffers[i]);
}
AudioQueueSetParameter(mQueue, kAudioQueueParam_Volume, 1.0);
mIsInitialized = true;
int ret = pipe(pip_fd);
if (ret == -1) {
NSLog(#"create pipe failed");
}
}
-(void)stopPlay
{
close(pip_fd[0]);
close(pip_fd[1]);
AudioQueueStop(mQueue, false);
if (mQueue){
AudioQueueDispose(mQueue, true);
mQueue = NULL;
mIsRunning = false;
}
mIsInitialized = false;
NSLog(#"stop play queue");
}
-(void)putAudioData:(short*)pcmData
{
if (!mIsRunning) {
memcpy(mBuffers[mIndex]->mAudioData, pcmData, mBufferByteSize);
mBuffers[mIndex]->mAudioDataByteSize = mBufferByteSize;
mBuffers[mIndex]->mPacketDescriptionCount = mBufferByteSize/2;
AudioQueueEnqueueBuffer(mQueue, mBuffers[mIndex], 0, NULL);
NSLog(#"fill audio queue buffer[%d]",mIndex);
if(mIndex == kNumberBuffers - 1) {
mIsRunning = true;
mIndex = 0;
AudioQueueStart(mQueue, NULL);
}else {
mIndex++;
}
}else {
if(write(pip_fd[1], pcmData, mBufferByteSize) < 0){
NSLog(#"write to the pipe failed!");
}
}
}
#end
I have reffered to this to play a PCM file using Audio Queues.
The code is as follows:
#import "PlayPCM.h"
AudioFileID audioFile;
SInt64 inStartingPacket = 0;
AudioQueueRef audioQueue;
#implementation PlayPCM
void AudioOutputCallback(
void* inUserData,
AudioQueueRef outAQ,
AudioQueueBufferRef outBuffer)
{
AudioStreamPacketDescription* packetDescs;
UInt32 bytesRead;
UInt32 numPackets = 8000;
OSStatus status;
status = AudioFileReadPackets(audioFile,
false,
&bytesRead,
packetDescs,
inStartingPacket,
&numPackets,
outBuffer->mAudioData);
if(numPackets)
{
outBuffer->mAudioDataByteSize = bytesRead;
status = AudioQueueEnqueueBuffer(audioQueue,
outBuffer,
0,
packetDescs);
inStartingPacket += numPackets;
}
else
{
NSLog(#"number of packets = null ") ;
AudioQueueFreeBuffer(audioQueue, outBuffer);
}
}
-(id)init{
if (self = [super init]) {
}
return self;
}
- (void)setupAudioFormat
{
NSLog(#"setting format");
format.mFormatID = kAudioFormatLinearPCM;
format.mSampleRate = 44100;
format.mFramesPerPacket = 1;
format.mChannelsPerFrame = 1;
format.mBytesPerFrame = 2;
format.mBytesPerPacket = 2;
format.mBitsPerChannel = 16;
format.mFormatFlags = kLinearPCMFormatFlagIsBigEndian |
kLinearPCMFormatFlagIsSignedInteger |
kLinearPCMFormatFlagIsPacked;
}
- (void)startPlayback
{
int counter = 0;
[self setupAudioFormat];
OSStatus status;
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:# "test1.wav"];
NSLog(#"file path = %#",filePath);
//fUrl = [NSURL URLWithPath:#"file:///Users/Inscripts/Desktop/test1.wav"];
fUrl = [NSURL fileURLWithPath:filePath];
//CFURLRef fileURL = (__bridge CFURLRef)(fUrl);
CFURLRef fileURL = CFURLCreateWithString(NULL, (CFStringRef) filePath, NULL);
status = AudioFileOpenURL(fileURL, kAudioFileReadPermission, 0,&audioFile);
NSLog(#"file opening status = %d",(int)status);
if(status == 0)
{ NSLog(#"file opened");
status = AudioQueueNewOutput(&(format),
AudioOutputCallback,
(__bridge void *)(self),
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&audioQueue);
NSLog(#"audio queue create status = %d",(int)status);
if(status == 0)
{
AudioQueueAllocateBuffer(audioQueue, 1600000, &audioQueueBuffer);
AudioOutputCallback((__bridge void *)(self), audioQueue, audioQueueBuffer);
[self performSelector:#selector(startQueue) withObject:self afterDelay:50];
}
}
if(status != 0)
{
NSLog(#"failed");
// labelStatus.text = #"Play failed";
}
}
-(void)startQueue{
NSLog(#"start queue called");
OSStatus status = AudioQueueStart(audioQueue, NULL);
if(status == 0)
{
NSLog(#"ok");
// labelStatus.text = #"Playing";
}
}
test1.wav file is PCM encoded 16 bits per sample, sampling rate 44100 Hertz, stereo.
I can successfully create audio queue and read the file but all I can hear is crackling noise.
Can someone tell me what's the issue?
Is the sound really big endian data - i doubt with WAVE files.
See your format flags, and change them to use little endian data, so: !kLinearPCMFormatFlagIsBigEndian
Also consider using AudioFileOpenURLor related since that will read the actual wave format and you don't have to rely on your audio stream description.
After preparing more audio queue buffers, no more crackling noise.
please refer to apple's doc
...
/* AudioQueueAllocateBuffer(audioQueue, 1600000, &audioQueueBuffer);
AudioOutputCallback((__bridge void *)(self), audioQueue, audioQueueBuffer);*/
/* add more audio queue buffers, ex:3 */
int kNumberOfBuffers = 3;
AudioQueueBufferRef audioQueueBuffer[kNumberOfBuffers];
for (int i = 0; i<kNumberOfBuffers; i++) {
AudioQueueAllocateBuffer(audioQueue, 1600000, &audioQueueBuffer[i]);
AudioOutputCallback((__bridge void *)(self), audioQueue, audioQueueBuffer[i]);
}
[self performSelector:#selector(startQueue) withObject:self afterDelay:50];
...
So I have tried to read everything I can about FFT with the the Accelerate.framework and got an example working that works with MTAudioProcessingTap but I feel like I am doing something wrong and my plotted points shouldn't look like this.
#import "AudioTap.h"
#pragma mark - TapContext
typedef struct TapContext {
void *audioTap;
Float64 sampleRate;
UInt32 numSamples;
FFTSetup fftSetup;
COMPLEX_SPLIT split;
float *window;
float *inReal;
} TapContext;
#pragma mark - AudioTap Callbacks
static void TapInit(MTAudioProcessingTapRef tap, void *clientInfo, void **tapStorageOut)
{
TapContext *context = calloc(1, sizeof(TapContext));
context->audioTap = clientInfo;
context->sampleRate = NAN;
context->numSamples = 4096;
vDSP_Length log2n = log2f((float)context->numSamples);
int nOver2 = context->numSamples/2;
context->inReal = (float *) malloc(context->numSamples * sizeof(float));
context->split.realp = (float *) malloc(nOver2*sizeof(float));
context->split.imagp = (float *) malloc(nOver2*sizeof(float));
context->fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
context->window = (float *) malloc(context->numSamples * sizeof(float));
vDSP_hann_window(context->window, context->numSamples, vDSP_HANN_DENORM);
*tapStorageOut = context;
}
static void TapPrepare(MTAudioProcessingTapRef tap, CMItemCount numberFrames, const AudioStreamBasicDescription *format)
{
TapContext *context = (TapContext *)MTAudioProcessingTapGetStorage(tap);
context->sampleRate = format->mSampleRate;
if (format->mFormatFlags & kAudioFormatFlagIsNonInterleaved) {
NSLog(#"is Non Interleaved");
}
if (format->mFormatFlags & kAudioFormatFlagIsSignedInteger) {
NSLog(#"dealing with integers");
}
}
static void TapProcess(MTAudioProcessingTapRef tap, CMItemCount numberFrames, MTAudioProcessingTapFlags flags,
AudioBufferList *bufferListInOut, CMItemCount *numberFramesOut, MTAudioProcessingTapFlags *flagsOut)
{
OSStatus status;
status = MTAudioProcessingTapGetSourceAudio(tap, numberFrames, bufferListInOut, flagsOut, NULL, numberFramesOut);
if (status != noErr) {
NSLog(#"MTAudioProcessingTapGetSourceAudio: %d", (int)status);
return;
}
//UInt32 bufferCount = bufferListInOut->mNumberBuffers;
AudioBuffer *firstBuffer = &bufferListInOut->mBuffers[1];
float *bufferData = firstBuffer->mData;
//UInt32 dataSize = firstBuffer->mDataByteSize;
//printf(": %li", dataSize);
TapContext *context = (TapContext *)MTAudioProcessingTapGetStorage(tap);
vDSP_vmul(bufferData, 1, context->window, 1, context->inReal, 1, context->numSamples);
vDSP_ctoz((COMPLEX *)context->inReal, 2, &context->split, 1, context->numSamples/2);
vDSP_Length log2n = log2f((float)context->numSamples);
vDSP_fft_zrip(context->fftSetup, &context->split, 1, log2n, FFT_FORWARD);
context->split.imagp[0] = 0.0;
UInt32 i;
NSMutableArray *outData = [NSMutableArray array];
[outData addObject:[NSNumber numberWithFloat:0]];
for( i = 1; i < context->numSamples; i++) {
float power = context->split.realp[i] * context->split.realp[i] + context->split.imagp[i] * context->split.imagp[i];
//amp[i] = sqrtf(power);
[outData addObject:[NSNumber numberWithFloat:sqrtf(power)]];
}
AudioTap *audioTap = (__bridge AudioTap *)context->audioTap;
[audioTap updateSpectrum:outData];
}
static void TapUnprepare(MTAudioProcessingTapRef tap)
{
}
static void TapFinalize(MTAudioProcessingTapRef tap)
{
TapContext *context = (TapContext *)MTAudioProcessingTapGetStorage(tap);
free(context->split.realp);
free(context->split.imagp);
free(context->inReal);
free(context->window);
context->fftSetup = nil;
context->audioTap = nil;
free(context);
}
#pragma mark - AudioTap Implementation
#implementation AudioTap
- (id)initWithTrack:(AVAssetTrack *)track frameSize:(UInt32)frameSize
{
self = [super init];
if (self) {
_assetTrack = track;
_frameSize = frameSize;
[self setupAudioTap];
}
return self;
}
- (void)setupAudioTap
{
//MTAudioProcessingTap
MTAudioProcessingTapCallbacks callbacks;
callbacks.version = kMTAudioProcessingTapCallbacksVersion_0;
callbacks.init = TapInit;
callbacks.prepare = TapPrepare;
callbacks.process = TapProcess;
callbacks.unprepare = TapUnprepare;
callbacks.finalize = TapFinalize;
callbacks.clientInfo = (__bridge void *)self;
MTAudioProcessingTapRef tapRef;
OSStatus err = MTAudioProcessingTapCreate(kCFAllocatorDefault, &callbacks,
kMTAudioProcessingTapCreationFlag_PostEffects, &tapRef);
if (err || !tapRef) {
NSLog(#"Unable to create AudioProcessingTap.");
return;
}
//Audio Mix
AVMutableAudioMixInputParameters *inputParams = [AVMutableAudioMixInputParameters
audioMixInputParametersWithTrack:_assetTrack];
inputParams.audioTapProcessor = tapRef;
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
audioMix.inputParameters = #[inputParams];
_audioMix = audioMix;
}
- (void)updateSpectrum:(NSArray *)data
{
#autoreleasepool
{
dispatch_async(dispatch_get_main_queue(), ^{
// Forward left and right channel volume to delegate.
if (_delegate && [_delegate respondsToSelector:#selector(updateSpectrum:)]) {
[_delegate updateSpectrum:data];
}
});
}
}
#end
I was reading that the audioBuffer->mData property could be something else other then a float (ie SInt32, etc?), If that is true how to make sure i convert it properly before attempting the FFT on it?
The plot length and the real FFT magnitude result length (2^log2n)/2 are not the same.
I am new in ios developement.I am encoding a LinearPCM to MP3 in iOS.I'm trying to encode the raw PCM data from microphone to MP3 using AudioToolbox framework and Lame.And although everything seems to run fine if i record .caf format . i am getting only noise and distortions present in the encoded stream. I'm not sure that I setup AudioQueue correctly and also that I process the encoded buffer in the right wat... My code to setup audio recording:
sample project https://github.com/vecter/Audio-Queue-Services-Example
- (void)setupAudioFormat:(AudioStreamBasicDescription*)format
{
format->mSampleRate = 16000;
format->mFormatID = kAudioFormatLinearPCM;
format->mFramesPerPacket = 1;
format->mChannelsPerFrame = 1;
format->mBytesPerFrame = 2;
format->mBytesPerPacket = 2;
format->mBitsPerChannel = 16;
format->mReserved = 0;
format->mFormatFlags = kLinearPCMFormatFlagIsBigEndian |
kLinearPCMFormatFlagIsSignedInteger |
kLinearPCMFormatFlagIsPacked;
}
- (void)recordPressed:(id)sender
{
if (!playState.playing)
{
if (!recordState.recording)
{
printf("Starting recording\n");
self.mergedData =[[NSMutableData alloc] init];
[self startRecording];
}
else
{
printf("Stopping recording\n");
[self stopRecording];
}
}
else
{
printf("Can't start recording, currently playing\n");
}
}
- (void)startRecording
{
[self setupAudioFormat:&recordState.dataFormat];
recordState.currentPacket = 0;
recordState.pThis=self;
OSStatus status;
status = AudioQueueNewInput(&recordState.dataFormat,
AudioInputCallback,
&recordState,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&recordState.queue);
if (status == 0)
{
// Prime recording buffers with empty data
for (int i = 0; i < NUM_BUFFERS; i++)
{
AudioQueueAllocateBuffer(recordState.queue, 16000, &recordState.buffers[i]);
AudioQueueEnqueueBuffer (recordState.queue, recordState.buffers[i], 0, NULL);
}
status = AudioFileCreateWithURL(fileURL,
kAudioFileAIFFType,
&recordState.dataFormat,
kAudioFileFlags_EraseFile,
&recordState.audioFile);
gfp = lame_init();
lame_set_num_channels(gfp, 1);
lame_set_in_samplerate(gfp, recordState.dataFormat.mSampleRate);
lame_set_VBR(gfp, vbr_default);
lame_init_params(gfp);
if (status == 0)
{
recordState.recording = true;
status = AudioQueueStart(recordState.queue, NULL);
if (status == 0)
{
mergeData =[[NSMutableData alloc]init];
labelStatus.text = #"Recording";
}
}
}
if (status != 0)
{
[self stopRecording];
labelStatus.text = #"Record Failed";
}
}
- (void)stopRecording
{
recordState.recording = false;
AudioQueueStop(recordState.queue, true);
for(int i = 0; i < NUM_BUFFERS; i++)
{
AudioQueueFreeBuffer(recordState.queue, recordState.buffers[i]);
}
AudioQueueDispose(recordState.queue, true);
AudioFileClose(recordState.audioFile);
labelStatus.text = #"Idle";
}
Then the AudioQueue callback function calls to lame_encode_buffer and then writes the encoded buffer to file:
void AudioInputCallback(void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp * inStartTime,
UInt32 inNumberPacketDescriptions,
const AudioStreamPacketDescription * inPacketDescs)
{
RecordState * recordState = (RecordState*)inUserData;
if (!recordState->recording)
{
printf("Not recording, returning\n");
}
printf("Writing buffer %lld\n", recordState->currentPacket);
OSStatus status = AudioFileWritePackets(recordState->audioFile,
false,
inBuffer->mAudioDataByteSize,
inPacketDescs,
recordState->currentPacket,
&inNumberPacketDescriptions,
inBuffer->mAudioData);
if (status == 0)
{
recordState->currentPacket += inNumberPacketDescriptions;
}
AudioRecorderAppDelegate *this = recordState->pThis;
const int MP3_BUFFER_SIZE=inBuffer->mAudioDataByteSize*4;
unsigned char mEncodedBuffer[MP3_BUFFER_SIZE];
int encodedBytes=lame_encode_buffer_interleaved(this->gfp, (short int *)inBuffer->mAudioData , inNumberPacketDescriptions, mEncodedBuffer, MP3_BUFFER_SIZE);
NSData* data = [NSData dataWithBytes:mEncodedBuffer length:encodedBytes];
[this writeData:data];
lame_encode_flush(this->gfp, mEncodedBuffer, MP3_BUFFER_SIZE);
memset(&mEncodedBuffer, 0, sizeof(mEncodedBuffer));
AudioQueueEnqueueBuffer(recordState->queue, inBuffer, 0, NULL);
}
Appending data
- (void) writeData:(NSData *)data
{
[mergeData appendData:data];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString* docDir = [paths objectAtIndex:0];
NSString* file = [docDir stringByAppendingString:#"/lame.mp3"];
[mergeData writeToFile:file atomically:YES];
NSLog(#"%#",file);
}
Can anybody advise what's wrong here?
else post already done sample project?
Try this
void AQRecorder::MyInputBufferHandler( void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp * inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription* inPacketDesc)
{
AQRecorder *aqr = (AQRecorder *)inUserData;
// NSLog(#"%f",inStartTime->mSampleTime);
try
{
if (inNumPackets > 0)
{
AudioFileWritePackets(aqr->mRecordFile, FALSE, inBuffer->mAudioDataByteSize, inPacketDesc, aqr->mRecordPacket, &inNumPackets, inBuffer->mAudioData);
aqr->mRecordPacket += inNumPackets;
int MP3_SIZE =inBuffer->mAudioDataByteSize * 4;
unsigned char mp3_buffer[MP3_SIZE];
AppDelegate *delegate =[[UIApplication sharedApplication]delegate];
lame_t lame = lame_init();
lame_set_in_samplerate(lame, 44100);
lame_set_VBR(lame, vbr_default);
lame_init_params(lame);
// int encodedBytes=lame_encode_buffer_interleaved(lame, (short int *)inBuffer->mAudioData , inNumPackets, mp3_buffer, MP3_SIZE);
int encodedBytes = lame_encode_buffer(lame, (short*)inBuffer->mAudioData, (short*)inBuffer->mAudioData, inNumPackets, mp3_buffer, MP3_SIZE);
[delegate.mp3AudioData appendBytes:mp3_buffer length:encodedBytes];
if (inBuffer->mAudioDataByteSize != 0) {
}
else
{
int encode=lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
[delegate.mp3AudioData appendBytes:mp3_buffer length:encode];
}
lame_close(lame);
}
if (aqr->IsRunning())
{
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
} catch (CAXException e)
{
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
}
In my case this logic worked :
int encodedBytes=lame_encode_buffer_interleaved(lame, (short int *)inBuffer->mAudioData , inNumPackets, mp3_buffer, MP3_SIZE);
NSMutableData *data1=[[NSMutableData alloc]initWithBytes:mp3_buffer length:encodedBytes];
[this writeData:data];