FFmpeg基础:抽取视频文件中的音视频原始数据

news/2024/5/1 15:54:40/文章来源:https://blog.csdn.net/yang1fei2/article/details/127238192

文章目录

    • 视频流解码
    • 音频流解码

原始的音视频数据数据量很大,为了方便传输和存储,我们会对原始数据进行压缩和编码。h264是常见的视频编码标准之一,AAC是常见的音频编码标准之一。这里介绍一下如何通过FFmpeg库将视频文件中的h264视频流解码成原始YUV视频数据,如何将AAC音频流解码成原始的PCM音频数据。音视频流的基本解码流程如下图所示:
在这里插入图片描述

视频流解码

视频文件中视频流的解码流程大体分为以下几步:
1.分配媒体文件上下文并获取流信息
2.查找对应的视频流和解码器
3.读取视频流中的数据包并解码
4.将解码之后的数据输出到对应的文件中

使用FFmpeg库对视频流进行解码的示例如下所示:
(采用h264编码的视频流进行验证通过)

#define _CRT_SECURE_NO_WARNINGS#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>//视频文件上下文
static AVFormatContext *fmt_ctx = NULL;
//视频解码器上下文
static AVCodecContext *video_dec_ctx = NULL;
//图像宽高
static int width, height;
//像素格式
static enum AVPixelFormat pix_fmt;
//视频流
static AVStream *video_stream = NULL;
//输入文件路径
static const char *src_filename = NULL;
//目标视频文件地址
static const char *video_dst_filename = NULL;
static FILE *video_dst_file = NULL;//图像数据缓存
static uint8_t *video_dst_data[4] = { NULL };
static int      video_dst_linesize[4];
static int video_dst_bufsize;//数据流索引
static int video_stream_idx = -1;//帧信息和数据包索引
static AVFrame *frame = NULL;
static AVPacket *pkt = NULL;static int output_video_frame(AVFrame *frame)
{//将解码后的数据帧拷贝到对应的内存当中av_image_copy(video_dst_data, video_dst_linesize,(const uint8_t **)(frame->data), frame->linesize,pix_fmt, width, height);//将解析后的数据写入到文件中fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);return 0;
}
//解码数据包
static int decode_packet(AVCodecContext *dec, const AVPacket *pkt)
{int ret = 0;ret = avcodec_send_packet(dec, pkt);if (ret < 0) {fprintf(stderr, "Error submitting a packet for decoding (%s)\n", av_err2str(ret));return ret;}//对数据进行解码输出while (ret >= 0) {ret = avcodec_receive_frame(dec, frame);if (ret < 0) {if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))return 0;return ret;}//将数据内容输出到文件if (dec->codec->type == AVMEDIA_TYPE_VIDEO)ret = output_video_frame(frame);av_frame_unref(frame);if (ret < 0)return ret;}return 0;
}//查找视频流的解码器
static int open_codec_context(int *stream_idx,AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)
{int ret, stream_index;AVStream *st;const AVCodec *dec = NULL;//查找流ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);if (ret < 0) {fprintf(stderr, "Could not find %s stream in input file '%s'\n",av_get_media_type_string(type), src_filename);return ret;}else {stream_index = ret;st = fmt_ctx->streams[stream_index];//通过数据流查找对应的解码器dec = avcodec_find_decoder(st->codecpar->codec_id);if (!dec) {fprintf(stderr, "Failed to find %s codec\n",av_get_media_type_string(type));return AVERROR(EINVAL);}//创建解码器的上下文*dec_ctx = avcodec_alloc_context3(dec);if (!*dec_ctx) {fprintf(stderr, "Failed to allocate the %s codec context\n",av_get_media_type_string(type));return AVERROR(ENOMEM);}//从输入流中拷贝对应的参数到解码器当中if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) {fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",av_get_media_type_string(type));return ret;}//初始化解码器if ((ret = avcodec_open2(*dec_ctx, dec, NULL)) < 0) {fprintf(stderr, "Failed to open %s codec\n",av_get_media_type_string(type));return ret;}*stream_idx = stream_index;}return 0;
}int main(int argc, char **argv)
{int ret = 0;if (argc != 3) {av_log(NULL, AV_LOG_DEBUG, "input argumets number is not valid\n");return 1;}src_filename = argv[1];video_dst_filename = argv[2];//打开视频文件分配上下文if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {av_log(NULL, AV_LOG_DEBUG, "open media file failed\n");return 2;}//获取流信息if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {av_log(NULL, AV_LOG_DEBUG, "get stream info failed\n");return 3;}//获取视频流的索引和解码器if (open_codec_context(&video_stream_idx, &video_dec_ctx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {video_stream = fmt_ctx->streams[video_stream_idx];video_dst_file = fopen(video_dst_filename, "wb");if (!video_dst_file) {fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);ret = 1;goto end;}//创建图像容器(像素宽高和图片格式)width = video_dec_ctx->width;height = video_dec_ctx->height;pix_fmt = video_dec_ctx->pix_fmt;ret = av_image_alloc(video_dst_data, video_dst_linesize,width, height, pix_fmt, 1);if (ret < 0) {fprintf(stderr, "Could not allocate raw video buffer\n");goto end;}video_dst_bufsize = ret;}//分配帧frame = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate frame\n");ret = AVERROR(ENOMEM);goto end;}//分配数据包pkt = av_packet_alloc();if (!pkt) {fprintf(stderr, "Could not allocate packet\n");ret = AVERROR(ENOMEM);goto end;}if (video_stream)printf("Demuxing video from file '%s' into '%s'\n", src_filename, video_dst_filename);//读取视频流中的数据包,并解码while (av_read_frame(fmt_ctx, pkt) >= 0) {if (pkt->stream_index == video_stream_idx)ret = decode_packet(video_dec_ctx, pkt);av_packet_unref(pkt);if (ret < 0)break;}//刷新解码器if (video_dec_ctx)decode_packet(video_dec_ctx, NULL);//输出原始视频文件的播放格式if (video_stream) {printf("Play the output video file with the command:\n""ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s\n",av_get_pix_fmt_name(pix_fmt), width, height,video_dst_filename);}//清理释放对应的数据信息
end:avcodec_free_context(&video_dec_ctx);avformat_close_input(&fmt_ctx);if (video_dst_file)fclose(video_dst_file);av_packet_free(&pkt);av_frame_free(&frame);av_free(video_dst_data[0]);getchar();return ret < 0;
}

音频流解码

视频文件中的AAC音频流数据解码成PCM原始音频数据的流程和视频流的解码流程大体类似,分为以下几步:
1.分配媒体文件上下文,获取流信息
2.查找对应的音频流和解码器
3.读取音频流中的数据包并解码
4.将解码之后的数据输出到对应的文件中

使用FFmpeg库对音频流进行解码的示例如下所示:
(采用AAC编码的音频流进行验证通过)

#define _CRT_SECURE_NO_WARNINGS#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>//视频文件上下文
static AVFormatContext *fmt_ctx = NULL;
//音频解码器上下文
static AVCodecContext *audio_dec_ctx = NULL;
//音频流
static AVStream *audio_stream = NULL;
//输入文件路径
static const char *src_filename = NULL;
//目标音频文件地址
static const char *audio_dst_filename = NULL;
static FILE *audio_dst_file = NULL;//数据流索引
static int audio_stream_idx = -1;//帧信息和数据包索引
static AVFrame *frame = NULL;
static AVPacket *pkt = NULL;//输出音频信息
static int output_audio_frame(AVFrame *frame)
{size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample(frame->format);fwrite(frame->extended_data[0], 1, unpadded_linesize, audio_dst_file);return 0;
}//获取采样时的数据位类型
static int get_format_from_sample_fmt(const char **fmt,enum AVSampleFormat sample_fmt)
{int i;struct sample_fmt_entry {enum AVSampleFormat sample_fmt; const char *fmt_be, *fmt_le;} sample_fmt_entries[] = {{ AV_SAMPLE_FMT_U8,  "u8",    "u8" },{ AV_SAMPLE_FMT_S16, "s16be", "s16le" },{ AV_SAMPLE_FMT_S32, "s32be", "s32le" },{ AV_SAMPLE_FMT_FLT, "f32be", "f32le" },{ AV_SAMPLE_FMT_DBL, "f64be", "f64le" },};*fmt = NULL;for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {struct sample_fmt_entry *entry = &sample_fmt_entries[i];if (sample_fmt == entry->sample_fmt) {*fmt = AV_NE(entry->fmt_be, entry->fmt_le);return 0;}}fprintf(stderr,"sample format %s is not supported as output format\n",av_get_sample_fmt_name(sample_fmt));return -1;
}//解码数据包
static int decode_packet(AVCodecContext *dec, const AVPacket *pkt)
{int ret = 0;ret = avcodec_send_packet(dec, pkt);if (ret < 0) {fprintf(stderr, "Error submitting a packet for decoding (%s)\n", av_err2str(ret));return ret;}//对数据进行解码输出while (ret >= 0) {ret = avcodec_receive_frame(dec, frame);if (ret < 0) {if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))return 0;return ret;}//将数据内容输出到文件if (dec->codec->type == AVMEDIA_TYPE_AUDIO)ret = output_audio_frame(frame);av_frame_unref(frame);if (ret < 0)return ret;}return 0;
}//获取编码器
static int open_codec_context(int *stream_idx,AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)
{int ret, stream_index;AVStream *st;const AVCodec *dec = NULL;//查找流ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);if (ret < 0) {fprintf(stderr, "Could not find %s stream in input file '%s'\n",av_get_media_type_string(type), src_filename);return ret;}else {stream_index = ret;st = fmt_ctx->streams[stream_index];//通过数据流查找对应的解码器dec = avcodec_find_decoder(st->codecpar->codec_id);if (!dec) {fprintf(stderr, "Failed to find %s codec\n",av_get_media_type_string(type));return AVERROR(EINVAL);}//创建解码器的上下文*dec_ctx = avcodec_alloc_context3(dec);if (!*dec_ctx) {fprintf(stderr, "Failed to allocate the %s codec context\n",av_get_media_type_string(type));return AVERROR(ENOMEM);}//从输入流中拷贝对应的参数到解码器当中if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) {fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",av_get_media_type_string(type));return ret;}//初始化解码器if ((ret = avcodec_open2(*dec_ctx, dec, NULL)) < 0) {fprintf(stderr, "Failed to open %s codec\n",av_get_media_type_string(type));return ret;}*stream_idx = stream_index;}return 0;
}int main(int argc, char **argv)
{int ret = 0;if (argc != 3) {av_log(NULL, AV_LOG_DEBUG, "input argumets number is not valid\n");return 1;}src_filename = argv[1];audio_dst_filename = argv[2];//打开视频文件分配上下文if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {av_log(NULL, AV_LOG_DEBUG, "open media file failed\n");return 2;}//获取流信息if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {av_log(NULL, AV_LOG_DEBUG, "get stream info failed\n");return 3;}//获取音频流的索引和解码器if (open_codec_context(&audio_stream_idx, &audio_dec_ctx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {audio_stream = fmt_ctx->streams[audio_stream_idx];audio_dst_file = fopen(audio_dst_filename, "wb");if (!audio_dst_file) {fprintf(stderr, "Could not open destination file %s\n", audio_dst_filename);ret = 1;goto end;}}//分配帧frame = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate frame\n");ret = AVERROR(ENOMEM);goto end;}//分配数据包pkt = av_packet_alloc();if (!pkt) {fprintf(stderr, "Could not allocate packet\n");ret = AVERROR(ENOMEM);goto end;}if (audio_stream)printf("Demuxing audio from file '%s' into '%s'\n", src_filename, audio_dst_filename);//读取音频流的数据包,并解码while (av_read_frame(fmt_ctx, pkt) >= 0) {if (pkt->stream_index == audio_stream_idx)ret = decode_packet(audio_dec_ctx, pkt);av_packet_unref(pkt);if (ret < 0)break;}//刷新解码器if (audio_dec_ctx)decode_packet(audio_dec_ctx, NULL);//输出PCM原始音频文件的播放格式if (audio_dec_ctx) {//采样格式和通道数量enum AVSampleFormat sfmt = audio_dec_ctx->sample_fmt;int n_channels = audio_dec_ctx->channels;const char *fmt;//获取PCM音频的数据类型if (av_sample_fmt_is_planar(sfmt)) {const char *packed = av_get_sample_fmt_name(sfmt);printf("Warning: the sample format the decoder produced is planar ""(%s). This example will output the first channel only.\n",packed ? packed : "?");sfmt = av_get_packed_sample_fmt(sfmt);n_channels = 1;}if ((ret = get_format_from_sample_fmt(&fmt, sfmt)) < 0)goto end;//输出播放原始数据的命令printf("Play the output audio file with the command:\n""ffplay -f %s -ac %d -ar %d %s\n",fmt, n_channels, audio_dec_ctx->sample_rate,audio_dst_filename);}//清理释放对应的数据信息
end:avcodec_free_context(&audio_dec_ctx);avformat_close_input(&fmt_ctx);if (audio_dst_file)fclose(audio_dst_file);av_packet_free(&pkt);av_frame_free(&frame);getchar();return ret < 0;
} 

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

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

相关文章

[Unity] 获取UI组件的屏幕坐标(打包手机端使用前置摄像头)

因为我是打包到手机端&#xff0c;并且使用的是前置摄像头 在Canvas默认的render mode&#xff08;Screen Space - Overlay&#xff09;下&#xff1a; 我想要使用 arcamera.WorldToScreenPoint(button.transform.position) 将Canvas下的button世界坐标转为屏幕坐标&#xff…

手把手教你搭建属于自己的第一个Maven项目并引入Spring容器依赖

前言 最近在学习Maven和Spring&#xff0c;在这里做个学习记录并且分享一些我自己的学习经验&#xff0c;对Maven和Spring感兴趣的小伙伴可以一起来学习&#xff0c;有任何问题也欢迎一起来交流讨论。 Spring简介 Spring框架由Rod Johnson开发&#xff0c;2004年发布了Sprin…

(附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

技术分享预告|DocArray x Redis 比快更快的向量搜索

从国内的学生开发者&#xff0c;到深入参与国际化开源社区。小燕主动出击&#xff0c;不放过任何一个贡献机会。快来看看原子能科学研究院核物理专业小姐姐的神仙输出&#xff01;北京时间 10 月 11 号&#xff0c;本周二晚 10 点&#xff0c;小燕将在 Zoom 平台&#xff0c;面…

第03章 成人感冒不是大事,少往急诊跑

急诊是病菌最多的地方&#xff0c;能避则避我在安贞急诊工作了数个年头&#xff0c;最大的体会就是急诊像个大的农贸市场&#xff0c;每天人头攒动、人山人海、接踵摩肩、座无虚席、门庭若市、沸沸扬扬、热闹非凡、熙熙攘攘、车水马龙、人来人往……写了这么多词汇都不足以写出…

Redis(Jedis入门 Jedis连接池

本章导学&#xff1a; 什么是Jedis&#xff1f;Jedis入门案例 新建项目&#xff0c;导入依赖建立连接进行测试释放资源Jedis的连接池使用一、什么是Jedis Redis不仅是使用命令来操作&#xff0c;现在基本上主流的语言都有客户端支持&#xff0c;比如java、C、C#、C、php、Nod…

Spring IOC核心知识

控制反转&#xff08;IOC: Inversion Of Control&#xff09;&#xff0c;把对象的创建和对象之间的调用过程都交给Spring管理&#xff0c;从而降低耦合度。 IOC底层原理 三大技术&#xff1a;XML解析、工厂模式、反射 IOC创建对象的过程&#xff1a; IOC重要接口 IOC容器底…

路径分析—QGIS+PostgreSQL+PostGIS+pgRouting(一)

前言 因业务需求,需要做最短路径分析。最近几天查询资料,并自己动手,实现了简单的路径分析。 下面就介绍具体的实现过程。 本篇文章最终结果是在 PostgreSQL 数据库中实现的,后续的可视化展示会继续跟进。 一、道路数据处理 如果你已经有了道路数据,那就直接使用。 由于当…

传统应用如何“丝滑”转型为云原生应用?

市场需求瞬息万变&#xff0c;传统型的应用逐渐无法满足业务侧要求&#xff0c;传统应用向云原生应用转型&#xff0c;已势在必行。本篇文章将介绍基于CloudOS&#xff08;一站式云原生开发平台&#xff09;如何实现传统应用向云原生应用的转型。 在此之前&#xff0c;我们需…

python基础(老师PPT)

slist [Alice,2019001, Geoscience, 95] # 使用分号加tab作为分隔符&#xff0c;连接字符串 print(;\t.join(slist))# a X if condition else Z # condition为True时&#xff0c;赋值为X&#xff0c;否则赋值为Z a, b 1, 2 c a > b if a > b else a < b print(c)…

分布式seata案例学习-2

上篇文章学习了如何安装seata&#xff0c;先学习如何使用 事务的4个特性ACID 事务特性 at模式详解 AT模式运行机制 AT模式的特点就是对业务无入侵式&#xff0c;整体机制分二阶段提交 两阶段提交协议的演变&#xff1a; 一阶段&#xff1a;业务数据和回滚日志记录在同一个本地…

【复习整理归纳】| C++面经网络相关(三)

文章目录计算机网络性能指标计算机在发送文件前需要做许多前期的工作分层结构概念OSI参考模型数据链路层ARP网络协议UDP套接字TCPTCP粘包三次握手四次握手为什么建立连接协议是三次握手&#xff0c;而关闭连接却是四次挥手呢&#xff1f;为什么TIME_WAIT状态还需要等2MSL后才能…

C++ | 12天学好C++ (Day 12)->结构图可视化、代码加通俗理解

为每天进步一点点而努力。 C是计算机视觉的重要的一部分&#xff0c;虽然在初始学习时Python应用比较广&#xff0c;但是大多数公司还是C做计算机视觉类的比较多&#xff0c;因为C可加密&#xff0c;所以我们来一起探索吧&#xff01;看了这系列博客&#xff0c;将会学到C的基…

#边学边记 必修4 高项:对事的管理 第4章 项目进度管理之 制定进度计划

制订进度计划的输入、工具与技术和输出 制定进度计划过程分析活动顺序、持续时间、资源需求和进度制约因素&#xff0c;创建项目进度模型的过程。主要作用是把活动、持续时间、资源、资源可用性和逻辑关系代入进度规划工具&#xff0c;从而形成包含各个项目活动的计划日期的进…

在docker安装的centos容器内设置远程链接

写在前面 写在前面 运维这个行业&#xff0c;不需要按照顺序学习。 比如你学会了基础命令&#xff0c;直接学docker和k8s&#xff0c;和学会了基础命令开始学服务难度差不多。 再比如你学会了基础命令直接学shell脚本&#xff0c;也能学的会。 所以我一直主张&#xff0c;…

WEB漏洞-文件操作之文件下载读取全解

目录 前言: (一&#xff09;前置知识 &#xff08;二&#xff09;文件下载读取漏洞利用 1、网站目录的获取&#xff1a; 0x01 字典 0x02 网络爬虫 0x03 fuzz 0x04 自动化工具 0x05 下载传参的脚步文件 0x06 配合其他漏洞 2、下载数据库,操作系统配置文件 0x01 Window…

非空约束

1.创建表时添加约束   创建完表后,添加非空约束 删除name的非空约束

Vue3.0----组件基础【上】(第二章)

一、单页面应用程序 1. 什么是单页面应用程序 单页面应用程序&#xff08;英文名&#xff1a;Single Page Application&#xff09;简称 SPA&#xff0c;顾 名思义&#xff0c;指的是一个 Web 网站中只有唯一的一个 HTML 页面&#xff0c;所有的 功能与交互都在这唯一的一个页…

(六)Shell编程之函数、脚本引用、符号展开、重定向

一、定义函数 shell中函数的定义格式如下&#xff1a; [ function ] funname [()] {action;[return int;] }说明&#xff1a; 可以带function fun()定义&#xff0c;也可以直接fun()定义&#xff0c;如果带function函数名后的小括号可以省略。返回值语句[return int;]可以不写…