AVFormatContext封装层:理论与实战

文章目录

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


前言

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 编码),所以只有解码器定义:

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 封装格式定义如下:

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()

#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

打开媒体文件 ./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() 查找给定媒体文件中最佳的流(音频流或视频流)

#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

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、示例源码

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

ffmepg -i test.mp4 -c copy -f test.flv
#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、运行结果

-------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,欢迎交流!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/221640.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

上传文件接口的创建_FastAPI

上传文件接口的创建 功能描述代码效果演示与注意事项 功能描述 前端用户需要上传文件至平台&#xff0c;就比如CSDN的上传资源部分&#xff0c;都是一样的功能逻辑&#xff0c;想要实现这个功能其实并不难。 这里以上传的JSON格式文件为例&#xff0c;其他格式文件的话可以自…

Container容器技术简介

本文介绍了容器技术出现背景&#xff0c;docker技术与容器编排技术的简单说明 背景 在传统项目的生产环境中&#xff0c;迁移一个用户态进程往往非常麻烦&#xff0c;因为一个用户态进程背后会附带这非常多例如函数库、中间件等的依赖项&#xff0c;但又没有像apt和yum一样的…

用pip更新、安装python的包

查看pip的版本&#xff1a;python -m pip --version 例如&#xff0c;查看下pip的版本&#xff0c;在cmd下输入命令python -m pip --version&#xff0c;可以发现当前安装的pip的版本是23.2.1&#xff1a; 查看一个包的详情&#xff1a;python -m pip show 例如&#xff0c…

Leetcode—2477.到达首都的最少油耗【中等】

2023每日刷题&#xff08;五十&#xff09; Leetcode—2477.到达首都的最少油耗 算法思想 参考自灵茶山艾府 实现代码 class Solution { public:long long minimumFuelCost(vector<vector<int>>& roads, int seats) {int n roads.size() 1;vector<i…

【IEEE独立出版|EI会议征稿】2024年第四届消费电子与计算机工程国际学术会议(ICCECE 2024)

2024年第四届消费电子与计算机工程国际学术会议&#xff08;ICCECE 2024&#xff09; 2024 4th International Conference on Consumer Electronics and Computer Engineering 进入21世纪以来&#xff0c;计算机技术的高速发展带来了消费电子产品的快速更迭。在技术迅速发展历…

SOLIDWORKS 2024新功能之Simulation篇

SOLIDWORKS 2024 新功能 Simulation篇目录概述 • 自动保存模型文件 • 壳体的接合交互 • 收敛检查图解 • 去耦合混合自由体模式 • Direct Sparse 解算器已停用 • 增强型轴承接头 • 复制算例时排除网格和结果 • 导出模型形状数据 • 网格性能 • 性能增强功能 …

物联网水表和4G水表的区别有哪些?

随着科技的发展&#xff0c;水表也不再是传统的机械表&#xff0c;而是经过数字化和智能化改造的物联网水表和4G水表。这两种水表具有很多的不同点。那么&#xff0c;物联网水表和4G水表的区别有哪些&#xff1f; 首先&#xff0c;物联网水表和4G水表的通信方式不同。物联网水表…

27、pytest实战:一套用例同时验证生产、测试两个环境

前提 生产与测试环境接口地址相同&#xff0c;只是域名不同&#xff0c;例&#xff0c;生产环境为http://192.168.1.40&#xff0c;测试环境为http://192.168.1.50生产环境有严格要求&#xff0c;只允许查询操作&#xff0c;不允许进行增删改&#xff1b;测试环境可进行所有操…

计算机视觉 - 用于基于图切割算法的木材堆叠测量的权重估计(基于圆形霍夫变换和局部圆度测量)

一、背景说明 计算机视觉技术在木材行业中被用于检测和测量成堆木材中的原木。主要是测量原木的数量及其体积和尺寸。很多场景都是手动测量和收集此类数据&#xff0c;耗费人力并且存在人为错误的风险。可靠的替代工业技术是使用激光扫描仪来扫描&#xff0c;然后估计木材堆中每…

火焰图的基本认识与绘制方法

火焰图的认识与使用-目录 火焰图的基本认识火焰图有以下特征(on-cpu)火焰图能做什么火焰图类型On-CPU 火焰图和Off-CPU火焰图的使用场景火焰图分析技巧 如何绘制火焰图生成火焰图的流程1.生成火焰图的三个步骤 安装火焰图必备工具1.安装火焰图FlameGraph脚本2.安装火焰图数据采…

Redis穿透以及解决方法

Redis穿透是指当一个请求在缓存中和数据库都找不到对应的数据时&#xff0c;导致每次请求都要查询数据库&#xff0c;从而产生了大量的无效数据库查询&#xff0c;大量无效的数据库查询会导致数据库负载增加&#xff0c;降低数据库的性能和响应能力甚至宕机的风险。 这种情况通…

详细介绍如何使用 SSD 进行实时物体检测:单次 MultiBox 探测器-含源码

介绍 在实时对象检测中,主流范例传统上采用多步骤方法,包括边界框、像素或特征重采样以及高质量分类器应用的提议。虽然这种方法已经实现了高精度,但其计算需求往往阻碍了其对实时应用的适用性。然而,单次多框检测器 (SSD) 代表了基于深度学习的对象检测的突破性飞跃。SSD…

RPG项目01_层级设置

基于“RPG项目01_UI面板Game”&#xff0c; 找到狼人 添加组件&#xff0c;让狼人一定区域自动跟随主角进行攻击 解释&#xff1a;【烘培蓝色】因为如果什么都不做就会被烘培成蓝色对应的功能就是 可修改区域功能 当将区域设置成不可行走状态&#xff0c;则不为蓝色 烘培&…

Web自动化测试怎么做?Web网页测试全流程解析

1、功能测试 web网页测试中的功能测试&#xff0c;主要测试网页中的所有链接、数据库连接、用于在网页中提交或获取用户信息的表单、Cookie 测试等。 &#xff08;1&#xff09;查看所有链接&#xff1a; 测试从所有页面到被测特定域的传出链接。 测试所有内部链接。 测…

【头歌系统数据库实验】实验4 MySQL单表查询

目录 第1关. 在users表中新增一个用户&#xff0c;user_id为2019100904学号&#xff0c;name为2019-物联网-李明 第2关. 在users表中更新用户 user_id为robot_2 的信息&#xff0c;name设为 机器人二号 第3关. 将solution表中所有 problem_id 为1003 题目的解答结果&#xf…

Java抽象类(abstract class)和接口(interface)的区别——面试

1.抽象类&#xff08;abstract class&#xff09;和接口&#xff08;interface&#xff09;的区别&#xff1a; 抽象类可以有构造方法&#xff0c;接口中不能有构造方法。 抽象类中可以有普通成员变量&#xff0c;接口中没有普通成员变量。抽象类中可以包含非抽象的普通方法&am…

C语言进阶之路-指针、数组等混合小boss篇

目录 一、学习目标&#xff1a; 二、指针、数组的组合技能 引言 指针数组 语法 数组指针 三、勇士闯关秘籍 四、大杂脍 总结 一、学习目标&#xff1a; 知识点&#xff1a; 明确指针数组的用法和特点掌握数组指针的用法和特点回顾循环等小怪用法和特点 二、指针、数…

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之文件管理(3)》(24)

《Linux操作系统原理分析之文件管理&#xff08;3&#xff09;》&#xff08;24&#xff09; 7 文件管理7.5 文件存储空间的管理7.6 文件的共享和保护7.6.1 文件存取控制7.6.2 文件共享的实现方法7.6.3 文件的备份转储 7 文件管理 7.5 文件存储空间的管理 位示图 对每个磁盘…

Lookup Argument简史

1. 引言 主要参考Ingonyama团队2023年4月文章《A Brief History of Lookup Arguments》。 近年来zk-SNARKs的研究热点有&#xff1a; 让ZKP proof更succinct降低Prover time和Verifier time 但&#xff0c;大多数SNARKs仍受限于&#xff0c;易于转换为多项式的算术运算。通…

Unity3D实现鼠标悬浮UI或物体上显示文字信息

系列文章目录 Unity工具 文章目录 系列文章目录前言最终效果一、UI事件显示文字1-1 ui事件需要引用命名空间using UnityEngine.EventSystems;1-2 IPointerEnterHandler 接口1-3 IPointerExitHandler 接口1-4 IPointerMoveHandler 接口 二、场景搭建2-1 实现如下 三、代码实现3…