很早之前用过一段时间的ffmpeg,主要参考的雷神的博客,现在回过头搭建下开发环境,发现都不一样了,这里记录下环境搭建过程。
官网:https://www.ffmpeg.org/
github地址:https://github.com/FFmpeg/FFmpeg
打开官网:
点击Download,本文选择windows下的 Windows builds from gyan.dev
然后选择release builds下面的 ffmpeg-release-full-shared.7z下载
新建工程,在pro文件中加入:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
INCLUDEPATH += "C:/Qt/ffmpeg-5.0-full_build-shared/include"
LIBS += -LC:/Qt/ffmpeg-5.0-full_build-shared/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample
注意自己的库路径
代码中加入
extern "C" {
#include
#include
#include
#include
}
附上在线测试地址:http://www.w3school.com.cn/example/html5/mov_bbb.mp4
main.cpp
#include "mainwindow.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void createPreviewWidthFile(const char * file);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
extern "C" {
#include
#include
#include
#include
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//本地文件
//createPreviewWidthFile("C:/Users/wmm/Desktop/demo/test.mp4");
//在线地址
QElapsedTimer t;
t.start();
createPreviewWidthFile("http://www.w3school.com.cn/example/html5/mov_bbb.mp4");
qDebug()<< "last = "<< t.elapsed();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::createPreviewWidthFile(const char *file){
AVFormatContext* fmt_ctx_ = nullptr;
qDebug() << "111" << avcodec_version();
//打开视频文件
int errCode = avformat_open_input(&fmt_ctx_, file, nullptr, nullptr);
if(errCode != 0){
qDebug() << "avformat_open_input fail" << errCode;
return;
}
qDebug() << "222" ;
//读取音视频流信息
errCode = avformat_find_stream_info(fmt_ctx_, nullptr);
if(errCode != 0){
qDebug() << "avformat_find_stream_info fail" << errCode;
avformat_close_input(&fmt_ctx_);
return;
}
qDebug() << "333" ;
//打印输出视频相关信息
av_dump_format(fmt_ctx_, 0, file, 0);
qDebug() << "444" ;
AVPacket* pkt = av_packet_alloc();
AVFrame* temp_frame = av_frame_alloc();
SwsContext* sws_ctx = nullptr;
int ret = 0;
QImage preview;
bool preview_done = false;
int videoStream = 0;
for (int i=0; i<int(fmt_ctx_->nb_streams) && !preview_done; i++){
//只处理视频信息
if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
//查找视频解码器
videoStream = i;
const AVCodec* codec = avcodec_find_decoder(fmt_ctx_->streams[i]->codecpar->codec_id);
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
//根据提供的编解码器参数的值填充编解码器上下文
avcodec_parameters_to_context(codec_ctx, fmt_ctx_->streams[i]->codecpar);
//打开解码器
avcodec_open2(codec_ctx, codec, nullptr);
qDebug() << "555" ;
//读取帧数据
while (av_read_frame(fmt_ctx_, pkt) >= 0){
if (pkt->stream_index == videoStream) {
av_frame_unref(temp_frame);
qDebug() << "666" ;
//对视频帧数据进行解码
while ((ret = avcodec_receive_frame(codec_ctx, temp_frame)) == AVERROR(EAGAIN)){
ret = avcodec_send_packet(codec_ctx, pkt);
qDebug() << "777" ;
if (ret < 0) {
qCritical() << "Failed to send packet to decoder." << ret;
break;
}
}
if(ret < 0 && ret != AVERROR_EOF){
qDebug() << "Failed to receive packet from decoder." << ret;
continue;
}
qDebug() << "777" << temp_frame->width << temp_frame->height;
//等比例缩放
int dstH = 240;
int dstW = qRound(dstH * (float(temp_frame->width)/float(temp_frame->height)));
//消除可能的告警
dstH = (dstH >> 4) << 4;
dstW = (dstW >> 4) << 4;
qDebug() << "777" << dstW << dstH;
sws_ctx = sws_getContext(
temp_frame->width,
temp_frame->height,
static_cast<AVPixelFormat>(temp_frame->format),
dstW,
dstH,
static_cast<AVPixelFormat>(AV_PIX_FMT_RGBA),
SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr
);
int linesize[AV_NUM_DATA_POINTERS];
linesize[0] = dstW*4;
qDebug() << "888" ;
//生成图片
preview = QImage(dstW, dstH, QImage::Format_RGBA8888);
uint8_t* data = preview.bits();
sws_scale(sws_ctx,
temp_frame->data,
temp_frame->linesize,
0,
temp_frame->height,
&data,
linesize);
sws_freeContext(sws_ctx);
qDebug() << "9999" ;
avcodec_close(codec_ctx);
avcodec_free_context(&codec_ctx);
preview_done = true;
break;
}
}
}
}
qDebug() << "10" ;
av_frame_free(&temp_frame);
av_packet_free(&pkt);
avformat_close_input(&fmt_ctx_);
if(preview_done){
ui->label->setPixmap(QPixmap::fromImage(preview));
}
}
解决方法
调用 int av_read_frame(AVFormatContext *s, AVPacket *pkt) 函数 时,将会读取一帧数据并填充到AVPacket里面,但是读取的这帧数据可能是视频也可能是音频,也可能是字幕,具体是什么类型可以通过 AVPacket对象的 stream_index 属性来判断,因此在读取完一帧数据后,调用 avcodec_send_packet(inputCodecCtx, inputPacket) 函数将这帧数据发送到解码队列之前,一定要确保这帧数据(AVPacket)的类型与 解码器上下文的类型保持一致!
也就是说你AVPacket中的stream_index对应的是音频流数据,那你在调用avcodec_send_packet()函数时,第二个参数传入的必须是一个音频解码器上下文,只有这样send packet才有可能成功。