一、系统环境介绍
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
#include
/*
* 设置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;inb_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; icomboBox_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++基础学习教程: