FFmpeg常用API与示例(二)—— 解封装与转封装

封装层

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

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

  • avformat_open_input()
  • avformat_find_stream_info()
  • av_read_frame()
  • av_write_frame()
  • av_interleaved_write_frame()
  • avio_open()
  • avformat_write_header()
  • av_write_trailer()
1.解封装

1.分配解复用器上下文 avformat_alloc_context

2.根据 url 打开本地文件或网络流 avformat_open_input

3.读取媒体的部分数据包以获取码流信息 avformat_find_stream_info

4.读取码流信息:循环处理

4.1 从文件中读取数据包 av_read_frame

4.2 定位文件 avformat_seek_file 或 av_seek_frame

5.关闭解复用器 avformat_close_input

本例子实现的是将音视频分离,例如将封装格式为 FLV、MKV、MP4、AVI 等封装格式的文件,将音频、视频读取出来并打印。

实现的过程,可以大致用如下图表示:

在这里插入图片描述

#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()
{//需要读取的本地媒体文件相对路径为video1.mp4,这里由于文件video1.mp4就在工程目录下,所以相对路径为video1.mp4const char *path = "ande_10s.mp4";//const char *path = "audio1.mp3";///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。如果媒体文件只有音频,则值为1printf("视音频流的个数:%d\n", ic->nb_streams);//媒体文件的平均码率,单位为bpsprintf("媒体文件的平均码率:%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); //音频编解码器的采样率,单位为Hzif (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();getchar(); //加上这一句,防止程序打印完信息就马上退出了return 0;
}int mainsdf3324sf(int argc, char **argv)
{// 1. 打开文件const char *ifilename = "ande_10s.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:8printf("audio codec: %d\n", audio_stream->codecpar->codec_id); // 编码格式 AV_CODEC_ID_MP3:86017 AV_CODEC_ID_AAC:86018if (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: 27if (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();//getchar(); //加上这一句,防止程序打印完信息马上退出return 0;
}
2.转封装

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

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

在这里插入图片描述

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

在这里插入图片描述

/*** @file* libavformat/libavcodec demuxing and muxing API example.** Remux streams from one container format to another.* @example remuxing.c*/#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 main234ssfdx(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;if (argc < 3) {printf("usage: %s input output\n""API example program to remux a media file with libavformat and libavcodec.\n""The output format is guessed according to the file extension.\n""\n", argv[0]);return 1;}in_filename  = argv[1];out_filename = argv[2];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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3.buuctf-rsarsa

3.buuctf-2.rsarsa 方法1&#xff1a;求d后再求明文 很明显题目里有p,q,e,c让求明文m&#xff0c;有p,q,e那么就计算一下d&#xff0c;借用一下工具 这里的e是10进制的转为16进制为10001 再用脚本求 \#p和q在工具中运算后得到n和d&#xff0c;将密文&#xff0c;n和d放入即可…

Android使用Chaquo来运行Python的librosa的相关代码【有详细案例教程】

在某些情况下&#xff0c;我们可能需要在android上运行python的代码&#xff0c;那么常见的解释器有很多&#xff0c;目前比较成熟的就是chaquo&#xff0c;它适配的第三方机器学习的库很多&#xff0c;下面是它的简单使用教程 1.环境的搭建 1.1 在Android studio中新建安卓工…

机器学习正则化算法的总结,建议收藏。(上篇)

下篇地址&#xff1a; 正则化是一种用于降低机器学习模型过拟合风险的技术。当模型过度拟合训练数据时&#xff0c;它会在新样本上表现不佳。所以为了解决这个问题&#xff0c;我们必须要引入正则化算法。 正则化通过在模型的损失函数中添加一个正则项&#xff08;惩罚项&…

2023年建筑安全员C证模拟试题答案及解析

100分题库提供安全员考试试题、建筑安全员考试预测题、建筑安全员ABC考试真题、安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 单选题&#xff08;1-10&#xff09; 1. 发生生产安全事故&#xff0c;对负有责任的生产…

【Python 下载大量品牌网站的图片(一)】关于图片的处理和下载,吃满带宽,可多开窗口下载多个网站,DOS窗口类型

写作日期&#xff1a;2024.05.11 使用工具&#xff1a;Python 可修改功能&#xff1a;线程量、UA、Cookie、代理、存储目录、间隔时间、超时时间、图片压缩、图片缩放 默认功能&#xff1a;图片转换、断续下载、图片检测、路径处理、存储文件 GUI&#xff1a;DOS窗口 类型&…

excel转pdf的java实现

一、实现原理 采用java调用vbs脚本调用office应用把excel转成pdf。 支持文件格式&#xff1a;xlsx,xls,csv 二、前期准备 1、安装office软件 2、准备vbs脚本文件&#xff0c;放到C:\excel2pdf_script\目录下。&#xff08;本文只用2个文件&#xff09; 三、VBS转换脚本 1…

【机器学习300问】85、Adam梯度下降优化算法的原理是什么?

Adam优化算法取了两个算法名称的首字母——Adaptive Moment Estimation的缩写&#xff0c;结合了Momentum算法和RMSprop算法的优点。在Momentum中&#xff0c;会计算前一时刻的梯度&#xff0c;并将其用于当前时刻的梯度更新&#xff1b;而RMSprop会对梯度的大小进行自适应调整…

提高Rust安装与更新的速度

一、背景 因为rust安装过程中&#xff0c;默认的下载服务器为crates.io&#xff0c;这是一个国外的服务器&#xff0c;国内用户使用时&#xff0c;下载与更新的速度非常慢&#xff0c;因此&#xff0c;我们需要使用一个国内的服务器来提高下载与更新的速度。 本文推荐使用字节…

在windows下安装wsl子系统

一、安装环境 windows规格 版本Windows 10企业版版本号22H2操作系统内部版本19045.4291 二、安装过程 2.1 以管理员身份打开PowerShell&#xff08;win X快捷键&#xff09;&#xff1b; 2.2 输入命令&#xff1a;wsl --list --online&#xff08;简写&#xff1a;wsl -l …

FreeRTOS学习 -- 列表和列表项

列表和列表项是 FreeRTOS 的一个数据结构&#xff0c;FreeRTOS 大量使用到了列表和列表项。 一、什么是列表和列表项 1、列表 列表是 FreeRTOS 中的一个数据结构&#xff0c;被用来跟踪 FreeRTOS 中的任务。与列表相关的全部东西都在文件 list.c 和 list.h中。 List_t 结构体…

手写一个SPI FLASH 读写擦除控制器(未完)

文章目录 flash读写数据的特点1. 扇擦除SE&#xff08;Sector Erase&#xff09;1.1 flash_se 模块设计1.1.1 信号连接示意图&#xff1a;1.1.2 SE状态机1.1.3 波形图设计&#xff1a;1.1.4 代码 2. 页写PP(Page Program)2.1 flash_pp模块设计2.1.1 信号连接示意图&#xff1a;…

Python注意事项【自我维护版】

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 本篇博客在之前的博客上进行的维护 创建Python…