概述
FFmpeg一般采用SDL进行显示,如果不追求复杂的界面、交互和多线程功能,当然也可以使用OpenCV的imshow()方法进行显示了,而且实现起来比SDL更简单。方法也很简单,只需要把视频帧的BGR格式的数据(如果是RGB格式,需要转换)转存到OpenCV的Mat矩阵里。OpenCV的Mat是一个类,由两个数据部分组成: 矩阵头(包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等信息) 和一个指向存储所有像素值矩阵的指针。OpenCV的data属性是一个uchar类型的指针,它指向Mat数据矩阵的首地址;利用该属性,只需要把Mat的data指向FFmpeg的帧数据里即可,就可以用OpenCV的imshow()显示了。
下面给出两个方法,第一个是网上参考别人,并修改好的方法;第二个是鄙人再次精简,更好理解的方法
方法一:
先定义个out_buffer指针指向AVFrame帧数据,注意存储格式一定要选择与OpenCV格式类似的AV_PIX_FMT_BGR24
int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
经过FFmpeg的sws_scale()后,只需要在OpenCV初试化时,传入out_buffer即可,如下:
//Mat mRGB(pCodecCtx->height, pCodecCtx->width, CV_8UC3, out_buffer);//等效于下面
Mat mRGB(Size(pCodecCtx->width,pCodecCtx->height), CV_8UC3);
mRGB.data = out_buffer;
方法二:
前面已经说明,OpenCV的Mat是一个类,由两个数据部分组成: 矩阵头(包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等信息) 和一个指向存储所有像素值矩阵的指针。OpenCV的data属性是一个uchar类型的指针,它指向Mat数据矩阵的首地址;利用该属性,只需要把Mat的data指向FFmpeg的帧数据里即可,就可以用OpenCV的imshow()显示了。
因此,我们只需要在sws_scale后,把Mat地址直接指向AVFrame帧数据的首地址即可,不需要out_buffer,将上面的关键代码改为:
Mat mRGB(Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data
下面是完整的代码:
#define __STDC_CONSTANT_MACROS
#include <stdio.h>
// Opencv
#include <opencv/cv.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
//新版里的图像转换结构需要引入的头文件
#include "libswscale/swscale.h"
};
using namespace cv;
char* filename = "F:/FFmpeg/testvideo/屌丝男士.mov";;
int main()
{
AVCodec *pCodec; //解码器指针
AVCodecContext* pCodecCtx; //ffmpeg解码类的类成员
AVFrame* pAvFrame; //多媒体帧,保存解码后的数据帧
AVFormatContext* pFormatCtx; //保存视频流的信息
av_register_all(); //注册库中所有可用的文件格式和编码器
pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //检查文件头部
printf("Can't find the stream!n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息
printf("Can't find the stream information !n");
}
int videoindex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
}
if (videoindex == -1) {
printf("Don't find a video stream !n");
return -1;
}
pCodecCtx = pFormatCtx->streams[videoindex]->codec; //得到一个指向视频流的上下文指针
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到该格式的解码器
if (pCodec == NULL) {
printf("Cant't find the decoder !n"); //寻找解码器
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打开解码器
printf("Can't open the decoder !n");
return -1;
}
pAvFrame = av_frame_alloc(); //分配帧存储空间
AVFrame* pFrameBGR = av_frame_alloc(); //存储解码后转换的RGB数据
// 保存BGR,opencv中是按BGR来保存的
int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
printf("-----------输出文件信息---------n");
av_dump_format(pFormatCtx, 0, filename, 0);
printf("------------------------------");
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, //设置sws_scale转换格式为BGR24,这样转换后可以直接用OpenCV显示图像了
SWS_BICUBIC,
NULL, NULL, NULL);
int ret;
int got_picture;
cvNamedWindow("RGB", 1);
for (;;)
{
if (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == videoindex)
{
ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
if (ret < 0)
{
printf("Decode Error.(解码错误)n");
return -1;
}
if (got_picture)
{
//YUV to RGB
sws_scale(img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize);
//Mat mRGB(pCodecCtx->height, pCodecCtx->width, CV_8UC3, out_buffer);//(1)等效于下面
//Mat mRGB(Size(pCodecCtx->width,pCodecCtx->height), CV_8UC3);//(2)
//mRGB.data = out_buffer;//memcpy(pCvMat.data, out_buffer, size);
Mat mRGB(Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data
imshow("RGB", mRGB);
waitKey(40);
}
}
av_free_packet(packet);
}
else
{
break;
}
}
av_free(out_buffer);
av_free(pFrameBGR);
av_free(pAvFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
sws_freeContext(img_convert_ctx);
cvDestroyWindow("RGB");
system("pause");
return 0;
}
方法三:为了方便使用,这里提供一个函数可以实现AVFrame到OpenCV Mat的转换
cv::Mat avFrame2Mat(AVFrame* pAvFrame, AVCodecContext*pCodecCtx) {
AVFrame* pFrameBGR = av_frame_alloc(); //存储解码后转换的RGB数据
// 保存BGR,opencv中是按BGR来保存的
int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, //设置sws_scale转换格式为BGR24,这样转换后可以直接用OpenCV显示图像了
SWS_BICUBIC,
NULL, NULL, NULL);
sws_scale(img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize);
//Mat mRGB(pCodecCtx->height, pCodecCtx->width, CV_8UC3, out_buffer);//(1)等效于下面
//Mat mRGB(Size(pCodecCtx->width,pCodecCtx->height), CV_8UC3);//(2)
//mRGB.data = out_buffer;//memcpy(pCvMat.data, out_buffer, size);
Mat mRGB(Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data = (uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data
//av_free(out_buffer);
av_free(pFrameBGR);
//av_free(pAvFrame);
sws_freeContext(img_convert_ctx);
return mRGB;
}
完整的程序如下:
#define __STDC_CONSTANT_MACROS
#include <stdio.h>
// Opencv
#include <opencv/cv.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};
using namespace cv;
cv::Mat avFrame2Mat(AVFrame* pAvFrame, AVCodecContext*pCodecCtx);
char* filename = "F:/FFmpeg/testvideo/屌丝男士.mov";;
int main()
{
AVCodec *pCodec; //解码器指针
AVCodecContext* pCodecCtx; //ffmpeg解码类的类成员
AVFrame* pAvFrame; //多媒体帧,保存解码后的数据帧
AVFormatContext* pFormatCtx; //保存视频流的信息
av_register_all(); //注册库中所有可用的文件格式和编码器
pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //检查文件头部
printf("Can't find the stream!n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息
printf("Can't find the stream information !n");
}
int videoindex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
}
if (videoindex == -1) {
printf("Don't find a video stream !n");
return -1;
}
pCodecCtx = pFormatCtx->streams[videoindex]->codec; //得到一个指向视频流的上下文指针
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到该格式的解码器
if (pCodec == NULL) {
printf("Cant't find the decoder !n"); //寻找解码器
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打开解码器
printf("Can't open the decoder !n");
return -1;
}
pAvFrame = av_frame_alloc(); //分配帧存储空间
// 保存BGR,opencv中是按BGR来保存的
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
printf("-----------输出文件信息---------n");
av_dump_format(pFormatCtx, 0, filename, 0);
printf("------------------------------");
int ret;
int got_picture;
cvNamedWindow("RGB", 1);
for (;;)
{
if (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == videoindex)
{
ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
if (ret < 0)
{
printf("Decode Error.(解码错误)n");
return -1;
}
if (got_picture)
{
cv::Mat des = avFrame2Mat(pAvFrame, pCodecCtx);
imshow("RGB", des);
waitKey(40);
}
}
av_free_packet(packet);
}
else
{
break;
}
}
//av_free(out_buffer);
//av_free(pFrameBGR);
av_free(pAvFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
//sws_freeContext(img_convert_ctx);
cvDestroyWindow("RGB");
system("pause");
return 0;
}
cv::Mat avFrame2Mat(AVFrame* pAvFrame, AVCodecContext*pCodecCtx) {
AVFrame* pFrameBGR = av_frame_alloc(); //存储解码后转换的RGB数据
// 保存BGR,opencv中是按BGR来保存的
int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, //设置sws_scale转换格式为BGR24,这样转换后可以直接用OpenCV显示图像了
SWS_BICUBIC,
NULL, NULL, NULL);
sws_scale(img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize);
//Mat mRGB(pCodecCtx->height, pCodecCtx->width, CV_8UC3, out_buffer);//(1)等效于下面
//Mat mRGB(Size(pCodecCtx->width,pCodecCtx->height), CV_8UC3);//(2)
//mRGB.data = out_buffer;//memcpy(pCvMat.data, out_buffer, size);
Mat mRGB(Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data = (uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data
//av_free(out_buffer);
av_free(pFrameBGR);
//av_free(pAvFrame);
sws_freeContext(img_convert_ctx);
return mRGB;
}
参考资料:
【1】《利用ffmpeg和opencv进行视频的解码播放》https://www.jianshu.com/p/6ef3c18d61b0
【2】《ffmpeg中avframe的YUV格式数据到OpenCV中Mat的BGR格式转换》http://www.cnblogs.com/riddick/p/7719190.html
最后
以上就是痴情龙猫为你收集整理的FFmpeg转OpenCV Mat显示的全部内容,希望文章能够帮你解决FFmpeg转OpenCV Mat显示所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复