Qt版的Rtsp客户端

    其实这个实现还是很简单的。主要是要在编译ffmpeg的时候,开启rtsp,network,这样我们就可以直接利用avformat_open_input函数接受rtsp协议了。ffmpeg代码的编写的流程和输入文件是一样的。所以说整个实现过程还是比较简单的。同样,我这里会给出我在开发这个客户端的所有的参考资料。对于别人详细介绍的知识我就不会在赘述了。

   随便提一下,我一开始使用live555作为客户端,接受到的数据传给ffmpeg解码。但是解码现实的视频会花屏。经过测试x264编码一帧图像会产生多个slice,这也就是为什么有的NAL前面的起始码是0 0 0 1有的NAL却是0 0 1。作为第一个slice一般是用0 0 0 1,其余的则用0 0 1。而live555的frame是一个去掉了RTP包头的,以及去掉了NAL四个字节的起始码(主要是发送端去掉的)。而我通过ffmpeg测试发现,如果每次给一个NAL让ffmpeg解码是会出现错误的,应该给的是一个完整的一帧数据,也就是几个NAL(slice)。虽然实验是得出这个结论,但是我用live555接受到的数据进行组帧,发现还是无法解码。我目前还没有发现是什么问题,我想可能还是我自己某个细节没有注意到。后来听说ffmpeg可以直接接受rtsp流媒体,就测试了一下,发现OK了,于是qt版的Rtsp客户端就出来了。

   还是老样子,先给出一些我感觉比较有价值的资料链接吧。

   H.264中NAL、Slice与frame意思及相互关系 http://blog.chinaunix.net/uid-20235103-id-1970924.html。我觉得是对做流媒体来说还是比较有用的东西。

   qt我也是现学现用的,这里面还是给我一个qt的学习链接,主要是使用qtcreator来编写。http://bbs.qter.org/forum.php?mod=viewthread&tid=193

   ffmpeg的学习,感觉网上的资料还真是满丰富的。这里就给出文件的ffmpeg播放。后面我会介绍ffmpeg播放rtsp的。http://blog.csdn.net/leixiaohua1020/article/details/8652605


下面就是前面利用前面一篇文章写的rtsp服务器的。


Qt版的Rtsp客户端_第1张图片


Qt版的Rtsp客户端_第2张图片

/*
 * FFmpeg.cpp
 *
 *  Created on: 2014年2月25日
 *      Author: ny
 */

#include "FFmpeg.h"



FFmpeg::FFmpeg()
{
    pCodecCtx = NULL;
    videoStream=-1;

}

FFmpeg::~FFmpeg()
{
    sws_freeContext(pSwsCtx);
}

int FFmpeg::initial(QString & url)
{
    int err;
    rtspURL=url;
    AVCodec *pCodec;
    av_register_all();
    avformat_network_init();
    pFormatCtx = avformat_alloc_context();
    pFrame=avcodec_alloc_frame();
    err = avformat_open_input(&pFormatCtx, rtspURL.toStdString().c_str(), NULL,
                              NULL);
    if (err < 0)
    {
        printf("Can not open this file");
        return -1;
    }
    if (av_find_stream_info(pFormatCtx) < 0)
    {
        printf("Unable to get stream info");
        return -1;
    }
    int i = 0;
    videoStream = -1;
    for (i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1)
    {
        printf("Unable to find video stream");
        return -1;
    }
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;

    width=pCodecCtx->width;
    height=pCodecCtx->height;
    avpicture_alloc(&picture,PIX_FMT_RGB24,pCodecCtx->width,pCodecCtx->height);
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    pSwsCtx = sws_getContext(width, height, PIX_FMT_YUV420P, width,
            height, PIX_FMT_RGB24,
            SWS_BICUBIC, 0, 0, 0);

    if (pCodec == NULL)
    {
        printf("Unsupported codec");
        return -1;
    }
    printf("video size : width=%d height=%d \n", pCodecCtx->width,
           pCodecCtx->height);
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("Unable to open codec");
        return -1;
    }
    printf("initial successfully");
    return 0;
}

int FFmpeg::h264Decodec()
{
    int frameFinished=0;
    while (av_read_frame(pFormatCtx, &packet) >= 0)
    {
        if(packet.stream_index==videoStream)
        {
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
            if (frameFinished)
            {
                printf("***************ffmpeg decodec*******************\n");
                mutex.lock();
                int rs = sws_scale(pSwsCtx, (const uint8_t* const *) pFrame->data,
                                   pFrame->linesize, 0,
                                   height, picture.data, picture.linesize);
                mutex.unlock();
                if (rs == -1)
                {
                    printf("__________Can open to change to des imag_____________e\n");
                    return -1;
                }
            }
        }
    }
    return 1;

}



FFmpeg这个类主要是对ffmpeg的一些操作,里面主要是有两个函数,一个是 initial(QString & url)。其中的参数是我们用户的输入url,类型是QString。这里建议是传递QString不要传递char * 字符,很容易出现指针异常。initial主要功能就是初始化AV FormatCtx,猜想主要是利用了sps,pps初始化了。获取了图像的尺寸我们就开始初始化我,我们要转化的RGB AVPicture了。也就是我们解码后的数据通过swscale进行图像格式转化成RGB存储在picture对面里面。 h264Decodec()这个函数就是我们的解码函数,里面就是不断从网络中读取packet,如果是一个视频数据我们就开始解码,再然后就是图片格式转换。当然,这里面我们采取了多线程,一个是主线程也就是UI线程,一个是我们ffmpeg解码线程(其中包括了网络数据获取和解码以及图片格式转换)。所了我们这里面对picture数据是多线程共享的,所以了我加了一个安全锁。这样做就是避免图像刷性不完全。
#include "video.h"
#include "ui_video.h"
#include 
Video::Video(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::Video)
{
    ui->setupUi(this);
    ffmpeg=NULL;
}

Video::~Video()
{
    delete ui;
}
void Video::setFFmpeg(FFmpeg *ff)
{
    ffmpeg=ff;
}

void Video::paintEvent(QPaintEvent *)
{
    if(ffmpeg->picture.data!=NULL)
    {
     QPainter painter(this);
    if(ffmpeg->mutex.tryLock(1000))
    {

        QImage image=QImage(ffmpeg->picture.data[0],ffmpeg->width,ffmpeg->height,QImage::Format_RGB888);
        QPixmap  pix =  QPixmap::fromImage(image);
        painter.drawPixmap(0, 0, 640, 480, pix);
        update();
        ffmpeg->mutex.unlock();
    }
    }
}

这个是就是我们用来现实的video的,主要函数是一个回调函数 paintEvent函数。里面我们是用了ffmpeg的picture来初始化Qimage,然后显示上去。关于qt二维绘图我这里就不多了,给的资料已经很详细了。
#include "login.h"
#include 
#include 
#include 
#include "FFmpeg.h"
#include 
#include "video.h"
#include 


/**
 * @brief The RtspThread class
 * Receive Thread
 */
class RtspThread : public QThread
{
public :
    void run();
    void setRtspURL(QString url);
    void setFFmpeg(FFmpeg * ff);
private:
    QString  rtspURL;// user input url
    FFmpeg * ffmpeg;//mian thread and rtspthread use the some ffmpeg object
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Login login;// login Doialog
    Video video;//mainwindow for display video
    FFmpeg * ffmpeg;//global data for ffmpeg event
    ffmpeg=new FFmpeg();
    video.setFFmpeg(ffmpeg);
    RtspThread rtspthread;
    login.show();

    if(login.exec()==QDialog::Accepted)
    {
        printf("%s\n",login.getRtspURL().toStdString().c_str());
        rtspthread.setRtspURL(login.getRtspURL());
        rtspthread.setFFmpeg(ffmpeg);
        rtspthread.start();
        video.show();
        return a.exec();
    }else
    {
        return 0;
    }

}

void  RtspThread::run()
{
    ffmpeg->initial(rtspURL);
    ffmpeg->h264Decodec();
}

void RtspThread::setRtspURL(QString url)
{
    rtspURL=url;
}
void RtspThread::setFFmpeg(FFmpeg * ff)
{
    ffmpeg=ff;
}

这个就是我们程序的主函数。采用了多线方法实现的。使用了多窗口来获取用户输入。







你可能感兴趣的:(流媒体)