直播系统的组成部分一:音视频采集—视频采集部分

qi.wei

发布于 2020.02.21 00:35 阅读 2687 评论 0

直播系统的组成部分一:音视频采集—视频采集部分

 

文章分为以下几个部分:

    1.多媒体设备交互类库

    2. 采集摄像头画面

    3.采集屏幕画面

  

 

 

 

多媒体设备交互类库

    在ffmpeg中有一个类库:Libavdevice使用这个类库可以读取多媒体设备的数据,或者输出数据到多媒体设备。我们进行视频采集所用到的摄像头或者截屏,都需要用到这个库的一些函数进行操作。

    在使用Libavdevice类库的时候,首先需要在代码中:

#include "libavdevice/avdevice.h"

    然后在做任何逻辑操作之前先注册Libavdevice

avdevice_register_all();

    如果不注册,控制多媒体设备的那些函数就无法成功使用。

 

 

 

采集摄像头画面

    1.新建一个C++项目,ffmpeg的环境搭建参考之前的文章:

    http://www.lindasoft.com/view/article/details?articleId=638

 

 

    2.查看摄像头名称

    可以通过如下代码进行查看

//查看所有设备

void show_dshow_device()

{



    avdevice_register_all();

    AVFormatContext *pFormatCtx = avformat_alloc_context();

    AVDictionary* options = NULL;

    av_dict_set(&options,"list_devices","true",0);

    AVInputFormat *iformat = av_find_input_format("dshow");

    printf("Device Info=============\n");

    avformat_open_input(&pFormatCtx,"video=dummy",iformat,&options);

    printf("========================");

}

运行结果:

   

    能够看到我的摄像头名称为“Lenovo EasyCamera”,这个名称接下来我们会用到。

    查看设备名称也可以通过运行ffmpeg命令进行查看:

    ffmpeg -list_devices true -f dshow -i dummy 

 

 

    3.代码示例


#include <stdio.h>

//#include <QImage>

#include <QString>



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();
    printf("aaaaaa\n");

    //打开摄像头
    pFormatCtx = avformat_alloc_context();
    ifmt=av_find_input_format("dshow");
    avformat_open_input(&pFormatCtx,"video=Lenovo EasyCamera",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_RGB24, SWS_BICUBIC, NULL, NULL, NULL);

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

    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB24,
                   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)
            {

                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), "JPG", 100);
                printf("catch %d\n",i);

            }
        }
        av_free_packet(packet);
    }

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




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

    return 0;
}

 

 

 

采集屏幕画面

    采集屏幕画面和采集摄像头画面非常相似,只是把设置摄像头参数—打开摄像头这之间的步骤换成设置截屏参数—打开截屏。

    下面是代码,可以对比一下:


//捕获桌面画面
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();
    show_dshow_device();
    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;
    }
    printf("bbbbbbbbbb\n");



    //获取视频信息
    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_RGB24, SWS_BICUBIC, NULL, NULL, NULL);

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

    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB24,
                   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)
            {

                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), "JPG", 10);
                printf("catch %d\n",i);

            }
        }
        av_free_packet(packet);
    }

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

}