zoukankan      html  css  js  c++  java
  • WebRTC Native开发实战之视频编码

    1. 实时音视频开发主要步骤

    image

    2. 概述

    前面我们通过两篇文章分别介绍了视频采集的两种方式:采集摄像头和采集屏幕。获取数据之后,如果是要本地显示,那么就像我们之前做的那样,直接渲染出来就行;方式如果是进行存储或者进行传输,往往需要对数据进行编码压缩。

    webrtc中的视频编解码部分的主要实现是位于modules/video_coding. 我们初步能看到的编码器的接口是位于api/video_codecs/video_encoder.h,这里定义了webrtc::VideoEncoder,不过这是个虚类,只是用于进行接口定义和部分接口的实现。

    3. 创建编码器

    image

    media/engine/internal_encoder_factory.h中定义了创建编码器的通用接口:

    
    // media/engine/internal_encoder_factory.h
    
    namespace webrtc {
    
    class RTC_EXPORT InternalEncoderFactory : public VideoEncoderFactory {
     public:
      static std::vector<SdpVideoFormat> SupportedFormats();
      std::vector<SdpVideoFormat> GetSupportedFormats() const override;
    
      CodecInfo QueryVideoEncoder(const SdpVideoFormat& format) const override;
    
      std::unique_ptr<VideoEncoder> CreateVideoEncoder(
          const SdpVideoFormat& format) override;
    };
    
    }  // namespace webrtc
    
    • 可以使用InternalEncoderFactory::SupportedFormats接口得到所支持的编码器类型;
    • 可以使用InternalEncoderFactory::QueryVideoEncoder接口查询编码器的状态
      // media/engine/internal_encoder_factory.cc
      VideoEncoderFactory::CodecInfo InternalEncoderFactory::QueryVideoEncoder(
      const SdpVideoFormat& format) const {
        CodecInfo info;
        info.is_hardware_accelerated = false;
        info.has_internal_source = false;
        return info;
      }
      
    • 可以使用InternalEncoderFactory::CreateVideoEncoder接口来创建编码器webrtc::VideoEncoder实例
      // media/engine/internal_encoder_factory.cc
      
      std::unique_ptr<VideoEncoder> InternalEncoderFactory::CreateVideoEncoder(
          const SdpVideoFormat& format) {
        if (absl::EqualsIgnoreCase(format.name, cricket::kVp8CodecName))
          return VP8Encoder::Create();
        if (absl::EqualsIgnoreCase(format.name, cricket::kVp9CodecName))
          return VP9Encoder::Create(cricket::VideoCodec(format));
        if (absl::EqualsIgnoreCase(format.name, cricket::kH264CodecName))
          return H264Encoder::Create(cricket::VideoCodec(format));
        if (kIsLibaomAv1EncoderSupported &&
            absl::EqualsIgnoreCase(format.name, cricket::kAv1CodecName))
          return CreateLibaomAv1Encoder();
        RTC_LOG(LS_ERROR) << "Trying to created encoder of unsupported format "
                          << format.name;
        return nullptr;
      }
      

    InternalEncoderFactory::CreateVideoEncoder的实现里我们可以看到,webrtc支持四种类型的视频编码器:

    • VP8Encoder
    • VP9Encoder
    • H264Encoder
    • LibaomAv1Encoder

    H264默认是不支持的,需要在编译的时候加上特殊的编译参数rtc_use_h264=true

    4. 编码器的接口

    视频编码器的定义主要就是webrtc::VideoEncoder结构体,位于api/video_codecs/video_encoder.h:
    image

    • 可以通过VideoEncoder::RegisterEncodeCompleteCallback注册一个EncodedImageCallback对象来接收编码后的数据
      // api/video_codecs/video_encoder.h
      
      class EncodedImageCallback {
       public:
        virtual ~EncodedImageCallback() {}
      
        struct Result {
          enum Error {
            OK,
      
            // Failed to send the packet.
            ERROR_SEND_FAILED,
          };
      
          explicit Result(Error error) : error(error) {}
          Result(Error error, uint32_t frame_id) : error(error), frame_id(frame_id) {}
      
          Error error;
      
          // Frame ID assigned to the frame. The frame ID should be the same as the ID
          // seen by the receiver for this frame. RTP timestamp of the frame is used
          // as frame ID when RTP is used to send video. Must be used only when
          // error=OK.
          uint32_t frame_id = 0;
      
          // Tells the encoder that the next frame is should be dropped.
          bool drop_next_frame = false;
        };
      
        // Used to signal the encoder about reason a frame is dropped.
        // kDroppedByMediaOptimizations - dropped by MediaOptimizations (for rate
        // limiting purposes).
        // kDroppedByEncoder - dropped by encoder's internal rate limiter.
        enum class DropReason : uint8_t {
          kDroppedByMediaOptimizations,
          kDroppedByEncoder
        };
      
        // Callback function which is called when an image has been encoded.
        virtual Result OnEncodedImage(
            const EncodedImage& encoded_image,
            const CodecSpecificInfo* codec_specific_info,
            const RTPFragmentationHeader* fragmentation) = 0;
      
        virtual void OnDroppedFrame(DropReason reason) {}
      };
      
      
    • 使用VideoEncoder::Encode接口来编码一个VideoFrame;
    • 还有其他接口可以传递参数给编码器。

    5. 实战

    从上面的分析看,我们在开发中如果要自己创建视频编码器的话,最简单直接的办法是直接使用InternalEncoderFactory::CreateVideoEncoder接口来创建视频编码器。虽然InternalEncoderFactory的命名看起来是不对外开放的接口,但是其实在官方的examples/unityplugin/simple_peer_connection.cc下就有直接使用这个接口:

    bool SimplePeerConnection::InitializePeerConnection(const char** turn_urls,
                                                        const int no_of_urls,
                                                        const char* username,
                                                        const char* credential,
                                                        bool is_receiver) {
    bool SimplePeerConnection::InitializePeerConnection(const char** turn_urls,
                                                        const int no_of_urls,
                                                        const char* username,
                                                        const char* credential,
                                                        bool is_receiver) {
      RTC_DCHECK(peer_connection_.get() == nullptr);
    
      if (g_peer_connection_factory == nullptr) {
        g_worker_thread = rtc::Thread::Create();
        g_worker_thread->Start();
        g_signaling_thread = rtc::Thread::Create();
        g_signaling_thread->Start();
    
        g_peer_connection_factory = webrtc::CreatePeerConnectionFactory(
            g_worker_thread.get(), g_worker_thread.get(), g_signaling_thread.get(),
            nullptr, webrtc::CreateBuiltinAudioEncoderFactory(),
            webrtc::CreateBuiltinAudioDecoderFactory(),
            std::unique_ptr<webrtc::VideoEncoderFactory>(
                new webrtc::MultiplexEncoderFactory(
                    std::make_unique<webrtc::InternalEncoderFactory>())),
            std::unique_ptr<webrtc::VideoDecoderFactory>(
                new webrtc::MultiplexDecoderFactory(
                    std::make_unique<webrtc::InternalDecoderFactory>())),
            nullptr, nullptr);
      }
      if (!g_peer_connection_factory.get()) {
        DeletePeerConnection();
        return false;
      }
    
      g_peer_count++;
      if (!CreatePeerConnection(turn_urls, no_of_urls, username, credential)) {
        DeletePeerConnection();
        return false;
      }
    
      mandatory_receive_ = is_receiver;
      return peer_connection_.get() != nullptr;
    }  
    

    因此我们自己在开发时,也可以直接使用.

    这里我们在之前采集屏幕的例子上继续修改,添加一个video_encode_handler.h类用于进行编码:

    // video_encode_handler.h
    
    #ifndef EXAMPLES_DESKTOP_CAPTURE_VIDEO_ENCODE_HANDLER_H_
    #define EXAMPLES_DESKTOP_CAPTURE_VIDEO_ENCODE_HANDLER_H_
    
    #include "api/video/video_frame.h"
    #include "api/video/video_sink_interface.h"
    #include "api/video_codecs/video_encoder.h"
    #include "api/video/encoded_image.h"
    #include "modules/video_coding/include/video_codec_interface.h"
    #include "modules/include/module_common_types.h"
    
    namespace webrtc_demo {
    
    class VideoEncodeHandler : public rtc::VideoSinkInterface<webrtc::VideoFrame>,
                               public webrtc::EncodedImageCallback {
     public:
      enum class VideoEncodeType {
        VP8,
        VP9,
        H264,
        UNSUPPORT_TYPE,
      };
    
      ~VideoEncodeHandler();
    
      static std::unique_ptr<VideoEncodeHandler> Create(const VideoEncodeType type);
    
     private:
      VideoEncodeHandler(const VideoEncodeType type);
    
      // rtc::VideoSinkInterface override
      void OnFrame(const webrtc::VideoFrame& frame) override;
    
      // webrtc::EncodedImageCallback override
      webrtc::EncodedImageCallback::Result OnEncodedImage(
          const webrtc::EncodedImage& encoded_image,
          const webrtc::CodecSpecificInfo* codec_specific_info,
          const webrtc::RTPFragmentationHeader* fragmentation) override;
      void OnDroppedFrame(webrtc::EncodedImageCallback::DropReason reason) override;
    
      webrtc::VideoCodec DefaultCodecSettings(size_t width, size_t height, size_t keyFrameInterval);
    
      void ReInitEncoder();
    
      std::unique_ptr<webrtc::VideoEncoder> video_encoder_;
      std::string encode_type_name_;
      size_t frame_width_ = 0;
      size_t frame_height_ = 0;
    };
    
    }  // namespace webrtc_demo
    
    #endif  // EXAMPLES_DESKTOP_CAPTURE_VIDEO_ENCODE_HANDLER_H_
    

    首先,作为一个Sink可以注册给抓屏对象,使得能够获取到抓屏得到的frame;然后将自己作为EncodedImageCallback实例注册给VideoEncoder:

    // video_encode_handler.cc
    
    #include "examples/desktop_capture/video_encode_handler.h"
    
    #include "api/video/video_codec_type.h"
    #include "api/video_codecs/sdp_video_format.h"
    #include "api/video_codecs/video_codec.h"
    #include "media/engine/internal_encoder_factory.h"
    #include "rtc_base/logging.h"
    #include "test/video_codec_settings.h"
    
    namespace webrtc_demo {
    
    constexpr size_t kWidth = 1920;
    constexpr size_t kHeight = 1080;
    constexpr size_t kBaseKeyFrameInterval = 30;
    
    const webrtc::VideoEncoder::Capabilities kCapabilities(false);
    const webrtc::VideoEncoder::Settings kSettings(kCapabilities,
                                                   /*number_of_cores=*/1,
                                                   /*max_payload_size=*/0);
    
    VideoEncodeHandler::VideoEncodeHandler(const VideoEncodeType type)
        : video_encoder_(nullptr), frame_width_(kWidth), frame_height_(kHeight) {
      switch (type) {
        case VideoEncodeType::VP8:
          encode_type_name_ = "VP8";
          break;
        case VideoEncodeType::VP9:
          encode_type_name_ = "VP9";
          break;
        case VideoEncodeType::H264:
          encode_type_name_ = "H264";
          break;
        default:
          break;
      }
    
      auto support_formats = webrtc::InternalEncoderFactory::SupportedFormats();
      for (auto& format : support_formats) {
        RTC_LOG(LS_INFO) << "Support encode: " << format.ToString();
        if (!video_encoder_ && format.name == encode_type_name_) {
          RTC_LOG(LS_INFO) << "Find encode: " << format.name;
          std::unique_ptr<webrtc::InternalEncoderFactory> encode_factory =
              std::make_unique<webrtc::InternalEncoderFactory>();
          video_encoder_ = encode_factory->CreateVideoEncoder(format);
          video_encoder_->RegisterEncodeCompleteCallback(this);
    
          ReInitEncoder();
        }
      }
    }
    
    void VideoEncodeHandler::ReInitEncoder() {
      webrtc::VideoCodec codec_settings =
          DefaultCodecSettings(frame_width_, frame_height_, kBaseKeyFrameInterval);
      video_encoder_->InitEncode(&codec_settings, kSettings);
    }
    
    VideoEncodeHandler::~VideoEncodeHandler() {}
    
    std::unique_ptr<VideoEncodeHandler> VideoEncodeHandler::Create(
        const VideoEncodeType type) {
      if (type >= VideoEncodeType::UNSUPPORT_TYPE) {
        RTC_LOG(LS_WARNING) << "Not support encode type: " << type;
        return nullptr;
      }
    
      return std::unique_ptr<VideoEncodeHandler>(new VideoEncodeHandler(type));
    }
    
    void VideoEncodeHandler::OnFrame(const webrtc::VideoFrame& frame) {
      // RTC_LOG(LS_INFO) << "-----VideoEncodeHandler::OnFrame-----";
      if (!video_encoder_) {
        RTC_LOG(LS_ERROR) << "Encoder not valid";
        return;
      }
    
      if (frame.width() != (int)frame_width_ || frame.height() != (int)frame_height_) {
        frame_width_ = frame.width();
        frame_height_ = frame.height();
        ReInitEncoder();
      }
    
      if (video_encoder_) {
        video_encoder_->Encode(frame, nullptr);
      }
    }
    
    webrtc::EncodedImageCallback::Result VideoEncodeHandler::OnEncodedImage(
        const webrtc::EncodedImage& encoded_image,
        const webrtc::CodecSpecificInfo* codec_specific_info,
        const webrtc::RTPFragmentationHeader* fragmentation) {
      RTC_LOG(LS_INFO) << "-----VideoEncodeHandler::OnEncodedImage-----" << encoded_image.size() << ", " << encoded_image.Timestamp() << "--" << encoded_image._completeFrame;
    
      return webrtc::EncodedImageCallback::Result(
          webrtc::EncodedImageCallback::Result::OK);
    }
    
    void VideoEncodeHandler::OnDroppedFrame(
        webrtc::EncodedImageCallback::DropReason reason) {
      RTC_LOG(LS_INFO) << "-----VideoEncodeHandler::OnDroppedFrame-----";
    }
    
    webrtc::VideoCodec VideoEncodeHandler::DefaultCodecSettings(
        size_t width,
        size_t height,
        size_t key_frame_interval) {
      webrtc::VideoCodec codec_settings;
      webrtc::VideoCodecType codec_type =
          webrtc::PayloadStringToCodecType(encode_type_name_);
      webrtc::test::CodecSettings(codec_type, &codec_settings);
    
      codec_settings.width = static_cast<uint16_t>(width);
      codec_settings.height = static_cast<uint16_t>(height);
    
      switch (codec_settings.codecType) {
        case webrtc::kVideoCodecVP8:
          codec_settings.VP8()->keyFrameInterval = key_frame_interval;
          codec_settings.VP8()->frameDroppingOn = true;
          codec_settings.VP8()->numberOfTemporalLayers = 1;
          break;
        case webrtc::kVideoCodecVP9:
          codec_settings.VP9()->keyFrameInterval = key_frame_interval;
          codec_settings.VP9()->frameDroppingOn = true;
          codec_settings.VP9()->numberOfTemporalLayers = 1;
          break;
        case webrtc::kVideoCodecAV1:
          codec_settings.qpMax = 63;
          break;
        case webrtc::kVideoCodecH264:
          codec_settings.H264()->keyFrameInterval = key_frame_interval;
          break;
        default:
          break;
      }
    
      return codec_settings;
    }
    
    }  // namespace webrtc_demo
    
    
    • webrtc::InternalEncoderFactory::SupportedFormats先确认我们支持的编码格式,然后创建encoder:
      std::unique_ptr<webrtc::InternalEncoderFactory> encode_factory =
            std::make_unique<webrtc::InternalEncoderFactory>();
      video_encoder_ = encode_factory->CreateVideoEncoder(format);
      video_encoder_->RegisterEncodeCompleteCallback(this);
      
    • 在进行Encode编码之前,我们需要想初始化我们的编码器。
      void VideoEncodeHandler::ReInitEncoder() {
          webrtc::VideoCodec codec_settings =
                DefaultCodecSettings(frame_width_, frame_height_, kBaseKeyFrameInterval);
          video_encoder_->InitEncode(&codec_settings, kSettings);
      }
      
    • 获取默认的编码器配置是调用了test/video_codec_settings.h里的webrtc::test::CodecSettings接口进行初步的初始化,实际上完全可以自己仿照着写一个。
    • 进行编码VideoEncoder::Encode
    • 获取编码结果只要通过重载的EncodedImageCallback::OnEncodedImage函数就可以实现。

    最后在main函数中添加:

    #include "examples/desktop_capture/desktop_capture.h"
    #include "test/video_renderer.h"
    #include "rtc_base/logging.h"
    #include "examples/desktop_capture/video_encode_handler.h"
    
    #include <thread>
    
    int main() {
      std::unique_ptr<webrtc_demo::DesktopCapture> capturer(webrtc_demo::DesktopCapture::Create(30,0));
    
      capturer->StartCapture();
    
      std::unique_ptr<webrtc::test::VideoRenderer> renderer(webrtc::test::VideoRenderer::Create(capturer->GetWindowTitle().c_str(), 720, 480));
      capturer->AddOrUpdateSink(renderer.get(), rtc::VideoSinkWants());
    
      std::unique_ptr<webrtc_demo::VideoEncodeHandler> video_encode(webrtc_demo::VideoEncodeHandler::Create(webrtc_demo::VideoEncodeHandler::VideoEncodeType::VP8));
      capturer->AddOrUpdateSink(video_encode.get(), rtc::VideoSinkWants());
    
      std::this_thread::sleep_for(std::chrono::seconds(30));
      capturer->RemoveSink(renderer.get());
      capturer->RemoveSink(video_encode.get());
    
      RTC_LOG(WARNING) << "Demo exit";
      return 0;
    }
    

    视频编码的代码分支: https://github.com/243286065/webrtc-cpp-demo/tree/76e12021e0469d5108930ed4f28df308d6916791
    提交commit: https://github.com/243286065/webrtc-cpp-demo/commit/76e12021e0469d5108930ed4f28df308d6916791

    webrtc中其实主要使用VideoStreamEncoder对象来创建VideoEncoder实例,不过基本原理差不多,只是它做了更多的封装,就编码器来说,和我们的VideoEncodeHandler大致实现原理是一样的。

  • 相关阅读:
    java学习(19-IO高级)
    java学习(18-异常)
    java学习(17-Map和Set)
    java学习笔记(16-集合)
    java学习笔记(15-高级api)
    java学习笔记(14-包和权限修饰符)
    Deno文件处理详解:如何写入文件、如何读文件、如何创建删除以及检查文件和目录。
    2020年12月8日建站随笔:IPlayIO中文网从上线到现在的一些总结
    2020 FreeBSD如何更换国内仓库源
    推荐11个值得研究学习的Python开源项目(从入门到python高级开发)
  • 原文地址:https://www.cnblogs.com/xl2432/p/13864448.html
Copyright © 2011-2022 走看看