FFmpeg入门 - 格式转换

news2024/12/22 17:13:28

1、音频分⽚(plane)与打包(packed)


解码出来的AVFrame,它的data字段放的是视频像素数据或者音频的PCM裸流数据,linesize字段放的是对齐后的画面行长度或者音频的分片长度:

/**

* For video, size in bytes of each picture line.

* For audio, size in bytes of each plane.

*

* For audio, only linesize[0] may be set. For planar audio, each channel

* plane must be the same size.

*

* For video the linesizes should be multiples of the CPUs alignment

* preference, this is 16 or 32 for modern desktop CPUs.

* Some code requires such alignment other code can be slower without

* correct alignment, for yet other it makes no difference.

*

* @note The linesize may be larger than the size of usable data -- there

* may be extra padding present for performance reasons.

*/

intlinesize[AV_NUM_DATA_POINTERS];

视频相关的在之前的博客中有介绍,音频的话可以看到它只有linesize[0]会被设置,如果有多个分片,每个分片的size都是相等的。

要理解这里的分片size,先要理解音频数据的两种存储格式分⽚(plane)与打包(packed)。以常见的双声道音频为例子,

分⽚存储的数据左声道和右声道分开存储,左声道存储在data[0],右声道存储在data[1],他们的数据buffer的size都是linesize[0]。

打包存储的数据按照LRLRLR...的形式交替存储在data[0]中,这个数据buffer的size是linesize[0]。

AVSampleFormat枚举音频的格式,带P后缀的格式是分配存储的:

AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar

AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar

AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar

AV_SAMPLE_FMT_FLTP, ///< float, planar

AV_SAMPLE_FMT_DBLP, ///< double, planar

不带P后缀的格式是打包存储的:

AV_SAMPLE_FMT_U8, ///< unsigned 8 bits

AV_SAMPLE_FMT_S16, ///< signed 16 bits

AV_SAMPLE_FMT_S32, ///< signed 32 bits

AV_SAMPLE_FMT_FLT, ///< float

AV_SAMPLE_FMT_DBL, ///< double

2、音频数据的实际长度


这里有个坑点备注里面也写的很清楚了,linesize标明的大小可能会大于实际的音视频数据大小,因为可能会有额外的填充。

@note The linesize may be larger than the size of usable data -- there
may be extra padding present for performance reasons.

所以音频数据实际的长度需要用音频的参数计算出来:

intchannelCount=audioStreamDecoder.GetChannelCount();

intbytePerSample=audioStreamDecoder.GetBytePerSample();

intsize=frame->nb_samples*channelCount*bytePerSample;

3、音频格式转换


视频之前的demo中已经可以使用OpenGL播放,而音频可以交给OpenSL来播放,之前我写过一篇《OpenSL ES 学习笔记》详细的使用细节我就不展开介绍了,直接将代码拷贝来使用。

但是由于OpenSLES只支持打包的几种音频格式:

#define SL_PCMSAMPLEFORMAT_FIXED_8 ((SLuint16) 0x0008)

#define SL_PCMSAMPLEFORMAT_FIXED_16 ((SLuint16) 0x0010)

#define SL_PCMSAMPLEFORMAT_FIXED_20 ((SLuint16) 0x0014)

#define SL_PCMSAMPLEFORMAT_FIXED_24 ((SLuint16) 0x0018)

#define SL_PCMSAMPLEFORMAT_FIXED_28 ((SLuint16) 0x001C)

#define SL_PCMSAMPLEFORMAT_FIXED_32 ((SLuint16) 0x0020)

这里我们指的AudioStreamDecoder的目标格式为AV_SAMPLE_FMT_S16,如果原始音频格式不是它,则对音频做转码:

audioStreamDecoder.Init(reader, audioIndex, AVSampleFormat::AV_SAMPLE_FMT_S16);

boolAudioStreamDecoder::Init(MediaReader*reader, intstreamIndex, AVSampleFormatsampleFormat) {

...

boolresult=StreamDecoder::Init(reader, streamIndex);

if (sampleFormat==AVSampleFormat::AV_SAMPLE_FMT_NONE) {

mSampleFormat=mCodecContext->sample_fmt;

} else {

mSampleFormat=sampleFormat;

}

if (mSampleFormat!=mCodecContext->sample_fmt) {

mSwrContext=swr_alloc_set_opts(

NULL,

mCodecContext->channel_layout,

mSampleFormat,

mCodecContext->sample_rate,

mCodecContext->channel_layout,

mCodecContext->sample_fmt,

mCodecContext->sample_rate,

0,

NULL);

swr_init(mSwrContext);

// 虽然前面的swr_alloc_set_opts已经设置了这几个参数

// 但是用于接收的AVFrame不设置这几个参数也会接收不到数据

// 原因是后面的swr_convert_frame函数会通过av_frame_get_buffer创建数据的buff

// 而av_frame_get_buffer需要AVFrame设置好这些参数去计算buff的大小

mSwrFrame=av_frame_alloc();

mSwrFrame->channel_layout=mCodecContext->channel_layout;

mSwrFrame->sample_rate=mCodecContext->sample_rate;

mSwrFrame->format=mSampleFormat;

}

returnresult;

}

AVFrame*AudioStreamDecoder::NextFrame() {

AVFrame*frame=StreamDecoder::NextFrame();

if (NULL==frame) {

returnNULL;

}

if (NULL==mSwrContext) {

returnframe;

}

swr_convert_frame(mSwrContext, mSwrFrame, frame);

returnmSwrFrame;

}

这里我们使用swr_convert_frame进行转码:

intswr_convert_frame(SwrContext*swr, // 转码上下文

AVFrame*output, // 转码后输出到这个AVFrame

constAVFrame*input// 原始输入AVFrame

);

这个方法要求输入输出的AVFrame都设置了channel_layout、 sample_rate、format参数,然后回调用av_frame_get_buffer为output创建数据buff:

/**

* ...

*

* Input and output AVFrames must have channel_layout, sample_rate and format set.

*

* If the output AVFrame does not have the data pointers allocated the nb_samples

* field will be set using av_frame_get_buffer()

* is called to allocate the frame.

* ...

*/

intswr_convert_frame(SwrContext*swr,

AVFrame*output, constAVFrame*input);

SwrContext为转码的上下文,通过swr_alloc_set_opts和swr_init创建,需要把转码前后的音频channel_layout、 sample_rate、format信息传入:

structSwrContext*swr_alloc_set_opts(structSwrContext*s,

int64_tout_ch_layout, enumAVSampleFormatout_sample_fmt, intout_sample_rate,

int64_t in_ch_layout, enumAVSampleFormat in_sample_fmt, int in_sample_rate,

intlog_offset, void*log_ctx);

intswr_init(structSwrContext*s);

4、视频格式转换


之前的demo里面我们判断了视频格式不为AV_PIX_FMT_YUV420P则直接报错,这里我们仿照音频转换的例子,判断原始视频格式不为AV_PIX_FMT_YUV420P则使用sws_scale进行格式转换:

boolVideoStreamDecoder::Init(MediaReader*reader, intstreamIndex, AVPixelFormatpixelFormat) {

...

boolresult=StreamDecoder::Init(reader, streamIndex);

if (AVPixelFormat::AV_PIX_FMT_NONE==pixelFormat) {

mPixelFormat=mCodecContext->pix_fmt;

} else {

mPixelFormat=pixelFormat;

}

if (mPixelFormat!=mCodecContext->pix_fmt) {

intwidth=mCodecContext->width;

intheight=mCodecContext->height;

mSwrFrame=av_frame_alloc();

// 方式一,使用av_frame_get_buffer创建数据存储空间,av_frame_free的时候会自动释放

mSwrFrame->width=width;

mSwrFrame->height=height;

mSwrFrame->format=mPixelFormat;

av_frame_get_buffer(mSwrFrame, 0);

// 方式二,使用av_image_fill_arrays指定存储空间,需要我们手动调用av_malloc、av_free去创建、释放空间

// unsigned char* buffer = (unsigned char *)av_malloc(

// av_image_get_buffer_size(mPixelFormat, width, height, 16)

// );

// av_image_fill_arrays(mSwrFrame->data, mSwrFrame->linesize, buffer, mPixelFormat, width, height, 16);

mSwsContext=sws_getContext(

mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt,

width, height, mPixelFormat, SWS_BICUBIC,

NULL, NULL, NULL

);

}

returnresult;

}

AVFrame*VideoStreamDecoder::NextFrame() {

AVFrame*frame=StreamDecoder::NextFrame();

if (NULL==frame) {

returnNULL;

}

if (NULL==mSwsContext) {

returnframe;

}

sws_scale(mSwsContext, frame->data,

frame->linesize, 0, mCodecContext->height,

mSwrFrame->data, mSwrFrame->linesize);

returnmSwrFrame;

}

sws_scale看名字虽然是缩放,但它实际上也会对format进行转换,转换的参数由SwsContext提供:

structSwsContext*sws_getContext(

intsrcW, // 源图像的宽

intsrcH, // 源图像的高

enumAVPixelFormatsrcFormat, // 源图像的格式

intdstW, // 目标图像的宽

intdstH, // 目标图像的高

enumAVPixelFormatdstFormat, // 目标图像的格式

intflags, // 暂时可忽略

SwsFilter*srcFilter, // 暂时可忽略

SwsFilter*dstFilter, // 暂时可忽略

constdouble*param // 暂时可忽略

);

sws_scale支持区域转码,可以如我们的demo将整幅图像进行转码,也可以将图像切成多个区域分别转码,这样方便实用多线程加快转码效率:

intsws_scale(

structSwsContext*c, // 转码上下文

constuint8_t*constsrcSlice[], // 源画面区域像素数据,对应源AVFrame的data字段

constintsrcStride[], // 源画面区域行宽数据,对应源AVFrame的linesize字段

intsrcSliceY, // 源画面区域起始Y坐标,用于计算应该放到目标图像的哪个位置

intsrcSliceH, // 源画面区域行数,用于计算应该放到目标图像的哪个位置

uint8_t*constdst[], // 转码后图像数据存储,对应目标AVFrame的data字段

constintdstStride[] // 转码后行宽数据存储,对应目标AVFrame的linesize字段

);

srcSlice和srcStride存储了源图像部分区域的图像数据,srcSliceY和srcSliceH告诉转码器这部分区域的坐标范围,用于计算偏移量将转码结果存放到dst和dstStride中。

例如下面的代码就将一幅完整的图像分成上下两部分分别进行转码:

inthalfHeight=mCodecContext->height/2;

// 转码上半部分图像

uint8_t*dataTop[AV_NUM_DATA_POINTERS] = {

frame->data[0],

frame->data[1],

frame->data[2]

};

sws_scale(mSwsContext, dataTop,

frame->linesize, 0,

halfHeight,

mSwrFrame->data, mSwrFrame->linesize);

// 转码下半部分图像

uint8_t*dataBottom[AV_NUM_DATA_POINTERS] = {

frame->data[0] + (frame->linesize[0] *halfHeight),

frame->data[1] + (frame->linesize[1] *halfHeight),

frame->data[2] + (frame->linesize[2] *halfHeight),

};

sws_scale(mSwsContext, dataBottom,

frame->linesize, halfHeight,

mCodecContext->height-halfHeight,

mSwrFrame->data, mSwrFrame->linesize);

5、AVFrame内存管理机制


我们创建了一个新的AVFrame用于接收转码后的图像:

mSwrFrame=av_frame_alloc();

// 方式一,使用av_frame_get_buffer创建数据存储空间,av_frame_free的时候会自动释放

mSwrFrame->width=width;

mSwrFrame->height=height;

mSwrFrame->format=mPixelFormat;

av_frame_get_buffer(mSwrFrame, 0);

// 方式二,使用av_image_fill_arrays指定存储空间,需要我们手动调用av_malloc、av_free去创建、释放buffer的空间

// int bufferSize = av_image_get_buffer_size(mPixelFormat, width, height, 16);

// unsigned char* buffer = (unsigned char *)av_malloc(bufferSize);

// av_image_fill_arrays(mSwrFrame->data, mSwrFrame->linesize, buffer, mPixelFormat, width, height, 16);

av_frame_alloc创建出来的AVFrame只是一个壳,我们需要为它提供实际存储像素数据和行宽数据的内存空间,如上所示有两种方法:

1.通过av_frame_get_buffer创建存储空间,data成员的空间实际上是由buf[0]->data提供的:

LOGD("mSwrFrame --> buf : 0x%X~0x%X, data[0]: 0x%X, data[1]: 0x%X, data[2]: 0x%X",

mSwrFrame->buf[0]->data,

mSwrFrame->buf[0]->data+mSwrFrame->buf[0]->size,

mSwrFrame->data[0],

mSwrFrame->data[1],

mSwrFrame->data[2]

);

// mSwrFrame --> buf : 0x2E6E8AC0~0x2E753F40, data[0]: 0x2E6E8AC0, data[1]: 0x2E7302E0, data[2]: 0x2E742100

  1. 通过av_image_fill_arrays指定外部存储空间,data成员的空间就是我们指的的外部空间,而buf成员是NULL:

LOGD("mSwrFrame --> buffer : 0x%X~0x%X, buf : 0x%X, data[0]: 0x%X, data[1]: 0x%X, data[2]: 0x%X",

buffer,

buffer+bufferSize,

mSwrFrame->buf[0],

mSwrFrame->data[0],

mSwrFrame->data[1],

mSwrFrame->data[2]

);

// FFmpegDemo: mSwrFrame --> buffer : 0x2DAE4DC0~0x2DB4D5C0, buf : 0x0, data[0]: 0x2DAE4DC0, data[1]: 0x2DB2A780, data[2]: 0x2DB3BEA0

而av_frame_free内部会去释放AVFrame里buf的空间,对于data成员它只是简单的把指针赋值为0,所以通过av_frame_get_buffer创建存储空间,而通过av_image_fill_arrays指定外部存储空间需要我们手动调用av_free去释放外部空间。

6、align


细心的同学可能还看到了av_image_get_buffer_size和av_image_fill_arrays都传了个16的align,这里对应的就是之前讲的linesize的字节对齐,会填充数据让linesize变成16、或者32的整数倍:

@paramalign thevalueusedinsrcforlinesizealignment

这里如果为0会填充失败:

1.png

而为1不做填充会出现和实际解码中的linesize不一致导致画面异常:

2.png

av_frame_get_buffer则比较人性化,它推荐你填0让它自己去判断应该按多少对齐:

*@paramalignRequiredbuffersizealignment. Ifequalto0, alignmentwillbe

* chosenautomaticallyforthecurrentCPU. Itishighly

* recommendedtopass0hereunlessyouknowwhatyouaredoing.

7、完整代码


完整的demo代码已经放到Github上,感兴趣的同学可以下载来看看

原文:https://www.jianshu.com/p/9a093d8b91ef

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python3 入门教程||Python3 条件控制||Python3 循环

Python3 条件控制 if语句 Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块。 Python 中 if 语句的一般形式如下所示&#xff1a; if condition_1:statement_block_1 流程图如下所示&#xff1a; 这种if语句只有在符合条件的时候才会执行代…

华大Flash檫写导致重启异常问题

一、华大Flash写入注意事项由Flash操作说明我们可以看出重要一点&#xff0c;就是檫写Flash函数地址需要定义在32K之前&#xff08;即0x8000之前&#xff09;&#xff0c;否则将写入失败。二、先上的错误的源代码这个代码是我应用中导致硬件卡死重启的&#xff0c;其实也不算错…

FreeSWITCH 呼入系统的简要设计

文章目录1. 呼入处理方案2. 细节处理1. a-leg 的拨号计划2. originate 呼叫坐席1. 呼入处理方案 使用 FreeSWICTH 的 ESL 模块&#xff0c;一个简单的呼入处理时序如下图所示&#xff0c;关键步骤做如下补充: 用户呼入到 FreeSWITCH 实例&#xff0c;a-leg 创建FreeSWITCH 根据…

DC真实数据都有哪些?Filecoin为DC数据存储的解决方案又是什么?

对于生活在数字时代的我们而言&#xff0c;数据或许就和平日呼吸的空气一样&#xff0c;已经不需要我们再去思考其概念。我们的日常生活中无时无刻都有数据的身影&#xff0c;日常的购物消费、出行、学习、记录&#xff0c;当我们每天生活有数字化加持的小区里&#xff0c;工作…

网上图书资料管理系统

技术&#xff1a;Java、JSP等摘要&#xff1a;Internet带给我们的不仅是无穷的信息&#xff0c;更为我们带来了很多的便利。在这个科技高速发展的时代&#xff0c;网络应用十分广泛&#xff0c;所以许多人愿意通过网络来使他们的生活变得更加的方便。网上图书资料管理系统的出现…

Spring Cloud(微服务)学习篇(五)

Spring Cloud(微服务)学习篇(五) 1 nacos配置文件的读取 1.1 访问localhost:8848/index.html并输入账户密码后进入nacos界面并点击配置列表 1.2 点击右侧的号 1.3 点击加号后,进入新建配置界面,并做好如下配置 1.4 往下翻动,点击发布按钮 1.5 发布成功后的界面 1.6 在pom.xml…

ChatGPT解答:PYQT5 的mwindow源码文件里面写了很多的函数方法,随着时间的推移越来越多,代码行数太多,影响了性能,如何解决

ChatGPT解答&#xff1a; PYQT5 的mwindow源码文件里面写了很多的函数方法&#xff0c;随着时间的推移越来越多&#xff0c;代码行数太多&#xff0c;影响了性能&#xff0c;如何解决 以下为可复制的内容与实例&#xff1a; PYQT5 的mwindow源码文件里面写了很多的函数方法&a…

第一道pwn栈溢出题

代码和解题思路来自启明星辰的《ctf安全竞赛入门》&#xff0c;当然还有好多热心的师傅们的指导。1.代码&#xff1a;#include "stdio.h" void shell() {system("/bin/sh"); } void vuln() {printf("Please input your name:\n");char s[8];gets…

实现pdf文件预览

前言 工作上接到的一个任务&#xff0c;实现pdf的在线预览&#xff0c;其实uniapp中已经有对应的api&#xff1a;uni.openDocument(OBJECT)&#xff08;新开页面打开文档&#xff0c;支持格式&#xff1a;doc, xls, ppt, pdf, docx, xlsx, pptx。&#xff09;**实现了相关功能…

冬奥会信息管理系统

摘 要伴随着社会以及科学技术的发展&#xff0c;互联网已经渗透在人们的身边&#xff0c;网络慢慢的变成了人们的生活必不可少的一部分&#xff0c;紧接着网络飞速的发展&#xff0c;系统管理这一名词已不陌生&#xff0c;越来越多的体育馆等机构都会定制一款属于自己个性化的管…

【小程序】盒模型笔记

边框样式参数border中solid是实线&#xff0c;dotted是点状&#xff0c;dashed是虚线。还有其它一些&#xff0c;double(双边框)&#xff0c;groove,ridge,inset,outset等3D边框。可单独拎出来定义边框宽度&#xff0c;border-width由前篇可知&#xff0c;padding\margin上下左…

Python 元类编程实现一个简单的 ORM

概述 什么是ORM?    ORM全称“Object Relational Mapping”&#xff0c;即对象-关系映射&#xff0c;就是把关系数据库的一行映射为一个对象&#xff0c;也就是一个类对应一个表&#xff0c;这样&#xff0c;写代码更简单&#xff0c;不用直接操作SQL语句。 现在我们就要实…

白盒测试重点复习内容

白盒测试白盒测试之逻辑覆盖法逻辑覆盖用例设计方法1.语句覆盖2.判定覆盖(分支覆盖)3.条件覆盖4.判定条件覆盖5.条件组合覆盖6.路径覆盖白盒测试之基本路径测试法基本路径测试方法的步骤1.根据程序流程图画控制流图2.计算圈复杂度3.导出测试用例4.准备测试用例5.例题白盒测试总…

简单介绍编程进制

十进制 十进制的位权为 10&#xff0c;比如十进制的 123&#xff0c;123 1 * 10 ^ 2 2 * 10 ^ 1 3 * 10 ^ 0。 二进制 二进制的位权为 2&#xff0c;比如十进制的 4&#xff0c;二进制为 100&#xff0c;4 1 * 2 ^ 2 0 * 2 ^ 1 0 *2 ^ 0。 Java7 之前&#xff0c;不支…

【PyTorch】教程:torch.nn.Hardshrink

torch.nn.Hardshrink CLASS torch.nn.Hardshrink(lambd0.5) 参数 lambd ([float]) – the λ\lambdaλ 默认为 0.5 定义 HardShrink(x){x,if x>λx,if x<−λ0,otherwise \text{HardShrink}(x) \begin{cases} x, & \text{ if } x > \lambda \\ x, & \text{…

1528. 重新排列字符串

1528. 重新排列字符串https://leetcode.cn/problems/shuffle-string/ 难度简单52收藏分享切换为英文接收动态反馈 给你一个字符串 s 和一个 长度相同 的整数数组 indices 。 请你重新排列字符串 s &#xff0c;其中第 i 个字符需要移动到 indices[i] 指示的位置。 返回重新…

Python写一个自动发送直播弹幕的工具,非常简单

哈喽大家好&#xff0c;今天给大家用Python整一个可以在直播间自动发弹幕的工具&#xff0c;来为喜欢的主播疯狂扣6 &#xff01; 事情原由昨晚回家&#xff0c;表弟在看LOL直播&#xff0c;看得我气不打一处来&#xff0c;差点就想锤他。 身为程序员的表弟&#xff0c;看直…

教师论文|科技专著管理系统

技术&#xff1a;Java、JSP等摘要&#xff1a;随着计算机和互联网技术的发展&#xff0c;社会的信息化程度越来越高&#xff0c;各行各业只有适应这种发展趋势&#xff0c;才能增强自己的适应能力和竞争能力&#xff0c;不断发展壮大。大学作为教育的基地&#xff0c;是社会进步…

Java 方法简介

如果需要经常做某一操作&#xff0c;则需要重复写类似的代码&#xff0c;比如查找某个数。此外&#xff0c;某些复杂的操作需要分成多个步骤进行&#xff0c;以便理解和维护。 为此&#xff0c;计算机引入了函数的概念&#xff0c;用来减少重复代码&#xff0c;分解复杂操作。…

【Servlet篇3】HttpServletResponse的常用方法

HttpServletResponse代表的是一个HTTP请求对应的响应。 在这一篇文章当中&#xff0c;已经提到了HTTP响应是由哪几部分组成的&#xff1a; 【网络原理7】认识HTTP_革凡成圣211的博客-CSDN博客HTTP抓包&#xff0c;Fiddler的使用https://blog.csdn.net/weixin_56738054/articl…