Modify PeerConnection for end-to-end QuicDataChannel usage
To allow end-to-end QuicDataChannel usage with a PeerConnection, RTCConfiguration has been modified to include a boolean for whether to do QUIC, since negotiation of QUIC is not implemented. If one peer does QUIC, then it will be assumed that the other peer must do QUIC or the connection will fail. PeerConnection has been modified to create data channels of type QuicDataChannel when the peer wants to do QUIC. WebRtcSession has been modified to use a QuicTransportChannel instead of a DtlsTransportChannelWrapper/DataChannel when QUIC should be used. Modification of previous in-flight CL: https://codereview.chromium.org/1844803002/ Review-Url: https://codereview.webrtc.org/2089553002 Cr-Commit-Position: refs/heads/master@{#13470}
This commit is contained in:
parent
367efdcec1
commit
36c8d69ce1
@ -61,10 +61,12 @@ bool ParseQuicDataMessageHeader(const char* data,
|
|||||||
|
|
||||||
QuicDataChannel::QuicDataChannel(rtc::Thread* signaling_thread,
|
QuicDataChannel::QuicDataChannel(rtc::Thread* signaling_thread,
|
||||||
rtc::Thread* worker_thread,
|
rtc::Thread* worker_thread,
|
||||||
|
rtc::Thread* network_thread,
|
||||||
const std::string& label,
|
const std::string& label,
|
||||||
const DataChannelInit& config)
|
const DataChannelInit& config)
|
||||||
: signaling_thread_(signaling_thread),
|
: signaling_thread_(signaling_thread),
|
||||||
worker_thread_(worker_thread),
|
worker_thread_(worker_thread),
|
||||||
|
network_thread_(network_thread),
|
||||||
id_(config.id),
|
id_(config.id),
|
||||||
state_(kConnecting),
|
state_(kConnecting),
|
||||||
buffered_amount_(0),
|
buffered_amount_(0),
|
||||||
@ -91,12 +93,12 @@ bool QuicDataChannel::Send(const DataBuffer& buffer) {
|
|||||||
<< " is not open so cannot send.";
|
<< " is not open so cannot send.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return worker_thread_->Invoke<bool>(
|
return network_thread_->Invoke<bool>(
|
||||||
RTC_FROM_HERE, rtc::Bind(&QuicDataChannel::Send_w, this, buffer));
|
RTC_FROM_HERE, rtc::Bind(&QuicDataChannel::Send_n, this, buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuicDataChannel::Send_w(const DataBuffer& buffer) {
|
bool QuicDataChannel::Send_n(const DataBuffer& buffer) {
|
||||||
RTC_DCHECK(worker_thread_->IsCurrent());
|
RTC_DCHECK(network_thread_->IsCurrent());
|
||||||
|
|
||||||
// Encode and send the header containing the data channel ID and message ID.
|
// Encode and send the header containing the data channel ID and message ID.
|
||||||
rtc::CopyOnWriteBuffer header;
|
rtc::CopyOnWriteBuffer header;
|
||||||
@ -256,7 +258,7 @@ DataChannelInterface::DataState QuicDataChannel::SetTransportChannel_w() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void QuicDataChannel::OnIncomingMessage(Message&& message) {
|
void QuicDataChannel::OnIncomingMessage(Message&& message) {
|
||||||
RTC_DCHECK(worker_thread_->IsCurrent());
|
RTC_DCHECK(network_thread_->IsCurrent());
|
||||||
RTC_DCHECK(message.stream);
|
RTC_DCHECK(message.stream);
|
||||||
if (!observer_) {
|
if (!observer_) {
|
||||||
LOG(LS_WARNING) << "QUIC data channel " << id_
|
LOG(LS_WARNING) << "QUIC data channel " << id_
|
||||||
@ -295,7 +297,7 @@ void QuicDataChannel::OnIncomingMessage(Message&& message) {
|
|||||||
void QuicDataChannel::OnDataReceived(net::QuicStreamId stream_id,
|
void QuicDataChannel::OnDataReceived(net::QuicStreamId stream_id,
|
||||||
const char* data,
|
const char* data,
|
||||||
size_t len) {
|
size_t len) {
|
||||||
RTC_DCHECK(worker_thread_->IsCurrent());
|
RTC_DCHECK(network_thread_->IsCurrent());
|
||||||
RTC_DCHECK(data);
|
RTC_DCHECK(data);
|
||||||
const auto& kv = incoming_quic_messages_.find(stream_id);
|
const auto& kv = incoming_quic_messages_.find(stream_id);
|
||||||
if (kv == incoming_quic_messages_.end()) {
|
if (kv == incoming_quic_messages_.end()) {
|
||||||
@ -325,7 +327,7 @@ void QuicDataChannel::OnDataReceived(net::QuicStreamId stream_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void QuicDataChannel::OnReadyToSend(cricket::TransportChannel* channel) {
|
void QuicDataChannel::OnReadyToSend(cricket::TransportChannel* channel) {
|
||||||
RTC_DCHECK(worker_thread_->IsCurrent());
|
RTC_DCHECK(network_thread_->IsCurrent());
|
||||||
RTC_DCHECK(channel == quic_transport_channel_);
|
RTC_DCHECK(channel == quic_transport_channel_);
|
||||||
LOG(LS_INFO) << "QuicTransportChannel is ready to send";
|
LOG(LS_INFO) << "QuicTransportChannel is ready to send";
|
||||||
invoker_.AsyncInvoke<void>(
|
invoker_.AsyncInvoke<void>(
|
||||||
@ -342,7 +344,7 @@ void QuicDataChannel::OnWriteBlockedStreamClosed(net::QuicStreamId stream_id,
|
|||||||
|
|
||||||
void QuicDataChannel::OnIncomingQueuedStreamClosed(net::QuicStreamId stream_id,
|
void QuicDataChannel::OnIncomingQueuedStreamClosed(net::QuicStreamId stream_id,
|
||||||
int error) {
|
int error) {
|
||||||
RTC_DCHECK(worker_thread_->IsCurrent());
|
RTC_DCHECK(network_thread_->IsCurrent());
|
||||||
LOG(LS_VERBOSE) << "Incoming queued stream " << stream_id << " is closed.";
|
LOG(LS_VERBOSE) << "Incoming queued stream " << stream_id << " is closed.";
|
||||||
incoming_quic_messages_.erase(stream_id);
|
incoming_quic_messages_.erase(stream_id);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,6 +88,7 @@ class QuicDataChannel : public rtc::RefCountedObject<DataChannelInterface>,
|
|||||||
|
|
||||||
QuicDataChannel(rtc::Thread* signaling_thread,
|
QuicDataChannel(rtc::Thread* signaling_thread,
|
||||||
rtc::Thread* worker_thread,
|
rtc::Thread* worker_thread,
|
||||||
|
rtc::Thread* network_thread,
|
||||||
const std::string& label,
|
const std::string& label,
|
||||||
const DataChannelInit& config);
|
const DataChannelInit& config);
|
||||||
~QuicDataChannel() override;
|
~QuicDataChannel() override;
|
||||||
@ -155,11 +156,13 @@ class QuicDataChannel : public rtc::RefCountedObject<DataChannelInterface>,
|
|||||||
void OnReadyToSend(cricket::TransportChannel* channel);
|
void OnReadyToSend(cricket::TransportChannel* channel);
|
||||||
void OnConnectionClosed();
|
void OnConnectionClosed();
|
||||||
|
|
||||||
// Worker thread methods.
|
// Network thread methods.
|
||||||
// Sends the data buffer to the remote peer using an outgoing QUIC stream.
|
// Sends the data buffer to the remote peer using an outgoing QUIC stream.
|
||||||
// Returns true if the data buffer can be successfully sent, or if it is
|
// Returns true if the data buffer can be successfully sent, or if it is
|
||||||
// queued to be sent later.
|
// queued to be sent later.
|
||||||
bool Send_w(const DataBuffer& buffer);
|
bool Send_n(const DataBuffer& buffer);
|
||||||
|
|
||||||
|
// Worker thread methods.
|
||||||
// Connects the |quic_transport_channel_| signals to this QuicDataChannel,
|
// Connects the |quic_transport_channel_| signals to this QuicDataChannel,
|
||||||
// then returns the new QuicDataChannel state.
|
// then returns the new QuicDataChannel state.
|
||||||
DataState SetTransportChannel_w();
|
DataState SetTransportChannel_w();
|
||||||
@ -185,8 +188,10 @@ class QuicDataChannel : public rtc::RefCountedObject<DataChannelInterface>,
|
|||||||
cricket::QuicTransportChannel* quic_transport_channel_ = nullptr;
|
cricket::QuicTransportChannel* quic_transport_channel_ = nullptr;
|
||||||
// Signaling thread for DataChannelInterface methods.
|
// Signaling thread for DataChannelInterface methods.
|
||||||
rtc::Thread* const signaling_thread_;
|
rtc::Thread* const signaling_thread_;
|
||||||
// Worker thread for sending data and |quic_transport_channel_| callbacks.
|
// Worker thread for |quic_transport_channel_| callbacks.
|
||||||
rtc::Thread* const worker_thread_;
|
rtc::Thread* const worker_thread_;
|
||||||
|
// Network thread for sending data and |quic_transport_channel_| callbacks.
|
||||||
|
rtc::Thread* const network_thread_;
|
||||||
rtc::AsyncInvoker invoker_;
|
rtc::AsyncInvoker invoker_;
|
||||||
// Map of QUIC stream ID => ReliableQuicStream* for write blocked QUIC
|
// Map of QUIC stream ID => ReliableQuicStream* for write blocked QUIC
|
||||||
// streams.
|
// streams.
|
||||||
|
|||||||
@ -120,8 +120,9 @@ class FakeQuicDataTransport : public sigslot::has_slots<> {
|
|||||||
DataChannelInit config;
|
DataChannelInit config;
|
||||||
config.id = id;
|
config.id = id;
|
||||||
config.protocol = protocol;
|
config.protocol = protocol;
|
||||||
rtc::scoped_refptr<QuicDataChannel> data_channel(new QuicDataChannel(
|
rtc::scoped_refptr<QuicDataChannel> data_channel(
|
||||||
rtc::Thread::Current(), rtc::Thread::Current(), label, config));
|
new QuicDataChannel(rtc::Thread::Current(), rtc::Thread::Current(),
|
||||||
|
rtc::Thread::Current(), label, config));
|
||||||
data_channel_by_id_[id] = data_channel;
|
data_channel_by_id_[id] = data_channel;
|
||||||
return data_channel;
|
return data_channel;
|
||||||
}
|
}
|
||||||
@ -201,8 +202,6 @@ class QuicDataChannelPeer {
|
|||||||
|
|
||||||
// Connects |ice_transport_channel_| to that of the other peer.
|
// Connects |ice_transport_channel_| to that of the other peer.
|
||||||
void Connect(QuicDataChannelPeer* other_peer) {
|
void Connect(QuicDataChannelPeer* other_peer) {
|
||||||
ice_transport_channel_->Connect();
|
|
||||||
other_peer->ice_transport_channel_->Connect();
|
|
||||||
ice_transport_channel_->SetDestination(other_peer->ice_transport_channel_);
|
ice_transport_channel_->SetDestination(other_peer->ice_transport_channel_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,14 @@
|
|||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
QuicDataTransport::QuicDataTransport(rtc::Thread* signaling_thread,
|
QuicDataTransport::QuicDataTransport(rtc::Thread* signaling_thread,
|
||||||
rtc::Thread* worker_thread)
|
rtc::Thread* worker_thread,
|
||||||
: signaling_thread_(signaling_thread), worker_thread_(worker_thread) {
|
rtc::Thread* network_thread)
|
||||||
|
: signaling_thread_(signaling_thread),
|
||||||
|
worker_thread_(worker_thread),
|
||||||
|
network_thread_(network_thread) {
|
||||||
RTC_DCHECK(signaling_thread_);
|
RTC_DCHECK(signaling_thread_);
|
||||||
RTC_DCHECK(worker_thread_);
|
RTC_DCHECK(worker_thread_);
|
||||||
|
RTC_DCHECK(network_thread_);
|
||||||
}
|
}
|
||||||
|
|
||||||
QuicDataTransport::~QuicDataTransport() {}
|
QuicDataTransport::~QuicDataTransport() {}
|
||||||
@ -68,8 +72,8 @@ rtc::scoped_refptr<DataChannelInterface> QuicDataTransport::CreateDataChannel(
|
|||||||
LOG(LS_ERROR) << "QUIC data channel already exists with id " << config->id;
|
LOG(LS_ERROR) << "QUIC data channel already exists with id " << config->id;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
rtc::scoped_refptr<QuicDataChannel> data_channel(
|
rtc::scoped_refptr<QuicDataChannel> data_channel(new QuicDataChannel(
|
||||||
new QuicDataChannel(signaling_thread_, worker_thread_, label, *config));
|
signaling_thread_, worker_thread_, network_thread_, label, *config));
|
||||||
if (quic_transport_channel_) {
|
if (quic_transport_channel_) {
|
||||||
if (!data_channel->SetTransportChannel(quic_transport_channel_)) {
|
if (!data_channel->SetTransportChannel(quic_transport_channel_)) {
|
||||||
LOG(LS_ERROR)
|
LOG(LS_ERROR)
|
||||||
|
|||||||
@ -36,7 +36,9 @@ namespace webrtc {
|
|||||||
// exists, it sends the QUIC stream to the QuicDataChannel.
|
// exists, it sends the QUIC stream to the QuicDataChannel.
|
||||||
class QuicDataTransport : public sigslot::has_slots<> {
|
class QuicDataTransport : public sigslot::has_slots<> {
|
||||||
public:
|
public:
|
||||||
QuicDataTransport(rtc::Thread* signaling_thread, rtc::Thread* worker_thread);
|
QuicDataTransport(rtc::Thread* signaling_thread,
|
||||||
|
rtc::Thread* worker_thread,
|
||||||
|
rtc::Thread* network_thread);
|
||||||
~QuicDataTransport() override;
|
~QuicDataTransport() override;
|
||||||
|
|
||||||
// Sets the QUIC transport channel for the QuicDataChannels and the
|
// Sets the QUIC transport channel for the QuicDataChannels and the
|
||||||
@ -80,9 +82,10 @@ class QuicDataTransport : public sigslot::has_slots<> {
|
|||||||
quic_stream_by_id_;
|
quic_stream_by_id_;
|
||||||
// QuicTransportChannel for sending/receiving data.
|
// QuicTransportChannel for sending/receiving data.
|
||||||
cricket::QuicTransportChannel* quic_transport_channel_ = nullptr;
|
cricket::QuicTransportChannel* quic_transport_channel_ = nullptr;
|
||||||
// Signaling and worker threads for the QUIC data channel.
|
// Threads for the QUIC data channel.
|
||||||
rtc::Thread* const signaling_thread_;
|
rtc::Thread* const signaling_thread_;
|
||||||
rtc::Thread* const worker_thread_;
|
rtc::Thread* const worker_thread_;
|
||||||
|
rtc::Thread* const network_thread_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -64,7 +64,9 @@ class FakeObserver : public DataChannelObserver {
|
|||||||
class QuicDataTransportPeer {
|
class QuicDataTransportPeer {
|
||||||
public:
|
public:
|
||||||
QuicDataTransportPeer()
|
QuicDataTransportPeer()
|
||||||
: quic_data_transport_(rtc::Thread::Current(), rtc::Thread::Current()),
|
: quic_data_transport_(rtc::Thread::Current(),
|
||||||
|
rtc::Thread::Current(),
|
||||||
|
rtc::Thread::Current()),
|
||||||
ice_transport_channel_(new FakeTransportChannel("data", 0)),
|
ice_transport_channel_(new FakeTransportChannel("data", 0)),
|
||||||
quic_transport_channel_(ice_transport_channel_) {
|
quic_transport_channel_(ice_transport_channel_) {
|
||||||
ice_transport_channel_->SetAsync(true);
|
ice_transport_channel_->SetAsync(true);
|
||||||
@ -80,8 +82,6 @@ class QuicDataTransportPeer {
|
|||||||
|
|
||||||
// Connects |ice_transport_channel_| to that of the other peer.
|
// Connects |ice_transport_channel_| to that of the other peer.
|
||||||
void Connect(QuicDataTransportPeer* other_peer) {
|
void Connect(QuicDataTransportPeer* other_peer) {
|
||||||
ice_transport_channel_->Connect();
|
|
||||||
other_peer->ice_transport_channel_->Connect();
|
|
||||||
ice_transport_channel_->SetDestination(other_peer->ice_transport_channel_);
|
ice_transport_channel_->SetDestination(other_peer->ice_transport_channel_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -295,8 +295,6 @@ void QuicSessionTest::CreateClientAndServerSessions() {
|
|||||||
channel2->SetAsync(true);
|
channel2->SetAsync(true);
|
||||||
|
|
||||||
// Configure peers to send packets to each other.
|
// Configure peers to send packets to each other.
|
||||||
channel1->Connect();
|
|
||||||
channel2->Connect();
|
|
||||||
channel1->SetDestination(channel2.get());
|
channel1->SetDestination(channel2.get());
|
||||||
|
|
||||||
client_peer_ = CreateSession(std::move(channel1), Perspective::IS_CLIENT);
|
client_peer_ = CreateSession(std::move(channel1), Perspective::IS_CLIENT);
|
||||||
|
|||||||
@ -395,7 +395,8 @@ void QuicTransportChannel::OnRouteChange(TransportChannel* channel,
|
|||||||
void QuicTransportChannel::OnSelectedCandidatePairChanged(
|
void QuicTransportChannel::OnSelectedCandidatePairChanged(
|
||||||
TransportChannel* channel,
|
TransportChannel* channel,
|
||||||
CandidatePairInterface* selected_candidate_pair,
|
CandidatePairInterface* selected_candidate_pair,
|
||||||
int last_sent_packet_id bool ready_to_send) {
|
int last_sent_packet_id,
|
||||||
|
bool ready_to_send) {
|
||||||
ASSERT(channel == channel_.get());
|
ASSERT(channel == channel_.get());
|
||||||
SignalSelectedCandidatePairChanged(this, selected_candidate_pair,
|
SignalSelectedCandidatePairChanged(this, selected_candidate_pair,
|
||||||
last_sent_packet_id, ready_to_send);
|
last_sent_packet_id, ready_to_send);
|
||||||
|
|||||||
@ -166,9 +166,6 @@ class QuicTransportChannel : public TransportChannelImpl,
|
|||||||
void SetIceConfig(const IceConfig& config) override {
|
void SetIceConfig(const IceConfig& config) override {
|
||||||
channel_->SetIceConfig(config);
|
channel_->SetIceConfig(config);
|
||||||
}
|
}
|
||||||
void Connect() override {
|
|
||||||
channel_->Connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// QuicPacketWriter overrides.
|
// QuicPacketWriter overrides.
|
||||||
// Called from net::QuicConnection when |quic_| has packets to write.
|
// Called from net::QuicConnection when |quic_| has packets to write.
|
||||||
|
|||||||
@ -112,8 +112,6 @@ class QuicTestPeer : public sigslot::has_slots<> {
|
|||||||
|
|
||||||
// Connects |ice_channel_| to that of the other peer.
|
// Connects |ice_channel_| to that of the other peer.
|
||||||
void Connect(QuicTestPeer* other_peer) {
|
void Connect(QuicTestPeer* other_peer) {
|
||||||
ice_channel_->Connect();
|
|
||||||
other_peer->ice_channel_->Connect();
|
|
||||||
ice_channel_->SetDestination(other_peer->ice_channel_);
|
ice_channel_->SetDestination(other_peer->ice_channel_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,8 +417,6 @@ TEST_F(QuicTransportChannelTest, TransferInvalidSrtp) {
|
|||||||
// Test that QuicTransportChannel::WritePacket blocks when the ICE
|
// Test that QuicTransportChannel::WritePacket blocks when the ICE
|
||||||
// channel is not writable, and otherwise succeeds.
|
// channel is not writable, and otherwise succeeds.
|
||||||
TEST_F(QuicTransportChannelTest, QuicWritePacket) {
|
TEST_F(QuicTransportChannelTest, QuicWritePacket) {
|
||||||
peer1_.ice_channel()->Connect();
|
|
||||||
peer2_.ice_channel()->Connect();
|
|
||||||
peer1_.ice_channel()->SetDestination(peer2_.ice_channel());
|
peer1_.ice_channel()->SetDestination(peer2_.ice_channel());
|
||||||
std::string packet = "FAKEQUICPACKET";
|
std::string packet = "FAKEQUICPACKET";
|
||||||
|
|
||||||
|
|||||||
@ -49,6 +49,7 @@ using rtc::SR_BLOCK;
|
|||||||
|
|
||||||
// Arbitrary number for a stream's write blocked priority.
|
// Arbitrary number for a stream's write blocked priority.
|
||||||
static const SpdyPriority kDefaultPriority = 3;
|
static const SpdyPriority kDefaultPriority = 3;
|
||||||
|
static const net::QuicStreamId kStreamId = 5;
|
||||||
|
|
||||||
// QuicSession that does not create streams and writes data from
|
// QuicSession that does not create streams and writes data from
|
||||||
// ReliableQuicStream to a string.
|
// ReliableQuicStream to a string.
|
||||||
@ -78,7 +79,7 @@ class MockQuicSession : public QuicSession {
|
|||||||
|
|
||||||
net::ReliableQuicStream* CreateIncomingDynamicStream(
|
net::ReliableQuicStream* CreateIncomingDynamicStream(
|
||||||
QuicStreamId id) override {
|
QuicStreamId id) override {
|
||||||
return nullptr;
|
return new ReliableQuicStream(kStreamId, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
net::ReliableQuicStream* CreateOutgoingDynamicStream(
|
net::ReliableQuicStream* CreateOutgoingDynamicStream(
|
||||||
@ -142,7 +143,6 @@ class ReliableQuicStreamTest : public ::testing::Test,
|
|||||||
ReliableQuicStreamTest() {}
|
ReliableQuicStreamTest() {}
|
||||||
|
|
||||||
void CreateReliableQuicStream() {
|
void CreateReliableQuicStream() {
|
||||||
const net::QuicStreamId kStreamId = 5;
|
|
||||||
|
|
||||||
// Arbitrary values for QuicConnection.
|
// Arbitrary values for QuicConnection.
|
||||||
QuicConnectionHelper* quic_helper =
|
QuicConnectionHelper* quic_helper =
|
||||||
@ -232,7 +232,7 @@ TEST_F(ReliableQuicStreamTest, BufferData) {
|
|||||||
// Read an entire string.
|
// Read an entire string.
|
||||||
TEST_F(ReliableQuicStreamTest, ReadDataWhole) {
|
TEST_F(ReliableQuicStreamTest, ReadDataWhole) {
|
||||||
CreateReliableQuicStream();
|
CreateReliableQuicStream();
|
||||||
net::QuicStreamFrame frame(-1, false, 0, "Hello, World!");
|
net::QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!");
|
||||||
stream_->OnStreamFrame(frame);
|
stream_->OnStreamFrame(frame);
|
||||||
|
|
||||||
EXPECT_EQ("Hello, World!", read_buffer_);
|
EXPECT_EQ("Hello, World!", read_buffer_);
|
||||||
@ -241,7 +241,7 @@ TEST_F(ReliableQuicStreamTest, ReadDataWhole) {
|
|||||||
// Read part of a string.
|
// Read part of a string.
|
||||||
TEST_F(ReliableQuicStreamTest, ReadDataPartial) {
|
TEST_F(ReliableQuicStreamTest, ReadDataPartial) {
|
||||||
CreateReliableQuicStream();
|
CreateReliableQuicStream();
|
||||||
net::QuicStreamFrame frame(-1, false, 0, "Hello, World!");
|
net::QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!");
|
||||||
frame.frame_length = 5;
|
frame.frame_length = 5;
|
||||||
stream_->OnStreamFrame(frame);
|
stream_->OnStreamFrame(frame);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user