Qt基于FFmpeg读取摄像头并进行H264编码

一、读取摄像头的基础知识

1、读取摄像头用到的类库Libavdevice:可以读取电脑(或者其他设备上)的多媒体设备的数据,或者输出数据到指定的多媒体设备上等。

这个库支持以下设备作为输入端:avfoundation、bktr、dshow(本文使用)、fbdev、gdigrab、jack、lavfi、libcdio、openal、vfwcap等

支持以下设备作为输出端:alsa、caca、decklink、fbdev、opengl、sndio、xv等

2、libavdevice使用

头文件:#include "libavdevice/avdevice.h"

注册:libavdevice:avdevice_register_all()

2.1使用libavdevice读取数据和直接打开视频文件类似。因为系统的设备也被FFmpeg认为是一种输入的格式(即AVInputFormat)。使用FFmpeg打开一个普通的视频文件如下:

AVFormatContext *formatContext = avformat_alloc_context();
avformat_open_input(&formatContext , "Warcraft3_End.avi",nullptr,nullptr);

2.2使用libavdevice,唯一的不同在于需要首先查找用于输入的设备。(av_find_input_format())

AVFormatContext *formatContext = avformat_alloc_context();
AVInputFormat *ifmt=av_find_input_format("dshow");//windows上视频的推流格式
avformat_open_input(&formatContext , "video=Integrated Camera", ifmt,NULL);

注:1、如果是指定了vfwcap设备作为输入设备,在URL中指定打开第0个摄像头设备如下

avformat_open_input(&formatContext , 0, ifmt,NULL);

2、URL的格式是"video={设备名称}",设备名称外面不能加引号,注意空格。

3、dshow的设备名称必须要提前获取

2.3可以在   我的电脑--》管理--》设备管理器   中查找本机摄像头名称也可以通过代码获取 

获取摄像头数量和名称

.pro文件中添加如下:

QT         +=   multimedia
QT         +=  multimediawidgets
    //头文件QCameraInfo、QCamera
    QList cameraList = QCameraInfo::availableCameras();
    qDebug()<

本文实现的是读取摄像头生成yuv和rgb数据两份数据,利用解码线程类实现边解码生成rgb数据边在窗口类播放,利用生成的yuv数据进行编码生成h264文件(可播放)

二、实现效果

1、读取摄像头:

Qt基于FFmpeg读取摄像头并进行H264编码_第1张图片

 2、摄像头数据解码生成YUV数据后再编码生成.h264文件,可播放

用eseye_u.exe 打开.h264文件如下图:

Qt基于FFmpeg读取摄像头并进行H264编码_第2张图片

 三、代码

解码类cpp文件如下:

关于编码类(yuv—>h264)的博客和源码请看文末链接

fdecode::fdecode()
{
    fcode = new fcode_h264;
    fcode->codecInit();//编码器初始化
}

void fdecode::registerFFmpeg()
{
    av_register_all();//注册所有组件
    avdevice_register_all();//注册摄像头
}

void fdecode::openVideoStream(QString filename)
{
    //参数1:封装格式上下文->AVFormatContext->包含了视频信息(视频格式、大小等等...)双指针定义一颗星*,
    //参数2:要打开流的路径(文件名)
    //AVFormatContext保存视频(视频流)相关信息的结构体
    AVFormatContext  * formatContent = avformat_alloc_context();

    /*2、打开摄像头*/
    AVInputFormat *fmt = av_find_input_format("dshow");//windows上视频的推流格式
    QString cameraName = QString("video=%1").arg(filename);
    int res = avformat_open_input(&formatContent,cameraName.toStdString().c_str(),fmt,nullptr);//"video=USB2.0 VGA UVC WebCam"
    if(res!=0)
    {
        qDebug()<<"打开摄像头失败";
        return ;
    }
    QList cameraList = QCameraInfo::availableCameras();
    qDebug()<nb_streams;i++)
    {
        if(formatContent->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)//streams->有AVStream结构体,AVStream->codec
        {
            //找到视频流(只有一个)
            videoType = i;//标识视频流这个类型
            break;
        }
    }
    if(videoType == -1)
    {
        qDebug()<<"没有找到视频流相关信息";
        return;
    }
    //3.3根据视频流查找编码器对应的上下文对象结构体,存储编码器以及宽高格式等
    AVCodecContext *codec = formatContent->streams[videoType]->codec;
    /*4、有视频流,则查找对应视频流的解码器*/
    AVCodec *decoder = avcodec_find_decoder(codec->codec_id);//需要解码器的id
    if(decoder ==nullptr)
    {
        qDebug()<<"没有找到对应的解码器";
        return;
    }

    /*5、找到解码器后打开解码器*/
    //参数:1.初始化的上下文对象 2.打开的解码器 3.类似目录的东西(没有)
    res = avcodec_open2(codec,decoder,nullptr);
    if(res!=0)
    {
        qDebug()<<"解码器打开失败";
        return;
    }
    /*6、获取到的每一帧码流(视频流)数据写到文件中,进行循环解码*/
    /*6.1写到文件用FILE结构体*/
    AVPacket *pkt=nullptr;//pkt这时没有指向,要我们给他分配内存空间,希望把读出来的数据放到这块内存去
    pkt = (AVPacket *)malloc(sizeof(AVPacket));
    //码流数据是存到buffer里面,也需要我们动态开空间(AVBufferRef *buf;)
    //开空间不知道一帧的码流数据是多少?其实编解码器告诉了宽高,以此可以计算出给码流数据开多大空间
    int bufSize = codec->width*codec->height;//计算一帧(图)数据的大小
    av_new_packet(pkt,bufSize);

    AVFrame *pictureRGB = av_frame_alloc();;//保存解码及剔除后的像素数据(做准备)
    pictureRGB->width = codec->width;
    pictureRGB->height = codec->height;
    pictureRGB->format = codec->pix_fmt;//格式的设置
    //要存解码后的像素数据到pictureRGB,那这个数据有多大呢?
    //获取解码后的一帧像素数据有多大
    int numByte = avpicture_get_size(AV_PIX_FMT_RGB32,codec->width,codec->height);
    //开的空间用来保存像素数据的大小
    uint8_t *buffer = (uint8_t *)av_malloc(numByte*sizeof(uint8_t));
    //像素数据填充到AVFrame的pictureRGB里
    avpicture_fill((AVPicture *)pictureRGB,buffer,AV_PIX_FMT_RGB32,codec->width,codec->height);
    //因为解码之后要伸展,所以先进行转换规则的设置,转换完进入第七步解码
    SwsContext *swsContent = nullptr;
    swsContent = sws_getContext(codec->width,codec->height,codec->pix_fmt,
                                codec->width,codec->height,AV_PIX_FMT_RGB32,
                                SWS_BICUBIC,nullptr,nullptr,nullptr);

    AVFrame *pictureYUV = av_frame_alloc();
    pictureYUV->width = codec->width;
    pictureYUV->height = codec->height;
    pictureYUV->format = codec->pix_fmt;//格式的设置
    int numByte2 = avpicture_get_size(AV_PIX_FMT_YUV420P,codec->width,codec->height);
    uint8_t *bufferYUV = (uint8_t *)av_malloc(numByte2*sizeof(uint8_t));

    avpicture_fill((AVPicture *)pictureYUV,bufferYUV,AV_PIX_FMT_YUV420P,codec->width,codec->height);
    SwsContext *swsContentYUV = nullptr;
    swsContentYUV = sws_getContext(codec->width,codec->height,codec->pix_fmt,
                                codec->width,codec->height,AV_PIX_FMT_YUV420P,
                                SWS_BICUBIC,nullptr,nullptr,nullptr);
    int count = 0;
    AVFrame *picture = av_frame_alloc();//保存原始RGB、YUV数据
    while(av_read_frame(formatContent,pkt) >= 0)//成功读到了数据(一帧一帧读(循环):av_read_frame)
    {
        /*6.2AVPacket->AVStream,要判断读到的每一帧的码流数据是不是视频流*/
        if(pkt->stream_index == videoType)
        {

            avcodec_send_packet(codec,pkt);
            res = avcodec_receive_frame(codec,picture);
                //把解码得到的损坏的像素数据剔除(存到pictureRGB中)
                sws_scale(swsContent,(const uint8_t * const *)picture->data,picture->linesize,0,picture->height,
                          pictureRGB->data,pictureRGB->linesize);

                sws_scale(swsContentYUV,(const uint8_t * const *)picture->data,picture->linesize,0,picture->height,
                          pictureYUV->data,pictureYUV->linesize);
                count++;
                QImage desImage = QImage((uchar*)buffer,codec->width,codec->height,
                                         QImage::Format_RGB32,nullptr,nullptr); //RGB32

                emit sendImage(desImage);//得到图片的时候触发信号
                msleep(25);

                fcode->codecFrame(pictureYUV);//解码得到一帧像素数据后调用编码函数(YUV-H264)

            if(count ==400)//录制400帧结束循环
            {
                break;
            }

        }
        //每次都存在同一块内存空间里,要清空上一次的操作
        av_packet_unref(pkt);//不是free
    }

    qDebug()<<"保存码流数据和像素数据成功";
    fcode->writeEndFrame();//写入尾巴帧

}

void fdecode::run()
{
    this->fileName="USB2.0 VGA UVC WebCam";//摄像头名称
    this->registerFFmpeg();//注册
    this->openVideoStream(this->fileName);
}

注意点:

要进行录像多少帧就结束的判断,否则当直接关掉运行窗口,尾巴帧都没有写入,导致生成h264文件时为0kb

 有关编码类(yuv—>h264),请读者参考我的博客:

https://blog.csdn.net/hml111666/article/details/122571168

 演示用的eseye_u.exe破解版免安装包:

eseye_u.exe破解版免安装包-编解码文档类资源-CSDN下载

演示用的视频下载链接:

ffmpeg(h264、yuv、rgb)演示视频-C/C++文档类资源-CSDN下载

源码下载链接:

 Qt基于FFmpeg读取摄像头并进行H264编码-编解码文档类资源-CSDN下载

你可能感兴趣的:(Qt实战,流媒体,qt,音视频,ffmpeg)