FFmpeg 框架简介和文件解复用

news2024/12/22 5:16:38

文章目录

  • ffmpeg框架简介
    • libavformat库
    • libavcodec库
    • libavdevice库
  • 复用(muxers)和解复用(demuxers)
    • 容器格式
      • FLV
        • Script Tag Data结构(脚本类型、帧类型)
        • Audio Tag Data结构(音频Tag)
        • Video Tag Data结构(视频Tag)
      • MP4
        • Box结构如图所示:
        • 整体结构
    • 文件解复用
    • 文件复用
    • 项目实战
      • 抽取音频数据
      • 抽取视频
    • 容器格式转换

ffmpeg框架简介

八大库:

  1. libavformat:复用和解复用,格式封装
  2. libavcodec:编码、解码
  3. libavutil:通用音视频工具,像素、IO、时间等工具
  4. iibavfilter:过滤器,可以用作音视频特效处理
  5. libavdevice:设备(摄像头、麦克风)
  6. libswscale:视频图像缩放,像素格式互换
  7. libswresample:重采样
  8. libpostproc:后期处理

libavformat库

libavformat库包含I/O模块和Muxing/Demuxing库,它是一个处理各种媒体容器格式的库。它的两个主要用途是拆分(即将媒体文件拆分为组件流)和反向拆分(以指定的容器格式写入提供的数据)。它还有一个I/O模块,支持访问数据的多种协议(如file、tcp、http等)。除非你绝对确定你不会使用libavformat的网络功能,否则你也应该调用avforamt_network_init()来初始化网络功能。

支持的输入格式(即解复用)由AVInputFormat结构体描述,相反,输出格式(即复用)由AVOutputFormat描述。可以使用av_demuxer_iterate()/av_muxer_iterate()函数遍历所有输入/输出格式。协议层不是公共API的一部分,因此您只能使用avio_enum_protocols()函数获得受支持协议的名称。

用于复用和解复用的主要结构是AVFormatContext,它保存关于读取或写入的文件的所有信息,与大多数Libavformat库的结构体一样,它不能在堆栈上分配或直接使用av_malloc(),要创建AVFormatContext,必须使用avformat_alloc_context(),有些函数会自动分配内存,如avformat_open_input(),最重要的是,AVFormatContext包含:

  1. 输入或输出格式。它是自动检测或由用户设置输入。输出总是由用户设置。
  2. AVSteams的数组,它描述了存储在文件中的所有基本流。AVStreams通常在这个数组中使用它们的索引来引用。
  3. I/O context。对于输入,它是由libavformat库打开的或由用户设置的,对于输出,始终由用户设置,除非您处理AVFMT_NOFILE格式。
    使用AVOptions机制可以配置复用器和解复用器。通用的(与格式无关的)libavformat选项由AVFormatContext提供,它们可以从用户程序中通过调用av_opt_next()/av_opt_find()对分配的AVFormatContext,或其avformat_get_class()中的AVClass进行检查。私有特定于格式的选项由AVFormatContext提供,priv_data当且仅当AVInputFormat,priv_class/AVOutputFormat相应格式结构的priv_class为非null时有效。如果I/O上下文的AVClass为非null,则可以提供进一步的选项

libavformat中的URL由协议、':‘和特定于协议的字符串组成。支持不带协议标识的url和使用’:'来表示本地文件,但已弃用,本地文件应该用’file:'标识

libavcodec库

avcodec_send_packet()/avcodec_receive_frame()/avcodec_send_frame()/avcodec_receive_packet()函数提供了编码/解码API,它将输入作为解码,输出作为编码

该API在编码/解码和音频/视频方面非常相似,工作方式如下:

  1. 像往常一样设置并打开AVCodecContext
  2. 发送有效的输入:
  • 对于解码,调用avcodec_send_packet()以向解码器提供包含原始压缩数据的AVPacket
  • 对于编码,调用avcodec_send_frame()以向解码器提供包含未压缩音频或视频的AVFrame
    在两种情况下,建议对AVPackets和AVFrames进行引用计数,否则libavcodec可能需要复制输入数据。(libavformat总是返回引用计数的AVPackets,av_frame_get_buffer()分配引用计数的AVFrames)
  1. 在循环接收输出,定期调用avcodec_receive_*()函数并处理其输出:
  • 对于解码,调用avcodec_receive_frame(),成功时,它降返回一个包含未压缩音频或视频数据的AVFrame
  • 对于编码,调用avcodec_receive_packet(),成功时,它将返回一个带有压缩帧的AVPacket
    重复此调用,直到返回AVERROR(EAGAIN)或错误,AVERROR(EAGAIN)返回值表示需要新的输入数据以生成新的输出。在这种情况下,继续发送输入,对于每个输入帧/包,编解码器通常会返回1个输出帧/包,但也可以是0或多于1个。

在解码或者编码开始时,编解码器可能接受多个输入帧./包而不返回帧,直到其内部缓冲区填满。如果按照上述步骤操作,这种情况会被透明处理。

理论上,发送输入可能导致EAGAIN,这只有在没有接收到全部输出时才会发生。您可以利用这一点来构建除了上面建议的循环之外的其他解码或编码循环。例如,您可以尝试在每次迭代中发送新的输入,并在返回EAGAIN时尝试接收输出。

流结束的情况需要对编码器进行"flush"(也成为draining),因为编解码器可能会在内部缓冲多个帧或包以提高性能或出于必要性(考虑B帧)。处理方式如下:

  1. 而不是提供有效的输入,向avcodec_send_packet()解码或avcodec_send_frame()编码函数发送NULL,这将进入排空模式
  2. 在循环中调用avcodec_receive_frame()解码或avcodec_receive_packet()编码,直到返回AVERROR_EOF。这些函数不会返回AVERROR(EAGAIN),除非您忘记进入排空模式。
  3. 在解码可以再次开始之前,必须使用avcodec_flush_buffers()重置编解码器。

强烈建议按照上述提纲使用API。但也可以在这种严格的模式之外调用函数。例如,可以反复调用avcodec_send_packet(),而不调用avcodec_receive_frame(),在这种情况下,avcodec_send_packet()将成功,直到编解码器的内部缓冲区被填满(通常是每个输出帧的大小,初始输入后),然后使用AVERROR(EAGAIN)拒绝输入。一旦开始拒绝输入,您别无选择,只能读取至少一些输出。

并非所有编解码器都会遵循严格且可预测的数据流,唯一的保证是在一个端口的send/receive调用返回AVERROR(EAGAIN)意味着在另一端口的receive/send调用将成功,或者至少不会以AVERROR(EAGAIN)失败,总的来说,没有编解码器允许无限制地缓冲输入或输出。

编解码器不允许对发送和接收都返回AVERROR(EAGAIN)。这将是一种无效的状态,可能使编解码器用户陷入无休止的循环。API没有时间的概念:尝试执行avcodec_send_packet()不可能导致AVERROR(EAGAIN),但在1秒后的重复调用接受包(不涉及其他receive/flush API调用)。API是一个严格的状态机,时间的流逝不应该影响它。在某些情况下,某些依赖于时间的行为可能仍然被视为可以接受的,但绝不能导致在任何时候同时返回EAGAIN的发送/接收。还必须绝对避免当前状态是“不稳定”的且可以在发送/接收API之间“翻转”的情况。例如,编解码器不允许随机决定在刚刚在avcodec_send_packet()调用上返回AVERROR(EAGAIN)后,现在实际上想要消耗一个包而不适宜返回一个帧。

libavdevice库

libavdevice是专用设备muxer/demuxer库

libavdevice是libavformat的补充库。它提供了各种特殊平台特定的muxers和demuxers,例如用于抓取设备,音频捕获和播放等。因此,libavdevice中的(de)muxers是AVFMT_NOFILE类型的,它们使用自己的I/O函数。传递给avformat_open_input()的文件名通常不指向实际存在的文件,但具有某些特定于设备的特殊含义。例如,对于xcbgrab,它是显示名称,

要使用libavdevice,只需要调用avdevice_register_all()来注册所有编译的复用器和解复用器。它们都使用标准的libavformat API。

复用(muxers)和解复用(demuxers)

容器格式

FLV

FLV是Adobe公司推出的一种流媒体格式,由于其封装后的音视频文件体积小,封装简单等特点,非常适合于互联网上使用,目前主流的视频网站基本都支持FLV,采用FLV格式封装的文件后缀为.flv。

FLV封装格式是由一个文件头(file header)和文件体(file body)组成。其中,FLV body由一对对的(Previous_Tag_Size字段+tag)组成,Previous_Tag_Size字段排列在Tag之前,占用4个字节。Previous_Tag_Size记录了前面一个Tag的大小,用于逆向读取处理,FLV header后的第一个Pervious_Tag_Size的值为0。Tag一般可以分为3种类型:脚本,数据类型、音频数据类型、视频数据。FLV数据以大端序进行存储,在解析时需要注意,一个标准FLV文件结构如下图:
在这里插入图片描述

Script Tag Data结构(脚本类型、帧类型)

该类型Tag又被称为MetaData Tag,存放一些关于FLV视频和音频的元信息,比如:duration、width、height等。通常该类型tag会作为FLV文件的第一个tag,并且只有一个,跟在File Header后。该类型Tag Data的结构如下所示:
在这里插入图片描述
第一个AMF包:
第一个字节表示AMF包类型,一般总是0x02,表示字符串,第2-3个字节为UI16类型值,标识字符串的长度,一般总是0x000A(onMeataData长度)。后面字节为具体的字符串,一般总为onMetaData(6F 6E 4D 65 74 61 44 61 74 61).
第二个AMF包
第1个字节表示AMF包类型,一般总是0x08,表示数组,第2-5个字节为UI32类型值,表示数组元素的个数,后面即为各数组元素的封装,数组元素为元素名称和值组成的对。常见的数组元素如下表所示

Comment例如
duration时长(秒)210.732
width视频宽度768.000
height视频高度320.000
videodatarate视频码率207.260
framerate视频帧率25.000
videocodecid视频编码ID7.000(H264为7)
audiodatarate音频码率29.329
audiosamplerate音频采样率44100.000
stereo是否立体声1
audiocodecid音频编码ID10.000(aac为10)
major_brand格式规范相关isom
minor_version格式规范相关512
compatible_brands格式规范相关isomiso2avc1mp41
encoder封装工具名称Lavf54.63.104
filesize文件大小(字节)6636853.000
Audio Tag Data结构(音频Tag)

音频Tag开始的第1个字节包含了音频数据的参数信息,从第二个字节开始为音频流数据
在这里插入图片描述
第1个字节的前4位数值表示了音频编码类型

含义
0Linear PCM,platform endian
1ADPCM
2MP3
3Linear PCM,little endian
4Neltymoser 16-kHz mono
5Neltymoser 8-kHz mono
6Neltymoser
7G.711 A-law logarithmic PCM
8G.711 mu-law logarithmic PCM
9reserved
10AAC
14MP# 8-kHz
15Device-specific sound

第1个字节的第5-6位的数值表示音频采样率。

含义
05.5kHz
111kHz
222kHz
344kHz

从上表可以发现FLV封装格式并不支持48kHz的采样率
第1个字节的第7为表示音频采样精度

含义
08bits
116bits

第1个字节的第8位表示音频类型

含义
0sndMono
1sndStereo
Video Tag Data结构(视频Tag)

视频Tag也用开始的第1个字节包含视频数据的参数信息,从第2个字节为视频流数据
在这里插入图片描述
第1个字节的前4位的数值表示帧类型

含义
1keyframe ( for AVC, a seekable frame )
2inter frame ( for AVC, a nonseekable frame )
3disposable inter frame ( H.263 only )
4generated keyframe ( reserved for server use )
5video info/command frame

第1个字节的后4位数值表示视频编码类型

含义
1JPEG ( currently unused )
2Sorenson H.263
3Screen video
4On2 VP6
5On2 VP6 with alpha channel
6Screen video version 2
7AVC

MP4

MP4起源于QuickTime,全名是MPEG-4 Part 14,属于MPEG-4的一部分。这部分内容主要规定了多媒体容器的格式。后来成为ISO/IEC 14996-14国际标准,其中MP4就是对这种标准的一种具体实现,基于这个标准进行扩展或者裁剪还产生了像M4V、F4V等封装格式。

MP4文件中的所有数据都装在box中,也就是说MP4文件由若干个box组成,每个box有类型和长度,可以将box理解为一个数据对象块。box中可以包含另一个box,这种box成为container box,一个MP4文件首先会有且只有一个ftyp类型的box,作为MP$格式的标志并包含关于文件的一些信息,之后会有且只有一个moov类型的box,它是一种container box,子box包含了媒体的metadata信息,MPC文件的媒体数据包含在mdat类型的box中,该类型的box也是container box,可以有多个,也可以没有(当媒体数据全部引用其他文件时),媒体数据的结构由metadata进行描述。

Box结构如图所示:

在这里插入图片描述
其中,size指明了整个box所占用的大小,包括header部分,如果box很大,超过了uint32的最大数值,size就被设置为1,并且用接下来的8位uint64来存放大小

整体结构

在这里插入图片描述
Box 的类型详见下表(其中 * 表示当父 Box 存在时,则必须包含该 Box):
在这里插入图片描述

文件解复用

解复用器(Demuxers)读取媒体文件并将其拆分为数据块(Packet),一个数据包包含一个或多个编码帧,这些帧属于单一的基本流。在libavformat API中,这个过程由以下函数表示:

  1. avformat_open_input()用于打开文件。
  2. av_read_frame()用于读取单个数据包。
  3. avformat_close_input()用于清理工作。

从打开的AVFormatContext中读取数据是通过反复调用av_read_frame()来完成的。每次调用,如果成功,将返回一个AVPacket,其中包含一个AVStream的编码数据,由AVPacket.stream_index字段标识。如果调用者希望解码数据,这个数据包可以直接传递给libavcodec解码函数avcodec_send_packet()或avcodec_decode_subtitle2()。

如果已知,AVPacket.pts、AVPacket.dts和AVPacket.duration时间信息将被设置。如果流没有提供这些信息,它们也可能未设置(例如AV_NOPTS_VSLUE表示pts(播放时间戳)/dts(解码时间戳)未设置,0表示duration未设置)。时间信息的单位是AVStream.tim_base,即必须乘以时间基准才能将其转换为秒。

由av_read_frame()返回的数据包始终是引用计数的,即AVPacket.buf已设置,用户可以无限期保留它,当不再需要数据包时,必须使用av_packet_unref()进行减引用计数。当引用计数为0时,会自动释放内存,或者用av_packet_free()函数释放。

    const char *url = "G:/qtproject/ffmpegTest/source/audio.mp3";
    AVFormatContext *s = NULL;                          // 格式上下文
    int ret = avformat_open_input(&s, url, NULL, NULL); // 打开输入流
    if (ret < 0)
    {
        char buf[1024] = {0};
        av_strerror(ret, buf, sizeof(buf));
        qDebug() << "open input failed:" << buf;
        return;
    }
    avformat_find_stream_info(s, NULL); // 获取流信息
    av_dump_format(s, 0, url, 0);       // 打印流信息
    avformat_close_input(&s);           // 关闭输入流

由于打开的文件的格式通常在avformat_open_input()返回之前是不知道的,因此不可能在预分配的上下文中设置demuxer私有选项,相反,这些选项应该传递给avformat_open_input(),包装在AVDictonary中:

    AVDictionary *options = NULL;                         // 选项
    av_dict_set(&options, "video_size", "640x480", 0);    // 设置参数
    av_dict_set(&options, "pixel_format", "rgb24", 0);    // 设置参数
    if (avformat_open_input(&s, url, NULL, &options) < 0) // 打开输入流
    {
        char buf[1024] = {0};
        av_strerror(ret, buf, sizeof(buf));
        qDebug() << "open input failed:" << buf;
        return;
    }
    av_dict_free(&options); // 选项释放

这段代码将私有选项video_sizepixel_format传递给demuxer。它们对于解复用原始数据来说是必要的,因为它不知道如何解释原始视频数据,如果格式与原始视频不同,则demuxer将无法识别这些选项,因此不会应用这些选项,然后将这些无法识别的选项返回到选项字典中,使用已识别的选项。调用程序可以随心所欲地处理这些无法识别的选项,例如:

    AVDictionaryEntry *e = NULL;
    if (e = av_dict_get(options, "", e, AV_DICT_IGNORE_SUFFIX))
    {
        fprintf(stderr, "found %s = %s\n", e->key, e->value); // 打印选项
        abort();
    }
    av_dict_free(&options); // 选项释放

详细案例:

#include "Widget.h"
#include "./ui_Widget.h"
#include <stdio.h> // 标准输入输出
extern "C"
{
#include <libavcodec/avcodec.h>   // 编码器
#include <libavformat/avformat.h> //格式上下文
#include <libavutil/avutil.h>     // 错误处理
}

Widget::Widget(QWidget *parent)
    : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    AVFormatContext *pFormatCtx = NULL; // 格式上下文
    // const char *url = "G:/qtproject/ffmpegTest/source/video-60fps.MP4"; // 输入文件路径
    const char *url = "../source/video-60fps.flv";               // 输入文件路径
    int ret = avformat_open_input(&pFormatCtx, url, NULL, NULL); // 打开输入文件,路径如果有中文要转义成utf-8

    char *errbuf = new char[1024]; // 定义错误信息缓冲区
    if (ret < 0)
    {
        // 打印错误信息,av_err2str(ret)是avutil库提供的函数,将错误码转换成字符串
        // 这里mingw编译器会报错.换成如下代码
        qDebug() << "打开输入文件失败:" << av_make_error_string(errbuf, 1024, ret);
        return;
    }
    ret = avformat_find_stream_info(pFormatCtx, NULL); // 获取视频信息,如果是flv文件,需要调用这个函数,如果是mp4文件,可以不调用,因为avformat_open_input函数已经获取了视频信息
    if (ret < 0)
    {
        // printf("获取视频信息失败:%s\n", av_err2str(ret));
        qDebug() << "获取视频信息失败:" << av_make_error_string(errbuf, 1024, ret);
    }
    av_dump_format(pFormatCtx, 0, url, 0); // 打印视频信息
    // 获取视频信息
    const AVCodec *pCodec = NULL;                                                             // 编码器
    int videoIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0); // 查找视频流,返回视频流索引
    if (videoIndex < 0)
    {
        qDebug() << "查找视频流失败:" << av_make_error_string(errbuf, 1024, ret);
    }
    else
    {

        AVStream *pVideoStream = pFormatCtx->streams[videoIndex];  // 获取视频流
        printf("FPS:%lf\n", av_q2d(pVideoStream->avg_frame_rate)); // 获取视频帧率
        printf("编码器ID :%d\n", pVideoStream->codecpar->codec_id);
        printf("分辨率 :%dx%d\n", pVideoStream->codecpar->width, pVideoStream->codecpar->height); // 获取视频分辨率
        printf("视频时长:%d(秒)\n", pFormatCtx->duration / AV_TIME_BASE);                        // 获取视频时长
    }
    // 获取音频信息
    const AVCodec *pAudioCodec = NULL;                                                             // 编码器
    int audioIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, &pAudioCodec, 0); // 查找音频流,返回音频流索引
    if (audioIndex < 0)
    {
        qDebug() << "查找音频流失败:" << av_make_error_string(errbuf, 1024, ret);
    }
    else
    {
        AVStream *pAudioStream = pFormatCtx->streams[audioIndex];                                         // 获取音频流
        printf("音频采样率 :%d\n", pAudioStream->codecpar->sample_rate);                                  // 获取音频采样率
        printf("音频声道数 :%d\n", pAudioStream->codecpar->ch_layout.nb_channels);                        // 获取音频声道数
        printf("音频时长:%d(秒)\n", pFormatCtx->duration / AV_TIME_BASE);                                // 获取音频时长
        printf("采样格式 :%s\n", av_get_sample_fmt_name((AVSampleFormat)pAudioStream->codecpar->format)); // 获取音频采样格式
        printf("采样数量 :%d\n", pAudioStream->codecpar->frame_size);                                     // 获取音频采样数量
    }

    if (videoIndex == AVERROR_STREAM_NOT_FOUND && audioIndex == AVERROR_STREAM_NOT_FOUND)
        return;

    AVPacket *packet = av_packet_alloc(); // 分配一个packet
    if (!packet)
        return;
    for (int i = 0; i < 10; i++)
    {
        if (av_read_frame(pFormatCtx, packet) >= 0)
        {
            if (packet->stream_index == videoIndex)
            {
                // 处理视频帧
                printf("视频帧大小:%d\n", packet->size);
                printf("视频帧时间:%lld\n", packet->pts);
                printf("视频帧持续时间:%lld\n", packet->duration);
                printf("视频帧时间基:%d/%d\n", packet->time_base.num, packet->time_base.den); // 时间基
            }
            else if (packet->stream_index == audioIndex)
            {
                // 处理音频帧
                printf("音频帧大小:%d\n", packet->size);
                printf("音频帧时间:%lld\n", packet->pts);
                printf("音频帧持续时间:%lld\n", packet->duration);
                printf("音频帧时间基:%d/%d\n", packet->time_base.num, packet->time_base.den); // 时间基
            }
        }
    }

    avformat_close_input(&pFormatCtx); // 关闭输入文件
    av_packet_free(&packet);           // 释放packet
}

文件复用

复用器(Muxers)接收以AVPackets形式编码的数据,并将其写入文件或其他指定容器格式的输出字节流。

复用的主要API函数有:

  • avformat_write_header()用于写入文件头。
  • av_write_frame()/av_interleaved_write_frame()用于写入数据包。
  • av_write_trailer()用于完成文件的封装。

项目实战

抽取音频数据

void Widget::muxers()
{
    char errbuf[1024];                     // 错误信息缓冲区
    AVFormatContext *pInFormatCtx = NULL;  // 打开文件上下文
    AVFormatContext *pOutFormatCtx = NULL; // 输出文件上下文

    const AVOutputFormat *pOutFmt = NULL; // 输出格式
    AVStream *pOutStream = NULL;          // 输出流
    AVStream *pInStream = NULL;           // 输入流

    const char *inputFile = "../source/video-30fps.MP4"; // 输入文件
    const char *outputFile = "../output/out.aac";        // 输出文件

    // 打开输入文件
    if (avformat_open_input(&pInFormatCtx, inputFile, NULL, NULL) != 0)
    {
        av_strerror(1, errbuf, sizeof(errbuf));
        printf("无法打开输入文件:%s\n", errbuf);
        return;
    }

    // 获取输入文件信息
    if (avformat_find_stream_info(pInFormatCtx, NULL) < 0)
    {
        av_strerror(1, errbuf, sizeof(errbuf));
        printf("无法获取输入文件信息:%s\n", errbuf);
        return;
    }

    // 查找音频流
    int audioIndex = av_find_best_stream(pInFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if (audioIndex < 0)
    {
        av_strerror(1, errbuf, sizeof(errbuf));
        printf("无法找到音频流:%s\n", errbuf);
        return;
    }
    pInStream = pInFormatCtx->streams[audioIndex];
    // 创建输出文件上下文
    pOutFormatCtx = avformat_alloc_context();
    if (!pOutFormatCtx)
    {
        printf("无法创建输出文件上下文\n");
        return;
    }

    // 设置输出文件格式
    pOutFmt = av_guess_format(NULL, outputFile, NULL);
    if (!pOutFmt)
    {
        printf("无法获取输出文件格式\n");
        return;
    }

    pOutFormatCtx->oformat = pOutFmt;
    // 添加输出流
    pOutStream = avformat_new_stream(pOutFormatCtx, NULL);
    if (!pOutStream)
    {
        printf("无法添加输出流\n");
        return;
    }

    // 复制输入流参数到输出流
    if (avcodec_parameters_copy(pOutStream->codecpar, pInStream->codecpar) < 0)
    {
        printf("无法复制输入流参数到输出流\n");
        return;
    }
    pOutStream->codecpar->codec_tag = 0; //设置输出流标签\

    // 打开输出文件
    if (avio_open(&pOutFormatCtx->pb, outputFile, AVIO_FLAG_WRITE) < 0)
    {
        printf("无法打开输出文件\n");
        return;
    }

    // 写文件头
    if (avformat_write_header(pOutFormatCtx, NULL) < 0)
    {
        printf("无法写入文件头\n");
        return;
    }
    // 写入数据
    AVPacket packet; // 数据包
    while (av_read_frame(pInFormatCtx, &packet) >= 0)
    {
        if (packet.stream_index == audioIndex)
        {
            av_packet_rescale_ts(&packet, pInStream->time_base, pOutStream->time_base); // 时间基转换
            packet.stream_index = pOutStream->index;                                    // 设置流索引

            int ret = av_interleaved_write_frame(pOutFormatCtx, &packet); // 写入数据包
            if (ret < 0)
            {
                av_strerror(ret, errbuf, sizeof(errbuf));
                printf("写入数据包失败:%s\n", errbuf);
                break;
            }
            av_packet_unref(&packet); // 释放数据包
        }
    }
    // 写文件尾
    av_write_trailer(pOutFormatCtx);
    // 释放资源
    avformat_close_input(&pInFormatCtx);
    avformat_free_context(pOutFormatCtx);
    // 关闭文件
    avio_close(pOutFormatCtx->pb);
}

抽取视频

和音频是一样的,格式换成视频格式,要和原视频info的视频格式一样

容器格式转换

void Widget::conversion()
{
    char errbuf[1024];                                   // 错误信息缓冲区
    const char *inputFile = "../source/video-30fps.MP4"; // 输入文件
    const char *outputFile = "../output/out.flv";        // 输出文件
    AVFormatContext *pInFormatCtx = NULL;                // 打开文件上下文
    AVFormatContext *pOutFormatCtx = NULL;               // 输出文件上下文
    // 打开输入文件
    if (avformat_open_input(&pInFormatCtx, inputFile, NULL, NULL) != 0)
    {
        av_strerror(1, errbuf, sizeof(errbuf));
        printf("无法打开输入文件:%s\n", errbuf);
        return;
    }
    // 获取输入文件信息
    if (avformat_find_stream_info(pInFormatCtx, NULL) < 0)
    {
        av_strerror(1, errbuf, sizeof(errbuf));
        printf("无法获取输入文件信息:%s\n", errbuf);
        return;
    }
    // 创建输出文件上下文
    if (avformat_alloc_output_context2(&pOutFormatCtx, NULL, NULL, outputFile) < 0)
    {
        printf("无法创建输出文件上下文\n");
        return;
    }
    // 查找并复制流
    int *streamMap = (int *)av_calloc(pInFormatCtx->nb_streams, sizeof(int)); // 分配内存
    if (!streamMap)
        return;
    int index = 0;
    for (int i = 0; i < pInFormatCtx->nb_streams; i++)
    {
        AVStream *inStream = pInFormatCtx->streams[i];
        if (inStream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO && inStream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO && inStream->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE)
        {
            streamMap[i] = -1; // 不是视频流和音频流和字幕流,不复制
            continue;
        }
        streamMap[i] = index++;

        AVStream *outStream = avformat_new_stream(pOutFormatCtx, NULL);
        if (!outStream)
            return;
        if (avcodec_parameters_copy(outStream->codecpar, inStream->codecpar) < 0)
        {
            printf("无法复制输入流参数到输出流\n");
            return;
        }
        outStream->codecpar->codec_tag = 0; // 设置输出流标签
    }
    // 打开输出文件
    if (avio_open(&pOutFormatCtx->pb, outputFile, AVIO_FLAG_WRITE) < 0)
    {
        printf("无法打开输出文件\n");
        return;
    }
    // 写文件头
    if (avformat_write_header(pOutFormatCtx, NULL) < 0)
    {
        printf("无法写入文件头\n");
        return;
    }
    // 写入数据
    AVPacket packet; // 数据包
    while (av_read_frame(pInFormatCtx, &packet) >= 0)
    {
        if (streamMap[packet.stream_index] == -1)
        {
            continue; // 不复制该流
        }

        AVStream *pInStream = pInFormatCtx->streams[packet.stream_index];           // 输入流
        packet.stream_index = streamMap[packet.stream_index];                       // 设置输出流索引
        AVStream *pOutStream = pOutFormatCtx->streams[packet.stream_index];         // 输出流
        av_packet_rescale_ts(&packet, pInStream->time_base, pOutStream->time_base); // 时间基转换
        av_interleaved_write_frame(pOutFormatCtx, &packet);                         // 写入数据包
        av_packet_unref(&packet);                                                   // 释放数据包
    }
    // 写文件尾
    if (av_write_trailer(pOutFormatCtx) < 0)
    {
        printf("无法写入文件尾\n");
        return;
    }
    // 关闭文件
    avformat_close_input(&pInFormatCtx);
    avformat_close_input(&pOutFormatCtx);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于LSTM长短期记忆神经网络的多分类预测【MATLAB】

在深度学习中&#xff0c;长短期记忆网络&#xff08;LSTM, Long Short-Term Memory&#xff09;是一种强大的循环神经网络&#xff08;RNN&#xff09;变体&#xff0c;专门为解决序列数据中的长距离依赖问题而设计。LSTM因其强大的记忆能力&#xff0c;广泛应用于自然语言处理…

在Excel中如果制作可以自动填充的序号,删除或者合并单元也可用

大家好&#xff0c;我是小鱼。在日常的办公中有时需要制作带序号的表格&#xff0c;这样可以通过序号来直观地看到有多少条信息&#xff0c;但是如果普通的批量添加序号的话&#xff0c;一旦我们删除或者合并某几行数据&#xff0c;前面的序号不会自动更新&#xff0c;序号显示…

从ESP8266编程到树莓派Pico集成:实现手机APP控制LED灯

物联网(IoT)技术正广泛应用于智能家居领域,通过WiFi模块(如ESP8266)连接嵌入式开发板(如树莓派Pico),可实现设备远程控制。本文将从ESP8266模块的固件编程开始,直至其与树莓派Pico集成,完成通过手机APP远程控制LED灯的功能实现。 一、项目目标 通过手机APP与ESP8266…

【机器学习】分而知变,积而见道:微积分中的世界之思

文章目录 微积分基础&#xff1a;理解变化与累积的数学前言一、微积分概述与基础概念1.1 微积分的定义与重要性1.1.1 微积分的基本组成1.1.2 微积分在机器学习中的应用 1.2 微积分的历史与发展 二、极限与连续性2.1 极限的定义与计算2.1.1 极限的直观理解2.1.2 极限的数学定义2…

【嵌入式开发笔记】OpenOCD到嵌入式调试

最近在把玩一块Risc-V的开发板&#xff0c;使用开发板调试时&#xff0c;需要用到专门的下载器和OpenOCD进行调试。 为了连接这个板子&#xff0c;费了九牛二虎之力。 这里简单记录一下自己的折腾经过吧。 0x00 环境准备 0x0001 调试背景 系统&#xff1a;Virtual Box Ub…

知网研学 | 知网文献(CAJ+PDF)批量下载

知网文献&#xff08;CAJPDF&#xff09;批量下载 一、知网研学安装二、插件及脚本安装三、CAJ批量下载四、脚本下载及PDF批量下载浏览器取消拦截窗口 一、知网研学安装 批量下载知网文件&#xff0c;格式为es6文件&#xff0c;需使用知网研学软件打开&#xff0c;故需先安装该…

ScottPlot学习的常用笔记-02

ScottPlot学习的常用笔记-02 写在前面Why&Target&#xff1a;这里记一些杂项。上下文&背景 先记一下这几个小时的新收获先说一下&#xff0c;为什么可开发可视工具缩放的问题ScottPlot5.0起步.net Core: WinExe.Net Framework也是可以的 写在前面 Why&Target&…

uniapp使用腾讯地图接口的时候提示此key每秒请求量已达到上限或者提示此key每日调用量已达到上限问题解决

要在创建的key上添加配额 点击配额之后进入分配页面&#xff0c;分配完之后刷新uniapp就可以调用成功了。

【Harmony Next】多个图文配合解释DevEco Studio工程中,如何配置App相关内容,一次解决多个问题?

解决App配置相关问题列表 1、Harmony Next如何配置图标&#xff1f; 2、Harmony Next如何配置App名称&#xff1f; 3、Harmony Next如何配置版本号&#xff1f; 4、Harmony Next如何配置Bundle ID? 5、Harmony Next如何配置build号&#xff1f; 6、Harmony Next多语言配置在哪…

vue3实现打印table订单表格

废话少说&#xff0c;直接上代码&#xff01; /utils/commonFunction.ts 把数字转换成繁体中文封装函数 export default function () {// 把数字转换成繁体中文function convertCurrency(money: any) {//汉字的数字var cnNums new Array("零","壹",&quo…

VSCode:Markdown插件安装使用 -- 最简洁的VSCode中Markdown插件安装使用

VSCode&#xff1a;Markdown插件安装使用 1.安装Marktext2.使用Marktext 本文&#xff0c;将在Visual Studio Code中&#xff0c;安装和使用Markdown插件&#xff0c;以Marktext插件为例。 1.安装Marktext 打开VSCode&#xff0c;侧边栏中找到扩展模块(或CtrlShiftX快捷键)&am…

五十个网络安全学习项目——(九)无线网络安全分析

五十个网络安全学习项目——&#xff08;九&#xff09;无线网络安全分析 这个系列灵感来源是&#xff1a;50个网络安全项目创意&#xff1a;覆盖新手至专家级&#xff0c;本人打算把这些项目都做一遍&#xff0c;做好记录&#xff0c;也算是对自己的提升。 本文将对WAPI 协议…

Sigrity System Explorer Snip Via Pattern From Layout模式从其它设计中截取过孔模型和仿真分析操作指导

Sigrity System Explorer Snip Via Pattern From Layout模式从其它设计中截取过孔模型和仿真分析操作指导 Sigrity System Explorer Snip Via Pattern From Layout模式支持从其它设计中截取过孔模型用于仿真分析,同样以差分模板为例 具体操作如下 双击打开System Explorer软件…

【Java基础面试题025】什么是Java的Integer缓存池?

回答重点 Java的Integer缓存池&#xff08;Integer Cache&#xff09;是为了提升性能和节省内存。根据实践发现大部分的数据操作都集中在值比较小的范围&#xff0c;因此缓存这些对象可以减少内存分配和垃圾回收的负担&#xff0c;提升性能 在 -128到127范围内的Integer对象会…

AI广告爆发元年,心动网络能否成下一个Applovin?

如果说2023年标志着AI大模型技术的崛起&#xff0c;那么2024年无疑是AI广告应用爆发的元年。 大洋彼岸的Applovin凭借着智能广告分发引擎完成彻底翻身&#xff0c;股价上涨超过30倍。一跃成为AI领域乃至整个美股市场的明星。 与此同时&#xff0c;心动网络作为同样深耕于游戏…

基于Python3编写的Golang程序多平台交叉编译自动化脚本

import argparse import os import shutil import sys from shutil import copy2from loguru import loggerclass GoBuild:"""一个用于构建跨平台执行文件的类。初始化函数&#xff0c;设置构建的主文件、生成的执行文件名称以及目标平台。:param f: 需要构建的…

java全栈day20--Web后端实战(Mybatis基础2)

一、Mybatis基础 1.1辅助配置 配置 SQL 提示。 默认在 mybatis 中编写 SQL 语句是不识别的。可以做如下配置&#xff1a; 现在就有sql提示了 新的问题 产生原因&#xff1a; Idea 和数据库没有建立连接&#xff0c;不识别表信息 解决方式&#xff1a;在 Idea 中配置 MySQL 数…

MacOS下PostIn安装配置指南

PostIn是一款开源免费的接口管理工具&#xff0c; 下面介绍私有部署版本的MacOS下安装与配置。私有部署版本更适合有严格数据安全要求的企业&#xff0c;实现对数据和系统的完全控制。 &#xfeff; &#xfeff; 1、MacOS服务端安装 Mac安装包下载地址&#xff1a;下载Mac安…

【Apache Doris】周FAQ集锦:第 26 期

SQL问题 Q1 doris 3.0存算分离模式下&#xff0c;建表的时是否需要指定表的副本数 不需要&#xff0c;指定了也会忽略&#xff1b;存算分离模式下&#xff0c;数据副本由远端存储去管控。 Q2 doris 通过dbeaver查询时报错&#xff1a;[SXXXX]… doris的错误码通常都是EXXXX&…

【Mongo工具】Mongo迁移工具之Mongo-shake

Mongo-Shake 简介 Mongo-Shake 是一个基于 MongoDB 操作日志&#xff08;oplog&#xff09;的通用服务平台。它从源 MongoDB 数据库中获取操作日志&#xff0c;并在目标 MongoDB 数据库中重放&#xff0c;或者通过不同的隧道发送到其他终端。如果目标端是 MongoDB 数据库&…