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

//
//  LiveTelecastController.m
//  FFmpegDemo
//
//  Created by huoliquankai on 2017/7/20.
//  Copyright © 2017年 深圳机械行业协会. All rights reserved.
//

#import "LiveTelecastController.h"
#import 
#import "avcodec.h"
#import "imgutils.h"
#import "avdevice.h"
#import "swscale.h"
#import "x264.h"
#include "time.h"
#import "GPUImage.h"


@interface LiveTelecastController () 
@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摄像头直播推流)