其实这个实现还是很简单的。主要是要在编译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服务器的。
/*
* 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;
}
#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();
}
}
}
#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;
}
这个就是我们程序的主函数。采用了多线方法实现的。使用了多窗口来获取用户输入。