当前位置: 首页 > news >正文

FFmpeg 框架简介和文件解复用

文章目录

  • 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-8char *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(); // 分配一个packetif (!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);
}

相关文章:

FFmpeg 框架简介和文件解复用

文章目录 ffmpeg框架简介libavformat库libavcodec库libavdevice库 复用&#xff08;muxers&#xff09;和解复用&#xff08;demuxers&#xff09;容器格式FLVScript Tag Data结构&#xff08;脚本类型、帧类型&#xff09;Audio Tag Data结构&#xff08;音频Tag&#xff09;V…...

《Java核心技术I》Swing中的边框

边框 BorderFactory静态方法创建边框&#xff0c;凹斜面&#xff0c;凸斜面&#xff0c;蚀刻&#xff0c;直线&#xff0c;蒙版&#xff0c;空白。 边框添加标题&#xff0c;BorderFactory.createTitledBorder 组合边框&#xff0c;BorderFactory.createCompoundBorder JCo…...

MySQL 中的常见错误与排查

在 MySQL 数据库的日常运维中&#xff0c;管理员可能会遇到各种错误。无论是查询性能问题、连接异常、数据一致性问题&#xff0c;还是磁盘空间不足等&#xff0c;及时排查并解决这些问题是保证数据库稳定运行的关键。本文将列出 MySQL 中一些常见的错误及其排查方法。 一、连接…...

SQL 查询方式比较:子查询与自连接

在 SQL 中&#xff0c;子查询和自连接是两种常见的查询方式&#xff0c;它们的功能虽然可以相同&#xff0c;但实现的方式不同。本文通过具体示例&#xff0c;深入探讨这两种查询方式&#xff0c;并配合数据展示&#xff0c;帮助大家理解它们的使用场景和差异。 数据示例 假设…...

Linux下学【MySQL】所有常用类型详解( 配实操图 通俗易懂 )

每日激励&#xff1a;“当你觉得你会幸运时&#xff0c;幸运就会眷顾你&#xff0c;所以努力吧&#xff0c;只要你把事情做好&#xff0c;并觉得你会幸运&#xff0c;你将会变得幸运且充实。” 绪论​&#xff1a; 本章继续学习MySQL的知识&#xff0c;本章主要讲到mysql中的所…...

Gin-vue-admin(1):环境配置和安装

目录 环境配置如果443网络连接问题&#xff0c;需要添加代理服务器 后端运行前端运行 环境配置 git clone https://gitcode.com/gh_mirrors/gi/gin-vue-admin.git到server文件目录下 go mod tidygo mod tidy 是 Go 语言模块系统中的一个命令&#xff0c;用于维护 go.mod 文件…...

如何在centos系统上挂载U盘

在CentOS上挂载NTFS格式的U盘,需要执行一系列步骤,包括识别U盘设备、安装必要的软件、创建挂载点,并最终挂载U盘。以下是在CentOS上挂载NTFS格式U盘的详细步骤: 一、准备工作 确认CentOS版本: 确保你的CentOS系统已经安装并正常运行。不同版本的CentOS在命令和工具方面可能…...

2024年12月大语言模型最新对比:GPT-4、Claude 3、文心一言等详细评测

前言 随着人工智能技术的快速发展&#xff0c;大语言模型(LLM)已经成为了技术领域最热门的话题。本文将详细对比目前主流的大语言模型&#xff0c;帮助大家选择最适合的工具。 一、OpenAI GPT系列 1. GPT-4 核心优势&#xff1a; 多模态理解能力强 逻辑推理能力出色 创造…...

openjdk17 从C++视角看 String的intern的jni方法JVM_InternString方法被gcc编译器连接

symbols-unix 文件部分内容 JVM_IHashCode JVM_InitClassName JVM_InitStackTraceElement JVM_InitStackTraceElementArray JVM_InitializeFromArchive JVM_InternString 要理解在 symbols-unix 文件中包含 JVM_InternString 方法的原因&#xff0c;我们需要从构建过程、符号…...

day16 python(4)——UnitTest

【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、UnitTest框架介绍 1.1 UnitTest框架 1.2 unitTest的组成&#xff08;5部分&#xff09; 1.2.1 TestCase&#xff08;测试用例&#xff09; 1.2.2 TestSuit 和 TestRunner 【方法1】 【方法2】 1…...

Kafka快速扫描

Architecture 系统间解耦&#xff0c;异步通信&#xff0c;削峰填谷 Topic 消息主题&#xff0c;用于存储消息 Partition 分区&#xff0c;通过扩大分区&#xff0c;可以提高存储量 Broker 部署Kafka服务的设备 Leader kafka主分区 Follwer kafka从分区 高性能之道&#xff1a…...

python打包时候遇到问题:ImportError: DLL load failed while importing _ufuncs: 找不到指定的模块

问题&#xff1a;python打包时候遇到问题&#xff1a;ImportError: DLL load failed while importing _ufuncs: 找不到指定的模块 解决方法 pip uninstall scipy pip install scipy总结&#xff1a;卸载出问题的库并重新安装&#xff0c;再次通过pyinstaller -F -w xxx.py 打包…...

【6】期末复习C#第6套

1.两个指针变量不能 2.函数可以有也可以没有形参 3.开始执行点是程序中的main函数 4.调用函数时形参和实参各占一个独立的存储单元 5.给指针赋NULL值和地址值 6.函数头和函数体 7.&#xff08;C&#xff09;形式参数是局部变量 8.在C语言中&#xff0c;一维数组的定义方…...

开源轮子 - EasyExcel01(核心api)

EasyExcel01 - 核心api 本文整理自掘金大佬 - 竹子爱熊猫 https://juejin.cn/post/7405158045662576640 文章目录 EasyExcel01 - 核心api一&#xff1a;初相识EasyExcel1&#xff1a;写入excel入门2&#xff1a;读取Excel入门 二&#xff1a;数据模型注解1&#xff1a;读写通用…...

SpringBoot3+Vue3开发在线考试系统

项目介绍 项目分为3种角色&#xff0c;分别为&#xff1a;超级管理员、老师、学生。超级管理员&#xff0c;负责系统的设置、角色的创建、菜单的管理、老师的管理等功能&#xff0c;也可以叫做系统管理员&#xff1b;老师角色&#xff0c;负责系统业务的管理&#xff0c;包括学…...

2. Kafka入门-开发环境准备

Kafka入门-开发环境准备 1. 环境准备2. Centos7安装2.1 镜像安装2.2 初始化配置2.3 JDK1.8安装 ---------------------------------------------------------------------------------------------- 1. 环境准备 2. Centos7安装 2.1 镜像安装 2.2 初始化配置 设置系统时区 …...

Halcon中histo_2dim(Operator)算子原理及应用详解

在Halcon中&#xff0c;histo_2dim算子是一个用于计算双通道灰度值图像的直方图的工具。以下是对该算子的原理及应用的详细解释&#xff1a; 一、原理 histo_2dim算子的函数原型为&#xff1a;histo_2dim(Regions, ImageCol, ImageRow : Histo2Dim : : )。 输入参数&#xff…...

TCP 与 UDP

TCP与UDP的区别分析 TCP&#xff08;传输控制协议&#xff09;和UDP&#xff08;用户数据报协议&#xff09;是互联网协议族&#xff08;TCP/IP&#xff09;中的两种重要传输层协议。它们在数据传输的方式、特性以及应用场景方面存在显著差异。以下将从多个方面详细分析TCP与U…...

ubuntu 安装更新 ollama新版本

ubuntu 安装更新 ollama新版本 我这里是 2024-12-18 ollama 版本是 0.5.3 1手动下载 ollama-linux-amd64.tgz https://github.com/ollama/ollama/releases 2下载脚本 https://ollama.com/install.sh install.sh 和 ollama-linux-amd64.tgz 在相同路径下 修改&#xff1a;…...

Numpy基本操作

目录 1、生成数组的方法 1.1、生成0和1的数组 1.2、从现有数组生成 1.2.1、生成方式 1.3、生成固定范围的数组 1.4、生成随机数组 1.4.1、使用模块介绍 1.4.2、均匀分布 1.4.3、正态分布 1.4.4、正态分布创建方式 1、生成数组的方法 1.1、生成0和1的数组 import numpy…...

本地部署webrtc应用怎么把http协议改成https协议?

环境&#xff1a; WSL2 Ubuntu22.04 webrtc视频聊天应用 问题描述&#xff1a; 本地部署webrtc应用怎么把http协议改成https协议&#xff1f; http协议在安卓手机浏览器上用不了麦克风本&#xff0c;来地应用webrtc 本来是http协议&#xff0c;在安卓手机上浏览器不支持使…...

基于xiaothink对Wanyv-50M模型进行c-eval评估

使用pypi安装xiaothink&#xff1a; pip install xiaothink1.0.2下载模型&#xff1a; 万语-50M 开始评估(修改模型路径后即可直接开始运行&#xff0c;结果保存在output文件夹里)&#xff1a; import os import json import pandas as pd import re from tqdm import tqdm i…...

使用k6进行kafka负载测试

1.安装环境 kafka环境 参考Docker搭建kafka环境-CSDN博客 xk6-kafka环境 ./xk6 build --with github.com/mostafa/xk6-kafkalatest 查看安装情况 2.编写脚本 test_kafka.js // Either import the module object import * as kafka from "k6/x/kafka";// Or in…...

Unity A*算法实现+演示

注意&#xff1a; 本文是对基于下方文章链接的理论&#xff0c;并最终代码实现&#xff0c;感谢作者大大的描述&#xff0c;非常详细&#xff0c;流程稍微做了些改动&#xff0c;文末有工程网盘链接&#xff0c;感兴趣的可以下载。 A*算法详解(个人认为最详细,最通俗易懂的一…...

浏览器要求用户确认 Cookies Privacy(隐私相关内容)是基于隐私法规的要求,VUE 实现,html 代码

Cookie Notices and Cookie Consent | Cookiepedia 1. 法律法规要求 许多国家和地区的隐私法律要求网站在存储或处理用户数据&#xff08;包括 Cookies&#xff09;之前必须获得用户的明确同意&#xff1a; GDPR&#xff08;欧盟通用数据保护条例&#xff09; 要求&#xff…...

如何设计高效的商品系统并提升扩展性:从架构到实践的全方位探索

在现代电商、零售及企业资源管理系统中&#xff0c;商品管理无疑是核心模块之一。随着市场的变化与企业规模的扩展&#xff0c;商品系统需要具备强大的功能支持以及高效的扩展能力&#xff0c;以应对日益复杂的业务需求。一个设计良好的商品系统不仅仅是一个商品信息的容器&…...

使用计算机创建一个虚拟世界

创建一个虚拟世界是一项复杂而多方面的工作&#xff0c;它涉及多个领域的知识&#xff0c;包括计算机图形学、编程、物理模拟、声音设计、艺术设计等。以下是创建虚拟世界的基本步骤和工具建议&#xff1a; 1. 确定虚拟世界的目标和范围 目标&#xff1a;明确这个虚拟世界的用…...

datasets笔记:两种数据集对象

Datasets 提供两种数据集对象&#xff1a;Dataset 和 ✨ IterableDataset ✨。 Dataset 提供快速随机访问数据集中的行&#xff0c;并支持内存映射&#xff0c;因此即使加载大型数据集也只需较少的内存。IterableDataset 适用于超大数据集&#xff0c;甚至无法完全下载到磁盘或…...

【ETCD】【Linearizable Read OR Serializable Read】ETCD 数据读取:强一致性 vs 高性能,选择最适合的读取模式

ETCD 提供了两种不同类型的读取操作方式&#xff0c;分别是 Linearizable Read&#xff08;线性化读取&#xff09;和 Serializable Read&#xff08;可串行化读取&#xff09;。这两种方式主要区分在读取数据时对一致性的要求不同。 目录 1. Linearizable Read&#xff08;线…...

【CSS in Depth 2 精译_089】15.2:CSS 过渡特效中的定时函数

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第五部分 添加动效 ✔️【第 15 章 过渡】 ✔️ 15.1 状态间的由此及彼15.2 定时函数 ✔️ 15.2.1 定制贝塞尔曲线 ✔️15.2.2 阶跃 ✔️ 15.3 非动画属性 文章目录 15.2 定时函数 Timing function…...