ffmpeg解封装解码

文章目录

封装和解封装

封装

是把音频流 ,视频流,字幕流,其他成分 按照一定的规则组合程一个视频文件(mp4/ flv)的

解封装

流程和封装完全相反 是把一个视频文件的音频流,视频流,字幕流,其他成分给分离出来。

相关接口

◼ avformat_alloc_context();负责申请一个AVFormatContext

结构的内存,并进行简单初始化 AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体

-注意:这个接口 也不是必须调用的 因为在avformat_open_input()的的时候 传入第一个参数 ,如果检测为空的化,这个函数内部会自动进行检测和分配相关的内存

◼ avformat_free_context();释放该结构里的所有东西以及该结构本身

◼ avformat_close_input();关闭解复用器。关闭后就不再需要使用avformat_free_context 进行释放。

◼ avformat_open_input();打开输入视频文件

◼ avformat_find_stream_info():获取视频文件信息

◼ av_read_frame(); 读取音视频包

◼ avformat_seek_file(); 定位文件

◼ av_seek_frame():定位文件

解封装的流程图

关于AVPacket的解释

FFMpeg AVPacket 之理解与掌握

这里的讲解是比较好的

总结来说 ,就是用来装一帧数据流的包 ,包里面有包头和包体,

av_packet_alloc()是分配一个包结构

这里的使用是 av_read_frame() 来记录一帧,并进行输出 ,有一点需要注意的是 ,在读取新的帧的时候 要调用av_packet_unref去释放相关的包结构,避免相关包在释放的时候 查看到引用不为0而不去释放,导致的未知的错误事件发生

并且在最后不用的时候 要调用av_packet_free 去释放掉对应的空间

av_packet_ref 可以让一个包去引用另外一个包的内容

注意不能通过赋值直接去引用,这样两者指向的是 同一个包头 ,这样包头释放的时候 会导致两个指针的失效.

如何区分不同的码流,视频流,音频流?

第一种方式av_find_best_stream

通过接口

举例 获取视频流

video_index = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,-1,-1, NULL, 0)

获取音频流

audio_index = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,-1,-1, NULL, 0)

可选参数

第二种方式 通过遍历流

下面AVFormatContext里面有相关流的数量

通过 遍历 streams里面的数据 去获取对应的流

in_stream->codecpar->codec_type 可以表示对应的流

下面有代码演示具体的用法

注意点:
avformat_open_input和avformat_find_stream_info分别用于打 开一个流和分析流信息。 在初始信息不足的情况下(比如FLV和H264文件), avformat_find_stream_info接口需要在内部调用 read_frame_internal接口读取流数据(音视频帧),然后再分 析后,设置核心数据结构AVFormatContext。 由于需要读取数据包,avformat_find_stream_info接口会带来 很大的延迟。

代码

cpp 复制代码
#include <libavformat/avformat.h>
#include <stdio.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;
        }
    }
    // 解码的packet包 分配一个
    // 这个相当于一个包 包含包头和包体
    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;
        }
        // 这里最大读取10帧的数据
        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;
}
相关推荐
heromps4 小时前
在 macOS 上安装 FFmpeg 的详细指南
macos·ffmpeg
changingshow5 小时前
Windows 10 系统安装 FFmpeg 查看、转换、编辑音频文件
ffmpeg
职场人参5 小时前
pdf转换成word有哪些方法?10种将PDF转成word的方法
开发语言·计算机视觉·ffmpeg·pdf
cuijiecheng20188 小时前
FFmpeg源码:avio_skip函数分析
ffmpeg
田小呱1 天前
ffmpeg视频数据查看
ffmpeg·音视频
晓风残月淡2 天前
ffmpeg常用命令大全
ffmpeg
JackieZhengChina2 天前
使用ffmpeg缩小视频体积的几种方式
ffmpeg·音视频
_DCG_2 天前
ffmpeg拉取rtsp网络视频流报错解析
网络·ffmpeg
cuijiecheng20182 天前
音视频入门基础:FLV专题(5)——FFmpeg源码中,判断某文件是否为FLV文件的实现
ffmpeg·音视频