首先的话,我想写感谢一个带我进入音视频处理领域的人,虽然从未谋面,但是是他的博客指引了我学习音视频开发的道路,启蒙了一个曾经迷茫的程序员。但是很可惜,他已经在2016年不幸离开了我们,他是雷霄骅,一位乐于分享的传媒大学大佬。我相信有很多初次接触ffmpeg的人都是从他的博客起步的。我想写这个音视频学习的系列,很大一部分也是觉得应该将大佬分享的精神继承下去,这应该是我认为对他最好的缅怀了....

先展示一下效果:

ps:这里推流的代码也是学习自雷大的最简单的Android推流器

解析一下代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{//引入ffmpeg相关的库以及我们的逻辑代码库native-libstatic {System.loadLibrary("avcodec");System.loadLibrary("avdevice");System.loadLibrary("avfilter");System.loadLibrary("avformat");System.loadLibrary("avutil");System.loadLibrary("swresample");System.loadLibrary("swscale");System.loadLibrary("native-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);requestPermission();TextView tv = findViewById(R.id.tv);tv.setText(stringFromJNI());}@Overridepublic void onClick(View view) {switch (((TextView)view).getText().toString()){case "开始推流":new Thread(new Runnable() {@Overridepublic void run() {start_encode_push(Environment.getExternalStorageDirectory()+"/DCIM/vid.mp4","rtmp://192.168.31.23/zxb/mylive");}}).start();break;case "BeiPusher基于(FFmpeg封装音视频推流)":Intent intent = new Intent(MainActivity.this,LiveActivity.class);startActivity(intent);break;case "BeiPusher+OpenGLEs渲染":intent = new Intent(MainActivity.this,OpenGLRenderActivity.class);startActivity(intent);break;case "BeiPusher+OpenGLEs渲染+MTCNN面部识别":intent = new Intent(MainActivity.this, MTCNNOpenglRenderActivity.class);startActivity(intent);break;}}

在这里,我是用DCIM目录下的vid.mp4视频作为视频源进行演示,推流的地址是在ubuntu运行的SRS视频服务器,如果你觉得搭建麻烦,也可以使用类似于斗鱼这样的直播平台,开一个主播账号,他会分配你一个直播地址,放入到这里效果是一样的。

public native int start_encode_push(String filePath,String url);

进入到start_encode_push(),也就是进入到我们c端的核心代码了

由于jni函数名映射成java函数名的时候是依靠“_”来间隔包、类、方法的,如果你的函数中有“_”字符的话,jni必须能够区分函数名中的“_”是字符还是分隔符,所以在函数名前面需要加1用于区分。

#include <jni.h>
#include <string>
#include<android/log.h>
#include <exception>
#include <iostream>
#include <netdb.h>
#include <sys/socket.h>using namespace std;jobject pushCallback = NULL;
jclass cls = NULL;
jmethodID mid = NULL;
//调用android的日志 否则控制台不会打印任何信息
#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, "PushStream", format, ##__VA_ARGS__)
#define LOGI(format, ...)  __android_log_print(ANDROID_LOG_INFO,  "PushStream", format, ##__VA_ARGS__)
//由于ffmpeg是纯c编写的库,所以需要指定编码器为c
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#include "libswresample/swresample.h"
#include "libavdevice/avdevice.h"
//引入时间
#include "libavutil/time.h"//对方法名有疑问的请看上面的红色字体解释
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_beipush_MainActivity_start_1encode_1push(JNIEnv *env, jobject thiz,jstring file_path, jstring outUrl_) {const char *inUrl = env->GetStringUTFChars(file_path, 0);LOGI("path:%s", inUrl);int videoindex = -1;//所有代码执行之前要调用av_register_all和avformat_network_init//初始化所有的封装和解封装 flv mp4 mp3 mov。不包含编码和解码av_register_all();avdevice_register_all();//初始化网络库avformat_network_init();//输出的地址const char *outUrl = env->GetStringUTFChars(outUrl_, 0);////                   输入流处理部分/ //输入上下文AVFormatContext *ictx = NULL;//输出上下文AVFormatContext *octx = NULL;AVPacket pkt;int ret = 0;char buf[1024] = {0};//打开文件,解封文件头ret = avformat_open_input(&ictx, inUrl, 0, NULL);if (ret < 0) {av_strerror(ret, buf, 1024);LOGE("Couldn't open file %s: %d(%s)", inUrl, ret, buf);LOGE("打开文件失败:%d", ret);return ret;}LOGI("avformat_open_input success!");//获取音频视频的信息 .h264 flv 没有头信息//将音视频信息写入ictxret = avformat_find_stream_info(ictx, 0);if (ret != 0) {LOGE("获取音视频信息失败:%d", ret);return ret;}//0打印所有音视频相关信息  inUrl 打印时候显示,av_dump_format(ictx, 0, inUrl, 0);////                   输出流处理部分///如果是输入文件 flv可以不传,可以从文件中判断。如果是流则必须传//创建输出上下文ret = avformat_alloc_output_context2(&octx, NULL, "flv", outUrl);if (ret < 0) {LOGE("创建音视频输出流失败:%d", ret);return ret;}LOGI("avformat_alloc_output_context2 success!");int i;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 |= AV_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;}//找到视频流的位置for (i = 0; i < ictx->nb_streams; i++) {if (ictx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {videoindex = i;break;}}av_dump_format(octx, 0, outUrl, 1);////                   准备推流///打开IOret = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);if (ret < 0) {LOGE("输出流io打开错误");return ret;}LOGI("avio_open success!");//写入头部信息ret = avformat_write_header(octx, 0);if (ret < 0) {LOGE("头部信息写入错误");return ret;}LOGI("avformat_write_header Success!");//推流每一帧数据//int64_t pts  [ pts*(num/den)  第几秒显示]//int64_t dts  解码时间 [P帧(相对于上一帧的变化) I帧(关键帧,完整的数据) B帧(上一帧和下一帧的变化)]  有了B帧压缩率更高。//获取当前的时间戳  微妙long long start_time = av_gettime();long long frame_index = 0;LOGI("start push >>>>>>>>>>>>>>>");while (1) {//输入输出视频流AVStream *in_stream, *out_stream;//获取解码前数据//这里注意一下 因为MP4和flv同样都是一种视频格式,它里面的视频数据都是h264编码的nalu片段,所以直接取出重新打包即可ret = av_read_frame(ictx, &pkt);if (ret < 0) {break;}//没有显示时间(比如未解码的 H.264 )if (pkt.pts == AV_NOPTS_VALUE) {//AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。AVRational time_base = ictx->streams[videoindex]->time_base;//计算两帧之间的时间/*r_frame_rate 帧率 通常是24、25fpsav_q2d 转化为double类型通过帧率计算1秒多少帧 也是一帧应该显示多长时间单位:微秒*/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_base) * AV_TIME_BASE);//微秒/微秒//编码时间等于显示时间pkt.dts = pkt.pts;pkt.duration =(double) calc_duration / (double) (av_q2d(time_base) * AV_TIME_BASE);}//延时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));}}in_stream = ictx->streams[pkt.stream_index];out_stream = octx->streams[pkt.stream_index];//计算延时后,重新指定时间戳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);pkt.pos = -1;if (pkt.stream_index == videoindex) {LOGI("Send %lld video frames to output URL\n", frame_index);frame_index++;}//回调数据 仅测试使用
//        callback(env, pkt.pts, pkt.dts, pkt.duration, frame_index);LOGI("发送H264裸流:%lld",pkt.pts);//向输出上下文发送(向地址推送)ret = av_interleaved_write_frame(octx, &pkt);if (ret < 0) {LOGI("S发送数据包出错\n");break;}//释放av_packet_unref(&pkt);}ret = 0;LOGI("finish===============");//关闭输出上下文,这个很关键。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(file_path, inUrl);env->ReleaseStringUTFChars(outUrl_, outUrl);return ret;
}

代码注释的还是比较详细的,如果有疑问,或者是不正确的地方,欢迎评论区告诉我!!!!

谢谢大家阅读,共同进步...

(三)利用ffmpeg实现简单的MP4推流相关推荐

  1. Java 利用ffmpeg工具实现视频MP4转m3u8

    Java 利用ffmpeg工具实现视频MP4转m3u8(一) 前言 (一)ffmpeg工具转码 1.如何安装ffmpeg工具 2.如何使用ffmpeg工具进行视频转码 (二)播放m3u8文件 1.vi ...

  2. 利用 FFmpeg 进行简单的音频拼接 降噪 合成

    ** 利用 FFmpeg 进行简单的音频拼接 降噪 合成 ** 项目需要M4a 格式的音频拼接,由于 m4a 不能像 mp3 直接以流的方式进行拼接,所以简单学习了一下 FFmepeg .在这里分享一 ...

  3. 利用FFmpeg转码生成MP4文件

    利用FFmpeg转码生成MP4文件 2017年06月24日 14:42:53 阅读数:2401 项目中,需要把一路音频流及一路视频流分别转码,生成指定格式(MP4)文件.在使用ffmpeg转码生成mp ...

  4. 记录一下利用ffmpeg将avi转为mp4

    ffmpeg -i .\Video.avi -c copy -map 0 video.mp4 或 ffmpeg -i .\Video.avi -c:v libx264 -crf 19 -preset ...

  5. Android录屏并利用FFmpeg转换成gif(三) 在Android中使用ffmpeg命令

    Android录屏并利用FFmpeg转换成gif(三) 写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲这种方 ...

  6. Android录屏并利用FFmpeg转换成gif(四) 将mp4文件转换成gif文件

    Android录屏并利用FFmpeg转换成gif(四) 写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲这种方 ...

  7. Android录屏并利用FFmpeg转换成gif(一)录屏

    Android录屏并利用FFmpeg转换成gif(一) 录屏 写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲 ...

  8. Android录屏并利用FFmpeg转换成gif(二)交叉编译FFmpeg源码

    Android录屏并利用FFmpeg转换成gif(二) 写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲这种方 ...

  9. ffmpeg实现摄像头拉流_利用ffmpeg一步一步编程实现摄像头采集编码推流直播系统...

    了解过ffmpeg的人都知道,利用ffmpeg命令即可实现将电脑中摄像头的画面发布出去,例如发布为UDP,RTP,RTMP等,甚至可以发布为HLS,将m3u8文件和视频ts片段保存至Web服务器,普通 ...

最新文章

  1. [转]Android JNI使用方法
  2. Android数据存储
  3. 被声明为已否决 解决方法
  4. Windows 服务器安全维护知识
  5. NLPCC:预训练在小米的推理优化落地
  6. How Do Annotations Work in Java?--转
  7. 去 BAT 面试,总结了这 55 道 MySQL 面试题!
  8. python网页优化_python大佬养成计划----JavaScript对html的优化
  9. CONVERSION_EXIT_SDATE_OUTPUT
  10. 复现网状的记忆Transformer图像描述模型(失败)
  11. 360公布权威机构对扣扣保镖的测试报告
  12. sdram 时钟相位_Nios II 和SDRAM时钟相位计算
  13. 学习分享|量化风控从入门到放弃
  14. 二段式提交和三段式提交
  15. 软件测试岗位具体是做什么的?
  16. 英语老师自用省心天花板小程序
  17. python控制qq群_Python3 selenium 实现QQ群接龙自动化功能
  18. 广州大学计算机投档分数线,2021年广州大学最低投档分数线及录取位次
  19. python数组定义_python定义数组
  20. (四)联想词和top热词的设计与开发

热门文章

  1. events.js:187 throw er; // Unhandled 'error' event ^ Error: connect ETIMEDOUT at Co
  2. 计算机摄影采用的成像媒介,核磁共振成像射频脉冲生成方法、系统和计算机可读媒介...
  3. 【嵌入式】计算机体系结构:冯诺依曼架构和哈佛架构
  4. 蓝牙定位技术在智慧养老系统中的应用
  5. Starling 显示原理与优化
  6. AR实践:基于ARKit实现电影中的全息视频会议
  7. 后端分页+前端分页显示(Angular+Primeng+SpringBoot)
  8. 用注意力probe估计神经网络组件提供多少句法信息
  9. 是用Process插件还是结构方程模型软件?
  10. 基于单片机热电偶智能体温检测系统设计-基于单片机一氧化碳CO有毒气体采集报警系统设计-基于单片机热电偶智能体温检测系统设计(仿真,原理图,报告)【资料转发分享】