从WebRTC提供的示例工程peerconnection_client入手学习Webrtc

转自:https://www.cnblogs.com/fangkm/p/4370492.html

转载请注明出处:http://www.cnblogs.com/fangkm/p/4370492.html

上一篇文章简单地介绍了下WebRTC的协议流程,这一篇就开始介绍框架与接口。

一提到框架,本能地不知道从什么地方入手了。曾经直接从Chromium项目对WebRTC的源码的集成方面入手,后来发现这个步子迈的太大了,看的越多,概念越混乱,看了半个月感觉也没啥沉淀。还是从WebRTC提供的示例工程peerconnection_client入手比较轻便。先抛开音视频流的构建和渲染流程,示例工程核心的代码结构如下:

从面向对象的视角来看,WebRTC的设计还是非常棒的,真正地做到了接口编程的概念,对WebRTC功能的使用都通过接口来进行,这样最大程度上保证了WebRTC模块的可定制性,这样就可以让WebRTC更多地回归到描述协议的本质。如果WebRTC对这些接口的实现不能满足你的业务需求,理论上你可以提供自己的实现逻辑。本图中的PeerConnectionFactoryInterface和PeerConnectionInterface没有这种定制的代表性,因为重新提供它们的实现逻辑的需求场景基本上不存在(即便不用重写,但也支持参数的定制,具体请参见CreatePeerConnectionFactory的重载方法)。但是音视频相关的接口定制的场景就很普遍了,比如Chromium浏览器集成WebRTC,但是音视频采集需要走Chromium自己的音视频模块,所以Chromium对WebRTC音视频的采集接口重新做了实现适配,以后有机会肯定非常乐意分享下Chromium源码对WebRTC的集成,不过那也是在对WebRTC熟悉完之后的工作了。

图中Conductor是该示例工程提供的核心业务类,整个WebRTC的使用都浓缩在这个类中。Conductor通过CreatePeerConnectionFactory方法创建了一个PeerConnectionFactoryInterface接口的实现对象,通过这个接口,可以创建关键的PeerConnectionInterface接口,PeerConnectionInterface接口是WebRTC的协议核心。此外,PeerConnectionFactoryInterface接口还提供了创建本地音视频流的功能接口,这个部分稍后再述。根据图中PeerConnectionInterface接口的成员方法可以看出,WebRTC通信流程的交互接口基本上都在这里面了,给Conductor的回调通知是通过PeerConnectionObserver接口来完成。具体的交互流程请参见上一篇博文。

接下来分析本地音视频的相关接口,由于音视频内容较多,这里先介绍下接口概念,不谈具体实现(下一节专门讲解WebRTC原生的音视频采集),还是以peerconnection_client工程为例:

这里涉及到非常多的音视频相关接口,基本上都是概念性的,最怕遇到新的设计概念,主要是怕自己理解有误差,下面谈一下我对这些接口概念的理解:

MediaStream概念: 表示媒体流,由MediaStreamInterface接口抽象,每个媒体流都有一个唯一的标识(通过label成员方法返回),它由一系列的音频Track(由AudioTrackInterface接口抽象)和视频Track组成(由VideoTrackInterface接口抽象)。

Track概念:具体指上图结构中AudioTrackInterface和VideoTrackInterface接口,Track表示的是在媒体流中轨的概念,AudioTrackInterface标识的是音频轨,VideoTrackInterface标识的是视频轨,一个MediaStreamInterface标识的媒体流中允许携带多个媒体轨数据,它们之间是独立的,在编码渲染的流程中各自处理。如果概念还很模糊,轨的概念就相当于音频数据中的声道概念(左声道、右声道)、视频数据中的YUV场的概念。Track既然封装了媒体轨数据,那就必然有个媒体源做为数据的提供者,如音频Track由AudioSourceInterface接口作为数据源提供者,视频Track由VideoSourceInterface接口作为数据的提供者。有输入接口就必然有输出接口,这部分在该图中只展示了视频数据的输出接口VideoRendererInterface,这里的Render命名的意思并不是仅限于将视频和音频数据渲染出来,应该理解成输出接口。

VideoSourceInterface:抽象视频源接口供VideoTracks使用,同一个源可以被多个VideoTracks共用。视频源接纳了一个VideoCapturer接口,抽象视频采集的逻辑,我们可以提供自己的VideoCapturer实现做为视频数据的采集源。VideoCapturer是个关键的定制接口,比如Chromium源码就是自己实现了VideoCapturer接口而没用原生的WebRTC采集实现,但Chromium的音视频采集都在browser进程,因此它对VideoCapturer接口的实现要比想象的复杂,它需要从主进程接收到视频帧数据然后触发VideoCapturer的SignalFrameCaptured信号。

AudioSourceInterface:概念上同VideoSourceInterface类似,抽象音频源接口供AudioTracks使用,但是从源码中理解,这是个伪概念,因为没有提供一个类似于VideoCapturer的AudioCapturer接口,这里没有音频的采集逻辑,实际上WebRTC的音频采集接口使用的是AudioDeviceModule,在创建PeerConnectionFactory的时候可以由外界定制,如果没有,则内部创建AudioDeviceModuleImpl来实现此接口完成音频设备的采集工作。可能是功力不够,反正我是不太理解音频采集和视频采集这种设计的不对称性。如果也封装一个AudioCapturer接口的概念,这样可定制性是不是可以更高。

构建媒体流的过程基本上就是构建Video Track和Audio Track,并将其添加到Media Stream里。在peerconnection_client工程中,Conductor依赖DeviceManagerInterface接口的CreateVideoCapturer方法创建一个当前可用的视频设备采集对象VideoCapturer,将它作为视频采集源中的数据来源(通过挂接VideoCapturer的SignalVideoFrame信号来接收视频数据),此外MainWnd还创建了一个内部类VideoRenderer从VideoRendererInterface接口派生,并将其添加到Video Track中, VideoRenderer的实现就是将接收到的视频帧数据渲染到窗口上。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这里是一个ESP32 WebRTC推流的Demo: 1. 首先,你需要先在ESP32上安装WebRTC的库。可以使用ESP-IDF的组件来安装,具体方法可以参考官方文档。 2. 接下来,你需要准备一个WebRTC服务器,可以使用开源的Janus Gateway或者Kurento Media Server等。 3. 在ESP32上编写代码,连接到WebRTC服务器并进行推流。下面是一个简单的示例: ```c #include "esp_camera.h" #include "esp_log.h" #include "esp_http_client.h" #include "esp_websocket_client.h" #include "esp_timer.h" #include "esp_wifi.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "nvs_flash.h" #include "sdkconfig.h" #define CAMERA_MODEL_AI_THINKER #include "camera_pins.h" #define EXAMPLE_ESP_WIFI_SSID "your_wifi_ssid" #define EXAMPLE_ESP_WIFI_PASS "your_wifi_password" #define EXAMPLE_ESP_MAXIMUM_RETRY 10 static const char *TAG = "webrtc_demo"; static esp_http_client_handle_t client; static esp_websocket_client_handle_t websocket; static EventGroupHandle_t wifi_event_group; const int CONNECTED_BIT = BIT0; static int wifi_retry_num = 0; static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { if (wifi_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) { esp_wifi_connect(); wifi_retry_num++; ESP_LOGI(TAG, "retry to connect to the AP"); } else { xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); } ESP_LOGI(TAG,"connect to the AP fail"); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; ESP_LOGI(TAG, "got ip:%s", ip4addr_ntoa(&event->ip_info.ip)); wifi_retry_num = 0; xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); } } static void wifi_init_sta(void) { wifi_event_group = xEventGroupCreate(); tcpip_adapter_init(); ESP_ERROR_CHECK(esp_event_loop_create_default()); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL)); wifi_config_t wifi_config = { .sta = { .ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS, }, }; ESP_LOGI(TAG, "Connecting to %s", wifi_config.sta.ssid); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); ESP_LOGI(TAG, "Connected to AP"); } static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { switch (event_id) { case WEBSOCKET_EVENT_CONNECTED: ESP_LOGI(TAG, "websocket connected"); break; case WEBSOCKET_EVENT_DISCONNECTED: ESP_LOGI(TAG, "websocket disconnected"); break; case WEBSOCKET_EVENT_DATA: ESP_LOGI(TAG, "websocket received data"); break; case WEBSOCKET_EVENT_ERROR: ESP_LOGI(TAG, "websocket error"); break; default: break; } } static void websocket_init(void) { esp_websocket_client_config_t websocket_cfg = { .uri = "wss://your_webrtc_server/ws", .disable_auto_reconnect = true, .keepalive_interval_ms = 5000, .event_handle = websocket_event_handler, }; ESP_ERROR_CHECK(esp_websocket_client_create(&websocket_cfg, &websocket)); ESP_ERROR_CHECK(esp_websocket_client_start(websocket)); } static void http_event_handler(esp_http_client_event_t *evt) { switch (evt->event_id) { case HTTP_EVENT_ON_DATA: ESP_LOGI(TAG, "http received data"); break; case HTTP_EVENT_ON_FINISH: ESP_LOGI(TAG, "http request finished"); break; case HTTP_EVENT_ERROR: ESP_LOGI(TAG, "http error"); break; default: break; } } static void http_init(void) { esp_http_client_config_t http_cfg = { .url = "https://your_webrtc_server/start", .event_handler = http_event_handler, .method = HTTP_METHOD_POST, }; client = esp_http_client_init(&http_cfg); ESP_ERROR_CHECK(esp_http_client_perform(client)); } void app_main() { nvs_flash_init(); ESP_ERROR_CHECK(esp_camera_init(&config)); wifi_init_sta(); websocket_init(); http_init(); while (true) { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { ESP_LOGE(TAG, "failed to get camera frame buffer"); continue; } esp_err_t err = esp_websocket_client_send_binary(websocket, fb->buf, fb->len, portMAX_DELAY); if (err != ESP_OK) { ESP_LOGE(TAG, "failed to send data over websocket: %d", err); } esp_camera_fb_return(fb); } } ``` 这个Demo需要用到ESP32的摄像头模块,推流的视频数据将从摄像头读取,并通过WebRTC协议发送到服务器。 你需要将代码中的`your_wifi_ssid`和`your_wifi_password`替换为你的WiFi名称和密码,将`your_webrtc_server`替换为你的WebRTC服务器地址。 请注意,这只是一个示例,实际情况下你需要根据你的需求进行修改和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值