[*] 编译

This commit is contained in:
acgist
2024-05-10 22:26:21 +08:00
parent 4947ef8c4c
commit 41e574caf5
19 changed files with 467 additions and 204 deletions

View File

@@ -3,7 +3,7 @@
## 支持版本
* SDK 11
* WebRTC m114
* WebRTC m120
* libmediasoupclient m120
## C++终端
@@ -25,12 +25,16 @@
## 鸿蒙编译
```
# WebRTC版本m114
# WebRTC版本m120
# libmediasoupclient版本m120
gn gen ./out/ohos_webrtc --args='target_os="ohos" target_cpu="arm64" is_clang=true is_debug=false use_rtti=true rtc_use_h264=true use_custom_libcxx=false rtc_include_tests=false is_component_build=false treat_warnings_as_errors=false rtc_build_examples=false libyuv_include_tests=false rtc_use_dummy_audio_file_devices=true ohos_sdk_native_root="/data/dev/ohos-sdk/linux/native"'
# armeabi-v7a
gn gen ./out/armeabi-v7a --args='target_os="ohos" target_cpu="arm" is_clang=true is_debug=false use_rtti=true rtc_use_h264=true rtc_use_h265=true rtc_libvpx_build_vp9=true is_component_build=false rtc_include_tests=false libyuv_include_tests=false rtc_build_examples=false treat_warnings_as_errors=false ohos_sdk_native_root="/data/dev/ohos-sdk/linux/native"'
ninja -C ./out/armeabi-v7a -j 32
ninja -C ./out/ohos_webrtc -j 32
# arm64-v8a
gn gen ./out/arm64-v8a --args='target_os="ohos" target_cpu="arm64" is_clang=true is_debug=false use_rtti=true rtc_use_h264=true rtc_use_h265=true rtc_libvpx_build_vp9=true is_component_build=false rtc_include_tests=false libyuv_include_tests=false rtc_build_examples=false treat_warnings_as_errors=false ohos_sdk_native_root="/data/dev/ohos-sdk/linux/native"'
ninja -C ./out/arm64-v8a -j 32
```
## openharmony-sig/ohos_webrtc

View File

@@ -13,15 +13,15 @@
{
"name": "default",
"material": {
"signAlg" : "SHA256withECDSA",
"keyAlias" : "debugKey",
"signAlg" : "SHA256withECDSA",
"profile" : "C:/Users/acgis/.ohos/config/openharmony/default_taoyao_RrmPaKOZ5zMCU4QlpK1kz_6UoyvfsA4RcQxqexSUJ_8=.p7b",
"certpath" : "C:/Users/acgis/.ohos/config/openharmony/default_taoyao_RrmPaKOZ5zMCU4QlpK1kz_6UoyvfsA4RcQxqexSUJ_8=.cer",
"storeFile": "C:/Users/acgis/.ohos/config/openharmony/default_taoyao_RrmPaKOZ5zMCU4QlpK1kz_6UoyvfsA4RcQxqexSUJ_8=.p12",
"keyPassword" : "0000001B8701B2982D8D56F7049724D2E89B72966750D8E2D94B37DDF1538D9D16EB00C8D4DA39EE4C0FC7",
"storePassword": "0000001B64642A0B174393A9875F6FCDE476F4BA4682F1EF92210832926366EA9E075136DCBB6361C9D4DD"
}
"keyPassword" : "0000001BD55750CF879F40EE4EA1B96386BD83692A36E1776974D52D7439FEEEFCB8D2EC5C798FC7C815AE",
"storePassword": "0000001B2AC157AB2A1227E77D5E4925A0B59DE84E94B4D146D9C4DA5EAB030537941F0436A3C3E9F26424"
}
},
],
},
"modules": [

View File

@@ -4,12 +4,17 @@
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"cppFlags" : "",
"arguments" : "",
"arguments" : " \
-DMEDIASOUPCLIENT_LOG_DEV=OFF \
-DMEDIASOUPCLIENT_LOG_TRACE=OFF \
-DMEDIASOUPCLIENT_BUILD_TESTS=OFF \
",
// 不要自作多情添加一些配置🤡🤡🤡🤡
// "cppFlags" : "-D_LIBCPP_STD_VER=17",
// "arguments" : "-DOHOS_STL=c++_static",
// "arguments" : "-DOHOS_STL=c++_shared",
"abiFilters": [ "arm64-v8a" ]
// "abiFilters": [ "arm64-v8a" ]
"abiFilters": [ "armeabi-v7a" ]
}
},
"targets": [

View File

@@ -8,22 +8,20 @@ project(taoyao VERSION 1.0.0 LANGUAGES C CXX)
# C编译选项
set(CMAKE_C_STANDARD 17)
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c17 -O3")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -std=c17 -O0 -g")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -std=c17 -O3")
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-rtti -std=c17 -O3")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-rtti -std=c17 -O0 -g")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fno-rtti -std=c17 -O3")
# C++编译选项
set(CMAKE_CXX_STANDARD 17)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++17 -O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -std=c++17 -O3")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -std=c++17 -O3")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-rtti -std=c++17 -O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti -std=c++17 -O3")
option(MEDIASOUPCLIENT_LOG_DEV OFF)
option(MEDIASOUPCLIENT_LOG_TRACE OFF)
option(MEDIASOUPCLIENT_BUILD_TESTS OFF)
set(LIBWEBRTC_BINARY_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/webrtc/lib/")
set(LIBWEBRTC_BINARY_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/webrtc/lib/${OHOS_ARCH}/")
set(LIBWEBRTC_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/webrtc/src/")
ADD_DEFINITIONS(-DNDEBUG)
ADD_DEFINITIONS(-DVK_USE_PLATFORM_OHOS=1)
add_subdirectory("./deps/libmediasoupclient")
@@ -37,29 +35,27 @@ add_library(
media/RoomClient.cpp
media/LocalClient.cpp
media/RemoteClient.cpp
media/MediaManager.cpp
media/Player.cpp
media/AudioPlayer.cpp
media/VideoPlayer.cpp
media/VideoDecoder.cpp
media/Capturer.cpp
media/VideoEncoder.cpp
media/AudioCapturer.cpp
media/VideoCapturer.cpp
media/VideoEncoder.cpp
media/MediaManager.cpp
)
target_include_directories(
${PROJECT_NAME} PUBLIC
"./include"
"./deps/webrtc"
"./deps/webrtc/src"
"./deps/libmediasoupclient/include"
"./deps/libmediasoupclient/deps/libsdptransform/include"
)
target_link_libraries(
${PROJECT_NAME} PUBLIC
mediasoupclient
${LIBWEBRTC_BINARY_PATH}/libwebrtc.a
# NAPI
libace_napi.z.so
# LOG
@@ -76,14 +72,15 @@ target_link_libraries(
libohcamera.so
# 图片
libnative_image.so
# NativeBuffer
libnative_buffer.so
# NativeWindow
libnative_window.so
# 编码解码
libnative_media_aenc.so
libnative_media_venc.so
libnative_media_core.so
libnative_media_codecbase.so
# NativeBuffer
libnative_buffer.so
# NativeWindow
libnative_window.so
# libmediasoupclient
mediasoupclient
)

View File

@@ -1,8 +1,6 @@
/**
* 采集器
*
* TODO: 释放资源判断空指针
*
* @author acgist
*
* https://docs.openharmony.cn/pages/v4.1/zh-cn/application-dev/media/camera/camera-overview.md
@@ -19,6 +17,7 @@
#ifndef TAOYAO_CAPTURER_HPP
#define TAOYAO_CAPTURER_HPP
// OpenGL ES || VULKAN
#define __VULKAN__ true
#ifndef __VULKAN__
#define __OPENGL__ true
@@ -31,6 +30,7 @@
#include <GLES3/gl32.h>
#include "./Signal.hpp"
#include "./WebRTC.hpp"
#include <vulkan/vulkan.h>
@@ -43,6 +43,7 @@
#include <ohcamera/capture_session.h>
#include "api/media_stream_track.h"
#include "api/media_stream_interface.h"
#include <ohaudio/native_audiocapturer.h>
#include <ohaudio/native_audiostreambuilder.h>
@@ -52,27 +53,22 @@ namespace acgist {
/**
* 采集器
*
* @tparam Sink 输出管道
* @tparam Source 输出管道
*/
template <typename Sink>
template <typename Source>
class Capturer {
protected:
bool running = false;
public:
// id = rtc::scoped_refptr
std::map<std::string, Sink*> map;
Source* source;
public:
Capturer();
virtual ~Capturer();
public:
// 添加管道
virtual bool add(const std::string& id, Sink* sink);
// 删除管道
virtual bool remove(const std::string& id);
// 开始采集
virtual bool start() = 0;
// 结束采集
@@ -80,42 +76,19 @@ public:
};
template <typename Sink>
acgist::Capturer<Sink>::Capturer() {}
template <typename Source>
acgist::Capturer<Source>::Capturer() {}
template <typename Sink>
acgist::Capturer<Sink>::~Capturer() {
for (auto iterator = this->map.begin(); iterator != this->map.end(); ++iterator) {
template <typename Source>
acgist::Capturer<Source>::~Capturer() {
// TODO释放
// delete iterator->second;
// iterator->second = nullptr;
}
this->map.clear();
}
template <typename Sink>
bool acgist::Capturer<Sink>::add(const std::string& id, Sink* sink) {
this->map.insert({ id, sink });
return true;
}
template <typename Sink>
bool acgist::Capturer<Sink>::remove(const std::string& id) {
auto iterator = this->map.find(id);
if (iterator == this->map.end()) {
return false;
}
// TODO释放
// delete iterator->second;
// iterator->second = nullptr;
this->map.erase(iterator);
return true;
// delete this->source;
}
/**
* 音频采集器
*/
class AudioCapturer: public Capturer<webrtc::AudioTrackSinkInterface> {
class AudioCapturer: public Capturer<acgist::TaoyaoAudioTrackSource> {
public:
// 音频构造器
@@ -136,7 +109,7 @@ public:
/**
* 视频采集器
*/
class VideoCapturer: public Capturer<rtc::VideoSinkInterface<webrtc::VideoFrame>> {
class VideoCapturer: public Capturer<acgist::TaoyaoVideoTrackSource> {
public:
// ================ Vulkan ================

View File

@@ -13,22 +13,13 @@
#include <memory>
#include <thread>
#include "api/media_stream_interface.h"
#include "./WebRTC.hpp"
#include "./Capturer.hpp"
#include "api/peer_connection_interface.h"
#include "api/video/video_sink_interface.h"
#include "api/video/video_source_interface.h"
namespace acgist {
class TaoyaoAudioSink : public webrtc::AudioTrackSinkInterface {
};
class TaoyaoVideoSource : public webrtc::VideoTrackSourceInterface {
};
class TaoyaoVideoSink : public rtc::VideoSinkInterface<webrtc::RecordableEncodedFrame> {
};
class MediaManager {
public:
@@ -37,6 +28,10 @@ public:
public:
int localClientRef = 0;
rtc::scoped_refptr<webrtc::AudioSourceInterface> audioTrackSource = nullptr;
acgist::TaoyaoVideoTrackSource* videoTrackSource = nullptr;
acgist::AudioCapturer* audioCapturer = nullptr;
acgist::VideoCapturer* videoCapturer = nullptr;
std::unique_ptr<rtc::Thread> networkThread = nullptr;
std::unique_ptr<rtc::Thread> signalingThread = nullptr;
std::unique_ptr<rtc::Thread> workerThread = nullptr;
@@ -47,14 +42,6 @@ protected:
bool newPeerConnectionFactory();
// 释放PC工厂
bool releasePeerConnectionFactory();
public:
// 加载媒体
bool init();
// 新增本地终端
int newLocalClient();
// 释放本地终端
int releaseLocalClient();
// 开始采集
bool startCapture();
// 开始采集音频
@@ -67,6 +54,14 @@ public:
bool stopAudioCapture();
// 结束采集视频
bool stopVideoCapture();
public:
// 加载媒体
bool init();
// 新增本地终端
int newLocalClient();
// 释放本地终端
int releaseLocalClient();
// 音频来源
rtc::scoped_refptr<webrtc::AudioTrackInterface> getAudioTrack();
// 视频来源

View File

@@ -7,7 +7,6 @@
#ifndef TAOYAO_SIGNAL_HPP
#define TAOYAO_SIGNAL_HPP
#include <bits/alltypes.h>
#include <string>
#include <json.hpp>

View File

@@ -1,10 +1,36 @@
/**
* WebRTC功能
*
* 视频编码解码
*
* @author acgist
*/
#ifndef TAOYAO_WEBRTC_HPP
#define TAOYAO_WEBRTC_HPP
#include "./Signal.hpp"
#include <ohaudio/native_audiorenderer.h>
#include <ohaudio/native_audiostreambuilder.h>
//#include <ohaudio/native_audiorenderer.h>
//#include <ohaudio/native_audiostreambuilder.h>
//#include "api/audio_codecs/audio_encoder.h"
//#include "api/audio_codecs/audio_decoder.h"
//#include "api/audio_codecs/audio_encoder_factory.h"
//#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/media_stream_track.h"
#include "api/media_stream_interface.h"
#include "api/video/video_sink_interface.h"
#include "api/video/video_source_interface.h"
#include "media/base/video_broadcaster.h"
#include "media/base/adapted_video_track_source.h"
#include "api/video_codecs/video_encoder.h"
#include "api/video_codecs/video_decoder.h"
#include "api/video_codecs/video_encoder_factory.h"
#include "api/video_codecs/video_decoder_factory.h"
#include <multimedia/player_framework/native_avformat.h>
#include <multimedia/player_framework/native_avbuffer.h>
@@ -13,23 +39,50 @@
#include <multimedia/player_framework/native_avcodec_videoencoder.h>
#include <multimedia/player_framework/native_avcodec_videodecoder.h>
#include "api/rtp_sender_interface.h"
#include "api/video_codecs/sdp_video_format.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
#include "modules/video_coding/codecs/vp8/include/vp8.h"
#include "modules/video_coding/codecs/vp9/include/vp9.h"
#include "modules/video_coding/codecs/h264/include/h264.h"
namespace acgist {
/**
* 音频轨道来源
*/
class TaoyaoAudioTrackSource : public webrtc::AudioTrackSinkInterface {
public:
virtual void OnData(const void* audio_data, int bits_per_sample, int sample_rate, size_t number_of_channels, size_t number_of_frames) override;
};
/**
* 视频管道
*/
class VideoTrackSinkInterface {
public:
virtual void OnData(const webrtc::VideoFrame& videoFrame) = 0;
};
/**
* 视频轨道来源
*/
class TaoyaoVideoTrackSource : public VideoTrackSinkInterface, public rtc::AdaptedVideoTrackSource {
public:
TaoyaoVideoTrackSource();
virtual ~TaoyaoVideoTrackSource() override;
public:
virtual webrtc::MediaSourceInterface::SourceState state() const override;
virtual bool remote() const override;
virtual bool is_screencast() const override;
virtual absl::optional<bool> needs_denoising() const override;
virtual void OnData(const webrtc::VideoFrame& videoFrame) override;
};
/**
* 视频编码
*/
class VideoEncoder : public webrtc::VideoEncoder {
class TaoyaoVideoEncoder : public webrtc::VideoEncoder {
public:
// 视频编码器
@@ -38,8 +91,8 @@ public:
OHNativeWindow* nativeWindow = nullptr;
public:
VideoEncoder();
virtual ~VideoEncoder();
TaoyaoVideoEncoder();
virtual ~TaoyaoVideoEncoder() override;
public:
// 初始配置
@@ -58,25 +111,34 @@ public:
virtual bool start();
// 结束编码
virtual bool stop();
virtual int32_t Release() override;
virtual int32_t RegisterEncodeCompleteCallback(webrtc::EncodedImageCallback* callback) override;
virtual void SetRates(const webrtc::VideoEncoder::RateControlParameters& parameters) override;
virtual webrtc::VideoEncoder::EncoderInfo GetEncoderInfo() const override;
virtual int32_t Encode(const webrtc::VideoFrame& frame, const std::vector<webrtc::VideoFrameType>* frame_types) override;
};
/**
* 视频解码器
*/
class VideoDecoder : public webrtc::VideoDecoder {
class TaoyaoVideoDecoder : public webrtc::VideoDecoder {
public:
VideoDecoder();
virtual ~VideoDecoder();
TaoyaoVideoDecoder();
virtual ~TaoyaoVideoDecoder() override;
public:
virtual bool start();
virtual bool stop();
virtual int32_t Release() override;
virtual int32_t RegisterDecodeCompleteCallback(webrtc::DecodedImageCallback* callback) override;
virtual bool Configure(const webrtc::VideoDecoder::Settings& settings) override;
virtual int32_t Decode(const webrtc::EncodedImage& input_image, bool missing_frames, int64_t render_time_ms) override;
};
class TaoyaoVideoEncoderFactory : webrtc::VideoEncoderFactory {
class TaoyaoVideoEncoderFactory : public webrtc::VideoEncoderFactory {
public:
TaoyaoVideoEncoderFactory();
@@ -88,7 +150,7 @@ public:
};
class TaoyaoVideoDecoderFactory : webrtc::VideoDecoderFactory {
class TaoyaoVideoDecoderFactory : public webrtc::VideoDecoderFactory {
public:
TaoyaoVideoDecoderFactory();

View File

@@ -1,9 +1,11 @@
/**
* 音频采集不用实现(系统已经实现)
* 这里只是用来学习使用
*/
#include "../include/Capturer.hpp"
#include <hilog/log.h>
#include "rtc_base/time_utils.h"
// 采集回调
static int32_t OnError(OH_AudioCapturer* capturer, void* userData, OH_AudioStream_Result error);
static int32_t OnReadData(OH_AudioCapturer* capturer, void* userData, void* buffer, int32_t length);
@@ -77,10 +79,7 @@ static int32_t OnError(OH_AudioCapturer* capturer, void* userData, OH_AudioStrea
static int32_t OnReadData(OH_AudioCapturer* capturer, void* userData, void* buffer, int32_t length) {
acgist::AudioCapturer* audioCapturer = (acgist::AudioCapturer*) userData;
int64_t timeMillis = rtc::TimeMillis();
for (auto iterator = audioCapturer->map.begin(); iterator != audioCapturer->map.end(); ++iterator) {
iterator->second->OnData(buffer, acgist::bitsPerSample, acgist::samplingRate, acgist::channelCount, sizeof(buffer) / 2, timeMillis);
}
audioCapturer->source->OnData(buffer, acgist::bitsPerSample, acgist::samplingRate, acgist::channelCount, sizeof(buffer) / 2);
return 0;
}

View File

@@ -1,3 +1,8 @@
/**
* 音频播放不用实现(系统已经实现)
* 这里只是用来学习使用
*/
#include "../include/Player.hpp"
#include <hilog/log.h>

View File

@@ -2,7 +2,7 @@
#include <mutex>
#include "hilog/log.h"
#include <hilog/log.h>
#include "api/create_peerconnection_factory.h"
#include "api/audio_codecs/audio_decoder_factory.h"
@@ -45,17 +45,23 @@ bool acgist::MediaManager::newPeerConnectionFactory() {
}
this->peerConnectionFactory = webrtc::CreatePeerConnectionFactory(
this->networkThread.get(),
// worker和signaling使用相同线程
this->workerThread.get(),
// this->signalingThread.get(),
this->signalingThread.get(),
nullptr /* default_adm */,
// 音频设备
nullptr,
// 音频编码
webrtc::CreateBuiltinAudioEncoderFactory(),
// 音频解码
webrtc::CreateBuiltinAudioDecoderFactory(),
// 视频编码
webrtc::CreateBuiltinVideoEncoderFactory(),
// 视频解码
webrtc::CreateBuiltinVideoDecoderFactory(),
nullptr /* audio_mixer */,
nullptr /* audio_processing */
// 混音
nullptr,
// 音频处理
nullptr
);
return this->peerConnectionFactory != nullptr;
}
@@ -65,7 +71,11 @@ bool acgist::MediaManager::releasePeerConnectionFactory() {
return true;
}
OH_LOG_INFO(LOG_APP, "释放PeerConnectionFactory");
// TODO释放
if(this->peerConnectionFactory != nullptr) {
this->peerConnectionFactory->Release();
// delete this->peerConnectionFactory;
this->peerConnectionFactory = nullptr;
}
return true;
}
@@ -78,6 +88,7 @@ int acgist::MediaManager::newLocalClient() {
this->startCapture();
}
}
return this->localClientRef;
}
int acgist::MediaManager::releaseLocalClient() {
@@ -93,31 +104,56 @@ int acgist::MediaManager::releaseLocalClient() {
}
}
}
return this->localClientRef;
}
bool acgist::MediaManager::startCapture() {
this->startAudioCapture();
this->startVideoCapture();
return true;
}
bool acgist::MediaManager::startAudioCapture() {
if(this->audioCapturer != nullptr) {
return true;
}
this->audioCapturer = new acgist::AudioCapturer();
this->audioCapturer->start();
return true;
}
bool acgist::MediaManager::startVideoCapture() {
if(this->videoCapturer != nullptr) {
return true;
}
this->videoCapturer = new acgist::VideoCapturer();
this->videoCapturer->start();
return true;
}
bool acgist::MediaManager::stopCapture() {
this->stopAudioCapture();
this->stopVideoCapture();
return true;
}
bool acgist::MediaManager::stopAudioCapture() {
if(this->audioCapturer == nullptr) {
return true;
}
this->audioCapturer->stop();
delete this->audioCapturer;
this->audioCapturer = nullptr;
return true;
}
bool acgist::MediaManager::stopVideoCapture() {
if(this->videoCapturer == nullptr) {
return true;
}
this->videoCapturer->stop();
delete this->videoCapturer;
this->videoCapturer = nullptr;
return true;
}
@@ -127,12 +163,11 @@ rtc::scoped_refptr<webrtc::AudioTrackInterface> acgist::MediaManager::getAudioTr
options.auto_gain_control = true;
options.echo_cancellation = true;
options.noise_suppression = true;
auto audioSource = this->peerConnectionFactory->CreateAudioSource(options);
return this->peerConnectionFactory->CreateAudioTrack("taoyao-audio", audioSource.get());
this->audioTrackSource = this->peerConnectionFactory->CreateAudioSource(options);
return this->peerConnectionFactory->CreateAudioTrack("taoyao-audio", audioTrackSource.get());
}
rtc::scoped_refptr<webrtc::VideoTrackInterface> acgist::MediaManager::getVideoTrack() {
// webrtc::VideoTrackSourceInterface videoSource;
// this->peerConnectionFactory->CreateVideoTrack("taoyao-video", videoSource);
return nullptr;
this->videoTrackSource = new rtc::RefCountedObject<acgist::TaoyaoVideoTrackSource>();
return this->peerConnectionFactory->CreateVideoTrack("taoyao-video", this->videoTrackSource);
}

View File

@@ -1 +1,34 @@
#include "../include/WebRTC.hpp"
#include "api/video_codecs/video_encoder.h"
#include "api/video_codecs/video_decoder.h"
acgist::TaoyaoVideoDecoder::TaoyaoVideoDecoder() {
}
acgist::TaoyaoVideoDecoder::~TaoyaoVideoDecoder() {
}
bool acgist::TaoyaoVideoDecoder::start() {
return true;
}
bool acgist::TaoyaoVideoDecoder::stop() {
return true;
}
int32_t acgist::TaoyaoVideoDecoder::Release() {
return 0;
}
int32_t acgist::TaoyaoVideoDecoder::RegisterDecodeCompleteCallback(webrtc::DecodedImageCallback* callback) {
return 0;
}
bool acgist::TaoyaoVideoDecoder::Configure(const webrtc::VideoDecoder::Settings& settings) {
return true;
}
int32_t acgist::TaoyaoVideoDecoder::Decode(const webrtc::EncodedImage& input_image, bool missing_frames, int64_t render_time_ms) {
return 0;
}

View File

@@ -9,13 +9,16 @@
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_avcapability.h>
#include "api/video_codecs/video_encoder.h"
#include "api/video_codecs/video_decoder.h"
// 编码回调
static void OnError(OH_AVCodec* codec, int32_t errorCode, void* userData);
static void OnStreamChanged(OH_AVCodec* codec, OH_AVFormat* format, void* userData);
static void OnNeedInputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData);
static void OnNewOutputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData);
acgist::VideoEncoder::VideoEncoder() {
acgist::TaoyaoVideoEncoder::TaoyaoVideoEncoder() {
OH_AVCapability* capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
const char* codecName = OH_AVCapability_GetName(capability);
this->avCodec = OH_VideoEncoder_CreateByName(codecName);
@@ -29,7 +32,7 @@ acgist::VideoEncoder::VideoEncoder() {
this->initFormatConfig(format);
ret = OH_VideoEncoder_Configure(this->avCodec, format);
OH_AVFormat_Destroy(format);
OH_LOG_INFO(LOG_APP, "配置编码参数:%o %d %d %f %ld", ret, acgist::width, acgist::height, acgist::frameRate, acgist::bitrate);
OH_LOG_INFO(LOG_APP, "配置编码参数:%o %d %d %f %lld", ret, acgist::width, acgist::height, acgist::frameRate, acgist::bitrate);
// 准备就绪
ret = OH_VideoEncoder_Prepare(this->avCodec);
OH_LOG_INFO(LOG_APP, "视频编码准备就绪:%o", ret);
@@ -38,7 +41,7 @@ acgist::VideoEncoder::VideoEncoder() {
OH_LOG_INFO(LOG_APP, "配置视频窗口:%o", ret);
}
acgist::VideoEncoder::~VideoEncoder() {
acgist::TaoyaoVideoEncoder::~TaoyaoVideoEncoder() {
if(this->avCodec != nullptr) {
OH_AVErrCode ret = OH_VideoEncoder_Destroy(this->avCodec);
this->avCodec = nullptr;
@@ -51,7 +54,7 @@ acgist::VideoEncoder::~VideoEncoder() {
}
}
void acgist::VideoEncoder::initFormatConfig(OH_AVFormat* format) {
void acgist::TaoyaoVideoEncoder::initFormatConfig(OH_AVFormat* format) {
// https://docs.openharmony.cn/pages/v4.1/zh-cn/application-dev/media/avcodec/video-encoding.md
// 配置视频宽度
OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, acgist::width);
@@ -81,21 +84,21 @@ void acgist::VideoEncoder::initFormatConfig(OH_AVFormat* format) {
OH_AVFormat_SetIntValue(format, OH_MD_KEY_QUALITY, 0);
}
void acgist::VideoEncoder::restart() {
void acgist::TaoyaoVideoEncoder::restart() {
OH_AVErrCode ret = OH_VideoEncoder_Flush(this->avCodec);
OH_LOG_INFO(LOG_APP, "清空编码队列:%o", ret);
ret = OH_VideoEncoder_Start(this->avCodec);
OH_LOG_INFO(LOG_APP, "开始视频编码:%o", ret);
}
void acgist::VideoEncoder::reset(OH_AVFormat* format) {
void acgist::TaoyaoVideoEncoder::reset(OH_AVFormat* format) {
OH_AVErrCode ret = OH_VideoEncoder_Reset(this->avCodec);
OH_LOG_INFO(LOG_APP, "重置视频编码:%o", ret);
ret = OH_VideoEncoder_Configure(this->avCodec, format);
OH_LOG_INFO(LOG_APP, "配置视频编码:%o", ret);
}
void acgist::VideoEncoder::resetIntConfig(const char* key, int32_t value) {
void acgist::TaoyaoVideoEncoder::resetIntConfig(const char* key, int32_t value) {
OH_AVFormat* format = OH_AVFormat_Create();
OH_AVFormat_SetIntValue(format, key, value);
OH_AVErrCode ret = OH_VideoEncoder_SetParameter(this->avCodec, format);
@@ -103,15 +106,15 @@ void acgist::VideoEncoder::resetIntConfig(const char* key, int32_t value) {
OH_AVFormat_Destroy(format);
}
void acgist::VideoEncoder::resetLongConfig(const char* key, int64_t value) {
void acgist::TaoyaoVideoEncoder::resetLongConfig(const char* key, int64_t value) {
OH_AVFormat* format = OH_AVFormat_Create();
OH_AVFormat_SetLongValue(format, key, value);
OH_AVErrCode ret = OH_VideoEncoder_SetParameter(this->avCodec, format);
OH_LOG_INFO(LOG_APP, "动态配置视频编码:%o %s %ld", ret, key, value);
OH_LOG_INFO(LOG_APP, "动态配置视频编码:%o %s %lld", ret, key, value);
OH_AVFormat_Destroy(format);
}
void acgist::VideoEncoder::resetDoubleConfig(const char* key, double value) {
void acgist::TaoyaoVideoEncoder::resetDoubleConfig(const char* key, double value) {
OH_AVFormat* format = OH_AVFormat_Create();
OH_AVFormat_SetDoubleValue(format, key, value);
OH_AVErrCode ret = OH_VideoEncoder_SetParameter(this->avCodec, format);
@@ -119,13 +122,13 @@ void acgist::VideoEncoder::resetDoubleConfig(const char* key, double value) {
OH_AVFormat_Destroy(format);
}
bool acgist::VideoEncoder::start() {
bool acgist::TaoyaoVideoEncoder::start() {
OH_AVErrCode ret = OH_VideoEncoder_Start(this->avCodec);
OH_LOG_INFO(LOG_APP, "开始视频编码:%o", ret);
return ret == OH_AVErrCode::AV_ERR_OK;
}
bool acgist::VideoEncoder::stop() {
bool acgist::TaoyaoVideoEncoder::stop() {
OH_AVErrCode ret = OH_VideoEncoder_NotifyEndOfStream(this->avCodec);
OH_LOG_INFO(LOG_APP, "通知视频编码结束:%o", ret);
ret = OH_VideoEncoder_Stop(this->avCodec);
@@ -133,6 +136,27 @@ bool acgist::VideoEncoder::stop() {
return ret == OH_AVErrCode::AV_ERR_OK;
}
int32_t acgist::TaoyaoVideoEncoder::Release() {
return 0;
}
int32_t acgist::TaoyaoVideoEncoder::RegisterEncodeCompleteCallback(webrtc::EncodedImageCallback* callback) {
return 0;
}
void acgist::TaoyaoVideoEncoder::SetRates(const webrtc::VideoEncoder::RateControlParameters& parameters) {
}
webrtc::VideoEncoder::EncoderInfo acgist::TaoyaoVideoEncoder::GetEncoderInfo() const {
webrtc::VideoEncoder::EncoderInfo info;
// TODO
return info;
}
int32_t acgist::TaoyaoVideoEncoder::Encode(const webrtc::VideoFrame& frame, const std::vector<webrtc::VideoFrameType>* frame_types) {
return 0;
}
static void OnError(OH_AVCodec* codec, int32_t errorCode, void* userData) {
OH_LOG_WARN(LOG_APP, "视频编码发送错误:%d", errorCode);
}

View File

@@ -2,8 +2,50 @@
#include <hilog/log.h>
#include "media/base/codec.h"
#include "api/video_codecs/sdp_video_format.h"
#include "modules/video_coding/codecs/vp8/include/vp8.h"
#include "modules/video_coding/codecs/vp9/include/vp9.h"
#include "modules/video_coding/codecs/h264/include/h264.h"
//#include "api/audio_codecs/builtin_audio_decoder_factory.h"
//#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
static const std::string h264ProfileLevelId = "42e01f";
void acgist::TaoyaoAudioTrackSource::OnData(const void* audio_data, int bits_per_sample, int sample_rate, size_t number_of_channels, size_t number_of_frames) {
// TODO: 数据转发
}
acgist::TaoyaoVideoTrackSource::TaoyaoVideoTrackSource() {
}
acgist::TaoyaoVideoTrackSource::~TaoyaoVideoTrackSource() {
}
webrtc::MediaSourceInterface::SourceState acgist::TaoyaoVideoTrackSource::state() const {
return webrtc::MediaSourceInterface::SourceState::kLive;
}
bool acgist::TaoyaoVideoTrackSource::remote() const {
return false;
}
bool acgist::TaoyaoVideoTrackSource::is_screencast() const {
return false;
}
absl::optional<bool> acgist::TaoyaoVideoTrackSource::needs_denoising() const {
return false;
}
void acgist::TaoyaoVideoTrackSource::OnData(const webrtc::VideoFrame& videoFrame) {
// TODO
}
acgist::TaoyaoVideoEncoderFactory::TaoyaoVideoEncoderFactory() {
}
@@ -30,26 +72,78 @@ std::vector<webrtc::SdpVideoFormat> acgist::TaoyaoVideoEncoderFactory::GetSuppor
}
std::unique_ptr<webrtc::VideoEncoder> acgist::TaoyaoVideoEncoderFactory::CreateVideoEncoder(const webrtc::SdpVideoFormat& format) {
OH_LOG_DEBUG(LOG_APP, "返回WebRTC编码器%o", format.name.data());
OH_LOG_DEBUG(LOG_APP, "返回WebRTC编码器%s", format.name.data());
// 硬编
// TODO: 大小写
if (std::strcmp(format.name.data(), "H264") == 0) {
// return std::unique_ptr<webrtc::VideoEncoder>(new acgist::TaoyaoVideoEncoder());
}
// 软便
if (cricket::CodecNamesEq(format.name, cricket::kVp8CodecName)) {
return VP8Encoder::Create();
// TODO: 大小写
if (std::strcmp(format.name.data(), cricket::kVp8CodecName) == 0) {
return webrtc::VP8Encoder::Create();
}
if (cricket::CodecNamesEq(format.name, cricket::kVp9CodecName)) {
return VP9Encoder::Create(cricket::VideoCodec(format));
if (std::strcmp(format.name.data(), cricket::kVp9CodecName) == 0) {
// return webrtc::VP9Encoder::Create();
return webrtc::VP9Encoder::Create(cricket::CreateVideoCodec(format));
}
if (cricket::CodecNamesEq(format.name, cricket::kH264CodecName)) {
return H264Encoder::Create(cricket::VideoCodec(format));
if (std::strcmp(format.name.data(), cricket::kH264CodecName) == 0) {
// return webrtc::H264Encoder::Create();
return webrtc::H264Encoder::Create(cricket::CreateVideoCodec(format));
}
return nullptr;
}
acgist::TaoyaoVideoDecoderFactory::TaoyaoVideoDecoderFactory() {
}
acgist::TaoyaoVideoDecoderFactory::~TaoyaoVideoDecoderFactory() {
}
std::vector<webrtc::SdpVideoFormat> acgist::TaoyaoVideoDecoderFactory::GetSupportedFormats() const {
std::vector<webrtc::SdpVideoFormat> supported_codecs;
std::map<std::string, std::string> params;
params["profile-level-id"] = h264ProfileLevelId;
params["packetization-mode"] = "1";
params["level-asymmetry-allowed"] = "1";
webrtc::SdpVideoFormat dst_format("H264", params);
for (const webrtc::SdpVideoFormat& format : webrtc::SupportedH264Codecs()) {
if (format == dst_format) {
supported_codecs.push_back(format);
break;
}
}
supported_codecs.push_back(webrtc::SdpVideoFormat(cricket::kVp8CodecName));
supported_codecs.push_back(webrtc::SdpVideoFormat(cricket::kVp9CodecName));
supported_codecs.push_back(webrtc::SdpVideoFormat(cricket::kH264CodecName));
return supported_codecs;
}
std::unique_ptr<webrtc::VideoDecoder> acgist::TaoyaoVideoDecoderFactory::CreateVideoDecoder(const webrtc::SdpVideoFormat& format) {
OH_LOG_DEBUG(LOG_APP, "返回WebRTC解码器%s", format.name.data());
// 硬解
// TODO: 大小写
if (absl::EqualsIgnoreCase(format.name.data(), "H264") == 0) {
// return std::unique_ptr<webrtc::VideoDecoder>(new acgist::TaoyaoVideoDecoder());
}
// 软解
// TODO: 大小写
if (absl::EqualsIgnoreCase(format.name.data(), cricket::kVp8CodecName) == 0) {
return webrtc::VP8Decoder::Create();
}
if (absl::EqualsIgnoreCase(format.name.data(), cricket::kVp9CodecName) == 0) {
return webrtc::VP9Decoder::Create();
}
if (absl::EqualsIgnoreCase(format.name.data(), cricket::kH264CodecName) == 0) {
return webrtc::H264Decoder::Create();
}
return nullptr;
}
std::unique_ptr<webrtc::VideoEncoderFactory> webrtc::CreateBuiltinVideoEncoderFactory() {
return std::unique_ptr(new acgist::TaoyaoVideoEncoderFactory());
return std::unique_ptr<webrtc::VideoEncoderFactory>(new acgist::TaoyaoVideoEncoderFactory());
}
std::unique_ptr<webrtc::VideoDecoderFactory> webrtc::CreateBuiltinVideoDecoderFactory() {
return std::unique_ptr(new acgist::TaoyaoVideoDecoderFactory());
return std::unique_ptr<webrtc::VideoDecoderFactory>(new acgist::TaoyaoVideoDecoderFactory());
}

View File

@@ -1,20 +1,13 @@
import { signal } from "../taoyao/TaoyaoSignal";
import { setting } from "../taoyao/Setting";
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Button("加载系统")
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
signal.init();
});
}
.width("50%");
Column() {
Button("连接信令")
.fontSize(20)
@@ -23,7 +16,8 @@ struct Index {
signal.connect();
});
}
.width("50%");
.width("100%")
.height("20%");
Column() {
Button("断开信令")
.fontSize(20)
@@ -32,7 +26,18 @@ struct Index {
signal.close();
});
}
.width("50%");
.width("100%")
.height("20%");
Column() {
Button("加载系统")
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
signal.init();
});
}
.width("100%")
.height("20%");
Column() {
Button("卸载系统")
.fontSize(20)
@@ -41,7 +46,19 @@ struct Index {
signal.shutdown();
});
}
.width("50%");
.width("100%")
.height("20%");
Column() {
Text(
`
CA证书${getContext(this).resourceDir + setting.caPath}
信令地址:${setting.signalAddress}
`
)
.fontSize(20)
}
.width("100%")
.height("20%");
}
.height("100%");
}

View File

@@ -22,11 +22,14 @@ class Signal {
class Setting {
// 信令地址
signalAddress: string = "wss://192.168.1.100:8888/websocket.signal";
signalAddress: string = "wss://192.168.8.57:8888/websocket.signal";
// signalAddress: string = "wss://192.168.1.100:8888/websocket.signal";
// 信令版本
version: string = "1.0.0";
// 信令配置
signal : Signal = new Signal();
// CA证书
caPath : string = getContext(this) + "/cacert.pem";
};

View File

@@ -5,17 +5,13 @@
*/
import hilog from "@ohos.hilog";
import { BusinessError } from '@ohos.base';
import webSocket from "@ohos.net.webSocket";
import { setting } from "./Setting";
import taoyaoModule from "libtaoyao.so";
interface BusinessError<T = void> extends Error {
code : number;
data?: T;
}
class TaoyaoSignal {
// WebSocket信令连接
@@ -40,7 +36,7 @@ class TaoyaoSignal {
}
shutdown() {
taoyaoModule.shutdown("{}");
taoyaoModule.shutdown(JSON.stringify(setting.signal));
}
/**
@@ -48,36 +44,40 @@ class TaoyaoSignal {
*/
connect() {
if(this.socket) {
hilog.info(0x0000, "TaoyaoSignal", "信令以及连接关闭旧的连接:%s", setting.signalAddress);
hilog.info(0x0000, "TaoyaoSignal", "信令已经连接关闭旧的连接:%{public}s", setting.signalAddress);
this.socket.close();
}
this.connected = false;
this.socket = webSocket.createWebSocket();
this.socket.on("open", (err: BusinessError, value: Object) => {
hilog.info(0x0000, "TaoyaoSignal", "打开信令:%s", setting.signalAddress, value, err);
hilog.info(0x0000, "TaoyaoSignal", "打开信令:%{public}s %{public}s %{public}s", setting.signalAddress, JSON.stringify(value), JSON.stringify(err));
this.register();
this.connected = true;
});
this.socket.on("message", (err: BusinessError, value: string | ArrayBuffer) => {
const message = value?.toString();
hilog.debug(0x0000, "TaoyaoSignal", "信令消息:%s", message, err);
hilog.debug(0x0000, "TaoyaoSignal", "信令消息:%{public}s %{public}s", message, JSON.stringify(err));
try {
this.onMessage(message);
} catch (error) {
hilog.error(0x0000, "TaoyaoSignal", "处理信令消息异常:%s", message, error);
hilog.error(0x0000, "TaoyaoSignal", "处理信令消息异常:%{public}s %{public}s", message, JSON.stringify(error));
}
});
this.socket.on("close", (err: BusinessError, value: Object) => {
hilog.error(0x0000, "TaoyaoSignal", "关闭信令:%s", setting.signalAddress, value, err);
hilog.error(0x0000, "TaoyaoSignal", "关闭信令:%{public}s %{public}s %{public}s", setting.signalAddress, JSON.stringify(value), JSON.stringify(err));
this.reconnect();
});
this.socket.on("error", (err: BusinessError) => {
hilog.error(0x0000, "TaoyaoSignal", "信令异常:%s", setting.signalAddress, err);
hilog.error(0x0000, "TaoyaoSignal", "信令异常:%{public}s %{public}s", setting.signalAddress, JSON.stringify(err));
this.reconnect();
});
hilog.info(0x0000, "TaoyaoSignal", "连接信令:%s", setting.signalAddress);
this.socket.connect(setting.signalAddress, (err: BusinessError, value: boolean) => {
hilog.info(0x0000, "TaoyaoSignal", "信令连接成功:%s", setting.signalAddress, value, err);
// 配置CA证书
const options: webSocket.WebSocketRequestOptions = {
caPath: setting.caPath
};
hilog.info(0x0000, "TaoyaoSignal", "连接信令:%{public}s %{public}s", setting.signalAddress, setting.caPath);
this.socket.connect(setting.signalAddress, options, (err: BusinessError, value: boolean) => {
hilog.info(0x0000, "TaoyaoSignal", "信令连接:%{public}s %{public}s %{public}s", setting.signalAddress, JSON.stringify(value), JSON.stringify(err));
});
};
@@ -88,7 +88,7 @@ class TaoyaoSignal {
if(this.closed) {
// 已经关闭忽略重连
} else {
hilog.info(0x0000, "TaoyaoSignal", "重连信令连接:%s", setting.signalAddress);
hilog.info(0x0000, "TaoyaoSignal", "重连信令连接:%{public}s", setting.signalAddress);
setTimeout((): void => this.connect(), 5000);
}
}
@@ -97,7 +97,7 @@ class TaoyaoSignal {
* 关闭信令
*/
close() {
hilog.info(0x0000, "TaoyaoSignal", "关闭信令:%s", setting.signalAddress);
hilog.info(0x0000, "TaoyaoSignal", "关闭信令:%{public}s", setting.signalAddress);
this.closed = true;
this.connected = false;
if(this.socket) {
@@ -121,7 +121,7 @@ class TaoyaoSignal {
const body = response.body as Record<string, Object>;
const index = body.index as number;
this.clientIndex = index;
hilog.info(0x0000, "TaoyaoSignal", "信令注册成功:%d", index);
hilog.info(0x0000, "TaoyaoSignal", "信令注册成功:%{public}d", index);
this.heartbeat();
}
@@ -176,11 +176,12 @@ class TaoyaoSignal {
"header": header,
"body" : body
};
const json = JSON.stringify(message);
try {
hilog.debug(0x0000, "TaoyaoSignal", "发送消息:%o", message);
this.socket?.send(JSON.stringify(message));
hilog.debug(0x0000, "TaoyaoSignal", "发送消息:%{public}s", json);
this.socket?.send(json);
} catch (error) {
hilog.error(0x0000, "TaoyaoSignal", "发送消息异常:%o", message, error);
hilog.error(0x0000, "TaoyaoSignal", "发送消息异常:%{public}s %{public}s", json, JSON.stringify(error));
}
}
@@ -218,11 +219,12 @@ class TaoyaoSignal {
return true;
});
// 发送消息
const json = JSON.stringify(message);
try {
hilog.debug(0x0000, "TaoyaoSignal", "发送请求:%o", message);
this.socket?.send(JSON.stringify(message));
hilog.debug(0x0000, "TaoyaoSignal", "发送请求:%{public}s", json);
this.socket?.send(json);
} catch (error) {
hilog.error(0x0000, "TaoyaoSignal", "发送消息异常:%o", message, error);
hilog.error(0x0000, "TaoyaoSignal", "发送消息异常:%{public}s %{public}s", json, JSON.stringify(error));
reject(error);
}
});
@@ -239,7 +241,7 @@ class TaoyaoSignal {
const id : number = header.id as number;
const signal: string = header.signal as string;
if (this.callbackMapping.has(id)) {
hilog.debug(0x0000, "TaoyaoSignal", "处理同步消息:%s", message);
hilog.debug(0x0000, "TaoyaoSignal", "处理同步消息:%{public}s", message);
try {
const callback = this.callbackMapping.get(id) as Function;
if(callback(json)) {
@@ -292,10 +294,10 @@ class TaoyaoSignal {
break;
default:
ret = -1;
hilog.warn(0x0000, "TaoyaoSignal", "没有适配信令:%s", signal);
hilog.warn(0x0000, "TaoyaoSignal", "没有适配信令:%{public}s", signal);
break;
}
hilog.debug(0x0000, "TaoyaoSignal", "处理异步消息:%d %s", ret, message);
hilog.debug(0x0000, "TaoyaoSignal", "处理异步消息:%{public}d %{public}s", ret, message);
}
/**

View File

@@ -64,12 +64,6 @@
"when": "always"
}
},
{
"name": "ohos.permission.MEDIA_LOCATION",
"usedScene": {
"when": "always"
}
}
]
}
}

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDjzCCAnegAwIBAgIJAKPjuujtbFnoMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV
BAYTAmNuMQswCQYDVQQIDAJnZDELMAkGA1UEBwwCZ3oxDzANBgNVBAoMBmFjZ2lz
dDEPMA0GA1UECwwGYWNnaXN0MRMwEQYDVQQDDAphY2dpc3QuY29tMB4XDTIzMDIy
NzEzMzUyNloXDTMzMDIyNDEzMzUyNlowXjELMAkGA1UEBhMCY24xCzAJBgNVBAgM
AmdkMQswCQYDVQQHDAJnejEPMA0GA1UECgwGYWNnaXN0MQ8wDQYDVQQLDAZhY2dp
c3QxEzARBgNVBAMMCmFjZ2lzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDbt9orZnoTtzbaI9+S8uqqvi8rqOzi+b3tRHOYE+JVQNxWf8vTvKJ5
mDDrBqICVy2SCtwkxXgrjDcRQVKK1IiDqxQ4oY6DCZetx4gQhYk9ychYsPPKnRg8
bQEG48DM1EhmxhozUv7kaiUMS0LNODfzLTH/C25Nhgt3laGCtcIWOQliO9AVOxam
EasfYP01AfL2qahk1s5N7fK9poLpbR9BS8ZUYMxZ5xOIUcc5eithBgGvuHUv9nEY
Dart6XPC4z3YE9liwrxYwcBxztdvCA2EWeh1k0wNcrT/eJG3cuGgzsPDjI/BORq1
DWFKJOXrWmhmIlw+VaQ6PIiD4/aQ50xfAgMBAAGjUDBOMB0GA1UdDgQWBBR98tbO
eDI9mBcuZ96keDld1w54OzAfBgNVHSMEGDAWgBR98tbOeDI9mBcuZ96keDld1w54
OzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCavKK+pJCmWDqFNMoX
YwdRPDJS7LoCFV7C8oTkX5myCKNOi11bzlyqP/EkelubtRgaNr+GZCyhwxPiJvRx
ZrWsoWpOH8OEdADM9lU+UXR23Ufmo9jFFEL7jZ9u9OmOJWAM5xM1KqCBd5+KRvfE
oEHXdayfy6l00F+rsgaMm6IKdZcthAxVVEKO60GfwavcuvIiHVVLxW21H8BMoqd6
Erigq7wJRRH+qm7Q5fVpmo1L7E6T2cBvGcFHKuQFdoxDlH4N6tPRDuRSODKFE//O
D1ViQY65nn35mawbz2AgUUPvWiBDYYomeKIiGl859PukeP1jwDZcECFtrhH4s1p+
at2z
-----END CERTIFICATE-----