最近做一个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的直播,开发过程难点记录相关推荐

  1. 直播类APP功能及技术难点

    目前市面上直播app有的基本功能 1.聊天:私聊.聊天室.点亮.推送.黑名单等; 2.礼物:普通礼物.豪华礼物.红包.排行榜.第三方充值.内购.礼物动态更新.提现等: 3.直播列表:关注.热门.最新. ...

  2. 短视频app搭建的技术难点是什么?

    近年来,短视频app的流行引起了广泛关注.越来越多的企业开始投入资源来开发短视频app,以满足用户的需求.然而,短视频app的开发过程中,存在许多技术难点需要解决.本文将深入分析短视频app搭建的技术 ...

  3. Axure高保真移动端电商app通用模板、axure高保真移动端教育app通用模板、旅游app通用模板、电商app、教育app、旅游app 、直播、在线教育、旅游、Axure原型、rp原型

    Axure高保真移动端电商app通用模板.axure高保真移动端教育app通用模板.旅游app通用模板.电商app.教育app.旅游app .直播.在线教育.旅游.Axure原型.rp原型 Axure ...

  4. android tv 云播放器,Android TV开发总结(六)构建一个TV app的直播节目实例

    近年来,Android TV的迅速发展,传统的有线电视受到较大的冲击,在TV上用户同样也可以看到各个有线电视的直播频道,相对于手机,这种直播节目,体验效果更佳,尤其是一样赛事节目,大屏幕看得才够痛快, ...

  5. 直播app源代码 直播软件开发Android UI动画 仿直播点赞飘心动画效果

    直播app源代码 直播软件开发Android UI动画 仿直播点赞飘心动画效果 一个飘心的小动画,之前看也看到网上有很多轮子,但是感觉不是很符合我的需求,所以自己就凑活凑活搞出来一个,废话不多说先看图 ...

  6. 一个 TV app 的直播节目实例,包含各央视频道及卫视频道

    LivePlayback 项目地址:hejunlin2013/LivePlayback 简介:一个 TV app 的直播节目实例,包含各央视频道及卫视频道 PS:注册过魅族帐号的同鞋,帮忙投下魅族开发 ...

  7. Android 直播 APP实现直播流程

    Android 直播 APP实现直播流程 直播本质 1. 主播端采集音视频 2. 视频处理(美颜,水印) 3. 视频编解码 视频编码框架 视频编码技术 压缩方式 视频编解码和压缩时的关键知识点 4. ...

  8. 行车记录仪设备APP开发功能

    一般的汽车上面都会有行车记录仪,但是在出行的过程中的当中,为了安全起见,或者是对当前的路况进行实时记录,很多人都会选择给自己的汽车添加相关的行车记录仪,以便于解决出行方面所遇到的一些问题.为了提高用户 ...

  9. 抖音APP给直播软件公司带来哪些启示

    抖音APP给直播软件公司带来哪些启示 移动互联网发展,给了很多新创公司机会,所以说每次时代的变革,都是时代给予我们的机会 抖音源码,短视频源码之外的互动. 直播开发,在我看来肯定不是,人们在『多省快好 ...

最新文章

  1. 使用汇编语言编写第一个程序
  2. 双绞线是计算机网络的一种通信线路吗,计算机网络环境的信道传输技术分析
  3. 实验四 查找和排序算法实现
  4. 5下载的demo在哪_归类专业能力水平评价练习盘!快来下载呀
  5. 操作多个表_5_记录匹配不上
  6. python编程规则_python编程规则
  7. 页面加载事件html5,JavaScript页面加载事件实例讲解
  8. linux重启ipv6_Linux关闭、开启、配置IPv6
  9. ubuntu 修改默认用户名_Tars框架在Ubuntu上的部署小结
  10. 编程让鼠标一直动_相见恨晚的效率提升工具,低价捡漏可以自定义编程快捷键的鼠标...
  11. Android获取Java类名/文件名/方法名/行号
  12. steam加速_玩转steam的新姿势:必备加速器推荐!
  13. python-文字pdf转换为图片pdf
  14. linux pdf中文乱码,英文乱码(乱码为方格之类的解决方法)
  15. windows7系统,ping本机ip地址请求超时的解决方案
  16. yii2 aliases web.php,别名(Aliases) - Yii2 权威指南
  17. 用迭代法求 a 的平方根。求平方根的迭代公式为····
  18. 瑞萨E1仿真器(R0E000010KCE00)支持的MCU系列--V850 Family
  19. 关于ACM比赛的感悟
  20. 2020-mac教程-sudo spctl --master-disable用不了

热门文章

  1. linux 下tomcat配置多域名访问怎么访问到的是一个站点,linux 配置多个子域名映射到tomcat中不同项目...
  2. vue3 入门到进阶,如何学习?
  3. 电脑基础知识入门(培训教材)
  4. python恶搞代码打开对方摄像头_大神们救命啊!!如何实现把摄像头读入的视频在tk界面呈现出来?...
  5. 构建Java镜像的10个最佳实践
  6. Aflow安装与测试以及遇到的问题
  7. 聚焦安全:互联网场景的身份认证方法分析
  8. scipy.spatial.qhull.QhullError: QH7023 qhull option warning: unknown ‘Q‘ qhull option ‘Qn‘, skip to
  9. Java-Fizzbuzz
  10. 【GD32F427开发板试用】+demo的正确打开方式(一)