FFmpeg解析流程

583 阅读3分钟

简单流程

  • 创建format context(AVFormatContext): avformat_alloc_context

  • 打开文件流(路径): avformat_open_input

  • 寻找流信息: avformat_find_stream_info

  • 获取音视频流的索引值: formatContext->streams[i]->codecpar->codec_type == (isVideoStream ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO)

  • 获取音视频流(AVStream): m_formatContext->streams[m_audioStreamIndex]

  • 解析音视频数据帧(AVPacket): av_read_frame

  • 获取extra data(AVPacket): av_bitstream_filter_filter

AVFormatContext

简介

在使用FFMPEG进行开发的时候,AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。

常用属性

  • AVIOContext *pb:输入数据的缓存

  • unsigned int nb_streams:视音频流的个数

  • AVStream **streams:视音频流

  • char filename[1024]:文件名

  • int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)

  • int bit_rate:比特率(单位bps,转换为kbps需要除以1000)

  • AVDictionary *metadata:元数据

用法

通过路径初始化

- (AVFormatContext *)createFormatContextbyFilePath:(NSString *)filePath {
    if (filePath == nil) {
        log4cplus_error(kModuleName, "%s: file path is NULL",__func__);
        return NULL;
    }
    
    AVFormatContext  *formatContext = NULL;
    AVDictionary     *opts          = NULL;
    
    av_dict_set(&opts, "timeout", "1000000", 0);//设置超时1秒
    
    formatContext = avformat_alloc_context();
    BOOL isSuccess = avformat_open_input(&formatContext, [filePath cStringUsingEncoding:NSUTF8StringEncoding], NULL, &opts) < 0 ? NO : YES;
    av_dict_free(&opts);
    if (!isSuccess) {
        if (formatContext) {
            avformat_free_context(formatContext);
        }
        return NULL;
    }
    
    if (avformat_find_stream_info(formatContext, NULL) < 0) {
        avformat_close_input(&formatContext);
        return NULL;
    }
    
    return formatContext;
}

AVStream

简介

AVStream是存储每一个视频/音频流信息的结构体

属性

  • int index:标识该视频/音频流

  • AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系)

  • AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间

  • int64_t duration:该视频/音频流长度

  • AVDictionary *metadata:元数据信息

  • AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的)

  • AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。

用法

查找AVStream的下标

- (int)getAVStreamIndexWithFormatContext:(AVFormatContext *)formatContext isVideoStream:(BOOL)isVideoStream {
    int avStreamIndex = -1;
    for (int i = 0; i < formatContext->nb_streams; i++) {
        if ((isVideoStream ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO) == formatContext->streams[i]->codecpar->codec_type) {
            avStreamIndex = i;
        }
    }
    
    if (avStreamIndex == -1) {
        log4cplus_error(kModuleName, "%s: Not find video stream",__func__);
        return NULL;
    }else {
        return avStreamIndex;
    }
}

通过下标获取视频流

// Get video stream
    AVStream *videoStream = m_formatContext->streams[m_videoStreamIndex];
    m_video_width  = videoStream->codecpar->width;
    m_video_height = videoStream->codecpar->height;
    m_video_fps    = GetAVStreamFPSTimeBase(videoStream);

通过AVMediaType判断是音频还是视频(通常会在构建的时候将之转为其他名字避免和iOS原生冲突如FfmpegaVMediaType)

stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO

通过AVCodecID判断需要支持的格式

AVCodecID codecID = stream->codecpar->codec_id;
log4cplus_info(kModuleName, "%s: Current video codec format is %s",__func__, avcodec_find_decoder(codecID)->name);
// 目前只支持H264、H265(HEVC iOS11)编码格式的视频文件
if ((codecID != AV_CODEC_ID_H264 && codecID != AV_CODEC_ID_HEVC) || (codecID == AV_CODEC_ID_HEVC && [[UIDevice currentDevice].systemVersion floatValue] < 11.0)) {
   log4cplus_error(kModuleName, "%s: Not suuport the codec",__func__);
   return NO;
  }

通过判断需要

AVPacket

简介

AVPacket是存储压缩编码数据相关信息的结构体

属性

typedef struct AVPacket {
    
    int64_t pts; // 显示时间戳
    int64_t dts; // 解码时间戳
    int   stream_index; // Packet所在stream的index
    int   flags; // 标志,其中最低为1表示该数据是一个关键帧
    AVPacketSideData *side_data;
    int side_data_elems;
    int64_t duration; // 数据的时长,以所属媒体流的时间基准为单位
    int64_t pos; // 数据在媒体流中的位置,未知则值为-1
    
    uint8_t *data; // 指向保存压缩数据的指针,这就是AVPacket实际的数据。(例如对于H.264来说。1个AVPacket的data通常对应一个NAL。因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。)
    int   size; // 容器提供的一些附加数据
    AVBufferRef *buf; // 用来管理data指针引用的数据缓存的
} AVPacket;

用法

AVPacket    packet; 
av_init_packet(&packet); // 初始化packet的值为默认值,该函数不会影响data引用的数据缓存空间和size,需要单独处理
int size = av_read_frame(formatContext, &packet); // 从媒体流中读取帧填充到填充到Packet的数据缓存空间
if (size < 0 || packet.size < 0) { // 解析完成
}
av_packet_unref(&packet); // 释放packet,包括其data引用的数据缓存

参考链接

iOS利用VideoToolbox实现视频硬解码

AVPacket详解

FFMPEG结构体分析:AVPacket

FFMPEG中最关键的结构体之间的关系