//获取流数据,里面的OnGetFrameListener 接口是需要传到JNI层进行回调的 public native void getFrame(OnGetFrameListener frameListener); // 这个是回调接口 public interface OnGetFrameListener { public void getFrameData(FrameDataBean dataBean); }
下面是JNI代码:
//获取要回调的方法ID
getFrameMethodId = (*env).GetMethodID(javaClass,
"方法名",
"方法的相关签名");
// 这个是需要回调的方法中的参数
jclass class_temp = (*env).FindClass("包名/类名");
// 创建一下jni线程,用于处理循环获取实时数据流
pthread_t pt;
// 获取参数对象(object)中的每个参数
env->GetFieldID()
// 给每个参数赋值,根据参数类型的不同选择不同的SetXXField()方法
env->SetObjectField()
在这里要注意:如果是实时的传递大数据,比如大型的数组,我们应该这么处理:
// 创建一个jobject,对应的就是 ByteBuffer
jobject buffer = 0;
buffer = env->NewDirectByteBuffer();
(*env).SetObjectField(X, X, buffer);
这个很关键,浪费我2天时间
首先,我们得要问一下C层同事:给的帧数据中有没有包含帧头部,这与我们之后的解码相关,我这里是没有帧头部的。
这里只给出方法名:
// 参数frame是我们需要处理的帧数据,offset正常没有偏移设为0,length是我们的数组长度(这些都是没有帧头部的情况;假如有帧头部的话,offset就是帧头部的长度,length就是数组长度-帧头部长度)
private void onFrame(byte[] frame, int offset, int length)
可以参考这个链接:https://blog.csdn.net/qq_36467463/article/details/77977562
这个我们可以自己编译ijkplayer,这个有FFmpeg内核处理,需要支持h264 aac h265 http https都可以在相关的ffmpeg文件中进行修改,我自己编译好的so库就不给大家了,毕竟需求不一样。
在jni中开启线程循环获取数据(线程不休眠或者休眠时间很短暂,此【usleep】方法),这时候mediacode解码会报错:MediaCodec.native_dequeueInputBuffer java.lang.IllegalStateException;这样的情况会导致完全展示不了帧画面或者延迟、马赛克。
解决办法:但是经过调试,在jni的线程while循环中usleep 10毫秒的话,可以正常解码,正常播放直播。
原理以及问题的产生点:如果不做线程休眠会导致硬解码这边的缓冲区溢出。
处理:WiFi连接方式改用之前的连接方法,
wifiManager.disableNetwork(wifiManager.getConnectionInfo().getNetworkId());
int netId = wifiManager.addNetwork(getWifiConfig(ssid, capabilities, pws));
boolean isConnected = wifiManager.enableNetwork(netId, true);
Android10 的WiFi连接方式为,
private void connectWifiVersionCodeQ(String ssid, String pws, final ConnectWifi connectWifi) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
.setSsidPattern(new PatternMatcher(ssid, PatternMatcher.PATTERN_PREFIX))
.setWpa2Passphrase(pws)
.build();
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(specifier)
.build();
connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(@NonNull Network network) {
try {
network.bindSocket(TcpTestClass.TCP_TEST_CLASS.getSocket());
} catch (IOException e) {
e.printStackTrace();
LogUtil.LOG_UTIL.e(this, "e: " + e);
}
connectWifi.connectSuccess();
}
@Override
public void onUnavailable() {
connectWifi.connectFail();
}
};
connectivityManager.requestNetwork(request, networkCallback);
}
}
背景:项目播放的AAC是终端设备那边传送过来的实时流帧数据,采用mediacode进行硬解码
问题点:同一样的代码,对于不同的AAC文件,有的可以解码,有的不能解码。例如:
MediaFormat mediaFormat = new MediaFormat();
//数据类型
mediaFormat.setString(MediaFormat.KEY_MIME, mine);
//声道个数
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, KEY_CHANNEL_COUNT);
//采样率
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, KEY_SAMPLE_RATE);
//比特率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 95435);
//用来标记AAC是否有adts头,1->有
mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
//用来标记aac的类型
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
byte[] data = new byte[]{(byte) 0x11, (byte) 0x90};
ByteBuffer csd_0 = ByteBuffer.wrap(data);
mediaFormat.setByteBuffer("csd-0", csd_0);
//解码器配置
mDecoder.configure(mediaFormat, null, null, 0);
这样设置的MediaFormat对应的是这样的AAC:
箭头标注的是AudioTrack的构造函数的参数。
下面是另外一个AAC文件格式,项目中用到的AAC格式:
MediaFormat mediaFormat = new MediaFormat();
//数据类型
mediaFormat.setString(MediaFormat.KEY_MIME, mine);
//声道个数
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, KEY_CHANNEL_COUNT);
//采样率
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, KEY_SAMPLE_RATE);
//比特率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 15678);
//用来标记AAC是否有adts头,1->有
mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
//用来标记aac的类型
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
//ADT头的解码信息
byte[] data = new byte[]{(byte) 0x15, (byte) 0x88};
ByteBuffer csd_0 = ByteBuffer.wrap(data);
mediaFormat.setByteBuffer("csd-0", csd_0);
//解码器配置
mDecoder.configure(mediaFormat, null, null, 0);
两者的能否解码成功,主要还是看 mediaFormat.setByteBuffer("csd-0", csd_0),ADT头的解码信息这块。具体说明可以参考下面这个博客:https://blog.csdn.net/chailongger/article/details/84378721。
一开始用同样的代码,解码失败,各种查看AAC信息,设置采样率、声道等等都解码失败,最后发现关键点还是mediaFormat.setByteBuffer("csd-0", csd_0),ADT头的解码信息这块。还是对于这块不熟啊!!!!!!
首先对于这个不熟悉,看文档以及网上各种搜,大部分都是合成文件形式的demon,这个大家可以去查一下,网上很多的;但是我这项目是实时流,所以这又是一个坑,哈哈哈............
我接下来要讲的内容,希望大家先看一下这篇文章:https://blog.csdn.net/stn_lcd/article/details/72625954
大致流程就是
new MediaMuxer()--->mediaMuxer.addTrack()--->mediaMuxer.start()--->mediaMuxer.writeSampleData()
1、mediaMuxer.addTrack(MediaFormat format)
一、对于h264的format我们最重要的是设置pps和PSP,
mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(csd0));
mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(csd1));
根据h264的PSP、pps规则,0x67之后表示的为PSP,0x68之后的为pps,他们中间以0x00, 0x00, 0x00, 0x01分隔,一般这个都不会变的,我是用notepad++的HEX 16进制插件查看的h264的PSP、pps。
二、对于AAC的format我们则需要设置
mediaFormat.setByteBuffer("csd-0", csd_0),adt头的编码信息?我AAC解码的adt头的解码信息和在这的adt头信息不一样,得要根据声道数、采样率算出对应的16进制
2、MediaCodec.BufferInfo作为mediaMuxer.writeSampleData()中参数,
videoBufferInfo.size = videoByteBuffer.limit();
videoBufferInfo.offset = 0;
videoBufferInfo.flags = type;
videoBufferInfo.presentationTimeUs += 1000 * 1000 / VIDEO_FRAME_RATE;
h264:size、offset不用说了,flags标志的为是否关键帧,我这C层会回调给我I帧是时候,flags=1,其他flags=0,videoBufferInfo.presentationTimeUs表示h264的时间戳(单位微秒),所以我这是1000*1000/帧率之后自增
AAC:size、offset不用说了,flags标志的为是否关键帧,我这都给flags=1了,我用FFmpeg的ffprobe命令查看MP4的AAC信息flags都是关键帧,presentationTimeUs=1024*1000/采样率*1000的自增。
3、什么时候mediaMuxer.addTrack()?
h264、AAC我都是在mDecoder.dequeueOutputBuffer()=INFO_OUTPUT_FORMAT_CHANGED执行的
4、MediaMuxer在调用stop出错的问题:
format没设置正确、MediaCodec.BufferInfo没设置正确、addtrack()方法调用时机不正确等等都会导致
5、合成的视频有马赛克或者花屏?
这个是由于视频帧数据输入的时候不是从I帧关键帧开始的,我们只需要判断实时流帧数据的类型,从I帧开始进行合成就行