ffmpeg简易播放器(3)–使用ffmpeg解码视频并用opencv显示视频

ffmpeg的安装

这里我采用的linux下编译源码的方式安装ffmpeg,当然也可以使用apt-get等方式安装,但是我当时使用apt-get安装的ffmpeg使用cmake总是找不到ffmpeg的库,所以我选择了编译源码的方式。

官网下载最新的源码包
ffmpeg简易播放器(3)--使用ffmpeg解码视频并用opencv显示视频

然后解压进入文件夹,使用自带的configure程序进行配置

./configure --prefix=/path/to/your/ffmpeg 

这里只配置了安装路径,这个路径决定了一会make install后ffmpeg会安装到哪里,其他的配置选项可以通过./configure --help查看或者网路搜索。

然后终端输入

make -j4 # 执行完上一步后,输入 make install # 若指定目录需要root权限别忘了加sudo 

使用cmake添加ffmpeg

由于ffmpeg并未提供类似于ffmpegConfig.cmake的配置文件,而是使用pkg-config来配置。
ffmpeg简易播放器(3)--使用ffmpeg解码视频并用opencv显示视频

一般来说,pkg-config的配置文件.pc或者cmake的配置文件XXXConfig.cmake都会放在库安装包的/lib/pkgconfig或者/lib/cmake/XXX/目录下。

由于我想要用cmake去管理包,且ffmpeg本身的的量级其实很小,因此直接去写了一份FindFFMPEG.cmake文件

# FindFFMPEG.cmake set(FIND_FFMPEG TRUE) set(FFMPEG_SOURCE /path/to/your/ffmpeg)  set(FFMPEG_INCLUDE_DIRS ${FFMPEG_SOURCE}/include) set(FFMPEG_LIBDIRS_DIRS ${FFMPEG_SOURCE}/lib)  find_library(FFMPEG_AVCODEC_LIBRARY avcodec ${FFMPEG_LIBDIRS_DIR}) find_library(FFMPEG_AVFORMAT_LIBRARY avformat ${FFMPEG_LIBDIRS_DIR}) find_library(FFMPEG_AVUTIL_LIBRARY avutil ${FFMPEG_LIBDIRS_DIR}) find_library(FFMPEG_SWSCALE_LIBRARY swscale ${FFMPEG_LIBDIRS_DIR}) find_library(FFMPEG_SWRESAMPLE_LIBRARY swresample ${FFMPEG_LIBDIRS_DIR})  set(FFMPEG_LIBS ${FFMPEG_AVCODEC_LIBRARY} ${FFMPEG_AVFORMAT_LIBRARY} ${FFMPEG_AVUTIL_LIBRARY} ${FFMPEG_SWSCALE_LIBRARY} ${FFMPEG_SWRESAMPLE_LIBRARY}) 

主要是通过find_library去找到ffmpeg的库文件,然后通过set设置变量。将这份配置文件与CMakeLists.txt放在同一目录下,在写CMakeLists.txt时,只需要添加如下代码

# CMakeLists.txt ......# 省略 # start ...... set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR})  find_package(FFMPEG REQUIRED)  include_directories(${FFMPEG_INCLUDE_DIRS})  ......#省略 # 生成可执行文件 ......  target_link_libraries(${PROJECT_NAME} ${FFMPEG_LIBS}) 

这里面引用了我要用的几个库,如果你要用其他库,可以在FindFFMPEG.cmake中添加对应的find_library,以及set对应的变量即可。

至于为啥这样做,可以看看我的另一篇findpackage()使用指南

至于使用apt install ffmpeg的方式安装的ffmpeg,可以去搜索如何通过cmake去引用pkg-config管理的包,也可以参考我的另一篇zbar库的使用中引用zbar库的方式。

使用ffmpeg

由于ffmpeg是纯C语言编写的,而对于c语言来说,其不存在函数重载,因此纯c文件编译时生成的函数签名就是函数名。而c++由于函数重载,因此编译时会对函数名进行重整(称为符号修饰),修饰后的函数名会包含函数参数的类型,个数等信息。这就导致c文件与c++文件编译时生成的函数签名不同,导致编译器连接时找不到对应的函数入口。

因此若想在c++文件中使用ffmpeg的函数,需要在c++文件中使用extern "C"修饰,告诉编译器这是一个c语言函数,不要对其进行符号修饰。

extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> ...... }  ## 常用结构  在使用ffmpeg解码音视频时,我们需要使用到一些常用的结构体,这里简单介绍一下。 ```c++ AVFormatContext //格式上下文,用于存储音视频的格式信息如音视频流的个数,音视频流的编码信息等 AVCodecContext //编解码上下文,用于存储编解码器以及编解码时的参数信息,如编解码器的类型,编解码器的参数等 AVCodec //编解码器,用于存储编解码器的信息,如编解码器的名称,编解码器的类型等 AVPacket //存储编码后的音视频数据 AVFrame //存储解码后的音视频数据 SwsContext //用于图像转换的上下文,可以将不同格式的图像转换为我们需要的格式 SwrContext //用于音频转换的上下文,可以将不同格式的音频重采样为我们需要的格式 

解码视频

extern "C"  { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> #include <libavutil/imgutils.h> } int main() {     ....... } 

这就完成了准备工作。接下来就是获取视频的信息,打开视频文件,获取视频流的索引等操作。

// av_register_all() 很多教程第一句是这个,但是在ffmpeg4.0之后已经被废弃(deprecated),不需要再调用     AVFormatContext *pFormatCtx = nullptr;     /*寻找音视频文件中的信息并传入AVFormatContext结构中*/     if (avformat_open_input(&pFormatCtx, videoPath.c_str(), nullptr, nullptr) != 0)     {         //注意AVFormatContext必须是空指针(nullptr)或者是由avformat_alloc_context()分配的内存,否则报错。前者由上述函数分配内存。         cerr << "打开视频文件失败" << endl;         return -1;     }     /*获取流信息,包括视频流、音频流等*/     if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)     {         cerr << "获取信息失败" << endl;         return -1;     }     /*打印视频信息*/     av_dump_format(pFormatCtx, 0, videoPath.c_str(), 0);      /*获取视频流的索引*/     videoStreamIdx = -1;     for (int i = 0; i < pFormatCtx->nb_streams/*流的数量*/; i++)     {         if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) /*若是视频流,找到记录索引并退出*/         {             videoStreamIdx = i;             break;         }     }     if (videoStreamIdx == -1)     {         cerr << "没有找到视频流" << endl;         return -1;     }  

然后是获取解码上下文用于解码

    /*获取解码器*/     AVCodec *pCodec = nullptr;     AVCodecParameters *pCodecParameters = pFormatCtx->streams[videoStreamIdx]->codecpar;     pCodec = avcodec_find_decoder(pCodecParameters->codec_id);     if (pCodec == nullptr)     {         cerr << "没有找到解码器" << endl;         return -1;     }     /*获取解码上下文*/     AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);     if (avcodec_parameters_to_context(pCodecCtx, pCodecParameters) < 0)     {         cerr << "获取解码上下文失败" << endl;         return -1;     }     /*打开解码器*/     if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)     {         cerr << "打开解码器失败" << endl;         return -1;     } 

接着对数据进行解码并显示,这里使用opencv进行显示。

    /*初始化转换上下文,将yuv转化到rgb*/     SWsContext *pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height,                              pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,                              AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr);      /*分配容器内存*/     AVFrame *pFrame = av_frame_alloc();     AVFrame *pFrameRGB = av_frame_alloc();     AVPackeat *pPacket = av_packet_alloc();     uint8_t *buffer = nullptr;     //  获取一帧图像的大小     int bufferSz = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1);     buffer = (uint8_t *)av_malloc(bufferSz); // 存储一帧图像的内存     // 将buffer与pFrameRGB绑定,并指定存储格式为RBG24位     av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1);     // 读取一个packet     while (av_read_frame(pFormatCtx, pPacket) >= 0)     {         if (pPacket->stream_index == videoStreamIdx)         {             // 将packet发送给解码器             int ret = avcodec_send_packet(pCodecCtx, pPacket);             if (ret < 0)             {                 cerr << "发送失败" << endl;                 return -1;             }             while (ret >= 0)             {   // 从解码器获取一帧已经解码好的帧                 ret = avcodec_receive_frame(pCodecCtx, pFrame);                 if (ret == AVERROR_EOF)                 {                     /*如果读到末尾结束就退出*/                     break;                 }                 // 转换,将yuv格式转换为RBG                 sws_scale(pSwsCtx, (uint8_t const *const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);                  // 将数据转换为opencv格式                 Mat img(pCodecCtx->height, pCodecCtx->width, CV_8UC3, pFrameRGB->data[0]);                 // 注意opencv中的颜色空间是BGR                 cvtColor(img, img, COLOR_RGB2BGR);                 imshow("video", img);                 waitKey(1);             }         }         av_packet_unref(pPacket);     }      // 释放资源     av_frame_free(&pFrame);     av_frame_free(&pFrameRGB);     av_packet_free(&pPacket);     avcodec_free_context(&pCodecCtx);     avformat_close_input(&pFormatCtx);     sws_freeContext(pSwsCtx);     av_free(buffer);     return 0; 

发表评论

评论已关闭。

相关文章