欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于iOS的网络音视频实时传输系统(二)- 捕获音视频数据

程序员文章站 2022-07-06 22:53:29
...

下载


GitHub:

client 端:https://github.com/AmoAmoAmo/Smart_Device_Client

server端:https://github.com/AmoAmoAmo/Smart_Device_Server

另还写了一份macOS版的server,但是目前还有一些问题,有兴趣的去看看吧:https://github.com/AmoAmoAmo/Server_Mac



获取音视频数据,这里使用的是AVCaptureSession,可以方便地自定义相机界面,看起来大概是这样的:

基于iOS的网络音视频实时传输系统(二)- 捕获音视频数据


关于AVCaptureSession的使用步骤这里不赘述,网上一搜一大把,无非就是设置输入输出对象,设置代理、预览层之类。

这里直接上代码了,具体注释都有哦。


过程


初始化session

    self.avSession = [[AVCaptureSession alloc] init];
    self.avSession.sessionPreset = AVCaptureSessionPreset640x480;
     
    // 设备对象 (audio)
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    // 输入流
    AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:nil];
    // 输出流
    AVCaptureAudioDataOutput *audioOutput = [[AVCaptureAudioDataOutput alloc] init];
    [audioOutput setSampleBufferDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    // 添加输入输出流
    if ([_avSession canAddInput:audioInput]) {
        [_avSession addInput:audioInput];
    }
    if ([_avSession canAddOutput:audioOutput]) {
        [_avSession addOutput:audioOutput];
    }
    
    
    // 设备对象 (video)
    AVCaptureDevice *inputCamera = nil;
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices)
    {
        if ([device position] == AVCaptureDevicePositionBack)
        {
            inputCamera = device;
        }
    }
    
    AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:nil];
    
    if ([self.avSession canAddInput:videoInput]) {
        [self.avSession addInput:videoInput];
    }
    
    self.videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    [self.videoOutput setAlwaysDiscardsLateVideoFrames:NO];  // 是否抛弃延迟的帧:NO
    
    [self.videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
    [self.videoOutput setSampleBufferDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    if ([self.avSession canAddOutput:self.videoOutput]) {
        [self.avSession addOutput:self.videoOutput];
    }
    AVCaptureConnection *connection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
    [connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight]; // 因为要横屏,所以让输出视频图像旋转90°
    
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.avSession];
    [self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [[self.previewLayer connection] setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];// 预览层也让它右转90°
    [self.previewLayer setFrame:self.captureView.bounds];
    [self.captureView.layer insertSublayer:self.previewLayer above:0];


AVCapture输出流代理

设备捕获到的音视频数据都在这里取得,然后进行编码等操作。

// 默认情况下,为30 fps,意味着该函数每秒调用30次
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    // 获取输入设备数据,有可能是音频有可能是视频
    if (captureOutput == self.videoOutput) {

        // 收到数据,开始编码
        [self.videoEncoder startH264EncodeWithSampleBuffer:sampleBuffer andReturnData:^(NSData *data) {

            // 当TCP需要开始传输数据时,开始传编码后的数据
            if (self.isReadyToEncode) {
                // 返回一个编码后的数据 data,传给TCP 开始发送给client
                [self.tcpServer sendVideoDataToClientWithData:data];
            }
        }];
     
    }
    else
    {
        // 音频
        dispatch_sync(mEncodeQueue, ^{
            // block回调 返回编码后的音频数据
//            printf("----- audio -------\n");
            [self.audioEncoder encodeSampleBuffer:sampleBuffer completionBlock:^(NSData *encodedData, NSError *error) {
                printf("----- Audio encodedData length = %d ----- \n", (int)[encodedData length]);
                // 写入socket
                if (self.isReadyToEncode) {
                    [self.tcpServer sendAudioDataToClientWithData:encodedData];
                }
            }];
        });
        
    }
}



问题记录

server端使用VideoToolbox进行视频编码时,发现在client端解析出来的视频画面方向发生了旋转,client端如下所示:

基于iOS的网络音视频实时传输系统(二)- 捕获音视频数据

首先排除了client端接收数据或硬解码的过程中的问题,因为把server端编码后的数据使用NSFileHandle写入文件,用VLC播放的情况与上图所示一致。

首先,判断源及目标图像是否翻转:

BOOL isFlipped = CVImageBufferIsFlipped(pixelBuffer);
NSLog(@"pixelBuffer is %s", isFlipped ? "flipped" : "not flipped");
打印结果如下:

pixelBuffer is flipped
发现在原始数据中,图像都是翻转的

找了好久,最后发现在server端设置捕获视频的output的地方把它旋转(左右横屏):

AVCaptureConnection *connection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
[connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight]; // 因为要横屏,所以让输出视频图像旋转90°

运行一下:

基于iOS的网络音视频实时传输系统(二)- 捕获音视频数据

问题就这么解决啦



相关文章


基于iOS的网络音视频实时传输系统(一)- 前言

基于iOS的网络音视频实时传输系统(二)- 捕获音视频数据

基于iOS的网络音视频实时传输系统(三)- VideoToolbox编码音视频数据为H264、AAC

基于iOS的网络音视频实时传输系统(四)- 自定义socket协议(TCP、UDP)

基于iOS的网络音视频实时传输系统(五)- 使用VideoToolbox硬解码H264

基于iOS的网络音视频实时传输系统(六)- AudioQueue播放音频,OpenGL渲染显示图像