热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

androidffmpeg优点_Android基于FFmpeg进行RTMP推流(一)

释放双眼,带上耳机,听听看~!简介开发环境FFmpegsdk下载项目配置代码流程开发环境vs2017FFmpegsdk下载下载地址这里下载

释放双眼,带上耳机,听听看~!

简介

开发环境

FFmpeg sdk下载

项目配置

代码流程

开发环境

vs 2017

FFmpeg sdk下载

下载地址

这里下载3.3.3 — 32bit — share和Dev

8.png

Shared包含运行时的动态库在bin目录下

Dev包含开发是编译需要的头文件(include目录下)和库文件(lib目录下)

项目配置

先看下项目的目录结构

9.png

这里的bin、include、lib就是我们刚才在FFmpeg下载的相关文件。

src是我们的项目源码目录。

新建Win32控制台应用程序、选择位置、项目名称。注意:去掉“为结局方案创建目录”的勾选

10.png

然后选择空项目、去掉预编译头。完成项目的创建

11.png

项目属性配置

右击项目属性

【常规】=>【输出目录】 修改为....bin

【调试】=>【工作目录】修改为....bin

【C/C++】=>【常规】=>【附加包含目录】修改为....include

-【链接器】=>【常规】=>【附加库目录】修改为....lib

注意:这里所有的路径都是相对路径,相对于源码的路径

这里设置输出目录到bin。是因为win下运行时会默认在当前运行的目录下寻找dll文件。而我们的dll文件放在bin目录下。

开发流程

Flow Chart.png

流程详解

av_register_all()

该方法初始化所有的封装和解封装。在使用FFmpeg的时候首先要调用这个方法。

找到这个方法的源码libavformatallformats.c

void av_register_all(void)

{

static AVOnce control = AV_ONCE_INIT;

ff_thread_once(&control, register_all);

}

这里控制注册方法只被调用一次register_all是函数指针。看到实现部分。

static void register_all(void)

{

avcodec_register_all();

/* (de)muxers */

REGISTER_MUXER (A64, a64);

REGISTER_DEMUXER (AA, aa);

REGISTER_DEMUXER (AAC, aac);

//...

}

这里面就是进行各种注册,而REGISTER_MUXER 、REGISTER_DEMUXER 是前面定义的宏。我们看到是静态方法,说明该方法只能在所在的文件中使用,这也防止被注册多次。

#define REGISTER_MUXER(X, x)

{

extern AVOutputFormat ff_##x##_muxer;

if (CONFIG_##X##_MUXER)

av_register_output_format(&ff_##x##_muxer);

}

#define REGISTER_DEMUXER(X, x)

{

extern AVInputFormat ff_##x##_demuxer;

if (CONFIG_##X##_DEMUXER)

av_register_input_format(&ff_##x##_demuxer);

}

#define REGISTER_MUXDEMUX(X, x) REGISTER_MUXER(X, x); REGISTER_DEMUXER(X, x)

av_register_input_format和av_register_output_format我们也可以单独去初始化。这里内部细节就不做过多介绍。

avformat_network_init()

网络相关初始化。如果我们使用了网络拉流和推流等等,要先初始化。

avformat_open_input()

声明是

int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);

定义在libavformatutils.c中。主要功能

输入输出结构体AVIOContext的初始化;

输入数据的协议URLProtocol,通过函数指针的方式,与FFMPEG关联,剩下的就是调用该URLProtocol的函数进行open,read等操作了

avformat_find_stream_info

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

可以读取视音频数据并且获得一些相关的信息。定义在libavformatutils.c下

avformat_alloc_output_context2

int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,

const char *format_name, const char *filename);

定义在libavformatmux.c中

ctx:函数调用成功之后创建的AVFormatContext结构体。

oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。

PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。

format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。

filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。

函数执行成功的话,其返回值大于等于0。

内部流程

调用avformat_alloc_context()初始化一个默认的AVFormatContext。

如果指定了输入的AVOutputFormat,则直接将输入的AVOutputFormat赋值给AVOutputFormat的oformat。如果没有指定输入的AVOutputFormat,就需要根据文件格式名称或者文件名推测输出的AVOutputFormat。无论是通过文件格式名称还是文件名推测输出格式,都会调用一个函数av_guess_format()。

avio_open

打开FFmpeg的输入输出文件

int avio_open2(AVIOContext **s, const char *url, int flags,

const AVIOInterruptCB *int_cb, AVDictionary **options);

s:函数调用成功之后创建的AVIOContext结构体。

url:输入输出协议的地址(文件也是一种“广义”的协议,对于文件来说就是文件的路径)。

flags:打开地址的方式。可以选择只读,只写,或者读写。取值如下。

AVIO_FLAG_READ:只读。

AVIO_FLAG_WRITE:只写。

AVIO_FLAG_READ_WRITE:读写。

int_cb:不太清楚

options:不太清楚

avformat_write_header

写视频文件头,av_write_trailer()用于写视频文件尾

av_read_frame

定义在libavformatutils.c中

读取码流中的音频若干帧或者视频一帧。解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。

这里我贴上官方的注释,很详细:

/**

* Return the next frame of a stream.

* This function returns what is stored in the file, and does not validate

* that what is there are valid frames for the decoder. It will split what is

* stored in the file into frames and return one for each call. It will not

* omit invalid data between valid frames so as to give the decoder the maximum

* information possible for decoding.

*

* If pkt->buf is NULL, then the packet is valid until the next

* av_read_frame() or until avformat_close_input(). Otherwise the packet

* is valid indefinitely. In both cases the packet must be freed with

* av_packet_unref when it is no longer needed. For video, the packet contains

* exactly one frame. For audio, it contains an integer number of frames if each

* frame has a known fixed size (e.g. PCM or ADPCM data). If the audio frames

* have a variable size (e.g. MPEG audio), then it contains one frame.

*

* pkt->pts, pkt->dts and pkt->duration are always set to correct

* values in AVStream.time_base units (and guessed if the format cannot

* provide them). pkt->pts can be AV_NOPTS_VALUE if the video format

* has B-frames, so it is better to rely on pkt->dts if you do not

* decompress the payload.

*

* &#64;return 0 if OK, <0 on error or end of file

*/

总结起来每段的核心意思

读取码流中的音频若干帧或者视频一帧

如果pkt->buf是空&#xff0c;那么就要等待下一次av_read_frame调用。否则无法确定是否有效

pts dts duration通常被设置为正确的值。但如果视频帧包括Bzh帧&#xff0c;那么pts可以是AV_NOPTS_VALUE。所以最好依赖dts。

av_interleaved_write_frame

输出一帧视音频数据

核心类

AVFormatContext

AVFormatContext是一个贯穿始终的数据结构&#xff0c;很多函数都要用到它作为参数。它是FFMPEG解封装(flv&#xff0c;mp4&#xff0c;rmvb&#xff0c;avi)功能的结构体。

内部的成员变量&#xff0c;大家可以查看头文件。这里我们列举下一些常用重要的成员变量&#xff1a;

struct AVInputFormat *iformat&#xff1a;输入数据的封装格式

AVIOContext *pb&#xff1a;输入数据的缓存

unsigned int nb_streams&#xff1a;视音频流的个数

AVStream **streams&#xff1a;视音频流

char filename[1024]&#xff1a;文件名

int64_t duration&#xff1a;时长(单位&#xff1a;微秒us&#xff0c;转换为秒需要除以1000000)

int bit_rate&#xff1a;比特率(单位bps&#xff0c;转换为kbps需要除以1000)

AVDictionary *metadata&#xff1a;元数据

视频的原数据(metadata)信息可以通过AVDictionary获取。元数据存储在AVDictionaryEntry结构体中

typedef struct AVDictionaryEntry {

char *key;

char *value;

} AVDictionaryEntry;

每一条元数据分为key和value两个属性。

在ffmpeg中通过av_dict_get()函数获得视频的原数据。

cout <

string meta, key, value;

AVDictionaryEntry *m &#61; NULL;

while (m &#61; av_dict_get(ictx->metadata, "", m, AV_DICT_IGNORE_SUFFIX)) {

key&#61;m->key;

value&#61;m->value;

meta.append(key).append("t:").append(value).append("rn");

}

cout <

AVStream

AVStream是存储每一个视频/音频流信息的结构体。

int index&#xff1a;标识该视频/音频流

AVCodecContext *codec&#xff1a;指向该视频/音频流的AVCodecContext(它们是一一对应的关系)

AVRational time_base&#xff1a;时基。通过该值可以把PTS&#xff0c;DTS转化为真正的时间。- FFMPEG其他结构体中也有这个字段&#xff0c;但是根据我的经验&#xff0c;只有AVStream中的time_base是可用的。PTS*time_base&#61;真正的时间

int64_t duration&#xff1a;该视频/音频流长度

AVDictionary *metadata&#xff1a;元数据信息

AVRational avg_frame_rate&#xff1a;帧率(注&#xff1a;对视频来说&#xff0c;这个挺重要的)

AVPacket attached_pic&#xff1a;附带的图片。比如说一些MP3&#xff0c;AAC音频文件附带的专辑封面。

AVPacket

AVPacket是存储压缩编码数据相关信息的结构体。

uint8_t *data&#xff1a;压缩编码的数据。

例如对于H.264来说。1个AVPacket的data通常对应一个NAL。

注意&#xff1a;在这里只是对应&#xff0c;而不是一模一样。他们之间有微小的差别&#xff1a;使用FFMPEG类库分离出多媒体文件中的H.264码流

因此在使用FFMPEG进行视音频处理的时候&#xff0c;常常可以将得到的AVPacket的data数据直接写成文件&#xff0c;从而得到视音频的码流文件。

int size&#xff1a;data的大小

int64_t pts&#xff1a;显示时间戳

int64_t dts&#xff1a;解码时间戳

int stream_index&#xff1a;标识该AVPacket所属的视频/音频流。

源码

#include

using namespace std;

//引入头文件

extern "C"

{

#include "libavformat/avformat.h"

//引入时间

#include "libavutil/time.h"

}

//引入库

#pragma comment(lib,"avformat.lib")

//工具库&#xff0c;包括获取错误信息等

#pragma comment(lib,"avutil.lib")

//编解码的库

#pragma comment(lib,"avcodec.lib")

int avError(int errNum);

static double r2d(AVRational r)

{

return r.num &#61;&#61; 0 || r.den &#61;&#61; 0 ? 0. : (double)r.num / (double)r.den;

}

int main() {

//所有代码执行之前要调用av_register_all和avformat_network_init

//初始化所有的封装和解封装 flv mp4 mp3 mov。不包含编码和解码

av_register_all();

//初始化网络库

avformat_network_init();

//使用的相对路径&#xff0c;执行文件在bin目录下。test.mp4放到bin目录下即可

const char *inUrl &#61; "test.flv";

//输出的地址

const char *outUrl &#61; "rtmp://192.168.136.131/live/test";

//

// 输入流处理部分

/

//打开文件&#xff0c;解封装 avformat_open_input

//AVFormatContext **ps 输入封装的上下文。包含所有的格式内容和所有的IO。如果是文件就是文件IO&#xff0c;网络就对应网络IO

//const char *url 路径

//AVInputFormt * fmt 封装器

//AVDictionary ** options 参数设置

AVFormatContext *ictx &#61; NULL;

//打开文件&#xff0c;解封文件头

int ret &#61; avformat_open_input(&ictx, inUrl, 0, NULL);

if (ret <0) {

return avError(ret);

}

cout <<"avformat_open_input success!" <

//获取音频视频的信息 .h264 flv 没有头信息

ret &#61; avformat_find_stream_info(ictx, 0);

if (ret !&#61; 0) {

return avError(ret);

}

//打印视频视频信息

//0打印所有 inUrl 打印时候显示&#xff0c;

av_dump_format(ictx, 0, inUrl, 0);

//

// 输出流处理部分

/

AVFormatContext * octx &#61; NULL;

//如果是输入文件 flv可以不传&#xff0c;可以从文件中判断。如果是流则必须传

//创建输出上下文

ret &#61; avformat_alloc_output_context2(&octx, NULL, "flv", outUrl);

if (ret <0) {

return avError(ret);

}

cout <<"avformat_alloc_output_context2 success!" <

//配置输出流

//AVIOcontext *pb //IO上下文

//AVStream **streams 指针数组&#xff0c;存放多个输出流 视频音频字幕流

//int nb_streams;

//duration ,bit_rate

//AVStream

//AVRational time_base

//AVCodecParameters *codecpar 音视频参数

//AVCodecContext *codec

//遍历输入的AVStream

for (int i &#61; 0; i nb_streams; i&#43;&#43;) {

//创建一个新的流到octx中

AVStream *out &#61; avformat_new_stream(octx, ictx->streams[i]->codec->codec);

if (!out) {

return avError(0);

}

//复制配置信息 用于mp4 过时的方法

//ret&#61;avcodec_copy_context(out->codec, ictx->streams[i]->codec);

ret &#61; avcodec_parameters_copy(out->codecpar, ictx->streams[i]->codecpar);

if (ret <0) {

return avError(ret);

}

out->codec->codec_tag &#61; 0;

}

av_dump_format(octx, 0, outUrl, 1);

//

// 准备推流

/

//打开IO

ret &#61; avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);

if (ret <0) {

avError(ret);

}

//写入头部信息

ret &#61; avformat_write_header(octx, 0);

if (ret <0) {

avError(ret);

}

cout <<"avformat_write_header Success!" <

//推流每一帧数据

//int64_t pts [ pts*(num/den) 第几秒显示]

//int64_t dts 解码时间 [P帧(相对于上一帧的变化) I帧(关键帧&#xff0c;完整的数据) B帧(上一帧和下一帧的变化)] 有了B帧压缩率更高。

//uint8_t *data

//int size

//int stream_index

//int flag

AVPacket avPacket;

//获取当前的时间戳 微妙

long long startTime &#61; av_gettime();

while (true)

{

ret &#61; av_read_frame(ictx, &avPacket);

if (ret <0) {

break;

}

cout <

//计算转换时间戳 pts dts

//获取时间基数

AVRational itime &#61; ictx->streams[avPacket.stream_index]->time_base;

AVRational otime &#61; octx->streams[avPacket.stream_index]->time_base;

avPacket.pts &#61; av_rescale_q_rnd(avPacket.pts, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));

avPacket.dts &#61; av_rescale_q_rnd(avPacket.pts, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));

//到这一帧时候经历了多长时间

avPacket.duration &#61; av_rescale_q_rnd(avPacket.duration, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));

avPacket.pos &#61; -1;

//视频帧推送速度

if (ictx->streams[avPacket.stream_index]->codecpar->codec_type &#61;&#61; AVMEDIA_TYPE_VIDEO) {

AVRational tb &#61; ictx->streams[avPacket.stream_index]->time_base;

//已经过去的时间

long long now &#61; av_gettime() - startTime;

long long dts &#61; 0;

dts &#61; avPacket.dts * (1000 * 1000 * r2d(tb));

if (dts > now)

av_usleep(dts - now);

else {

cout <<"sss";

}

}

//推送 会自动释放空间 不需要调用av_packet_unref

ret &#61; av_interleaved_write_frame(octx, &avPacket);

if (ret <0) {

break;

}

//视频帧推送速度

//if (avPacket.stream_index &#61;&#61; 0)

// av_usleep(30 * 1000);

//释放空间。内部指向的视频空间和音频空间

//av_packet_unref(&avPacket);

}

return 0;

}

int avError(int errNum) {

char buf[1024];

//获取错误信息

av_strerror(errNum, buf, sizeof(buf));

cout <<" failed! " <

return -1;

}

彩蛋

上面的代码在推送flv格式文件时候可能没问题&#xff0c;当换成mp4或者rmvb时候可能出现各种问题。如果你是在无法解开这个问题&#xff0c;请看下节基于FFmpeg进行RTMP推流(二)



推荐阅读
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
  • 本文介绍了如何利用Struts1框架构建一个简易的四则运算计算器。通过采用DispatchAction来处理不同类型的计算请求,并使用动态Form来优化开发流程,确保代码的简洁性和可维护性。同时,系统提供了用户友好的错误提示,以增强用户体验。 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • 在Android应用开发中,实现与MySQL数据库的连接是一项重要的技术任务。本文详细介绍了Android连接MySQL数据库的操作流程和技术要点。首先,Android平台提供了SQLiteOpenHelper类作为数据库辅助工具,用于创建或打开数据库。开发者可以通过继承并扩展该类,实现对数据库的初始化和版本管理。此外,文章还探讨了使用第三方库如Retrofit或Volley进行网络请求,以及如何通过JSON格式交换数据,确保与MySQL服务器的高效通信。 ... [详细]
  • 深入解析:React与Webpack配置进阶指南(第二部分)
    在本篇进阶指南的第二部分中,我们将继续探讨 React 与 Webpack 的高级配置技巧。通过实际案例,我们将展示如何使用 React 和 Webpack 构建一个简单的 Todo 应用程序,具体包括 `TodoApp.js` 文件中的代码实现,如导入 React 和自定义组件 `TodoList`。此外,我们还将深入讲解 Webpack 配置文件的优化方法,以提升开发效率和应用性能。 ... [详细]
  • 本文介绍了如何利用Apache POI库高效读取Excel文件中的数据。通过实际测试,除了分数被转换为小数存储外,其他数据均能正确读取。若在使用过程中发现任何问题,请及时留言反馈,以便我们进行更新和改进。 ... [详细]
  • 本文探讨了如何通过编程手段在Linux系统中禁用硬件预取功能。基于Intel® Core™微架构的应用性能优化需求,文章详细介绍了相关配置方法和代码实现,旨在帮助开发人员有效控制硬件预取行为,提升应用程序的运行效率。 ... [详细]
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • Spring框架中枚举参数的正确使用方法与技巧
    本文详细阐述了在Spring Boot框架中正确使用枚举参数的方法与技巧,旨在帮助开发者更高效地掌握和应用枚举类型的数据传递,适合对Spring Boot感兴趣的读者深入学习。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 在本地环境中部署了两个不同版本的 Flink 集群,分别为 1.9.1 和 1.9.2。近期在尝试启动 1.9.1 版本的 Flink 任务时,遇到了 TaskExecutor 启动失败的问题。尽管 TaskManager 日志显示正常,但任务仍无法成功启动。经过详细分析,发现该问题是由 Kafka 版本不兼容引起的。通过调整 Kafka 客户端配置并升级相关依赖,最终成功解决了这一故障。 ... [详细]
  • 本文介绍了一种利用Dom4j库和JFileChooser组件在Java中实现XML文件自定义路径导出的方法。通过创建一个Document对象并设置根元素,结合JFileChooser选择目标路径,实现了灵活的XML文件导出功能。具体步骤包括初始化Document对象、构建XML结构以及使用JFileChooser选择保存路径,确保用户能够方便地将生成的XML文件保存到指定位置。 ... [详细]
author-avatar
手机用户2602886175
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有