FFmpeg In Android - 多媒体文件解封装/解码/保存Yuv

2023-05-16

FFMPEG视音频编解码零基础学习方法
100行代码实现最简单的基于FFMPEG+SDL的视频播放器

本文例子的源码_demuxing_decoding.cpp,修改自源码自带的例子ffmpeg源码/doc/example/demuxing_decoding.c

FFmpeg很庞大,可以参考上面的链接,csdn上雷霄骅的博客,稍微熟悉点后可以看官方自带的例子,ffmpeg-3.3.8/doc/examples,通过例子熟悉FFmpeg的流程和函数.不过看了很多例子都是FFmpeg+SDL的,实际上Android开发者真的需要用SDL吗,作为学习的例子,渲染部分用Android平台自身的功能好了.

这个例子说明了怎样使用ffmpeg进行多媒体文件的解封装,解码成Yuv420p,并保存为文件.然后可以用Yuv播放器播放该文件.
流程图如下,图片来源:
在这里插入图片描述
涉及到的结构体说明
AVFormatContext 详细介绍
AVFormatContext,在解封装阶段用到,主要存储音视频封装格式中包含的信息,是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。下面看几个主要变量的作用(在这里考虑解码的情况):

struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据的缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据

AVInputFormat
AVInputFormat存储输入音视频使用的封装格式,每种音视频封装格式都对应一个AVInputFormat 结构。比如它包含的对象:

/**
* A comma separated list of short names for the format. New names
* may be appended with a minor bump.
/
const char name;
/

* Descriptive name for the format, meant to be more human-readable
* than name. You should use the NULL_IF_CONFIG_SMALL() macro
* to define it.
*/
const char *long_name;

输入一个mp4文件,打印它的name和long_name如下:
name:mov,mp4,m4a,3gp,3g2,mj2, long_name:(null)

AVStream 详细介绍
AVStream是存储每一个视频/音频流信息的结构体。
AVStream重要的变量如下所示:

int index:标识该视频/音频流
AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系)
AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间
int64_t duration:该视频/音频流长度
AVDictionary *metadata:元数据信息
AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的)
AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。

AVCodecContext 详细介绍
音视频流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)的东西,这里面包含了流中所使用的关于编解码器的所有信息.关键变量:

enum AVMediaType codec_type:编解码器的类型(视频,音频…)
struct AVCodec *codec:采用的解码器AVCodec(H.264,MPEG2…)
int bit_rate:平均比特率
uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)
AVRational time_base:根据该参数,可以把PTS转化为实际的时间(单位为秒s)
int width, height:如果是视频的话,代表宽和高
int refs:运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有了)
int sample_rate:采样率(音频)
int channels:声道数(音频)
enum AVSampleFormat sample_fmt:采样格式
int profile:型(H.264里面就有,其他编码标准应该也有)
int level:级(和profile差不太多)

在这里需要注意:AVCodecContext中很多的参数是编码的时候使用的,而不是解码的时候使用的。

AVCodec 详细介绍
AVCodec是存储编解码器信息的结构体,主要的几个变量:

const char *name:编解码器的名字,比较短
const char *long_name:编解码器的名字,全称,比较长
enum AVMediaType type:指明了类型,是视频,音频,还是字幕
enum AVCodecID id:ID,不重复
const AVRational *supported_framerates:支持的帧率(仅视频)
const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)
const int *supported_samplerates:支持的采样率(仅音频)
const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)
const uint64_t *channel_layouts:支持的声道数(仅音频)
int priv_data_size:私有数据的大小

AVPixelFormat定义如下:
AV_PIX_FMT_NONE = -1,
    AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
    AV_PIX_FMT_YUYV422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
    AV_PIX_FMT_RGB24,     ///< packed RGB 8:8:8, 24bpp, RGBRGB...
    AV_PIX_FMT_BGR24,     ///< packed RGB 8:8:8, 24bpp, BGRBGR...
    AV_PIX_FMT_YUV422P,   ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
    AV_PIX_FMT_YUV444P,   ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)
    AV_PIX_FMT_YUV410P,   ///< planar YUV 4:1:0,  9bpp, (1 Cr & Cb sample per 4x4 Y samples)
    AV_PIX_FMT_YUV411P,   ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples)
    ...(代码太长,略)

AVIOContext 详细介绍
AVIOContext是FFMPEG管理输入输出数据的结构体

AVPacket 详细介绍
AVPacket是存储编码数据的结构体,典型用法是由解封装器填充数据,然后传递给解码器,或者从编码器接收数据,然后传递给封装器.重要的变量:

uint8_t *data:压缩编码的数据。
例如对于H.264来说。1个AVPacket的data通常对应一个NAL。
注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流
因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。
int size:data的大小
int64_t pts:显示时间戳
int64_t dts:解码时间戳
int stream_index:标识该AVPacket所属的视频/音频流。

AVFrame 详细介绍
AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。

主要变量:

uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。
int width, height:视频帧宽和高(1920x1080,1280x720…)
int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个
int format:解码后原始数据类型(YUV420,YUV422,RGB24…)
int key_frame:是否是关键帧
enum AVPictureType pict_type:帧类型(I,B,P…)
AVRational sample_aspect_ratio:宽高比(16:9,4:3…)
int64_t pts:显示时间戳
int coded_picture_number:编码帧序号
int display_picture_number:显示帧序号
int8_t *qscale_table:QP表
uint8_t *mbskip_table:跳过宏块表
int16_t (*motion_val[2])[2]:运动矢量表
uint32_t *mb_type:宏块类型表
short *dct_coeff:DCT系数,这个没有提取过
int8_t *ref_index[2]:运动估计参考帧列表(貌似H.264这种比较新的标准才会涉及到多参考帧)
int interlaced_frame:是否是隔行扫描
uint8_t motion_subsample_log2:一个宏块中的运动矢量采样个数,取log的

其他的变量不再一一列举,源代码中都有详细的说明。在这里重点分析一下几个需要一定的理解的变量:
1. data[]

对于packed格式的数据(例如RGB24),会存到data[0]里面。

对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]…(YUV420P中data[0]存Y,data[1]存U,data[2]存V), 具体参见:FFMPEG 实现 YUV,RGB各种图像原始数据之间的转换(swscale)

2.pict_type, 包含以下类型:

enum AVPictureType {
    AV_PICTURE_TYPE_NONE = 0, ///< Undefined
    AV_PICTURE_TYPE_I,     ///< Intra
    AV_PICTURE_TYPE_P,     ///< Predicted
    AV_PICTURE_TYPE_B,     ///< Bi-dir predicted
    AV_PICTURE_TYPE_S,     ///< S(GMC)-VOP MPEG4
    AV_PICTURE_TYPE_SI,    ///< Switching Intra
    AV_PICTURE_TYPE_SP,    ///< Switching Predicted
    AV_PICTURE_TYPE_BI,    ///< BI type
};

转:FFMPEG中最关键的结构体之间的关系



下面是函数说明(注意随着ffmpeg的版本不同,流程图上的函数名可能不同了):

/**
 * Initialize libavformat and register all the muxers, demuxers and
 * protocols. If you do not call this function, then you can select
 * exactly which formats you want to support.
 *
 * @see av_register_input_format()
 * @see av_register_output_format()
 */
void av_register_all(void);
注册了所有的文件格式和编解码器的库,
也可以只注册特定的格式和编解码器:av_register_input_format(),  av_register_output_format()
/**
 * Open an input stream and read the header. The codecs are not opened.
 * The stream must be closed with avformat_close_input().
 *
 * @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
 *           May be a pointer to NULL, in which case an AVFormatContext is allocated by this
 *           function and written into ps.
 *           Note that a user-supplied AVFormatContext will be freed on failure.
 * @param url URL of the stream to open.
 * @param fmt If non-NULL, this parameter forces a specific input format.
 *            Otherwise the format is autodetected.
 * @param options  A dictionary filled with AVFormatContext and demuxer-private options.
 *                 On return this parameter will be destroyed and replaced with a dict containing
 *                 options that were not found. May be NULL.
 *
 * @return 0 on success, a negative AVERROR on failure.
 *
 * @note If you want to use custom IO, preallocate the format context and set its pb field.
 */
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
读取文件的头部并且把信息保存到第一个参数AVFormatContext结构体中。
最后三个参数用来指定特殊的文件格式,缓冲大小和格式参数,但如果把它们设置为空 NULL 或者 0,将自动检测这些参数。
/**
 * Read packets of a media file to get stream information. This
 * is useful for file formats with no headers such as MPEG. This
 * function also computes the real framerate in case of MPEG-2 repeat
 * frame mode.
 * The logical file position is not changed by this function;
 * examined packets may be buffered for later processing.
 *
 * @param ic media file handle
 * @param options  If non-NULL, an ic.nb_streams long array of pointers to
 *                 dictionaries, where i-th member contains options for
 *                 codec corresponding to i-th stream.
 *                 On return each dictionary will be filled with options that were not found.
 * @return >=0 if OK, AVERROR_xxx on error
 *
 * @note this function isn't guaranteed to open all the codecs, so
 *       options being non-empty at return is a perfectly normal behavior.
 *
 * @todo Let the user decide somehow what information is needed so that
 *       we do not waste time getting stuff the user does not need.
 */
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
avformat_open_input函数只是检测了文件的头部,这个函数检查在文件中的流的信息

/**
 * Find the "best" stream in the file.
 * The best stream is determined according to various heuristics as the most
 * likely to be what the user expects.
 * If the decoder parameter is non-NULL, av_find_best_stream will find the
 * default decoder for the stream's codec; streams for which no decoder can
 * be found are ignored.
 *
 * @param ic                media file handle
 * @param type              stream type: video, audio, subtitles, etc.
 * @param wanted_stream_nb  user-requested stream number,
 *                          or -1 for automatic selection
 * @param related_stream    try to find a stream related (eg. in the same
 *                          program) to this one, or -1 if none
 * @param decoder_ret       if non-NULL, returns the decoder for the
 *                          selected stream
 * @param flags             flags; none are currently defined
 * @return  the non-negative stream number in case of success,
 *          AVERROR_STREAM_NOT_FOUND if no stream with the requested type
 *          could be found,
 *          AVERROR_DECODER_NOT_FOUND if streams were found but no decoder
 * @note  If av_find_best_stream returns successfully and decoder_ret is not
 *        NULL, then *decoder_ret is guaranteed to be set to a valid AVCodec.
 */
int av_find_best_stream(AVFormatContext *ic,
                        enum AVMediaType type,
                        int wanted_stream_nb,
                        int related_stream,
                        AVCodec **decoder_ret,
                        int flags);
 找到视频流或音频流的索引,其中包含编解码器的信息
/**
 * Find a registered decoder with a matching codec ID.
 *
 * @param id AVCodecID of the requested decoder
 * @return A decoder if one was found, NULL otherwise.
 */
AVCodec *avcodec_find_decoder(enum AVCodecID id);
找到编解码器
/**
 * Allocate an AVCodecContext and set its fields to default values. The
 * resulting struct should be freed with avcodec_free_context().
 *
 * @param codec if non-NULL, allocate private data and initialize defaults
 *              for the given codec. It is illegal to then call avcodec_open2()
 *              with a different codec.
 *              If NULL, then the codec-specific defaults won't be initialized,
 *              which may result in suboptimal default settings (this is
 *              important mainly for encoders, e.g. libx264).
 *
 * @return An AVCodecContext filled with default values or NULL on failure.
 */
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
流中关于编解码器的信息就是被我们叫做“编解码器上下文”(codec context)的东西。这里面包含了流中所
使用的关于编解码器的所有信息,现在我们有了一个指向它的指针.
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
打开编码器
/**
 * Allocate an AVFrame and set its fields to default values.  The resulting
 * struct must be freed using av_frame_free().
 *
 * @return An AVFrame filled with default values or NULL on failure.
 *
 * @note this only allocates the AVFrame itself, not the data buffers. Those
 * must be allocated through other means, e.g. with av_frame_get_buffer() or
 * manually.
 */
AVFrame *av_frame_alloc(void);
分配一个AVFrame并设为默认值,用来存放帧数据
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
从流中读取一帧完整数据或者多帧完整(ffmpeg内部保证了完整性)
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                         int *got_picture_ptr,
                         const AVPacket *avpkt);
 解码原始流中的一帧数据(输出yuv数据)
/**
 * Copy image in src_data to dst_data.
 *
 * @param dst_linesizes linesizes for the image in dst_data
 * @param src_linesizes linesizes for the image in src_data
 */
void av_image_copy(uint8_t *dst_data[4], int dst_linesizes[4],
                   const uint8_t *src_data[4], const int src_linesizes[4],
                   enum AVPixelFormat pix_fmt, int width, int height);
 拷贝图片数据
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FFmpeg In Android - 多媒体文件解封装/解码/保存Yuv 的相关文章

随机推荐

  • Spark2 Linear Regression线性回归

    本文使用SparkMllib搭建简单的线性回归例子 数据下载地址 package SparkMlibCourse3 import org span class hljs preprocessor apache span span class
  • 2011

    2011 Problem Description The GeoSurvComp geologic survey company is responsible for detecting underground oil deposits G
  • linux 自动关机 计划任务

    设置192 168 67 232 231在21 xff1a 30自动关机 1 xff09 手动命令方式 xff03 shutdown h 21 30 2 xff09 计划任务方式 xff0c 定时关机 1 添加定时任务 root 64 se
  • 微信自动回复(python)

    前言 xff1a 自从微信禁止网页版登陆之后 xff0c itchat 库也就都不能用了 xff0c 新号不能再登录网页版 xff0c 而且itchat 库在headers里面只放了UserAgent xff0c 其他字段都没有放 所以在你
  • linux下性能监控shell脚本实现系列一(服务器整体性能监控)

    在实现监控脚本前 xff0c 我们先了解下一些已经成型的监控程序 xff0c 比如大名鼎鼎的nmon nmon官网 http nmon sourceforge net pmwiki php nmon使用简单说明 http www ibm c
  • python utf-8转汉字

    其实也不麻烦 xff0c 看下边的代码 汉字编码转换 testgbk 61 39 汉字 39 testunit 61 testgbk decode 39 gbk 39 汉字解码 testutf8 61 testgbk encode 39 u
  • linux中如何查看进程启动时间,持续时长

    root 64 gasdb2 Transsoft ps eo pid lstart etime grep 5359 5359 Tue Sep 16 14 47 22 2014 00 43 其中5359为进程号 xff0c 方便做过滤用的 T
  • 错误: 程序包org.apache.log4j不存在问题处理

    将maven中依赖修改如下 xff1a lt dependency gt lt groupId gt log4j lt groupId gt lt artifactId gt log4j lt artifactId gt lt versio
  • 构建maven项目失败解决办法

    通过eclipse的maven插件新建maven项目时 xff0c 出现如下错误 Unable to create project from archetype org apache maven archetypes maven arche
  • 关于java中的非静态代码块和静态代码块

    非静态代码块如 xff1a System out println 34 2 34 以上代码块就是非静态的代码块 xff0c 请注意这里的方法名 作用域 返回值 参数一概没有 xff0c 非静态代码块会在每次类被调用或者被实例化时就会被执行
  • jsoninclude.include.non_null 不起作用问题

    在开发过程中 xff0c 遇到在属性上加jsoninclude include non null注解 xff0c 有些属性起了作用 xff0c 有些却没有起作用 xff0c 下面使用代码简要说明一下 xff1a 64 Data public
  • 再次认识java的序列化

    首先是我们为什么要序列化 xff1f 我们可以来想想游戏的场景 xff0c 一个游戏有很多关卡 xff0c 并不是一次性能够打完的 如果我们打完一关 xff0c 这时候需要退出游戏休息了 当我们再次进入游戏之后发现这个游戏竟然需要重新打起
  • 控制actionbar中的向上键

    android中会给配置了 android parentActivityName 的activity默认加入一个向上返回键 xff0c 如下 xff1a 当点击向上返回后 xff0c 默认的行为是finish自身 xff0c startac
  • Android Studio快捷键

    Alt 43 回车 导入包 自动修正 Ctrl 43 N 查找类 Ctrl 43 Shift 43 N 查找文件 Ctrl 43 Alt 43 L 格式化代码 Ctrl 43 Alt 43 O 优化导入的类和包 Alt 43 Insert
  • Ubuntu 16.04 出现E: Problem executing scripts E: Sub-process returned an error code处理办法

    E Problem executing scripts APT Update Post Invoke Success 39 if usr bin test w var cache app info a e usr bin appstream
  • android studio “leaked window“ 错误

    昨天玩我做的app 发现app运行没问题 xff0c 但log里面出现下面的错误 xff1a Activity com example thirdversionclock MainActivity welcome has leaked wi
  • Archlinux 安装桌面环境 dwm + polybar

    Archlinux 安装桌面环境 dwm 43 polybar 关于git一 安装一些必要软件1 壁纸 状态栏等软件2 安装dwm和st 三 安装Polybar四 Fish Shell五 登录管理器六 一些个人使用的工具 2023 1 31
  • Arch Linux 安装和配置(陆续更新)

    Arch Linux 安装和配置 陆续更新 南国一年一度的回南天到来 xff0c 周末百无聊赖 xff0c 研究一下Arch Linux xff0c 整理此文 xff0c 其他使用笔记陆续补充 一 在Vmware中安装Arch Linux
  • ASP.NET Core MemoryCache 缓存

    ASP NET Core 中的缓存内存 xff08 MemoryCache xff09 ASP NET Core 中的缓存内存 ASP NET Core 中的分布式缓存 xff08 SQL Server 和 Redis 分布式缓存 xff0
  • FFmpeg In Android - 多媒体文件解封装/解码/保存Yuv

    FFMPEG视音频编解码零基础学习方法 100行代码实现最简单的基于FFMPEG 43 SDL的视频播放器 本文例子的源码 demuxing decoding cpp 修改自源码自带的例子ffmpeg源码 doc example demux