直播系统的组成部分二:推流--摄像头画面实时推流

qi.wei

发布于 2020.02.26 01:07 阅读 2682 评论 0

直播系统的组成部分二:推流--摄像头画面实时推流

 

 

文章分为以下几个部分:

    1.前言

    2.需要注意的地方

    3.代码示例

 

 

 

 

前言

    一个直播系统,从开播到观看的大致流程是:1.从开播端采集视频数据,并把数据编码封装推流到流媒体服务器;2.流媒体服务器接收并转发数据;3.观看端进行收流播放。

    之前讲过视频的采集,也讲过最简单的推流器,那么现在就来完成从开播端采集视频数据,并把数据编码封装推流到流媒体服务器的逻辑。

    视频采集:

    http://www.lindasoft.com/view/article/details?articleId=655

    最简单的推流器:

    http://www.lindasoft.com/view/article/details?articleId=659

 

 

 

需要注意的地方

    学会了视频采集和简单的推流器之后,编写摄像头画面实时推流需要注意的地方是编码部分,解码和编码都是用的AVCodec结构体,但是解码和编码的概念恰好相反,解码是通过avcodec_decode_video2()AVPacket数据解码成AVFrame数据,编码是通过avcodec_encode_video2()AVFrame编码得到AVPacket数据。推流发送的是一帧帧编码的数据。

 

 

 

代码示例

    下列代码可以和之前的视频采集、推流器代码对比着看,比较容易理解,在代码中每一步我都做了详细的注释。


#include <stdio.h>

#include <QString>



extern "C"

{

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

#include "libavutil/pixfmt.h"

#include "libswscale/swscale.h"

#include "libavdevice/avdevice.h"

#include "libavutil/time.h"

}

int main()

{

    AVFormatContext *ifmt_ctx,*ofmt_ctx;//包含了封装格式的数据。

    AVCodecContext *pCodecCtx;//码流数据的解码方式相关数据。

    AVCodec *pCodec;//AVCodecContext中所包含的解码器信息。

    AVFrame *pFrame, *pFrameYUV;//存储压缩编码数据相关信息的结构体。

    AVPacket *dec_pkt,enc_pkt;//解码后的数据

    uint8_t *out_buffer;

    AVInputFormat *ifmt;



    static struct SwsContext *img_convert_ctx;



    int videoStream, i, numBytes;

    int ret, dec_got_frame,enc_got_frame;



    const char *out_path;



    //推流地址

    out_path="rtmp://alirtmp.kingrisingsun.xyz/test/1221?auth_key=1582384790-0-0-6d1142e8aa505bc0dbe33f52165d94b3";



    av_register_all(); //初始化FFMPEG

    avdevice_register_all();//初始化设备(获取摄像头画面之前必须要做的)

    avformat_network_init();//初始化网络组件





    ifmt_ctx = NULL;

    ofmt_ctx = avformat_alloc_context();



    //打开摄像头

    //这里的Lenovo EasyCamera是我的摄像头名称,怎么获取详见http://www.lindasoft.com/view/article/details?articleId=655

    ifmt=av_find_input_format("dshow");

    avformat_open_input(&ifmt_ctx,"video=Lenovo EasyCamera",ifmt,NULL) ;







    //获取视频信息

    if (avformat_find_stream_info(ifmt_ctx, NULL) < 0) {

        printf("Could't find stream infomation.\n");

        return;

    }



    videoStream = -1;



    ///循环查找视频中包含的流信息,直到找到视频类型的流

    ///便将其记录下来 保存到videoStream变量中

    ///这里我们现在只处理视频流  音频流先不管他

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {

        if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {

            videoStream = i;

        }

    }



    ///如果videoStream为-1 说明没有找到视频流

    if (videoStream == -1) {

        printf("Didn't find a video stream.\n");

        return;

    }

    else

    {

        printf("video stream OK\n");

    }



    //打开解码器

    if (avcodec_open2(ifmt_ctx->streams[videoStream]->codec, avcodec_find_decoder(ifmt_ctx->streams[videoStream]->codec->codec_id), NULL)<0)

    {

        printf("Could not open codec.(无法打开解码器)\n");

        return;

    }



    //初始化一个用于输出的AVFormatContext结构体ofmt_ctx

    avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_path);



    //查找编码器

    pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);

    if (!pCodec){

        printf("Can not find encoder! (没有找到合适的编码器!)\n");

        return;

    }

    else

    {

        printf("find encoder OK\n");

    }



    //获取编解码器上下文信息

    pCodecCtx=avcodec_alloc_context3(pCodec);

    //设置解码器上下文信息

    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

    pCodecCtx->width = ifmt_ctx->streams[videoStream]->codec->width;

    pCodecCtx->height = ifmt_ctx->streams[videoStream]->codec->height;

    pCodecCtx->time_base.num = 1;

    pCodecCtx->time_base.den = 25;

    pCodecCtx->bit_rate = 400000;

    pCodecCtx->gop_size = 250;

    /* Some formats,for example,flv, want stream headers to be separate. */

    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)

        pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;



    //H264 codec param

    //pCodecCtx->me_range = 16;

    //pCodecCtx->max_qdiff = 4;

    //pCodecCtx->qcompress = 0.6;

    pCodecCtx->qmin = 10;

    pCodecCtx->qmax = 51;

    //Optional Param

    pCodecCtx->max_b_frames = 3;



    // Set H264 preset and tune

    AVDictionary *param = 0;

    av_dict_set(&param, "preset", "fast", 0);

    av_dict_set(&param, "tune", "zerolatency", 0);



    //打开编码器

    if (avcodec_open2(pCodecCtx, pCodec,&param) < 0){

        printf("Failed to open encoder! (编码器打开失败!)\n");

        return;

    }

    else

    {

        printf("open encoder OK\n");

    }



    //根据编码器创建输出流

    AVStream *video_st = avformat_new_stream(ofmt_ctx, pCodec);

    if (video_st == NULL){

        return;

    }

    video_st->time_base.num = 1;

    video_st->time_base.den = 25;

    video_st->codec = pCodecCtx;



    //打开输出文件,out_path是推流地址

    if (avio_open(&ofmt_ctx->pb,out_path, AVIO_FLAG_READ_WRITE) < 0){

        printf("Failed to open output file! (输出文件打开失败!)\n");

        return;

    }

    else

    {

        printf("open output file OK\n");

    }



    //打印输出流信息

    av_dump_format(ofmt_ctx, 0, out_path, 1);



    //写头文件

    avformat_write_header(ofmt_ctx,NULL);







    //转YUV数据用

    pFrame = av_frame_alloc();

    pFrameYUV = av_frame_alloc();



    //prepare before decode and encode

    dec_pkt = (AVPacket *)av_malloc(sizeof(AVPacket));

    //enc_pkt = (AVPacket *)av_malloc(sizeof(AVPacket));

    //camera data has a pix fmt of RGB,convert it to YUV420

    img_convert_ctx = sws_getContext(ifmt_ctx->streams[videoStream]->codec->width, ifmt_ctx->streams[videoStream]->codec->height,

                                     ifmt_ctx->streams[videoStream]->codec->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);



    out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));

    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);







    //start decode and encode

    int64_t start_time=av_gettime();

    int framecnt = 0;

    printf("Begin\n");

    //逐帧读取

    while (av_read_frame(ifmt_ctx, dec_pkt) >= 0){

        //            if (exit_thread)

        //                break;

        av_log(NULL, AV_LOG_DEBUG, "Going to reencode the frame\n");

        pFrame = av_frame_alloc();//获取空间

        if (!pFrame) {

            ret = AVERROR(ENOMEM);

            return;

        }

        //av_packet_rescale_ts(dec_pkt, ifmt_ctx->streams[dec_pkt->stream_index]->time_base,

        //  ifmt_ctx->streams[dec_pkt->stream_index]->codec->time_base);



        //解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。

        ret = avcodec_decode_video2(ifmt_ctx->streams[dec_pkt->stream_index]->codec, pFrame,

                &dec_got_frame, dec_pkt);

        //解码失败

        if (ret < 0) {

            av_frame_free(&pFrame);

            av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");

            break;

        }

        if (dec_got_frame){

            //AVFrame色彩空间转换

            sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);



            enc_pkt.data = NULL;

            enc_pkt.size = 0;

            //设置enc_pkt默认值

            av_init_packet(&enc_pkt);

            //把YUV数据进行编码

            ret = avcodec_encode_video2(pCodecCtx, &enc_pkt, pFrameYUV, &enc_got_frame);

            av_frame_free(&pFrame);

            if (enc_got_frame == 1){

                //printf("Succeed to encode frame: %5d\tsize:%5d\n", framecnt, enc_pkt.size);

                framecnt++;

                enc_pkt.stream_index = video_st->index;



                //计算PTS

                AVRational time_base = ofmt_ctx->streams[videoStream]->time_base;//{ 1, 1000 };

                AVRational r_framerate1 = ifmt_ctx->streams[videoStream]->r_frame_rate;// { 50, 2 };

                AVRational time_base_q = { 1, AV_TIME_BASE };

                //Duration between 2 frames (us)

                int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate1));  //内部时间戳

                //Parameters

                //enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));

                enc_pkt.pts = av_rescale_q(framecnt*calc_duration, time_base_q, time_base);

                enc_pkt.dts = enc_pkt.pts;

                enc_pkt.duration = av_rescale_q(calc_duration, time_base_q, time_base); //(double)(calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));

                enc_pkt.pos = -1;



                //设置延时

                int64_t pts_time = av_rescale_q(enc_pkt.dts, time_base, time_base_q);

                int64_t now_time = av_gettime() - start_time;

                if (pts_time > now_time)

                    av_usleep(pts_time - now_time);



                ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);

                printf("%d\n",framecnt);

                av_free_packet(&enc_pkt);

            }

        }

        else {

            av_frame_free(&pFrame);

        }

        av_free_packet(dec_pkt);

    }



    av_free(out_buffer);

    av_free(pFrameYUV);

    avcodec_close(pCodecCtx);

    avformat_close_input(&ifmt_ctx);

    avformat_close_input(&ofmt_ctx);



    return 0;

}