AVFormatContext封装层:理论与实战

文章目录

  • 前言
  • 一、封装格式简介
    • [1、FFmpeg 中的封装格式](#1、FFmpeg 中的封装格式)
    • [2、查看 FFmpeg 支持的封装格式](#2、查看 FFmpeg 支持的封装格式)
  • [二、API 介绍](#二、API 介绍)
  • [三、 实战 1:解封装](#三、 实战 1:解封装)
    • 1、原理讲解
    • [2、示例源码 1](#2、示例源码 1)
    • [3、运行结果 1](#3、运行结果 1)
    • [4、示例源码 2](#4、示例源码 2)
    • [5、运行结果 2](#5、运行结果 2)
  • [四、 实战 2:转封装](#四、 实战 2:转封装)

前言

AVFormatContext 是一个贯穿始终的数据结构,很多函数都用到它作为参数,是输入输出相关信息的一个容器,本文讲解 AVFormatContext 的封装层,主要包括两大数据结构:AVInputFormat,AVOutputFormat。


一、封装格式简介

封装格式(container format)可以看作是编码流(音频流、视频流等)数据的一层外壳,将编码后的数据存储于此封装格式的文件之内。

封装又称容器,容器的称法更为形象,所谓容器,就是存放内容的器具,例如饮料是内容,那么装饮料的瓶子就是容器。

不同封装格式适用于不同的场合,支持的编码格式不一样,几个常用的封装格式如下:

1、FFmpeg 中的封装格式

FFmpeg 关于封装格式的处理涉及打开输入文件、打开输出文件、从输入文件读取编码帧、往输出文件写入编码帧这几个步骤,这些都不涉及编码解码层面。

在 FFmpeg 中,mux 指复用,是 multiplex 的缩写,表示将多路流(视频、音频、字幕等)混入一路输出中(普通文件、流等)。demux 指解复用,是 mux 的反操作,表示从一路输入中分离出多路流(视频、音频、字幕等)。

mux 处理的是输入格式,demux 处理的输出格式。输入/输出媒体格式涉及文件格式和封装格式两个概念【封装格式】 。

  • 文件格式由文件扩展名标识【文件格式】,主要起提示作用,通过扩展名提示文件类型(或封装格式)信息。封装格式则是存储媒体内容的实际容器格式,不同的封装格式对应不同的文件扩展名,很多时候也用文件格式代指封装格式,例如常用 ts 格式(文件格式)代指 mpegts 格式(封装格式)。
    • 例如,我们把 test.ts 改名为 test.mkv,mkv 扩展名提示了此文件封装格式为 Matroska,但文件内容并无任何变化,使用 ffprobe 工具仍能正确探测出封装格式为 mpegts。

2、查看 FFmpeg 支持的封装格式

使用 ffmpeg -formats 命令可以查看 FFmpeg 支持的封装格式。 FFmpeg 支持的封装非常多, 下面仅列出最常用的几种:

  • h264/aac 裸流封装格式
    • h264 裸流封装格式和 aac 裸流封装格式在后面的解复用和复用例程中会用到,这里先讨论一下。h264 本来是编码格式,当作封装格式时表示的是 H.264 裸流格式,所谓裸流就是不含封装信息的流,也就是没穿衣服的流。aac 等封装格式类似。

看一下 FFmpeg 工程源码中 h264 编码格式以及 h264 封装格式的定义:FFmpeg 工程包含 h264 解码器,而不包含 h264 编码器(一般使用第三方 libx264 编码器用作 h264 编码),所以只有解码器定义:

cpp 复制代码
AVCodec ff_h264_decoder = {
	.name = "h264",
	.long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC /
	MPEG-4 part 10"),
	.type = AVMEDIA_TYPE_VIDEO,
	.id = AV_CODEC_ID_H264,
	......
};

h264 封装格式定义如下:

cpp 复制代码
AVOutputFormat ff_h264_muxer = {
	.name = "h264",
	.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),
	.extensions = "h264,264",
	.audio_codec = AV_CODEC_ID_NONE,
	.video_codec = AV_CODEC_ID_H264,
	.write_header = force_one_stream,
	.write_packet = ff_raw_write_packet,
	.check_bitstream = h264_check_bitstream,
	.flags = AVFMT_NOTIMESTAMPS,
};

AVOutputFormat ff_h264_muxer = {
	.name = "h264",
	.long_name = NULL_IF_CONFIG_SMALL("raw H.264 video"),
	.extensions = "h264,264",
	.audio_codec = AV_CODEC_ID_NONE,
	.video_codec = AV_CODEC_ID_H264,
	.write_header = force_one_stream,
	.write_packet = ff_raw_write_packet,
	.check_bitstream = h264_check_bitstream,
	.flags = AVFMT_NOTIMESTAMPS,
};

二、API 介绍

FFmpeg 中将编码帧及未编码帧均称作 frame,本文为方便,将编码帧称作 packet,未编码帧称作 frame。

未压缩的,未编码的:原始图片(yuv,rgb)或声音(pcm 流)。

压缩的, 编码的:H264/265,aac/ac3/mp3

最主要的 API 有如下几个:

  • avformat_open_input():这个函数会打开输入媒体文件,读取文件头,将文件格式信息存储在第一个参数 AVFormatContext 中。
  • avformat_find_stream_info():这个函数会读取一段视频文件数据并尝试解码,将取到的流信息填入 AVFormatContext.streams 中。AVFormatContext.streams 是一个指针数组,数组大小是 AVFormatContext.nb_streams
  • av_read_frame():本函数用于解复用过程。本函数将存储在输入文件中的数据分割为多个 packet, 每次调用将得到一个 packet。 packet 可能是视频帧、音频帧或其他数据,解码器只会解码视频帧或音频帧,非音视频数据并不会
    被扔掉、从而能向解码器提供尽可能多的信息。
    • 对于视频来说,一个 packet 只包含一个视频帧;
    • 对于音频来说,若是帧长固定的格式则一个 packet 可包含整数个音频帧,若是帧长可变的格式则一个 packet 只包含一个音频帧。
    • 读取到的 packet 每次使用完之后应调用 av_packet_unref(AVPacket *pkt) 清空 packet。否则会造成内存泄露。
  • av_write_frame():本函数用于复用过程,将 packet 写入输出媒体。
    • packet 交织是指:不同流的 packet 在输出媒体文件中应严格按照 packet 中 dts 递增的顺序交错存放。
    • 本函数直接将 packet 写入复用器(muxer),不会缓存或记录任何 packet。本函数不负责不同流的 packet 交织问题。,由调用者负责。
    • 如果调用者不愿处理 packet 交织问题,应调用 av_interleaved_write_frame() 替代本函数。
  • av_interleaved_write_frame():本函数用于复用过程, 将 packet 写入输出媒体。
    • 本函数将按需在内部缓存 packet,从而确保输出媒体中不同流的 packet 能按照 dts 增长的顺序正确交织。
  • avio_open() :创建并初始化一个 AVIOContext,用于访问输出媒体文件。
  • avformat_write_header():向输出文件写入文件头信息。
  • av_write_trailer():向输出文件写入文件尾信息。

三、 实战 1:解封装

1、原理讲解

本例子实现的是将音视频分离,例如将封装格式为 FLV、MKV、MP4、AVI 等封装格式的文件,将音频、视频读取出来并打印。实现的过程,可以大致用如下图表示:

2、示例源码 1

兼容旧版本使用遍历的方式查找给定媒体文件中音频流或视频流,未使用新版本的 FFmpeg 新增加的函数 av_find_best_stream()

cpp 复制代码
#include <stdio.h>
extern "C" {
    #include <libavformat/avformat.h>
}

/**
* @brief 将一个AVRational类型的分数转换为double类型的浮点数
* @param r:r为一个AVRational类型的结构体变量,成员num表示分子,成员den表示分母,r的值即为(double)r.num / (double)r.den。用这种方法表示可以最大程度地避免精度的损失
* @return 如果变量r的分母den为0,则返回0(为了避免除数为0导致程序死掉);其余情况返回(double)r.num / (double)r.den
*/
static double r2d(AVRational r)
{
    return r.den == 0 ? 0 : (double)r.num / (double)r.den;
}

int main()
{
    //需要读取的本地媒体文件相对路径为 ./debug/test.mp4
    const char *path = "./debug/test.mp4";
    ///av_register_all(); //初始化所有组件,只有调用了该函数,才能使用复用器和编解码器。否则,调用函数avformat_open_input会失败,无法获取媒体文件的信息
    avformat_network_init(); //打开网络流。这里如果只需要读取本地媒体文件,不需要用到网络功能,可以不用加上这一句
    AVDictionary *opts = NULL;

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

    //媒体打开函数,调用该函数可以获得路径为path的媒体文件的信息,并把这些信息保存到指针ic指向的空间中(调用该函数后会分配一个空间,让指针ic指向该空间)
    int re = avformat_open_input(&ic, path, NULL, &opts);
    if (re != 0)  //如果打开媒体文件失败,打印失败原因。比如,如果上面没有调用函数av_register_all,则会打印"XXX failed!:Invaliddata found when processing input"
    {
        char buf[1024] = { 0 };
        av_strerror(re, buf, sizeof(buf) - 1);
        printf("open %s failed!:%s", path, buf);
    }
    else         //打开媒体文件成功
    {
        printf("打开媒体文件 %s 成功!\n", path);

        //调用该函数可以进一步读取一部分视音频数据并且获得一些相关的信息。
        //调用avformat_open_input之后,我们无法获取到正确和所有的媒体参数,所以还得要调用avformat_find_stream_info进一步的去获取。
        avformat_find_stream_info(ic, NULL);

        //调用avformat_open_input读取到的媒体文件的路径/名字
        printf("媒体文件名称:%s\n", ic->filename);

        //视音频流的个数,如果一个媒体文件既有音频,又有视频,则nb_streams的值为2。如果媒体文件只有音频,则值为1
        printf("视音频流的个数:%d\n", ic->nb_streams);


        //媒体文件的平均码率,单位为bps
        printf("媒体文件的平均码率:%lldbps\n", ic->bit_rate);

        printf("duration:%d\n", ic->duration);
        int tns, thh, tmm, tss;
        tns = (ic->duration) / AV_TIME_BASE;
        thh = tns / 3600;
        tmm = (tns % 3600) / 60;
        tss = (tns % 60);
        printf("媒体文件总时长:%d时%d分%d秒\n", thh, tmm, tss); //通过上述运算,可以得到媒体文件的总时长

        printf("\n");
        //通过遍历的方式读取媒体文件视频和音频的信息,
        //新版本的FFmpeg新增加了函数av_find_best_stream,也可以取得同样的效果,但这里为了兼容旧版本还是用这种遍历的方式
        for (int i = 0; i < ic->nb_streams; i++)
        {
            AVStream *as = ic->streams[i];
            if (AVMEDIA_TYPE_AUDIO == as->codecpar->codec_type) //如果是音频流,则打印音频的信息
            {
                printf("音频信息:\n");
                printf("index:%d\n", as->index);  //如果一个媒体文件既有音频,又有视频,则音频index的值一般为1。但该值不一定准确,所以还是得通过as->codecpar->codec_type判断是视频还是音频
                printf("音频采样率:%dHz\n", as->codecpar->sample_rate); //音频编解码器的采样率,单位为Hz
                if (AV_SAMPLE_FMT_FLTP == as->codecpar->format)   //音频采样格式
                {
                    printf("音频采样格式:AV_SAMPLE_FMT_FLTP\n");
                }
                else if (AV_SAMPLE_FMT_S16P == as->codecpar->format)
                {
                    printf("音频采样格式:AV_SAMPLE_FMT_S16P\n");
                }
                printf("音频信道数目:%d\n", as->codecpar->channels); //音频信道数目
                if (AV_CODEC_ID_AAC == as->codecpar->codec_id)   //音频压缩编码格式
                {
                    printf("音频压缩编码格式:AAC\n");
                }
                else if (AV_CODEC_ID_MP3 == as->codecpar->codec_id)
                {
                    printf("音频压缩编码格式:MP3\n");
                }
                int DurationAudio = (as->duration) * r2d(as->time_base); //音频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
                printf("音频总时长:%d时%d分%d秒\n", DurationAudio / 3600, (DurationAudio % 3600) / 60, (DurationAudio % 60)); //将音频总时长转换为时分秒的格式打印到控制台上
                printf("\n");
            }
            else if (AVMEDIA_TYPE_VIDEO == as->codecpar->codec_type)  //如果是视频流,则打印视频的信息
            {
                printf("视频信息:\n");
                printf("index:%d\n", as->index);   //如果一个媒体文件既有音频,又有视频,则视频index的值一般为0。但该值不一定准确,所以还是得通过as->codecpar->codec_type判断是视频还是音频
                printf("视频帧率:%lffps\n", r2d(as->avg_frame_rate)); //视频帧率,单位为fps,表示每秒出现多少帧
                if (AV_CODEC_ID_MPEG4 == as->codecpar->codec_id) //视频压缩编码格式
                {
                    printf("视频压缩编码格式:MPEG4\n");
                }
                printf("帧宽度:%d 帧高度:%d\n", as->codecpar->width, as->codecpar->height); //视频帧宽度和帧高度
                int DurationVideo = (as->duration) * r2d(as->time_base); //视频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的
                printf("视频总时长:%d时%d分%d秒\n", DurationVideo / 3600, (DurationVideo % 3600) / 60, (DurationVideo % 60)); //将视频总时长转换为时分秒的格式打印到控制台上
                printf("\n");
            }
        }

        //av_dump_format(ic, 0, path, 0);
    }
    if (ic)
    {
        avformat_close_input(&ic); //关闭一个AVFormatContext,和函数avformat_open_input()成对使用
    }
    avformat_network_deinit();

    return 0;
}

3、运行结果 1

bash 复制代码
打开媒体文件 ./debug/test.mp4 成功!
媒体文件名称:./debug/test.mp4
视音频流的个数:2
媒体文件的平均码率:1436830bps
duration:117312000
媒体文件总时长:0时1分57秒

视频信息:
index:0
视频帧率:25.000000fps
帧宽度:1280 帧高度:720
视频总时长:0时1分57秒

音频信息:
index:1
音频采样率:48000Hz
音频采样格式:AV_SAMPLE_FMT_FLTP
音频信道数目:6
音频压缩编码格式:AAC
音频总时长:0时1分57秒

使用 MediaInfo 打开分析可以看到与上面的打印信息是对应上的

4、示例源码 2

使用新版本的 FFmpeg 新增加的函数 av_find_best_stream() 查找给定媒体文件中最佳的流(音频流或视频流)

cpp 复制代码
#include <stdio.h>
extern "C"
{
    #include <libavformat/avformat.h>
}
int main(int argc, char **argv)
{
// 1. 打开文件
    const char *ifilename = "./debug/test.mp4";
    printf("in_filename = %s\n", ifilename);
    avformat_network_init();

    // AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体
    AVFormatContext *ifmt_ctx = NULL;           // 输入文件的demux
    // 打开文件,主要是探测协议类型,如果是网络文件则创建网络链接
    int ret = avformat_open_input(&ifmt_ctx, ifilename, NULL, NULL);
    if (ret < 0) {
        char buf[1024] = {0};
        av_strerror(ret, buf, sizeof (buf) - 1);
        printf("open %s failed: %s\n", ifilename, buf);
        return -1;
    }

// 2. 读取码流信息
    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", ifilename, buf);
        avformat_close_input(&ifmt_ctx);
        return -1;
    }


// 3.打印总体信息
    printf_s("\n==== av_dump_format in_filename:%s ===\n", ifilename);
    av_dump_format(ifmt_ctx, 0, ifilename, 0);
    printf_s("\n==== av_dump_format finish =======\n\n");
    printf("media name:%s\n", ifmt_ctx->url);
    printf("stream number:%d\n", ifmt_ctx->nb_streams); //  nb_streams媒体流数量
    printf("media average ratio:%lldkbps\n",(int64_t)(ifmt_ctx->bit_rate/1024)); //  媒体文件的码率,单位为bps/1000=Kbps
    // duration: 媒体文件时长,单位微妙
    int total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE;  // 1000us = 1ms, 1000ms = 1秒
    printf("audio duration: %02d:%02d:%02d\n",
           total_seconds / 3600, (total_seconds % 3600) / 60, (total_seconds % 60));
    printf("\n");

// 4.读取码流信息
    // 音频
    int audioindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if (audioindex < 0) {
        printf("av_find_best_stream %s eror.", av_get_media_type_string(AVMEDIA_TYPE_AUDIO));
        return -1;
    }
    AVStream *audio_stream = ifmt_ctx->streams[audioindex];
    printf("----- Audio info:\n");
    printf("index: %d\n", audio_stream->index); // 序列号
    printf("samplarate: %d Hz\n", audio_stream->codecpar->sample_rate); // 采样率
    printf("sampleformat: %d\n", audio_stream->codecpar->format); // 采样格式 AV_SAMPLE_FMT_FLTP:8
    printf("audio codec: %d\n", audio_stream->codecpar->codec_id); // 编码格式 AV_CODEC_ID_MP3:86017 AV_CODEC_ID_AAC:86018
    if (audio_stream->duration != AV_NOPTS_VALUE) {
        int audio_duration = audio_stream->duration * av_q2d(audio_stream->time_base);
        printf("audio duration: %02d:%02d:%02d\n",
               audio_duration / 3600, (audio_duration % 3600) / 60, (audio_duration % 60));
    }

    // 视频
    int videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoindex < 0) {
        printf("av_find_best_stream %s eror.", av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        return -1;
    }
    AVStream *video_stream = ifmt_ctx->streams[videoindex];
    printf("----- Video info:\n");
    printf("index: %d\n", video_stream->index); // 序列号
    printf("fps: %lf\n", av_q2d(video_stream->avg_frame_rate)); // 帧率
    printf("width: %d, height:%d \n", video_stream->codecpar->width, video_stream->codecpar->height);
    printf("video codec: %d\n", video_stream->codecpar->codec_id); // 编码格式 AV_CODEC_ID_H264: 27
    if (video_stream->duration != AV_NOPTS_VALUE) {
        int video_duration = video_stream->duration * av_q2d(video_stream->time_base);
        printf("audio duration: %02d:%02d:%02d\n",
               video_duration / 3600, (video_duration % 3600) / 60, (video_duration % 60));
    }

// 5.提取码流
    AVPacket *pkt = av_packet_alloc();
    int pkt_count = 0;
    int print_max_count = 100;
    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);
    }

// 6.结束
    if(pkt)
        av_packet_free(&pkt);
    if(ifmt_ctx)
        avformat_close_input(&ifmt_ctx);


    avformat_network_deinit();

    return 0;
}

5、运行结果 2

bash 复制代码
in_filename = ./debug/test.mp4

==== av_dump_format in_filename:./debug/test.mp4 ===

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

media name:./debug/test.mp4
stream number:2
media average ratio:1403kbps
audio duration: 00:01:57

----- Audio info:
index: 1
samplarate: 48000 Hz
sampleformat: 8
audio codec: 86018
audio duration: 00:01:57
----- Video info:
index: 0
fps: 25.000000
width: 1280, height:720 
video codec: 27
audio duration: 00:01:57

-----av_read_frame start
audio pts: 0
audio dts: 0
audio size: 967
audio pos: 48
audio duration: 0.021333

video pts: 0
video dts: 0
video size: 105222
video pos: 1015
video duration: 0.040000
....
audio pts: 65536
audio dts: 65536
audio size: 949
audio pos: 346354
audio duration: 0.021333

video pts: 17408
video dts: 17408
video size: 6906
video pos: 347303
video duration: 0.040000
av_read_frame end
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './debug/test.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    creation_time   : 1970-01-01T00:00:00.000000Z
    encoder         : Lavf53.24.2
  Duration: 00:01:57.31, start: 0.000000, bitrate: 1436 kb/s
    Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1048 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)
    Metadata:
      creation_time   : 1970-01-01T00:00:00.000000Z
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, 5.1, fltp, 383 kb/s (default)
    Metadata:
      creation_time   : 1970-01-01T00:00:00.000000Z
      handler_name    : SoundHandler

四、 实战 2:转封装

1、原理讲解

从一种视频容器转成另一种视频容器。

所谓的封装格式转换,就是在 AVI,FLV,MKV,MP4 这些格式之间转换(对应 .avi.flv.mkv.mp4 文件)。需要注意的是,本程序并不进行视音频的编码和解码工作。而是直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。

传统的转码程序工作原理如下图所示:

上图例举了一个举例:FLV(视频:H.264,音频:AAC)转码为 AVI(视频:MPEG2,音频 MP3)的例子。可见视频转码的过程通俗地讲相当于把视频和音频重新"录" 了一遍。

本程序的工作原理如下图所示:

由图可见,本程序并不进行视频和音频的编解码工作,因此本程序和普通的转码软件相比,有以下两个特点:

  • 处理速度极快。视音频编解码算法十分复杂,占据了转码的绝大部分时间。因为不需要进行视音频的编码和解码,所以节约了大量的时间。
  • 视音频质量无损。
  • 因为不需要进行视音频的编码和解码, 所以不会有视音频的压缩损伤。

程序流程图如下图:

2、示例源码

以下源码实现下面命令同样的功能

bash 复制代码
ffmepg -i test.mp4 -c copy -f test.flv
cpp 复制代码
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>

static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;

    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);
}

int main(int argc, char **argv)
{
    AVOutputFormat *ofmt = NULL;
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;
    const char *in_filename, *out_filename;
    int ret, i;
    int stream_index = 0;
    int *stream_mapping = NULL;
    int stream_mapping_size = 0;

    in_filename  = "./debug/test.mp4";
    out_filename = "./debug/test.flv";

    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        fprintf(stderr, "Could not open input file '%s'", in_filename);
        goto end;
    }

    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        fprintf(stderr, "Failed to retrieve input stream information");
        goto end;
    }

    printf("\n\n-------av_dump_format:ifmt_ctx----------------\n");
    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if (!ofmt_ctx) {
        fprintf(stderr, "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    stream_mapping_size = ifmt_ctx->nb_streams;
    stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));
    if (!stream_mapping) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    ofmt = ofmt_ctx->oformat;

    for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        AVStream *out_stream;
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVCodecParameters *in_codecpar = in_stream->codecpar;

        if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
            in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
            in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
            stream_mapping[i] = -1;
            continue;
        }

        stream_mapping[i] = stream_index++;

        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream) {
            fprintf(stderr, "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }

        ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy codec parameters\n");
            goto end;
        }
        out_stream->codecpar->codec_tag = 0;
    }
    printf("\n\n-------av_dump_format:ofmt_ctx----------------\n");
    av_dump_format(ofmt_ctx, 0, out_filename, 1);

    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Could not open output file '%s'", out_filename);
            goto end;
        }
    }

    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error occurred when opening output file\n");
        goto end;
    }

    while (1) {
        AVStream *in_stream, *out_stream;

        /// 读取音视频压缩包
        ret = av_read_frame(ifmt_ctx, &pkt);
        if (ret < 0)
            break;

        in_stream  = ifmt_ctx->streams[pkt.stream_index];
        if (pkt.stream_index >= stream_mapping_size ||
            stream_mapping[pkt.stream_index] < 0) {
            av_packet_unref(&pkt);
            continue;
        }

        pkt.stream_index = stream_mapping[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];
        log_packet(ifmt_ctx, &pkt, "in");

        /* copy packet */
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        log_packet(ofmt_ctx, &pkt, "out");

        /// 交织写音视频包
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            fprintf(stderr, "Error muxing packet\n");
            break;
        }
        av_packet_unref(&pkt);//包需要解引用
    }

    av_write_trailer(ofmt_ctx);
end:

    avformat_close_input(&ifmt_ctx);

    /* close output */
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_closep(&ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);

    av_freep(&stream_mapping);

    if (ret < 0 && ret != AVERROR_EOF) {
        fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
        return 1;
    }

    return 0;
}

3、运行结果

bash 复制代码
-------av_dump_format:ifmt_ctx----------------


-------av_dump_format:ofmt_ctx----------------
in: pts:0 pts_time:0 dts:0 dts_time:0 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:0 pts_time:0 dts:0 dts_time:0 duration:21 duration_time:0.021 stream_index:1
in: pts:0 pts_time:0 dts:0 dts_time:0 duration:512 duration_time:0.04 stream_index:0
out: pts:0 pts_time:0 dts:0 dts_time:0 duration:40 duration_time:0.04 stream_index:0
in: pts:1024 pts_time:0.0213333 dts:1024 dts_time:0.0213333 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:21 pts_time:0.021 dts:21 dts_time:0.021 duration:21 duration_time:0.021 stream_index:1
....
in: pts:1500672 pts_time:117.24 dts:1500672 dts_time:117.24 duration:512 duration_time:0.04 stream_index:0
out: pts:117240 pts_time:117.24 dts:117240 dts_time:117.24 duration:40 duration_time:0.04 stream_index:0
in: pts:5628928 pts_time:117.269 dts:5628928 dts_time:117.269 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:117269 pts_time:117.269 dts:117269 dts_time:117.269 duration:21 duration_time:0.021 stream_index:1
in: pts:5629952 pts_time:117.291 dts:5629952 dts_time:117.291 duration:1024 duration_time:0.0213333 stream_index:1
out: pts:117291 pts_time:117.291 dts:117291 dts_time:117.291 duration:21 duration_time:0.021 stream_index:1
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './debug/test.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    creation_time   : 1970-01-01T00:00:00.000000Z
    encoder         : Lavf53.24.2
  Duration: 00:01:57.31, start: 0.000000, bitrate: 1436 kb/s
    Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1048 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)
    Metadata:
      creation_time   : 1970-01-01T00:00:00.000000Z
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, 5.1, fltp, 383 kb/s (default)
    Metadata:
      creation_time   : 1970-01-01T00:00:00.000000Z
      handler_name    : SoundHandler
Output #0, flv, to './debug/test.flv':
    Stream #0:0: Video: h264 (Main), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 1048 kb/s
    Stream #0:1: Audio: aac (LC), 48000 Hz, 5.1, fltp, 383 kb/s

使用 MediaInfo 查看未进行转封装前 test.mp4 的封装格式为 MPEG-4

使用 MediaInfo 查看转封装后新生成的 test.flv 文件的封装格式为 Flash Video


我的qq:2442391036,欢迎交流!


相关推荐
mo47764 小时前
Webrtc音频模块(四) 音频采集
音视频·webrtc
icy、泡芙4 小时前
T527-----音频调试
linux·驱动开发·音视频
易我数据恢复大师4 小时前
怎么提取音频保存到本地?电脑音频提取方法
音视频·软件·音频提取
野蛮的大西瓜4 小时前
开源呼叫中心中,如何将ASR与IVR菜单结合,实现动态的IVR交互
人工智能·机器人·自动化·音视频·信息与通信
嘟嘟实验室6 小时前
微信小程序xr-frame透明视频实现
微信小程序·ffmpeg·音视频·xr
红米饭配南瓜汤7 小时前
WebRTC服务质量(09)- Pacer机制(01) 流程概述
网络·音视频·webrtc
是十一月末10 小时前
Python进阶之opencv图片和视频基本读取关闭
python·opencv·音视频·cv2
gomogomono13 小时前
HDR视频技术之十一:HEVCH.265 的 HDR 编码方案
音视频·h.265·hdr·yuv
Eric.Lee202114 小时前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
却道天凉_好个秋14 小时前
音视频学习(二十四):hls协议
音视频·hls