直播系统的组成部分三:拉流--拉流播放
文章分为以下几个部分:
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函数
}
到这里就实现了最简单的收流播放,当然后续还会加入解码音频和音频同步。
{{ cmt.username }}
{{ cmt.content }}
{{ cmt.commentDate | formatDate('YYYY.MM.DD hh:mm') }}