1/*
2 * Copyright (C) 2018 Metrological Group B.V.
3 * Copyright (C) 2018 Igalia S.L. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * aint with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22
23#if ENABLE(VIDEO) && ENABLE(MEDIA_STREAM) && USE(LIBWEBRTC) && USE(GSTREAMER)
24#include "GStreamerVideoEncoderFactory.h"
25
26#include "GStreamerVideoEncoder.h"
27#include "GStreamerVideoFrameLibWebRTC.h"
28#include "webrtc/common_video/h264/h264_common.h"
29#include "webrtc/common_video/h264/profile_level_id.h"
30#include "webrtc/media/base/codec.h"
31#include "webrtc/modules/video_coding/codecs/h264/include/h264.h"
32#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
33#include "webrtc/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.h"
34#include "webrtc/modules/video_coding/include/video_codec_interface.h"
35#include "webrtc/modules/video_coding/utility/simulcast_utility.h"
36
37#include <gst/app/gstappsink.h>
38#include <gst/app/gstappsrc.h>
39#define GST_USE_UNSTABLE_API 1
40#include <gst/codecparsers/gsth264parser.h>
41#undef GST_USE_UNSTABLE_API
42#include <gst/pbutils/encoding-profile.h>
43#include <gst/video/video.h>
44#include <wtf/HashMap.h>
45#include <wtf/HexNumber.h>
46#include <wtf/Lock.h>
47#include <wtf/StdMap.h>
48
49// Required for unified builds
50#ifdef GST_CAT_DEFAULT
51#undef GST_CAT_DEFAULT
52#endif
53
54GST_DEBUG_CATEGORY(webkit_webrtcenc_debug);
55#define GST_CAT_DEFAULT webkit_webrtcenc_debug
56
57#define KBIT_TO_BIT 1024
58
59namespace WebCore {
60
61class GStreamerVideoEncoder : public webrtc::VideoEncoder {
62public:
63 GStreamerVideoEncoder(const webrtc::SdpVideoFormat&)
64 : m_firstFramePts(GST_CLOCK_TIME_NONE)
65 , m_restrictionCaps(adoptGRef(gst_caps_new_empty_simple("video/x-raw")))
66 , m_adapter(adoptGRef(gst_adapter_new()))
67 {
68 }
69 GStreamerVideoEncoder()
70 : m_firstFramePts(GST_CLOCK_TIME_NONE)
71 , m_restrictionCaps(adoptGRef(gst_caps_new_empty_simple("video/x-raw")))
72 , m_adapter(adoptGRef(gst_adapter_new()))
73 {
74 }
75
76 int SetRates(uint32_t newBitrate, uint32_t frameRate) override
77 {
78 GST_INFO_OBJECT(m_pipeline.get(), "New bitrate: %d - framerate is %d",
79 newBitrate, frameRate);
80
81 auto caps = adoptGRef(gst_caps_copy(m_restrictionCaps.get()));
82
83 SetRestrictionCaps(WTFMove(caps));
84
85 if (m_encoder)
86 g_object_set(m_encoder, "bitrate", newBitrate, nullptr);
87
88 return WEBRTC_VIDEO_CODEC_OK;
89 }
90
91 GstElement* pipeline()
92 {
93 return m_pipeline.get();
94 }
95
96 GstElement* makeElement(const gchar* factoryName)
97 {
98 auto name = makeString(Name(), "_enc_", factoryName, "_0x", hex(reinterpret_cast<uintptr_t>(this)));
99 auto elem = gst_element_factory_make(factoryName, name.utf8().data());
100
101 return elem;
102 }
103
104 int32_t InitEncode(const webrtc::VideoCodec* codecSettings, int32_t, size_t)
105 {
106 g_return_val_if_fail(codecSettings, WEBRTC_VIDEO_CODEC_ERR_PARAMETER);
107 g_return_val_if_fail(codecSettings->codecType == CodecType(), WEBRTC_VIDEO_CODEC_ERR_PARAMETER);
108
109 if (webrtc::SimulcastUtility::NumberOfSimulcastStreams(*codecSettings) > 1) {
110 GST_ERROR("Simulcast not supported.");
111
112 return WEBRTC_VIDEO_CODEC_ERR_SIMULCAST_PARAMETERS_NOT_SUPPORTED;
113 }
114
115 m_encodedFrame._size = codecSettings->width * codecSettings->height * 3;
116 m_encodedFrame._buffer = new uint8_t[m_encodedFrame._size];
117 m_encodedImageBuffer.reset(m_encodedFrame._buffer);
118 m_encodedFrame._completeFrame = true;
119 m_encodedFrame._encodedWidth = 0;
120 m_encodedFrame._encodedHeight = 0;
121 m_encodedFrame._length = 0;
122
123 m_pipeline = makeElement("pipeline");
124
125 connectSimpleBusMessageCallback(m_pipeline.get());
126 auto encoder = createEncoder();
127 ASSERT(encoder);
128 m_encoder = encoder.get();
129
130 g_object_set(m_encoder, "keyframe-interval", KeyframeInterval(codecSettings), nullptr);
131
132 m_src = makeElement("appsrc");
133 g_object_set(m_src, "is-live", true, "format", GST_FORMAT_TIME, nullptr);
134
135 auto videoconvert = makeElement("videoconvert");
136 m_sink = makeElement("appsink");
137 g_object_set(m_sink, "sync", FALSE, nullptr);
138
139 auto name = makeString(Name(), "_enc_rawcapsfilter_0x", hex(reinterpret_cast<uintptr_t>(this)));
140 m_capsFilter = gst_element_factory_make("capsfilter", name.utf8().data());
141 if (m_restrictionCaps)
142 g_object_set(m_capsFilter, "caps", m_restrictionCaps.get(), nullptr);
143
144 gst_bin_add_many(GST_BIN(m_pipeline.get()), m_src, videoconvert, m_capsFilter, encoder.leakRef(), m_sink, nullptr);
145 if (!gst_element_link_many(m_src, videoconvert, m_capsFilter, m_encoder, m_sink, nullptr)) {
146 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_pipeline.get()), GST_DEBUG_GRAPH_SHOW_VERBOSE, "webkit-webrtc-encoder.error");
147
148 ASSERT_NOT_REACHED();
149 }
150
151 gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING);
152
153 return WEBRTC_VIDEO_CODEC_OK;
154 }
155
156 bool SupportsNativeHandle() const final
157 {
158 return true;
159 }
160
161 int32_t RegisterEncodeCompleteCallback(webrtc::EncodedImageCallback* callback) final
162 {
163 m_imageReadyCb = callback;
164
165 return WEBRTC_VIDEO_CODEC_OK;
166 }
167
168 int32_t Release() final
169 {
170 m_encodedFrame._buffer = nullptr;
171 m_encodedImageBuffer.reset();
172 if (m_pipeline) {
173 GRefPtr<GstBus> bus = adoptGRef(gst_pipeline_get_bus(GST_PIPELINE(m_pipeline.get())));
174 gst_bus_set_sync_handler(bus.get(), nullptr, nullptr, nullptr);
175
176 gst_element_set_state(m_pipeline.get(), GST_STATE_NULL);
177 m_src = nullptr;
178 m_encoder = nullptr;
179 m_capsFilter = nullptr;
180 m_sink = nullptr;
181 m_pipeline = nullptr;
182 }
183
184 return WEBRTC_VIDEO_CODEC_OK;
185 }
186
187 int32_t returnFromFlowReturn(GstFlowReturn flow)
188 {
189 switch (flow) {
190 case GST_FLOW_OK:
191 return WEBRTC_VIDEO_CODEC_OK;
192 case GST_FLOW_FLUSHING:
193 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
194 default:
195 return WEBRTC_VIDEO_CODEC_ERROR;
196 }
197 }
198
199
200 int32_t Encode(const webrtc::VideoFrame& frame,
201 const webrtc::CodecSpecificInfo*,
202 const std::vector<webrtc::FrameType>* frameTypes) final
203 {
204 int32_t res;
205
206 if (!m_imageReadyCb) {
207 GST_INFO_OBJECT(m_pipeline.get(), "No encoded callback set yet!");
208
209 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
210 }
211
212 if (!m_src) {
213 GST_INFO_OBJECT(m_pipeline.get(), "No source set yet!");
214
215 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
216 }
217
218 auto sample = GStreamerSampleFromLibWebRTCVideoFrame(frame);
219 auto buffer = gst_sample_get_buffer(sample.get());
220
221 if (!GST_CLOCK_TIME_IS_VALID(m_firstFramePts)) {
222 m_firstFramePts = GST_BUFFER_PTS(buffer);
223 auto pad = adoptGRef(gst_element_get_static_pad(m_src, "src"));
224 gst_pad_set_offset(pad.get(), -m_firstFramePts);
225 }
226
227 for (auto frame_type : *frameTypes) {
228 if (frame_type == webrtc::kVideoFrameKey) {
229 auto pad = adoptGRef(gst_element_get_static_pad(m_src, "src"));
230 auto forceKeyUnit = gst_video_event_new_downstream_force_key_unit(GST_CLOCK_TIME_NONE,
231 GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, FALSE, 1);
232 GST_INFO_OBJECT(m_pipeline.get(), "Requesting KEYFRAME!");
233
234 if (!gst_pad_push_event(pad.get(), forceKeyUnit))
235 GST_WARNING_OBJECT(pipeline(), "Could not send ForceKeyUnit event");
236
237 break;
238 }
239 }
240
241 res = returnFromFlowReturn(gst_app_src_push_sample(GST_APP_SRC(m_src), sample.get()));
242 if (res != WEBRTC_VIDEO_CODEC_OK)
243 return res;
244
245 auto encodedSample = adoptGRef(gst_app_sink_try_pull_sample(GST_APP_SINK(m_sink), 5 * GST_SECOND));
246 if (!encodedSample) {
247 GST_ERROR("Didn't get any encodedSample");
248 return WEBRTC_VIDEO_CODEC_ERROR;
249 }
250
251 auto encodedBuffer = gst_sample_get_buffer(encodedSample.get());
252 auto encodedCaps = gst_sample_get_caps(encodedSample.get());
253
254 webrtc::RTPFragmentationHeader fragmentationInfo;
255
256 Fragmentize(&m_encodedFrame, &m_encodedImageBuffer, &m_encodedImageBufferSize, encodedBuffer, &fragmentationInfo);
257 if (!m_encodedFrame._size)
258 return WEBRTC_VIDEO_CODEC_OK;
259
260 gst_structure_get(gst_caps_get_structure(encodedCaps, 0),
261 "width", G_TYPE_INT, &m_encodedFrame._encodedWidth,
262 "height", G_TYPE_INT, &m_encodedFrame._encodedHeight,
263 nullptr);
264
265 m_encodedFrame._frameType = GST_BUFFER_FLAG_IS_SET(encodedBuffer, GST_BUFFER_FLAG_DELTA_UNIT) ? webrtc::kVideoFrameDelta : webrtc::kVideoFrameKey;
266 m_encodedFrame._completeFrame = true;
267 m_encodedFrame.capture_time_ms_ = frame.render_time_ms();
268 m_encodedFrame.SetTimestamp(frame.timestamp());
269
270 GST_LOG_OBJECT(m_pipeline.get(), "Got buffer capture_time_ms: %ld _timestamp: %u",
271 m_encodedFrame.capture_time_ms_, m_encodedFrame.Timestamp());
272
273 webrtc::CodecSpecificInfo codecInfo;
274 PopulateCodecSpecific(&codecInfo, encodedBuffer);
275 webrtc::EncodedImageCallback::Result result = m_imageReadyCb->OnEncodedImage(m_encodedFrame, &codecInfo, &fragmentationInfo);
276 if (result.error != webrtc::EncodedImageCallback::Result::OK)
277 GST_ERROR_OBJECT(m_pipeline.get(), "Encode callback failed: %d", result.error);
278
279 return WEBRTC_VIDEO_CODEC_OK;
280 }
281
282 GRefPtr<GstElement> createEncoder(void)
283 {
284 GRefPtr<GstElement> encoder = nullptr;
285 GstElement* webrtcencoder = GST_ELEMENT(g_object_ref_sink(gst_element_factory_make("webrtcvideoencoder", NULL)));
286
287 g_object_set(webrtcencoder, "format", adoptGRef(gst_caps_from_string(Caps())).get(), NULL);
288 g_object_get(webrtcencoder, "encoder", &encoder.outPtr(), NULL);
289
290 if (!encoder) {
291 GST_INFO("No encoder found for %s", Caps());
292
293 return nullptr;
294 }
295
296 return webrtcencoder;
297 }
298
299 void AddCodecIfSupported(std::vector<webrtc::SdpVideoFormat>* supportedFormats)
300 {
301 GstElement* encoder;
302
303 if (createEncoder().get() != nullptr) {
304 webrtc::SdpVideoFormat format = ConfigureSupportedCodec(encoder);
305
306 supportedFormats->push_back(format);
307 }
308 }
309
310 virtual const gchar* Caps()
311 {
312 return nullptr;
313 }
314
315 virtual webrtc::VideoCodecType CodecType() = 0;
316 virtual webrtc::SdpVideoFormat ConfigureSupportedCodec(GstElement*)
317 {
318 return webrtc::SdpVideoFormat(Name());
319 }
320
321 virtual void PopulateCodecSpecific(webrtc::CodecSpecificInfo*, GstBuffer*) = 0;
322
323 virtual void Fragmentize(webrtc::EncodedImage* encodedImage, std::unique_ptr<uint8_t[]>* encodedImageBuffer,
324 size_t* bufferSize, GstBuffer* buffer, webrtc::RTPFragmentationHeader* fragmentationInfo)
325 {
326 auto map = GstMappedBuffer::create(buffer, GST_MAP_READ);
327
328 if (*bufferSize < map->size()) {
329 encodedImage->_size = map->size();
330 encodedImage->_buffer = new uint8_t[encodedImage->_size];
331 encodedImageBuffer->reset(encodedImage->_buffer);
332 *bufferSize = map->size();
333 }
334
335 memcpy(encodedImage->_buffer, map->data(), map->size());
336 encodedImage->_length = map->size();
337 encodedImage->_size = map->size();
338
339 fragmentationInfo->VerifyAndAllocateFragmentationHeader(1);
340 fragmentationInfo->fragmentationOffset[0] = 0;
341 fragmentationInfo->fragmentationLength[0] = map->size();
342 fragmentationInfo->fragmentationPlType[0] = 0;
343 fragmentationInfo->fragmentationTimeDiff[0] = 0;
344 }
345
346 const char* ImplementationName() const
347 {
348 GRefPtr<GstElement> encoderImplementation;
349 g_return_val_if_fail(m_encoder, nullptr);
350
351 g_object_get(m_encoder, "encoder", &encoderImplementation.outPtr(), nullptr);
352
353 return GST_OBJECT_NAME(gst_element_get_factory(encoderImplementation.get()));
354 }
355
356 virtual const gchar* Name() = 0;
357 virtual int KeyframeInterval(const webrtc::VideoCodec* codecSettings) = 0;
358
359 void SetRestrictionCaps(GRefPtr<GstCaps> caps)
360 {
361 if (m_restrictionCaps)
362 g_object_set(m_capsFilter, "caps", m_restrictionCaps.get(), nullptr);
363
364 m_restrictionCaps = caps;
365 }
366
367private:
368 GRefPtr<GstElement> m_pipeline;
369 GstElement* m_src;
370 GstElement* m_encoder;
371 GstElement* m_capsFilter;
372
373 webrtc::EncodedImageCallback* m_imageReadyCb;
374 GstClockTime m_firstFramePts;
375 GRefPtr<GstCaps> m_restrictionCaps;
376 webrtc::EncodedImage m_encodedFrame;
377 std::unique_ptr<uint8_t[]> m_encodedImageBuffer;
378 size_t m_encodedImageBufferSize;
379
380 Lock m_bufferMapLock;
381 GRefPtr<GstAdapter> m_adapter;
382 GstElement* m_sink;
383};
384
385class GStreamerH264Encoder : public GStreamerVideoEncoder {
386public:
387 GStreamerH264Encoder() { }
388
389 GStreamerH264Encoder(const webrtc::SdpVideoFormat& format)
390 : m_parser(gst_h264_nal_parser_new())
391 , packetizationMode(webrtc::H264PacketizationMode::NonInterleaved)
392 {
393 auto it = format.parameters.find(cricket::kH264FmtpPacketizationMode);
394
395 if (it != format.parameters.end() && it->second == "1")
396 packetizationMode = webrtc::H264PacketizationMode::NonInterleaved;
397 }
398
399 int KeyframeInterval(const webrtc::VideoCodec* codecSettings) final
400 {
401 return codecSettings->H264().keyFrameInterval;
402 }
403
404 // FIXME - MT. safety!
405 void Fragmentize(webrtc::EncodedImage* encodedImage, std::unique_ptr<uint8_t[]>* encodedImageBuffer, size_t *bufferSize,
406 GstBuffer* gstbuffer, webrtc::RTPFragmentationHeader* fragmentationHeader) final
407 {
408 GstH264NalUnit nalu;
409 auto parserResult = GST_H264_PARSER_OK;
410
411 gsize offset = 0;
412 size_t requiredSize = 0;
413
414 std::vector<GstH264NalUnit> nals;
415
416 const uint8_t startCode[4] = { 0, 0, 0, 1 };
417 auto map = GstMappedBuffer::create(gstbuffer, GST_MAP_READ);
418 while (parserResult == GST_H264_PARSER_OK) {
419 parserResult = gst_h264_parser_identify_nalu(m_parser, map->data(), offset, map->size(), &nalu);
420
421 nalu.sc_offset = offset;
422 nalu.offset = offset + sizeof(startCode);
423 if (parserResult != GST_H264_PARSER_OK && parserResult != GST_H264_PARSER_NO_NAL_END)
424 break;
425
426 requiredSize += nalu.size + sizeof(startCode);
427 nals.push_back(nalu);
428 offset = nalu.offset + nalu.size;
429 }
430
431 if (encodedImage->_size < requiredSize) {
432 encodedImage->_size = requiredSize;
433 encodedImage->_buffer = new uint8_t[encodedImage->_size];
434 encodedImageBuffer->reset(encodedImage->_buffer);
435 *bufferSize = map->size();
436 }
437
438 // Iterate nal units and fill the Fragmentation info.
439 fragmentationHeader->VerifyAndAllocateFragmentationHeader(nals.size());
440 size_t fragmentIndex = 0;
441 encodedImage->_length = 0;
442 for (std::vector<GstH264NalUnit>::iterator nal = nals.begin(); nal != nals.end(); ++nal, fragmentIndex++) {
443
444 ASSERT(map->data()[nal->sc_offset + 0] == startCode[0]);
445 ASSERT(map->data()[nal->sc_offset + 1] == startCode[1]);
446 ASSERT(map->data()[nal->sc_offset + 2] == startCode[2]);
447 ASSERT(map->data()[nal->sc_offset + 3] == startCode[3]);
448
449 fragmentationHeader->fragmentationOffset[fragmentIndex] = nal->offset;
450 fragmentationHeader->fragmentationLength[fragmentIndex] = nal->size;
451
452 memcpy(encodedImage->_buffer + encodedImage->_length, &map->data()[nal->sc_offset],
453 sizeof(startCode) + nal->size);
454 encodedImage->_length += nal->size + sizeof(startCode);
455 }
456 }
457
458 webrtc::SdpVideoFormat ConfigureSupportedCodec(GstElement*) final
459 {
460 // TODO- Create from encoder src pad caps template
461 return webrtc::SdpVideoFormat(cricket::kH264CodecName,
462 { { cricket::kH264FmtpProfileLevelId, cricket::kH264ProfileLevelConstrainedBaseline },
463 { cricket::kH264FmtpLevelAsymmetryAllowed, "1" },
464 { cricket::kH264FmtpPacketizationMode, "1" } });
465 }
466
467 const gchar* Caps() final { return "video/x-h264"; }
468 const gchar* Name() final { return cricket::kH264CodecName; }
469 GstH264NalParser* m_parser;
470 webrtc::VideoCodecType CodecType() final { return webrtc::kVideoCodecH264; }
471
472 void PopulateCodecSpecific(webrtc::CodecSpecificInfo* codecSpecificInfos, GstBuffer*) final
473 {
474 codecSpecificInfos->codecType = CodecType();
475 codecSpecificInfos->codec_name = ImplementationName();
476 webrtc::CodecSpecificInfoH264* h264Info = &(codecSpecificInfos->codecSpecific.H264);
477 h264Info->packetization_mode = packetizationMode;
478 }
479
480 webrtc::H264PacketizationMode packetizationMode;
481};
482
483class GStreamerVP8Encoder : public GStreamerVideoEncoder {
484public:
485 GStreamerVP8Encoder() { }
486 GStreamerVP8Encoder(const webrtc::SdpVideoFormat&) { }
487 const gchar* Caps() final { return "video/x-vp8"; }
488 const gchar* Name() final { return cricket::kVp8CodecName; }
489 webrtc::VideoCodecType CodecType() final { return webrtc::kVideoCodecVP8; }
490
491 int KeyframeInterval(const webrtc::VideoCodec* codecSettings) final
492 {
493 return codecSettings->VP8().keyFrameInterval;
494 }
495
496 void PopulateCodecSpecific(webrtc::CodecSpecificInfo* codecSpecificInfos, GstBuffer* buffer) final
497 {
498 codecSpecificInfos->codecType = webrtc::kVideoCodecVP8;
499 codecSpecificInfos->codec_name = ImplementationName();
500 webrtc::CodecSpecificInfoVP8* vp8Info = &(codecSpecificInfos->codecSpecific.VP8);
501 vp8Info->temporalIdx = 0;
502
503 vp8Info->keyIdx = webrtc::kNoKeyIdx;
504 vp8Info->nonReference = GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT);
505 }
506};
507
508std::unique_ptr<webrtc::VideoEncoder> GStreamerVideoEncoderFactory::CreateVideoEncoder(const webrtc::SdpVideoFormat& format)
509{
510 if (format.name == cricket::kVp8CodecName) {
511 GRefPtr<GstElement> webrtcencoder = adoptGRef(GST_ELEMENT(g_object_ref_sink(gst_element_factory_make("webrtcvideoencoder", NULL))));
512 GRefPtr<GstElement> encoder = nullptr;
513
514 g_object_set(webrtcencoder.get(), "format", adoptGRef(gst_caps_from_string("video/x-vp8")).get(), NULL);
515 g_object_get(webrtcencoder.get(), "encoder", &encoder.outPtr(), NULL);
516
517 if (encoder)
518 return std::make_unique<GStreamerVP8Encoder>(format);
519
520 GST_INFO("Using VP8 Encoder from LibWebRTC.");
521 return std::make_unique<webrtc::LibvpxVp8Encoder>();
522 }
523
524 if (format.name == cricket::kH264CodecName)
525 return std::make_unique<GStreamerH264Encoder>(format);
526
527 return nullptr;
528}
529
530GStreamerVideoEncoderFactory::GStreamerVideoEncoderFactory()
531{
532 static std::once_flag debugRegisteredFlag;
533
534 std::call_once(debugRegisteredFlag, [] {
535 GST_DEBUG_CATEGORY_INIT(webkit_webrtcenc_debug, "webkitlibwebrtcvideoencoder", 0, "WebKit WebRTC video encoder");
536 gst_element_register(nullptr, "webrtcvideoencoder", GST_RANK_PRIMARY, GST_TYPE_WEBRTC_VIDEO_ENCODER);
537 });
538}
539
540std::vector<webrtc::SdpVideoFormat> GStreamerVideoEncoderFactory::GetSupportedFormats() const
541{
542 std::vector<webrtc::SdpVideoFormat> supportedCodecs;
543
544 supportedCodecs.push_back(webrtc::SdpVideoFormat(cricket::kVp8CodecName));
545 GStreamerH264Encoder().AddCodecIfSupported(&supportedCodecs);
546
547 return supportedCodecs;
548}
549
550} // namespace WebCore
551#endif
552