FFmpeg 框架简介和文件解复用
文章目录
- ffmpeg框架简介
- libavformat库
- libavcodec库
- libavdevice库
- 复用(muxers)和解复用(demuxers)
- 容器格式
- FLV
- Script Tag Data结构(脚本类型、帧类型)
- Audio Tag Data结构(音频Tag)
- Video Tag Data结构(视频Tag)
- MP4
- Box结构如图所示:
- 整体结构
- 文件解复用
- 文件复用
- 项目实战
- 抽取音频数据
- 抽取视频
- 容器格式转换
ffmpeg框架简介
八大库:
- libavformat:复用和解复用,格式封装
- libavcodec:编码、解码
- libavutil:通用音视频工具,像素、IO、时间等工具
- iibavfilter:过滤器,可以用作音视频特效处理
- libavdevice:设备(摄像头、麦克风)
- libswscale:视频图像缩放,像素格式互换
- libswresample:重采样
- 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包含:
- 输入或输出格式。它是自动检测或由用户设置输入。输出总是由用户设置。
- AVSteams的数组,它描述了存储在文件中的所有基本流。AVStreams通常在这个数组中使用它们的索引来引用。
- 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在编码/解码和音频/视频方面非常相似,工作方式如下:
- 像往常一样设置并打开AVCodecContext
- 发送有效的输入:
- 对于解码,调用avcodec_send_packet()以向解码器提供包含原始压缩数据的AVPacket
- 对于编码,调用avcodec_send_frame()以向解码器提供包含未压缩音频或视频的AVFrame
在两种情况下,建议对AVPackets和AVFrames进行引用计数,否则libavcodec可能需要复制输入数据。(libavformat总是返回引用计数的AVPackets,av_frame_get_buffer()分配引用计数的AVFrames)
- 在循环接收输出,定期调用avcodec_receive_*()函数并处理其输出:
- 对于解码,调用avcodec_receive_frame(),成功时,它降返回一个包含未压缩音频或视频数据的AVFrame
- 对于编码,调用avcodec_receive_packet(),成功时,它将返回一个带有压缩帧的AVPacket
重复此调用,直到返回AVERROR(EAGAIN)或错误,AVERROR(EAGAIN)返回值表示需要新的输入数据以生成新的输出。在这种情况下,继续发送输入,对于每个输入帧/包,编解码器通常会返回1个输出帧/包,但也可以是0或多于1个。
在解码或者编码开始时,编解码器可能接受多个输入帧./包而不返回帧,直到其内部缓冲区填满。如果按照上述步骤操作,这种情况会被透明处理。
理论上,发送输入可能导致EAGAIN,这只有在没有接收到全部输出时才会发生。您可以利用这一点来构建除了上面建议的循环之外的其他解码或编码循环。例如,您可以尝试在每次迭代中发送新的输入,并在返回EAGAIN时尝试接收输出。
流结束的情况需要对编码器进行"flush"(也成为draining),因为编解码器可能会在内部缓冲多个帧或包以提高性能或出于必要性(考虑B帧)。处理方式如下:
- 而不是提供有效的输入,向avcodec_send_packet()解码或avcodec_send_frame()编码函数发送NULL,这将进入排空模式
- 在循环中调用avcodec_receive_frame()解码或avcodec_receive_packet()编码,直到返回AVERROR_EOF。这些函数不会返回AVERROR(EAGAIN),除非您忘记进入排空模式。
- 在解码可以再次开始之前,必须使用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 | 视频编码ID | 7.000(H264为7) |
audiodatarate | 音频码率 | 29.329 |
audiosamplerate | 音频采样率 | 44100.000 |
stereo | 是否立体声 | 1 |
audiocodecid | 音频编码ID | 10.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位数值表示了音频编码类型
值 | 含义 |
---|---|
0 | Linear PCM,platform endian |
1 | ADPCM |
2 | MP3 |
3 | Linear PCM,little endian |
4 | Neltymoser 16-kHz mono |
5 | Neltymoser 8-kHz mono |
6 | Neltymoser |
7 | G.711 A-law logarithmic PCM |
8 | G.711 mu-law logarithmic PCM |
9 | reserved |
10 | AAC |
14 | MP# 8-kHz |
15 | Device-specific sound |
第1个字节的第5-6位的数值表示音频采样率。
值 | 含义 |
---|---|
0 | 5.5kHz |
1 | 11kHz |
2 | 22kHz |
3 | 44kHz |
从上表可以发现FLV封装格式并不支持48kHz的采样率
第1个字节的第7为表示音频采样精度
值 | 含义 |
---|---|
0 | 8bits |
1 | 16bits |
第1个字节的第8位表示音频类型
值 | 含义 |
---|---|
0 | sndMono |
1 | sndStereo |
Video Tag Data结构(视频Tag)
视频Tag也用开始的第1个字节包含视频数据的参数信息,从第2个字节为视频流数据
第1个字节的前4位的数值表示帧类型
值 | 含义 |
---|---|
1 | keyframe ( for AVC, a seekable frame ) |
2 | inter frame ( for AVC, a nonseekable frame ) |
3 | disposable inter frame ( H.263 only ) |
4 | generated keyframe ( reserved for server use ) |
5 | video info/command frame |
第1个字节的后4位数值表示视频编码类型
值 | 含义 |
---|---|
1 | JPEG ( currently unused ) |
2 | Sorenson H.263 |
3 | Screen video |
4 | On2 VP6 |
5 | On2 VP6 with alpha channel |
6 | Screen video version 2 |
7 | AVC |
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中,这个过程由以下函数表示:
- avformat_open_input()用于打开文件。
- av_read_frame()用于读取单个数据包。
- 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_size和pixel_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库 复用(muxers)和解复用(demuxers)容器格式FLVScript Tag Data结构(脚本类型、帧类型)Audio Tag Data结构(音频Tag)V…...
观察者模式(sigslot in C++)
大家,我是东风,今天抽点时间整理一下我很久前关注的一个不错的库,可以支持我们在使用标准C的时候使用信号槽机制进行观察者模式设计,sigslot 官网: http://sigslot.sourceforge.net/ 本文较为详尽探讨了一种观察者模…...
git企业开发的相关理论(二)
目录 git企业开发的相关理论(一) 八.修改文件 九.版本回退 十.撤销修改 情况一(还没有add) 情况二(add后还没有commit) 情况三(commit后还没有push) 十一.删除本地仓库中的文件 方法一 方法二 十二.理解分支 1.常见的分支工作流程 2.合并冲…...
力扣-图论-70【算法学习day.70】
前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程(例如想要掌握基础用法,该刷哪些题?)我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非…...
jmeter中的prev对象
在jmeter中通过beanshell、JSR223的各种处理器编写脚本时,都会看到页面上有这样的说明 这些ctx、vars、props、OUT、sampler、prev等等都是可以直接在脚本中使用的对象,由jmeter抛出 今天主要讲一下prev的使用 SampleResult prev jmctx.getPreviousRe…...
机器学习中的密度聚类算法:深入解析与应用
在机器学习的广阔领域中,聚类算法作为一种无监督学习方法,扮演着至关重要的角色。其中,密度聚类算法以其独特的优势,在数据挖掘、图像分割、市场细分等多个领域得到了广泛应用。 一、密度聚类算法的基本原理 密度聚类算法是一种…...
简单分析一下 a,b,c=a+1,a+1,b+1 执行原理
在 Go 语言中,赋值表达式 a, b, c x, y, z 是同时进行的,但是其计算顺序是从左到右依次进行的。即在 a, b, c 被赋值之前,先计算 x, y, z 的值,并依次将它们赋值给 a, b, c。 例如:a, b, c a1, a1, b1,其…...
2025年前端面试热门题目——HTML|CSS|Javascript|TS知识
以下是对这些 HTML 面试问题的详细解答: 1. HTML 的 src 和 href 属性有什么区别? src (Source) 属性: 用于嵌入资源,例如图像、脚本或 iframe。加载资源时,当前页面的加载会暂停,直到资源加载完成。常用于 <img&g…...
将4G太阳能无线监控的视频接入电子监控大屏,要考虑哪些方面?
随着科技的飞速发展,4G太阳能无线监控系统以其独特的优势在远程监控领域脱颖而出。这种系统结合了太阳能供电的环保特性和4G无线传输的便捷性,为各种环境尤其是无电或电网不稳定的地区提供了一种高效、可靠的视频监控解决方案。将这些视频流接入大屏显示…...
【102. 二叉树的层序遍历 中等】
题目: 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]] 示例…...
文件包含tomato靶机通关
靶机地址:192.168.152.152 注:靶机打开后在 kali 中扫描一下就能得到 打开网站 第一步信息收集 将网址放到 dirb 中扫描一下 得到了三个目录 我们挨个访问一下 第一个是主目录 第二个是主页面 第三个报错 第二步 我们在主目录页面继续访问 我们进行…...
oracle dblink 的创建及使用
Oracle Database Link(DB Link)是Oracle提供的一种功能,允许你在一个数据库中直接访问另一个远程或本地数据库的对象(如表、视图、序列等)。DB Link的设置简化了跨数据库操作,使得数据的集成和同步变得更加…...
java开发入门学习五-流程控制
流程控制语句 if, if...else, if..else if..else 与前端相同 略 switch case 与前端不同的是case不能使用表达式,使用表达式会报错 class TestSwitch {public static void main(String[] args) {// switch 表达式只能是特定的数据类型…...
【蓝桥杯——物联网设计与开发】拓展模块3 - 温度传感器模块
一、温度传感器模块 (1)资源介绍 🔅原理图 蓝桥杯物联网竞赛实训平台提供了一个拓展接口 CN2,所有拓展模块均可直接安装在 Lora 终端上使用; 图1 拓展接口 温度传感器模块电路原理图如下所示: 图2 …...
Zookeeper 底层原理解析
一、引言 在分布式系统的浩瀚星空中,Zookeeper 宛如一颗最为闪耀的导航星,为众多分布式应用指引方向、保驾护航。无论是大名鼎鼎的 Hadoop、HBase,还是其他各类复杂的分布式架构,Zookeeper 都扮演着不可或缺的关键角色。它如同一…...
面试题整理9----谈谈对k8s的理解1
谈谈对k8s的理解 1. Kubernetes 概念 1.1 Kubernetes是什么 Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,方便进行声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态系统,其服务、支持和工具的…...
PromptGIP:Unifying lmage Processing as Visual Prompting Question Answering
“Unifying Image Processing as Visual Prompting Question Answering” 文章提出了一种名为 PromptGIP 的通用模型,将图像处理任务统一为视觉提示问答范式,在多个图像处理任务上展现出良好性能,为通用图像处理提供了新的思路和方法。 confe…...
chart文件结构
在 Helm 中,Chart 是一个用于定义、安装和升级 Kubernetes 应用程序的包。Chart 文件结构遵循一定的目录和文件组织方式,以下是典型的 Helm Chart 文件结构: 1. Chart 文件结构示例 mychart/ ├── Chart.yaml # 描述 Chart 的基…...
SQL优化
SQL优化 插入数据 insert优化 批量插入 insert into tb_test 2values(1, Tom), (2, Cat), (3, jerry); 手动提交事务 start transaction; insert into test1 values(4, Tom), (5, Cat), (6, jerry); insert into test1 values(7, Tom), (8, Cat), (9, jerry); insert int…...
输出1-100之间的随机数,控制输出格式,每行10个(注释有详解)
C 随机数生成与格式化输出 在编程中,随机数的生成是一个常见的需求,尤其是在游戏开发、模拟实验和数据分析等领域。本文将通过一个简单的 C 程序来演示如何生成随机数并进行格式化输出。我们将逐步解析代码,并讨论其工作原理及应用场景。 代…...
【数字化】华为数字化转型架构蓝图-2
目录 1、客户联结的架构思路 1.1 ROADS体验设计 1.2 具体应用场景 1.3 统一的数据底座 1.4 案例与成效 2、一线作战平台的架构思路 2.1 核心要素 2.2 关键功能 2.3 实施路径 2.4 案例与成效 3、能力数字化的架构思路 3.1 能力数字化的核心目标 3.2 能力数字化的实…...
MyBatis是什么?为什么有全自动ORM框架还是MyBatis比较受欢迎?
MyBatis是什么? MyBatis是一个半自动的ORM持久层框架,内部封装了JDBC,mybatis是通过XML或注解的方式将需要执行的statement配置,支持定制化sql,存储过程以及高级映射。 解释 所谓的半自动ORM意思就是将JDBC的工作交…...
基础元器件的学习
1、二极管 1.1二极管的符号 ZD是稳压二极管 VD、V、D是普通二极管的符号。 1.2二极管的反向恢复时间 首先交流电为上正下负,然后下正上负。当二极管接到反向电压,二极管存在寄生电容,电压不能立刻突变,当输入频率变高时&#…...
GTID下复制问题和解决
环境介绍 数据库1主2从,mysql版本是v5.19 表结构 一、主库新增记录,从库提示主键冲突 模拟故障 1, master上关闭 sql_log_bin,删除id 103 后打开 2, 确认此时从库有id103,主库没有 3, master insert id103 主从异常…...
Linux 下的 GPT 和 MBR 分区表详解
文章目录 Linux 下的 GPT 和 MBR 分区表详解一、分区表的作用二、MBR(Master Boot Record)1. **特点**2. **优点**3. **缺点**4. **适用场景** 三、GPT(GUID Partition Table)1. **特点**2. **优点**3. **缺点**4. **适用场景** 四…...
mysql的事务控制和数据库的备份和恢复
事务控制语句 行锁和死锁 行锁 两个客户端同时对同一索引行进行操作 客户端1正常运行 客户端2想修改,被锁行 除非将事务提交才能继续运行 死锁 客户端1删除第5行 客户端2设置第1行为排他锁 客户端1删除行1被锁 客户端2更新行5被锁 如何避免死锁 mysql的备份和还…...
2014年IMO第4题
△ A B C \triangle ABC △ABC 中, B C BC BC 上有一点 P P P 满足 ∠ B A P = ∠ A C B \angle BAP=\angle ACB ∠BAP=∠ACB, 还有一点 Q Q Q 满足 ∠ A = Q A C = ∠ A B C \angle A=QAC=\angle ABC ∠A=QAC=∠ABC. 分别延长 A P AP AP, A Q AQ AQ 一倍至 M M M, N …...
如何实现层叠布局
文章目录 1 概念介绍2 使用方法3 示例代码我们在上一章回中介绍了GirdView Widget,本章回中将介绍Stack这种Widget,闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 在Flutter中Stack主要用来叠加显示其它的Widget,类似我们日常生活中的楼层或者说PS中的图层,因此它也是一…...
Qwen2.5-7B-Instruct Lora微调
Qwen2.5-7B-Instruct Lora 微调 本文简要介绍如何基于 transformers、peft 等框架,对 Qwen2.5-7B-Instruct 模型进行 Lora 微调。Lora 是一种高效微调方法。 环境配置 在完成基本环境配置和本地模型部署的情况下,你还需要安装一些第三方库,…...
MacOS安装MySQL
官网下载MySQL 苹果芯片选择ARM版本 安装过程中会要求你输入root的密码(不少于8位),这里设置为12345678 打开系统设置查看是否成功安装MySQL 配置MySQL环境变量 vi ~/.zshrc加入一行export PATH$PATH:/usr/local/mysql/bin 执行source ~/…...
基础库正则表达式
我们已经可以用requests 库来获取网页的源代码,得到 HTML 代码。但我们真正想要的数据是包含在 HTML代码之中的,要怎样才能从 HTML,代码中获取想要的信息呢?正则表达式就是其中一个有效的方法。 本篇博客我们将了解一下正则表达式的相关用法。正则表达…...
Matlab 和 R 语言的数组索引都是从 1 开始,并且是左闭右闭的
文章目录 一、前言二、主要内容三、小结 🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一、前言 在早期的计算机科学中,数组索引从 1 开始是很常见的。例如,Fortran 和 Pascal 等编程语言也采用了从 1 开始的索引。 这种索引…...
选择排序和冒泡排序;MySQL架构
1. 选择排序和冒泡排序 (1)选择排序 原理: 选择排序有升序和降序两种排序方法。升序排序的原理是:对于一个无序数列,先假定其中一个数为这个数列的最小值,然后让这个假定最小值和其他数依次比较࿰…...
蓝桥杯算法训练 黑色星期五
题目描述 有些西方人比较迷信,如果某个月的13号正好是星期五,他们就会觉得不太吉利,用古人的说法,就是“诸事不宜”。请你编写一个程序,统计出在某个特定的年份中,出现了多少次既是13号又是星期五的情形&am…...
Mybatis-Plus快速入门
参考:黑马MyBatisPlus教程全套视频教程,快速精通mybatisplus框架 1.Mapper-plus配置 1.MapperScan("Mapper目录的位置") 2.Mapper层文件需要继承BaseMapper extends BaseMapper<实体类> 3.开启日志 4.配置类 Configuration public cl…...
MySQL库的操作
目录 1. 创建数据库2. 创建数据库案例3. 认识系统编码以及字符集和校验规则4. 操纵数据库4.1 查看数据库4.2 显示创建语句4.3 修改数据库4.4 数据库的删除4.5 备份和恢复4.6 查看连接情况 1. 创建数据库 (1)语法: create database db_name;…...
JVM性能优化一:初识内存泄露-内存溢出-垃圾回收
本文主要是让你充分的认识到什么叫做内存泄露,什么叫做内存溢出,别再傻傻分不清了,别再动不动的升级服务器的内存了。 文章目录 1.基本概念1.1.内存泄露1.2.内存溢出1.3.垃圾回收1.4.内存泄露-垃圾回收-内存溢出三者的关系关系 2.代码示例2.…...
2024年山东省职业院校技能大赛网络建设与运维X86架构与ARM架构搭建赛题
完整赛题解析主页联系! 一、X86架构计算机操作系统安装与管理 1.PC1 系统为 ubuntu-desktop-amd64 系统(已安装,语言为英文),登录用户为 ubuntu,密码为Key-1122。配置ubuntu用户能免密使用sudo命令。 sud…...
flask_sqlalchemy event监听查询事件
flask_sqlalchemy event监听查询事件 在Flask-SQLAlchemy中,可以使用事件监听器来监控查询事件。这可以通过listens_for(ModelClass, “event_name”)装饰器来实现,其中ModelClass是你想要监控的模型类,event_name是你想要监控的事件名称&…...
解决vscode ssh远程连接服务器一直卡在下载 vscode server问题
目录 方法1:使用科学上网 方法2:手动下载 方法3 在使用vscode使用ssh远程连接服务器时,一直卡在下载"vscode 服务器"阶段,但MobaXterm可以正常连接服务器,大概率是网络问题,解决方法如下: 方…...
OpenAI发布全新AI模型 o3 与 o3-mini:推理与编码能力迎来重大突破. AGI 来临
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
Java --- 多线程
目录 前言: 一.线程的创建: 1.通过继承 Thread 类来创建线程: 2.通过Runnable接口创建线程: 3.通过Java8引入的lambda语法: 线程的优先级: 二.线程的生命周期: 三. 中断线程:…...
医学图像 三维重建,原图与灰度图叠加,原图与多图叠加显示;多图像融合显示,彩色灰度图像融合
Part1: Summary 我们在做图像分割或融合时,有时需要显示多份数据进行叠加显示;可能需要这种效果: 四视图: 基于这个,我看一下网上的实现总结了一下;实现了以下几种效果: Part2:多种…...
Linux中的多线程
1.Linux线程概念 什么叫做线程? 我们认为,线程操作系统调度的基本单位!重新理解进程? Linux内核观点:进程是承担分配系统资源的基本实体,即操作系统分配资源,是以进程为单位进行分配的。线程是进程内部的执行流资源…...
hive常用函数有哪些
Hive是一个基于Hadoop的数据仓库工具,它提供了类似于SQL的接口,用于数据查询和分析。Hive提供了许多内置函数,这些函数可以分为几种类型,包括: • 字符串函数:用于处理字符串数据。 • concat()࿱…...
深度学习试题及答案解析(二)
1. 神经风格转换中,优化算法的每次迭代更新的是什么? 神经风格转换(Neural Style Transfer, NST)是一种使用深度学习技术,特别是卷积神经网络(CNN),来将一幅图像的风格应用到另一幅图…...
【CSS in Depth 2 精译_089】15.2:CSS 过渡特效中的定时函数
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第五部分 添加动效 ✔️【第 15 章 过渡】 ✔️ 15.1 状态间的由此及彼15.2 定时函数 ✔️ 15.2.1 定制贝塞尔曲线 ✔️15.2.2 阶跃 ✔️ 15.3 非动画属性 文章目录 15.2 定时函数 Timing function…...
LINUX内核常用加锁
1、mutex互斥锁 互斥锁的实现主要利用到了原子变量可以锁内存总线的机制来对lock变量值进行原子修改,并通过在加锁及释放锁过程中引入内存屏障(加锁引入lfence,释放锁引入sfence),来确保锁临界区资源(Critical Section)能够在不同的CPU之间可…...
【Select 语法全解密】.NET开源ORM框架 SqlSugar 系列
系列文章目录 🎀🎀🎀 .NET开源 ORM 框架 SqlSugar 系列 🎀🎀🎀 文章目录 系列文章目录前言一、Select 执行位置二、返回一个字段和多个字段三、单表返回DTO四、多表返回DTO4.1 手动DTO4.2 实体自动映射14.…...
STM32之GPIO输出与输出
欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 一.GPIO输入1.1GPIP简介1.2GPIO基本结构1.3GPIO位结构1.4GPIO的八种模式1.4.1浮空/上拉/下拉输入1.4.2 模拟输入1.4.3 推挽输出\开漏输出 二.GPIO输入2.1.按键介绍2.2传感器模块介绍2.3按键电路 一.G…...