直播系统的组成部分三:拉流--拉流播放

qi.wei

发布于 2020.03.04 00:33 阅读 2934 评论 0

直播系统的组成部分三:拉流--拉流播放

 

 

文章分为以下几个部分:

    1.实现拉流播放的逻辑

    2.具体流程

 

 

 

 

实现拉流播放的逻辑

    之前已经实现了拉流:

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

    现在通过QT把画面播放出来。逻辑是:开启一个线程专门来做拉流的处理,每获取到一帧数据,先把数据解码,然后转换成QImage格式,通过发送信号的方式将QImage传递到主线程,然后在主线程的界面上显示出来。

 

 

 

具体流程

    创建一个QT桌面应用程序,先把ffmpeg引用进来,引用方式参考:

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

    新建一个类继承自QTtread,这个类用来拉流用来,类名我这里叫做VideoPlayer(不太标准,参考的时候可以改下)。

    videoplayer.h

#ifndef VIDEOPLAYER_H

#define VIDEOPLAYER_H



#include <QThread>

#include <QImage>



class VideoPlayer : public QThread

{

    Q_OBJECT



public:

    explicit VideoPlayer();

    ~VideoPlayer();



    void setFileName(QString path){mFileName = path;}



    void startPlay();



signals:

    void sig_GetOneFrame(QImage); //每获取到一帧图像 就发送此信号



protected:

    void run();

    void getVideo();



private:

    QString mFileName;

};



#endif // VIDEOPLAYER_H

    注意里面定义了一个信号sig_GetOneFrame(),以QImage为参数,拉流的过程中每读到一帧就会触发这个信号,把读取到的那一帧画面以QImage的形式传递到主线程中去,主线程中不要忘记对这个信号做connect。

 

    videoplayer.cpp:

#include "videoplayer.h"
#include <QDebug>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/pixfmt.h"
#include "libswscale/swscale.h"
}

#include <stdio.h>

VideoPlayer::VideoPlayer()
{

}

VideoPlayer::~VideoPlayer()
{

}

void VideoPlayer::startPlay()
{
    ///调用 QThread 的start函数 将会自动执行下面的run函数 run函数是一个新的线程
    this->start();

}

void VideoPlayer::run()
{
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx ;
    AVCodec * pCodec;

    AVFrame *pFrame, *pFrameRGB;
    AVPacket  *packet;
    uint8_t *out_buffer;

    static struct SwsContext *img_convert_ctx;

    int videoStream, i, numBytes;
    int ret, got_picture;

    avformat_network_init();
    //初始化ffmpeg,之后才能正常使用编码器和解码器
    av_register_all();

    //Allocate an AVFormatContext,
    pFormatCtx = avformat_alloc_context();

    //ffmpeg取rtsp流时av_read_frame阻塞的解决办法 设置参数优化
    AVDictionary* avdic=NULL;
    av_dict_set(&avdic, "burrer sice", "102400", 0);    //设置缓存大小,1080p可将值调大
    av_dict_set(&avdic, "rtsp_transport", "udp", 0);    //以udp方式打开,如果以tcp方式打开将udp替换为tcp
    av_dict_set(&avdic, "stimeout", "2000000", 0);      //设置超时断开连接世界,单位微秒
    av_dict_set(&avdic, "max_delay", "500000", 0);      //设置最大时延

    //rstp地址,可根据实际情况修改,这里我们用湖南卫视的收流地址
    char url[]="rtmp://58.200.131.2:1935/livetv/hunantv";

    if(avformat_open_input(&pFormatCtx, url, NULL, &avdic) != 0) {

        qDebug("can't open the f1le. n") ;return;
    }

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

        qDebug("Could't rind stream infomation. n");return;
    }


    videoStream = -1;
    //遍历流信息,直到找到视频流
    for (i = 0; i< pFormatCtx->nb_streams; i++) {

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

            videoStream = i;
        }
    }
    //如果没找到videoStream = 初值-1
    if (videoStream == -1) {

        qDebug("Didn't find a video stream. \n") ;return;
    }

    //查找视频解码器
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    pCodecCtx->bit_rate = 0;    //初始化为0
    pCodecCtx->time_base.num = 1;//下面两行,一秒钟25帧
    pCodecCtx->time_base.den = 10;
    pCodecCtx->frame_number = 1;//每包一个视频帧

    if (pCodec == NULL) {

        qDebug("Codec not found. \n");return;
    }

    //打开视频解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL)< 0) {

        qDebug("Could not open codec.\n") ;return;
    }


    pFrame=av_frame_alloc();

    pFrameRGB = av_frame_alloc() ;

    //设置图像转换的结构体变量
    ///这里我们改成了 将解码后的YUV数据转换成RGB32
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
                                     pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
                                     PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);

    numBytes = avpicture_get_size(PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);

    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, PIX_FMT_RGB32,
                   pCodecCtx->width, pCodecCtx->height);

    int y_size = pCodecCtx->width * pCodecCtx->height;

    packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
    av_new_packet(packet, y_size); //分配packet的数据

    av_dump_format(pFormatCtx, 0, url, 0); //输出视频信息

    av_init_packet(packet);
    while (1)
    {
        if(av_read_frame(pFormatCtx, packet)<0){
            qDebug()<<"av_ read_ frame < 0" ;break;//这里人为读取完了
        }
        ret = -1;
        if(packet->stream_index == videoStream)
        {

            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);

            if(ret<0)
            {
                qDebug("decnd- error . (n");
                return;
            }
            else
            {
                qDebug()<<"ret"<<ret;
            }

            if (got_picture)
            {

                qDebug()<<"获取到图像";

                qDebug()<<"pFrame->linesize"<<pFrame->linesize;
                qDebug()<<"pCodecCtx->height"<<pCodecCtx->height;
                qDebug()<<"pFrameRGB->linesize"<<pFrameRGB->linesize;
                sws_scale(img_convert_ctx,
                          (uint8_t const * const *)pFrame->data,
                          pFrame->linesize,0, pCodecCtx->height, pFrameRGB->data ,pFrameRGB->linesize);

                //把RGB数据用QImage加载
                QImage tmpImg((uchar *)out_buffer , pCodecCtx->width, pCodecCtx->height , QImage::Format_RGB32);
                QImage image = tmpImg.copy();

                emit sig_GetOneFrame(image);

                //提取出图像中的R数据
                for(int i=0;i <pCodecCtx->width; i++)
                {
                    for(int j=0;j<pCodecCtx->height;j++)
                    {
                        QRgb rgb=image .pixel(i,j);
                        int r=qRed(rgb);
                        image.setPixel(i,j,qRgb(r,0,0));
                    }
                }
                //emit sig_GetRFrame(image);
            }
            else qDebug()<< "got_picture"<<got_picture ;

        }
        else qDebug()<< "packct->stream_index not video stream" ;

        av_free_packet(packet);//释放资源否则内存一直上升
        //msleep(100);
    }
    av_free(out_buffer);
    av_free(pFrameRGB);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
}


void VideoPlayer::getVideo()
{

}

 

    主线程MainWindow。

    Mainwindow.h:
 

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QImage>
#include <QPaintEvent>

#include "videoplayer/videoplayer.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

protected:
    void paintEvent(QPaintEvent *event);

private:
    Ui::MainWindow *ui;

    VideoPlayer *mPlayer; //播放线程

    QImage mImage; //记录当前的图像

private slots:
    void slotGetOneFrame(QImage img);

};

#endif // MAINWINDOW_H

 

    Mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QPainter>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    mPlayer = new VideoPlayer;
    connect(mPlayer,SIGNAL(sig_GetOneFrame(QImage)),this,SLOT(slotGetOneFrame(QImage)));

    mPlayer->setFileName("E:/1.mp4");
    mPlayer->startPlay();

}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setBrush(Qt::black);
    painter.drawRect(0, 0, this->width(), this->height()); //先画成黑色

    if (mImage.size().width() <= 0) return;

    ///将图像按比例缩放成和窗口一样大小
    QImage img = mImage.scaled(this->size(),Qt::KeepAspectRatio);

    int x = this->width() - img.width();
    int y = this->height() - img.height();

    x /= 2;
    y /= 2;

    painter.drawImage(QPoint(x,y),img); //画出图像

}

void MainWindow::slotGetOneFrame(QImage img)
{
    mImage = img;
    update(); //调用update将执行 paintEvent函数
}

    到这里就实现了最简单的收流播放,当然后续还会加入解码音频和音频同步。