Agora 音视频SDK在android端如何得到远端视频流的地址

想要把Agora和easyAR结合起来使用,easyAR那边android 默认渲染的输入帧来源都是相机,而我是想把通过Agora SDK接收到的远端视频流传给easyAR。可是那边在自定义输入帧来源时需要输入帧的首地址,虽然这边也看过了android可以调用C++的回调接口,用onRenderVideoFrame()来获取原始远端视频,但其实我本身只是想把收到的视频地址给传过去,并且github上的示例代码里在.java文件下参数里也没了YUV三个缓冲区的指针,不知道该怎么办。新手求助下,谢谢了

可以参考这里的实现方式:

这里的视频地址是指什么呢?我的理解,你目前的需求应该是把接受到的视频流传给 easyAR 吧?那就需要按照你说的,通过裸数据接口获取到原始数据流数据,然后通过 easyAR 的接口把视频数据塞给 easyAR 呀。

我之所以表达“传视频地址”是因为根据easyAR那边的文档,如果我想把除手机自带摄像头外的视频流给easyAR的组件的话,那么就必须得调用一个它自己定义的接口方法wrap(long ptr, int size, @Nonnull FunctorOfVoid deleter)(这个方法它的说明是包装一个指定长度的原始内存快),然后这里面第一个参数ptr就是我的视频流所在内存的首地址(这个参数意义是我一个负责unity端的同学看了unity的这个方法说明告诉我的)。现在问题主要出现在了虽然我和我那同学同样都是想把接收到的视频源传给easyAR的组件(我俩都是用的声网+easyAR来开发的,不同的是他是针对的unity来进行而我是针对android),但他在声网本身的unity端样例里就找到了关于接收到的视频流的渲染函数并在里面就有接收视频的指针,所以他很轻松的就把这个地址指针给放过去了,可是在android这边由于我用的是java所以肯定是没有指针这个概念的,再加上我本身也不是搞这块编程的所以很多东西也并不熟悉。
您回复我参考的那段C++代码我之前也看过(也集成到了之前的代码里),主要还是在于我不是很明白C++和java进行本地交互这块是怎么运作的,现在我的想法是在我java代码里去调用这部分C++的函数去把视频的地址拿出来再把这地址给过去,不知道这种想法从原理上讲可不可以,并且就算C++部分的函数能拿到原始视频流的地址可那也是一个指针(就比如onRenderVideoFrame()这个监听方法在官方的java样例里就没有了YUV那三缓冲区的个指针参数),这种东西在java代码里能够存在吗?并且easyAR那边那个地址的参数类型还是long,我现在实在是感到费解,不知道您怎么看这个。
感谢耐心回复。

你好。

我对 EasyAR 了解的不多。如果你不太能解释 wrap 方法参数含义的话,找到对应的官方文档的链接发给我们,可以尝试帮你解释一下。

如果把你的需求解释成我们这边的术语,我想应该是 “把远端视频帧数据发到 EasyAR 做自渲染” 。

撇开 wrap 这个方法不谈,我们的 MediaIO 接口可以帮助你获取远端数据流。上边提到的 C++ 接口当然也可以达到同样的效果,只是 MediaIO 作为 Java 层 API,对你而言更方便。请参考以下链接

https://docs.agora.io/cn/Interactive%20Broadcast/custom_video_android?platform=Android

如果很不幸 wrap 方法第一帧必须的是帧数据的地址,那么只能采用 C++ 裸数据的方式,具体的要在了解 wrap 方法的使用方式之后才能确定。

另外,无论是 MediaIO 还是裸数据,Agora SDK 发送和接收的是整个帧数组,格式为 I420。如果 EasyAR 必须以分量的形式接收数据,那么只能将其拆开,方法可以在网上搜索得到。


感谢回复,上面这个图是easyAR大概的一个流程框架图,我的问题在于红色所圈部分,这一部分就相当于声网这边获得的远方视频数据传到easyAR组件中的一步。由于easyAR在android端默认的输入帧来源都是手机自带摄像头,所以它们并没有专门针对像我这种情况做解释说明,也没有给出样例(只是在功能中说了支持自定义摄像头),不过根据我们在unity端的实验,发现可以通过红色所圈部分的Throttler这个类中的input()(它代表着一个输入帧端口)去传进一个它定义的inputFrame型数据(也就是视频输入帧,包含了视频画面和时间戳等参数),这个inputFrame我们可以自己构造,但是视频数据,或者说视频的画面要在这当中加入的话根据easyAR的文档我们需要先放在一个image类下,再往下走就是需要构造出一个它家的buffer类,官方的原话是“Buffer 存储了原始字节数组,可以用来访问图像数据”,而wrap方法就是这个buffer内中的一个,这是这页的链接:https://help.easyar.cn/EasyAR%20Sense/v4/ApiReference/Buffer.html
它的作用根据官方说是“包装一个指定长度的原始内存块”,后面的参数其实并没有做说明,但是我一位负责上面unity端的同学根据C++的这个函数显示以及测试后告诉我第一个参数ptr是视频的地址,而第二个相当于一个帧的大小。
我的问题就是卡在了构造这个所谓的原视内存块wrap这里,因为就算我在声网这边能拿到原始视频数据进行一些操作,但由于貌似java中根本不存在指针的概念,因而我不知道该怎么去拿到声网这边视频的地址。
另:我同学在unity端成功地把声网的远端视频传给了easyAR就是因为他在声网的远端渲染函数里找到了代表接收到视频的指针(另外我同学的说法是这里接受的直接是RGBA的格式,对此我也有点疑惑,因为根据我找的文档接收的格式貌似是YUV的),并直接把这个函数给public了,所以easyAR那边很轻松地就包装了这个原视内存块,且加上RGBA格式在内存中是连续存放的,所以最后测试也没啥问题。我就是在想我能否通过java调用C++函数的这种方法来把地址找出来,可是这样一来java中指针不能存在的问题就还是困扰着我。
最后再次感谢耐心回复。

你应该是在纠结如何创建 Buffer。其实对于 Android 端来说,更方便的是使用下边的两个方法来创建 Buffer:

1)Buffer.wrapByteArray
2)Buffer.wrapBuffer

这两个方法接受的参数,都是可以从 Agora MediaIO 接口得到的,而且无论是 byte 数组和 ByteBuffer 都是可以任意选的。

十分感谢回复,我目前正照着这个思路来做,不过另外还想请教一点就是关于rtc接收到的远端视频的格式这一块是由远方发送端来决定的还是说我本地在接收的时候通过上面提到的这些api自己设定想要的格式。
因为我之前的话把处理原始数据流的相关的C++接口已经集成到了工程里,打算先使onRenderVideoFrame()将捕获到的数据以byte数组的形式给传过去。不过我看原来github上的样例代码里关于这个回调的参数貌似接收到的视频是YUV格式的,所以照我目前这样不做什么操作的话接收到的远端视频就是YUV_I420格式的对吗?

是的,在不添加转换函数的情况下,onRender接口出来的视频帧是YUV I420和422格式的。

2.9.1版本后支持返回RGBA

好的,那么能再问下如果是采用Agora MediaIO 接口的话接收到的视频不做任何转换的话也默认是YUV_I420格式的吗

以及如何用MediaIO 接口来获取到远方视频感觉看完文档也没太明白,我好像没有看到像onRenderVideoFrame()这样的捕获远端视频的回调函数, IVideoFrameConsumer里的那三个函数应该也不能直接接收到远端视频吧?

此外我一开始选择的C++接口的方式编译的时候显示能够通过,但是运行的时候闪退,我看了下日志显示说raw-data-api-java下面的那个.so库文件不存在,然后我也去找了另一篇问题中提到的这个路径image ,发现连library_and_local_jars_jni这个文件夹也没有,请问下这种情况该怎么做

library_and_local_jars_jni是在intermediates底下的JNI库目录,你打开intermediates看看


这是intermediates的文件夹,里面也没有,这个JNI是需要我去自己新建一个再把完整的生成.so文件的步骤做一遍吗?

请问下,我现在虽然把处理原始数据流相关的C++接口集成进代码了,编译也都通过了,也没有提示我.so文件不存在啥的,但是我测试发现public void onRenderVideoFrame(int uid, byte[] data, int frameType, int width, int height…)这个方法似乎没起作用?我本来在这个回调里面让一个全局的byte [] mdata将接收到的byte[] data进行了复制,但是传到easyAR那边后就会报错这里面是空值,并且我也查看了日志,发现在这个会回调里本来要打印的log也没有显示出来,请问这种情况是我还有哪里没处理正确吗?

你在onRenderVideoFrame后调用下setIsEnableWriteToFile录制文件,看看录制到的文件是什么情况
RGBA是不是空的也就是0KB,YUV是不是只有几百K一张的单帧画面。

有没有在前面添加 registerVideoFrame(8) 结束前再deRegisterVideoFrame一下看看

我是参照着Agora-Plugin-Raw-Data-API-Android-Java这个例子中的代码将处理原始数据的部分加入之前的代码里,但这个例子中VideoChatViewActivity.java并没有 registerVideoFrame这一步,它implements的是MediaDataVideoObserver这个自定义接口,我对此也比较疑惑。