突然发现又有好久没有写技术blog了,主要原因是最近时间都用来研究libav去了(因为api极类似ffmpeg,虽然出自同一份代码的另外一个分支,因项目选用libav,故下文均用libav代替),其实要从知道这个库的时候已经很久了,早在加入avplayer开源社区的已经略有耳闻,看着他们讨论我却一直不知这个库能具体帮我做到哪些功能,插不上嘴呢,更强迫了我学习它的热情,下面就来一一解惑,希望就能帮到类似几个月前的我那样的同行。

1、提供API解码、编码市面上主流几乎全部的视频、音频格式文件。

2、通用视频转换命令行工具ffmpeg、avconv,可以帮我们快速将媒体文件格式进行转换,且做一些简单的resize或者resample,其工具提供了非常强大的filter,各种变幻根据参数都能实现,没有你想不到的,只有你找不到的。

3、简单的播放器avplay命令,这个播放器支持libav所有能够支持的video codec,算个简单的万能播放器了,虽然seek功能弱爆了,并且还没有pause、stop、显示时间等等功能,不过有些应急时候绝对首选它了。

4、libav还提供avprobe命令,可以让你瞬间了解这个媒体文件其中真实的video/audio编码(不会受到文件扩展名的误导)、拥有哪些stream(一般MP4分为视频、音频、字幕),一目了然。

读到此你一定会很感激我,不像大多数技术博客那样直接贴上很多大段大段的代码一下吓走好多初学者....我不能保证接下来一定不贴代码上来,但是我会尽量克制自己的....

本文主要将以第1点API解码编码的介绍为主。因为libav是基于C实现的,调用习惯全是基于函数式的,这样的优点就是跨平台好吧,缺点就是会使client代码比较臃肿,到处充斥着free、alloc等等。如果你是一个纯面向对象发烧支持者,请不要往下看,以免伤身且药还不能停。

libav提供一个函数avformat_open_input,即打开一个媒体文件,用AVFormatContext指针接受返回结果,代码看起来就是这样:

AVFormatContext* pformat_context = avformat_alloc_context();
if(avformat_open_input(&pformat_context, file.c_str(), nullptr, ) != )
{
printf("can't open the file %s\n", file.c_str());
return false;
}

然后你要做的是将所打开的FormatContext读取其中的stream,其中会有各种各样的stream类型,你需要做的事情就是将这个stream的index记录下来。

shared_ptr<AVFormatContext> format_context(pformat_context, [](AVFormatContext*& p){ avformat_close_input(&p); });
if(avformat_find_stream_info(format_context.get(), nullptr) < )
{
printf("can't find suitable codec parameters\n");
return false;
} // find out the audio and video stream
int video_stream_index = -, audio_stream_index = -;
for(unsigned int i = ; i < format_context->nb_streams && (video_stream_index == - || audio_stream_index == -); i++)
{
if(format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_stream_index = i;
}
else if (format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
audio_stream_index = i;
}
} if(video_stream_index == - && audio_stream_index == -)
{
printf("input file contains no video stream or audio stream.\n");
return false;
}

对照stream可以使用avprobe命令查看视频文件本身的内容。
获取到stream信息之后,你就需要创建decoder来解码视频啦~ libav提供一个函数avcodec_find_decoder根据你自己找到的video index和audio index去寻找codec_id作为参数得到AVCodec指针,再使用函数avcodec_open2传入这个指针即可。

// open the video decoder
AVCodecContext* video_codec_context = nullptr;
if (video_stream_index != -)
{
video_codec_context = format_context->streams[video_stream_index]->codec;
AVCodec* video_codec = avcodec_find_decoder(video_codec_context->codec_id);
if(video_codec == nullptr)
{
printf("can't find suitable video decoder\n");
return false;
}
if(avcodec_open2(video_codec_context, video_codec, nullptr) < )
{
printf("can't open the video decoder\n");
return false;
}
} // open the audio decoder
AVCodecContext* audio_codec_context = nullptr;
if (audio_stream_index != -)
{
audio_codec_context = format_context->streams[audio_stream_index]->codec;
AVCodec* audio_codec = avcodec_find_decoder(audio_codec_context->codec_id);
if (audio_codec == nullptr)
{
printf("can't find suitable audio decoder\n");
return false;
}
if (avcodec_open2(audio_codec_context, audio_codec, nullptr) < )
{
printf("can't open the audio decoder\n");
return false;
}
}

注意,open之后一定要调用对应的api close,比方avformat_close_input这些都是必备的,就不全贴出来了。

接下来讲这课程最重要的部分——decode video,先创建用于接收av_read_frame读出来的数据包,

AVPacket packet = {};
av_init_packet(&packet);

然后使用一个循环调用av_read_frame,查注释你会知道return>=0为成功,然后判断packet的stream_index是video_stream_index还是audio_stream_index,从而使用不同的decode函数(avcodec_decode_video2 / avcodec_decode_audio4)做解码,视频如果是MP4将得到AV_PIX_FMT_YUV420P数据,音频将得到原始音频AV_SAMPLE_FMT_FLTP采样数据。

但是我们一般不会使用YUV420P进行图像、视频处理,而是使用bitmap来进行处理,所以需要在这里借助另外一个函数sws_scale,第一个参数查看源码了解到是一个结构体struct,并不需要手动填充它,而且你也没办法手动填充它,libav并不希望你这么做(没有将细节写在include中),因此有一个sws_getContext函数是专门做这件事情的。

struct SwsContext *sws_getContext(
int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);

看到参数你就能很容易的猜到,你需要提供原视频的尺寸和格式,可以在已打开的视频的codec中获得,目标视频尺寸你自己随便设置都可以,dstFormat可以设置为:AV_PIX_FMT_BGRA,更多可以参见:pixfmt.h 中的 enum AVPixelFormat,如果是BGRA,图片则为32位,包含透明通道,方便之后叠加图层处理。如果读者跟着我的步骤走,应该就能达到连续输出图片的功能了,再加入图像识别的更多功能:脸谱识别、手势识别、车牌识别,就直接可以用了,是不是很激动?

libav(ffmpeg)简明教程(1)的更多相关文章

  1. libav(ffmpeg)简明教程(2)

    距离上一次教程又过去了将近一个多月,相信大家已经都将我上节课所说的东西所完全消化掉了. 这节课就来点轻松的,说说libav的命令使用吧. 注:遇到不懂的或者本文没有提到的可以用例如命令后加 --hel ...

  2. 2013 duilib入门简明教程 -- 第一个程序 Hello World(3)

    小伙伴们有点迫不及待了么,来看一看Hello World吧: 新建一个空的win32项目,新建一个main.cpp文件,将以下代码复制进去: #include <windows.h> #i ...

  3. 2013 duilib入门简明教程 -- 部分bug (11)

     一.WindowImplBase的bug     在第8个教程[2013 duilib入门简明教程 -- 完整的自绘标题栏(8)]中,可以发现窗口最大化之后有两个问题,     1.最大化按钮的样式 ...

  4. 2013 duilib入门简明教程 -- 部分bug 2 (14)

        上一个教程中提到了ActiveX的Bug,即如果主窗口直接用变量生成,则关闭窗口时会产生崩溃            如果用new的方式生成,则不会崩溃,所以给出一个临时的快速解决方案,即主窗口 ...

  5. 2013 duilib入门简明教程 -- 自绘控件 (15)

        在[2013 duilib入门简明教程 -- 复杂控件介绍 (13)]中虽然介绍了界面设计器上的所有控件,但是还有一些控件并没有被放到界面设计器上,还有一些常用控件duilib并没有提供(比如 ...

  6. 2013 duilib入门简明教程 -- 事件处理和消息响应 (17)

        界面的显示方面就都讲完啦,下面来介绍下控件的响应.     前面的教程只讲了按钮和Tab的响应,即在Notify函数里处理.其实duilib还提供了另外一种响应的方法,即消息映射DUI_BEG ...

  7. 2013 duilib入门简明教程 -- FAQ (19)

        虽然前面的教程几乎把所有的知识点都罗列了,但是有很多问题经常在群里出现,所以这里再次整理一下.     需要注意的是,在下面的问题中,除了加上XML属性外,主窗口必须继承自WindowImpl ...

  8. Mac安装Windows 10的简明教程

    每次在Mac上安装Windows都是一件非常痛苦的事情,曾经为了装Win8把整台Mac的硬盘数据都弄丢了,最后通过龟速系统恢复模式恢复了MacOSX(50M电信光纤下载了3天才把系统下载完),相信和我 ...

  9. Docker简明教程

    Docker简明教程 [编者的话]使用Docker来写代码更高效并能有效提升自己的技能.Docker能打包你的开发环境,消除包的依赖冲突,并通过集装箱式的应用来减少开发时间和学习时间. Docker作 ...

随机推荐

  1. XLua 基础

    一.Lua文件加载 1.Resources文件夹下加载  xxx.lua.txt  文件 1.)Resources加载xuaTest文件 // 1.Resources加载xuaTest文件 TextA ...

  2. C#类的成员(字段、属性、方法)

    前面定义的Person的类,里面的成员包括:字段.属性.方法.事件等,此外,前面说的嵌套类也是类的成员. a.类的成员为分:静态成员(static)和非静态成员 b.静态成员用static标识,不标识 ...

  3. SAS笔记(4) FIRST.和LAST.临时变量

    FIRST.和LAST.临时变量是SAS很有特色的一点,我在R和Python中暂时没有发现类似的功能(也许它们也有这个功能,我不知道而已).考虑这样一种场景:我们有患者就诊的数据,每一条观测对应一个患 ...

  4. CF17E Palisection(回文自动机)

    题意翻译 给定一个长度为n的小写字母串.问你有多少对相交的回文子 串(包含也算相交) . 输入格式 第一行是字符串长度n(1<=n<=2*10^6),第二行字符串 输出格式 相交的回文子串 ...

  5. [APIO2019T1]奇怪装置

    考古学家发现古代文明留下了一种奇怪的装置.该装置包含两个屏幕,分别显示两个整数x和y.经过研究,科学家对该装置得出了一个结论:该装置是一个特殊的时钟,它从过去的某个时间点开始测量经过的时刻数t,但该装 ...

  6. 洛谷P1057 传球游戏(记忆化搜索)

    点我进入题目 题目大意:n个小孩围一圈传球,每个人可以给左边的人或右边的人传球,1号小孩开始,一共传m次,请问有多少种可能的路径使球回到1号小孩. 输入输出:输入n,m,输出路径的数量. 数据范围:4 ...

  7. Linux服务之 Nginx安装

    安装包下载: 链接:https://pan.baidu.com/s/1yna9nvT_9iYw4_0uVQRgFw 提取码:nurm yum -y install gcc automake autoc ...

  8. PAT甲级——1095 Cars on Campus (排序、映射、字符串操作、题意理解)

    本文同步发布在CSDN:https://blog.csdn.net/weixin_44385565/article/details/93135047 1095 Cars on Campus (30 分 ...

  9. Jmeter report优化

    优化大致过程 生成并的报告模板: <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet ...

  10. LeetCode初级算法(动态规划+设计问题篇)

    目录 爬楼梯 买卖股票的最佳时机 最大子序和 打家劫舍 动态规划小结 Shuffle an Array 最小栈 爬楼梯 第一想法自然是递归,而且爬楼梯很明显是一个斐波拉切数列,所以就有了以下代码: c ...