1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/encoder/VP8TrackEncoder.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,569 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "VP8TrackEncoder.h" 1.10 +#include "vpx/vp8cx.h" 1.11 +#include "vpx/vpx_encoder.h" 1.12 +#include "VideoUtils.h" 1.13 +#include "prsystem.h" 1.14 +#include "WebMWriter.h" 1.15 +#include "libyuv.h" 1.16 + 1.17 +namespace mozilla { 1.18 + 1.19 +#ifdef PR_LOGGING 1.20 +PRLogModuleInfo* gVP8TrackEncoderLog; 1.21 +#define VP8LOG(msg, ...) PR_LOG(gVP8TrackEncoderLog, PR_LOG_DEBUG, \ 1.22 + (msg, ##__VA_ARGS__)) 1.23 +// Debug logging macro with object pointer and class name. 1.24 +#else 1.25 +#define VP8LOG(msg, ...) 1.26 +#endif 1.27 + 1.28 +#define DEFAULT_BITRATE 2500 // in kbit/s 1.29 +#define DEFAULT_ENCODE_FRAMERATE 30 1.30 + 1.31 +using namespace mozilla::layers; 1.32 + 1.33 +VP8TrackEncoder::VP8TrackEncoder() 1.34 + : VideoTrackEncoder() 1.35 + , mEncodedFrameDuration(0) 1.36 + , mEncodedTimestamp(0) 1.37 + , mRemainingTicks(0) 1.38 + , mVPXContext(new vpx_codec_ctx_t()) 1.39 + , mVPXImageWrapper(new vpx_image_t()) 1.40 +{ 1.41 + MOZ_COUNT_CTOR(VP8TrackEncoder); 1.42 +#ifdef PR_LOGGING 1.43 + if (!gVP8TrackEncoderLog) { 1.44 + gVP8TrackEncoderLog = PR_NewLogModule("VP8TrackEncoder"); 1.45 + } 1.46 +#endif 1.47 +} 1.48 + 1.49 +VP8TrackEncoder::~VP8TrackEncoder() 1.50 +{ 1.51 + if (mInitialized) { 1.52 + vpx_codec_destroy(mVPXContext); 1.53 + } 1.54 + 1.55 + if (mVPXImageWrapper) { 1.56 + vpx_img_free(mVPXImageWrapper); 1.57 + } 1.58 + MOZ_COUNT_DTOR(VP8TrackEncoder); 1.59 +} 1.60 + 1.61 +nsresult 1.62 +VP8TrackEncoder::Init(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth, 1.63 + int32_t aDisplayHeight,TrackRate aTrackRate) 1.64 +{ 1.65 + if (aWidth < 1 || aHeight < 1 || aDisplayWidth < 1 || aDisplayHeight < 1 1.66 + || aTrackRate <= 0) { 1.67 + return NS_ERROR_FAILURE; 1.68 + } 1.69 + 1.70 + ReentrantMonitorAutoEnter mon(mReentrantMonitor); 1.71 + 1.72 + mTrackRate = aTrackRate; 1.73 + mEncodedFrameRate = DEFAULT_ENCODE_FRAMERATE; 1.74 + mEncodedFrameDuration = mTrackRate / mEncodedFrameRate; 1.75 + mFrameWidth = aWidth; 1.76 + mFrameHeight = aHeight; 1.77 + mDisplayWidth = aDisplayWidth; 1.78 + mDisplayHeight = aDisplayHeight; 1.79 + 1.80 + // Encoder configuration structure. 1.81 + vpx_codec_enc_cfg_t config; 1.82 + memset(&config, 0, sizeof(vpx_codec_enc_cfg_t)); 1.83 + if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &config, 0)) { 1.84 + return NS_ERROR_FAILURE; 1.85 + } 1.86 + 1.87 + // Creating a wrapper to the image - setting image data to NULL. Actual 1.88 + // pointer will be set in encode. Setting align to 1, as it is meaningless 1.89 + // (actual memory is not allocated). 1.90 + vpx_img_wrap(mVPXImageWrapper, IMG_FMT_I420, 1.91 + mFrameWidth, mFrameHeight, 1, nullptr); 1.92 + 1.93 + config.g_w = mFrameWidth; 1.94 + config.g_h = mFrameHeight; 1.95 + // TODO: Maybe we should have various aFrameRate bitrate pair for each devices? 1.96 + // or for different platform 1.97 + config.rc_target_bitrate = DEFAULT_BITRATE; // in kbit/s 1.98 + 1.99 + // Setting the time base of the codec 1.100 + config.g_timebase.num = 1; 1.101 + config.g_timebase.den = mTrackRate; 1.102 + 1.103 + config.g_error_resilient = 0; 1.104 + 1.105 + config.g_lag_in_frames = 0; // 0- no frame lagging 1.106 + 1.107 + int32_t number_of_cores = PR_GetNumberOfProcessors(); 1.108 + if (mFrameWidth * mFrameHeight > 1280 * 960 && number_of_cores >= 6) { 1.109 + config.g_threads = 3; // 3 threads for 1080p. 1.110 + } else if (mFrameWidth * mFrameHeight > 640 * 480 && number_of_cores >= 3) { 1.111 + config.g_threads = 2; // 2 threads for qHD/HD. 1.112 + } else { 1.113 + config.g_threads = 1; // 1 thread for VGA or less 1.114 + } 1.115 + 1.116 + // rate control settings 1.117 + config.rc_dropframe_thresh = 0; 1.118 + config.rc_end_usage = VPX_CBR; 1.119 + config.g_pass = VPX_RC_ONE_PASS; 1.120 + config.rc_resize_allowed = 1; 1.121 + config.rc_undershoot_pct = 100; 1.122 + config.rc_overshoot_pct = 15; 1.123 + config.rc_buf_initial_sz = 500; 1.124 + config.rc_buf_optimal_sz = 600; 1.125 + config.rc_buf_sz = 1000; 1.126 + 1.127 + config.kf_mode = VPX_KF_AUTO; 1.128 + // Ensure that we can output one I-frame per second. 1.129 + config.kf_max_dist = mEncodedFrameRate; 1.130 + 1.131 + vpx_codec_flags_t flags = 0; 1.132 + flags |= VPX_CODEC_USE_OUTPUT_PARTITION; 1.133 + if (vpx_codec_enc_init(mVPXContext, vpx_codec_vp8_cx(), &config, flags)) { 1.134 + return NS_ERROR_FAILURE; 1.135 + } 1.136 + 1.137 + vpx_codec_control(mVPXContext, VP8E_SET_STATIC_THRESHOLD, 1); 1.138 + vpx_codec_control(mVPXContext, VP8E_SET_CPUUSED, -6); 1.139 + vpx_codec_control(mVPXContext, VP8E_SET_TOKEN_PARTITIONS, 1.140 + VP8_ONE_TOKENPARTITION); 1.141 + 1.142 + mInitialized = true; 1.143 + mon.NotifyAll(); 1.144 + 1.145 + return NS_OK; 1.146 +} 1.147 + 1.148 +already_AddRefed<TrackMetadataBase> 1.149 +VP8TrackEncoder::GetMetadata() 1.150 +{ 1.151 + { 1.152 + // Wait if mEncoder is not initialized. 1.153 + ReentrantMonitorAutoEnter mon(mReentrantMonitor); 1.154 + while (!mCanceled && !mInitialized) { 1.155 + mon.Wait(); 1.156 + } 1.157 + } 1.158 + 1.159 + if (mCanceled || mEncodingComplete) { 1.160 + return nullptr; 1.161 + } 1.162 + 1.163 + nsRefPtr<VP8Metadata> meta = new VP8Metadata(); 1.164 + meta->mWidth = mFrameWidth; 1.165 + meta->mHeight = mFrameHeight; 1.166 + meta->mDisplayWidth = mDisplayWidth; 1.167 + meta->mDisplayHeight = mDisplayHeight; 1.168 + meta->mEncodedFrameRate = mEncodedFrameRate; 1.169 + 1.170 + return meta.forget(); 1.171 +} 1.172 + 1.173 +nsresult 1.174 +VP8TrackEncoder::GetEncodedPartitions(EncodedFrameContainer& aData) 1.175 +{ 1.176 + vpx_codec_iter_t iter = nullptr; 1.177 + EncodedFrame::FrameType frameType = EncodedFrame::VP8_P_FRAME; 1.178 + nsTArray<uint8_t> frameData; 1.179 + nsresult rv; 1.180 + const vpx_codec_cx_pkt_t *pkt = nullptr; 1.181 + while ((pkt = vpx_codec_get_cx_data(mVPXContext, &iter)) != nullptr) { 1.182 + switch (pkt->kind) { 1.183 + case VPX_CODEC_CX_FRAME_PKT: { 1.184 + // Copy the encoded data from libvpx to frameData 1.185 + frameData.AppendElements((uint8_t*)pkt->data.frame.buf, 1.186 + pkt->data.frame.sz); 1.187 + break; 1.188 + } 1.189 + default: { 1.190 + break; 1.191 + } 1.192 + } 1.193 + // End of frame 1.194 + if ((pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) == 0) { 1.195 + if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) { 1.196 + frameType = EncodedFrame::VP8_I_FRAME; 1.197 + } 1.198 + break; 1.199 + } 1.200 + } 1.201 + 1.202 + if (!frameData.IsEmpty() && 1.203 + (pkt->data.frame.pts == mEncodedTimestamp)) { 1.204 + // Copy the encoded data to aData. 1.205 + EncodedFrame* videoData = new EncodedFrame(); 1.206 + videoData->SetFrameType(frameType); 1.207 + // Convert the timestamp and duration to Usecs. 1.208 + CheckedInt64 timestamp = FramesToUsecs(mEncodedTimestamp, mTrackRate); 1.209 + if (timestamp.isValid()) { 1.210 + videoData->SetTimeStamp( 1.211 + (uint64_t)FramesToUsecs(mEncodedTimestamp, mTrackRate).value()); 1.212 + } 1.213 + CheckedInt64 duration = FramesToUsecs(pkt->data.frame.duration, mTrackRate); 1.214 + if (duration.isValid()) { 1.215 + videoData->SetDuration( 1.216 + (uint64_t)FramesToUsecs(pkt->data.frame.duration, mTrackRate).value()); 1.217 + } 1.218 + rv = videoData->SwapInFrameData(frameData); 1.219 + NS_ENSURE_SUCCESS(rv, rv); 1.220 + VP8LOG("GetEncodedPartitions TimeStamp %lld Duration %lld\n", 1.221 + videoData->GetTimeStamp(), videoData->GetDuration()); 1.222 + VP8LOG("frameType %d\n", videoData->GetFrameType()); 1.223 + aData.AppendEncodedFrame(videoData); 1.224 + } 1.225 + 1.226 + return NS_OK; 1.227 +} 1.228 + 1.229 +void VP8TrackEncoder::PrepareMutedFrame() 1.230 +{ 1.231 + if (mMuteFrame.IsEmpty()) { 1.232 + CreateMutedFrame(&mMuteFrame); 1.233 + } 1.234 + 1.235 + uint32_t yPlaneSize = mFrameWidth * mFrameHeight; 1.236 + uint32_t halfWidth = (mFrameWidth + 1) / 2; 1.237 + uint32_t halfHeight = (mFrameHeight + 1) / 2; 1.238 + uint32_t uvPlaneSize = halfWidth * halfHeight; 1.239 + 1.240 + MOZ_ASSERT(mMuteFrame.Length() >= (yPlaneSize + uvPlaneSize * 2)); 1.241 + uint8_t *y = mMuteFrame.Elements(); 1.242 + uint8_t *cb = mMuteFrame.Elements() + yPlaneSize; 1.243 + uint8_t *cr = mMuteFrame.Elements() + yPlaneSize + uvPlaneSize; 1.244 + 1.245 + mVPXImageWrapper->planes[PLANE_Y] = y; 1.246 + mVPXImageWrapper->planes[PLANE_U] = cb; 1.247 + mVPXImageWrapper->planes[PLANE_V] = cr; 1.248 + mVPXImageWrapper->stride[VPX_PLANE_Y] = mFrameWidth; 1.249 + mVPXImageWrapper->stride[VPX_PLANE_U] = halfWidth; 1.250 + mVPXImageWrapper->stride[VPX_PLANE_V] = halfWidth; 1.251 +} 1.252 + 1.253 +static bool isYUV420(const PlanarYCbCrImage::Data *aData) 1.254 +{ 1.255 + if (aData->mYSize == aData->mCbCrSize * 2) { 1.256 + return true; 1.257 + } 1.258 + return false; 1.259 +} 1.260 + 1.261 +static bool isYUV422(const PlanarYCbCrImage::Data *aData) 1.262 +{ 1.263 + if ((aData->mYSize.width == aData->mCbCrSize.width * 2) && 1.264 + (aData->mYSize.height == aData->mCbCrSize.height)) { 1.265 + return true; 1.266 + } 1.267 + return false; 1.268 +} 1.269 + 1.270 +static bool isYUV444(const PlanarYCbCrImage::Data *aData) 1.271 +{ 1.272 + if (aData->mYSize == aData->mCbCrSize) { 1.273 + return true; 1.274 + } 1.275 + return false; 1.276 +} 1.277 + 1.278 +nsresult VP8TrackEncoder::PrepareRawFrame(VideoChunk &aChunk) 1.279 +{ 1.280 + if (aChunk.mFrame.GetForceBlack() || aChunk.IsNull()) { 1.281 + PrepareMutedFrame(); 1.282 + } else { 1.283 + Image* img = aChunk.mFrame.GetImage(); 1.284 + ImageFormat format = img->GetFormat(); 1.285 + if (format != ImageFormat::PLANAR_YCBCR) { 1.286 + VP8LOG("Unsupported video format\n"); 1.287 + return NS_ERROR_FAILURE; 1.288 + } 1.289 + 1.290 + // Cast away constness b/c some of the accessors are non-const 1.291 + PlanarYCbCrImage* yuv = 1.292 + const_cast<PlanarYCbCrImage *>(static_cast<const PlanarYCbCrImage *>(img)); 1.293 + // Big-time assumption here that this is all contiguous data coming 1.294 + // from getUserMedia or other sources. 1.295 + MOZ_ASSERT(yuv); 1.296 + if (!yuv->IsValid()) { 1.297 + NS_WARNING("PlanarYCbCrImage is not valid"); 1.298 + return NS_ERROR_FAILURE; 1.299 + } 1.300 + const PlanarYCbCrImage::Data *data = yuv->GetData(); 1.301 + 1.302 + if (isYUV420(data) && !data->mCbSkip) { // 420 planar 1.303 + mVPXImageWrapper->planes[PLANE_Y] = data->mYChannel; 1.304 + mVPXImageWrapper->planes[PLANE_U] = data->mCbChannel; 1.305 + mVPXImageWrapper->planes[PLANE_V] = data->mCrChannel; 1.306 + mVPXImageWrapper->stride[VPX_PLANE_Y] = data->mYStride; 1.307 + mVPXImageWrapper->stride[VPX_PLANE_U] = data->mCbCrStride; 1.308 + mVPXImageWrapper->stride[VPX_PLANE_V] = data->mCbCrStride; 1.309 + } else { 1.310 + uint32_t yPlaneSize = mFrameWidth * mFrameHeight; 1.311 + uint32_t halfWidth = (mFrameWidth + 1) / 2; 1.312 + uint32_t halfHeight = (mFrameHeight + 1) / 2; 1.313 + uint32_t uvPlaneSize = halfWidth * halfHeight; 1.314 + if (mI420Frame.IsEmpty()) { 1.315 + mI420Frame.SetLength(yPlaneSize + uvPlaneSize * 2); 1.316 + } 1.317 + 1.318 + MOZ_ASSERT(mI420Frame.Length() >= (yPlaneSize + uvPlaneSize * 2)); 1.319 + uint8_t *y = mI420Frame.Elements(); 1.320 + uint8_t *cb = mI420Frame.Elements() + yPlaneSize; 1.321 + uint8_t *cr = mI420Frame.Elements() + yPlaneSize + uvPlaneSize; 1.322 + 1.323 + if (isYUV420(data) && data->mCbSkip) { 1.324 + // If mCbSkip is set, we assume it's nv12 or nv21. 1.325 + if (data->mCbChannel < data->mCrChannel) { // nv12 1.326 + libyuv::NV12ToI420(data->mYChannel, data->mYStride, 1.327 + data->mCbChannel, data->mCbCrStride, 1.328 + y, mFrameWidth, 1.329 + cb, halfWidth, 1.330 + cr, halfWidth, 1.331 + mFrameWidth, mFrameHeight); 1.332 + } else { // nv21 1.333 + libyuv::NV21ToI420(data->mYChannel, data->mYStride, 1.334 + data->mCrChannel, data->mCbCrStride, 1.335 + y, mFrameWidth, 1.336 + cb, halfWidth, 1.337 + cr, halfWidth, 1.338 + mFrameWidth, mFrameHeight); 1.339 + } 1.340 + } else if (isYUV444(data) && !data->mCbSkip) { 1.341 + libyuv::I444ToI420(data->mYChannel, data->mYStride, 1.342 + data->mCbChannel, data->mCbCrStride, 1.343 + data->mCrChannel, data->mCbCrStride, 1.344 + y, mFrameWidth, 1.345 + cb, halfWidth, 1.346 + cr, halfWidth, 1.347 + mFrameWidth, mFrameHeight); 1.348 + } else if (isYUV422(data) && !data->mCbSkip) { 1.349 + libyuv::I422ToI420(data->mYChannel, data->mYStride, 1.350 + data->mCbChannel, data->mCbCrStride, 1.351 + data->mCrChannel, data->mCbCrStride, 1.352 + y, mFrameWidth, 1.353 + cb, halfWidth, 1.354 + cr, halfWidth, 1.355 + mFrameWidth, mFrameHeight); 1.356 + } else { 1.357 + VP8LOG("Unsupported planar format\n"); 1.358 + return NS_ERROR_NOT_IMPLEMENTED; 1.359 + } 1.360 + 1.361 + mVPXImageWrapper->planes[PLANE_Y] = y; 1.362 + mVPXImageWrapper->planes[PLANE_U] = cb; 1.363 + mVPXImageWrapper->planes[PLANE_V] = cr; 1.364 + mVPXImageWrapper->stride[VPX_PLANE_Y] = mFrameWidth; 1.365 + mVPXImageWrapper->stride[VPX_PLANE_U] = halfWidth; 1.366 + mVPXImageWrapper->stride[VPX_PLANE_V] = halfWidth; 1.367 + } 1.368 + } 1.369 + return NS_OK; 1.370 +} 1.371 + 1.372 +// These two define value used in GetNextEncodeOperation to determine the 1.373 +// EncodeOperation for next target frame. 1.374 +#define I_FRAME_RATIO (0.5) 1.375 +#define SKIP_FRAME_RATIO (0.75) 1.376 + 1.377 +/** 1.378 + * Compares the elapsed time from the beginning of GetEncodedTrack and 1.379 + * the processed frame duration in mSourceSegment 1.380 + * in order to set the nextEncodeOperation for next target frame. 1.381 + */ 1.382 +VP8TrackEncoder::EncodeOperation 1.383 +VP8TrackEncoder::GetNextEncodeOperation(TimeDuration aTimeElapsed, 1.384 + TrackTicks aProcessedDuration) 1.385 +{ 1.386 + int64_t durationInUsec = 1.387 + FramesToUsecs(aProcessedDuration + mEncodedFrameDuration, 1.388 + mTrackRate).value(); 1.389 + if (aTimeElapsed.ToMicroseconds() > (durationInUsec * SKIP_FRAME_RATIO)) { 1.390 + // The encoder is too slow. 1.391 + // We should skip next frame to consume the mSourceSegment. 1.392 + return SKIP_FRAME; 1.393 + } else if (aTimeElapsed.ToMicroseconds() > (durationInUsec * I_FRAME_RATIO)) { 1.394 + // The encoder is a little slow. 1.395 + // We force the encoder to encode an I-frame to accelerate. 1.396 + return ENCODE_I_FRAME; 1.397 + } else { 1.398 + return ENCODE_NORMAL_FRAME; 1.399 + } 1.400 +} 1.401 + 1.402 +TrackTicks 1.403 +VP8TrackEncoder::CalculateRemainingTicks(TrackTicks aDurationCopied, 1.404 + TrackTicks aEncodedDuration) 1.405 +{ 1.406 + return mRemainingTicks + aEncodedDuration - aDurationCopied; 1.407 +} 1.408 + 1.409 +// Try to extend the encodedDuration as long as possible if the target frame 1.410 +// has a long duration. 1.411 +TrackTicks 1.412 +VP8TrackEncoder::CalculateEncodedDuration(TrackTicks aDurationCopied) 1.413 +{ 1.414 + TrackTicks temp64 = aDurationCopied; 1.415 + TrackTicks encodedDuration = mEncodedFrameDuration; 1.416 + temp64 -= mRemainingTicks; 1.417 + while (temp64 > mEncodedFrameDuration) { 1.418 + temp64 -= mEncodedFrameDuration; 1.419 + encodedDuration += mEncodedFrameDuration; 1.420 + } 1.421 + return encodedDuration; 1.422 +} 1.423 + 1.424 +/** 1.425 + * Encoding flow in GetEncodedTrack(): 1.426 + * 1: Check the mInitialized state and the packet duration. 1.427 + * 2: Move the data from mRawSegment to mSourceSegment. 1.428 + * 3: Encode the video chunks in mSourceSegment in a for-loop. 1.429 + * 3.1: Pick the video chunk by mRemainingTicks. 1.430 + * 3.2: Calculate the encoding duration for the parameter of vpx_codec_encode(). 1.431 + * The encoding duration is a multiple of mEncodedFrameDuration. 1.432 + * 3.3: Setup the video chunk to mVPXImageWrapper by PrepareRawFrame(). 1.433 + * 3.4: Send frame into vp8 encoder by vpx_codec_encode(). 1.434 + * 3.5: Get the output frame from encoder by calling GetEncodedPartitions(). 1.435 + * 3.6: Calculate the mRemainingTicks for next target frame. 1.436 + * 3.7: Set the nextEncodeOperation for the next target frame. 1.437 + * There is a heuristic: If the frame duration we have processed in 1.438 + * mSourceSegment is 100ms, means that we can't spend more than 100ms to 1.439 + * encode it. 1.440 + * 4. Remove the encoded chunks in mSourceSegment after for-loop. 1.441 + * 1.442 + * Ex1: Input frame rate is 100 => input frame duration is 10ms for each. 1.443 + * mEncodedFrameRate is 30 => output frame duration is 33ms. 1.444 + * In this case, the frame duration in mSourceSegment will be: 1.445 + * 1st : 0~10ms 1.446 + * 2nd : 10~20ms 1.447 + * 3rd : 20~30ms 1.448 + * 4th : 30~40ms 1.449 + * ... 1.450 + * The VP8 encoder will take the 1st and 4th frames to encode. At beginning 1.451 + * mRemainingTicks is 0 for 1st frame, then the mRemainingTicks is set 1.452 + * to 23 to pick the 4th frame. (mEncodedFrameDuration - 1st frame duration) 1.453 + * 1.454 + * Ex2: Input frame rate is 25 => frame duration is 40ms for each. 1.455 + * mEncodedFrameRate is 30 => output frame duration is 33ms. 1.456 + * In this case, the frame duration in mSourceSegment will be: 1.457 + * 1st : 0~40ms 1.458 + * 2nd : 40~80ms 1.459 + * 3rd : 80~120ms 1.460 + * 4th : 120~160ms 1.461 + * ... 1.462 + * Because the input frame duration is 40ms larger than 33ms, so the first 1.463 + * encoded frame duration will be 66ms by calling CalculateEncodedDuration. 1.464 + * And the mRemainingTicks will be set to 26 1.465 + * (CalculateRemainingTicks 0+66-40) in order to pick the next frame(2nd) 1.466 + * in mSourceSegment. 1.467 + */ 1.468 +nsresult 1.469 +VP8TrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) 1.470 +{ 1.471 + { 1.472 + // Move all the samples from mRawSegment to mSourceSegment. We only hold 1.473 + // the monitor in this block. 1.474 + ReentrantMonitorAutoEnter mon(mReentrantMonitor); 1.475 + // Wait if mEncoder is not initialized, or when not enough raw data, but is 1.476 + // not the end of stream nor is being canceled. 1.477 + while (!mCanceled && (!mInitialized || 1.478 + (mRawSegment.GetDuration() + mSourceSegment.GetDuration() < 1.479 + mEncodedFrameDuration && !mEndOfStream))) { 1.480 + mon.Wait(); 1.481 + } 1.482 + if (mCanceled || mEncodingComplete) { 1.483 + return NS_ERROR_FAILURE; 1.484 + } 1.485 + mSourceSegment.AppendFrom(&mRawSegment); 1.486 + } 1.487 + 1.488 + VideoSegment::ChunkIterator iter(mSourceSegment); 1.489 + TrackTicks durationCopied = 0; 1.490 + TrackTicks totalProcessedDuration = 0; 1.491 + TimeStamp timebase = TimeStamp::Now(); 1.492 + EncodeOperation nextEncodeOperation = ENCODE_NORMAL_FRAME; 1.493 + 1.494 + for (; !iter.IsEnded(); iter.Next()) { 1.495 + VideoChunk &chunk = *iter; 1.496 + // Accumulate chunk's duration to durationCopied until it reaches 1.497 + // mRemainingTicks. 1.498 + durationCopied += chunk.GetDuration(); 1.499 + MOZ_ASSERT(mRemainingTicks <= mEncodedFrameDuration); 1.500 + VP8LOG("durationCopied %lld mRemainingTicks %lld\n", 1.501 + durationCopied, mRemainingTicks); 1.502 + if (durationCopied >= mRemainingTicks) { 1.503 + VP8LOG("nextEncodeOperation is %d\n",nextEncodeOperation); 1.504 + // Calculate encodedDuration for this target frame. 1.505 + TrackTicks encodedDuration = CalculateEncodedDuration(durationCopied); 1.506 + 1.507 + // Encode frame. 1.508 + if (nextEncodeOperation != SKIP_FRAME) { 1.509 + nsresult rv = PrepareRawFrame(chunk); 1.510 + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); 1.511 + 1.512 + // Encode the data with VP8 encoder 1.513 + int flags = (nextEncodeOperation == ENCODE_NORMAL_FRAME) ? 1.514 + 0 : VPX_EFLAG_FORCE_KF; 1.515 + if (vpx_codec_encode(mVPXContext, mVPXImageWrapper, mEncodedTimestamp, 1.516 + (unsigned long)encodedDuration, flags, 1.517 + VPX_DL_REALTIME)) { 1.518 + return NS_ERROR_FAILURE; 1.519 + } 1.520 + // Get the encoded data from VP8 encoder. 1.521 + GetEncodedPartitions(aData); 1.522 + } else { 1.523 + // SKIP_FRAME 1.524 + // Extend the duration of the last encoded data in aData 1.525 + // because this frame will be skip. 1.526 + nsRefPtr<EncodedFrame> last = nullptr; 1.527 + last = aData.GetEncodedFrames().LastElement(); 1.528 + if (last) { 1.529 + last->SetDuration(last->GetDuration() + encodedDuration); 1.530 + } 1.531 + } 1.532 + // Move forward the mEncodedTimestamp. 1.533 + mEncodedTimestamp += encodedDuration; 1.534 + totalProcessedDuration += durationCopied; 1.535 + // Calculate mRemainingTicks for next target frame. 1.536 + mRemainingTicks = CalculateRemainingTicks(durationCopied, 1.537 + encodedDuration); 1.538 + 1.539 + // Check the remain data is enough for next target frame. 1.540 + if (mSourceSegment.GetDuration() - totalProcessedDuration 1.541 + >= mEncodedFrameDuration) { 1.542 + TimeDuration elapsedTime = TimeStamp::Now() - timebase; 1.543 + nextEncodeOperation = GetNextEncodeOperation(elapsedTime, 1.544 + totalProcessedDuration); 1.545 + // Reset durationCopied for next iteration. 1.546 + durationCopied = 0; 1.547 + } else { 1.548 + // Process done, there is no enough data left for next iteration, 1.549 + // break the for-loop. 1.550 + break; 1.551 + } 1.552 + } 1.553 + } 1.554 + // Remove the chunks we have processed. 1.555 + mSourceSegment.RemoveLeading(totalProcessedDuration); 1.556 + VP8LOG("RemoveLeading %lld\n",totalProcessedDuration); 1.557 + 1.558 + // End of stream, pull the rest frames in encoder. 1.559 + if (mEndOfStream) { 1.560 + VP8LOG("mEndOfStream is true\n"); 1.561 + mEncodingComplete = true; 1.562 + if (vpx_codec_encode(mVPXContext, nullptr, mEncodedTimestamp, 1.563 + mEncodedFrameDuration, 0, VPX_DL_REALTIME)) { 1.564 + return NS_ERROR_FAILURE; 1.565 + } 1.566 + GetEncodedPartitions(aData); 1.567 + } 1.568 + 1.569 + return NS_OK ; 1.570 +} 1.571 + 1.572 +} // namespace mozilla