本地的VideoFrame渲染一(VideoTrack)

本文解析了WebRTC中VideoTrack的工作原理,介绍了本地视频采集流程,并探讨了视频帧从采集到渲染的整个过程。

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

VideoTrack是webrtc中视频流最上层的接口,它内部其实是经过层层封装,对于本地的VideoTrack(远端的还没研究),它最终通过VideoCaptureImpl完成本地视频的采集,VideoCaptureImpl实际上是通过DirectShow(windows平台上)与硬件打交道的。可以先看下它简化后的关系:
这里写图片描述

那么视频帧是如何通过ViedoTrack渲染出来的呢,先看另一个接口:
这里写图片描述

再看回开始那个VideoTrack的类图,其实它们共同实现了VideoSourceInterface接口:
这里写图片描述

从这可以看出点端倪来,VideoRender类继承VideoSinkInterface接口,当一帧视频准备好的时候,OnFrame(const VideoFrameT&)将被调用,前提是要先把VideoRender通过VideoTrack的AddOrUpdateSink()传递进去。可以看下传递过程,画完这个之后,我终于明白为什么他的名字叫Sink(下沉)了……
这里写图片描述

从上面可以看出,从上层的VideoTrack的AddOrUpdateSink()通过层层传递最终调用WebRtcVideoCapturer(VideoCapturer)的AddOrUpdateSink(),而它又调用了broadcaster_.AddOrUpdateSink(sink, wants);我们先来看看broadcaster_的类:

这里写图片描述

实际上,当调用VideoBroadcaster的AddOrUpdateSink(sink, wants)会调VideoSourceBase的AddOrUpdateSink(sink, wants); VideoSourceBase的代码如下:

void VideoSourceBase::AddOrUpdateSink(
    VideoSinkInterface<cricket::VideoFrame>* sink,
    const VideoSinkWants& wants) {
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  RTC_DCHECK(sink != nullptr);

  SinkPair* sink_pair = FindSinkPair(sink);
  if (!sink_pair) {
    sinks_.push_back(SinkPair(sink, wants));
  } else {
    sink_pair->wants = wants;
  }
}

可见AddOrUpdateSink里将VideoSinkInterface添加到sinks_中,sinks_是一个SinkPair的数组,如下:

  struct SinkPair {
    SinkPair(VideoSinkInterface<cricket::VideoFrame>* sink,
             VideoSinkWants wants)
        : sink(sink), wants(wants) {}
    VideoSinkInterface<cricket::VideoFrame>* sink;
    VideoSinkWants wants;
  };

它内部就包含了VideoSinkInterface和VideoSinkWants(现在先不管VideoSinkWants)。

写了这么多,还是没有回答如何渲染一帧视频出来,这又要联系到VideoCapturer类的实现,下一篇文章再说明。

在Android中,如果你想要捕获并保存视频流的某帧,你可以使用`MediaCodec` API,特别是当处理解码过程时。以下是个简单的步骤: 1. 首先,你需要创建个`SurfaceTexture`对象,这将用于接收来自摄像头或外部输入的视频数据。 ```java SurfaceTexture surfaceTexture = new SurfaceTexture(surfaceView.getHolder().getSurface()); ``` 2. 然后,初始化`MediaCodec`实例,指定你要解码的VideoTrack。解码器会从输入Source读取帧,并通过`Surface`发送给`SurfaceTexture`。 ```java MediaFormat format = MediaFormat.createVideoFormat(...); // 根据实际需要设置format MediaCodec mediaCodec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)); mediaCodec.configure(format, null, null, 0); mediaCodec.start(); ``` 3. 当你想获取帧时,可以请求解码器提供解码缓冲区,并将数据复制到个新的`Bitmap`对象。注意,不是所有的解码缓冲区都包含完整的帧,有时需要等待直到完整帧可用。 ```java BufferInfo bufferInfo = new BufferInfo(); mediaCodec.dequeueOutputBuffer(bufferInfo, 0, TimeUnit.SECONDS, surfaceTexture, 0); if (bufferInfo.isCompleted()) { ByteBuffer buffer = mediaCodec.outputBuffers()[bufferInfo.bufferIndex].mallocCopy(bufferInfo.size); byte[] frameData = new byte[buffer.remaining()]; buffer.get(frameData); Bitmap bitmap = BitmapFactory.decodeByteArray(frameData, 0, frameData.length); // 保存bitmap到文件或其他位置 } ``` 4. 最后,记得释放资源,如关闭解码器、`SurfaceTexture`等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值