QT学习总结--音视频采集

zhy

发布于 2020.04.17 10:46 阅读 451 评论 0

    完成直播系统的第一步是采集本地音视频数据。采集本地数据要使用到FFmpeg的libavdevice 类库,我们可以利用它来采集设备数据。

 

    使用libavdevice 需要包含其头文件,并对其注册。

#include "libavdevice/avdevice.h"

............
............

avdevice_register_all();

 

   采集摄像头和麦克风数据与获取本地文件的方法类似,多了一步查找输入设备av_find_input_format(),参数“dshow”是指Windows上的DirectShow输入设备。“gdigrab”是基于WIN32 GDI屏幕捕获设备,允许在Windows上捕获显示区域,我们获取屏幕信息的时候会用到。"HP Wide Vision HD Camera“是我的摄像头名称

    pFormatCtx = avformat_alloc_context();
    //查找设备
    ifmt=av_find_input_format("dshow");
    avformat_open_input(&pFormatCtx,"video=HP Wide Vision HD Camera",ifmt,NULL) ;

 

设备名称的获取方法:

//音频设备
QList<QAudioDeviceInfo> listAudio = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
    foreach(QAudioDeviceInfo audio, listAudio)
    {
        qDebug()<<audio.deviceName();
    }


//摄像头
    foreach (const QCameraInfo &cameraInfo, QCameraInfo::availableCameras()) {
        qDebug()<<cameraInfo.description();
    }

 

输出结果:

 

 

采集摄像头步骤:

    1. 初始化FFmpeg,注册libavdevice。

    2. 找到并打开输入设备

    3. 获取输入流(avformat_find_stream_info)

    4. 查找解码器,打开解码器

    5. 进入循环,读取packet数据,解码。

这和之前打开本地文件的顺序是一致的,不同的是第二步,由本地文件换为了本地设备。如果要获取屏幕,麦克风数据,需要修改第二步参数。

 

另外,在抓取摄像头和桌面数据时都用到了图片格式转换的函数,主要的函数有三个:

(1) sws_getContext():使用参数初始化SwsContext结构体。
(2) sws_scale():转换一帧图像。
(3) sws_freeContext():释放SwsContext结构体。

 

代码中是将抓取的数据,以图片的格式保存到了本地,用到了QImage,需要注意QImage的储存格式要和sws_scale转换成的格式保持一致(格式RGB32一致),否则会出现异常中断。

 

运行结果:

 

 

完整代码:

#include <stdio.h>
#include <QImage>
#include <QString>
#include <QDebug>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/pixfmt.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
}



//捕获摄像头画面
void catchCamera()
{

    AVFormatContext *pFormatCtx;//包含了封装格式的数据。
    AVCodecContext *pCodecCtx;//码流数据的解码方式相关数据。
    AVCodec *pCodec;//AVCodecContext中所包含的解码器信息。
    AVFrame *pFrame, *pFrameRGB;//存储压缩编码数据相关信息的结构体。
    AVPacket *packet;//解码后的数据
    uint8_t *out_buffer;
    AVInputFormat *ifmt;
    //转换图片格式上下文,用于转换图片格式
    static struct SwsContext *img_convert_ctx;

    int videoStream, i, numBytes;
    int ret, got_picture;

    av_register_all(); //初始化FFMPEG  调用了这个才能正常适用编码器和解码器
    //注册设备
    avdevice_register_all();
    //分配内存
    pFormatCtx = avformat_alloc_context();
    //打开摄像头
    ifmt=av_find_input_format("dshow");
    avformat_open_input(&pFormatCtx,"video=HP Wide Vision HD Camera",ifmt,NULL) ;
    //获取视频信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        printf("Could't find stream infomation.\n");
        return;
    }

    videoStream = -1;

    ///循环查找视频中包含的流信息,直到找到视频类型的流
    ///便将其记录下来 保存到videoStream变量中
    ///这里我们现在只处理视频流  音频流先不管他
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
        }
    }

    ///如果videoStream为-1 说明没有找到视频流
    if (videoStream == -1) {
        printf("Didn't find a video stream.\n");
        return;
    }

    ///查找解码器
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    if (pCodec == NULL) {
        printf("Codec not found.\n");
        return;
    }

    ///打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec.\n");
        return;
    }
    else
    {
        printf("打开解码器\n");
    }

    //分配内存
    pFrame = av_frame_alloc();
    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);

    //分配缓存空间
    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));

    //前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,
    //这里把av_malloc得到的内存和AVFrame关联起来。
    //当然,其还会设置AVFrame的其他成员
    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的数据


    int index = 0;

    printf("cccccc\n");
    ///我们就取10张图像
    for(int i=0;i<10;i++)
    {
        if(av_read_frame(pFormatCtx, packet) < 0)
        {
            printf("no frame\n");
            //break;
        }

        if(packet->stream_index==videoStream)
        {
            //解码
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

            if(got_picture)
            {
                //将图片格式转换为RGB32格式,转化后的数据存在pFrameRGB中
                sws_scale(img_convert_ctx,
                          (uint8_t const * const *) pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                          pFrameRGB->linesize);

                //构造函数,传入RGB数据,用QImage加载
                QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
                tmpImg.save(QString::number(i)+".png", "PNG", 100);
                printf("catch %d\n",i);

            }
        }
        av_free_packet(packet);
    }

    av_free(out_buffer);
    av_free(pFrameRGB);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
}

//捕获桌面画面
void catchDesktop()
{

    AVFormatContext *pFormatCtx;//包含了封装格式的数据。
    AVCodecContext *pCodecCtx;//码流数据的解码方式相关数据。
    AVCodec *pCodec;//AVCodecContext中所包含的解码器信息。
    AVFrame *pFrame, *pFrameRGB;//存储压缩编码数据相关信息的结构体。
    AVPacket *packet;//解码后的数据
    uint8_t *out_buffer;
    AVInputFormat *ifmt;
    AVDictionary* options = NULL;

    static struct SwsContext *img_convert_ctx;

    int videoStream, i, numBytes;
    int ret, got_picture;

    av_register_all(); //初始化FFMPEG  调用了这个才能正常适用编码器和解码器

    avdevice_register_all();
    printf("aaaaaa\n");

    //截屏
    pFormatCtx = avformat_alloc_context();
    //Set some options
    //grabbing frame rate
    av_dict_set(&options,"framerate","5",0);
    //The distance from the left edge of the screen or desktop
    av_dict_set(&options,"offset_x","20",0);
    //The distance from the top edge of the screen or desktop
    av_dict_set(&options,"offset_y","40",0);
    //Video frame size. The default is to capture the full screen
//    av_dict_set(&options,"video_size","640x480",0);
    ifmt=av_find_input_format("gdigrab");
    if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){
        printf("Couldn't open input stream.");
        return;
    }


    //获取视频信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        printf("Could't find stream infomation.\n");
        return;
    }

    videoStream = -1;

    ///循环查找视频中包含的流信息,直到找到视频类型的流
    ///便将其记录下来 保存到videoStream变量中
    ///这里我们现在只处理视频流  音频流先不管他
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
        }
    }

    ///如果videoStream为-1 说明没有找到视频流
    if (videoStream == -1) {
        printf("Didn't find a video stream.\n");
        return;
    }

    ///查找解码器
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    if (pCodec == NULL) {
        printf("Codec not found.\n");
        return;
    }

    ///打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec.\n");
        return;
    }
    else
    {
        printf("打开解码器\n");
    }

    pFrame = av_frame_alloc();
    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);

    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);

    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的数据

    int index = 0;

    ///我们就读取10张图像
    for(int i=0;i<10;i++)
    {
        if(av_read_frame(pFormatCtx, packet) < 0)
        {
            printf("no frame\n");
            //break;
        }

        if(packet->stream_index==videoStream)
        {
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

            if(got_picture)
            {

                //图像格式转换
                sws_scale(img_convert_ctx,
                          (uint8_t const * const *) pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                          pFrameRGB->linesize);

                qDebug()<<pFrameRGB;

                //把这个RGB数据 用QImage加载
                QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,
                              QImage::Format_RGB32);

                tmpImg.save(QString::number(i)+".png","PNG",100);

                printf("catch %d\n",i);

            }
        }

        av_free_packet(packet);
    }

    sws_freeContext(img_convert_ctx);
    av_free(out_buffer);
    av_free(pFrameRGB);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

}

//获取麦克风音频
void getVoice()
{

    AVFormatContext *aFormatCtx;//包含了封装格式的数据。
    AVCodecContext *aCodecCtx;//码流数据的解码方式相关数据。
    AVCodec *aCodec;//AVCodecContext中所包含的解码器信息。
    AVFrame *aFrame;//存储压缩编码数据相关信息的结构体。
    AVPacket *packet;//解码后的数据
    uint8_t *out_buffer;
    AVInputFormat *ifmt;


    int audioStream, i;
    int ret, got_frame;

    av_register_all(); //初始化FFMPEG  调用了这个才能正常适用编码器和解码器
    avdevice_register_all();//初始化设备


    aFormatCtx = avformat_alloc_context();

    //打开麦克风,这里用到音频输入设备的设备名称
    QString audioDeviceName = QStringLiteral("audio=麦克风阵列 (Realtek(R) Audio)");
    ifmt = av_find_input_format("dshow");
    if(avformat_open_input(&aFormatCtx,audioDeviceName.toUtf8().data(),ifmt,NULL)!=0){
        //fprintf(stderr,"Couldn't open input stream.(无法打开输入流)");
        printf("Couldn't open input stream.\n");
        return;
    }
    else
    {
        printf("open input stream OK.\n");
    }


    audioStream = -1;

    ///循环查找视频中包含的流信息,直到找到视频类型的流
    ///便将其记录下来 保存到audioStream变量中
    ///这里我们现在只处理视频流  音频流先不管他
    for (i = 0; i < aFormatCtx->nb_streams; i++) {
        if (aFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {//一定注意这个地方不要和视频流的类型弄混了
            audioStream = i;
        }
    }

    ///如果audioStream为-1 说明没有找到音频流
    if (audioStream == -1) {
        printf("Didn't find a video stream.\n");
        return;
    }
    else
    {
        printf("find a video stream.\n");
    }

    ///查找解码器
    aCodecCtx = aFormatCtx->streams[audioStream]->codec;
    aCodec = avcodec_find_decoder(aCodecCtx->codec_id);

    if (aCodec == NULL) {
        printf("Codec not found.\n");
        return;
    }
    else
    {
        printf("Codec is found.\n");
    }


    ///打开解码器
    if (avcodec_open2(aCodecCtx, aCodec, NULL) < 0) {
        printf("Could not open codec.\n");
        return;
    }
    else
    {
        printf("打开解码器\n");
    }

    aFrame = av_frame_alloc();
    packet=(AVPacket *)av_malloc(sizeof(AVPacket));

    FILE *fp_pcm=fopen("output.pcm","wb");

    ///这里打印出音频的信息
    qDebug()<<"audio info:";
    qDebug()<<"audio info:"<<aCodecCtx->sample_fmt<<aCodecCtx->bit_rate<<aCodecCtx->sample_rate<<aCodecCtx->channels;

    float Time = 0;

    for(int i=0;;i++)
    {
        if (Time > 10) break; //就采集10秒

        if(av_read_frame(aFormatCtx, packet) < 0)
        {
            break;
        }

        if(packet->stream_index==audioStream)
        {
            //解码
            ret = avcodec_decode_audio4(aCodecCtx, aFrame, &got_frame, packet);
            if(ret < 0)
            {
                fprintf(stderr,"Audio Error.");
                return;
            }

            //转换数据,写入文件
            if (got_frame)
            {
                int pcmSize = av_samples_get_buffer_size(NULL,aCodecCtx->channels, aFrame->nb_samples,aCodecCtx->sample_fmt, 1);
                uint8_t * pcmBuffer = aFrame->data[0];
                //采样数/采样率=时间
                float useTime = aFrame->nb_samples * 1.0 / aCodecCtx->sample_rate;
                Time += useTime;
                qDebug()<<i<<Time<<useTime;

                fwrite(pcmBuffer,1,pcmSize,fp_pcm); //写入文件

            }

        }
        av_free_packet(packet);
    }

    printf("END\n");
    fclose(fp_pcm);
    av_free(out_buffer);
    avcodec_close(aCodecCtx);
    avformat_close_input(&aFormatCtx);
}

int main()
{
//    catchCamera();
    catchDesktop();
//    getVoice();

    return 0;
}