音视频学习:FFMpeg基础--1

1. 解封装流程

文件/网络流

avformat_open_input(AVFormatContext **test_context, ...)打开文件和识别封装格式

avformat_find_stream_info()获取音视频详细信息

av_read_frame()得到AVPacket结构体,供解码器解码

avformat_close_input(),最后关闭解复用器

avformat_open_input() 的作用是什么?

avformat_open_input() 是 FFmpeg 解封装流程的入口函数,主要负责打开输入媒体源(文件或网络流),创建并初始化 AVFormatContext,建立底层 AVIOContext,自动探测封装格式(如 MP4、FLV、MKV、RTSP 等),调用对应 Demuxer 的 read_header() 解析媒体头部,并创建初步的 AVStream 信息。它不会 深入分析媒体数据或解码音视频帧,流的详细参数(如帧率、部分编码参数等)通常需要后续调用 avformat_find_stream_info() 进一步分析获得(例如FLV格式)。

avformat_open_input()和avformat_find_stream_info()的区别?

avformat_open_input():打开媒体源,识别容器,建立解封装环境。

avformat_find_stream_info():分析媒体流,完善 AVStreamAVCodecParameters 信息,为后续解码做准备。

1.1 AVFormatContext相关

AVFormatContext 用来描述一个输入或输出媒体容器(Container),可以理解为整个媒体文件(或媒体流)的管理者,管理多个 AVStream,而每个 AVStream 对应一路音频或视频流。

示例代码,更容易理解:

cpp 复制代码
#include <stdio.h>
#include <libavformat/avformat.h>


int main(int argc, char **argv)
{
    //打开网络流。这里如果只需要读取本地媒体文件,不需要用到网络功能,可以不用加上这一句
//    avformat_network_init();

    const char *default_filename = "believe.mp4";

    //AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体
    AVFormatContext *ifmt_ctx = NULL;           // 输入文件的demux

    int videoindex = -1;        // 视频索引
    int audioindex = -1;        // 音频索引


    // 打开文件,主要是探测协议类型,如果是网络文件则创建网络链接
    int ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);
    if (ret < 0)  //如果打开媒体文件失败,打印失败原因
    {
        char buf[1024] = { 0 };
        av_strerror(ret, buf, sizeof(buf) - 1);
        printf("open %s failed:%s\n", in_filename, buf);
        goto failed;
    }

接下来,对avformat_open_input()和avformat_find_stream_info()前后的AVFormatContext结构体进行打印比较;

cpp 复制代码
    printf_s("\n==== avformat_open_input后的上下文打印:%s ===\n", in_filename);
    // !@如果是FLV则AVFormatContext中填充了很少的信息,在下面avformat_find_stream_info()后会填充详细信息;
    // !@但对于MP4则会填充很多信息,avformat_find_stream_info()前后,AVFormatContext结构体中的内容是一样的;
    av_dump_format(ifmt_ctx, 0, in_filename, 0);
    printf_s("\n==== av_dump_format finish =======\n\n");

    ret = avformat_find_stream_info(ifmt_ctx, NULL);
    if (ret < 0)  //如果打开媒体文件失败,打印失败原因
    {
        char buf[1024] = { 0 };
        av_strerror(ret, buf, sizeof(buf) - 1);
        printf("avformat_find_stream_info %s failed:%s\n", in_filename, buf);
        goto failed;
    }

    //打开媒体文件成功
    printf_s("\n==== av_dump_format in_filename:%s ===\n", in_filename);
    av_dump_format(ifmt_ctx, 0, in_filename, 0);
    printf_s("\n==== av_dump_format finish =======\n\n");
cpp 复制代码
/*首先是mp4文件的前后对比,几乎一致*/
==== avformat_open_input后的上下文打印:believe.mp4 ===
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'believe.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf56.38.102
    comment         : www.ieway.cn
  Duration: 00:03:42.53, bitrate: N/A
    Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none, 1920x1080, 150 kb/s, 14.46 fps, 15 tbr, 15360 tbn (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 48000 Hz, 2 channels, 128 kb/s (default)
    Metadata:
      handler_name    : SoundHandler

==== av_dump_format finish =======

==== av_dump_format in_filename:believe.mp4 ===
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'believe.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf56.38.102
    comment         : www.ieway.cn
  Duration: 00:03:42.53, start: 0.000000, bitrate: 281 kb/s
    Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 1920x1080, 150 kb/s, 14.46 fps, 15 tbr, 15360 tbn, 30 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : SoundHandler

==== av_dump_format finish =======

// 然后是FLV格式的前后对比,avformat_open_input()后信息很少,avformat_find_stream_info()后信息详细
==== avformat_open_input后的上下文打印:believe.flv ===
Input #0, flv, from 'believe.flv':
  Duration: N/A, start: 0.000000, bitrate: N/A

==== av_dump_format finish =======

==== av_dump_format in_filename:believe.flv ===
Input #0, flv, from 'believe.flv':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    comment         : www.ieway.cn
    encoder         : Lavf58.29.100
  Duration: 00:03:42.53, start: 0.000000, bitrate: 286 kb/s
    Stream #0:0: Video: h264 (Constrained Baseline), yuv420p(progressive), 1920x1080, 150 kb/s, 14.46 fps, 15 tbr, 1k tbn, 30 tbc
    Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp, 128 kb/s

==== av_dump_format finish =======

打印一些成员变量,媒体文件详细信息

cpp 复制代码
    // url: 调用avformat_open_input读取到的媒体文件的路径/名字
    printf("media name:%s\n", ifmt_ctx->url);
    // nb_streams: nb_streams媒体流数量
    printf("stream number:%d\n", ifmt_ctx->nb_streams);
    // bit_rate: 媒体文件的码率,单位为bps
    printf("media average ratio:%lldkbps\n",(int64_t)(ifmt_ctx->bit_rate/1000));
    // 时间
    int total_seconds, hour, minute, second;
    // duration: 媒体文件时长,单位微妙
    total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE;  // 1000us = 1ms, 1000ms = 1秒
    hour = total_seconds / 3600;
    minute = (total_seconds % 3600) / 60;
    second = (total_seconds % 60);
    //通过上述运算,可以得到媒体文件的总时长
    printf("total duration: %02d:%02d:%02d\n", hour, minute, second);
    printf("\n");

获取各媒体流(AVStream信息)

cpp 复制代码
    /*
     * 老版本通过遍历的方式读取媒体文件视频和音频的信息
     * 新版本的FFmpeg新增加了函数av_find_best_stream,也可以取得同样的效果
     */
    for (uint32_t i = 0; i < ifmt_ctx->nb_streams; i++)
    {
        AVStream *in_stream = ifmt_ctx->streams[i];// 音频流、视频流、字幕流
        //!@如果是音频流,则打印音频的信息
        if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type)
        {
            printf("----- Audio info:\n");
            // index: 每个流成分在ffmpeg解复用分析后都有唯一的index作为标识
            printf("index:%d\n", in_stream->index);
            // sample_rate: 音频编解码器的采样率,单位为Hz
            printf("samplerate:%dHz\n", in_stream->codecpar->sample_rate);
            // codecpar->format: 音频采样格式
            if (AV_SAMPLE_FMT_FLTP == in_stream->codecpar->format)
            {
                printf("sampleformat:AV_SAMPLE_FMT_FLTP\n");
            }
            else if (AV_SAMPLE_FMT_S16P == in_stream->codecpar->format)
            {
                printf("sampleformat:AV_SAMPLE_FMT_S16P\n");
            }
            // channels: 音频信道数目
            printf("channel number:%d\n", in_stream->codecpar->channels);
            // !@codec_id: 音频压缩编码格式
            if (AV_CODEC_ID_AAC == in_stream->codecpar->codec_id)
            {
                printf("audio codec:AAC\n");
            }
            else if (AV_CODEC_ID_MP3 == in_stream->codecpar->codec_id)
            {
                printf("audio codec:MP3\n");
            }
            else
            {
                printf("audio codec_id:%d\n", in_stream->codecpar->codec_id);
            }
            // 音频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
            // !@值等于 AV_NOPTS_VALUE 代表容器未提供时长(直播流、部分 FLV、裸流经常无效)
            if(in_stream->duration != AV_NOPTS_VALUE)
            {
                // !@duration表示单条媒体流以当前流自己的 AVStream->time_base 为刻度单位的总播放时长(不是文件总时长AVFormatContext::duration)
                // time_base是个结构体里面有分子和分母(不存储浮点数,以这种形式存储分子分母)
                // !@av_q2d()的结果就是单位时间(单位时间/采样频率)
                // 因此换算成真实秒数公式如下:
                int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);
                //将音频总时长转换为时分秒的格式打印到控制台上
                printf("audio duration: %02d:%02d:%02d\n",
                       duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));
            }
            else
            {
                // 等于AV_NOPTS_VALUE意为无效的值
                printf("audio duration unknown");
            }

            printf("\n");

            audioindex = i; // 获取音频的索引
        }
        else if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)  //如果是视频流,则打印视频的信息
        {
            printf("----- Video info:\n");
            printf("index:%d\n", in_stream->index);
            // avg_frame_rate: 视频帧率,单位为fps,表示每秒出现多少帧
            printf("fps:%lffps\n", av_q2d(in_stream->avg_frame_rate));
            if (AV_CODEC_ID_MPEG4 == in_stream->codecpar->codec_id) //视频压缩编码格式
            {
                printf("video codec:MPEG4\n");
            }
            else if (AV_CODEC_ID_H264 == in_stream->codecpar->codec_id) //视频压缩编码格式
            {
                printf("video codec:H264\n");
            }
            else
            {
                printf("video codec_id:%d\n", in_stream->codecpar->codec_id);
            }
            // 视频帧宽度和帧高度
            printf("width:%d height:%d\n", in_stream->codecpar->width,
                   in_stream->codecpar->height);
            //视频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
            if(in_stream->duration != AV_NOPTS_VALUE)
            {
                int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);
                printf("video duration: %02d:%02d:%02d\n",
                       duration_video / 3600,
                       (duration_video % 3600) / 60,
                       (duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上
            }
            else
            {
                printf("video duration unknown");
            }

            printf("\n");
            videoindex = i;
        }
    }

获取前10个AVPacke,并打印其信息,其中pkt->duration * av_q2d(ifmt_ctx->streamsaudioindex->time_base))计算出来为此AVPacke的时间,视频为一帧的时长,音频则为一帧音频(n个采样点一个编码单元)的时长。

cpp 复制代码
    AVPacket *pkt = av_packet_alloc();

    int pkt_count = 0;
    int print_max_count = 10;
    printf("\n-----av_read_frame start\n");
    while (1)
    {
        ret = av_read_frame(ifmt_ctx, pkt); // 分离为音频packet和视频packet
        if (ret < 0)
        {
            printf("av_read_frame end\n");
            break;
        }

        if(pkt_count++ < print_max_count)
        {
            if (pkt->stream_index == audioindex)
            {
                printf("audio pts: %lld\n", pkt->pts);
                printf("audio dts: %lld\n", pkt->dts);
                printf("audio size: %d\n", pkt->size);
                printf("audio pos: %lld\n", pkt->pos);  // pos参数是这一包数据在整个码流中的位置(需要转16进制)
                printf("audio duration: %lf\n\n",
                       pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base));
            }
            else if (pkt->stream_index == videoindex)
            {
                printf("video pts: %lld\n", pkt->pts);
                printf("video dts: %lld\n", pkt->dts);
                printf("video size: %d\n", pkt->size);
                printf("video pos: %lld\n", pkt->pos);
                printf("video duration: %lf\n\n",
                       pkt->duration * av_q2d(ifmt_ctx->streams[videoindex]->time_base));
            }
            else
            {
                printf("unknown stream_index:\n", pkt->stream_index);
            }
        }

        // 释放内部buf
        av_packet_unref(pkt);
    }

    if(pkt)
        av_packet_free(&pkt);
failed:
    if(ifmt_ctx)
        avformat_close_input(&ifmt_ctx);


    getchar(); //加上这一句,防止程序打印完信息马上退出
    return 0;
}

学习资料:0voice · GitHub