音视频实践学习

  • android全平台编译ffmpeg以及x264与fdk-aac实践
  • ubuntu下使用nginx和nginx-rtmp-module配置直播推流服务器
  • android全平台编译ffmpeg合并为单个库实践
  • android-studio使用cmake编译ffmpeg实践
  • android全平台下基于ffmpeg解码MP4视频文件为YUV文件
  • android全平台编译ffmpeg支持命令行实践
  • android全平台基于ffmpeg解码本地MP4视频推流到RTMP服务器
  • android平台下音频编码之编译LAME库转码PCM为MP3
  • ubuntu平台下编译vlc-android视频播放器实践
  • 图解YU12、I420、YV12、NV12、NV21、YUV420P、YUV420SP、YUV422P、YUV444P的区别
  • 图解RGB565、RGB555、RGB16、RGB24、RGB32、ARGB32等格式的区别
  • YUV420P、YUV420SP、NV12、NV21和RGB互相转换并存储为JPEG以及PNG图片
  • android全平台编译libyuv库实现YUV和RGB的转换

概述

整个音视频设计到的模块是很庞大的,之前按照雷神的博客,操作了推流,也成功了,但是没有深入理解这个过程,最近一段时间看了下很多相关的博客,对这个过程有了一点理解,现在重新整理一下这个博客内容,先从最基本的视频推流开始,我们在电脑上使用ffmpeg完成对视频文件推流,很简单,直接使用ffmpeg的推流命令即可。

ffmpeg -re -i input.mp4 -vcodec copy -f flv rtmp://192.168.1.102:1935/onzhou/live

下面会结合一个完整的推流实例,来记录一下这个过程

推流流程分析

如果你和我一样,刚开始接触音视频的相关处理,云里雾里很正常,建议多去看看雷神的博客,再加上自己的理解和实践,会更深刻一些

不太喜欢直接拿别人的原文,无论如何自己都要操作一遍,上图是笔者结合新版本的ffmpeg-3.3.8版本中部分API,重新整理的一个流程图

//注册FFmpeg所有编解码器。
av_register_all()
//初始化网络组件。
avformat_network_init()
//打开一个输入流。
avformat_open_input()
//获取媒体的信息。
avformat_find_stream_info()
//推荐API用来代替avcodec_copy_context()
avcodec_parameters_to_context()
//输出RTMP
avformat_alloc_output_context2()
//申请AVCodecContext空间
avcodec_alloc_context3()
//初始化一个视音频编解码器的AVCodecContext
avcodec_open2()

配置环境

操作系统:ubuntu 16.05
ndk版本:android-ndk-r16b版本
ffmpeg版本:ffmpeg-3.3.8(使用android-ndk-r10e版本编译)

工程实践

新建个子工程:ffmpeg-stream-mp4

配置CMakeLists.txt文件和build.gradle文件比较简单,不多赘述

  • 定义好java层的类文件:主要传递MP4视频文件的路径到native层中处理,第二个参数是目标流地址
package com.onzhou.ffmpeg.streamer;public class NativeStreamer {static {System.loadLibrary("native-stream");}public native int startPublish(String mp4Path, String stream);public native void stopPublish();}

与之对于的native层的类实现:

AVOutputFormat *ofmt = NULL;
AVCodecContext *codec_ctx = NULL;
AVFormatContext *in_fmt = NULL, *out_fmt = NULL;
AVPacket avPacket;//退出标记
int exit_flag = 1;int start_publish(const char *mp4Path, const char *stream) {//记录帧下标int frame_index = 0;//退出标记exit_flag = 1;//1.注册所有组件av_register_all();//2.初始化网络avformat_network_init();//3.打开文件输入if (avformat_open_input(&in_fmt, mp4Path, 0, 0) < 0) {LOGE("Could not open input file.");goto end_line;}//4.查找相关流信息if (avformat_find_stream_info(in_fmt, 0) < 0) {LOGE("Failed to retrieve input stream information");goto end_line;}//遍历视频轨int videoIndex = -1;for (int index = 0; index < in_fmt->nb_streams; index++)if (in_fmt->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {videoIndex = index;break;}//5.初始化输出码流的AVFormatContextavformat_alloc_output_context2(&out_fmt, NULL, "flv", stream); //RTMPif (!out_fmt) {LOGE("Could not create output context");goto end_line;}ofmt = out_fmt->oformat;for (int index = 0; index < in_fmt->nb_streams; index++) {//6. 根据输入流创建一个输出流AVStream *in_stream = in_fmt->streams[index];codec_ctx = avcodec_alloc_context3(NULL);avcodec_parameters_to_context(codec_ctx, in_stream->codecpar);AVStream *out_stream = avformat_new_stream(out_fmt, codec_ctx->codec);if (!out_stream) {LOGE("Failed allocating output stream");goto end_line;}codec_ctx->codec_tag = 0;if (out_fmt->oformat->flags & AVFMT_GLOBALHEADER) {codec_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;}if (avcodec_parameters_from_context(out_stream->codecpar, codec_ctx) < 0) {goto end_line;}}//7.打开网络输出流if (!(ofmt->flags & AVFMT_NOFILE)) {if (avio_open(&out_fmt->pb, stream, AVIO_FLAG_WRITE) < 0) {LOGE("Could not open output URL '%s'", stream);goto end_line;}}//8.写文件头部if (avformat_write_header(out_fmt, NULL) < 0) {LOGE("Error occurred when opening output URL");goto end_line;}AVStream *in_stream = NULL, *out_stream = NULL;//记录开始时间int64_t start_time = av_gettime();//读取帧数据AVPacketwhile (exit_flag && av_read_frame(in_fmt, &avPacket) >= 0) {if (avPacket.stream_index == videoIndex) {//时间基AVRational time_base = in_fmt->streams[videoIndex]->time_base;AVRational time_base_q = {1, AV_TIME_BASE};int64_t pts_time = av_rescale_q(avPacket.dts, time_base, time_base_q);int64_t now_time = av_gettime() - start_time;if (pts_time > now_time) {av_usleep(pts_time - now_time);}}in_stream = in_fmt->streams[avPacket.stream_index];out_stream = out_fmt->streams[avPacket.stream_index];//PTS主要用于度量解码后的视频帧什么时候被显示出来avPacket.pts = av_rescale_q_rnd(avPacket.pts, in_stream->time_base, out_stream->time_base,AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);//DTS主要是标识读入内存中的字节流在什么时候开始送入解码器中进行解码avPacket.dts = av_rescale_q_rnd(avPacket.dts, in_stream->time_base, out_stream->time_base,AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);avPacket.duration = av_rescale_q(avPacket.duration, in_stream->time_base,out_stream->time_base);avPacket.pos = -1;if (avPacket.stream_index == videoIndex) {LOGI("Send %8d video frames to output URL", frame_index);frame_index++;}if (av_interleaved_write_frame(out_fmt, &avPacket) < 0) {LOGE("Error write frame");break;}av_packet_unref(&avPacket);}//9.收尾工作av_write_trailer(out_fmt);end_line://10.关闭avformat_close_input(&in_fmt);if (out_fmt && !(ofmt->flags & AVFMT_NOFILE)) {avio_close(out_fmt->pb);}avformat_free_context(out_fmt);return 0;
}/*** 停止推流*/
void stop_publish() {exit_flag = 0;
}

停止推流的函数比较简单,直接标记exit_flag=0,推流服务器的搭建,可以参考之前的文章ubuntu下使用nginx和nginx-rtmp-module配置直播推流服务器

最后是应用层的调用

public void onStartClick(View view) {mBtnStartPublish.setEnabled(false);mBtnStopPublish.setEnabled(true);if (nowStreamer == null) {nowStreamer = new NativeStreamer();}if (publishDisposable == null) {publishDisposable = Schedulers.newThread().scheduleDirect(new Runnable() {@Overridepublic void run() {final File inputVideo = new File(getExternalFilesDir(null), "input.mp4");nowStreamer.startPublish(inputVideo.getAbsolutePath(), PUBLISH_ADDRESS);}});}
}

运行应用,开始推流

```

我们在局域网中使用vlc播放器,打开网络串流rtmp://192.168.1.102:1935/onzhou/live

PTS/DTS问题

PTS:主要用于度量解码后的视频帧什么时候被显示出来
DTS:主要是标识读入内存中的字节流在什么时候开始送入解码器中进行解码
通常谈论到PTS和DTS的时候,一般都是跟time_base相关联的time_base使用来度量时间概念的,如果把1秒分为25等份,你可以理解就是一把尺,那么每一格表示的就是1/25秒。此时的time_base={1,25}
如果你是把1秒分成90000份,每一个刻度就是1/90000秒,此时的time_base={1,90000}time_base表示的就是每个刻度是多少秒

注意:正常情况下,一个视频文件都会有帧率信息,这个帧率影响画面流畅度(你可以理解为单位时间内出现的视频画面),那么我们在发送数据的时候就需要控制数据的发送间隔,过快和过慢都会导致画面显示不正常,计算PTSDTS间隔时间

项目地址:ffmpeg-stream-mp4
https://github.com/byhook/ffmpeg4android

参考:
https://blog.csdn.net/leixiaohua1020/article/details/47056051
https://blog.csdn.net/leixiaohua1020/article/details/39803457
https://blog.csdn.net/bixinwei22/article/details/78770090

android全平台基于ffmpeg解码本地MP4视频推流到RTMP服务器相关推荐

  1. android推流局域网,android全平台基于ffmpeg解码本地MP4视频推流到RTMP服务器

    音视频实践学习 本文目录 概述 还是先从最简单的搞起来,先从最基本的视频推流开始,要知道在电脑上使用ffmpeg完成推流,简直不要太简单,直接使用ffmpeg的推流命令即可,今天想在android平台 ...

  2. android 编译 aac,android全平台编译ffmpeg以及x264与fdk-aac实践

    目录 编译环境 单独编译完整功能ffmpeg库 第一步:编写config.sh环境配置文件 #NDK路径 export ANDROID_NDK_ROOT=/home/byhook/android/an ...

  3. EasyDarwin+FFmpeg实现本地音视频推流

    所需 EasyDarwin 用作流媒体转发服务器 FFmpeg 用于转码及推流 步骤 1. 安装 EasyDarwin,下载地址 2. 解压后如图: 有两种方法运行 (1) 运行 EasyDarwin ...

  4. android全平台编译libjpeg-turbo并基于ANativeWindow加载JPEG图片

    图形图像实践 android全平台编译libjpeg-turbo并基于ANativeWindow加载JPEG图片 android全平台编译libpng并基于ANativeWindow加载PNG图片 概 ...

  5. 音视频开发之旅(34) - 基于FFmpeg实现简单的视频解码器

    目录 FFmpeg解码过程流程图和关键的数据结构 mp4通过FFmpeg解码YUV裸视频数据 遇到的问题 资料 收获 一.FFmpeg解码过程流程图和关键的数据结构 FFmpeg解码涉及的知识点比较多 ...

  6. Jrtplib发送视频文件 + FFMPEG解码+VFW播放视频 (回调方式)

    在上篇文章<Jrtplib收发H264文件 + FFMPEG解码+VFW播放视频> 里,我们采用的模式是发送端读取本地H264文件, 把完整的Naul(包含起始码) 逐个发送给接收端,接收 ...

  7. 音视频开发(二十七):基于FFmpeg实现简单的视频解码器

    目录 FFmpeg解码过程流程图和关键的数据结构 mp4通过FFmpeg解码YUV裸视频数据 一.FFmpeg解码过程流程图和关键的数据结构 FFmpeg解码涉及的知识点比较多,很容易被函数和结构体搞 ...

  8. QT软件开发-基于FFMPEG设计录屏与rtsp、rtmp推流软件(支持桌面与摄像头)(一)

    QT软件开发-基于FFMPEG设计录屏与rtsp.rtmp推流软件(支持桌面与摄像头)(一) https://xiaolong.blog.csdn.net/article/details/126954 ...

  9. QT软件开发-基于FFMPEG设计录屏与rtsp、rtmp推流软件(支持桌面与摄像头)(四)

    QT软件开发-基于FFMPEG设计录屏与rtsp.rtmp推流软件(支持桌面与摄像头)(一) https://xiaolong.blog.csdn.net/article/details/126954 ...

最新文章

  1. 腾讯以及各大厂的 C++ 开发环境是什么样的?
  2. 【Visual C++】一些开发心得与调试技巧
  3. 教你 7 招,迅速提高服务器并发能力!
  4. Python:if判断与while、for循环语句
  5. Erlang命令行提示符汇总
  6. java 云 代码_云端如何编写Java代码
  7. neo4j 显示名字_Neo4j:绘制“我的名字是……我在工作”图
  8. FLEX组件AnyChart实例教程.
  9. 【Tensorflow】TensorFlow的嵌入layer和多层layer
  10. Spring事务的隔离级别
  11. lvm快照备份mysql
  12. Configure VNC in RHEL 7
  13. HBase Filter及对应Shell
  14. Excel 连接 MySQL 导入数据 自定义 SQL (Excel 2016 + 适用)
  15. 阿里巴巴代码规范 学习总结
  16. 做web网站开发的流程、步骤
  17. apple id两步验证服务器,apple id两步验证 苹果Apple ID两步式验证设置使用教程
  18. 优矿-python计算上证50之间的相关系数
  19. Opencv入门第一课打开窗口
  20. qt界面中禁用某个控件的鼠标滑轮事件

热门文章

  1. 什么是RC版本(转载)
  2. MATLAB当中一些简单的数值分析函数总结
  3. html 灯泡_您可以购买的不同类型的灯泡,以及如何选择
  4. AI语音外呼机器人是如何帮助电销行业获客
  5. 日本房地产泡沫 Japan Real Estate Bubble
  6. flink sql 知其所以然(二)| 自定义 redis 数据维表(附源码)
  7. 近期共享打印机出现打印或联机错误代码为0x0000000b11故障的处理方法
  8. 评价类(观点)题和理解类题目的辨析 区别一:审题干信息和题目要求。
  9. 花 30 美金请 AI 画家弄了个 logo,网友:画得非常好,下次别画了!
  10. 完全免费一级域名强注册地址 免费二级域名注册地址