webrtc保存视频

本文围绕WebRTC P2P音视频功能展开,介绍了保存视频的原理,指出视频流源头是摄像头、终点是屏幕,每帧数据需调用并通过渲染器绘制。还展示了实现效果,给出代码实现,提醒未调用接口无视频生成,生成文件大且只能用VLC播放,最后贴出老版本源码并提供参考链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、原理介绍

对于webrtc p2p音视频功能,不管是本端视频流还是对端视频流,视频流的源头都是摄像头,终点是都是屏幕(不同的终端需要不同的控件支持)。明白了这一点,每一帧数据需要调用VideoRenderer,然后通过渲染器在控件上进行绘制。
VideoRenderer中定义了如下的接口:

public static interface Callbacks {
    void renderFrame(org.webrtc.VideoRenderer.I420Frame i420Frame);
}

其基本原理和《webrtc系列——截取图像》一致,但不同的是我这次使用VideoFileRenderer这个类,该类继承自VideoRenderer,并且实现了renderFrame接口。
看一下主要的成员函数:

// 初始化
public VideoFileRenderer(java.lang.String outputFile, int outputFileWidth, int outputFileHeight, org.webrtc.EglBase.Context sharedContext) throws java.io.IOException { /* compiled code */ }

// 回调函数接口
public void renderFrame(org.webrtc.VideoRenderer.I420Frame frame) { /* compiled code */ }

// 保存视频文件的真正实现函数
private void renderFrameOnRenderThread(org.webrtc.VideoRenderer.I420Frame frame) { /* compiled code */ }

// 释放资源
public void release() { /* compiled code */ }

public static native void nativeI420Scale(java.nio.ByteBuffer byteBuffer, int i, java.nio.ByteBuffer byteBuffer1, int i1, java.nio.ByteBuffer byteBuffer2, int i2, int i3, int i4, java.nio.ByteBuffer byteBuffer3, int i5, int i6);

二、实现效果

在这里插入图片描述
上图是一个3秒钟的视频的截图。

三、代码实现

// 实现VideoRenderer.Callbacks的接口
private static class ProxyRenderer implements VideoRenderer.Callbacks {
    private VideoRenderer.Callbacks target;
    private int offest = 0;
    private int sum = 0;

    // 初始化VideoFileRenderer
    static  private VideoFileRenderer outFile = new VideoFileRenderer("/storage/emulated/0/1/webrtc_2.mp4", 640, 480, rootEglBase.getEglBaseContext());

    @Override
    synchronized public void renderFrame(VideoRenderer.I420Frame frame) {
        if (target == null) {
            Logging.d(TAG, "Dropping frame in proxy because target is null.");
            VideoRenderer.renderFrameDone(frame);
            return;
        }

        VideoRenderer.I420Frame frame2 =  new VideoRenderer.I420Frame(frame.width, frame.height, frame.rotationDegree, frame.yuvStrides, frame.yuvPlanes, NULL);

        target.renderFrame(frame2);

        outFile.renderFrame(frame);

        // 保存100帧画面后停止
        if(sum++ == 100)
        {
            // 释放资源
            outFile.release();
        }

        synchronized public void setTarget(VideoRenderer.Callbacks target) {
            this.target = target;
        }
    }
}

四、注意事项

如果未调用release()接口,不会有视频生成。
生成的视频文件没有经过压缩,文件比较大,且只能用VLC播放器进行播放。

五、拓展

把该文件的源码(老版本)贴出来,我用的老版本,新版本改动较大


/**
 * Can be used to save the video frames to file.
 */
public class VideoFileRenderer implements VideoRenderer.Callbacks {
  private static final String TAG = "VideoFileRenderer";

  private final YuvConverter yuvConverter;
  private final HandlerThread renderThread;
  private final Object handlerLock = new Object();
  private final Handler renderThreadHandler;
  private final FileOutputStream videoOutFile;
  private final int outputFileWidth;
  private final int outputFileHeight;
  private final int outputFrameSize;
  private final ByteBuffer outputFrameBuffer;

  public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
      EglBase.Context sharedContext) throws IOException {
    if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {
      throw new IllegalArgumentException("Does not support uneven width or height");
    }
    yuvConverter = new YuvConverter(sharedContext);

    this.outputFileWidth = outputFileWidth;
    this.outputFileHeight = outputFileHeight;

    outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;
    outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);

    videoOutFile = new FileOutputStream(outputFile);
    videoOutFile.write(
        ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n")
            .getBytes());

    renderThread = new HandlerThread(TAG);
    renderThread.start();
    renderThreadHandler = new Handler(renderThread.getLooper());
  }

  @Override
  public void renderFrame(final VideoRenderer.I420Frame frame) {
    renderThreadHandler.post(new Runnable() {
      @Override
      public void run() {
        renderFrameOnRenderThread(frame);
      }
    });
  }

  private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) {
    final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight();

    final float[] rotatedSamplingMatrix =
        RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
    final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
        false, frameAspectRatio, (float) outputFileWidth / outputFileHeight);
    final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);

    try {
      videoOutFile.write("FRAME\n".getBytes());
      if (!frame.yuvFrame) {
        yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth,
            frame.textureId, texMatrix);

        int stride = outputFileWidth;
        byte[] data = outputFrameBuffer.array();
        int offset = outputFrameBuffer.arrayOffset();

        // Write Y
        videoOutFile.write(data, offset, outputFileWidth * outputFileHeight);

        // Write U
        for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
          videoOutFile.write(data, offset + r * stride, stride / 2);
        }

        // Write V
        for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) {
          videoOutFile.write(data, offset + r * stride + stride / 2, stride / 2);
        }
      } else {
        nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1],
            frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height,
            outputFrameBuffer, outputFileWidth, outputFileHeight);
        videoOutFile.write(
            outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
      }
    } catch (IOException e) {
      Logging.e(TAG, "Failed to write to file for video out");
      throw new RuntimeException(e);
    } finally {
      VideoRenderer.renderFrameDone(frame);
    }
  }

  public void release() {
    final CountDownLatch cleanupBarrier = new CountDownLatch(1);
    renderThreadHandler.post(new Runnable() {
      @Override
      public void run() {
        try {
          videoOutFile.close();
        } catch (IOException e) {
          Logging.d(TAG, "Error closing output video file");
        }
        cleanupBarrier.countDown();
      }
    });
    ThreadUtils.awaitUninterruptibly(cleanupBarrier);
    renderThread.quit();
  }

  public static native void nativeI420Scale(ByteBuffer srcY, int strideY, ByteBuffer srcU,
      int strideU, ByteBuffer srcV, int strideV, int width, int height, ByteBuffer dst,
      int dstWidth, int dstHeight);
}

参考链接:
https://www.jianshu.com/p/5902d4953ed9
https://www.jianshu.com/p/e3d7d71522ee

WebRTC是一种支持实时音视频通信的开源技术标准,用于在浏览器中实现点对点的音视频通讯。Spring Boot是一个用于开发Java应用程序的框架,提供了开箱即用的功能和优化,使得开发者更加轻松地构建企业级应用。 结合两者,我们可以利用Spring Boot来构建一个基于WebRTC视频通话应用。首先,我们需要使用WebRTC提供的API来处理视频流的传输和处理。Spring Boot可以提供一个服务器端应用程序,用于接收和处理客户端发送的视频流。 在Spring Boot应用中,我们可以创建一个控制器类,用于处理相关的请求和操作。客户端可以通过发送视频流的方式与服务器建立连接,并传输视频服务器端则可以接收客户端发送的视频流,并进行一些处理,如前端展示、保存、处理等。 为了实现这一功能,我们需要在Spring Boot应用中集成WebRTC的相关库或框架,例如使用webrtc-java库或者使用Spring WebFlux框架来实现WebRTC的功能。 通过使用Spring Boot和WebRTC,我们可以很方便地构建一个实时视频通话的应用程序。无论是在网页上还是移动设备上,用户可以通过浏览器直接进行视频通话,而不需要安装额外的插件或应用程序。 总结起来,WebRTC是用于实现实时音视频通信的技术标准,而Spring Boot是用于构建Java应用程序的框架。结合两者,我们可以构建一个基于WebRTC视频通话应用程序,实现实时的音视频传输和处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值