FFmpeg学习教程

版权声明:本文为博主原创文章,未经博主允许不得转载(pan_jinquan) https://blog.csdn.net/guyuealian/article/details/79493019

一、FFmpeg库介绍

FFmpeg一共包含8个库:

  1. avcodec:编解码(最重要的库)。
  2. avformat:封装格式处理。
  3. avfilter:滤镜特效处理。
  4. avdevice:各种设备的输入输出。
  5. avutil:工具库(大部分库都需要这个库的支持)。
  6. postproc:后加工。
  7. swresample:音频采样数据格式转换。
  8. swscale:视频像素数据格式转换

FFmpeg解码函数简介:

  1. av_register_all();//注册所有文件格式和编解码库
  2. avformat_network_init();//打开网络视频流
  3. av_open_input_file();//读取文件头部把信息保存到AVFormatContext结构体
  4. av_find_stream_info();//为pFormatCtx->streams填充上正确的信息
  5. CODEC_TYPE_VIDEO;//通过判断得到视频流类型
  6. avcodec_find_decoder();//查找解码器
  7. avcodec_open();//打开编解码器
  8. avcodec_alloc_frame();//分配空间保存帧数据
  9. av_read_frame();//不断从流中提取帧数据
  10. avcodec_decode_video();//解码视频流
  11. avcodec_close();//关闭解码器
  12. avformat_close_input_file();//关闭输入文件

FFmpeg解码的流程图如下所示:

SDL2.0显示YUV的流程图:

 SDL2.x和SDL1.x显示流程的区别如下:

   参考博客地址:https://blog.csdn.net/leixiaohua1020/article/details/38868499

SDL1.x SDL2.x
SDL_SetVideoMode()  SDL_CreateWindow()
SDL_Surface SDL_Window
SDL_CreateYUVOverlay() SDL_CreateTexture()
SDL_Overlay SDL_Texture
   

资料参考:

【1】《最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)》https://blog.csdn.net/leixiaohua1020/article/details/38868499

二、FFmpeg数据结构详解:

1、AVFormatContext

     描述媒体文件或媒体流构成和基本信息(包含码流参数较多,位于:avformat.h),封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
主要变量:

struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据缓存
unsigned int nb_streams:音视频流个数(输入视频的AVStream 个数)
AVStream **streams:音视频流(输入视频的AVStream []数组)
char filename[1024]:文件名
int64_t duration:时长(单位:us)(输入视频的时长(以微秒为单位))
int bit_rate:比特率(输入视频的码率)
AVDictionary *metadata:元数据

2、AVInputFormat

   每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。

long_name:封装格式的长名称
extensions:封装格式的扩展名
id:封装格式ID
一些封装格式处理的接口函数

3、AVCodecContext:

   描述编解码器上下文的数据结构,包含编解码器需要的参数信息(位于:avcodec.h),编码器上下文结构体,保存了视频(音频)编解码相关信息
说明:

codec:编解码器的AVCodec
width, height:图像的宽高(只针对视频)
pix_fmt:像素格式(只针对视频)
sample_rate:采样率(只针对音频)
channels:声道数(只针对音频)
sample_fmt:采样格式(只针对音频)

4、AVStream:

   描述一个媒体流(存储视频/音频流信息的结构体,位于:avformat.h),视频文件中每个视频(音频)流对应一个该结构体
主要变量:

AVCodecContext *codec:视频/音频流的AVCodecContext
AVRational time_base:时间基准,真正的时间 =PTS*time_base
int64_t duration:该视频/音频流时间长度
AVDictionary *metadata:元数据信息
AVRational avg_frame_rate:帧率
AVPacket attached_pic:附加图片

5、AVCodec

   每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。

name:编解码器名称
long_name:编解码器长名称
type:编解码器类型
id:编解码器ID
一些编解码的接口函数

6、AVPacket:

  存储一帧压缩编码数据。

uint8_t *data:压缩编码数据,一个AVPacket的data通常对应一个NAL。
int   size:data的大小
int64_t pts:显示时间戳
int64_t dts:解码时间戳
int   stream_index:标识该AVPacket所属的视频/音频流。

7、AVFrame

   存储一帧解码后像素(采样)数据。

data:解码后的图像像素数据(音频采样数据)。
linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小。
width, height:图像的宽高(只针对视频)。
key_frame:是否为关键帧(只针对视频) 。
pict_type:帧类型(只针对视频) 。例如I,P,B。

三、常用的API接口

1、avformat_open_input

int avformat_open_input(AVFormatContext **ic_ptr,const char *filename,AVInputFormat *fmt,AVDictionary **options);

作用:打开文件或URL,并使基于字节流的底层输入模块得到初始化;解析多媒体文件或多媒体流的头信息,创建AVFormatContext结构并填充其中的关键字段,依次为各个原始流建立AVStream结构。
参数:

ic_ptr:用于返回avformat_open_input内部构造的一个AVFormatContext结构体。
filename:指定文件名。
fmt:用于显式指定输入文件的格式,如果设为空则自动判断其输入格式。
options:传入的附加参数。

     说明:这个函数通过解析多媒体文件或流的头信息及其他辅助数据,能够获取足够多的关于文件、流和编解码器的信息,但任何一种多媒体格式提供的信息都是有限的,而且不同的多媒体软件制作对头信息的设置各有不同,另外这些软件在产生多媒体内容时难免引入错误,这种情况下并不能保证获取到所有需要的信息,这是就要考虑另一个函数:avformat_find_stream_info。


2、avformat_find_stream_info

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
作用:用于获取必要的编解码器参数。需要得到各媒体流对应编解码器的类型和id,这是两个定义在avutils.h和avcodec.h中的枚举:

enum AVMediaType {
     AVMEDIA_TYPE_UNKNOWN = -1,
     AVMEDIA_TYPE_VIDEO,
     AVMEDIA_TYPE_AUDIO,
     AVMEDIA_TYPE_DATA,
     AVMEDIA_TYPE_SUBTITLE,
     AVMEDIA_TYPE_ATTACHMENT,
     AVMEDIA_TYPE_NB
};
enum CodecID {
     CODEC_ID_NONE,
     CODEC_ID_MPEG1VIDEO,
     CODEC_ID_MPEG2VIDEO,
     CODEC_ID_MPEG2VIDEO_XVMC,
     CODEC_ID_H261,
     CODEC_ID_H263,
CODEC_ID_H264,
     ...
};

    若媒体格式的数据流具有完整头信息,可以通过avformat_open_input得到编解码器的类型和id;否则,需要通过avformat_find_stream_info函数获取。此外,对于音频编解码器,时间基准、采样率、声道数、位宽、帧长度与视频编解码器图像大小、色彩空间等也需要从avformat_find_stream_info函数得到。

3、av_read_frame

int av_read_frame(AVFormatContext *s, AVPacket *pkt);
作用:用于从多媒体文件或多媒体流中读取媒体数据,数据由AVPacket结构pkt来存放。对于音频数据,若是固定比特率,则pkt中装载一个或多个音频帧;若为可变比特率,则pkt中装载一个音频帧。对于视频数据,pkt中装载有一个视频帧。注:当再次调用本函数之前,需使用av_free_packet释放pkt所占用的资源。

4、av_seek_frame

int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);
作用:通过改变媒体文件的读写指针来实现对媒体文件的随机访问,大多源于媒体播放器的快进、快退等功能。
参数:

s:AVFormatContext指针;
avformat_open_input返回得到。
stream_index:指定媒体流。
timestamp:时间标签。
flags:定位方式。

5、av_close_input_file

void av_close_input_file(AVFormatContext *s);

作用:关闭媒体文件,释放资源,关闭物理IO。

6、avcodec_find_decoder

AVCodec *avcodec_find_decoder(enum CodecID id);
AVCodec *avcodec_find_decoder_by_name(const char *name);
作用:根据指定解码器ID或者解码器名称查找相应的解码器并返回AVCodec 。

7、avcodec_open

int avcodec_open(AVCodecContext *avctx, AVCodec *codec);
作用:根据输入的AVCodec指针具体化AVCodecContext结构。在调用该函数之前,首先调用avcodec_alloc_context分配一个AVCodecContext结构,或调用avformat_open_input获取媒体文件中对应媒体流的AVCodecContext结构;
此外,通过avcodec_find_decoder获取AVCodec结构。

8、avcodec_decode_video2

int avcodec_decode_video2(AVCodecContext *avctx,AVFrame *picture,int *got_picture_ptr,AVPacket *avpkt);
作用:解码视频帧。
参数:
avctx:解码器上下文。
picture:输出数据。
got_picture_ptr:指示是否有解码数据输出。
avpkt:输入数据。

9、avcodec_decode_audio4

int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame, int *got_frame_ptr, AVPacket *avpkt);
作用:解码音频帧。输入数据在AVPacket结构中,输出数据在frame中,got_frame_ptr表示是否有数据输出。
参数:
avctx:解码器上下文。
frame:输出数据。
got_frame_ptr:指示是否有解码数据输出。

avpkt:输入数据。

10、avcodec_close

int avcodec_close(AVCodecContext *avctx);作用:关闭解码器,释放avcodec_open中分配的资源。

四、代码流程

    这是来自雷霄骅大神的教程代码《最简单的基于FFmpeg的解码器》

#include <stdio.h>  
  
#define __STDC_CONSTANT_MACROS  
  
extern "C"  
{  
#include "libavcodec/avcodec.h"  
#include "libavformat/avformat.h"  
#include "libswscale/swscale.h"  
};  
  
  
int main(int argc, char* argv[])  
{  
    AVFormatContext *pFormatCtx;  
    int             i, videoindex;  
    AVCodecContext  *pCodecCtx;  
    AVCodec         *pCodec;  
    AVFrame *pFrame,*pFrameYUV;  
    uint8_t *out_buffer;  
    AVPacket *packet;  
    int y_size;  
    int ret, got_picture;  
    struct SwsContext *img_convert_ctx;  
    //输入文件路径  
    char filepath[]="../video/Titanic.ts";  
  
    int frame_cnt;  
  
    av_register_all();//注册所有组件  
    avformat_network_init();  
    pFormatCtx = avformat_alloc_context();  
  
    //打开输入视频文件  
    if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){  
        printf("Couldn't open input stream.\n");  
        return -1;  
    }  
    //获取视频文件信息  
    if(avformat_find_stream_info(pFormatCtx,NULL)<0){  
        printf("Couldn't find stream information.\n");  
        return -1;  
    }  
    videoindex=-1;  
    for(i=0; i<pFormatCtx->nb_streams; i++)   
        if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){  
            videoindex=i;//找到视频的数组位置  
            break;  
        }  
    if(videoindex==-1){  
        printf("Didn't find a video stream.\n");  
        return -1;  
    }  
  
    pCodecCtx=pFormatCtx->streams[videoindex]->codec;  
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);//查找解码器  
    if(pCodec==NULL){  
        printf("Codec not found.\n");  
        return -1;  
    }  
    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){//打开解码器  
        printf("Could not open codec.\n");  
        return -1;  
    }  
    /* 
     * 在此处添加输出视频信息的代码 
     * 取自于pFormatCtx,使用fprintf() 
     */  
    printf("视频的时长:%dμs\n", pFormatCtx->duration);//输入视频的时长(以微秒为单位)  
  
  
  
    pFrame=av_frame_alloc();  
    pFrameYUV=av_frame_alloc();  
    out_buffer=(uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));  
    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);  
    packet=(AVPacket *)av_malloc(sizeof(AVPacket));  
    //Output Info-----------------------------  
    printf("--------------- File Information ----------------\n");  
    av_dump_format(pFormatCtx,0,filepath,0);  
    printf("-------------------------------------------------\n");  
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,   
        pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);   
  
    frame_cnt=0;  
    //从输入文件读取一帧压缩数据  
    while(av_read_frame(pFormatCtx, packet)>=0){  
        if(packet->stream_index==videoindex){  
                /* 
                 * 在此处添加输出H264码流的代码 
                 * 取自于packet,使用fwrite() 
                 */  
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);//解码一帧压缩数据  
            if(ret < 0){  
                printf("Decode Error.\n");  
                return -1;  
            }  
            if(got_picture){  
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,   
                    pFrameYUV->data, pFrameYUV->linesize);  
                printf("Decoded frame index: %d\n",frame_cnt);  
  
                /* 
                 * 在此处添加输出YUV的代码 
                 * 取自于pFrameYUV,使用fwrite() 
                 */  
  
                frame_cnt++;  
  
            }  
        }  
        av_free_packet(packet);  
    }  
  
    sws_freeContext(img_convert_ctx);    
  
    av_frame_free(&pFrameYUV);  
    av_frame_free(&pFrame);  
    avcodec_close(pCodecCtx);//关闭解码器  
    avformat_close_input(&pFormatCtx);//关闭输入视频文件。  
  
    return 0;  
}  

 

 

阅读更多 登录后自动展开

扫码向博主提问

pan_jinquan

博客专家

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • 图像处理
  • AI人工智能
  • OpenCV
  • TensorFlow
  • Caffe
去开通我的Chat快问
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页