简介
前面已经讲到如何在Linux环境下编译FFmpeg以及在Android项目中使用,这一节就开始真正的使用FFmpeg。在Android平台下用FFmepg解析视频文件并进行RTMP推流。如果对FFmpeg基础不熟或者不知道如何在Android项目中使用,请先阅读流媒体专栏里之前的文章。
注意:这里的工程沿用Linux下FFmpeg编译以及Android平台下使用里的工程和结构。
- 新增推流函数
- 异常处理
- 设置回调方法
- 常见问题
- 源码
新增推流函数
首先我们将所有FFmpeg的操作抽取到一个类里面,然后增加推流方法。
public class FFmpegHandle {
private static FFmpegHandle mInstance;
public static void init(Context context) {
mInstance = new FFmpegHandle();
}
public static FFmpegHandle getInstance() {
if (mInstance == null) {
throw new RuntimeException("FFmpegHandle must init fist");
}
return mInstance;
}
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("swscale-4");
System.loadLibrary("avfilter-6");
System.loadLibrary("avdevice-57");
System.loadLibrary("ffmpeg-handle");
}
public native int setCallback(PushCallback pushCallback);
public native String getAvcodecConfiguration();
public native int pushRtmpFile(String path);
}
我们先看到public native int pushRtmpFile(String path);
方法,这里主要传入的参数是文件的路径。然后在cpp层的代码中也增加方法
JNIEXPORT jint JNICALL
Java_com_wangheart_rtmpfile_ffmpeg_FFmpegHandle_pushRtmpFile(JNIEnv *env, jobject instance,
jstring path_) {
...省略代码
}
接下来就到了cpp层的开发,基本上和基于FFmpeg进行RTMP推流(二)中使用的代码一致,我们直接拷贝过来即可。至于FFmpeg的使用,这里就不重复讲了,不懂的可以看之前的文章。源码见末尾
异常处理
在我们之前的推流代码中,并没有做异常处理。这样在正式的使用中肯定不太好的。所以我们加上try catch。统一进行资源释放。源码见末尾
设置回调方法
为了方便我们查看推流的信息,我们新增一个回调类。
- FFmpegHandle增加本地调用方法
public native int setCallback(PushCallback pushCallback);
- 同样cpp层也需要增加对应函数
/**
* 设置回到对象
*/
extern "C"
JNIEXPORT jint JNICALL
Java_com_wangheart_rtmpfile_ffmpeg_FFmpegHandle_setCallback(JNIEnv *env, jobject instance,
jobject pushCallback1) {
//转换为全局变量
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);
return 0;
}
这里有个重点,就是对于传递进来的对象jobject pushCallback1
是局部变量。而我们需要在推流的时候使用到这个对象,所以需要转化成全局变量
pushCallback = env->NewGlobalRef(pushCallback1);
同样也需要定义对应的全局变量
jobject pushCallback = NULL;
jclass cls = NULL;
jmethodID mid = NULL;
GetObjectClass、GetMethodID、CallVoidMethod这几个方法看名称也知道其功能。我们在设置回到对象时候就讲方法获取出来,后面就不需要每次去查找。
cls = env->GetObjectClass(pushCallback);
if (cls == NULL) {
return -1;
}
mid = env->GetMethodID(cls, "videoCallback", "(JJJJ)V");
if (mid == NULL) {
return -2;
}
- 定义一个本地回调方法
int callback(JNIEnv *env, int64_t pts, int64_t dts, int64_t duration, long long index) {
// logw("=================")
if (pushCallback == NULL) {
return -3;
}
if (cls == NULL) {
return -1;
}
if (mid == NULL) {
return -2;
}
env->CallVoidMethod(pushCallback, mid, (jlong) pts, (jlong) dts, (jlong) duration,
(jlong) index);
return 0;
}
这样我们在推流的过程中就可以调用callback函数,将数据回调到java层。
//回调数据
callback(env, pkt.pts, pkt.dts, pkt.duration, frame_index);
- java层设置回调对象
int res = FFmpegHandle.getInstance().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() {
StringBuilder sb = new StringBuilder();
sb.append("pts: ").append(pts).append("\n");
sb.append("dts: ").append(dts).append("\n");
sb.append("duration: ").append(duration).append("\n");
sb.append("index: ").append(index).append("\n");
tvPushInfo.setText(sb.toString());
}
});
}
});
常见问题
第二次推流出现Operation not permitted
这是因为第一次推流完没有关闭输出上下文
//关闭输出上下文,这个很关键。Linux下FFmpeg编译以及Android平台下使用
if (octx != NULL)
avio_close(octx->pb);
加上即可。
源码
Github源码地址注意下载对应的版本
FFmpegHandle.java
public class FFmpegHandle {
private static FFmpegHandle mInstance;
public static void init(Context context) {
mInstance = new FFmpegHandle();
}
public static FFmpegHandle getInstance() {
if (mInstance == null) {
throw new RuntimeException("FFmpegHandle must init fist");
}
return mInstance;
}
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("swscale-4");
System.loadLibrary("avfilter-6");
System.loadLibrary("avdevice-57");
System.loadLibrary("ffmpeg-handle");
}
public native int setCallback(PushCallback pushCallback);
public native String getAvcodecConfiguration();
public native int pushRtmpFile(String path);
}
MainActivity.java
package com.wangheart.rtmpfile;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.wangheart.rtmpfile.ffmpeg.FFmpegHandle;
import com.wangheart.rtmpfile.ffmpeg.PushCallback;
import java.io.File;
public class MainActivity extends Activity {
private TextView tvCodecInfo;
private TextView tvPushInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initView() {
tvCodecInfo = findViewById(R.id.tv_codec_info);
tvPushInfo = findViewById(R.id.tv_push_info);
}
private void initData() {
FFmpegHandle.init(this);
tvCodecInfo.setText(FFmpegHandle.getInstance().getAvcodecConfiguration());
int res = FFmpegHandle.getInstance().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() {
StringBuilder sb = new StringBuilder();
sb.append("pts: ").append(pts).append("\n");
sb.append("dts: ").append(dts).append("\n");
sb.append("duration: ").