iOS 播放pcm文件 播放udp数据包传输过来的pcmData

由于项目需要 自己用socket 加udp做一个小区物业和业主的视频通话功能。我们需要采集麦克风声音传输出去和播放接收到的udp传过来的pcm原始音频文件。因为我们这个功能很简单,只需要通话几秒钟  最多20秒。就没有做音频编码和解码的操作,直接传输的pcm原始数据。

但是我在网上找了很多播放pcm文件的代码,很少找到即时播放传过来的udp包的,一般都是播放本地的pcm或者解码过来的稳定的数据流。不能满足我udp经常丢包 导致播放停止的问题。

结合了很多别人的播放器和数据包的处理 自己改了一下 这个播放器能满足我的需要了 希望能帮到有同样需求的人。



AudioQueuePlay.h

~~~

//

//  AudioQueuePlay.h

//  udp

//

//  Created by  on 3/19/18.

//  Copyright © 2018 . All rights reserved.

//

#import

#import

@interfaceAudioQueuePlay :NSObject

/**

 播放音频pcm数据 udp包传过来的

 @paramdata pcm data

 */

- (void)playWithData: (NSData*)data;

/**

 当播放不出声音的时候 重启一下播放器

 */

- (void)resetPlay;

/**

 开始播放

 */

- (void)startPlay;

/**

 停止播放

 */

- (void)stopPlay;

@end

~~~

我们的需求是通过socket 传输udp语音包(pcm)和图片实现视频通话 但由于udp的不可靠性 网速慢的时候 播放声音有问题 在网上找了好久的资料 终于找到能解决这个问题的代码  下面是我在网上看到的有意义的资料

 码农人生博客 比较详细了 网易云音乐的大神

http://msching.github.io/blog/categories/audio/

 从这里我找到了怎么填充空数据包的方法  赞

 https://my.oschina.net/xikan/blog/483929

 这个就是我现在的播放器来源 感谢  结合第二个加空数据包的 就是我完整的例子啦

https://segmentfault.com/a/1190000010177336

~~~

#import "AudioQueuePlay.h"

#define MIN_SIZE_PER_FRAME5000//缓冲区大小  如果一次性数据超出 会内存溢出崩溃

#define QUEUE_BUFFER_SIZE3      //队列缓冲个数

@interface AudioQueuePlay() {


    AudioQueueRefaudioQueue;                                //音频播放队列

    AudioStreamBasicDescription _audioDescription;

    AudioQueueBufferRefaudioQueueBuffers[QUEUE_BUFFER_SIZE];//音频缓存

    BOOLaudioQueueBufferUsed[QUEUE_BUFFER_SIZE];            //判断音频缓存是否在使用

    NSLock*sysnLock;

    NSMutableData*tempData;

    OSStatusosState;

}

@property (nonatomic, assign) BOOL isRuning;

@end

@implementation AudioQueuePlay

- (instancetype)init

{

    self= [superinit];

    if(self) {

        sysnLock= [[NSLockalloc]init];

        // 播放PCM使用

        //设置音频参数 这些参数根据传过来的音频参数自己设置 如果有偏差 可能会出现杂音或者卡顿的情况

        _audioDescription.mSampleRate = 22050;//采样率

        _audioDescription.mFormatID = kAudioFormatLinearPCM;

        // 下面这个是保存音频数据的方式的说明,如可以根据大端字节序或小端字节序,浮点数或整数以及不同体位去保存数据

        _audioDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;

        //1单声道 2双声道

        _audioDescription.mChannelsPerFrame = 1;

        //每一个packet一侦数据,每个数据包下的桢数,即每个数据包里面有多少桢

        _audioDescription.mFramesPerPacket = 1;

        //每个采样点16bit量化 语音每采样点占用位数

        _audioDescription.mBitsPerChannel = 16;

        _audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel / 8) * _audioDescription.mChannelsPerFrame;

        //每个数据包的bytes总数,每桢的bytes数*每个数据包的桢数

        _audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrame * _audioDescription.mFramesPerPacket;


        // 使用player的内部线程播放 新建输出

        AudioQueueNewOutput(&_audioDescription,AudioPlayerAQInputCallback, (__bridgevoid*_Nullable)(self),nil,0,0, &audioQueue);


        // 设置音量

        AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);

        // 初始化需要的缓冲区

        for(inti =0; i

            audioQueueBufferUsed[i] = false;

            osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);

        }


    }

    return self;

}

//添加静音包  当来源音频数据不足的时候 往里面添加静音包

- (void)fillNullData{

            if(_isRuning) {

                BOOLisNull =YES;

                for(inti =0; i<3; i++) {

                   BOOLused =audioQueueBufferUsed[i];

                    if(used) {

                        isNull =NO;

                    }

                }

                if(isNull) {

                    //填空数据包

//                    NSLog(@"填空数据包");

                    NSMutableData*tmpData = [[NSMutableDataalloc]init];

                    for(inti=0; i<600; i++) {

                        [tmpDataappendBytes:"\x00"length:1];

                    }

                    [selfplayWithData:tmpData];

                }

            }

}

- (void)startPlay{

    [self resetPlay];

}

- (void)stopPlay{

    _isRuning = NO;

    if (audioQueue) {

        AudioQueueStop(audioQueue,true);

    }

}

- (void)resetPlay {

    _isRuning = NO;

    if(audioQueue!=nil) {

        AudioQueueReset(audioQueue);

        //延迟执行

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            osState=AudioQueueStart(audioQueue,NULL);

            if(osState!=noErr) {

                NSLog(@"AudioQueueStart Error");

            }

            _isRuning=YES;

        });

    }

}

// 播放相关

-(void)playWithData:(NSData*)data {


    if(!_isRuning) {

        return;

    }

    [sysnLock lock];

    tempData = [NSMutableData new];

    [tempDataappendData:data];

    // 得到数据

    NSUInteger len = tempData.length;

    Byte*bytes = (Byte*)malloc(len);

    [tempDatagetBytes:byteslength: len];

    inti =0;

    while (true) {

        if (!audioQueueBufferUsed[i]) {

            audioQueueBufferUsed[i] = true;

            break;

        }else{

            i++;

            if(i >=QUEUE_BUFFER_SIZE) {

                i =0;

            }

        }

    }

    AudioQueueBufferRef buffer = audioQueueBuffers[i];

    buffer ->mAudioDataByteSize=  (unsignedint)len;

    // 把bytes的头地址开始的len字节给mAudioData

    memcpy(audioQueueBuffers[i] ->mAudioData, bytes, len);

    free(bytes);

    AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL);

    printf("本次播放数据大小: %lu \n", len);

    [sysnLock unlock];

}

// 回调回来把buffer状态设为未使用

staticvoidAudioPlayerAQInputCallback(void* inUserData,AudioQueueRefaudioQueueRef,AudioQueueBufferRefaudioQueueBufferRef) {


    AudioQueuePlay* player = (__bridgeAudioQueuePlay*)inUserData;


    [playerresetBufferState:audioQueueRefand:audioQueueBufferRef];

//    NSLog(@"AudioPlayerAQInputCallback");

}

- (void)resetBufferState:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {


    for(inti =0; i

        // 将这个buffer设为未使用

        if(audioQueueBufferRef ==audioQueueBuffers[i]) {

            audioQueueBufferUsed[i] = false;

        }

    }

    [self fillNullData];

}

// ************************** 内存回收 **********************************

- (void)dealloc {


    if(audioQueue!=nil) {

        AudioQueueStop(audioQueue,true);

    }


    audioQueue = nil;

    sysnLock = nil;

}

@end

~~~

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

推荐阅读更多精彩内容