其实这个实现还是很简单的。主要是要在编译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服务器的。
![](https://img-blog.csdn.net/20140304085920765?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbmlleW9uZ3M=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20140304085948234?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbmlleW9uZ3M=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
-
-
-
-
-
-
-
- #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数据是多线程共享的,所以了我加了一个安全锁。这样做就是避免图像刷性不完全。
- <span style="font-family: Arial, Helvetica, sans-serif;"></span><pre code_snippet_id="215818" snippet_file_name="blog_20140303_4_771610" name="code" class="cpp">#include "video.h"
- #include "ui_video.h"
- #include <QPainter>
- 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();
- }
- }
- }
- </pre><br>
- 这个是就是我们用来现实的video的,主要函数是一个回调函数<span style="font-family:Arial,Helvetica,sans-serif">paintEvent函数。里面我们是用了ffmpeg的picture来初始化Qimage,然后显示上去。关于qt二维绘图我这里就不多了,给的资料已经很详细了。</span><pre code_snippet_id="215818" snippet_file_name="blog_20140303_6_1404464" name="code" class="cpp"><span style="font-family: Arial, Helvetica, sans-serif;"></span><pre code_snippet_id="215818" snippet_file_name="blog_20140303_6_1404464" name="code" class="cpp">#include "login.h"
- #include <QApplication>
- #include <QString>
- #include <stdio.h>
- #include "FFmpeg.h"
- #include <qthread.h>
- #include "video.h"
- #include <unistd.h>
-
-
-
-
-
-
- class RtspThread : public QThread
- {
- public :
- void run();
- void setRtspURL(QString url);
- void setFFmpeg(FFmpeg * ff);
- private:
- QString rtspURL;
- FFmpeg * ffmpeg;
- };
-
- int main(int argc, char *argv[])
- {
- QApplication a(argc, argv);
- Login login;
- Video video;
- FFmpeg * ffmpeg;
- 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;
- }
- </pre><br>
- <p>这个就是我们程序的主函数。采用了多线方法实现的。使用了多窗口来获取用户输入。</p>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
-
- </pre>