FFmpeg --05-解封装流程

解复用流程:

  1. avformat_alloc_context 分配解复用器上下文
  2. avformat_open_input 根据url打开本地文件或网络流
  3. avformat_find_stream_info 读取媒体的部分数据包以获取码流信息
  4. av_read_frame 从文件中读取数据包
  5. avformat_close_input 关闭解复用器

函数分析:

1 avformat_open_input能否不调用avformat_alloc_context?
int avformat_open_input(AVFormatContext **ps, …);
函数说明:
May be a pointer to NULL, in which case an AVFormatContext is allocated by this
function and written into ps.
第一个参数可以传入空指针,内部为其分配空间
故可以在栈上定义的,AVFormatContext *ifmt_ctx = NULL;

2 AVFormatContext * ifmt_ctx 指针做参数传入解封装函数
AVFormatContext 是封装格式上下文结构体,保存了视频文件封装格式相关信息,可以通过ifmt_ctx指针可以用来读取文件相关信息,如文件路径,码流,文件时长,媒体流数量。

3 AVStream * in_stream 指针接收 ifmt_ctx指针指向的流文件
AVStream 对应文件中的一个流结构体,可以用来获取音频相关参数,如采样率

4 av_read_frame 音频包队列读取过程
首先对 AVPacket *pkt 申请空间,然后while(1)循环,循环体内部先通过av_read_frame读取音频包,获取音频信息,
之后将pkt 置空,读取完成后,释放pkt 空间

5 av_packet_unref 和 av_packet_free 的使用
void av_packet_unref(AVPacket *pkt);
void av_packet_free(AVPacket **pkt);
av_packet_unref(pkt) 将结构体置空,最后通过av_packet_free释放掉内存(先置空后释放内存,不然会导致内存泄露)

6 关闭解复用器
void avformat_close_input(AVFormatContext **s);

代码实现:

文件输入处理

支持命令行参数指定文件名,程序默认使用 believe.mp4 作为输入文件。若用户未提供文件名参数,则自动加载默认文件。

调用 FFmpeg 库初始化格式上下文,用于后续媒体文件解析。需检查文件是否存在以及格式是否有效,若初始化失败则报错退出。

媒体文件分析

文件级别信息
输出文件名及文件中包含的流数量(如视频流、音频流、字幕流等)。
通过计算文件大小和总时长,输出平均码率(单位 kbps)。
将总时长从秒数转换为 时:分:秒 格式显示。

流级别信息
区分音频流和视频流分别解析:

  • 音频流:输出采样率(Hz)、采样格式(如 s16p)、声道数(如立体声为 2)、编码格式(如 AAC)、时长(时:分:秒)。
  • 视频流:输出帧率(fps)、编码格式(如 H.264)、分辨率(宽度 x 高度)、时长(时:分:秒)。

数据包读取

读取文件的前 10 个媒体数据包(可能包含音频或视频包),并输出每个包的以下字段:

  • PTS:显示时间戳,反映帧应何时被渲染。
  • DTS:解码时间戳,反映帧应何时被解码。
  • 数据大小:包中数据的字节数。
  • 文件位置:包在文件中的偏移量。
  • 持续时间:包的有效时长(基于时间基单位)。

关键函数说明

// 初始化格式上下文
avformat_open_input(&format_ctx, filename, NULL, NULL);

// 获取流信息
avformat_find_stream_info(format_ctx, NULL);

// 遍历流并分类处理
for (int i = 0; i < format_ctx->nb_streams; i++) {
    AVStream *stream = format_ctx->streams[i];
    if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
        // 处理视频流
    } else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        // 处理音频流
    }
}

// 读取数据包
AVPacket pkt;
while (av_read_frame(format_ctx, &pkt) >= 0 && packet_count < 10) {
    // 输出包信息
    av_packet_unref(&pkt);
    packet_count++;
}

注意事项

  • 需处理时间基转换,确保 PTS/DTS 以可读格式输出。
  • 释放资源时依次关闭格式上下文并清理 FFmpeg 相关结构体。
  • 错误处理需覆盖文件打开失败、流信息获取失败等场景。

完成代码

#include <stdio.h>
#include <libavformat/avformat.h>


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

    const char *default_filename = "believe.mp4";

    char *in_filename = NULL;

    if(argv[1] == NULL)
    {
        in_filename = default_filename;
    }
    else
    {
        in_filename = argv[1];
    }
    printf("in_filename = %s\n", in_filename);

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

    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");
    // 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/1024));
    // 时间
    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");
    /*
     * 老版本通过遍历的方式读取媒体文件视频和音频的信息
     * 新版本的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);
            }
            // 音频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
            if(in_stream->duration != AV_NOPTS_VALUE)
            {
                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
            {
                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;
        }
    }

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

        av_packet_unref(pkt);
    }

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


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

运行结果:

in_filename = believe.mp4

==== 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 =======

media name:believe.mp4
stream number:2
media average ratio:274kbps
total duration: 00:03:42

----- Video info:
index:0
fps:14.464607fps
video codec:H264
width:1920 height:1080
video duration: 00:03:42

----- Audio info:
index:1
samplerate:48000Hz
sampleformat:AV_SAMPLE_FMT_FLTP
channel number:2
audio codec:AAC
audio duration: 00:03:42


-----av_read_frame start
audio pts: -678
audio dts: -678
audio size: 341
audio pos: 48
audio duration: 0.021333

video pts: 0
video dts: 0
video size: 66736
video pos: 389
video duration: 0.066667

audio pts: 346
audio dts: 346
audio size: 341
audio pos: 67125
audio duration: 0.021333

audio pts: 1370
audio dts: 1370
audio size: 342
audio pos: 67466
audio duration: 0.021333

audio pts: 2394
audio dts: 2394
audio size: 341
audio pos: 67808
audio duration: 0.021333

video pts: 1024
video dts: 1024
video size: 580
video pos: 68149
video duration: 0.066667

audio pts: 3418
audio dts: 3418
audio size: 341
audio pos: 68729
audio duration: 0.021333

audio pts: 4442
audio dts: 4442
audio size: 342
audio pos: 69070
audio duration: 0.021333

audio pts: 5466
audio dts: 5466
audio size: 341
audio pos: 69412
audio duration: 0.021333

video pts: 2048
video dts: 2048
video size: 11289
video pos: 69753
video duration: 0.066667

av_read_frame end
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八月的雨季997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值