ffmpeg简易播放器(3)--使用ffmpeg解码视频并用opencv显示视频
ffmpeg的安装
这里我采用的linux下编译源码的方式安装ffmpeg,当然也可以使用apt-get等方式安装,但是我当时使用apt-get安装的ffmpeg使用cmake总是找不到ffmpeg的库,所以我选择了编译源码的方式。
去官网下载最新的源码包

然后解压进入文件夹,使用自带的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来配置。

一般来说,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;
ffmpeg简易播放器(3)--使用ffmpeg解码视频并用opencv显示视频的更多相关文章
- FFmpeg简易播放器的实现-视频播放
本文为作者原创:https://www.cnblogs.com/leisure_chn/p/10047035.html,转载请注明出处 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...
- FFmpeg简易播放器的实现-音视频同步
本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10284653.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...
- FFmpeg简易播放器的实现-音视频播放
本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10235926.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...
- FFmpeg简易播放器的实现-音频播放
本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10068490.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...
- FFmpeg简易播放器的实现-最简版
本文为作者原创:https://www.cnblogs.com/leisure_chn/p/10040202.html,转载请注明出处 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...
- 基于ffmpeg网络播放器的教程与总结
基于ffmpeg网络播放器的教程与总结 一. 概述 为了解决在线无广告播放youku网上的视频.(youku把每个视频切换成若干个小视频). 视频资源解析可以从www.flvcd. ...
- <Win32_17>集音频和视频播放功能于一身的简易播放器
前段时间,在学习中科院杨老师的教学视频时,他说了一句话: "我很反对百八十行的教学程序,要来就来一个完整的程序" 对此,我很是赞同.所谓真刀真枪的做了,你才会发现其中的奥秘——然而 ...
- 基于QtAV的简易播放器(开源)
这个开源代码,是我利用QtAV源码,提取其中一部分代码,进行整合到我自己项目中,做的一个小型播放器测试,至于怎么安装一些环境以及QtAV源码编译在我以前写的一篇博客中可以看到(Qt第三方库QtAV-- ...
- ffmpeg+SDl+ 播放器 -01
最近因公司项目需要,打算自己在LINUX平台整一个播放器,来学习和研究音频编解码. 项目需求: 支持下列格式文件播放. 1> WMA 硬件解码,但需要软件分析ASF格式,提取Payload数据 ...
- 仿迅雷播放器教程 -- 基于ffmpeg的C++播放器 (1)
2011年12月份的时候发了这篇博客 http://blog.csdn.net/qq316293804/article/details/7107049 ,博文最后说会开源一个播放器,没想到快两年了,才 ...
随机推荐
- 在PyCharm中打包Python项目并将其运行到服务器上的方法
在PyCharm中打包Python项目并将其运行到服务器上的方法 在PyCharm中打包Python项目并将其运行到服务器上的过程,可以分解为几个关键步骤:创建项目.设置项目依赖.打包项目.配置服务器 ...
- python库asyncio的概念和用法
python 库 asyncio asyncio 是 Python 的标准库之一,用于编写异步应用程序.它提供了事件循环.协程.任务和其他工具来处理并发操作.以下是一些关于 asyncio 的基本概念 ...
- [TAD] Triangles of Absolute Differences-反帕斯卡三角形
[IMO2018] Triangles of Absolute Differences-反帕斯卡三角形 前言 叠甲 笔者不是学数竞的,在此感谢我的数竞生为我讲解题目. 笔者学艺不精,且知识面浅薄. 所 ...
- 干货分享:通用加解密函数(crypto),Air780E篇
一.加解密概述 加解密算法是保证数据安全的基础技术,无论是在数据传输.存储,还是用户身份验证中,都起着至关重要的作用.随着互联网的发展和信息安全威胁的增加,了解并掌握常用的加解密算法已经成为开发者和安 ...
- PTA题目集4~6的总结性Blog
· 前言 本次的三个作业,由答题判题程序- 4.家居强电电路模拟程序- 1.家居强电电路模拟程序 -2组成. 答题判题程序-4是对前三次判题程序的最后升级,设计多个子类继承于基础题类来实现对每种题型的 ...
- 鸿蒙NEXT开发案例:温度转换
[引言] 温度是日常生活中常见的物理量,但不同国家和地区可能使用不同的温度单位,如摄氏度(Celsius).华氏度(Fahrenheit).开尔文(Kelvin).兰氏度(Rankine)和列氏度(R ...
- Lua之基础篇
新到一家公司,接触有些业务竟然直接通过服务器,在nginx层面就完成了,主要是基于OpenResty和Lua来实现的.打算深入了解一下这门神奇的语言... 为了嵌入应用程序中,从而为应用程序提供灵活的 ...
- This request has been blocked; the content must be served over HTTPS.此请求已被阻止;内容必须通过HTTPS提供
近期在做将高德地图替换为天地图时遇到了这个问题,原因是https由于安全协议禁止了http的请求 http://api.tianditu.gov.cn/api?v=4.0&tk=XXXXXXX ...
- NATS: 对依赖注入支持
NuGet: NATS.Net 使用方法: serviceCollection.AddNats(); 在容器中添加了 2 个单例服务: NATS.Client.Core.NatsConnection ...
- 使用 VS Code 徒手构建 PDF 文件
使用 VS Code 徒手构建 PDF 文件 PDF 文件是广泛应用的页面描述文件格式,从本质上讲,文件内部的结构混合使用了文本格式描述和二进制格式描述,对于简单的文件,比如说我们今天要创建的第一个 ...