美文网首页
[木木方文技术分享之音视频五]FFmpeg+x264摄像头直播推

[木木方文技术分享之音视频五]FFmpeg+x264摄像头直播推

作者: 丿沧海一粟丿 | 来源:发表于2017-08-22 10:40 被阅读0次
    //
    //  LiveTelecastController.m
    //  FFmpegDemo
    //
    //  Created by huoliquankai on 2017/7/20.
    //  Copyright © 2017年 深圳机械行业协会. All rights reserved.
    //
    
    #import "LiveTelecastController.h"
    #import <AVFoundation/AVFoundation.h>
    #import "avcodec.h"
    #import "imgutils.h"
    #import "avdevice.h"
    #import "swscale.h"
    #import "x264.h"
    #include "time.h"
    #import "GPUImage.h"
    
    
    @interface LiveTelecastController () <AVCaptureVideoDataOutputSampleBufferDelegate>
    @property (nonatomic, strong)AVCaptureSession *session;
    @property (nonatomic, strong)AVCaptureDeviceInput *videoInput;
    @property (nonatomic, strong)AVCaptureVideoDataOutput *videoDataOutput;
    @property (nonatomic, strong)UIView *cameraShowView;
    @property (nonatomic, strong)AVCaptureVideoPreviewLayer *previewLayer;
    @end
    
    @implementation LiveTelecastController
    {
        AVOutputFormat *oFmt;
        AVFormatContext *pFormatCtx;
        AVStream *video_st;
        AVCodecContext *pCodecCtx;
        AVCodec *pCodec;
        AVFrame *pFrame;
        AVPacket pkt;
        int y_size;
        int framecnt;
        int encoder_h264_frame_width;
        int encoder_h264_frame_height;
        unsigned char *picture_buf;
        int picture_size;
        //
        AVStream *out_stream;
        int frame_count;
        int y_length;
        int uv_length;
        AVFormatContext *ofmt_ctx;
    //    AVFrame *yuv_frame;
        int src_height;
        int src_width;
        int64_t start_time;
        
    }
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            [self initialSession];
            [self initialCameraShowView];
        }
        return self;
    }
    
    - (void)initialSession {
        self.session = [[AVCaptureSession alloc] init];
        self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamera] error:nil];
        self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    //    表示设置摄像头返回的数据类型为YUV420SP类型
        NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
                                  [NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange],                                   kCVPixelBufferPixelFormatTypeKey,
                                  [NSNumber numberWithInt: 640], (id)kCVPixelBufferWidthKey,
                                  [NSNumber numberWithInt: 480], (id)kCVPixelBufferHeightKey,
                                  nil];
        [self.videoDataOutput setVideoSettings:outputSettings];
        dispatch_queue_t queue = dispatch_queue_create("linlinqi", NULL);
        [self.videoDataOutput setSampleBufferDelegate:self queue:queue];
        if ([self.session canAddInput:self.videoInput]) {
            [self.session addInput:self.videoInput];
        }
        if ([self.session canAddOutput:self.videoDataOutput]) {
            [self.session addOutput:self.videoDataOutput];
        } else {
            NSLog(@"failed get output");
        }
    }
    
    - (void)initialCameraShowView {
        self.cameraShowView = [[UIView alloc] initWithFrame:self.view.frame];
        [self.view addSubview:self.cameraShowView];
    }
    
    - (AVCaptureDevice *)backCamera {
        return [self cameraWithPosition:AVCaptureDevicePositionBack];
    }
    
    - (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {
        AVCaptureDeviceDiscoverySession *devicesIOS10 = [AVCaptureDeviceDiscoverySession  discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];
        
        NSArray *devicesIOS  = devicesIOS10.devices;
        for (AVCaptureDevice *device in devicesIOS) {
            if (device.position == position) {
                return device;
            }
        }
        return nil;
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        [self setUpCameraLayer];
    }
    //启动摄像头捕获数据
    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
    }
    
    - (void)setUpCameraLayer {
        if (self.previewLayer == nil) {
            self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
            UIView *view = self.cameraShowView;
            CALayer *viewLayer = [view layer];
            [viewLayer setMasksToBounds:YES];
            CGRect bounds = [view bounds];
            [self.previewLayer setFrame:bounds];
            [self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
            [viewLayer addSublayer:self.previewLayer];
        }
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        if (self.session) {
            [self streamerInit];
            [self.session startRunning];
        }
    }
    
    #pragma AVCaptureVideoDataOutputSampleBufferDelegate
    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
        CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        if ([self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo]) {
            //视频数据
            //
            CVPixelBufferLockBaseAddress(pixelBuffer, 0);
            //获取Y分量
            UInt8 *bufferPrt = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
            //获取UV分量
            UInt8 *bufferPrt1 = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
            size_t width = CVPixelBufferGetWidth(pixelBuffer);
            size_t height = CVPixelBufferGetHeight(pixelBuffer);
            size_t bytesrow0 = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
            size_t bytesrow1 = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
            UInt8 *yuv420_data = (UInt8 *)malloc(width * height * 3/2);
            /*convert NV21 data to YUV420*/
            UInt8 *pY = bufferPrt;
            UInt8 *pUV = bufferPrt1;
            UInt8 *pU = yuv420_data + width * height;
            UInt8 *pV = pU + width * height / 4;
            for (int i = 0; i < height; i ++) {
                memcpy(yuv420_data + i * width, pY + i * bytesrow0, width);
            }
            for (int j = 0; j < height/2; j ++) {
                for (int i = 0; i < width/2; i ++) {
                    *(pU++) = pUV[i<<1];
                    *(pV++) = pUV[(i<<1) + 1];
                    //*pU = pUV[2*i];
                    //pU++;
                }
                pUV += bytesrow1;
            }
            //这里可以开始使用yuv420_data去编码为视频了
            [self yuv420ToH264:yuv420_data];
            //编码完成后
            free(yuv420_data);
            CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
        }
    }
    
    - (void)streamerInit {
        int ret = 0;
        const char *address = "rtmp://192.168.0.111/live/livestream";
        encoder_h264_frame_width = 1920;
        encoder_h264_frame_height = 1080;
        src_width = 1920;
        src_height = 1080;
        y_length = encoder_h264_frame_width * encoder_h264_frame_height;
        uv_length = y_length/4;
        //
    //    av_log_set_callback(void (*callback)(void *, int, const char *, va_list))
        av_register_all();
        avformat_network_init();
        avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", address);
        if (!ofmt_ctx) {
            printf("不能打开输出");
            return;
        }
        //寻找编码器
        pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
        if (!pCodec) {
            printf("找不到编码器");
            return;
        }
        //初始化编码器操作者
        pCodecCtx = avcodec_alloc_context3(pCodec);
        pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;  //指定编码格式
        pCodecCtx->width = encoder_h264_frame_width;
        pCodecCtx->height = encoder_h264_frame_height;
        pCodecCtx->time_base.num = 1;
        pCodecCtx->time_base.den = 30;
        pCodecCtx->bit_rate = 400000;
    //    pCodecCtx->rc_max_rate = 400000;
    //    pCodecCtx->rc_max_rate = 400000;
    //    pCodecCtx->rc_buffer_size = 200000;
        pCodecCtx->gop_size = 250;
        if(ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
            pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
        }
        
        pCodecCtx->qmin = 10;
        pCodecCtx->qmax = 51;
        
        pCodecCtx->max_b_frames = 0;
        AVDictionary *dicParams = NULL;
        av_dict_set(&dicParams, "preset", "slow", 0);
        av_dict_set(&dicParams, "tune", "zerolatency", 0);
        //打开编码器
        if(avcodec_open2(pCodecCtx, pCodec, &dicParams) < 0) {
            printf("Failed to open encoder!\n");
            return;
        }
        //新建输出流
        out_stream = avformat_new_stream(ofmt_ctx, pCodec);
        if(!out_stream) {
            printf("Failed allocation output stream\n");
            return;
        }
        out_stream->time_base.num = 1;
        out_stream->time_base.den = 30;
        //复制一份编码器的配置给输出流
    //    avcodec_copy_context(out_stream->codec, pCodecCtx);
        avcodec_parameters_from_context(out_stream->codecpar, pCodecCtx);
        ret = avio_open(&ofmt_ctx->pb, address, AVIO_FLAG_WRITE);
        if(ret < 0) {
            printf("Could not open output URL %s", address);
            return;
        }
        
        ret = avformat_write_header(ofmt_ctx, NULL);
        if(ret < 0) {
            printf("Error occurred when open output URL\n");
            return;
        }
        //初始化一个帧的数据结构,用于编码用
        //指定AV_PIX_FMT_YUV420P这种格式的
        pFrame = av_frame_alloc();
        uint8_t *out_buffer = (uint8_t *) av_malloc(av_image_get_buffer_size(pCodecCtx->pix_fmt, src_width, src_height, 1));
        av_image_fill_arrays(pFrame->data, pFrame->linesize, out_buffer, pCodecCtx->pix_fmt, src_width, src_height, 1);
        
        start_time = av_gettime();
        
    }
    
    int encode(AVCodecContext *pCodecCtx, AVPacket* pPkt, AVFrame *pFrame, int *got_packet) {
        int ret;
        
        *got_packet = 0;
        
        ret = avcodec_send_frame(pCodecCtx, pFrame);
        if(ret <0 && ret != AVERROR_EOF) {
            return ret;
        }
        
        ret = avcodec_receive_packet(pCodecCtx, pPkt);
        if(ret < 0 && ret != AVERROR(EAGAIN)) {
            return ret;
        }
        
        if(ret >= 0) {
            *got_packet = 1;
        }
        
        return 0;
    }
    
    - (void)yuv420ToH264:(UInt8 *)yuv420_data {
        //
        picture_buf = yuv420_data;
        pFrame->data[0] = picture_buf;//y分量占一份所以              // Y
        pFrame->data[1] = picture_buf+ y_length;      // U
        pFrame->data[2] = picture_buf+ y_length*5/4;
        pFrame->pts = (1.0 / 30) * 90 * frame_count;
        int got_picture = 0;
        // Encode
        pFrame->width = encoder_h264_frame_width;
        pFrame->height = encoder_h264_frame_height;
        pFrame->format = AV_PIX_FMT_YUV420P;
        
        int ret = encode(pCodecCtx, &pkt, pFrame, &got_picture);
    //    encode(pCodecCtx, &pkt, pFrame, &got_picture)
        if(ret < 0) {
            printf("Failed to encode! \n");
            return;
        }
        //ofmt_ctx
        if (got_picture == 1) {
            printf("Succeed to encode frame: %5d\tsize:%5d\n", framecnt, pkt.size);
            framecnt++;
            pkt.stream_index = out_stream->index;
            //写PTS/DTS
            AVRational time_base = ofmt_ctx->streams[0]->time_base;
            AVRational r_frame_ratel = {30, 2};
            AVRational time_base_q = {1, AV_TIME_BASE};
            int64_t calc_duration = (double)(AV_TIME_BASE) * (1/av_q2d(r_frame_ratel));
    //        int64_t calc_duration = (double)AV_TIME_BASE/av_q2d(ofmt_ctx->streams[0]->r_frame_rate);
            pkt.pts = av_rescale_q(frame_count * calc_duration, time_base_q, time_base);
            pkt.dts = pkt.pts;
            pkt.pos = -1;
            frame_count ++;
            ofmt_ctx->duration = pkt.duration * frame_count;
            ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
            if (ret < 0) {
                printf("-====");
                return;
            }
            av_packet_unref(&pkt);
        }
    //    av_write_trailer(pFormatCtx);
    }
    @end
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    相关文章

      网友评论

          本文标题:[木木方文技术分享之音视频五]FFmpeg+x264摄像头直播推

          本文链接:https://www.haomeiwen.com/subject/jywkdxtx.html