IOS PCM播放以及降噪

1.PCM介绍

脉冲编码调制(Pulse Code Modulation,PCM),由A.里弗斯于bai1937年提出的,这一概念为数字du通信奠zhi定了基础,60年代它开始应用于市内电话网以扩充容量,使已有音频电缆的大部分芯线的传输容量扩大24~48倍。

2.XBEchoCancellation 使用

git地址

3.** 使用方法**

XBEchoCancellation.h

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>

typedef enum : NSUInteger {
    XBEchoCancellationRate_8k = 8000,
    XBEchoCancellationRate_20k = 20000,
    XBEchoCancellationRate_44k = 44100,
    XBEchoCancellationRate_96k = 96000
} XBEchoCancellationRate;

#define kRate (XBEchoCancellationRate_8k) //采样率
#define kChannels   (1)//声道数
#define kBits       (16)//位数


typedef enum : NSUInteger {
    XBEchoCancellationStatus_open,
    XBEchoCancellationStatus_close
} XBEchoCancellationStatus;

typedef void (^XBEchoCancellation_inputBlock)(AudioBufferList *bufferList);
typedef void (^XBEchoCancellation_outputBlock)(AudioBufferList *bufferList,UInt32 inNumberFrames);

@interface XBEchoCancellation : NSObject
///是否开启了回声消除
@property (nonatomic,assign,readonly) XBEchoCancellationStatus echoCancellationStatus;
@property (nonatomic,assign,readonly) AudioStreamBasicDescription streamFormat;
///录音的回调,回调的参数为从麦克风采集到的声音
@property (nonatomic,copy) XBEchoCancellation_inputBlock bl_input;
///播放的回调,回调的参数 buffer 为要向播放设备(扬声器、耳机、听筒等)传的数据,在回调里把数据传给 buffer
@property (nonatomic,copy) XBEchoCancellation_outputBlock bl_output;

+ (instancetype)shared;

- (void)startInput;
- (void)stopInput;

- (void)startOutput;
- (void)stopOutput;

- (void)openEchoCancellation;
- (void)closeEchoCancellation;

///开启服务,需要另外去开启 input 或者 output 功能
- (void)startService;
///停止所有功能(包括录音和播放)
- (void)stop;

// 音量控制
// output: para1 输出数据
// input : para2 输入数据
//         para3 输入长度
//         para4 音量控制参数,有效控制范围[0,100]
// 超过100,则为倍数,倍数为in_vol减去98的数值
+ (void)volume_controlOut_buf:(short *)out_buf in_buf:(short *)in_buf in_len:(int)in_len in_vol:(float)in_vol;

@end

XBEchoCancellation.m

#import "XBEchoCancellation.h"

typedef struct MyAUGraphStruct{
    AUGraph graph;
    AudioUnit remoteIOUnit;
} MyAUGraphStruct;


@interface XBEchoCancellation ()
{
    MyAUGraphStruct myStruct;
}
@property (nonatomic,assign) BOOL isCloseService; //没有声音服务
@property (nonatomic,assign) BOOL isNeedInputCallback; //需要录音回调(获取input即麦克风采集到的声音回调)
@property (nonatomic,assign) BOOL isNeedOutputCallback; //需要播放回调(output即向发声设备传递声音回调)

@end

@implementation XBEchoCancellation

@synthesize streamFormat;

+ (instancetype)shared
{
    return [self new];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static XBEchoCancellation *cancel = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cancel = [super allocWithZone:zone];
    });
    return cancel;
}
- (instancetype)init
{
    if (self = [super init])
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _echoCancellationStatus = XBEchoCancellationStatus_close;
            self.isCloseService = YES;
            [self startService];
        });
    }
    return self;
}

- (void)startInput
{
    [self startService];
    self.isNeedInputCallback = YES;
}
- (void)stopInput
{
    self.isNeedInputCallback = NO;
}
- (void)startOutput
{
    [self startService];
    self.isNeedOutputCallback = YES;
}
- (void)stopOutput
{
    self.isNeedOutputCallback = NO;
}
- (void)startService
{
    if (self.isCloseService == NO)
    {
        return;
    }
    
    [self setupSession];
    
    [self createAUGraph:&myStruct];
    
    [self setupRemoteIOUnit:&myStruct];
    
    [self startGraph:myStruct.graph];
    
    AudioOutputUnitStart(myStruct.remoteIOUnit);
    
    self.isCloseService = NO;
    NSLog(@"startService完成");
}

- (void)stop
{
    self.bl_input = nil;
    self.bl_output = nil;
    [self stopGraph:myStruct.graph];
}
- (void)openEchoCancellation
{
    if (self.isCloseService == YES)
    {
        return;
    }
    [self openOrCloseEchoCancellation:0];
}
- (void)closeEchoCancellation
{
    if (self.isCloseService == YES)
    {
        return;
    }
    [self openOrCloseEchoCancellation:1];
}
///0 开启,1 关闭
-(void)openOrCloseEchoCancellation:(UInt32)newEchoCancellationStatus
{
    if (self.isCloseService == YES)
    {
        return;
    }
    UInt32 echoCancellation;
    UInt32 size = sizeof(echoCancellation);
    CheckError(AudioUnitGetProperty(myStruct.remoteIOUnit,
                                    kAUVoiceIOProperty_BypassVoiceProcessing,
                                    kAudioUnitScope_Global,
                                    0,
                                    &echoCancellation,
                                    &size),
               "kAUVoiceIOProperty_BypassVoiceProcessing failed");
    if (newEchoCancellationStatus == echoCancellation)
    {
        return;
    }
    
    CheckError(AudioUnitSetProperty(myStruct.remoteIOUnit,
                                    kAUVoiceIOProperty_BypassVoiceProcessing,
                                    kAudioUnitScope_Global,
                                    0,
                                    &newEchoCancellationStatus,
                                    sizeof(newEchoCancellationStatus)),
               "AudioUnitSetProperty kAUVoiceIOProperty_BypassVoiceProcessing failed");
    _echoCancellationStatus = newEchoCancellationStatus == 0 ? XBEchoCancellationStatus_open : XBEchoCancellationStatus_close;
}

-(void)startGraph:(AUGraph)graph
{
    CheckError(AUGraphInitialize(graph),
               "AUGraphInitialize failed");
    CheckError(AUGraphStart(graph),
               "AUGraphStart failed");
    _echoCancellationStatus = XBEchoCancellationStatus_open;
}

- (void)stopGraph:(AUGraph)graph
{
    if (self.isCloseService == YES)
    {
        return;
    }
    CheckError(AUGraphUninitialize(graph),
               "AUGraphUninitialize failed");
    CheckError(AUGraphStop(graph),
               "AUGraphStop failed");
    self.isCloseService = YES;
    _echoCancellationStatus = XBEchoCancellationStatus_close;
}


-(void)createAUGraph:(MyAUGraphStruct*)augStruct{
    //Create graph
    CheckError(NewAUGraph(&augStruct->graph),
               "NewAUGraph failed");
    
    //Create nodes and add to the graph
    AudioComponentDescription inputcd = {0};
    inputcd.componentType = kAudioUnitType_Output;
    inputcd.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
    inputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
    
    AUNode remoteIONode;
    //Add node to the graph
    CheckError(AUGraphAddNode(augStruct->graph,
                              &inputcd,
                              &remoteIONode),
               "AUGraphAddNode failed");
    
    //Open the graph
    CheckError(AUGraphOpen(augStruct->graph),
               "AUGraphOpen failed");
    
    //Get reference to the node
    CheckError(AUGraphNodeInfo(augStruct->graph,
                               remoteIONode,
                               &inputcd,
                               &augStruct->remoteIOUnit),
               "AUGraphNodeInfo failed");
}


-(void)setupRemoteIOUnit:(MyAUGraphStruct*)augStruct{
    //Open input of the bus 1(input mic)
    UInt32 inputEnableFlag = 1;
    CheckError(AudioUnitSetProperty(augStruct->remoteIOUnit,
                                    kAudioOutputUnitProperty_EnableIO,
                                    kAudioUnitScope_Input,
                                    1,
                                    &inputEnableFlag,
                                    sizeof(inputEnableFlag)),
               "Open input of bus 1 failed");
    
    //Open output of bus 0(output speaker)
    UInt32 outputEnableFlag = 1;
    CheckError(AudioUnitSetProperty(augStruct->remoteIOUnit,
                                    kAudioOutputUnitProperty_EnableIO,
                                    kAudioUnitScope_Output,
                                    0,
                                    &outputEnableFlag,
                                    sizeof(outputEnableFlag)),
               "Open output of bus 0 failed");
    
    UInt32 mFramesPerPacket = 1;
    UInt32 mBytesPerFrame = kChannels * kBits / 8;
    //Set up stream format for input and output
    streamFormat.mFormatID = kAudioFormatLinearPCM;
    streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    streamFormat.mSampleRate = kRate;
    streamFormat.mFramesPerPacket = mFramesPerPacket;
    streamFormat.mBytesPerFrame = mBytesPerFrame;
    streamFormat.mBytesPerPacket = mBytesPerFrame * mFramesPerPacket;
    streamFormat.mBitsPerChannel = kBits;
    streamFormat.mChannelsPerFrame = kChannels;
    
    
    
    
    CheckError(AudioUnitSetProperty(augStruct->remoteIOUnit,
                                    kAudioUnitProperty_StreamFormat,
                                    kAudioUnitScope_Input,
                                    0,
                                    &streamFormat,
                                    sizeof(streamFormat)),
               "kAudioUnitProperty_StreamFormat of bus 0 failed");
    
    CheckError(AudioUnitSetProperty(augStruct->remoteIOUnit,
                                    kAudioUnitProperty_StreamFormat,
                                    kAudioUnitScope_Output,
                                    1,
                                    &streamFormat,
                                    sizeof(streamFormat)),
               "kAudioUnitProperty_StreamFormat of bus 1 failed");
    
    //    UInt32 maxFramesPerSlice = 4096;
    //    CheckError(AudioUnitSetProperty(augStruct->remoteIOUnit,
    //                                    kAudioUnitProperty_MaximumFramesPerSlice,
    //                                    kAudioUnitScope_Global,
    //                                    0,
    //                                    &maxFramesPerSlice,
    //                                    sizeof(UInt32)),
    //               "couldn't set max frames per slice on kAudioUnitSubType_RemoteIO");
    
    
    //    Float32 preferredBufferSize = 0.01;
    //    AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration,
    //                            sizeof(preferredBufferSize),
    //                            &preferredBufferSize);
    //
    //    AudioSessionSetActive(true);
    
    
    AURenderCallbackStruct input;
    input.inputProc = InputCallback_xb;
    input.inputProcRefCon = (__bridge void *)(self);
    CheckError(AudioUnitSetProperty(augStruct->remoteIOUnit,
                                    kAudioOutputUnitProperty_SetInputCallback,
                                    kAudioUnitScope_Output,
                                    1,
                                    &input,
                                    sizeof(input)),
               "couldnt set remote i/o render callback for output");
    
    AURenderCallbackStruct output;
    output.inputProc = outputRenderTone_xb;
    output.inputProcRefCon = (__bridge void *)(self);
    CheckError(AudioUnitSetProperty(augStruct->remoteIOUnit,
                                    kAudioUnitProperty_SetRenderCallback,
                                    kAudioUnitScope_Input,
                                    0,
                                    &output,
                                    sizeof(output)),
               "kAudioUnitProperty_SetRenderCallback failed");
    
    
    //    UInt32 flag=0;
    //    CheckError(AudioUnitSetProperty(augStruct->remoteIOUnit,
    //                                                kAudioUnitProperty_ShouldAllocateBuffer,
    //                                                kAudioUnitScope_Output,
    //                                                1,
    //                                                &flag,
    //                                                sizeof(flag)),
    //               "couldn't set property for ShouldAllocateBuffer");
    
    
}

-(void)createRemoteIONodeToGraph:(AUGraph*)graph
{
    
}

-(void)setupSession
{
    NSError *error = nil;
    AVAudioSession* session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:&error];
    [session setActive:YES error:nil];
}


#pragma mark - 其他方法

static void CheckError(OSStatus error, const char *operation)
{
    if (error == noErr) return;
    char errorString[20];
    // See if it appears to be a 4-char-code
    *(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(error);
    if (isprint(errorString[1]) && isprint(errorString[2]) &&
        isprint(errorString[3]) && isprint(errorString[4])) {
        errorString[0] = errorString[5] = '\'';
        errorString[6] = '\0';
    } else
        // No, format it as an integer
        sprintf(errorString, "%d", (int)error);
    fprintf(stderr, "Error: %s (%s)\n", operation, errorString);
    exit(1);
}

OSStatus InputCallback_xb(void *inRefCon,
                          AudioUnitRenderActionFlags *ioActionFlags,
                          const AudioTimeStamp *inTimeStamp,
                          UInt32 inBusNumber,
                          UInt32 inNumberFrames,
                          AudioBufferList *ioData){
    
    XBEchoCancellation *echoCancellation = (__bridge XBEchoCancellation*)inRefCon;
    if (echoCancellation.isNeedInputCallback == NO)
    {
        //        NSLog(@"没有开启声音输入回调");
        return noErr;
    }
    MyAUGraphStruct *myStruct = &(echoCancellation->myStruct);
    
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mData = NULL;
    bufferList.mBuffers[0].mDataByteSize = 0;
    
    
    
    
    AudioUnitRender(myStruct->remoteIOUnit,
                    ioActionFlags,
                    inTimeStamp,
                    1,
                    inNumberFrames,
                    &bufferList);
    //    AudioBuffer buffer = bufferList.mBuffers[0];
    
    if (echoCancellation.bl_input)
    {
        echoCancellation.bl_input(&bufferList);
    }
    
    //    NSLog(@"InputCallback");
    return noErr;
}
OSStatus outputRenderTone_xb(
                             void *inRefCon,
                             AudioUnitRenderActionFlags     *ioActionFlags,
                             const AudioTimeStamp         *inTimeStamp,
                             UInt32                         inBusNumber,
                             UInt32                         inNumberFrames,
                             AudioBufferList             *ioData)

{
    
    //TODO: implement this function
    memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
    
    XBEchoCancellation *echoCancellation = (__bridge XBEchoCancellation*)inRefCon;
    if (echoCancellation.isNeedOutputCallback == NO)
    {
        //        NSLog(@"没有开启声音输出回调");
        return noErr;
    }
    if (echoCancellation.bl_output)
    {
        echoCancellation.bl_output(ioData,inNumberFrames);
    }
    
    //    NSLog(@"outputRenderTone");
    return 0;
}

+ (void)volume_controlOut_buf:(short *)out_buf in_buf:(short *)in_buf in_len:(int)in_len in_vol:(float)in_vol
{
    volume_control(out_buf, in_buf, in_len, in_vol);
}

// 音量控制
// output: para1 输出数据
// input : para2 输入数据
//         para3 输入长度
//         para4 音量控制参数,有效控制范围[0,100]
// 超过100,则为倍数,倍数为in_vol减去98的数值
int volume_control(short* out_buf,short* in_buf,int in_len, float in_vol)
{
    int i,tmp;
    
    // in_vol[0,100]
    float vol = in_vol - 98;
    
    if(-98 < vol  &&  vol <0 )
    {
        vol = 1/(vol*(-1));
    }
    else if(0 <= vol && vol <= 1)
    {
        vol = 1;
    }
    /*else if(1 < vol && vol <= 2)
     {
     vol = vol;
     }*/
    else if(vol <= -98)
    {
        vol = 0;
    }
    //    else if(2 = vol)
    //    {
    //        vol = 2;
    //    }
    
    for(i=0; i<in_len/2; i++)
    {
        tmp = in_buf[i]*vol;
        if(tmp > 32767)
        {
            tmp = 32767;
        }
        else if( tmp < -32768)
        {
            tmp = -32768;
        }
        out_buf[i] = tmp;
    }
    
    return 0;
}

@end


接收麦克风信号

//开启语音流
-(void)openOutputStream{
    __weak __typeof__(self) weakSelf = self;
    

    if ([XBEchoCancellation shared].bl_input == nil)
    {
        [XBEchoCancellation shared].bl_input = ^(AudioBufferList *bufferList1) {
            

           AudioBuffer buffer = bufferList1->mBuffers[0];
            NSData *pcmData = [NSData dataWithBytes:buffer.mData length:buffer.mDataByteSize];
            
        

        };
    }
    [[XBEchoCancellation shared] startInput];
}

播放PCM

if ([XBEchoCancellation shared].bl_output == nil)
               {
                   [XBEchoCancellation shared].bl_output = ^(AudioBufferList *bufferList, UInt32 inNumberFrames) {
                       AudioBuffer buffer = bufferList->mBuffers[0];
                       if(self->cacheData.length<buffer.mDataByteSize){
                           return;
                       }
                       NSData *subData = [[NSData alloc]init];
                       //截取缓存数据
                       subData=[self->cacheData subdataWithRange:NSMakeRange(0, buffer.mDataByteSize)];
//                        NSLog(@"subData=%@",subData);
                       Byte *tempByte = (Byte *)[subData bytes];
                       memcpy(buffer.mData, tempByte, buffer.mDataByteSize);
                       //移除已播放完成的数据
                       [self->cacheData replaceBytesInRange:NSMakeRange(0, buffer.mDataByteSize) withBytes:NULL length:0];

                   };
               }

               [[XBEchoCancellation shared] startOutput];
        });
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352