Android 音视频深入 十五 FFmpeg 推流mp4文件(附源码下载)
源码地址
https://github.com/979451341/Rtmp
1.配置RTMP服务器
这个我不多说贴两个博客分别是在mac和windows环境上的,大家跟着弄
MAC搭建RTMP服务器
https://www.jianshu.com/p/6fcec3b9d644
这个是在windows上的,RTMP服务器搭建(crtmpserver和nginx)
https://www.jianshu.com/p/c71cc39f72ec
2.关于推流输出的ip地址我好好说说
我这里是手机开启热点,电脑连接手机,这个RTMP服务器的推流地址有localhost,服务器在电脑上,对于电脑这个localhost是127.0.0.1,但是对于外界比如手机,你不能用localhost,而是用这个电脑的在这个热点也就是局域网的ip地址,不是127.0.0.1这个只代表本设备节点的ip地址,这个你需要去手机设置——》更多——》移动网络共享——》便携式WLAN热点——》管理设备列表,就可以看到电脑的局域网ip地址了
3.说说代码
注册组件,第二个如果不加的话就不能获取网络信息,比如类似url
av_register_all();
avformat_network_init();
获取输入视频的信息,和创建输出url地址的环境
av_dump_format(ictx, 0, inUrl, 0);
ret = avformat_alloc_output_context2(&octx, NULL, "flv", outUrl);
if (ret < 0) {
avError(ret);
throw ret;
}
将输入视频流放入刚才创建的输出流里
for (i = 0; i < ictx->nb_streams; i++) {
//获取输入视频流
AVStream *in_stream = ictx->streams[i];
//为输出上下文添加音视频流(初始化一个音视频流容器)
AVStream *out_stream = avformat_new_stream(octx, in_stream->codec->codec);
if (!out_stream) {
printf("未能成功添加音视频流\n");
ret = AVERROR_UNKNOWN;
}
if (octx->oformat->flags & AVFMT_GLOBALHEADER) {
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
if (ret < 0) {
printf("copy 编解码器上下文失败\n");
}
out_stream->codecpar->codec_tag = 0;
// out_stream->codec->codec_tag = 0;
}
打开输出url,并写入头部数据
//打开IO
ret = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);
if (ret < 0) {
avError(ret);
throw ret;
}
logd("avio_open success!");
//写入头部信息
ret = avformat_write_header(octx, 0);
if (ret < 0) {
avError(ret);
throw ret;
}
然后开始循环解码并推流数据
首先获取一帧的数据
ret = av_read_frame(ictx, &pkt);
然后给这一帧的数据配置参数,如果原有配置没有时间就配置时间,我在这里再提两个概念
DTS(解码时间戳)和PTS(显示时间戳)分别是解码器进行解码和显示帧时相对于SCR(系统参考)的时间戳。SCR可以理解为解码器应该开始从磁盘读取数据时的时间。
if (pkt.pts == AV_NOPTS_VALUE) {
//AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。
AVRational time_base1 = ictx->streams[videoindex]->time_base;
int64_t calc_duration =
(double) AV_TIME_BASE / av_q2d(ictx->streams[videoindex]->r_frame_rate);
//配置参数
pkt.pts = (double) (frame_index * calc_duration) /
(double) (av_q2d(time_base1) * AV_TIME_BASE);
pkt.dts = pkt.pts;
pkt.duration =
(double) calc_duration / (double) (av_q2d(time_base1) * AV_TIME_BASE);
}
调节播放时间,就是当初我们解码视频之前记录了一个当前时间,然后在循环推流的时候又获取一次当前时间,两者的差值是我们视频应该播放的时间,如果视频播放太快就进程休眠 pkt.dts减去实际播放的时间的差值
if (pkt.stream_index == videoindex) {
AVRational time_base = ictx->streams[videoindex]->time_base;
AVRational time_base_q = {1, AV_TIME_BASE};
//计算视频播放时间
int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
//计算实际视频的播放时间
int64_t now_time = av_gettime() - start_time;
AVRational avr = ictx->streams[videoindex]->time_base;
cout << avr.num << " " << avr.den << " " << pkt.dts << " " << pkt.pts << " "
<< pts_time << endl;
if (pts_time > now_time) {
//睡眠一段时间(目的是让当前视频记录的播放时间与实际时间同步)
av_usleep((unsigned int) (pts_time - now_time));
}
}
如果延时了,这一帧的配置所记录的时间就应该改变
//计算延时后,重新指定时间戳
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
(AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
(AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = (int) av_rescale_q(pkt.duration, in_stream->time_base,
out_stream->time_base);
回调这一帧的时间参数,这里在MainActivity里实例化了接口,显示播放时间
int res = FFmpegHandle.setCallback(new PushCallback() {
@Override
public void videoCallback(final long pts, final long dts, final long duration, final long index) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(pts == -1){
tvPushInfo.setText("播放结束");
return ;
}
tvPushInfo.setText("播放时间:"+dts/1000+"秒");
}
});
}
});
然后段代码调用了c语言的setCallback函数,获取了接口的实例,和接口的videoCallback函数引用,这里还调用了一次这个函数初始化时间显示
//转换为全局变量
pushCallback = env->NewGlobalRef(pushCallback1);
if (pushCallback == NULL) {
return -3;
}
cls = env->GetObjectClass(pushCallback);
if (cls == NULL) {
return -1;
}
mid = env->GetMethodID(cls, "videoCallback", "(JJJJ)V");
if (mid == NULL) {
return -2;
}
env->CallVoidMethod(pushCallback, mid, (jlong) 0, (jlong) 0, (jlong) 0, (jlong) 0);
这个时候我们回到循环推流一帧帧数据的时候调用videoCallback函数
env->CallVoidMethod(pushCallback, mid, (jlong) pts, (jlong) dts, (jlong) duration,
(jlong) index);
然后就是向输出url输出数据,并释放这一帧的数据
ret = av_interleaved_write_frame(octx, &pkt);
av_packet_unref(&pkt);
释放资源
//关闭输出上下文,这个很关键。
if (octx != NULL)
avio_close(octx->pb);
//释放输出封装上下文
if (octx != NULL)
avformat_free_context(octx);
//关闭输入上下文
if (ictx != NULL)
avformat_close_input(&ictx);
octx = NULL;
ictx = NULL;
env->ReleaseStringUTFChars(path_, path);
env->ReleaseStringUTFChars(outUrl_, outUrl);
最后回调时间显示,说播放结束
callback(env, -1, -1, -1, -1);
4.关于接收推流数据
我这里使用的是VLC,这个mac和windows都有版本,FILE——》OPEN NETWORK,输入之前的输出url就可以了。这里要注意首先在app上开启推流再使用VLC打开url才可以
效果如下
参考文章
https://www.jianshu.com/p/dcac5da8f1da
这个博主对于推流真的熟练,大家如果对推流还想输入了解可以看看他的博客
Android 音视频深入 十五 FFmpeg 推流mp4文件(附源码下载)的更多相关文章
- Android 音视频深入 十六 FFmpeg 推流手机摄像头,实现直播 (附源码下载)
源码地址https://github.com/979451341/RtmpCamera/tree/master 配置RMTP服务器,虽然之前说了,这里就直接粘贴过来吧 1.配置RTMP服务器 这个我不 ...
- Android 音视频深入 十二 FFmpeg视频替换声音(附源码下载)
项目地址,求starhttps://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpeg%E7%BB%99%E8%A7%86%E ...
- Android 音视频深入 四 录视频MP4(附源码下载)
本篇项目地址,名字是<录音视频(有的播放器不能放,而且没有时长显示)>,求star https://github.com/979451341/Audio-and-video-learnin ...
- Android 音视频深入 十四 FFmpeg与OpenSL ES 播放mp3音乐,能暂停(附源码下载)
项目地址https://github.com/979451341/FFmpegOpenslES 这次说的是FFmpeg解码mp3,数据给OpenSL ES播放,并且能够暂停. 1.创建引擎 slCre ...
- Android 音视频深入 十八 FFmpeg播放视频,有声音(附源码下载)
项目地址https://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpegv%E6%92%AD%E6%94%BE%E8%A7% ...
- arcgis api 3.x for js 入门开发系列十八风向流动图(附源码下载)
前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...
- Android中Canvas绘图基础详解(附源码下载) (转)
Android中Canvas绘图基础详解(附源码下载) 原文链接 http://blog.csdn.net/iispring/article/details/49770651 AndroidCa ...
- Android 音视频深入 六 使用FFmpeg播放视频(附源码下载)
本篇项目地址,求starhttps://github.com/979451341/Audio-and-video-learning-materials/tree/master/FFmpeg%E6%92 ...
- 树莓派开发笔记(十五):树莓派4B+从源码编译安装mysql数据库
前言 树莓派使用数据库时,优先选择sqlite数据库,但是sqlite是文件数据库同时仅针对于单用户的情况,考虑到多用户的情况,在树莓派上部署安装mysql服务,通过读写锁事务等使用,可以实现多进 ...
随机推荐
- windows程序设计 获取系统文件路径
获取系统文件路径,打印到txt文件中. #include <windows.h> int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hP ...
- kubernetes1.4新特性(一):支持sysctl命令
sysctl是一个允许改变正在运行中的Linux系统内核参数的接口.可以通过sysctl修改Linux系统内核中的TCP/IP 堆栈和虚拟内存系统的高级选项,而且不需要重新启动Linux系统,就可以实 ...
- three.js 3d三维网页代码加密的实现方法
http://www.jiamisoft.com/blog/17827-three-js-3dsanweiwangyejiami.html https://www.html5tricks.com/ta ...
- K8S学习笔记之Kubernetes 部署策略详解
0x00 概述 在Kubernetes中有几种不同的方式发布应用,所以为了让应用在升级期间依然平稳提供服务,选择一个正确的发布策略就非常重要了. 选择正确的部署策略是要依赖于我们的业务需求的,下面我们 ...
- 【题解】Luogu P2730 魔板
蒟蒻的第一道蓝题--好像也没有蓝的程度 一篇无STL的超弱题解(入门写法无误了QAQ 传送门 很经典的一道BFS 这是初始状态. 操作A 操作B 操作C 思路1 不使用cantor展开的情况 1. 对 ...
- JS设计模式(4)迭代器模式
什么是迭代器模式? 定义:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示. 主要解决:不同的方式来遍历整个整合对象. 何时使用:遍历一个聚合对象. 如何解决:把在元素之间游 ...
- Android中的task和stack
今天在重新理了一遍intent的过程中发现task是一个神奇的东西,而它又和stack有着很深的联系.task顾名思义是一个任务,但是这个任务可不一定只是来自一个app,比如我用微信来发一张图片,那么 ...
- EJB到底是什么?
EJB到底是什么? 1. 我们不禁要问,什么是"服务集群"?什么是"企业级开发"? 既然说了EJB 是为了"服务集群"和"企业 ...
- spring boot +mybatis 整合 连接数据库测试(从0到1)
spring boot 整合mybatis 1.打开idea创建一个项目 2.在弹出的窗口中选择spring initializr(初始化项目),点击next 3.接下来填写group 与artifa ...
- 【Visual Studio 扩展工具】如何在ComponentOne的DataTree中实现RightToLeft布局
概述 C1FlexGrid提供了创建轮廓树的功能,其中可以显示缩进结构,每个节点行旁边都有折叠/展开图标. 然后,用户可以展开和折叠轮廓以查看所需的细节级别. 为此,C1FlexGrid允许您使用其T ...