基于iOS的网络音视频实时传输系统(H264+pcm+videoToolbox)无第三方实现

代码下载
在这里插入图片描述
此图为客户端采集数据,下面的视频是摄像头采集的数据
上面的视频是摄像头解码h264的试试绘制效果
IP地址 是 同一个局域网内另外一台设备的ip地址,在wifi里面详情就能看到

在这里插入图片描述

上图是tcp接收端的显示效果

ios 录音播放udp数据包传输过来的pcmData流
在这篇文章中,我实现了在局域网内通过udp协议 完成了pcm音频的录音与播放,
于是想把视频也能够通过局域网达到传输的效果,
这就有了本篇代码的实现方案。

最开始我和音频一样采用了udp的方案,发现了视频花屏非常严重,
意识到是udp丢包导致的,于是转换方案采用了tcp的方案,tcp不再丢包,
但在tcp接收端处理视频流的地方,遇到了分包和粘包的问题,
解决分包、粘包,只能通过协议包头和内容长度机制来解决。

以下是分包、粘包的解决思路:

由于tcp是可靠性连接,那么数据都不会丢失。
当我收到第一个包的前4个字节,我就可以解析这4个字节,
判定是否为发送端定义的包头,如果和包头一致,
我们就知道 这是视频流数据的包头了,然后再继续解析4个字节
,一般我们将这4个字节解析成为包内容的长度,然后我们判定
我们现在的包是否达到了包内容的长度,如果不够就继续接收下去
如果够了,就把包内容解析出来,解析完成后把数据缓冲清空。

总结上面这一段的意思
1.来了任何的数据都拼接到缓冲队列的最后
2.开一个while循环,当字节头大于4个字节就开始解析,
解析出包体长度后,如果当前缓冲长度不够,就继续等待数据拼接
3.如果缓冲长度够了,就解析这段内容,然后将这段内容从缓冲队列中移除。

代码一

// 接收到了数据 自动回调  sock客户端
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
   [self.currentLock lock];
    //收到一段data
   [self.completeData appendData:data];
   [self.currentLock unlock];
   
   [sock readDataWithTimeout:-1 tag:0];
}

while缓冲的内容,这里加了一个自动释放池防止内存增长过快


- (void)receiveDataHanle {
    while (!_exit) {
        @autoreleasepool {
            if (self.completeData.length <16) {
                usleep(20*1000);
                continue;
            }
            //缓冲队列大小大于16才能继续处理
            NSData *data = [self.completeData copy];
            NSRange range = NSMakeRange(0, 4);
            NSData *headData = [data subdataWithRange:range];
            int head= [self intWithData:headData];
            
            NSRange countRange = NSMakeRange(4,4);
            NSData *countData = [data subdataWithRange:countRange];
            int count = [self intWithData:countData];
            
            NSRange lengthRange = NSMakeRange(8,4);
            NSData *lengthData = [data subdataWithRange:lengthRange];
            int length = [self intWithData:lengthData];
            
            
            NSRange typeRange = NSMakeRange(12,4);
            NSData *typeData = [data subdataWithRange:typeRange];
            int type = [self intWithData:typeData];
            int completeDataLength = length+16;
            //打印字节头  序列号 消息内容长度  这个包总长
            NSLog(@"head=%d count=%d bodylength=%d completeDataLength=%d completeData.length=%lu",head,count,length,completeDataLength,(unsigned long)data.length);
            
            if (data.length>=completeDataLength) {
                //取出body数据
                NSRange dataRange = NSMakeRange(16, length);
                NSData *mData = [data subdataWithRange:dataRange];
                NSString *msg = [NSString stringWithFormat:@"序号[%d]类型[%@]应收长度[%d]收到长度[%lu]",count,type==0?@"A":@"V",length,(unsigned long)mData.length];
                //移除缓冲队列里面处理的部分
                NSLog(@"%@",msg);
                NSLog(@"[before]%lu",(unsigned long)self.completeData.length);
                [self.currentLock lock];
                self.completeData = [[self.completeData subdataWithRange:NSMakeRange(completeDataLength, self.completeData.length - completeDataLength)] mutableCopy];
                [self.currentLock unlock];
                NSLog(@"[after]%lu",(unsigned long)self.completeData.length);
                if (type == 0) {
                    // 音频
                    [self.audioPlayer addPCM:(void *)mData.bytes length:(UInt32)mData.length timeStamp:count];
                }else if (type == 1) {
                    // 视频
                    [self.decoder startH264DecodeWithVideoData:(char *)mData.bytes andLength:(int)mData.length andReturnDecodedData:^(CVPixelBufferRef pixelBuffer) {
                        [self.playView displayPixelBuffer:pixelBuffer];
                    }];
                }
                
            } else {
                //遇到了拆包 需要继续接收 直到满足 包大小;
                usleep(10*1000);
                continue;
            }
        }
       
    }
}

可以看下发送端的逻辑
视频发送

// 默认情况下,为30 fps,意味着该函数每秒调用30次
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    
    // 获取输入设备数据,有可能是音频有可能是视频
    if (captureOutput == self.videoOutput) {
        if ([sendSocket isConnected]) {
            // 收到数据,开始编码
            [self.videoEncoder startH264EncodeWithSampleBuffer:sampleBuffer andReturnData:^(NSData *data) {
                
                _count++;
                NSMutableData *mData = [[NSMutableData alloc] init];
                NSData *headData = [self bytewithInt:60000];//协议头 4位 (0-4)
                NSData *countData = [self bytewithInt:_count];//发到第几个包 4位 (4-8)
                NSData *legnthData = [self bytewithInt:(int)data.length];//当前包的长度 4位 (8-12)
                NSData *dataType = [self bytewithInt:1];//type 1为视频
                [mData appendData:headData];
                [mData appendData:countData];
                [mData appendData:legnthData];
                [mData appendData:dataType];
                [mData appendData:data];
                NSLog(@"序号[%d]视频 %lu data[%d]",_count,(unsigned long)data.length,mData.length);
                [sendSocket writeData:[mData copy] withTimeout:60 tag:201];
                [self.decoder startH264DecodeWithVideoData:(char *)data.bytes andLength:(int)data.length andReturnDecodedData:^(CVPixelBufferRef pixelBuffer) {
                        [self.playView displayPixelBuffer:pixelBuffer];
                }];
            }];
        }
    }
}

音频发送

- (void)didReceiveATAudioData:(NSData*)data {
    _count++;
    NSMutableData *mData = [[NSMutableData alloc] init];
    NSData *headData = [self bytewithInt:60000];//协议头 4位 (0-4)
    NSData *countData = [self bytewithInt:_count];//发到第几个包 4位 (4-8)
    NSData *legnthData = [self bytewithInt:(int)data.length];//当前包的长度 4位 (8-12)
    NSData *dataType = [self bytewithInt:0];//type 0为音频
    [mData appendData:headData];
    [mData appendData:countData];
    [mData appendData:legnthData];
    [mData appendData:dataType];
    [mData appendData:data];

    NSLog(@"音频 data%lu",(unsigned long)data.length);
    if ([sendSocket isConnected]) {
        [sendSocket writeData:[mData copy] withTimeout:60 tag:200];
    }
}

这两段代码只是为了让你理解发送端会将int型的固定长度的约定字段 转换成二进制,
而接收端会则会将二进制再次转换为约定字段,来判断协议头,当前序号,是音频还是视频,内容长度。没有这些约定内容,就算你有数据也不知道如何解析。

解决了这个问题后,可以看到tcp 的视频效果远比udp的花屏效果好,
而视频采集转码,使用的都是videotoolbox转换成H264

播放也是用videotoolbox再将H264转换回来。

附byte 和data型数据的互转
这个要求都是用4位的,如果你用别的位数,可以写个循环即可。

- (NSData * )bytewithInt:(int )i {
    Byte b1=i & 0xff;
    Byte b2=(i>>8) & 0xff;
    Byte b3=(i>>16) & 0xff;
    Byte b4=(i>>24) & 0xff;
    Byte byte[] = {b4,b3,b2,b1};
    NSData *data = [NSData dataWithBytes:byte length:sizeof(byte)];
    return data;
}

- (int)intWithData:(NSData *)data {
    Byte *byteK = (Byte *)data.bytes;
    int valueK;
    valueK = (int) (((byteK[0] & 0xFF)<<24)
                    |((byteK[1] & 0xFF)<<16)
                    |((byteK[2] & 0xFF)<<8)
                    |(byteK[3] & 0xFF));
    return valueK;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
1. 下载阿里云视频播放器SDK,解压后得到两个文件夹:aliyun-sdk-player-android和aliyun-sdk-player-ios。 2. 在React Native项目中安装react-native-aliplayer插件,使用命令:npm install react-native-aliplayer --save。 3. 在android/app/build.gradle文件中添加如下代码: ```gradle dependencies { implementation project(':react-native-aliplayer') ... } ``` 4. 在MainActivity.java中添加如下代码: ```java import com.aliplayer.AliPlayerPackage; public class MainActivity extends ReactActivity { @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new AliPlayerPackage() ); } ... } ``` 5. 在ios项目中,将aliyun-sdk-player-ios文件夹拖入项目中,并在Build Phases的Link Binary With Libraries中添加以下库: - libstdc++.6.tbd - libz.tbd - libresolv.tbd - libbz2.tbd - AVFoundation.framework - AudioToolbox.framework - CoreMedia.framework - MediaPlayer.framework - SystemConfiguration.framework - UIKit.framework - VideoToolbox.framework 6. 在AppDelegate.m中添加如下代码: ```objective-c #import "AliPlayer.h" ... - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [AliPlayer registerWithAppKey:@"your-app-key"]; ... } ``` 7. 在React Native中使用AliPlayer组件进行视频播放,例如: ```jsx import React from 'react'; import { View } from 'react-native'; import AliPlayer from 'react-native-aliplayer'; export default function App() { return ( <View style={{ flex: 1 }}> <AliPlayer source={{ uri: 'http://player.alicdn.com/video/aliyunmedia.mp4', }} style={{ flex: 1 }} /> </View> ); } ``` 以上就是React Native集成阿里云视频播放器SDK的步骤。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值