#include "ffmpeg.h" FFmpegThread::FFmpegThread(QObject *parent) : QThread(parent) { setObjectName("FFmpegThread"); stopped = false; isPlay = false; frameFinish = false; videoWidth = 0; videoHeight = 0; videoStreamIndex = -1; audioStreamIndex = -1; url = "rtsp://192.168.111.11:8554/1"; buffer = NULL; avPacket = NULL; yuvFrame = NULL; rgbFrame = NULL; formatCtx = NULL; videoCodecCtx = NULL; audioCodecCtx = NULL; swsContext = NULL; options = NULL; videoCodec = NULL; audioCodec = NULL; //初始化注册,一个软件中只注册一次即可 FFmpegThread::initlib(); } //一个软件中只需要初始化一次就行 void FFmpegThread::initlib() { static QMutex mutex; QMutexLocker locker(&mutex); static bool isInit = false; if (!isInit) { //注册库中所有可用的文件格式和解码器 av_register_all(); //注册所有设备,主要用于本地摄像机播放支持 #ifdef ffmpegdevice avdevice_register_all(); #endif //初始化网络流格式,使用网络流时必须先执行 avformat_network_init(); isInit = true; qDebug() << TIMEMS << "init ffmpeg lib ok" << " version:" << FFMPEG_VERSION; #if 0 //输出所有支持的解码器名称 QStringList listCodeName; AVCodec *code = av_codec_next(NULL); while (code != NULL) { listCodeName << code->name; code = code->next; } qDebug() << TIMEMS << listCodeName; #endif } } bool FFmpegThread::init() { //在打开码流前指定各种参数比如:探测时间/超时时间/最大延时等 //设置缓存大小,1080p可将值调大 av_dict_set(&options, "buffer_size", "8192000", 0); //以tcp方式打开,如果以udp方式打开将tcp替换为udp av_dict_set(&options, "rtsp_transport", "tcp", 0); //设置超时断开连接时间,单位微秒,3000000表示3秒 av_dict_set(&options, "stimeout", "3000000", 0); //设置最大时延,单位微秒,1000000表示1秒 av_dict_set(&options, "max_delay", "1000000", 0); //自动开启线程数 av_dict_set(&options, "threads", "auto", 0); //打开视频流 formatCtx = avformat_alloc_context(); int result = avformat_open_input(&formatCtx, url.toStdString().data(), NULL, &options); if (result < 0) { qDebug() << TIMEMS << "open input error" << url; return false; } //释放设置参数 if (options != NULL) { av_dict_free(&options); } //获取流信息 result = avformat_find_stream_info(formatCtx, NULL); if (result < 0) { qDebug() << TIMEMS << "find stream info error"; return false; } //----------视频流部分开始,打个标记方便折叠代码---------- if (1) { videoStreamIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &videoCodec, 0); if (videoStreamIndex < 0) { qDebug() << TIMEMS << "find video stream index error"; return false; } //获取视频流 AVStream *videoStream = formatCtx->streams[videoStreamIndex]; //获取视频流解码器,或者指定解码器 videoCodecCtx = videoStream->codec; videoCodec = avcodec_find_decoder(videoCodecCtx->codec_id); // videoCodec = avcodec_find_decoder_by_name("h264_qsv"); if (videoCodec == NULL) { qDebug() << TIMEMS << "video decoder not found"; return false; } //设置加速解码 videoCodecCtx->lowres = videoCodec->max_lowres; videoCodecCtx->flags2 |= AV_CODEC_FLAG2_FAST; //打开视频解码器 result = avcodec_open2(videoCodecCtx, videoCodec, NULL); if (result < 0) { qDebug() << TIMEMS << "open video codec error"; return false; } //获取分辨率大小 videoWidth = videoStream->codec->width; videoHeight = videoStream->codec->height; //如果没有获取到宽高则返回 if (videoWidth == 0 || videoHeight == 0) { qDebug() << TIMEMS << "find width height error"; return false; } QString videoInfo = QString("视频流信息 -> 索引: %1 解码: %2 格式: %3 " "时长: %4 秒 分辨率: %5*%6") .arg(videoStreamIndex) .arg(videoCodec->name) .arg(formatCtx->iformat->name) .arg((formatCtx->duration) / 1000000) .arg(videoWidth) .arg(videoHeight); qDebug() << TIMEMS << videoInfo; } //----------视频流部分开始---------- //----------音频流部分开始,打个标记方便折叠代码---------- if (1) { //循环查找音频流索引 audioStreamIndex = -1; for (uint i = 0; i < formatCtx->nb_streams; i++) { if (formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { audioStreamIndex = i; break; } } //有些没有音频流,所以这里不用返回 if (audioStreamIndex == -1) { qDebug() << TIMEMS << "find audio stream index error"; } else { //获取音频流 AVStream *audioStream = formatCtx->streams[audioStreamIndex]; audioCodecCtx = audioStream->codec; //获取音频流解码器,或者指定解码器 audioCodec = avcodec_find_decoder(audioCodecCtx->codec_id); // audioCodec = avcodec_find_decoder_by_name("aac"); if (audioCodec == NULL) { qDebug() << TIMEMS << "audio codec not found"; return false; } //打开音频解码器 result = avcodec_open2(audioCodecCtx, audioCodec, NULL); if (result < 0) { qDebug() << TIMEMS << "open audio codec error"; return false; } QString audioInfo = QString("音频流信息 -> 索引: %1 解码: %2 比特率: " "%3 声道数: %4 采样: %5") .arg(audioStreamIndex) .arg(audioCodec->name) .arg(formatCtx->bit_rate) .arg(audioCodecCtx->channels) .arg(audioCodecCtx->sample_rate); qDebug() << TIMEMS << audioInfo; } } //----------音频流部分结束---------- //预分配好内存 avPacket = av_packet_alloc(); yuvFrame = av_frame_alloc(); rgbFrame = av_frame_alloc(); int byte = avpicture_get_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight); buffer = (uint8_t *)av_malloc(byte * sizeof(uint8_t)); //定义像素格式 AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P; AVPixelFormat dstFormat = AV_PIX_FMT_RGB32; //通过解码器获取解码格式 srcFormat = videoCodecCtx->pix_fmt; //默认最快速度的解码采用的SWS_FAST_BILINEAR参数,可能会丢失部分图片数据,可以自行更改成其他参数 int flags = SWS_FAST_BILINEAR; //开辟缓存存储一帧数据 //以下两种方法都可以,avpicture_fill已经逐渐被废弃 // avpicture_fill((AVPicture *)rgbFrame, buffer, dstFormat, videoWidth, // videoHeight); av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, dstFormat, videoWidth, videoHeight, 1); //图像转换 swsContext = sws_getContext(videoWidth, videoHeight, srcFormat, videoWidth, videoHeight, dstFormat, flags, NULL, NULL, NULL); //输出视频信息 // av_dump_format(formatCtx, 0, url.toStdString().data(), 0); // qDebug() << TIMEMS << "init ffmpeg finsh"; return true; } void FFmpegThread::run() { qint64 startTime = av_gettime(); while (!stopped) { //根据标志位执行初始化操作 if (isPlay) { this->init(); isPlay = false; continue; } frameFinish = av_read_frame(formatCtx, avPacket); if (frameFinish >= 0) { //下面演示倍速播放 if (0) { double speed = 2.0; avPacket->pts = avPacket->pts / speed; avPacket->dts = avPacket->dts / speed; } //判断当前包是视频还是音频 int index = avPacket->stream_index; if (index == videoStreamIndex) { //解码视频流 avcodec_decode_video2 方法已被废弃 #if 0 avcodec_decode_video2(videoCodecCtx, yuvFrame, &frameFinish, avPacket); #else frameFinish = avcodec_send_packet(videoCodecCtx, avPacket); if (frameFinish < 0) { continue; } frameFinish = avcodec_receive_frame(videoCodecCtx, yuvFrame); if (frameFinish < 0) { continue; } #endif if (frameFinish >= 0) { //将数据转成一张图片 sws_scale(swsContext, (const uint8_t *const *)yuvFrame->data, yuvFrame->linesize, 0, videoHeight, rgbFrame->data, rgbFrame->linesize); //以下两种方法都可以 // QImage image(rgbFrame->data[0], videoWidth, videoHeight, // QImage::Format_RGB32); QImage image((uchar *)buffer, videoWidth, videoHeight, QImage::Format_RGB32); if (!image.isNull()) { emit receiveImage(image); } usleep(1); } #if 1 //延时(不然文件会立即全部播放完) AVRational timeBase = {1, AV_TIME_BASE}; int64_t ptsTime = av_rescale_q( avPacket->dts, formatCtx->streams[videoStreamIndex]->time_base, timeBase); int64_t nowTime = av_gettime() - startTime; if (ptsTime > nowTime) { av_usleep(ptsTime - nowTime); } #endif } else if (index == audioStreamIndex) { //解码音频流,自行处理 } } av_packet_unref(avPacket); av_freep(avPacket); usleep(1); } //线程结束后释放资源 free(); stopped = false; isPlay = false; qDebug() << TIMEMS << "stop ffmpeg thread"; } void FFmpegThread::setUrl(const QString &url) { this->url = url; } void FFmpegThread::free() { if (swsContext != NULL) { sws_freeContext(swsContext); swsContext = NULL; } if (avPacket != NULL) { av_packet_unref(avPacket); avPacket = NULL; } if (yuvFrame != NULL) { av_frame_free(&yuvFrame); yuvFrame = NULL; } if (rgbFrame != NULL) { av_frame_free(&rgbFrame); rgbFrame = NULL; } if (videoCodecCtx != NULL) { avcodec_close(videoCodecCtx); videoCodecCtx = NULL; } if (audioCodecCtx != NULL) { avcodec_close(audioCodecCtx); audioCodecCtx = NULL; } if (formatCtx != NULL) { avformat_close_input(&formatCtx); formatCtx = NULL; } av_dict_free(&options); // qDebug() << TIMEMS << "close ffmpeg ok"; } void FFmpegThread::play() { //通过标志位让线程执行初始化 isPlay = true; } void FFmpegThread::pause() {} void FFmpegThread::next() {} void FFmpegThread::stop() { //通过标志位让线程停止 stopped = true; } //实时视频显示窗体类 FFmpegWidget::FFmpegWidget(QWidget *parent) : QWidget(parent) { thread = new FFmpegThread(this); connect(thread, SIGNAL(receiveImage(QImage)), this, SLOT(updateImage(QImage))); image = QImage(); } FFmpegWidget::~FFmpegWidget() { close(); } void FFmpegWidget::paintEvent(QPaintEvent *) { if (image.isNull()) { return; } // qDebug() << TIMEMS << "paintEvent" << objectName(); QPainter painter(this); #if 0 //image = image.scaled(this->size(), Qt::KeepAspectRatio); //按照比例自动居中绘制 int pixX = rect().center().x() - image.width() / 2; int pixY = rect().center().y() - image.height() / 2; QPoint point(pixX, pixY); painter.drawImage(point, image); #else painter.drawImage(this->rect(), image); #endif } void FFmpegWidget::updateImage(const QImage &image) { // this->image = image.copy(); this->image = image; this->update(); } void FFmpegWidget::setUrl(const QString &url) { thread->setUrl(url); } void FFmpegWidget::open() { // qDebug() << TIMEMS << "open video" << objectName(); clear(); thread->play(); thread->start(); } void FFmpegWidget::pause() { thread->pause(); } void FFmpegWidget::next() { thread->next(); } void FFmpegWidget::close() { // qDebug() << TIMEMS << "close video" << objectName(); if (thread->isRunning()) { thread->stop(); thread->quit(); thread->wait(500); } QTimer::singleShot(1, this, SLOT(clear())); } void FFmpegWidget::clear() { image = QImage(); update(); }