Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | // Original author: ekr@rtfm.com |
michael@0 | 7 | |
michael@0 | 8 | #include "logging.h" |
michael@0 | 9 | #include "MediaPipeline.h" |
michael@0 | 10 | |
michael@0 | 11 | #ifndef USE_FAKE_MEDIA_STREAMS |
michael@0 | 12 | #include "MediaStreamGraphImpl.h" |
michael@0 | 13 | #endif |
michael@0 | 14 | |
michael@0 | 15 | #include <math.h> |
michael@0 | 16 | |
michael@0 | 17 | #include "nspr.h" |
michael@0 | 18 | #include "srtp.h" |
michael@0 | 19 | |
michael@0 | 20 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 21 | #include "VideoSegment.h" |
michael@0 | 22 | #include "Layers.h" |
michael@0 | 23 | #include "ImageTypes.h" |
michael@0 | 24 | #include "ImageContainer.h" |
michael@0 | 25 | #include "VideoUtils.h" |
michael@0 | 26 | #ifdef WEBRTC_GONK |
michael@0 | 27 | #include "GrallocImages.h" |
michael@0 | 28 | #include "mozilla/layers/GrallocTextureClient.h" |
michael@0 | 29 | #endif |
michael@0 | 30 | #endif |
michael@0 | 31 | |
michael@0 | 32 | #include "nsError.h" |
michael@0 | 33 | #include "AudioSegment.h" |
michael@0 | 34 | #include "MediaSegment.h" |
michael@0 | 35 | #include "databuffer.h" |
michael@0 | 36 | #include "transportflow.h" |
michael@0 | 37 | #include "transportlayer.h" |
michael@0 | 38 | #include "transportlayerdtls.h" |
michael@0 | 39 | #include "transportlayerice.h" |
michael@0 | 40 | #include "runnable_utils.h" |
michael@0 | 41 | #include "libyuv/convert.h" |
michael@0 | 42 | #include "mozilla/gfx/Point.h" |
michael@0 | 43 | #include "mozilla/gfx/Types.h" |
michael@0 | 44 | |
michael@0 | 45 | #include "webrtc/modules/interface/module_common_types.h" |
michael@0 | 46 | #include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" |
michael@0 | 47 | |
michael@0 | 48 | using namespace mozilla; |
michael@0 | 49 | using namespace mozilla::gfx; |
michael@0 | 50 | |
michael@0 | 51 | // Logging context |
michael@0 | 52 | MOZ_MTLOG_MODULE("mediapipeline") |
michael@0 | 53 | |
michael@0 | 54 | namespace mozilla { |
michael@0 | 55 | |
michael@0 | 56 | static char kDTLSExporterLabel[] = "EXTRACTOR-dtls_srtp"; |
michael@0 | 57 | |
michael@0 | 58 | MediaPipeline::~MediaPipeline() { |
michael@0 | 59 | ASSERT_ON_THREAD(main_thread_); |
michael@0 | 60 | MOZ_ASSERT(!stream_); // Check that we have shut down already. |
michael@0 | 61 | MOZ_MTLOG(ML_INFO, "Destroying MediaPipeline: " << description_); |
michael@0 | 62 | } |
michael@0 | 63 | |
michael@0 | 64 | nsresult MediaPipeline::Init() { |
michael@0 | 65 | ASSERT_ON_THREAD(main_thread_); |
michael@0 | 66 | |
michael@0 | 67 | RUN_ON_THREAD(sts_thread_, |
michael@0 | 68 | WrapRunnable( |
michael@0 | 69 | nsRefPtr<MediaPipeline>(this), |
michael@0 | 70 | &MediaPipeline::Init_s), |
michael@0 | 71 | NS_DISPATCH_NORMAL); |
michael@0 | 72 | |
michael@0 | 73 | return NS_OK; |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | nsresult MediaPipeline::Init_s() { |
michael@0 | 77 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 78 | conduit_->AttachTransport(transport_); |
michael@0 | 79 | |
michael@0 | 80 | nsresult res; |
michael@0 | 81 | MOZ_ASSERT(rtp_.transport_); |
michael@0 | 82 | MOZ_ASSERT(rtcp_.transport_); |
michael@0 | 83 | res = ConnectTransport_s(rtp_); |
michael@0 | 84 | if (NS_FAILED(res)) { |
michael@0 | 85 | return res; |
michael@0 | 86 | } |
michael@0 | 87 | |
michael@0 | 88 | if (rtcp_.transport_ != rtp_.transport_) { |
michael@0 | 89 | res = ConnectTransport_s(rtcp_); |
michael@0 | 90 | if (NS_FAILED(res)) { |
michael@0 | 91 | return res; |
michael@0 | 92 | } |
michael@0 | 93 | } |
michael@0 | 94 | |
michael@0 | 95 | if (possible_bundle_rtp_) { |
michael@0 | 96 | MOZ_ASSERT(possible_bundle_rtcp_); |
michael@0 | 97 | MOZ_ASSERT(possible_bundle_rtp_->transport_); |
michael@0 | 98 | MOZ_ASSERT(possible_bundle_rtcp_->transport_); |
michael@0 | 99 | |
michael@0 | 100 | res = ConnectTransport_s(*possible_bundle_rtp_); |
michael@0 | 101 | if (NS_FAILED(res)) { |
michael@0 | 102 | return res; |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | if (possible_bundle_rtcp_->transport_ != possible_bundle_rtp_->transport_) { |
michael@0 | 106 | res = ConnectTransport_s(*possible_bundle_rtcp_); |
michael@0 | 107 | if (NS_FAILED(res)) { |
michael@0 | 108 | return res; |
michael@0 | 109 | } |
michael@0 | 110 | } |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | return NS_OK; |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | |
michael@0 | 117 | // Disconnect us from the transport so that we can cleanly destruct the |
michael@0 | 118 | // pipeline on the main thread. ShutdownMedia_m() must have already been |
michael@0 | 119 | // called |
michael@0 | 120 | void MediaPipeline::ShutdownTransport_s() { |
michael@0 | 121 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 122 | MOZ_ASSERT(!stream_); // verifies that ShutdownMedia_m() has run |
michael@0 | 123 | |
michael@0 | 124 | disconnect_all(); |
michael@0 | 125 | transport_->Detach(); |
michael@0 | 126 | rtp_.transport_ = nullptr; |
michael@0 | 127 | rtcp_.transport_ = nullptr; |
michael@0 | 128 | possible_bundle_rtp_ = nullptr; |
michael@0 | 129 | possible_bundle_rtcp_ = nullptr; |
michael@0 | 130 | } |
michael@0 | 131 | |
michael@0 | 132 | void MediaPipeline::StateChange(TransportFlow *flow, TransportLayer::State state) { |
michael@0 | 133 | TransportInfo* info = GetTransportInfo_s(flow); |
michael@0 | 134 | MOZ_ASSERT(info); |
michael@0 | 135 | |
michael@0 | 136 | if (state == TransportLayer::TS_OPEN) { |
michael@0 | 137 | MOZ_MTLOG(ML_INFO, "Flow is ready"); |
michael@0 | 138 | TransportReady_s(*info); |
michael@0 | 139 | } else if (state == TransportLayer::TS_CLOSED || |
michael@0 | 140 | state == TransportLayer::TS_ERROR) { |
michael@0 | 141 | TransportFailed_s(*info); |
michael@0 | 142 | } |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | static bool MakeRtpTypeToStringArray(const char** array) { |
michael@0 | 146 | static const char* RTP_str = "RTP"; |
michael@0 | 147 | static const char* RTCP_str = "RTCP"; |
michael@0 | 148 | static const char* MUX_str = "RTP/RTCP mux"; |
michael@0 | 149 | array[MediaPipeline::RTP] = RTP_str; |
michael@0 | 150 | array[MediaPipeline::RTCP] = RTCP_str; |
michael@0 | 151 | array[MediaPipeline::MUX] = MUX_str; |
michael@0 | 152 | return true; |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | static const char* ToString(MediaPipeline::RtpType type) { |
michael@0 | 156 | static const char* array[(int)MediaPipeline::MAX_RTP_TYPE] = {nullptr}; |
michael@0 | 157 | // Dummy variable to cause init to happen only on first call |
michael@0 | 158 | static bool dummy = MakeRtpTypeToStringArray(array); |
michael@0 | 159 | (void)dummy; |
michael@0 | 160 | return array[type]; |
michael@0 | 161 | } |
michael@0 | 162 | |
michael@0 | 163 | nsresult MediaPipeline::TransportReady_s(TransportInfo &info) { |
michael@0 | 164 | MOZ_ASSERT(!description_.empty()); |
michael@0 | 165 | |
michael@0 | 166 | // TODO(ekr@rtfm.com): implement some kind of notification on |
michael@0 | 167 | // failure. bug 852665. |
michael@0 | 168 | if (info.state_ != MP_CONNECTING) { |
michael@0 | 169 | MOZ_MTLOG(ML_ERROR, "Transport ready for flow in wrong state:" << |
michael@0 | 170 | description_ << ": " << ToString(info.type_)); |
michael@0 | 171 | return NS_ERROR_FAILURE; |
michael@0 | 172 | } |
michael@0 | 173 | |
michael@0 | 174 | MOZ_MTLOG(ML_INFO, "Transport ready for pipeline " << |
michael@0 | 175 | static_cast<void *>(this) << " flow " << description_ << ": " << |
michael@0 | 176 | ToString(info.type_)); |
michael@0 | 177 | |
michael@0 | 178 | // TODO(bcampen@mozilla.com): Should we disconnect from the flow on failure? |
michael@0 | 179 | nsresult res; |
michael@0 | 180 | |
michael@0 | 181 | // Now instantiate the SRTP objects |
michael@0 | 182 | TransportLayerDtls *dtls = static_cast<TransportLayerDtls *>( |
michael@0 | 183 | info.transport_->GetLayer(TransportLayerDtls::ID())); |
michael@0 | 184 | MOZ_ASSERT(dtls); // DTLS is mandatory |
michael@0 | 185 | |
michael@0 | 186 | uint16_t cipher_suite; |
michael@0 | 187 | res = dtls->GetSrtpCipher(&cipher_suite); |
michael@0 | 188 | if (NS_FAILED(res)) { |
michael@0 | 189 | MOZ_MTLOG(ML_ERROR, "Failed to negotiate DTLS-SRTP. This is an error"); |
michael@0 | 190 | info.state_ = MP_CLOSED; |
michael@0 | 191 | UpdateRtcpMuxState(info); |
michael@0 | 192 | return res; |
michael@0 | 193 | } |
michael@0 | 194 | |
michael@0 | 195 | // SRTP Key Exporter as per RFC 5764 S 4.2 |
michael@0 | 196 | unsigned char srtp_block[SRTP_TOTAL_KEY_LENGTH * 2]; |
michael@0 | 197 | res = dtls->ExportKeyingMaterial(kDTLSExporterLabel, false, "", |
michael@0 | 198 | srtp_block, sizeof(srtp_block)); |
michael@0 | 199 | if (NS_FAILED(res)) { |
michael@0 | 200 | MOZ_MTLOG(ML_ERROR, "Failed to compute DTLS-SRTP keys. This is an error"); |
michael@0 | 201 | info.state_ = MP_CLOSED; |
michael@0 | 202 | UpdateRtcpMuxState(info); |
michael@0 | 203 | MOZ_CRASH(); // TODO: Remove once we have enough field experience to |
michael@0 | 204 | // know it doesn't happen. bug 798797. Note that the |
michael@0 | 205 | // code after this never executes. |
michael@0 | 206 | return res; |
michael@0 | 207 | } |
michael@0 | 208 | |
michael@0 | 209 | // Slice and dice as per RFC 5764 S 4.2 |
michael@0 | 210 | unsigned char client_write_key[SRTP_TOTAL_KEY_LENGTH]; |
michael@0 | 211 | unsigned char server_write_key[SRTP_TOTAL_KEY_LENGTH]; |
michael@0 | 212 | int offset = 0; |
michael@0 | 213 | memcpy(client_write_key, srtp_block + offset, SRTP_MASTER_KEY_LENGTH); |
michael@0 | 214 | offset += SRTP_MASTER_KEY_LENGTH; |
michael@0 | 215 | memcpy(server_write_key, srtp_block + offset, SRTP_MASTER_KEY_LENGTH); |
michael@0 | 216 | offset += SRTP_MASTER_KEY_LENGTH; |
michael@0 | 217 | memcpy(client_write_key + SRTP_MASTER_KEY_LENGTH, |
michael@0 | 218 | srtp_block + offset, SRTP_MASTER_SALT_LENGTH); |
michael@0 | 219 | offset += SRTP_MASTER_SALT_LENGTH; |
michael@0 | 220 | memcpy(server_write_key + SRTP_MASTER_KEY_LENGTH, |
michael@0 | 221 | srtp_block + offset, SRTP_MASTER_SALT_LENGTH); |
michael@0 | 222 | offset += SRTP_MASTER_SALT_LENGTH; |
michael@0 | 223 | MOZ_ASSERT(offset == sizeof(srtp_block)); |
michael@0 | 224 | |
michael@0 | 225 | unsigned char *write_key; |
michael@0 | 226 | unsigned char *read_key; |
michael@0 | 227 | |
michael@0 | 228 | if (dtls->role() == TransportLayerDtls::CLIENT) { |
michael@0 | 229 | write_key = client_write_key; |
michael@0 | 230 | read_key = server_write_key; |
michael@0 | 231 | } else { |
michael@0 | 232 | write_key = server_write_key; |
michael@0 | 233 | read_key = client_write_key; |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | MOZ_ASSERT(!info.send_srtp_ && !info.recv_srtp_); |
michael@0 | 237 | info.send_srtp_ = SrtpFlow::Create(cipher_suite, false, write_key, |
michael@0 | 238 | SRTP_TOTAL_KEY_LENGTH); |
michael@0 | 239 | info.recv_srtp_ = SrtpFlow::Create(cipher_suite, true, read_key, |
michael@0 | 240 | SRTP_TOTAL_KEY_LENGTH); |
michael@0 | 241 | if (!info.send_srtp_ || !info.recv_srtp_) { |
michael@0 | 242 | MOZ_MTLOG(ML_ERROR, "Couldn't create SRTP flow for " |
michael@0 | 243 | << ToString(info.type_)); |
michael@0 | 244 | info.state_ = MP_CLOSED; |
michael@0 | 245 | UpdateRtcpMuxState(info); |
michael@0 | 246 | return NS_ERROR_FAILURE; |
michael@0 | 247 | } |
michael@0 | 248 | |
michael@0 | 249 | MOZ_MTLOG(ML_INFO, "Listening for " << ToString(info.type_) |
michael@0 | 250 | << " packets received on " << |
michael@0 | 251 | static_cast<void *>(dtls->downward())); |
michael@0 | 252 | |
michael@0 | 253 | switch (info.type_) { |
michael@0 | 254 | case RTP: |
michael@0 | 255 | dtls->downward()->SignalPacketReceived.connect( |
michael@0 | 256 | this, |
michael@0 | 257 | &MediaPipeline::RtpPacketReceived); |
michael@0 | 258 | break; |
michael@0 | 259 | case RTCP: |
michael@0 | 260 | dtls->downward()->SignalPacketReceived.connect( |
michael@0 | 261 | this, |
michael@0 | 262 | &MediaPipeline::RtcpPacketReceived); |
michael@0 | 263 | break; |
michael@0 | 264 | case MUX: |
michael@0 | 265 | dtls->downward()->SignalPacketReceived.connect( |
michael@0 | 266 | this, |
michael@0 | 267 | &MediaPipeline::PacketReceived); |
michael@0 | 268 | break; |
michael@0 | 269 | default: |
michael@0 | 270 | MOZ_CRASH(); |
michael@0 | 271 | } |
michael@0 | 272 | |
michael@0 | 273 | info.state_ = MP_OPEN; |
michael@0 | 274 | UpdateRtcpMuxState(info); |
michael@0 | 275 | return NS_OK; |
michael@0 | 276 | } |
michael@0 | 277 | |
michael@0 | 278 | nsresult MediaPipeline::TransportFailed_s(TransportInfo &info) { |
michael@0 | 279 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 280 | |
michael@0 | 281 | info.state_ = MP_CLOSED; |
michael@0 | 282 | UpdateRtcpMuxState(info); |
michael@0 | 283 | |
michael@0 | 284 | MOZ_MTLOG(ML_INFO, "Transport closed for flow " << ToString(info.type_)); |
michael@0 | 285 | |
michael@0 | 286 | NS_WARNING( |
michael@0 | 287 | "MediaPipeline Transport failed. This is not properly cleaned up yet"); |
michael@0 | 288 | |
michael@0 | 289 | // TODO(ekr@rtfm.com): SECURITY: Figure out how to clean up if the |
michael@0 | 290 | // connection was good and now it is bad. |
michael@0 | 291 | // TODO(ekr@rtfm.com): Report up so that the PC knows we |
michael@0 | 292 | // have experienced an error. |
michael@0 | 293 | |
michael@0 | 294 | return NS_OK; |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | void MediaPipeline::UpdateRtcpMuxState(TransportInfo &info) { |
michael@0 | 298 | if (info.type_ == MUX) { |
michael@0 | 299 | if (info.transport_ == rtcp_.transport_) { |
michael@0 | 300 | rtcp_.state_ = info.state_; |
michael@0 | 301 | if (!rtcp_.send_srtp_) { |
michael@0 | 302 | rtcp_.send_srtp_ = info.send_srtp_; |
michael@0 | 303 | rtcp_.recv_srtp_ = info.recv_srtp_; |
michael@0 | 304 | } |
michael@0 | 305 | } else if (possible_bundle_rtcp_ && |
michael@0 | 306 | info.transport_ == possible_bundle_rtcp_->transport_) { |
michael@0 | 307 | possible_bundle_rtcp_->state_ = info.state_; |
michael@0 | 308 | if (!possible_bundle_rtcp_->send_srtp_) { |
michael@0 | 309 | possible_bundle_rtcp_->send_srtp_ = info.send_srtp_; |
michael@0 | 310 | possible_bundle_rtcp_->recv_srtp_ = info.recv_srtp_; |
michael@0 | 311 | } |
michael@0 | 312 | } |
michael@0 | 313 | } |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | nsresult MediaPipeline::SendPacket(TransportFlow *flow, const void *data, |
michael@0 | 317 | int len) { |
michael@0 | 318 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 319 | |
michael@0 | 320 | // Note that we bypass the DTLS layer here |
michael@0 | 321 | TransportLayerDtls *dtls = static_cast<TransportLayerDtls *>( |
michael@0 | 322 | flow->GetLayer(TransportLayerDtls::ID())); |
michael@0 | 323 | MOZ_ASSERT(dtls); |
michael@0 | 324 | |
michael@0 | 325 | TransportResult res = dtls->downward()-> |
michael@0 | 326 | SendPacket(static_cast<const unsigned char *>(data), len); |
michael@0 | 327 | |
michael@0 | 328 | if (res != len) { |
michael@0 | 329 | // Ignore blocking indications |
michael@0 | 330 | if (res == TE_WOULDBLOCK) |
michael@0 | 331 | return NS_OK; |
michael@0 | 332 | |
michael@0 | 333 | MOZ_MTLOG(ML_ERROR, "Failed write on stream"); |
michael@0 | 334 | return NS_BASE_STREAM_CLOSED; |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | return NS_OK; |
michael@0 | 338 | } |
michael@0 | 339 | |
michael@0 | 340 | void MediaPipeline::increment_rtp_packets_sent(int32_t bytes) { |
michael@0 | 341 | ++rtp_packets_sent_; |
michael@0 | 342 | rtp_bytes_sent_ += bytes; |
michael@0 | 343 | |
michael@0 | 344 | if (!(rtp_packets_sent_ % 100)) { |
michael@0 | 345 | MOZ_MTLOG(ML_INFO, "RTP sent packet count for " << description_ |
michael@0 | 346 | << " Pipeline " << static_cast<void *>(this) |
michael@0 | 347 | << " Flow : " << static_cast<void *>(rtp_.transport_) |
michael@0 | 348 | << ": " << rtp_packets_sent_ |
michael@0 | 349 | << " (" << rtp_bytes_sent_ << " bytes)"); |
michael@0 | 350 | } |
michael@0 | 351 | } |
michael@0 | 352 | |
michael@0 | 353 | void MediaPipeline::increment_rtcp_packets_sent() { |
michael@0 | 354 | ++rtcp_packets_sent_; |
michael@0 | 355 | if (!(rtcp_packets_sent_ % 100)) { |
michael@0 | 356 | MOZ_MTLOG(ML_INFO, "RTCP sent packet count for " << description_ |
michael@0 | 357 | << " Pipeline " << static_cast<void *>(this) |
michael@0 | 358 | << " Flow : " << static_cast<void *>(rtcp_.transport_) |
michael@0 | 359 | << ": " << rtcp_packets_sent_); |
michael@0 | 360 | } |
michael@0 | 361 | } |
michael@0 | 362 | |
michael@0 | 363 | void MediaPipeline::increment_rtp_packets_received(int32_t bytes) { |
michael@0 | 364 | ++rtp_packets_received_; |
michael@0 | 365 | rtp_bytes_received_ += bytes; |
michael@0 | 366 | if (!(rtp_packets_received_ % 100)) { |
michael@0 | 367 | MOZ_MTLOG(ML_INFO, "RTP received packet count for " << description_ |
michael@0 | 368 | << " Pipeline " << static_cast<void *>(this) |
michael@0 | 369 | << " Flow : " << static_cast<void *>(rtp_.transport_) |
michael@0 | 370 | << ": " << rtp_packets_received_ |
michael@0 | 371 | << " (" << rtp_bytes_received_ << " bytes)"); |
michael@0 | 372 | } |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | void MediaPipeline::increment_rtcp_packets_received() { |
michael@0 | 376 | ++rtcp_packets_received_; |
michael@0 | 377 | if (!(rtcp_packets_received_ % 100)) { |
michael@0 | 378 | MOZ_MTLOG(ML_INFO, "RTCP received packet count for " << description_ |
michael@0 | 379 | << " Pipeline " << static_cast<void *>(this) |
michael@0 | 380 | << " Flow : " << static_cast<void *>(rtcp_.transport_) |
michael@0 | 381 | << ": " << rtcp_packets_received_); |
michael@0 | 382 | } |
michael@0 | 383 | } |
michael@0 | 384 | |
michael@0 | 385 | void MediaPipeline::RtpPacketReceived(TransportLayer *layer, |
michael@0 | 386 | const unsigned char *data, |
michael@0 | 387 | size_t len) { |
michael@0 | 388 | if (!transport_->pipeline()) { |
michael@0 | 389 | MOZ_MTLOG(ML_ERROR, "Discarding incoming packet; transport disconnected"); |
michael@0 | 390 | return; |
michael@0 | 391 | } |
michael@0 | 392 | |
michael@0 | 393 | if (!conduit_) { |
michael@0 | 394 | MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; media disconnected"); |
michael@0 | 395 | return; |
michael@0 | 396 | } |
michael@0 | 397 | |
michael@0 | 398 | TransportInfo* info = &rtp_; |
michael@0 | 399 | |
michael@0 | 400 | if (possible_bundle_rtp_ && |
michael@0 | 401 | possible_bundle_rtp_->transport_->Contains(layer)) { |
michael@0 | 402 | // Received this on our possible bundle transport. Override info. |
michael@0 | 403 | info = possible_bundle_rtp_; |
michael@0 | 404 | } |
michael@0 | 405 | |
michael@0 | 406 | // TODO(bcampen@mozilla.com): Can either of these actually happen? If not, |
michael@0 | 407 | // the info variable can be removed, and this function gets simpler. |
michael@0 | 408 | if (info->state_ != MP_OPEN) { |
michael@0 | 409 | MOZ_MTLOG(ML_ERROR, "Discarding incoming packet; pipeline not open"); |
michael@0 | 410 | return; |
michael@0 | 411 | } |
michael@0 | 412 | |
michael@0 | 413 | if (info->transport_->state() != TransportLayer::TS_OPEN) { |
michael@0 | 414 | MOZ_MTLOG(ML_ERROR, "Discarding incoming packet; transport not open"); |
michael@0 | 415 | return; |
michael@0 | 416 | } |
michael@0 | 417 | |
michael@0 | 418 | // This should never happen. |
michael@0 | 419 | MOZ_ASSERT(info->recv_srtp_); |
michael@0 | 420 | |
michael@0 | 421 | if (direction_ == TRANSMIT) { |
michael@0 | 422 | return; |
michael@0 | 423 | } |
michael@0 | 424 | |
michael@0 | 425 | if (possible_bundle_rtp_ && (info == &rtp_)) { |
michael@0 | 426 | // We were not sure we would be using rtp_ or possible_bundle_rtp_, but we |
michael@0 | 427 | // have just received traffic that clears this up. |
michael@0 | 428 | // Don't let our filter prevent us from noticing this, if the filter is |
michael@0 | 429 | // incomplete (ie; no SSRCs in remote SDP, or no remote SDP at all). |
michael@0 | 430 | SetUsingBundle_s(false); |
michael@0 | 431 | MOZ_MTLOG(ML_INFO, "Ruled out the possibility that we're receiving bundle " |
michael@0 | 432 | "for " << description_); |
michael@0 | 433 | // TODO(bcampen@mozilla.com): Might be nice to detect when every |
michael@0 | 434 | // MediaPipeline but the master has determined that it isn't doing bundle, |
michael@0 | 435 | // since that means the master isn't doing bundle either. We could maybe |
michael@0 | 436 | // do this by putting some refcounted dummy variable in the filters, and |
michael@0 | 437 | // checking the value of the refcount. It is not clear whether this is |
michael@0 | 438 | // going to be useful in practice. |
michael@0 | 439 | } |
michael@0 | 440 | |
michael@0 | 441 | if (!len) { |
michael@0 | 442 | return; |
michael@0 | 443 | } |
michael@0 | 444 | |
michael@0 | 445 | // Filter out everything but RTP/RTCP |
michael@0 | 446 | if (data[0] < 128 || data[0] > 191) { |
michael@0 | 447 | return; |
michael@0 | 448 | } |
michael@0 | 449 | |
michael@0 | 450 | if (filter_) { |
michael@0 | 451 | webrtc::RTPHeader header; |
michael@0 | 452 | if (!rtp_parser_->Parse(data, len, &header) || |
michael@0 | 453 | !filter_->Filter(header)) { |
michael@0 | 454 | return; |
michael@0 | 455 | } |
michael@0 | 456 | } |
michael@0 | 457 | |
michael@0 | 458 | if (possible_bundle_rtp_) { |
michael@0 | 459 | // Just got traffic that passed our filter on the potential bundle |
michael@0 | 460 | // transport. Must be doing bundle. |
michael@0 | 461 | SetUsingBundle_s(true); |
michael@0 | 462 | MOZ_MTLOG(ML_INFO, "Confirmed the possibility that we're receiving bundle"); |
michael@0 | 463 | } |
michael@0 | 464 | |
michael@0 | 465 | // Everything is decided now; just use rtp_ |
michael@0 | 466 | MOZ_ASSERT(!possible_bundle_rtp_); |
michael@0 | 467 | MOZ_ASSERT(!possible_bundle_rtcp_); |
michael@0 | 468 | |
michael@0 | 469 | // Make a copy rather than cast away constness |
michael@0 | 470 | ScopedDeletePtr<unsigned char> inner_data( |
michael@0 | 471 | new unsigned char[len]); |
michael@0 | 472 | memcpy(inner_data, data, len); |
michael@0 | 473 | int out_len = 0; |
michael@0 | 474 | nsresult res = rtp_.recv_srtp_->UnprotectRtp(inner_data, |
michael@0 | 475 | len, len, &out_len); |
michael@0 | 476 | if (!NS_SUCCEEDED(res)) { |
michael@0 | 477 | char tmp[16]; |
michael@0 | 478 | |
michael@0 | 479 | PR_snprintf(tmp, sizeof(tmp), "%.2x %.2x %.2x %.2x", |
michael@0 | 480 | inner_data[0], |
michael@0 | 481 | inner_data[1], |
michael@0 | 482 | inner_data[2], |
michael@0 | 483 | inner_data[3]); |
michael@0 | 484 | |
michael@0 | 485 | MOZ_MTLOG(ML_NOTICE, "Error unprotecting RTP in " << description_ |
michael@0 | 486 | << "len= " << len << "[" << tmp << "...]"); |
michael@0 | 487 | |
michael@0 | 488 | return; |
michael@0 | 489 | } |
michael@0 | 490 | increment_rtp_packets_received(out_len); |
michael@0 | 491 | |
michael@0 | 492 | (void)conduit_->ReceivedRTPPacket(inner_data, out_len); // Ignore error codes |
michael@0 | 493 | } |
michael@0 | 494 | |
michael@0 | 495 | void MediaPipeline::RtcpPacketReceived(TransportLayer *layer, |
michael@0 | 496 | const unsigned char *data, |
michael@0 | 497 | size_t len) { |
michael@0 | 498 | if (!transport_->pipeline()) { |
michael@0 | 499 | MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; transport disconnected"); |
michael@0 | 500 | return; |
michael@0 | 501 | } |
michael@0 | 502 | |
michael@0 | 503 | if (!conduit_) { |
michael@0 | 504 | MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; media disconnected"); |
michael@0 | 505 | return; |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | TransportInfo* info = &rtcp_; |
michael@0 | 509 | if (possible_bundle_rtcp_ && |
michael@0 | 510 | possible_bundle_rtcp_->transport_->Contains(layer)) { |
michael@0 | 511 | info = possible_bundle_rtcp_; |
michael@0 | 512 | } |
michael@0 | 513 | |
michael@0 | 514 | if (info->state_ != MP_OPEN) { |
michael@0 | 515 | MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; pipeline not open"); |
michael@0 | 516 | return; |
michael@0 | 517 | } |
michael@0 | 518 | |
michael@0 | 519 | if (info->transport_->state() != TransportLayer::TS_OPEN) { |
michael@0 | 520 | MOZ_MTLOG(ML_ERROR, "Discarding incoming packet; transport not open"); |
michael@0 | 521 | return; |
michael@0 | 522 | } |
michael@0 | 523 | |
michael@0 | 524 | if (possible_bundle_rtp_ && (info == &rtcp_)) { |
michael@0 | 525 | // We have offered bundle, and received our first packet on a non-bundle |
michael@0 | 526 | // address. We are definitely not using the bundle address. |
michael@0 | 527 | SetUsingBundle_s(false); |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | if (!len) { |
michael@0 | 531 | return; |
michael@0 | 532 | } |
michael@0 | 533 | |
michael@0 | 534 | // Filter out everything but RTP/RTCP |
michael@0 | 535 | if (data[0] < 128 || data[0] > 191) { |
michael@0 | 536 | return; |
michael@0 | 537 | } |
michael@0 | 538 | |
michael@0 | 539 | MediaPipelineFilter::Result filter_result = MediaPipelineFilter::PASS; |
michael@0 | 540 | if (filter_) { |
michael@0 | 541 | filter_result = filter_->FilterRTCP(data, len); |
michael@0 | 542 | if (filter_result == MediaPipelineFilter::FAIL) { |
michael@0 | 543 | return; |
michael@0 | 544 | } |
michael@0 | 545 | } |
michael@0 | 546 | |
michael@0 | 547 | if (filter_result == MediaPipelineFilter::PASS && possible_bundle_rtp_) { |
michael@0 | 548 | // Just got traffic that passed our filter on the potential bundle |
michael@0 | 549 | // transport. Must be doing bundle. |
michael@0 | 550 | SetUsingBundle_s(true); |
michael@0 | 551 | } |
michael@0 | 552 | |
michael@0 | 553 | // We continue using info here, since it is possible that the filter did not |
michael@0 | 554 | // support the payload type (ie; returned MediaPipelineFilter::UNSUPPORTED). |
michael@0 | 555 | // In this case, we just let it pass, and hope the webrtc.org code does |
michael@0 | 556 | // something sane. |
michael@0 | 557 | increment_rtcp_packets_received(); |
michael@0 | 558 | |
michael@0 | 559 | MOZ_ASSERT(info->recv_srtp_); // This should never happen |
michael@0 | 560 | |
michael@0 | 561 | // Make a copy rather than cast away constness |
michael@0 | 562 | ScopedDeletePtr<unsigned char> inner_data( |
michael@0 | 563 | new unsigned char[len]); |
michael@0 | 564 | memcpy(inner_data, data, len); |
michael@0 | 565 | int out_len; |
michael@0 | 566 | |
michael@0 | 567 | nsresult res = info->recv_srtp_->UnprotectRtcp(inner_data, |
michael@0 | 568 | len, |
michael@0 | 569 | len, |
michael@0 | 570 | &out_len); |
michael@0 | 571 | |
michael@0 | 572 | if (!NS_SUCCEEDED(res)) |
michael@0 | 573 | return; |
michael@0 | 574 | |
michael@0 | 575 | (void)conduit_->ReceivedRTCPPacket(inner_data, out_len); // Ignore error codes |
michael@0 | 576 | } |
michael@0 | 577 | |
michael@0 | 578 | bool MediaPipeline::IsRtp(const unsigned char *data, size_t len) { |
michael@0 | 579 | if (len < 2) |
michael@0 | 580 | return false; |
michael@0 | 581 | |
michael@0 | 582 | // Check if this is a RTCP packet. Logic based on the types listed in |
michael@0 | 583 | // media/webrtc/trunk/src/modules/rtp_rtcp/source/rtp_utility.cc |
michael@0 | 584 | |
michael@0 | 585 | // Anything outside this range is RTP. |
michael@0 | 586 | if ((data[1] < 192) || (data[1] > 207)) |
michael@0 | 587 | return true; |
michael@0 | 588 | |
michael@0 | 589 | if (data[1] == 192) // FIR |
michael@0 | 590 | return false; |
michael@0 | 591 | |
michael@0 | 592 | if (data[1] == 193) // NACK, but could also be RTP. This makes us sad |
michael@0 | 593 | return true; // but it's how webrtc.org behaves. |
michael@0 | 594 | |
michael@0 | 595 | if (data[1] == 194) |
michael@0 | 596 | return true; |
michael@0 | 597 | |
michael@0 | 598 | if (data[1] == 195) // IJ. |
michael@0 | 599 | return false; |
michael@0 | 600 | |
michael@0 | 601 | if ((data[1] > 195) && (data[1] < 200)) // the > 195 is redundant |
michael@0 | 602 | return true; |
michael@0 | 603 | |
michael@0 | 604 | if ((data[1] >= 200) && (data[1] <= 207)) // SR, RR, SDES, BYE, |
michael@0 | 605 | return false; // APP, RTPFB, PSFB, XR |
michael@0 | 606 | |
michael@0 | 607 | MOZ_ASSERT(false); // Not reached, belt and suspenders. |
michael@0 | 608 | return true; |
michael@0 | 609 | } |
michael@0 | 610 | |
michael@0 | 611 | void MediaPipeline::PacketReceived(TransportLayer *layer, |
michael@0 | 612 | const unsigned char *data, |
michael@0 | 613 | size_t len) { |
michael@0 | 614 | if (!transport_->pipeline()) { |
michael@0 | 615 | MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; transport disconnected"); |
michael@0 | 616 | return; |
michael@0 | 617 | } |
michael@0 | 618 | |
michael@0 | 619 | if (IsRtp(data, len)) { |
michael@0 | 620 | RtpPacketReceived(layer, data, len); |
michael@0 | 621 | } else { |
michael@0 | 622 | RtcpPacketReceived(layer, data, len); |
michael@0 | 623 | } |
michael@0 | 624 | } |
michael@0 | 625 | |
michael@0 | 626 | nsresult MediaPipelineTransmit::Init() { |
michael@0 | 627 | char track_id_string[11]; |
michael@0 | 628 | ASSERT_ON_THREAD(main_thread_); |
michael@0 | 629 | |
michael@0 | 630 | // We can replace this when we are allowed to do streams or std::to_string |
michael@0 | 631 | PR_snprintf(track_id_string, sizeof(track_id_string), "%d", track_id_); |
michael@0 | 632 | |
michael@0 | 633 | description_ = pc_ + "| "; |
michael@0 | 634 | description_ += conduit_->type() == MediaSessionConduit::AUDIO ? |
michael@0 | 635 | "Transmit audio[" : "Transmit video["; |
michael@0 | 636 | description_ += track_id_string; |
michael@0 | 637 | description_ += "]"; |
michael@0 | 638 | |
michael@0 | 639 | // TODO(ekr@rtfm.com): Check for errors |
michael@0 | 640 | MOZ_MTLOG(ML_DEBUG, "Attaching pipeline to stream " |
michael@0 | 641 | << static_cast<void *>(stream_) << " conduit type=" << |
michael@0 | 642 | (conduit_->type() == MediaSessionConduit::AUDIO ?"audio":"video")); |
michael@0 | 643 | |
michael@0 | 644 | stream_->AddListener(listener_); |
michael@0 | 645 | |
michael@0 | 646 | // Is this a gUM mediastream? If so, also register the Listener directly with |
michael@0 | 647 | // the SourceMediaStream that's attached to the TrackUnion so we can get direct |
michael@0 | 648 | // unqueued (and not resampled) data |
michael@0 | 649 | if (domstream_->AddDirectListener(listener_)) { |
michael@0 | 650 | listener_->direct_connect_ = true; |
michael@0 | 651 | } |
michael@0 | 652 | |
michael@0 | 653 | return MediaPipeline::Init(); |
michael@0 | 654 | } |
michael@0 | 655 | |
michael@0 | 656 | nsresult MediaPipelineTransmit::TransportReady_s(TransportInfo &info) { |
michael@0 | 657 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 658 | // Call base ready function. |
michael@0 | 659 | MediaPipeline::TransportReady_s(info); |
michael@0 | 660 | |
michael@0 | 661 | // Should not be set for a transmitter |
michael@0 | 662 | MOZ_ASSERT(!possible_bundle_rtp_); |
michael@0 | 663 | if (&info == &rtp_) { |
michael@0 | 664 | // TODO(ekr@rtfm.com): Move onto MSG thread. |
michael@0 | 665 | listener_->SetActive(true); |
michael@0 | 666 | } |
michael@0 | 667 | |
michael@0 | 668 | return NS_OK; |
michael@0 | 669 | } |
michael@0 | 670 | |
michael@0 | 671 | void MediaPipeline::DisconnectTransport_s(TransportInfo &info) { |
michael@0 | 672 | MOZ_ASSERT(info.transport_); |
michael@0 | 673 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 674 | |
michael@0 | 675 | info.transport_->SignalStateChange.disconnect(this); |
michael@0 | 676 | // We do this even if we're a transmitter, since we are still possibly |
michael@0 | 677 | // registered to receive RTCP. |
michael@0 | 678 | TransportLayerDtls *dtls = static_cast<TransportLayerDtls *>( |
michael@0 | 679 | info.transport_->GetLayer(TransportLayerDtls::ID())); |
michael@0 | 680 | MOZ_ASSERT(dtls); // DTLS is mandatory |
michael@0 | 681 | MOZ_ASSERT(dtls->downward()); |
michael@0 | 682 | dtls->downward()->SignalPacketReceived.disconnect(this); |
michael@0 | 683 | } |
michael@0 | 684 | |
michael@0 | 685 | nsresult MediaPipeline::ConnectTransport_s(TransportInfo &info) { |
michael@0 | 686 | MOZ_ASSERT(info.transport_); |
michael@0 | 687 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 688 | |
michael@0 | 689 | // Look to see if the transport is ready |
michael@0 | 690 | if (info.transport_->state() == TransportLayer::TS_OPEN) { |
michael@0 | 691 | nsresult res = TransportReady_s(info); |
michael@0 | 692 | if (NS_FAILED(res)) { |
michael@0 | 693 | MOZ_MTLOG(ML_ERROR, "Error calling TransportReady(); res=" |
michael@0 | 694 | << static_cast<uint32_t>(res) << " in " << __FUNCTION__); |
michael@0 | 695 | return res; |
michael@0 | 696 | } |
michael@0 | 697 | } else if (info.transport_->state() == TransportLayer::TS_ERROR) { |
michael@0 | 698 | MOZ_MTLOG(ML_ERROR, ToString(info.type_) |
michael@0 | 699 | << "transport is already in error state"); |
michael@0 | 700 | TransportFailed_s(info); |
michael@0 | 701 | return NS_ERROR_FAILURE; |
michael@0 | 702 | } |
michael@0 | 703 | |
michael@0 | 704 | info.transport_->SignalStateChange.connect(this, |
michael@0 | 705 | &MediaPipeline::StateChange); |
michael@0 | 706 | |
michael@0 | 707 | return NS_OK; |
michael@0 | 708 | } |
michael@0 | 709 | |
michael@0 | 710 | MediaPipeline::TransportInfo* MediaPipeline::GetTransportInfo_s( |
michael@0 | 711 | TransportFlow *flow) { |
michael@0 | 712 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 713 | if (flow == rtp_.transport_) { |
michael@0 | 714 | return &rtp_; |
michael@0 | 715 | } |
michael@0 | 716 | |
michael@0 | 717 | if (flow == rtcp_.transport_) { |
michael@0 | 718 | return &rtcp_; |
michael@0 | 719 | } |
michael@0 | 720 | |
michael@0 | 721 | if (possible_bundle_rtp_) { |
michael@0 | 722 | if (flow == possible_bundle_rtp_->transport_) { |
michael@0 | 723 | return possible_bundle_rtp_; |
michael@0 | 724 | } |
michael@0 | 725 | |
michael@0 | 726 | if (flow == possible_bundle_rtcp_->transport_) { |
michael@0 | 727 | return possible_bundle_rtcp_; |
michael@0 | 728 | } |
michael@0 | 729 | } |
michael@0 | 730 | |
michael@0 | 731 | return nullptr; |
michael@0 | 732 | } |
michael@0 | 733 | |
michael@0 | 734 | void MediaPipeline::SetUsingBundle_s(bool decision) { |
michael@0 | 735 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 736 | // Note: This can be called either because of events on the STS thread, or |
michael@0 | 737 | // by events on the main thread (ie; receiving a remote description). It is |
michael@0 | 738 | // best to be careful of races here, so don't assume that transports are open. |
michael@0 | 739 | if (!possible_bundle_rtp_) { |
michael@0 | 740 | if (!decision) { |
michael@0 | 741 | // This can happen on the master pipeline. |
michael@0 | 742 | filter_ = nullptr; |
michael@0 | 743 | } |
michael@0 | 744 | return; |
michael@0 | 745 | } |
michael@0 | 746 | |
michael@0 | 747 | if (direction_ == RECEIVE) { |
michael@0 | 748 | if (decision) { |
michael@0 | 749 | MOZ_MTLOG(ML_INFO, "Non-master pipeline confirmed bundle for " |
michael@0 | 750 | << description_); |
michael@0 | 751 | // We're doing bundle. Release the unused flows, and copy the ones we |
michael@0 | 752 | // are using into the less wishy-washy members. |
michael@0 | 753 | DisconnectTransport_s(rtp_); |
michael@0 | 754 | DisconnectTransport_s(rtcp_); |
michael@0 | 755 | rtp_ = *possible_bundle_rtp_; |
michael@0 | 756 | rtcp_ = *possible_bundle_rtcp_; |
michael@0 | 757 | } else { |
michael@0 | 758 | MOZ_MTLOG(ML_INFO, "Non-master pipeline confirmed no bundle for " |
michael@0 | 759 | << description_); |
michael@0 | 760 | // We are not doing bundle |
michael@0 | 761 | DisconnectTransport_s(*possible_bundle_rtp_); |
michael@0 | 762 | DisconnectTransport_s(*possible_bundle_rtcp_); |
michael@0 | 763 | filter_ = nullptr; |
michael@0 | 764 | } |
michael@0 | 765 | |
michael@0 | 766 | // We are no longer in an ambiguous state. |
michael@0 | 767 | possible_bundle_rtp_ = nullptr; |
michael@0 | 768 | possible_bundle_rtcp_ = nullptr; |
michael@0 | 769 | } |
michael@0 | 770 | } |
michael@0 | 771 | |
michael@0 | 772 | MediaPipelineFilter* MediaPipeline::UpdateFilterFromRemoteDescription_s( |
michael@0 | 773 | nsAutoPtr<MediaPipelineFilter> filter) { |
michael@0 | 774 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 775 | // This is only supposed to relax the filter. Relaxing a missing filter is |
michael@0 | 776 | // not possible. |
michael@0 | 777 | MOZ_ASSERT(filter_); |
michael@0 | 778 | |
michael@0 | 779 | if (!filter) { |
michael@0 | 780 | filter_ = nullptr; |
michael@0 | 781 | } else { |
michael@0 | 782 | filter_->IncorporateRemoteDescription(*filter); |
michael@0 | 783 | } |
michael@0 | 784 | |
michael@0 | 785 | return filter_.get(); |
michael@0 | 786 | } |
michael@0 | 787 | |
michael@0 | 788 | nsresult MediaPipeline::PipelineTransport::SendRtpPacket( |
michael@0 | 789 | const void *data, int len) { |
michael@0 | 790 | |
michael@0 | 791 | nsAutoPtr<DataBuffer> buf(new DataBuffer(static_cast<const uint8_t *>(data), |
michael@0 | 792 | len)); |
michael@0 | 793 | |
michael@0 | 794 | RUN_ON_THREAD(sts_thread_, |
michael@0 | 795 | WrapRunnable( |
michael@0 | 796 | RefPtr<MediaPipeline::PipelineTransport>(this), |
michael@0 | 797 | &MediaPipeline::PipelineTransport::SendRtpPacket_s, |
michael@0 | 798 | buf), |
michael@0 | 799 | NS_DISPATCH_NORMAL); |
michael@0 | 800 | |
michael@0 | 801 | return NS_OK; |
michael@0 | 802 | } |
michael@0 | 803 | |
michael@0 | 804 | nsresult MediaPipeline::PipelineTransport::SendRtpPacket_s( |
michael@0 | 805 | nsAutoPtr<DataBuffer> data) { |
michael@0 | 806 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 807 | if (!pipeline_) |
michael@0 | 808 | return NS_OK; // Detached |
michael@0 | 809 | |
michael@0 | 810 | if (!pipeline_->rtp_.send_srtp_) { |
michael@0 | 811 | MOZ_MTLOG(ML_DEBUG, "Couldn't write RTP packet; SRTP not set up yet"); |
michael@0 | 812 | return NS_OK; |
michael@0 | 813 | } |
michael@0 | 814 | |
michael@0 | 815 | MOZ_ASSERT(pipeline_->rtp_.transport_); |
michael@0 | 816 | NS_ENSURE_TRUE(pipeline_->rtp_.transport_, NS_ERROR_NULL_POINTER); |
michael@0 | 817 | |
michael@0 | 818 | // libsrtp enciphers in place, so we need a new, big enough |
michael@0 | 819 | // buffer. |
michael@0 | 820 | // XXX. allocates and deletes one buffer per packet sent. |
michael@0 | 821 | // Bug 822129 |
michael@0 | 822 | int max_len = data->len() + SRTP_MAX_EXPANSION; |
michael@0 | 823 | ScopedDeletePtr<unsigned char> inner_data( |
michael@0 | 824 | new unsigned char[max_len]); |
michael@0 | 825 | memcpy(inner_data, data->data(), data->len()); |
michael@0 | 826 | |
michael@0 | 827 | int out_len; |
michael@0 | 828 | nsresult res = pipeline_->rtp_.send_srtp_->ProtectRtp(inner_data, |
michael@0 | 829 | data->len(), |
michael@0 | 830 | max_len, |
michael@0 | 831 | &out_len); |
michael@0 | 832 | if (!NS_SUCCEEDED(res)) |
michael@0 | 833 | return res; |
michael@0 | 834 | |
michael@0 | 835 | pipeline_->increment_rtp_packets_sent(out_len); |
michael@0 | 836 | return pipeline_->SendPacket(pipeline_->rtp_.transport_, inner_data, |
michael@0 | 837 | out_len); |
michael@0 | 838 | } |
michael@0 | 839 | |
michael@0 | 840 | nsresult MediaPipeline::PipelineTransport::SendRtcpPacket( |
michael@0 | 841 | const void *data, int len) { |
michael@0 | 842 | |
michael@0 | 843 | nsAutoPtr<DataBuffer> buf(new DataBuffer(static_cast<const uint8_t *>(data), |
michael@0 | 844 | len)); |
michael@0 | 845 | |
michael@0 | 846 | RUN_ON_THREAD(sts_thread_, |
michael@0 | 847 | WrapRunnable( |
michael@0 | 848 | RefPtr<MediaPipeline::PipelineTransport>(this), |
michael@0 | 849 | &MediaPipeline::PipelineTransport::SendRtcpPacket_s, |
michael@0 | 850 | buf), |
michael@0 | 851 | NS_DISPATCH_NORMAL); |
michael@0 | 852 | |
michael@0 | 853 | return NS_OK; |
michael@0 | 854 | } |
michael@0 | 855 | |
michael@0 | 856 | nsresult MediaPipeline::PipelineTransport::SendRtcpPacket_s( |
michael@0 | 857 | nsAutoPtr<DataBuffer> data) { |
michael@0 | 858 | ASSERT_ON_THREAD(sts_thread_); |
michael@0 | 859 | if (!pipeline_) |
michael@0 | 860 | return NS_OK; // Detached |
michael@0 | 861 | |
michael@0 | 862 | if (!pipeline_->rtcp_.send_srtp_) { |
michael@0 | 863 | MOZ_MTLOG(ML_DEBUG, "Couldn't write RTCP packet; SRTCP not set up yet"); |
michael@0 | 864 | return NS_OK; |
michael@0 | 865 | } |
michael@0 | 866 | |
michael@0 | 867 | MOZ_ASSERT(pipeline_->rtcp_.transport_); |
michael@0 | 868 | NS_ENSURE_TRUE(pipeline_->rtcp_.transport_, NS_ERROR_NULL_POINTER); |
michael@0 | 869 | |
michael@0 | 870 | // libsrtp enciphers in place, so we need a new, big enough |
michael@0 | 871 | // buffer. |
michael@0 | 872 | // XXX. allocates and deletes one buffer per packet sent. |
michael@0 | 873 | // Bug 822129. |
michael@0 | 874 | int max_len = data->len() + SRTP_MAX_EXPANSION; |
michael@0 | 875 | ScopedDeletePtr<unsigned char> inner_data( |
michael@0 | 876 | new unsigned char[max_len]); |
michael@0 | 877 | memcpy(inner_data, data->data(), data->len()); |
michael@0 | 878 | |
michael@0 | 879 | int out_len; |
michael@0 | 880 | nsresult res = pipeline_->rtcp_.send_srtp_->ProtectRtcp(inner_data, |
michael@0 | 881 | data->len(), |
michael@0 | 882 | max_len, |
michael@0 | 883 | &out_len); |
michael@0 | 884 | |
michael@0 | 885 | if (!NS_SUCCEEDED(res)) |
michael@0 | 886 | return res; |
michael@0 | 887 | |
michael@0 | 888 | pipeline_->increment_rtcp_packets_sent(); |
michael@0 | 889 | return pipeline_->SendPacket(pipeline_->rtcp_.transport_, inner_data, |
michael@0 | 890 | out_len); |
michael@0 | 891 | } |
michael@0 | 892 | |
michael@0 | 893 | // Called if we're attached with AddDirectListener() |
michael@0 | 894 | void MediaPipelineTransmit::PipelineListener:: |
michael@0 | 895 | NotifyRealtimeData(MediaStreamGraph* graph, TrackID tid, |
michael@0 | 896 | TrackRate rate, |
michael@0 | 897 | TrackTicks offset, |
michael@0 | 898 | uint32_t events, |
michael@0 | 899 | const MediaSegment& media) { |
michael@0 | 900 | MOZ_MTLOG(ML_DEBUG, "MediaPipeline::NotifyRealtimeData()"); |
michael@0 | 901 | |
michael@0 | 902 | NewData(graph, tid, rate, offset, events, media); |
michael@0 | 903 | } |
michael@0 | 904 | |
michael@0 | 905 | void MediaPipelineTransmit::PipelineListener:: |
michael@0 | 906 | NotifyQueuedTrackChanges(MediaStreamGraph* graph, TrackID tid, |
michael@0 | 907 | TrackRate rate, |
michael@0 | 908 | TrackTicks offset, |
michael@0 | 909 | uint32_t events, |
michael@0 | 910 | const MediaSegment& queued_media) { |
michael@0 | 911 | MOZ_MTLOG(ML_DEBUG, "MediaPipeline::NotifyQueuedTrackChanges()"); |
michael@0 | 912 | |
michael@0 | 913 | // ignore non-direct data if we're also getting direct data |
michael@0 | 914 | if (!direct_connect_) { |
michael@0 | 915 | NewData(graph, tid, rate, offset, events, queued_media); |
michael@0 | 916 | } |
michael@0 | 917 | } |
michael@0 | 918 | |
michael@0 | 919 | void MediaPipelineTransmit::PipelineListener:: |
michael@0 | 920 | NewData(MediaStreamGraph* graph, TrackID tid, |
michael@0 | 921 | TrackRate rate, |
michael@0 | 922 | TrackTicks offset, |
michael@0 | 923 | uint32_t events, |
michael@0 | 924 | const MediaSegment& media) { |
michael@0 | 925 | if (!active_) { |
michael@0 | 926 | MOZ_MTLOG(ML_DEBUG, "Discarding packets because transport not ready"); |
michael@0 | 927 | return; |
michael@0 | 928 | } |
michael@0 | 929 | |
michael@0 | 930 | // TODO(ekr@rtfm.com): For now assume that we have only one |
michael@0 | 931 | // track type and it's destined for us |
michael@0 | 932 | // See bug 784517 |
michael@0 | 933 | if (media.GetType() == MediaSegment::AUDIO) { |
michael@0 | 934 | if (conduit_->type() != MediaSessionConduit::AUDIO) { |
michael@0 | 935 | // Ignore data in case we have a muxed stream |
michael@0 | 936 | return; |
michael@0 | 937 | } |
michael@0 | 938 | AudioSegment* audio = const_cast<AudioSegment *>( |
michael@0 | 939 | static_cast<const AudioSegment *>(&media)); |
michael@0 | 940 | |
michael@0 | 941 | AudioSegment::ChunkIterator iter(*audio); |
michael@0 | 942 | while(!iter.IsEnded()) { |
michael@0 | 943 | ProcessAudioChunk(static_cast<AudioSessionConduit*>(conduit_.get()), |
michael@0 | 944 | rate, *iter); |
michael@0 | 945 | iter.Next(); |
michael@0 | 946 | } |
michael@0 | 947 | } else if (media.GetType() == MediaSegment::VIDEO) { |
michael@0 | 948 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 949 | if (conduit_->type() != MediaSessionConduit::VIDEO) { |
michael@0 | 950 | // Ignore data in case we have a muxed stream |
michael@0 | 951 | return; |
michael@0 | 952 | } |
michael@0 | 953 | VideoSegment* video = const_cast<VideoSegment *>( |
michael@0 | 954 | static_cast<const VideoSegment *>(&media)); |
michael@0 | 955 | |
michael@0 | 956 | VideoSegment::ChunkIterator iter(*video); |
michael@0 | 957 | while(!iter.IsEnded()) { |
michael@0 | 958 | ProcessVideoChunk(static_cast<VideoSessionConduit*>(conduit_.get()), |
michael@0 | 959 | rate, *iter); |
michael@0 | 960 | iter.Next(); |
michael@0 | 961 | } |
michael@0 | 962 | #endif |
michael@0 | 963 | } else { |
michael@0 | 964 | // Ignore |
michael@0 | 965 | } |
michael@0 | 966 | } |
michael@0 | 967 | |
michael@0 | 968 | void MediaPipelineTransmit::PipelineListener::ProcessAudioChunk( |
michael@0 | 969 | AudioSessionConduit *conduit, |
michael@0 | 970 | TrackRate rate, |
michael@0 | 971 | AudioChunk& chunk) { |
michael@0 | 972 | // TODO(ekr@rtfm.com): Do more than one channel |
michael@0 | 973 | nsAutoArrayPtr<int16_t> samples(new int16_t[chunk.mDuration]); |
michael@0 | 974 | |
michael@0 | 975 | if (chunk.mBuffer) { |
michael@0 | 976 | switch (chunk.mBufferFormat) { |
michael@0 | 977 | case AUDIO_FORMAT_FLOAT32: |
michael@0 | 978 | { |
michael@0 | 979 | const float* buf = static_cast<const float *>(chunk.mChannelData[0]); |
michael@0 | 980 | ConvertAudioSamplesWithScale(buf, static_cast<int16_t*>(samples), |
michael@0 | 981 | chunk.mDuration, chunk.mVolume); |
michael@0 | 982 | } |
michael@0 | 983 | break; |
michael@0 | 984 | case AUDIO_FORMAT_S16: |
michael@0 | 985 | { |
michael@0 | 986 | const short* buf = static_cast<const short *>(chunk.mChannelData[0]); |
michael@0 | 987 | ConvertAudioSamplesWithScale(buf, samples, chunk.mDuration, chunk.mVolume); |
michael@0 | 988 | } |
michael@0 | 989 | break; |
michael@0 | 990 | case AUDIO_FORMAT_SILENCE: |
michael@0 | 991 | memset(samples, 0, chunk.mDuration * sizeof(samples[0])); |
michael@0 | 992 | break; |
michael@0 | 993 | default: |
michael@0 | 994 | MOZ_ASSERT(PR_FALSE); |
michael@0 | 995 | return; |
michael@0 | 996 | break; |
michael@0 | 997 | } |
michael@0 | 998 | } else { |
michael@0 | 999 | // This means silence. |
michael@0 | 1000 | memset(samples, 0, chunk.mDuration * sizeof(samples[0])); |
michael@0 | 1001 | } |
michael@0 | 1002 | |
michael@0 | 1003 | MOZ_ASSERT(!(rate%100)); // rate should be a multiple of 100 |
michael@0 | 1004 | |
michael@0 | 1005 | // Check if the rate has changed since the last time we came through |
michael@0 | 1006 | // I realize it may be overkill to check if the rate has changed, but |
michael@0 | 1007 | // I believe it is possible (e.g. if we change sources) and it costs us |
michael@0 | 1008 | // very little to handle this case |
michael@0 | 1009 | |
michael@0 | 1010 | if (samplenum_10ms_ != rate/100) { |
michael@0 | 1011 | // Determine number of samples in 10 ms from the rate: |
michael@0 | 1012 | samplenum_10ms_ = rate/100; |
michael@0 | 1013 | // If we switch sample rates (e.g. if we switch codecs), |
michael@0 | 1014 | // we throw away what was in the sample_10ms_buffer at the old rate |
michael@0 | 1015 | samples_10ms_buffer_ = new int16_t[samplenum_10ms_]; |
michael@0 | 1016 | buffer_current_ = 0; |
michael@0 | 1017 | } |
michael@0 | 1018 | |
michael@0 | 1019 | // Vars to handle the non-sunny-day case (where the audio chunks |
michael@0 | 1020 | // we got are not multiples of 10ms OR there were samples left over |
michael@0 | 1021 | // from the last run) |
michael@0 | 1022 | int64_t chunk_remaining; |
michael@0 | 1023 | int64_t tocpy; |
michael@0 | 1024 | int16_t *samples_tmp = samples.get(); |
michael@0 | 1025 | |
michael@0 | 1026 | chunk_remaining = chunk.mDuration; |
michael@0 | 1027 | |
michael@0 | 1028 | MOZ_ASSERT(chunk_remaining >= 0); |
michael@0 | 1029 | |
michael@0 | 1030 | if (buffer_current_) { |
michael@0 | 1031 | tocpy = std::min(chunk_remaining, samplenum_10ms_ - buffer_current_); |
michael@0 | 1032 | memcpy(&samples_10ms_buffer_[buffer_current_], samples_tmp, tocpy * sizeof(int16_t)); |
michael@0 | 1033 | buffer_current_ += tocpy; |
michael@0 | 1034 | samples_tmp += tocpy; |
michael@0 | 1035 | chunk_remaining -= tocpy; |
michael@0 | 1036 | |
michael@0 | 1037 | if (buffer_current_ == samplenum_10ms_) { |
michael@0 | 1038 | // Send out the audio buffer we just finished filling |
michael@0 | 1039 | conduit->SendAudioFrame(samples_10ms_buffer_, samplenum_10ms_, rate, 0); |
michael@0 | 1040 | buffer_current_ = 0; |
michael@0 | 1041 | } else { |
michael@0 | 1042 | // We still don't have enough data to send a buffer |
michael@0 | 1043 | return; |
michael@0 | 1044 | } |
michael@0 | 1045 | } |
michael@0 | 1046 | |
michael@0 | 1047 | // Now send (more) frames if there is more than 10ms of input left |
michael@0 | 1048 | tocpy = (chunk_remaining / samplenum_10ms_) * samplenum_10ms_; |
michael@0 | 1049 | if (tocpy > 0) { |
michael@0 | 1050 | conduit->SendAudioFrame(samples_tmp, tocpy, rate, 0); |
michael@0 | 1051 | samples_tmp += tocpy; |
michael@0 | 1052 | chunk_remaining -= tocpy; |
michael@0 | 1053 | } |
michael@0 | 1054 | // Copy what remains for the next run |
michael@0 | 1055 | |
michael@0 | 1056 | MOZ_ASSERT(chunk_remaining < samplenum_10ms_); |
michael@0 | 1057 | |
michael@0 | 1058 | if (chunk_remaining) { |
michael@0 | 1059 | memcpy(samples_10ms_buffer_, samples_tmp, chunk_remaining * sizeof(int16_t)); |
michael@0 | 1060 | buffer_current_ = chunk_remaining; |
michael@0 | 1061 | } |
michael@0 | 1062 | |
michael@0 | 1063 | } |
michael@0 | 1064 | |
michael@0 | 1065 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 1066 | void MediaPipelineTransmit::PipelineListener::ProcessVideoChunk( |
michael@0 | 1067 | VideoSessionConduit* conduit, |
michael@0 | 1068 | TrackRate rate, |
michael@0 | 1069 | VideoChunk& chunk) { |
michael@0 | 1070 | layers::Image *img = chunk.mFrame.GetImage(); |
michael@0 | 1071 | |
michael@0 | 1072 | // We now need to send the video frame to the other side |
michael@0 | 1073 | if (!img) { |
michael@0 | 1074 | // segment.AppendFrame() allows null images, which show up here as null |
michael@0 | 1075 | return; |
michael@0 | 1076 | } |
michael@0 | 1077 | |
michael@0 | 1078 | gfx::IntSize size = img->GetSize(); |
michael@0 | 1079 | if ((size.width & 1) != 0 || (size.height & 1) != 0) { |
michael@0 | 1080 | MOZ_ASSERT(false, "Can't handle odd-sized images"); |
michael@0 | 1081 | return; |
michael@0 | 1082 | } |
michael@0 | 1083 | |
michael@0 | 1084 | if (chunk.mFrame.GetForceBlack()) { |
michael@0 | 1085 | uint32_t yPlaneLen = size.width*size.height; |
michael@0 | 1086 | uint32_t cbcrPlaneLen = yPlaneLen/2; |
michael@0 | 1087 | uint32_t length = yPlaneLen + cbcrPlaneLen; |
michael@0 | 1088 | |
michael@0 | 1089 | // Send a black image. |
michael@0 | 1090 | nsAutoArrayPtr<uint8_t> pixelData; |
michael@0 | 1091 | static const fallible_t fallible = fallible_t(); |
michael@0 | 1092 | pixelData = new (fallible) uint8_t[length]; |
michael@0 | 1093 | if (pixelData) { |
michael@0 | 1094 | memset(pixelData, 0x10, yPlaneLen); |
michael@0 | 1095 | // Fill Cb/Cr planes |
michael@0 | 1096 | memset(pixelData + yPlaneLen, 0x80, cbcrPlaneLen); |
michael@0 | 1097 | |
michael@0 | 1098 | MOZ_MTLOG(ML_DEBUG, "Sending a black video frame"); |
michael@0 | 1099 | conduit->SendVideoFrame(pixelData, length, size.width, size.height, |
michael@0 | 1100 | mozilla::kVideoI420, 0); |
michael@0 | 1101 | } |
michael@0 | 1102 | return; |
michael@0 | 1103 | } |
michael@0 | 1104 | |
michael@0 | 1105 | // We get passed duplicate frames every ~10ms even if there's no frame change! |
michael@0 | 1106 | int32_t serial = img->GetSerial(); |
michael@0 | 1107 | if (serial == last_img_) { |
michael@0 | 1108 | return; |
michael@0 | 1109 | } |
michael@0 | 1110 | last_img_ = serial; |
michael@0 | 1111 | |
michael@0 | 1112 | ImageFormat format = img->GetFormat(); |
michael@0 | 1113 | #ifdef WEBRTC_GONK |
michael@0 | 1114 | if (format == ImageFormat::GRALLOC_PLANAR_YCBCR) { |
michael@0 | 1115 | layers::GrallocImage *nativeImage = static_cast<layers::GrallocImage*>(img); |
michael@0 | 1116 | android::sp<android::GraphicBuffer> graphicBuffer = nativeImage->GetGraphicBuffer(); |
michael@0 | 1117 | void *basePtr; |
michael@0 | 1118 | graphicBuffer->lock(android::GraphicBuffer::USAGE_SW_READ_MASK, &basePtr); |
michael@0 | 1119 | conduit->SendVideoFrame(static_cast<unsigned char*>(basePtr), |
michael@0 | 1120 | (graphicBuffer->getWidth() * graphicBuffer->getHeight() * 3) / 2, |
michael@0 | 1121 | graphicBuffer->getWidth(), |
michael@0 | 1122 | graphicBuffer->getHeight(), |
michael@0 | 1123 | mozilla::kVideoNV21, 0); |
michael@0 | 1124 | graphicBuffer->unlock(); |
michael@0 | 1125 | } else |
michael@0 | 1126 | #endif |
michael@0 | 1127 | if (format == ImageFormat::PLANAR_YCBCR) { |
michael@0 | 1128 | // Cast away constness b/c some of the accessors are non-const |
michael@0 | 1129 | layers::PlanarYCbCrImage* yuv = |
michael@0 | 1130 | const_cast<layers::PlanarYCbCrImage *>( |
michael@0 | 1131 | static_cast<const layers::PlanarYCbCrImage *>(img)); |
michael@0 | 1132 | // Big-time assumption here that this is all contiguous data coming |
michael@0 | 1133 | // from getUserMedia or other sources. |
michael@0 | 1134 | const layers::PlanarYCbCrData *data = yuv->GetData(); |
michael@0 | 1135 | |
michael@0 | 1136 | uint8_t *y = data->mYChannel; |
michael@0 | 1137 | #ifdef DEBUG |
michael@0 | 1138 | uint8_t *cb = data->mCbChannel; |
michael@0 | 1139 | uint8_t *cr = data->mCrChannel; |
michael@0 | 1140 | #endif |
michael@0 | 1141 | uint32_t width = yuv->GetSize().width; |
michael@0 | 1142 | uint32_t height = yuv->GetSize().height; |
michael@0 | 1143 | uint32_t length = yuv->GetDataSize(); |
michael@0 | 1144 | |
michael@0 | 1145 | // SendVideoFrame only supports contiguous YCrCb 4:2:0 buffers |
michael@0 | 1146 | // Verify it's contiguous and in the right order |
michael@0 | 1147 | MOZ_ASSERT(cb == (y + width*height) && |
michael@0 | 1148 | cr == (cb + width*height/4)); |
michael@0 | 1149 | // XXX Consider making this a non-debug-only check if we ever implement |
michael@0 | 1150 | // any subclasses of PlanarYCbCrImage that allow disjoint buffers such |
michael@0 | 1151 | // that y+3(width*height)/2 might go outside the allocation. |
michael@0 | 1152 | // GrallocImage can have wider strides, and so in some cases |
michael@0 | 1153 | // would encode as garbage. If we need to encode it we'll either want to |
michael@0 | 1154 | // modify SendVideoFrame or copy/move the data in the buffer. |
michael@0 | 1155 | |
michael@0 | 1156 | // OK, pass it on to the conduit |
michael@0 | 1157 | MOZ_MTLOG(ML_DEBUG, "Sending a video frame"); |
michael@0 | 1158 | // Not much for us to do with an error |
michael@0 | 1159 | conduit->SendVideoFrame(y, length, width, height, mozilla::kVideoI420, 0); |
michael@0 | 1160 | } else if(format == ImageFormat::CAIRO_SURFACE) { |
michael@0 | 1161 | layers::CairoImage* rgb = |
michael@0 | 1162 | const_cast<layers::CairoImage *>( |
michael@0 | 1163 | static_cast<const layers::CairoImage *>(img)); |
michael@0 | 1164 | |
michael@0 | 1165 | gfx::IntSize size = rgb->GetSize(); |
michael@0 | 1166 | int half_width = (size.width + 1) >> 1; |
michael@0 | 1167 | int half_height = (size.height + 1) >> 1; |
michael@0 | 1168 | int c_size = half_width * half_height; |
michael@0 | 1169 | int buffer_size = size.width * size.height + 2 * c_size; |
michael@0 | 1170 | uint8* yuv = (uint8*) malloc(buffer_size); |
michael@0 | 1171 | if (!yuv) |
michael@0 | 1172 | return; |
michael@0 | 1173 | |
michael@0 | 1174 | int cb_offset = size.width * size.height; |
michael@0 | 1175 | int cr_offset = cb_offset + c_size; |
michael@0 | 1176 | RefPtr<gfx::SourceSurface> tempSurf = rgb->GetAsSourceSurface(); |
michael@0 | 1177 | RefPtr<gfx::DataSourceSurface> surf = tempSurf->GetDataSurface(); |
michael@0 | 1178 | |
michael@0 | 1179 | switch (surf->GetFormat()) { |
michael@0 | 1180 | case gfx::SurfaceFormat::B8G8R8A8: |
michael@0 | 1181 | case gfx::SurfaceFormat::B8G8R8X8: |
michael@0 | 1182 | libyuv::ARGBToI420(static_cast<uint8*>(surf->GetData()), surf->Stride(), |
michael@0 | 1183 | yuv, size.width, |
michael@0 | 1184 | yuv + cb_offset, half_width, |
michael@0 | 1185 | yuv + cr_offset, half_width, |
michael@0 | 1186 | size.width, size.height); |
michael@0 | 1187 | break; |
michael@0 | 1188 | case gfx::SurfaceFormat::R5G6B5: |
michael@0 | 1189 | libyuv::RGB565ToI420(static_cast<uint8*>(surf->GetData()), surf->Stride(), |
michael@0 | 1190 | yuv, size.width, |
michael@0 | 1191 | yuv + cb_offset, half_width, |
michael@0 | 1192 | yuv + cr_offset, half_width, |
michael@0 | 1193 | size.width, size.height); |
michael@0 | 1194 | break; |
michael@0 | 1195 | default: |
michael@0 | 1196 | MOZ_MTLOG(ML_ERROR, "Unsupported RGB video format"); |
michael@0 | 1197 | MOZ_ASSERT(PR_FALSE); |
michael@0 | 1198 | } |
michael@0 | 1199 | conduit->SendVideoFrame(yuv, buffer_size, size.width, size.height, mozilla::kVideoI420, 0); |
michael@0 | 1200 | } else { |
michael@0 | 1201 | MOZ_MTLOG(ML_ERROR, "Unsupported video format"); |
michael@0 | 1202 | MOZ_ASSERT(PR_FALSE); |
michael@0 | 1203 | return; |
michael@0 | 1204 | } |
michael@0 | 1205 | } |
michael@0 | 1206 | #endif |
michael@0 | 1207 | |
michael@0 | 1208 | nsresult MediaPipelineReceiveAudio::Init() { |
michael@0 | 1209 | char track_id_string[11]; |
michael@0 | 1210 | ASSERT_ON_THREAD(main_thread_); |
michael@0 | 1211 | MOZ_MTLOG(ML_DEBUG, __FUNCTION__); |
michael@0 | 1212 | |
michael@0 | 1213 | // We can replace this when we are allowed to do streams or std::to_string |
michael@0 | 1214 | PR_snprintf(track_id_string, sizeof(track_id_string), "%d", track_id_); |
michael@0 | 1215 | |
michael@0 | 1216 | description_ = pc_ + "| Receive audio["; |
michael@0 | 1217 | description_ += track_id_string; |
michael@0 | 1218 | description_ += "]"; |
michael@0 | 1219 | |
michael@0 | 1220 | listener_->AddSelf(new AudioSegment()); |
michael@0 | 1221 | |
michael@0 | 1222 | return MediaPipelineReceive::Init(); |
michael@0 | 1223 | } |
michael@0 | 1224 | |
michael@0 | 1225 | |
michael@0 | 1226 | // Add a track and listener on the MSG thread using the MSG command queue |
michael@0 | 1227 | static void AddTrackAndListener(MediaStream* source, |
michael@0 | 1228 | TrackID track_id, TrackRate track_rate, |
michael@0 | 1229 | MediaStreamListener* listener, MediaSegment* segment, |
michael@0 | 1230 | const RefPtr<TrackAddedCallback>& completed) { |
michael@0 | 1231 | // This both adds the listener and the track |
michael@0 | 1232 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 1233 | class Message : public ControlMessage { |
michael@0 | 1234 | public: |
michael@0 | 1235 | Message(MediaStream* stream, TrackID track, TrackRate rate, |
michael@0 | 1236 | MediaSegment* segment, MediaStreamListener* listener, |
michael@0 | 1237 | const RefPtr<TrackAddedCallback>& completed) |
michael@0 | 1238 | : ControlMessage(stream), |
michael@0 | 1239 | track_id_(track), |
michael@0 | 1240 | track_rate_(rate), |
michael@0 | 1241 | segment_(segment), |
michael@0 | 1242 | listener_(listener), |
michael@0 | 1243 | completed_(completed) {} |
michael@0 | 1244 | |
michael@0 | 1245 | virtual void Run() MOZ_OVERRIDE { |
michael@0 | 1246 | StreamTime current_end = mStream->GetBufferEnd(); |
michael@0 | 1247 | TrackTicks current_ticks = TimeToTicksRoundUp(track_rate_, current_end); |
michael@0 | 1248 | |
michael@0 | 1249 | mStream->AddListenerImpl(listener_.forget()); |
michael@0 | 1250 | |
michael@0 | 1251 | // Add a track 'now' to avoid possible underrun, especially if we add |
michael@0 | 1252 | // a track "later". |
michael@0 | 1253 | |
michael@0 | 1254 | if (current_end != 0L) { |
michael@0 | 1255 | MOZ_MTLOG(ML_DEBUG, "added track @ " << current_end << |
michael@0 | 1256 | " -> " << MediaTimeToSeconds(current_end)); |
michael@0 | 1257 | } |
michael@0 | 1258 | |
michael@0 | 1259 | // To avoid assertions, we need to insert a dummy segment that covers up |
michael@0 | 1260 | // to the "start" time for the track |
michael@0 | 1261 | segment_->AppendNullData(current_ticks); |
michael@0 | 1262 | mStream->AsSourceStream()->AddTrack(track_id_, track_rate_, |
michael@0 | 1263 | current_ticks, segment_); |
michael@0 | 1264 | // AdvanceKnownTracksTicksTime(HEAT_DEATH_OF_UNIVERSE) means that in |
michael@0 | 1265 | // theory per the API, we can't add more tracks before that |
michael@0 | 1266 | // time. However, the impl actually allows it, and it avoids a whole |
michael@0 | 1267 | // bunch of locking that would be required (and potential blocking) |
michael@0 | 1268 | // if we used smaller values and updated them on each NotifyPull. |
michael@0 | 1269 | mStream->AsSourceStream()->AdvanceKnownTracksTime(STREAM_TIME_MAX); |
michael@0 | 1270 | |
michael@0 | 1271 | // We need to know how much has been "inserted" because we're given absolute |
michael@0 | 1272 | // times in NotifyPull. |
michael@0 | 1273 | completed_->TrackAdded(current_ticks); |
michael@0 | 1274 | } |
michael@0 | 1275 | private: |
michael@0 | 1276 | TrackID track_id_; |
michael@0 | 1277 | TrackRate track_rate_; |
michael@0 | 1278 | MediaSegment* segment_; |
michael@0 | 1279 | nsRefPtr<MediaStreamListener> listener_; |
michael@0 | 1280 | const RefPtr<TrackAddedCallback> completed_; |
michael@0 | 1281 | }; |
michael@0 | 1282 | |
michael@0 | 1283 | MOZ_ASSERT(listener); |
michael@0 | 1284 | |
michael@0 | 1285 | source->GraphImpl()->AppendMessage(new Message(source, track_id, track_rate, segment, listener, completed)); |
michael@0 | 1286 | #else |
michael@0 | 1287 | source->AddListener(listener); |
michael@0 | 1288 | source->AsSourceStream()->AddTrack(track_id, track_rate, 0, segment); |
michael@0 | 1289 | #endif |
michael@0 | 1290 | } |
michael@0 | 1291 | |
michael@0 | 1292 | void GenericReceiveListener::AddSelf(MediaSegment* segment) { |
michael@0 | 1293 | RefPtr<TrackAddedCallback> callback = new GenericReceiveCallback(this); |
michael@0 | 1294 | AddTrackAndListener(source_, track_id_, track_rate_, this, segment, callback); |
michael@0 | 1295 | } |
michael@0 | 1296 | |
michael@0 | 1297 | MediaPipelineReceiveAudio::PipelineListener::PipelineListener( |
michael@0 | 1298 | SourceMediaStream * source, TrackID track_id, |
michael@0 | 1299 | const RefPtr<MediaSessionConduit>& conduit) |
michael@0 | 1300 | : GenericReceiveListener(source, track_id, 16000), // XXX rate assumption |
michael@0 | 1301 | conduit_(conduit) |
michael@0 | 1302 | { |
michael@0 | 1303 | MOZ_ASSERT(track_rate_%100 == 0); |
michael@0 | 1304 | } |
michael@0 | 1305 | |
michael@0 | 1306 | void MediaPipelineReceiveAudio::PipelineListener:: |
michael@0 | 1307 | NotifyPull(MediaStreamGraph* graph, StreamTime desired_time) { |
michael@0 | 1308 | MOZ_ASSERT(source_); |
michael@0 | 1309 | if (!source_) { |
michael@0 | 1310 | MOZ_MTLOG(ML_ERROR, "NotifyPull() called from a non-SourceMediaStream"); |
michael@0 | 1311 | return; |
michael@0 | 1312 | } |
michael@0 | 1313 | |
michael@0 | 1314 | // This comparison is done in total time to avoid accumulated roundoff errors. |
michael@0 | 1315 | while (TicksToTimeRoundDown(track_rate_, played_ticks_) < desired_time) { |
michael@0 | 1316 | // TODO(ekr@rtfm.com): Is there a way to avoid mallocating here? Or reduce the size? |
michael@0 | 1317 | // Max size given mono is 480*2*1 = 960 (48KHz) |
michael@0 | 1318 | #define AUDIO_SAMPLE_BUFFER_MAX 1000 |
michael@0 | 1319 | MOZ_ASSERT((track_rate_/100)*sizeof(uint16_t) <= AUDIO_SAMPLE_BUFFER_MAX); |
michael@0 | 1320 | |
michael@0 | 1321 | nsRefPtr<SharedBuffer> samples = SharedBuffer::Create(AUDIO_SAMPLE_BUFFER_MAX); |
michael@0 | 1322 | int16_t *samples_data = static_cast<int16_t *>(samples->Data()); |
michael@0 | 1323 | int samples_length; |
michael@0 | 1324 | |
michael@0 | 1325 | // This fetches 10ms of data |
michael@0 | 1326 | MediaConduitErrorCode err = |
michael@0 | 1327 | static_cast<AudioSessionConduit*>(conduit_.get())->GetAudioFrame( |
michael@0 | 1328 | samples_data, |
michael@0 | 1329 | track_rate_, |
michael@0 | 1330 | 0, // TODO(ekr@rtfm.com): better estimate of "capture" (really playout) delay |
michael@0 | 1331 | samples_length); |
michael@0 | 1332 | MOZ_ASSERT(samples_length < AUDIO_SAMPLE_BUFFER_MAX); |
michael@0 | 1333 | |
michael@0 | 1334 | if (err != kMediaConduitNoError) { |
michael@0 | 1335 | // Insert silence on conduit/GIPS failure (extremely unlikely) |
michael@0 | 1336 | MOZ_MTLOG(ML_ERROR, "Audio conduit failed (" << err |
michael@0 | 1337 | << ") to return data @ " << played_ticks_ |
michael@0 | 1338 | << " (desired " << desired_time << " -> " |
michael@0 | 1339 | << MediaTimeToSeconds(desired_time) << ")"); |
michael@0 | 1340 | MOZ_ASSERT(err == kMediaConduitNoError); |
michael@0 | 1341 | samples_length = (track_rate_/100)*sizeof(uint16_t); // if this is not enough we'll loop and provide more |
michael@0 | 1342 | memset(samples_data, '\0', samples_length); |
michael@0 | 1343 | } |
michael@0 | 1344 | |
michael@0 | 1345 | MOZ_MTLOG(ML_DEBUG, "Audio conduit returned buffer of length " |
michael@0 | 1346 | << samples_length); |
michael@0 | 1347 | |
michael@0 | 1348 | AudioSegment segment; |
michael@0 | 1349 | nsAutoTArray<const int16_t*,1> channels; |
michael@0 | 1350 | channels.AppendElement(samples_data); |
michael@0 | 1351 | segment.AppendFrames(samples.forget(), channels, samples_length); |
michael@0 | 1352 | |
michael@0 | 1353 | // Handle track not actually added yet or removed/finished |
michael@0 | 1354 | if (source_->AppendToTrack(track_id_, &segment)) { |
michael@0 | 1355 | played_ticks_ += track_rate_/100; // 10ms in TrackTicks |
michael@0 | 1356 | } else { |
michael@0 | 1357 | MOZ_MTLOG(ML_ERROR, "AppendToTrack failed"); |
michael@0 | 1358 | // we can't un-read the data, but that's ok since we don't want to |
michael@0 | 1359 | // buffer - but don't i-loop! |
michael@0 | 1360 | return; |
michael@0 | 1361 | } |
michael@0 | 1362 | } |
michael@0 | 1363 | } |
michael@0 | 1364 | |
michael@0 | 1365 | nsresult MediaPipelineReceiveVideo::Init() { |
michael@0 | 1366 | char track_id_string[11]; |
michael@0 | 1367 | ASSERT_ON_THREAD(main_thread_); |
michael@0 | 1368 | MOZ_MTLOG(ML_DEBUG, __FUNCTION__); |
michael@0 | 1369 | |
michael@0 | 1370 | // We can replace this when we are allowed to do streams or std::to_string |
michael@0 | 1371 | PR_snprintf(track_id_string, sizeof(track_id_string), "%d", track_id_); |
michael@0 | 1372 | |
michael@0 | 1373 | description_ = pc_ + "| Receive video["; |
michael@0 | 1374 | description_ += track_id_string; |
michael@0 | 1375 | description_ += "]"; |
michael@0 | 1376 | |
michael@0 | 1377 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 1378 | listener_->AddSelf(new VideoSegment()); |
michael@0 | 1379 | #endif |
michael@0 | 1380 | |
michael@0 | 1381 | // Always happens before we can DetachMediaStream() |
michael@0 | 1382 | static_cast<VideoSessionConduit *>(conduit_.get())-> |
michael@0 | 1383 | AttachRenderer(renderer_); |
michael@0 | 1384 | |
michael@0 | 1385 | return MediaPipelineReceive::Init(); |
michael@0 | 1386 | } |
michael@0 | 1387 | |
michael@0 | 1388 | MediaPipelineReceiveVideo::PipelineListener::PipelineListener( |
michael@0 | 1389 | SourceMediaStream* source, TrackID track_id) |
michael@0 | 1390 | : GenericReceiveListener(source, track_id, USECS_PER_S), |
michael@0 | 1391 | width_(640), |
michael@0 | 1392 | height_(480), |
michael@0 | 1393 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 1394 | image_container_(), |
michael@0 | 1395 | image_(), |
michael@0 | 1396 | #endif |
michael@0 | 1397 | monitor_("Video PipelineListener") { |
michael@0 | 1398 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 1399 | image_container_ = layers::LayerManager::CreateImageContainer(); |
michael@0 | 1400 | #endif |
michael@0 | 1401 | } |
michael@0 | 1402 | |
michael@0 | 1403 | void MediaPipelineReceiveVideo::PipelineListener::RenderVideoFrame( |
michael@0 | 1404 | const unsigned char* buffer, |
michael@0 | 1405 | unsigned int buffer_size, |
michael@0 | 1406 | uint32_t time_stamp, |
michael@0 | 1407 | int64_t render_time, |
michael@0 | 1408 | const RefPtr<layers::Image>& video_image) { |
michael@0 | 1409 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 1410 | ReentrantMonitorAutoEnter enter(monitor_); |
michael@0 | 1411 | |
michael@0 | 1412 | if (buffer) { |
michael@0 | 1413 | // Create a video frame using |buffer|. |
michael@0 | 1414 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 1415 | ImageFormat format = ImageFormat::GRALLOC_PLANAR_YCBCR; |
michael@0 | 1416 | #else |
michael@0 | 1417 | ImageFormat format = ImageFormat::PLANAR_YCBCR; |
michael@0 | 1418 | #endif |
michael@0 | 1419 | nsRefPtr<layers::Image> image = image_container_->CreateImage(format); |
michael@0 | 1420 | layers::PlanarYCbCrImage* yuvImage = static_cast<layers::PlanarYCbCrImage*>(image.get()); |
michael@0 | 1421 | uint8_t* frame = const_cast<uint8_t*>(static_cast<const uint8_t*> (buffer)); |
michael@0 | 1422 | const uint8_t lumaBpp = 8; |
michael@0 | 1423 | const uint8_t chromaBpp = 4; |
michael@0 | 1424 | |
michael@0 | 1425 | layers::PlanarYCbCrData yuvData; |
michael@0 | 1426 | yuvData.mYChannel = frame; |
michael@0 | 1427 | yuvData.mYSize = IntSize(width_, height_); |
michael@0 | 1428 | yuvData.mYStride = width_ * lumaBpp/ 8; |
michael@0 | 1429 | yuvData.mCbCrStride = width_ * chromaBpp / 8; |
michael@0 | 1430 | yuvData.mCbChannel = frame + height_ * yuvData.mYStride; |
michael@0 | 1431 | yuvData.mCrChannel = yuvData.mCbChannel + height_ * yuvData.mCbCrStride / 2; |
michael@0 | 1432 | yuvData.mCbCrSize = IntSize(width_/ 2, height_/ 2); |
michael@0 | 1433 | yuvData.mPicX = 0; |
michael@0 | 1434 | yuvData.mPicY = 0; |
michael@0 | 1435 | yuvData.mPicSize = IntSize(width_, height_); |
michael@0 | 1436 | yuvData.mStereoMode = StereoMode::MONO; |
michael@0 | 1437 | |
michael@0 | 1438 | yuvImage->SetData(yuvData); |
michael@0 | 1439 | |
michael@0 | 1440 | image_ = image.forget(); |
michael@0 | 1441 | } |
michael@0 | 1442 | #ifdef WEBRTC_GONK |
michael@0 | 1443 | else { |
michael@0 | 1444 | // Decoder produced video frame that can be appended to the track directly. |
michael@0 | 1445 | MOZ_ASSERT(video_image); |
michael@0 | 1446 | image_ = video_image; |
michael@0 | 1447 | } |
michael@0 | 1448 | #endif // WEBRTC_GONK |
michael@0 | 1449 | #endif // MOZILLA_INTERNAL_API |
michael@0 | 1450 | } |
michael@0 | 1451 | |
michael@0 | 1452 | void MediaPipelineReceiveVideo::PipelineListener:: |
michael@0 | 1453 | NotifyPull(MediaStreamGraph* graph, StreamTime desired_time) { |
michael@0 | 1454 | ReentrantMonitorAutoEnter enter(monitor_); |
michael@0 | 1455 | |
michael@0 | 1456 | #ifdef MOZILLA_INTERNAL_API |
michael@0 | 1457 | nsRefPtr<layers::Image> image = image_; |
michael@0 | 1458 | TrackTicks target = TimeToTicksRoundUp(USECS_PER_S, desired_time); |
michael@0 | 1459 | TrackTicks delta = target - played_ticks_; |
michael@0 | 1460 | |
michael@0 | 1461 | // Don't append if we've already provided a frame that supposedly |
michael@0 | 1462 | // goes past the current aDesiredTime Doing so means a negative |
michael@0 | 1463 | // delta and thus messes up handling of the graph |
michael@0 | 1464 | if (delta > 0) { |
michael@0 | 1465 | VideoSegment segment; |
michael@0 | 1466 | segment.AppendFrame(image.forget(), delta, IntSize(width_, height_)); |
michael@0 | 1467 | // Handle track not actually added yet or removed/finished |
michael@0 | 1468 | if (source_->AppendToTrack(track_id_, &segment)) { |
michael@0 | 1469 | played_ticks_ = target; |
michael@0 | 1470 | } else { |
michael@0 | 1471 | MOZ_MTLOG(ML_ERROR, "AppendToTrack failed"); |
michael@0 | 1472 | return; |
michael@0 | 1473 | } |
michael@0 | 1474 | } |
michael@0 | 1475 | #endif |
michael@0 | 1476 | } |
michael@0 | 1477 | |
michael@0 | 1478 | |
michael@0 | 1479 | } // end namespace |