QT调用FFMEG库部署到Android设备,读取摄像头一帧数据显示

一、系统环境介绍

PC环境: ubuntu18.04

Android版本: 8.1

Android设备: 友善之臂 RK3399 开发板

摄像头:  罗技USB摄像头

FFMPEG版本: 4.2.2

NDK版本: R19C

QT版本:  5.12

二、QT代码

关于FFMPEG库的编译、QT的环境搭建等问题,可以看上篇文章。

直接上核心代码: 

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QProcess>
#include <QDebug>

/*
 * 设置QT界面的样式
*/
void MainWindow::SetStyle(const QString &qssFile)
{
    QFile file(qssFile);
    if (file.open(QFile::ReadOnly)) {
        QString qss = QLatin1String(file.readAll());
        qApp->setStyleSheet(qss);
        QString PaletteColor = qss.mid(20,7);
        qApp->setPalette(QPalette(QColor(PaletteColor)));
        file.close();
    }
    else
    {
        qApp->setStyleSheet("");
    }
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->SetStyle(":/images/blue.css" );        //设置样式表
    this->setWindowIcon(QIcon(":/images/log.ico")); //设置图标
    this->setWindowTitle("FFMPEG测试DEMO");
    RefreshCameraList(); //刷新摄像头列表
}


MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::get_ffmpeg_version_info()
{
    QProcess process;
    process.start("pwd");
    process.waitForFinished();
    QByteArray output = process.readAllStandardOutput();
    QString str_output = output;
    ui->plainTextEdit_FFMPEG_info_Display->insertPlainText("当前APP的工作路径:"+str_output);
    ui->plainTextEdit_FFMPEG_info_Display->insertPlainText(tr("FFMPEG的版本号:%1\n").arg(av_version_info()));
    av_register_all();
    AVCodec *c_temp = av_codec_next(nullptr);
    QString info="FFMPEG支持的解码器:\n";
    while (c_temp != nullptr)
    {
        if (c_temp->decode != nullptr)
        {
            info+="[Decode]";
        }
        else
        {
            info+="[Encode]";
        }
        switch (c_temp->type)
        {
        case AVMEDIA_TYPE_VIDEO:
            info+="[Video]";
            break;
        case AVMEDIA_TYPE_AUDIO:
            info+="[Audeo]";
            break;
        default:
            info+="[Other]";
            break;
        }
        info+=c_temp->name;
        info+=tr(" ID=%1").arg(c_temp->id);
        info+="\n";
        c_temp = c_temp->next;
    }
    ui->plainTextEdit_FFMPEG_info_Display->insertPlainText(info);
}

int MainWindow::FFMPEG_Init_Config(const char *video,const char *size)
{
    AVInputFormat   *ifmt;
    AVFormatContext *pFormatCtx;
    AVCodecContext  *pCodecCtx;
    AVCodec         *pCodec;
    AVDictionary    *options=nullptr;
    AVPacket        *packet;
    AVFrame         *pFrame,*pFrameYUV;

    int videoindex;
    int i,ret,got_picture;
    /*1. FFMPEG初始化*/
    av_register_all();
    avcodec_register_all();
    avdevice_register_all(); //注册多媒体设备交互的类库
    /*2. 查找用于输入的设备*/
    ifmt=av_find_input_format("video4linux2");
    pFormatCtx=avformat_alloc_context();
    av_dict_set(&options,"video_size",size,0); //设置摄像头输出的分辨率
    //av_dict_set(&options,"framerate","30",0);     //设置摄像头帧率. 每秒为单位,这里设置每秒30帧.
    //一般帧率不用设置,默认为最高,帧率和输出的图像尺寸有关系
    if(avformat_open_input(&pFormatCtx,video,ifmt,&options)!=0)
    {
        ui->plainTextEdit_CameraOpenInfo->insertPlainText(tr("输入设备打开失败: %1\n")
                                                             .arg(video));
        return -1;
    }
    if(avformat_find_stream_info(pFormatCtx,nullptr)<0)
    {
        ui->plainTextEdit_CameraOpenInfo->insertPlainText("查找输入流失败.\n");
        return -2;
    }
    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)
    {
        ui->plainTextEdit_CameraOpenInfo->insertPlainText("视频流查找失败.\n");
        return -3;
    }
    pCodecCtx=pFormatCtx->streams[videoindex]->codec;
    ui->plainTextEdit_CameraOpenInfo->insertPlainText(tr("摄像头尺寸(WxH): %1 x %2 \n").arg(pCodecCtx->width).arg(pCodecCtx->height));
    ui->plainTextEdit_CameraOpenInfo->insertPlainText(tr("codec_id=%1").arg(pCodecCtx->codec_id));
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec==nullptr)
    {
        ui->plainTextEdit_CameraOpenInfo->insertPlainText("找不到编解码器.\n");
        return -4;
    }
    if(avcodec_open2(pCodecCtx, pCodec,nullptr)<0)
    {
        ui->plainTextEdit_CameraOpenInfo->insertPlainText("无法打开编解码器.\n");
        return -5;
    }

    packet=(AVPacket *)av_malloc(sizeof(AVPacket));
    pFrame=av_frame_alloc();
    pFrameYUV=av_frame_alloc();
    unsigned char *out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,16));   // avpicture_get_size

    av_image_fill_arrays(pFrameYUV->data,pFrameYUV->linesize,out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,16);

    struct SwsContext *img_convert_ctx;
    img_convert_ctx=sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

    //读取一帧数据
    if(av_read_frame(pFormatCtx, packet)>=0)
    {
        //输出图像的大小
        ui->plainTextEdit_CameraOpenInfo->insertPlainText(tr("数据大小=%1\n").arg(packet->size));
        //判断是否是视频流
        if(packet->stream_index==videoindex)
        {
            //解码从摄像头获取的数据,pframe结构
            ret=avcodec_decode_video2(pCodecCtx, pFrame,&got_picture,packet);
            if(ret<0)
            {
                ui->plainTextEdit_CameraOpenInfo->insertPlainText("解码Error.\n");
                return -6;
            }
            else
            {
                if(got_picture)
                {
                     size_t y_size=pCodecCtx->width*pCodecCtx->height;
                     sws_scale(img_convert_ctx,(const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);  //根据前面配置的缩放参数,进行图像格式转换以及缩放等操作

                     unsigned char *p=new unsigned char[y_size]; //申请空间
                     unsigned char *rgb24_p=new unsigned char[pCodecCtx->width*pCodecCtx->height*3];

                     //将YUV数据拷贝到缓冲区
                     memcpy(p,pFrameYUV->data[0],y_size);
                     memcpy(p+y_size,pFrameYUV->data[1],y_size/4);
                     memcpy(p+y_size+y_size/4,pFrameYUV->data[2],y_size/4);
                     //将YUV数据转为RGB格式plainTextEdit_FFMPEG_info_Display
                     YUV420P_to_RGB24(p,rgb24_p,pCodecCtx->width,pCodecCtx->height);
                     //加载到QIMAGE显示到QT控件
                     QImage image(rgb24_p,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB888);
                     QPixmap my_pixmap;
                     my_pixmap.convertFromImage(image);
                     ui->label_ImageDisplay->setPixmap(my_pixmap);

                     delete[] p; //释放空间
                     delete[] rgb24_p; //释放空间
                }
            }
        }
    }
    avcodec_close(pCodecCtx); //关闭编码器
    avformat_close_input(&pFormatCtx); //关闭输入设备
    sws_freeContext(img_convert_ctx);
    av_free(out_buffer);
    av_free(pFrameYUV);
    return 0;
}

/**
 * YUV420P转RGB24
 * @param data
 * @param rgb
 * @param width
 * @param height
 */
void MainWindow::YUV420P_to_RGB24(unsigned char *data, unsigned char *rgb, int width, int height)
{
    int index = 0;
    unsigned char *ybase = data;
    unsigned char *ubase = &data[width * height];
    unsigned char *vbase = &data[width * height * 5 / 4];
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            //YYYYYYYYUUVV
            u_char Y = ybase[x + y * width];
            u_char U = ubase[y / 2 * width / 2 + (x / 2)];
            u_char V = vbase[y / 2 * width / 2 + (x / 2)];
            rgb[index++] = Y + 1.402 * (V - 128); //R
            rgb[index++] = Y - 0.34413 * (U - 128) - 0.71414 * (V - 128); //G
            rgb[index++] = Y + 1.772 * (U - 128); //B
        }
    }
}

void MainWindow::on_pushButton_up_camear_clicked()
{
    if(ui->comboBox_camera_number->currentText().isEmpty())
    {
        ui->plainTextEdit_CameraOpenInfo->insertPlainText("未选择摄像头.\n");
        return;
    }
    FFMPEG_Init_Config(ui->comboBox_camera_number->currentText().toLatin1().data(),
                       ui->comboBox_CamearSize->currentText().toLatin1().data());
}

void MainWindow::on_pushButton_getInfo_clicked()
{
    get_ffmpeg_version_info();
}

void MainWindow::on_pushButton_RefreshCamear_clicked()
{
    RefreshCameraList();
}

//刷新摄像头;列表
void MainWindow::RefreshCameraList(void)
{
    QDir dir("/dev"); //构造目录
    QStringList infolist = dir.entryList(QDir::System);
    ui->comboBox_camera_number->clear();
    for(int i=0; i<infolist.size(); i++)
    {
        if(infolist.at(i).contains("video",Qt::CaseInsensitive))
        {
            ui->comboBox_camera_number->addItem("/dev/"+infolist.at(i));
        }
    }
}

由于Android程序最高权限只是system级别,默认没有权限访问/dev/设备节点文件,导致程序无法打开设备节点。

所以,需要提前使用ADB命令进入到Android设备shell终端,chmod 777 /dev/video* 执行命令修改权限。

Android8.1 下使用adb获取权限方法

1. 第一次启动系统,如果需要更改系统文件,需要关闭安全验证 (注:安装apk不需要关闭),关闭安全验证后需要重启
adb root
adb disable-verity
adb reboot
2. 重启后,获得root权限,并重新挂载 /system 开启写入权限
adb root
adb remount
3. 上传文件
adb push example.txt /system/

修改权限的步骤:

wbyq@wbyq:~/qt_code/android_app/android$ adb devices
wbyq@wbyq:~/qt_code/android_app/android$ adb root
wbyq@wbyq:~/qt_code/android_app/android$ adb remount
wbyq@wbyq:~/qt_code/android_app/android$ adb shell
nanopc-t4:/ # chmod 777 /dev/video1
nanopc-t4:/ # exit
wbyq@wbyq:~/qt_code/android_app/android$

权限修改之后就可以,打开APP,测试程序。

下面公众号里有全套QT、C、C++基础学习教程:

发布了14 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/xiaolong1126626497/article/details/104749999
今日推荐