Learning ffmpeg

参考资料 https://zhuanlan.zhihu.com/p/142593316

ffmpeg概念

多媒体文件是一个容器,在容器中有很多流,每个流是由不同的编码器编码的,一个包中包含一个或多个帧

ffmpeg 模块

libavcodec

提供了一系列编码器的实现

libavformat

实现了流协议、容器格式及其基本IO访问

libswscale

支持色彩转换和缩放功能

libavfilter

提供了各种音视频过滤器

libavdevice

提供了访问获取设备和回放设备的接口

libswresample

实现了混音和重采样

libavutil

包括了hash器,解码器和各种工具函数

image.png

image.png

image.png

变量

封装格式

AVFormatContext

这个类是最顶层的结构体,通过直接和流文件相对应

AVFormatContext* pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0)
{
    printf("Can't find the stream!\n");
}

AVFormatContext 描述了媒体文件的构成及基本信息,是统领全局的结构体,很多函数都要用到它作为参数,格式转换过程中实现输入和输出功能、保存相关数据的主要结构,描述一个媒体文件或媒体流构成和基本信息。

  • nb_streams/streams: AVStream结构指针数组,包含了所有内嵌媒体流的描述,其内部有AVInputFormatAVOutputFormat结构体,来表示输入输出的文件格式

AVFormatContextlibavformat中非常重要的结构,它几乎是ffmpeg中的一棵树,其成员AVStream可以包含0种或多种流nb_stream
AVStream中有可以包含已经打开的编解码器codec,另外还有AVIOContext成员,这个成员的作用是io。可以重写AVIOContext结构的成员函数read_packetwrite_packet等,来实现从不同介质读取音视频数据(比如从网络、内存或磁盘)

AVInputFormat

解复用器对象,每种输入的封装格式(FLV, MP4, TS等)对应一个该结构体

AVOutputFormat

复用器对象,每种输出的封装格式(FLV, MP4, TS等)对应一个该结构体

AVStream

用于描述一个媒体流,其中大部分信息可通过avformat_open_input根据头文件信息确定,其他信息可通过avformat_find_stream_info获得,典型的有视频流、中英文视频流、字幕流,可以通过av_new_streamavformat_new_stream创建。

编解码

AVCodecContext

描述编解码器上下文的数据结构,保存AVCodec指针和与编码器相关的数据

  • codec_name
  • height/width
  • sample_fmt
  • time_base
    指向解码器的指针
AVCodecContext* pCodecCtx = pFormatCtx->streams[videoindex]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

它包含了当前流媒体的几乎所有参数(宽高,码率)以及编解码指针AVCodec,甚至还可以设置硬件加速相关(linux下的VAAPI),其中最重要的就是AVCodec,它直接指向编码器实现。

AVCodec

AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec == NULL){
    printf("Cant't find the decoder !\n");
    return -1;
}

解码器对象本身,每个AVCodecContext中都包含一个AVCodec
可以通过avcodec_find_decoder获得

AVCodecParameters

// 获取其中一个流的编码器参数
AVCodecParameters pCodecpar = pFormatCtx->streams[i]->codecpar;
// 根据编码器参数填充AVCodecContext
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);

编解码参数,每个AVStream中都含有一个AVCodecParameters,用来存放当前流的编解码参数。

数据存放

AVPacket

存放编码后,解码前的压缩数据,暂存解码之前的媒体数据(一个音/视频帧、一个字幕包)及附加信息(解码时间戳、显示时间戳、时长等),主要用于建立缓冲区并装载数据

  • data/size/pos 数据缓冲区指针、长度和流媒体中的偏移量
  • flags
    • AV_PKT_FLAG_KEY表示该数据是一个关键帧
    • AV_PKT_FLAG_CORRUPT 表示该数据已经损坏
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
if(av_read_frame(pFormatCtx, packet) >= 0){
    ...
}

AVFrame

AVFrame* pAvFrame = av_frame_alloc();
avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);

avcodec_decode_video2在新的API被拆解成两个函数

ret = avcodec_send_packet(pCodecCtx, packet);
got_picture = avcodec_reveive_frame(pCodecCtx, pAvFrame);

存放编码前,解码后的原始数据

  • data数组
  • linesize
  • key_frame 是否为关键帧

图像转换

SwsContext

图像转换上下文

libswscale模块提供图像缩放、图像格式转换功能。其中贯穿整个模块的是SwsContext结构体,方法包括sws_alloc_context分配、sws_init_context初始化、sws_getContext获取上下文、sws_get_cachedContext获取缓存,sws_freeContext释放上下文的方法。

struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
    pCodecCtx->width, // srcW
    pCodecCtx->height, // dstW
    pCodecCtx->pix_fmt,
    pCodecCtx->width,
    pCodecCtx->height,
    AV_PIX_FMT_BGR24,
    SWS_BICUBIC,
    NULL, // srcFilter
    NULL, // dstFilter
    NULL
);

函数

av_register_all()

初始化ffmpeg的环境,过时的API,新版本不需要了

avformat_alloc_context()

分配一个AVFromatContext

AVFormatContext *pFormatCtx = avformat_alloc_context();

avformat_open_input()

https://ffmpeg.org/doxygen/4.1/group__lavf__decoding.html#ga31d601155e9035d5b0e7efedc894ee49

根据输入视频,获取AVCodecContext

if(avformat_open_input(&pFormatCtx, NULL) < 0){
  printf("Can't find the stream!\n");
}

avformat_find_stream_info()

探测是否有AVFormatContext中是否有stream存在

avcodec_alloc_context3()

为AVCodecContext分配内存,并利用传入的AVCodec初始化

avcodec_parameters_to_context()

获取AVCodecParameters

avcodec_find_decoder()

查找解码器

AVCodec *pCodec  = avcodec_find_decoder(pCodecCtx->codec_id);

avcodec_open2()

打开编码器

if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
    printf("Can't open the decoder !\n");
    return -1;
}

av_frame_alloc()

为帧分配空间

AVFrame *pFrameBGR = av_frame_alloc();

av_image_get_buffer_size()

根据AVCodeContext的记录的长宽高,计算buffer的大小

int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);

av_image_fill_arrays()

填充AVFrame的数据

AVFrame *pFrameBGR = av_frame_alloc();
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
av_image_fill_arrays(pFrameBRG->data, pFrameBRG->linesize, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);

av_dump_format()

打印AVFormatContext和对应文件的信息

av_dump_format(pFormatCtx, 0, filename, 0);

sws_getContext()

获取SwsContext上下文

SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
    pCodecCtx->width,
    pCodecCtx->pix_fmt,
    pCodecCtx->width,
    pCodecCtx->height,
    AV_PIX_FMT_BGR24, // 设置sws_scale转换格式为BGR24,这样转换后可以直接使用OpenCV查看图像
    SWS_BICUBIC,
    NULL, // SwsFilter* srcFilter
    NULL, // SwsFilter* dstFilter
    NULL
);

av_read_frame()

AVFormatContext读取一个AVPacket

AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
if(av_read_frame(pFormatCtx, packet) >= 0){
    ...
}

avcodec_decode_video2()

这个函数过时了,可以替换为avcode_send_packetavcodec_receive_frame

AVpacket读取一帧到AVFrame

int ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);

avcode_send_packet()

发送编码数据包

ret = avcodec_send_packet(pCodecCtx, packet);

avcodec_receive_frame()

接收编码后的数据

got_picture = avcodec_receive_frame(pCodeCtx, pAvFrame);

sws_scale

将一个AVFrame的data转换为另一个AVFrame的data

sws_scale(
    img_convert_ctx,
    (const uint8_t* const*)pAvFrame->data,
    pAvFrame->lineSize,
    0,
    pCodecCtx->height,
    pFrameBGR->data,
    pFrameBGR->linesize,
);

av_packet_unref()

av_packet_unfer(packet);

av_free()

// uint8_t *out_buffer = (uint8_t *)av_malloc(size);
av_free(out_buffer);
// pAvFrame = av_frame_alloc();
av_free(pAvFrame);

avcodec_close()

avcodec_close(pCodecCtx);

avformat_close_input()

avformat_close_input(&pFormatCtx);

sws_freeContext()

sws_freeContext(img_convert_ctx);

一个跑通的ffmpeg转opencv的demo

旧API

// https://blog.csdn.net/guyuealian/article/details/79607568
#define __STDC_CONSTANT_MACROS

#include <opencv2/opencv.hpp>

extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h" //新版里的图像转换结构需要引入的头文件
}

#include <iostream>

const char* filename = "/home/cf206/下载/palace.mp4";

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(); // 存储解码后的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;

    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);

                    cv::Mat mRGB(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
                    mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data
                    cv::imshow("RGB", mRGB);
                    cv::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);

    system("pause");
}

新API

改写成新版ffmpeg的API

#define __STDC_CONSTANT_MACROS

#include <opencv2/opencv.hpp>

extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h" //新版里的图像转换结构需要引入的头文件
}

#include <iostream>

const char* filename = "/home/tuo/Videos/tracking.mp4";

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;
//      }
//  }

pCodecCtx = avcodec_alloc_context3(NULL);
    if (pCodecCtx == NULL)
    {
        printf("Could not allocate AVCodecContext\n");
        return -1;
    }

    int videoindex = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息
    {
        avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);
        if (pCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoindex = i;
            break;
        }
    }

    if (videoindex == -1) {
        printf("Don't find a video stream !\n");
        return -1;
    }

    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(); // 存储解码后的BGR数据

    // int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
    int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1); 
    uint8_t *out_buffer = (uint8_t *)av_malloc(size);
    // avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
    av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);

    AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
    printf("-----------输出文件信息---------\n");
    av_dump_format(pFormatCtx, 0, filename, 0);
    printf("------------------------------");

  std::cout <<  pCodecCtx->height << " " << pCodecCtx->width << std::endl;


    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;

    for (;;)
    {
        if (av_read_frame(pFormatCtx, packet) >= 0)
        {
            if (packet->stream_index == videoindex)
            {
                //ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
        ret = avcodec_send_packet(pCodecCtx, packet);
        got_picture = avcodec_receive_frame(pCodecCtx, pAvFrame);

                if (ret < 0)
                {
                    printf("Decode Error.(解码错误)\n");
                    return -1;
                }
                if (got_picture == 0)
                {
                    //YUV to RGB
                    sws_scale(img_convert_ctx,
                        (const uint8_t* const*)pAvFrame->data,
                        pAvFrame->linesize,
                        0,
                        pCodecCtx->height,
                        pFrameBGR->data,
                        pFrameBGR->linesize);

                    cv::Mat mRGB(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
                    mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data
                    cv::imshow("RGB", mRGB);
                    cv::waitKey(40);
                }
            }
        //  av_free_packet(packet);
      av_packet_unref(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);

    system("pause");
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容