libav(ffmpeg)简明教程(1)

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

一、提供API解码、编码市面上主流几乎所有的视频、音频格式文件。ide

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

三、简单的播放器avplay命令,这个播放器支持libav全部可以支持的video codec,算个简单的万能播放器了,虽然seek功能弱爆了,而且尚未pause、stop、显示时间等等功能,不过有些应急时候绝对首选它了。工具

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

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

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

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

AVFormatContext* pformat_context = avformat_alloc_context();
if(avformat_open_input(&pformat_context, file.c_str(), nullptr, 0) != 0)
{
     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) < 0)
    {
        printf("can't find suitable codec parameters\n");
        return false;
    }

    // find out the audio and video stream
    int video_stream_index = -1, audio_stream_index = -1;
    for(unsigned int i = 0; i < format_context->nb_streams && (video_stream_index == -1 || audio_stream_index == -1); 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 == -1 && audio_stream_index == -1)
    {
        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 != -1)
    {
        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) < 0)
        {
            printf("can't open the video decoder\n");
            return false;
        }
    }

    // open the audio decoder
    AVCodecContext* audio_codec_context = nullptr;
    if (audio_stream_index != -1)
    {
        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) < 0)
        {
            printf("can't open the audio decoder\n");
            return false;
        }
    }

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

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

AVPacket packet = {0};
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位,包含透明通道,方便以后叠加图层处理。若是读者跟着个人步骤走,应该就能达到连续输出图片的功能了,再加入图像识别的更多功能:脸谱识别、手势识别、车牌识别,就直接能够用了,是否是很激动?

相关文章
相关标签/搜索