我正在尝试在Android(移动)应用上使用WebRTC制作一个简单的功能。 该应用程序现在可以进行简单的视频通话:将两个设备相互连接,让他们听到和看到。 我想要实现的是在通话期间的一些现场绘画。简单地说:User1调用User2,调用连接,然后User1点击一个绘制按钮,它将冻结视频帧并允许他在这个冻结帧上绘制。显然,User2应该会在手机上看到这个画面。 现在我可以冻结框架(通过调用videoCapture.stopCapture())并使用自定义SurfaceViewRenderer绘制它。问题是User2看不到图形,只看到冻结的框架。
首先,我尝试创建一个新的视频图像,其中包含绘图画布和要绘制的冻结帧,但我无法成功。
使用peerConnectionFactory.createVideoTrack("ARDAMSv1_" + rand, videoSource);
创建视频群时
我应该指定轨道的视频源,但源只能是VideoSource
,并且此VideoSource只能使用VideoCapturer
创建,该docker run -d -it --net bridge --name 5114100123_10.151.36.227 --publish 9001:3128 fourirakbar/debian-squid:version1
直接链接到设备相机(没有任何绘图)它当然)。这就解释了为什么User2没有在他的设备上看到任何图画
我的问题是:如何创建一个VideoCapturer,它可以流式传输相机流(冻结帧)和带有绘图的画布?
所以我尝试将自己的VideoCapturer实现为:
1)捕获视图(例如包含图形和冻结帧的布局)并将其流式传输到VideoSource
或2)捕获摄像机视图,但在流式传输之前也将图形添加到帧中
我无法完成任何这项工作,因为我不知道如何操纵I420Frame对象来绘制它并使用正确的回调返回它。
也许我对这种方法完全错了,需要做一些完全不同的事情,我对任何建议持开放态度。 PS:我正在使用Android API 25和WebRTC 1.0.19742。我不想使用任何付费的第三方SDK / lib。
有没有人知道如何从一个Android应用程序到另一个Android应用程序实现简单的WebRTC实时绘图?
答案 0 :(得分:1)
几周前我们回到了该功能,我设法找到了一种方法。
我扩展了自己的CameraCapturer类,以在渲染之前获得相机框架的支撑。然后,我创建了自己的CanvasView以便能够在其上进行绘制。
从那里开始,我将两个位图(相机视图+我的画布与图形)合并在一起,然后用OpenGL在缓冲区上绘制它并在SurfaceView上显示它。
如果有人感兴趣,我可能会发布一些代码。
答案 1 :(得分:1)
@Override
public void startCapture(int width, int height, int fps) {
Log.d("InitialsClass", "startCapture");
surTexture.stopListening();
cameraHeight = 480;
cameraWidth = 640;
int horizontalSpacing = 16;
int verticalSpacing = 20;
int x = horizontalSpacing;
int y = cameraHeight - verticalSpacing;
cameraBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
YuvFrame frame = new YuvFrame(null, PROCESSING_NONE, appContext);
surTexture.startListening(new VideoSink() {
@Override
public void onFrame(VideoFrame videoFrame) {
frame.fromVideoFrame(videoFrame, PROCESSING_NONE);
}
});
if (captureThread == null || !captureThread.isInterrupted()) {
captureThread = new Thread(() -> {
try {
if (matrix == null) {
matrix = new Matrix();
}
long start = System.nanoTime();
capturerObs.onCapturerStarted(true);
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
YuvConverter yuvConverter = new YuvConverter();
WindowManager windowManager = (WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
// Set filtering
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
//The bitmap is drawn on the GPU at this point.
TextureBufferImpl buffer = new TextureBufferImpl(cameraWidth, cameraHeight - 3, VideoFrame.TextureBuffer.Type.RGB, textures[0], matrix, surTexture.getHandler(), yuvConverter, null);
Resources resources = appContext.getResources();
float scale = resources.getDisplayMetrics().density;
Log.d("InitialsClass before", "camera start capturer width- " + cameraWidth + " height- " + cameraHeight);
while (!Thread.currentThread().isInterrupted()) {
ByteBuffer gBuffer = frame.getByteBuffer();
if (gBuffer != null) {
Log.d("InitialsClass ", "gBuffer not null");
cameraBitmap.copyPixelsFromBuffer(gBuffer);
}
if (cameraBitmap != null) {
if (canvas == null) {
canvas = new Canvas();
}
if (appContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT)
rotationDegree = -90;
else {
assert windowManager != null;
if (windowManager.getDefaultDisplay().getRotation() == Surface.ROTATION_0) {
// clockwise
rotationDegree = 0;
} else if (windowManager.getDefaultDisplay().getRotation() == Surface.ROTATION_90) {
// anti-clockwise
rotationDegree = -180;
}
}
canvas.save(); //save the position of the canvas
canvas.rotate(rotationDegree, (cameraBitmap.getWidth() / 2), (cameraBitmap.getHeight() / 2)); //rotate the canvas
canvas.drawBitmap(cameraBitmap, 0, 0, null); //draw the image on the rotated canvas
canvas.restore(); // restore the canvas position.
matrix.setScale(-1, 1);
matrix.postTranslate(/*weakBitmap.get().getWidth()*/ cameraBitmap.getWidth(), 0);
matrix.setScale(1, -1);
matrix.postTranslate(0, /*weakBitmap.get().getHeight()*/ cameraBitmap.getHeight());
canvas.setMatrix(matrix);
if (textPaint == null) {
textPaint = new TextPaint();
}
textPaint.setColor(Color.WHITE);
textPaint.setTypeface(Typeface.create(typeFace, Typeface.BOLD));
textPaint.setTextSize((int) (11 * scale));
if (textBounds == null) {
textBounds = new Rect();
}
textPaint.getTextBounds(userName, 0, userName.length(), textBounds);
textPaint.setTextAlign(Paint.Align.LEFT);
textPaint.setAntiAlias(true);
canvas.drawText(userName, x, y, textPaint);
if (paint == null) {
paint = new Paint();
}
if (isLocalCandidate) {
paint.setColor(Color.GREEN);
} else {
paint.setColor(Color.TRANSPARENT);
}
paint.setStrokeWidth(8);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(0, 8, cameraWidth - 8, cameraHeight - 8, paint);
if (surTexture != null && surTexture.getHandler() != null && surTexture.getHandler().getLooper().getThread().isAlive()) {
surTexture.getHandler().post(() -> {
// Load the bitmap into the bound texture.
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, /*weakBitmap.get()*/ cameraBitmap, 0);
//We transfer it to the VideoFrame
VideoFrame.I420Buffer i420Buf = yuvConverter.convert(buffer);
long frameTime = System.nanoTime() - start;
VideoFrame videoFrame = new VideoFrame(i420Buf, 0, frameTime);
capturerObs.onFrameCaptured(videoFrame);
});
}
}
Thread.sleep(100);
}
} catch (InterruptedException ex) {
Log.d("InitialsClass camera", ex.toString());
Thread.currentThread().interrupt();
return;
}
});
}
captureThread.start();
}
@阿奈尔。看看吧。
答案 2 :(得分:0)
我正在开发类似的应用程序,所以我分享了在webrtc流上绘制:
我专门为您制作了一个小样here(本地webrtc,请参阅日志)。
PS:我使用的是getDisplayMedia,而不是userMedia,因为我的网络摄像头是 kaput ...