一、看前要先了解FFmpeg和Qt的用法
从笔记直接复制粘贴过来的,被吐槽了~_~,整理一下
这只是个Demo、不要想着直接复制粘贴就能跑起来、代码仅供参考、多研究!程序员呢 !(!_!)!
本人新手小白
二、废话不多说直接上代码
1.创建一个跑线程的类,处理音视频编解码并显示这些都必须在线程中进行,不然会卡主界面GUI线程
class Worker:public QObject
{
Q_OBJECT
public:
Worker();
~Worker();
signals:
void sig_GetOneFrame(QImage);
protected slots:
void ffmpeg_test();
private:
};
2.利用FFmpeg提供的接口打开摄像头并获取摄像头流数据,处理并发
void Worker::ffmpeg_test()
{
AVFormatContext *pFormatCtx;//FFMPEG所有的操作都要通过这个AVFormatContext来进行
AVInputFormat *ifmt;//使用libavdevice的时候,唯一的不同在于需要首先查找用于输入的设备
int i, videoindex;
int numBytes;
int ret, got_picture;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVFrame *pFrame, *pFrameRGB;
AVPacket *packet;
uint8_t *out_buffer;
qDebug() << "current thread ID --- ffmpeg_test:" << QThread::currentThreadId();
//1_开始
qDebug() << "Hello FFmpeg!";
unsigned version = avcodec_version();//获取FFmpeg版本号 unsigned int 类型
qDebug() << "version is:" << version;
//2_初始化
av_register_all(); //初始化FFMPEG 调用了这个才能正常适用编码器和解码器
avformat_network_init();//初始化FFmpeg网络模块
avdevice_register_all();//初始化libavdevice并注册所有输入和输出设备
pFormatCtx = avformat_alloc_context();//分配一个AVFormatContext,查找用于输入的设备
//3_使用libavdevice读取数据,和直接打开视频文件比较类似,
// 因为系统的设备也被FFmpeg认为是一种输入的格式(即AVInputFormat)
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();//获取当前可用摄像头
qDebug() << cameras.size();
QString cam_name = QString("video=") + cameras.at(0).description();//格式必须为"video=Integrated Camera"
qDebug() << cam_name;
QByteArray char_cam_name = cam_name.toLatin1();//将QString类型数据转化为 const char* 类型
#if USE_DSHOW
ifmt = av_find_input_format("dshow");//Libavdevice选择dshow(DirectShow)设备作为输入端
//Set own video device's name
if(avformat_open_input(&pFormatCtx,char_cam_name,ifmt,NULL)!=0){//打开指定设备 —— cameras.at()
qDebug() << "Couldn't open input stream.\n";
}else{
qDebug() << "Success open input stream —— " << char_cam_name;
}
#else
AVInputFormat *ifmt=av_find_input_format("vfwcap");
if(avformat_open_input(&pFormatCtx,"0",ifmt,NULL)!=0){
qDebug() << "Couldn't open input stream.\n";
}
//使用DirectShow作为输入设备
// pFormatCtx = avformat_alloc_context();
// ifmt=av_find_input_format("dshow");
// avformat_open_input(&pFormatCtx,"video=Integrated Camera",ifmt,NULL) ;
//打开测试视频
// pFormatCtx = avformat_alloc_context();
// avformat_open_input(&pFormatCtx, "test.h265",NULL,NULL);
#endif
if(avformat_find_stream_info(pFormatCtx,NULL) < 0)//读取一个媒体文件的数据包以获取流信息
{
qDebug() << "Couldn't find stream information.\n";
}else{
qDebug() << "Success find stream information!\n";
}
//4_循环查找数据包包含的流信息,直到找到视频类型的流
// 便将其记录下来 保存到videoStream变量中
videoindex = -1;
for(i = 0; i < pFormatCtx->nb_streams; i++)
{
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoindex=i;
break;
}
}
if(videoindex==-1)
{
qDebug() << "Couldn't find a video stream.\n";
}else{
qDebug() << "Success find a video stream!\n";
}
//5_查找对应的解码器并打开
pCodecCtx = pFormatCtx->streams[videoindex]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
// AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);//软编码
// AVCodec * codec = avcodec_find_encoder_by_name("nvenc_h264");//硬编码
if(pCodec == NULL)
{
qDebug() << ("Codec not found.\n");
}else{
qDebug() << "Codec found Successfuly!\n";
}
// pCodecCtx->bit_rate =0; //初始化为0
// pCodecCtx->time_base.num=1; //下面两行:一秒钟25帧
// pCodecCtx->time_base.den=10;
// pCodecCtx->frame_number=1; //每包一个视频帧
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)//打开解码器
{
qDebug() << ("Could not open codec.\n");
}else{
qDebug() << "Success open codec!\n";
//6_开始准备读取视频
pFrame = av_frame_alloc();//分配一个AVFrame并将其字段设置为默认值
pFrameRGB = av_frame_alloc();
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);//分配和返回一个SwsContext你需要它来执行使用swsscale()的缩放/转换操作
numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
qDebug() << numBytes;
out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
pCodecCtx->width, pCodecCtx->height);//根据指定的图像参数和提供的图像数据缓冲区设置图像域
int y_size = pCodecCtx->width * pCodecCtx->height;
packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据
av_dump_format(pFormatCtx, 0, QApplication::applicationDirPath().toLatin1(), 0); //输出视频信息
//7_解码压缩
while (1)
{
if (av_read_frame(pFormatCtx, packet) < 0)
{
break; //这里认为视频读取完了
}
if (packet->stream_index == videoindex) {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);//解码一帧视频数据
if (ret < 0) {
qDebug() << ("decode error.");
}
if (got_picture) {
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);//在 pFrame->data 中缩放图像切片,并将得到的缩放切片放在pFrameRGB->data图像中
//把这个RGB数据 用QImage加载
QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
emit sig_GetOneFrame(image); //发送信号
QThread::msleep(10);
}
}
av_free_packet(packet); //释放资源,否则内存会一直上升
QThread::msleep(10);
}
av_free(out_buffer);
av_free(pFrameRGB);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
}
//主界面界面显示
void CameraWidget::slot_GetOneFrame(QImage img)
{
ui->label->setPixmap(QPixmap::fromImage(img));
}
3.主线程中初始化调用,开启线程
t = new QThread;
worker = new Worker;
connect(t, &QThread::finished, worker, &QObject::deleteLater);//防止内存泄漏
connect(worker, SIGNAL(sig_GetOneFrame(QImage)), this, SLOT(slot_GetOneFrame(QImage)));
worker->moveToThread(t);
t->start();
QTimer::singleShot(1,worker,SLOT(ffmpeg_test()));//1毫秒之后槽函数已经在线程中运行