DVR行车记录仪与APP的直播,开发过程难点记录
最近做一个APP与行车记录仪的直播功能。
C层同事说没有后台服务器支撑,所以我之前编译好的ijkplayer的so库也就没用了。
与C层同事沟通如下:
1、JNI调用这块我自己写(不是很熟悉....呜呜)
2、根据C层的结构体,我Java这边写出对应的实体类bean
目前大致是这样的。
问题点:
1、在JNI层进行回调实时流数据到Java层,代码如下:
//获取流数据,里面的OnGetFrameListener 接口是需要传到JNI层进行回调的public native void getFrame(OnGetFrameListener frameListener);// 这个是回调接口public interface OnGetFrameListener {public void getFrameData(FrameDataBean dataBean);}
下面是JNI代码:
//获取要回调的方法IDgetFrameMethodId = (*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天时间
这样的话就获取到实时数据流了。
2、播放实时数据流(我这里C层给的是一帧一帧的char[],java可以转为byte[])
首先,我们得要问一下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
3、假如是播放有服务器的直播或者回放的话
这个我们可以自己编译ijkplayer,这个有FFmpeg内核处理,需要支持h264 aac h265 http https都可以在相关的ffmpeg文件中进行修改,我自己编译好的so库就不给大家了,毕竟需求不一样。
4、在JNI中开启线程,从C层循环获取数据【一帧h264数据】后,用mediacode解码导致画面延迟、马赛克
在jni中开启线程循环获取数据(线程不休眠或者休眠时间很短暂,此【usleep】方法),这时候mediacode解码会报错:MediaCodec.native_dequeueInputBuffer java.lang.IllegalStateException;这样的情况会导致完全展示不了帧画面或者延迟、马赛克。
解决办法:但是经过调试,在jni的线程while循环中usleep 10毫秒的话,可以正常解码,正常播放直播。
原理以及问题的产生点:如果不做线程休眠会导致硬解码这边的缓冲区溢出。
5、处理Android10系统连接WiFi导致socket在不同网段无法通讯的问题
处理: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() {@Overridepublic 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();}@Overridepublic void onUnavailable() {connectWifi.connectFail();}};connectivityManager.requestNetwork(request, networkCallback);}}
当然假如你对手机有root权限,在手机的路由表添加IP地址也可以,但是Android这块在底层已经写死,也不建议这么做。
6、使用mediacode解码AAC帧数据,有的可以解码,有的不能解码的问题:
背景:项目播放的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头的解码信息这块。还是对于这块不熟啊!!!!!!
7、使用MediaMuxer合成实时流的h264+aac帧数据封装成MP4问题点
首先对于这个不熟悉,看文档以及网上各种搜,大部分都是合成文件形式的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帧开始进行合成就行
DVR行车记录仪与APP的直播,开发过程难点记录相关推荐
- 直播类APP功能及技术难点
目前市面上直播app有的基本功能 1.聊天:私聊.聊天室.点亮.推送.黑名单等; 2.礼物:普通礼物.豪华礼物.红包.排行榜.第三方充值.内购.礼物动态更新.提现等: 3.直播列表:关注.热门.最新. ...
- 短视频app搭建的技术难点是什么?
近年来,短视频app的流行引起了广泛关注.越来越多的企业开始投入资源来开发短视频app,以满足用户的需求.然而,短视频app的开发过程中,存在许多技术难点需要解决.本文将深入分析短视频app搭建的技术 ...
- Axure高保真移动端电商app通用模板、axure高保真移动端教育app通用模板、旅游app通用模板、电商app、教育app、旅游app 、直播、在线教育、旅游、Axure原型、rp原型
Axure高保真移动端电商app通用模板.axure高保真移动端教育app通用模板.旅游app通用模板.电商app.教育app.旅游app .直播.在线教育.旅游.Axure原型.rp原型 Axure ...
- android tv 云播放器,Android TV开发总结(六)构建一个TV app的直播节目实例
近年来,Android TV的迅速发展,传统的有线电视受到较大的冲击,在TV上用户同样也可以看到各个有线电视的直播频道,相对于手机,这种直播节目,体验效果更佳,尤其是一样赛事节目,大屏幕看得才够痛快, ...
- 直播app源代码 直播软件开发Android UI动画 仿直播点赞飘心动画效果
直播app源代码 直播软件开发Android UI动画 仿直播点赞飘心动画效果 一个飘心的小动画,之前看也看到网上有很多轮子,但是感觉不是很符合我的需求,所以自己就凑活凑活搞出来一个,废话不多说先看图 ...
- 一个 TV app 的直播节目实例,包含各央视频道及卫视频道
LivePlayback 项目地址:hejunlin2013/LivePlayback 简介:一个 TV app 的直播节目实例,包含各央视频道及卫视频道 PS:注册过魅族帐号的同鞋,帮忙投下魅族开发 ...
- Android 直播 APP实现直播流程
Android 直播 APP实现直播流程 直播本质 1. 主播端采集音视频 2. 视频处理(美颜,水印) 3. 视频编解码 视频编码框架 视频编码技术 压缩方式 视频编解码和压缩时的关键知识点 4. ...
- 行车记录仪设备APP开发功能
一般的汽车上面都会有行车记录仪,但是在出行的过程中的当中,为了安全起见,或者是对当前的路况进行实时记录,很多人都会选择给自己的汽车添加相关的行车记录仪,以便于解决出行方面所遇到的一些问题.为了提高用户 ...
- 抖音APP给直播软件公司带来哪些启示
抖音APP给直播软件公司带来哪些启示 移动互联网发展,给了很多新创公司机会,所以说每次时代的变革,都是时代给予我们的机会 抖音源码,短视频源码之外的互动. 直播开发,在我看来肯定不是,人们在『多省快好 ...
最新文章
- 使用汇编语言编写第一个程序
- 双绞线是计算机网络的一种通信线路吗,计算机网络环境的信道传输技术分析
- 实验四 查找和排序算法实现
- 5下载的demo在哪_归类专业能力水平评价练习盘!快来下载呀
- 操作多个表_5_记录匹配不上
- python编程规则_python编程规则
- 页面加载事件html5,JavaScript页面加载事件实例讲解
- linux重启ipv6_Linux关闭、开启、配置IPv6
- ubuntu 修改默认用户名_Tars框架在Ubuntu上的部署小结
- 编程让鼠标一直动_相见恨晚的效率提升工具,低价捡漏可以自定义编程快捷键的鼠标...
- Android获取Java类名/文件名/方法名/行号
- steam加速_玩转steam的新姿势:必备加速器推荐!
- python-文字pdf转换为图片pdf
- linux pdf中文乱码,英文乱码(乱码为方格之类的解决方法)
- windows7系统,ping本机ip地址请求超时的解决方案
- yii2 aliases web.php,别名(Aliases) - Yii2 权威指南
- 用迭代法求 a 的平方根。求平方根的迭代公式为····
- 瑞萨E1仿真器(R0E000010KCE00)支持的MCU系列--V850 Family
- 关于ACM比赛的感悟
- 2020-mac教程-sudo spctl --master-disable用不了
热门文章
- linux 下tomcat配置多域名访问怎么访问到的是一个站点,linux 配置多个子域名映射到tomcat中不同项目...
- vue3 入门到进阶,如何学习?
- 电脑基础知识入门(培训教材)
- python恶搞代码打开对方摄像头_大神们救命啊!!如何实现把摄像头读入的视频在tk界面呈现出来?...
- 构建Java镜像的10个最佳实践
- Aflow安装与测试以及遇到的问题
- 聚焦安全:互联网场景的身份认证方法分析
- scipy.spatial.qhull.QhullError: QH7023 qhull option warning: unknown ‘Q‘ qhull option ‘Qn‘, skip to
- Java-Fizzbuzz
- 【GD32F427开发板试用】+demo的正确打开方式(一)