zoukankan      html  css  js  c++  java
  • 流媒体解码及H.264编码推流

    简介

    • 相关理论
    • 程序流程
    • Opencv解析视频流
    • 像素格式转换
    • FFmpeg进行H.264编码
    • FFmpeg进行格式封装和推流

    这里我们使用了FFmpge的sdk和Opencv的sdk。为了方便测试,我们直接使用在线的rtsp网络流。rtmp://live.hkstv.hk.lxdns.com/live/hks这个是香港卫视的rtsp流,可以用vlc播放器测试是否可以播放。

    FFmpeg支持的像素格式

    1 所有的像素格式的名称都是以“AV_PIX_FMT_”开头
    2 像素格式名称后面有“P”的,代表是planar格式,否则就是packed格式。
    Planar格式不同的分量分别存储在不同的数组中,例如AV_PIX_FMT_YUV420P存储方式如下:

    • data[0]: Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8……
    • data[1]: U1, U2, U3, U4……
    • data[2]: V1, V2, V3, V4……
      Packed格式的数据都存储在同一个数组中,例如AV_PIX_FMT_RGB24存储方式如下:
      data[0]: R1, G1, B1, R2, G2, B2, R3, G3, B3, R4, G4, B4……

    3 像素格式名称后面有“BE”的,代表是Big Endian格式;名称后面有“LE”的,代表是Little Endian格式。

    封装相关类

    • AVFormatContext:统领全局的基本结构体。主要用于处理封装格式(FLV/MKV/RMVB等)。
    • AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。
    • AVStream,AVCodecContext:视音频流对应的结构体,用于视音频编解码。
    • AVFrame:存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)
    • AVPacket:存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)

    图像格式转换以及图像缩放

    sws_getContext
    初始化函数

    • int srcW,int srcH 为原始图像数据的高和宽;
    • int dstW,int dstH 为输出图像数据的高和宽;
    • enum AVPixelFormat srcFormat 为输入和输出图片数据的类型;eg:AV_PIX_FMT_YUV420、PAV_PIX_FMT_RGB24;
      int flags 为scale算法种类;eg:SWS_BICUBIC、SWS_BICUBLIN、SWS_POINT、SWS_SINC;
    • SwsFilter *srcFilter ,SwsFilter *dstFilter,const double *param 可以不用管,全为NULL即可
      sws_scale
      执行图像格式转换以及图像缩放
    • struct SwsContext *c 为sws_getContext函数返回的值;
    • const uint8_t *const srcSlice[],uint8_t *const dst[] 为输入输出图像数据各颜色通道的buffer指针数组;
    • const int srcStride[],const int dstStride[] 为输入输出图像数据各颜色通道每行存储的字节数数组;
    • int srcSliceY 为从输入图像数据的第多少列开始逐行扫描,通常设为0;
    • int srcSliceH 为需要扫描多少行,通常为输入图像数据的高度;
      sws_freeContext
      程序流程

    大体上分四个大步骤:

    1.各种初始化

    • Opencv读取视频流
    • 像素格式转换
    • FFmpeg进行H.264编码
    • FFmpeg进行视频格式封装。
    • FFmpeg进行推流

    接下来我们来细化流程。

    Opencv读取视频流

    打开并读取视频帧使用VideoCapture类。open来打开。打开完成后可获取相关的视频信息。如尺寸,fps等

    //获取视频帧的尺寸
    int inWidth = cam.get(CAP_PROP_FRAME_WIDTH);
    int inHeight = cam.get(CAP_PROP_FRAME_HEIGHT);
    int fps = cam.get(CAP_PROP_FPS);
    

    真正的读取视频帧可以使用read()方法。保存在Mat中。而read()中主要分两个步骤

    使用grab()方法解码视频帧
    注意解码的步骤不许要做,保证后面的数据能够正确解析
    使用retrieve将yuv转换为rgb数据
    这里的yuv和rgb都是未压缩的数据,得到的rgb数据就可以直接显示。

    像素格式转换

    初始化格式转换上下文

    vsc = sws_getCachedContext(vsc,
        inWidth, inHeight, AV_PIX_FMT_BGR24,     //源宽、高、像素格式
        inWidth, inHeight, AV_PIX_FMT_YUV420P,//目标宽、高、像素格式
        SWS_BICUBIC,  // 尺寸变化使用算法
        0, 0, 0
    );
    

    初始化存放YUV数据的AVFrame

    AVFrame *yuv = NULL;
    yuv = av_frame_alloc();
    yuv->format = AV_PIX_FMT_YUV420P;
    yuv->width = inWidth;
    yuv->height = inHeight;
    yuv->pts = 0;
    //分配yuv空间
    int ret = av_frame_get_buffer(yuv, 32);
    

    rgb转yuv,这里要注意rgb和yuv的存储格式。前面FFmpeg支持的像素格式有讲到

    ///rgb to yuv
    //输入的数据结构
    uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
    //indata[0] bgrbgrbgr
    //plane indata[0] bbbbb indata[1]ggggg indata[2]rrrrr 
    indata[0] = frame.data;
    int insize[AV_NUM_DATA_POINTERS] = { 0 };
    //一行(宽)数据的字节数
    insize[0] = frame.cols * frame.elemSize();
    int h = sws_scale(vsc, indata, insize, 0, frame.rows, //源数据
        yuv->data, yuv->linesize);
    

    FFmpeg进行H.264编码

    编码的方法就比较简单了,传入AVFrame。然后再用AVPacket去接收即可,这里还有一点要注意就是,pts的设置。

    ///h264编码
    yuv->pts = vpts;
    vpts++;
    ret = avcodec_send_frame(vc, yuv);
    if (ret != 0)
        continue;
    ret = avcodec_receive_packet(vc, &pack);
    

    FFmpeg进行视频格式封装和推流

    需要强调的就是pts,dts,和duration的转换。前面H.264编码的时候,我们已经设置了pts。但是输出的time_base不一致,所以需要进行转换

    //计算pts dts和duration。
    pack.pts = av_rescale_q(pack.pts, vc->time_base, vs->time_base);
    pack.dts = av_rescale_q(pack.dts, vc->time_base, vs->time_base);
    pack.duration = av_rescale_q(pack.duration, vc->time_base, vs->time_base);
    

    完整源码

    #include <opencv2/highgui.hpp>
    #include <iostream>
    extern "C"
    {
    #include <libswscale/swscale.h>
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    }
    #pragma comment(lib, "swscale.lib")
    #pragma comment(lib, "avcodec.lib")
    #pragma comment(lib, "avutil.lib")
    #pragma comment(lib, "avformat.lib")
    #pragma comment(lib,"opencv_world320d.lib")
    using namespace std;
    using namespace cv;
    int main(int argc, char *argv[])
    {
        //原地址
        char *inUrl = "http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8";
        //nginx-rtmp 直播服务器rtmp推流URL
        char *outUrl = "rtmp://192.166.11.13/live";
        //注册所有的编解码器
        avcodec_register_all();
        //注册所有的封装器
        av_register_all();
        //注册所有网络协议
        avformat_network_init();
        VideoCapture cam;
        Mat frame;
        namedWindow("video");
        //像素格式转换上下文
        SwsContext *vsc = NULL;
        //输出的数据结构 存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)
        AVFrame *yuv = NULL;
        //编码器上下文
        AVCodecContext *vc = NULL;
        //rtmp flv 封装器
        AVFormatContext *ic = NULL;
        try
        {   ////////////////////////////////////////////////////////////////
            /// 1 使用opencv打开源视频流
            cam.open(inUrl);
            if (!cam.isOpened())
            {
                throw exception("cam open failed!");
            }
            cout << inUrl << " cam open success" << endl;
            //获取视频帧的尺寸
            int inWidth = cam.get(CAP_PROP_FRAME_WIDTH);
            int inHeight = cam.get(CAP_PROP_FRAME_HEIGHT);
            int fps = cam.get(CAP_PROP_FPS);
            ///2 初始化格式转换上下文
            vsc = sws_getCachedContext(vsc,
                inWidth, inHeight, AV_PIX_FMT_BGR24,     //源宽、高、像素格式
                inWidth, inHeight, AV_PIX_FMT_YUV420P,//目标宽、高、像素格式
                SWS_BICUBIC,  // 尺寸变化使用算法
                0, 0, 0
            );
            if (!vsc)
            {
                throw exception("sws_getCachedContext failed!");
            }
            ///3 初始化输出的数据结构。未压缩的数据
            yuv = av_frame_alloc();
            yuv->format = AV_PIX_FMT_YUV420P;
            yuv->width = inWidth;
            yuv->height = inHeight;
            yuv->pts = 0;
            //分配yuv空间
            int ret = av_frame_get_buffer(yuv, 32);
            if (ret != 0)
            {
                char buf[1024] = { 0 };
                av_strerror(ret, buf, sizeof(buf) - 1);
                throw exception(buf);
            }
            ///4 初始化编码上下文
            //a 找到编码器
            AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
            if (!codec)
            {
                throw exception("Can`t find h264 encoder!");
            }
            //b 创建编码器上下文
            vc = avcodec_alloc_context3(codec);
            if (!vc)
            {
                throw exception("avcodec_alloc_context3 failed!");
            }
            //c 配置编码器参数
            vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //全局参数
            vc->codec_id = codec->id;
            vc->thread_count = 8;
            vc->bit_rate = 50 * 1024 * 8;//压缩后每秒视频的bit位大小 50kB
            vc->width = inWidth;
            vc->height = inHeight;
            vc->time_base = { 1,fps };
            vc->framerate = { fps,1 };
            //画面组的大小,多少帧一个关键帧
            vc->gop_size = 50;
            vc->max_b_frames = 0;
            vc->pix_fmt = AV_PIX_FMT_YUV420P;
            //d 打开编码器上下文
            ret = avcodec_open2(vc, 0, 0);
            if (ret != 0)
            {
                char buf[1024] = { 0 };
                av_strerror(ret, buf, sizeof(buf) - 1);
                throw exception(buf);
            }
            cout << "avcodec_open2 success!" << endl;
            ///5 输出封装器和视频流配置
            //a 创建输出封装器上下文
            ret = avformat_alloc_output_context2(&ic, 0, "flv", outUrl);
            if (ret != 0)
            {
                char buf[1024] = { 0 };
                av_strerror(ret, buf, sizeof(buf) - 1);
                throw exception(buf);
            }
            //b 添加视频流   视音频流对应的结构体,用于视音频编解码。
            AVStream *vs = avformat_new_stream(ic, NULL);
            if (!vs)
            {
                throw exception("avformat_new_stream failed");
            }
            //附加标志,这个一定要设置
            vs->codecpar->codec_tag = 0;
            //从编码器复制参数
            avcodec_parameters_from_context(vs->codecpar, vc);
            av_dump_format(ic, 0, outUrl, 1);
            ///打开rtmp 的网络输出IO  AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。
            ret = avio_open(&ic->pb, outUrl, AVIO_FLAG_WRITE);
            if (ret != 0)
            {
                char buf[1024] = { 0 };
                av_strerror(ret, buf, sizeof(buf) - 1);
                throw exception(buf);
            }
            //写入封装头
            ret = avformat_write_header(ic, NULL);
            if (ret != 0)
            {
                char buf[1024] = { 0 };
                av_strerror(ret, buf, sizeof(buf) - 1);
                throw exception(buf);
            }
            //存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)
            AVPacket pack;
            memset(&pack, 0, sizeof(pack));
            int vpts = 0;
            for (;;)
            {
                ///读取rtsp视频帧,解码视频帧
                if (!cam.grab())
                {
                    continue;
                }
                ///yuv转换为rgb
                if (!cam.retrieve(frame))
                {
                    continue;
                }
                imshow("video", frame);
                waitKey(1);
                ///rgb to yuv
                //输入的数据结构
                uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
                //indata[0] bgrbgrbgr
                //plane indata[0] bbbbb indata[1]ggggg indata[2]rrrrr 
                indata[0] = frame.data;
                int insize[AV_NUM_DATA_POINTERS] = { 0 };
                //一行(宽)数据的字节数
                insize[0] = frame.cols * frame.elemSize();
                int h = sws_scale(vsc, indata, insize, 0, frame.rows, //源数据
                    yuv->data, yuv->linesize);
                if (h <= 0)
                {
                    continue;
                }
                //cout << h << " " << flush;
                ///h264编码
                yuv->pts = vpts;
                vpts++;
                ret = avcodec_send_frame(vc, yuv);
                if (ret != 0)
                    continue;
                ret = avcodec_receive_packet(vc, &pack);
                if (ret != 0 || pack.size > 0)
                {
                    //cout << "*" << pack.size << flush;
                }
                else
                {
                    continue;
                }
                //计算pts dts和duration。
                pack.pts = av_rescale_q(pack.pts, vc->time_base, vs->time_base);
                pack.dts = av_rescale_q(pack.dts, vc->time_base, vs->time_base);
                pack.duration = av_rescale_q(pack.duration, vc->time_base, vs->time_base);
                ret = av_interleaved_write_frame(ic, &pack);
                if (ret == 0)
                {
                    cout << "#" << flush;
                }
            }
        }
        catch (exception &ex)
        {
            if (cam.isOpened())
                cam.release();
            if (vsc)
            {
                sws_freeContext(vsc);
                vsc = NULL;
            }
            if (vc)
            {
                avio_closep(&ic->pb);
                avcodec_free_context(&vc);
            }
            cerr << ex.what() << endl;
        }
        getchar();
        return 0;
    }
    

    原创作者:eric__
    原文链接:https://www.jianshu.com/p/f83ef0a6f5cc
    在这里插入图片描述
    欢迎关注我的微信公众号「码农突围」,分享Python、Java、大数据、机器学习、人工智能等技术,关注码农技术提升•职场突围•思维跃迁,20万+码农成长充电第一站,陪有梦想的你一起成长

  • 相关阅读:
    5 Things Every Manager Should Know about Microsoft SharePoint 关于微软SharePoint每个经理应该知道的五件事
    Microsoft SharePoint 2010, is it a true Document Management System? 微软SharePoint 2010,它是真正的文档管理系统吗?
    You think you use SharePoint but you really don't 你认为你使用了SharePoint,但是实际上不是
    Introducing Document Management in SharePoint 2010 介绍SharePoint 2010中的文档管理
    Creating Your Own Document Management System With SharePoint 使用SharePoint创建你自己的文档管理系统
    MVP模式介绍
    权重初始化的选择
    机器学习中线性模型和非线性的区别
    神经网络激励函数的作用是什么
    深度学习中,交叉熵损失函数为什么优于均方差损失函数
  • 原文地址:https://www.cnblogs.com/hejunlin/p/12852917.html
Copyright © 2011-2022 走看看