Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; 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 | #include "VP8TrackEncoder.h" |
michael@0 | 7 | #include "vpx/vp8cx.h" |
michael@0 | 8 | #include "vpx/vpx_encoder.h" |
michael@0 | 9 | #include "VideoUtils.h" |
michael@0 | 10 | #include "prsystem.h" |
michael@0 | 11 | #include "WebMWriter.h" |
michael@0 | 12 | #include "libyuv.h" |
michael@0 | 13 | |
michael@0 | 14 | namespace mozilla { |
michael@0 | 15 | |
michael@0 | 16 | #ifdef PR_LOGGING |
michael@0 | 17 | PRLogModuleInfo* gVP8TrackEncoderLog; |
michael@0 | 18 | #define VP8LOG(msg, ...) PR_LOG(gVP8TrackEncoderLog, PR_LOG_DEBUG, \ |
michael@0 | 19 | (msg, ##__VA_ARGS__)) |
michael@0 | 20 | // Debug logging macro with object pointer and class name. |
michael@0 | 21 | #else |
michael@0 | 22 | #define VP8LOG(msg, ...) |
michael@0 | 23 | #endif |
michael@0 | 24 | |
michael@0 | 25 | #define DEFAULT_BITRATE 2500 // in kbit/s |
michael@0 | 26 | #define DEFAULT_ENCODE_FRAMERATE 30 |
michael@0 | 27 | |
michael@0 | 28 | using namespace mozilla::layers; |
michael@0 | 29 | |
michael@0 | 30 | VP8TrackEncoder::VP8TrackEncoder() |
michael@0 | 31 | : VideoTrackEncoder() |
michael@0 | 32 | , mEncodedFrameDuration(0) |
michael@0 | 33 | , mEncodedTimestamp(0) |
michael@0 | 34 | , mRemainingTicks(0) |
michael@0 | 35 | , mVPXContext(new vpx_codec_ctx_t()) |
michael@0 | 36 | , mVPXImageWrapper(new vpx_image_t()) |
michael@0 | 37 | { |
michael@0 | 38 | MOZ_COUNT_CTOR(VP8TrackEncoder); |
michael@0 | 39 | #ifdef PR_LOGGING |
michael@0 | 40 | if (!gVP8TrackEncoderLog) { |
michael@0 | 41 | gVP8TrackEncoderLog = PR_NewLogModule("VP8TrackEncoder"); |
michael@0 | 42 | } |
michael@0 | 43 | #endif |
michael@0 | 44 | } |
michael@0 | 45 | |
michael@0 | 46 | VP8TrackEncoder::~VP8TrackEncoder() |
michael@0 | 47 | { |
michael@0 | 48 | if (mInitialized) { |
michael@0 | 49 | vpx_codec_destroy(mVPXContext); |
michael@0 | 50 | } |
michael@0 | 51 | |
michael@0 | 52 | if (mVPXImageWrapper) { |
michael@0 | 53 | vpx_img_free(mVPXImageWrapper); |
michael@0 | 54 | } |
michael@0 | 55 | MOZ_COUNT_DTOR(VP8TrackEncoder); |
michael@0 | 56 | } |
michael@0 | 57 | |
michael@0 | 58 | nsresult |
michael@0 | 59 | VP8TrackEncoder::Init(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth, |
michael@0 | 60 | int32_t aDisplayHeight,TrackRate aTrackRate) |
michael@0 | 61 | { |
michael@0 | 62 | if (aWidth < 1 || aHeight < 1 || aDisplayWidth < 1 || aDisplayHeight < 1 |
michael@0 | 63 | || aTrackRate <= 0) { |
michael@0 | 64 | return NS_ERROR_FAILURE; |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
michael@0 | 68 | |
michael@0 | 69 | mTrackRate = aTrackRate; |
michael@0 | 70 | mEncodedFrameRate = DEFAULT_ENCODE_FRAMERATE; |
michael@0 | 71 | mEncodedFrameDuration = mTrackRate / mEncodedFrameRate; |
michael@0 | 72 | mFrameWidth = aWidth; |
michael@0 | 73 | mFrameHeight = aHeight; |
michael@0 | 74 | mDisplayWidth = aDisplayWidth; |
michael@0 | 75 | mDisplayHeight = aDisplayHeight; |
michael@0 | 76 | |
michael@0 | 77 | // Encoder configuration structure. |
michael@0 | 78 | vpx_codec_enc_cfg_t config; |
michael@0 | 79 | memset(&config, 0, sizeof(vpx_codec_enc_cfg_t)); |
michael@0 | 80 | if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &config, 0)) { |
michael@0 | 81 | return NS_ERROR_FAILURE; |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | // Creating a wrapper to the image - setting image data to NULL. Actual |
michael@0 | 85 | // pointer will be set in encode. Setting align to 1, as it is meaningless |
michael@0 | 86 | // (actual memory is not allocated). |
michael@0 | 87 | vpx_img_wrap(mVPXImageWrapper, IMG_FMT_I420, |
michael@0 | 88 | mFrameWidth, mFrameHeight, 1, nullptr); |
michael@0 | 89 | |
michael@0 | 90 | config.g_w = mFrameWidth; |
michael@0 | 91 | config.g_h = mFrameHeight; |
michael@0 | 92 | // TODO: Maybe we should have various aFrameRate bitrate pair for each devices? |
michael@0 | 93 | // or for different platform |
michael@0 | 94 | config.rc_target_bitrate = DEFAULT_BITRATE; // in kbit/s |
michael@0 | 95 | |
michael@0 | 96 | // Setting the time base of the codec |
michael@0 | 97 | config.g_timebase.num = 1; |
michael@0 | 98 | config.g_timebase.den = mTrackRate; |
michael@0 | 99 | |
michael@0 | 100 | config.g_error_resilient = 0; |
michael@0 | 101 | |
michael@0 | 102 | config.g_lag_in_frames = 0; // 0- no frame lagging |
michael@0 | 103 | |
michael@0 | 104 | int32_t number_of_cores = PR_GetNumberOfProcessors(); |
michael@0 | 105 | if (mFrameWidth * mFrameHeight > 1280 * 960 && number_of_cores >= 6) { |
michael@0 | 106 | config.g_threads = 3; // 3 threads for 1080p. |
michael@0 | 107 | } else if (mFrameWidth * mFrameHeight > 640 * 480 && number_of_cores >= 3) { |
michael@0 | 108 | config.g_threads = 2; // 2 threads for qHD/HD. |
michael@0 | 109 | } else { |
michael@0 | 110 | config.g_threads = 1; // 1 thread for VGA or less |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | // rate control settings |
michael@0 | 114 | config.rc_dropframe_thresh = 0; |
michael@0 | 115 | config.rc_end_usage = VPX_CBR; |
michael@0 | 116 | config.g_pass = VPX_RC_ONE_PASS; |
michael@0 | 117 | config.rc_resize_allowed = 1; |
michael@0 | 118 | config.rc_undershoot_pct = 100; |
michael@0 | 119 | config.rc_overshoot_pct = 15; |
michael@0 | 120 | config.rc_buf_initial_sz = 500; |
michael@0 | 121 | config.rc_buf_optimal_sz = 600; |
michael@0 | 122 | config.rc_buf_sz = 1000; |
michael@0 | 123 | |
michael@0 | 124 | config.kf_mode = VPX_KF_AUTO; |
michael@0 | 125 | // Ensure that we can output one I-frame per second. |
michael@0 | 126 | config.kf_max_dist = mEncodedFrameRate; |
michael@0 | 127 | |
michael@0 | 128 | vpx_codec_flags_t flags = 0; |
michael@0 | 129 | flags |= VPX_CODEC_USE_OUTPUT_PARTITION; |
michael@0 | 130 | if (vpx_codec_enc_init(mVPXContext, vpx_codec_vp8_cx(), &config, flags)) { |
michael@0 | 131 | return NS_ERROR_FAILURE; |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | vpx_codec_control(mVPXContext, VP8E_SET_STATIC_THRESHOLD, 1); |
michael@0 | 135 | vpx_codec_control(mVPXContext, VP8E_SET_CPUUSED, -6); |
michael@0 | 136 | vpx_codec_control(mVPXContext, VP8E_SET_TOKEN_PARTITIONS, |
michael@0 | 137 | VP8_ONE_TOKENPARTITION); |
michael@0 | 138 | |
michael@0 | 139 | mInitialized = true; |
michael@0 | 140 | mon.NotifyAll(); |
michael@0 | 141 | |
michael@0 | 142 | return NS_OK; |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | already_AddRefed<TrackMetadataBase> |
michael@0 | 146 | VP8TrackEncoder::GetMetadata() |
michael@0 | 147 | { |
michael@0 | 148 | { |
michael@0 | 149 | // Wait if mEncoder is not initialized. |
michael@0 | 150 | ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
michael@0 | 151 | while (!mCanceled && !mInitialized) { |
michael@0 | 152 | mon.Wait(); |
michael@0 | 153 | } |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | if (mCanceled || mEncodingComplete) { |
michael@0 | 157 | return nullptr; |
michael@0 | 158 | } |
michael@0 | 159 | |
michael@0 | 160 | nsRefPtr<VP8Metadata> meta = new VP8Metadata(); |
michael@0 | 161 | meta->mWidth = mFrameWidth; |
michael@0 | 162 | meta->mHeight = mFrameHeight; |
michael@0 | 163 | meta->mDisplayWidth = mDisplayWidth; |
michael@0 | 164 | meta->mDisplayHeight = mDisplayHeight; |
michael@0 | 165 | meta->mEncodedFrameRate = mEncodedFrameRate; |
michael@0 | 166 | |
michael@0 | 167 | return meta.forget(); |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | nsresult |
michael@0 | 171 | VP8TrackEncoder::GetEncodedPartitions(EncodedFrameContainer& aData) |
michael@0 | 172 | { |
michael@0 | 173 | vpx_codec_iter_t iter = nullptr; |
michael@0 | 174 | EncodedFrame::FrameType frameType = EncodedFrame::VP8_P_FRAME; |
michael@0 | 175 | nsTArray<uint8_t> frameData; |
michael@0 | 176 | nsresult rv; |
michael@0 | 177 | const vpx_codec_cx_pkt_t *pkt = nullptr; |
michael@0 | 178 | while ((pkt = vpx_codec_get_cx_data(mVPXContext, &iter)) != nullptr) { |
michael@0 | 179 | switch (pkt->kind) { |
michael@0 | 180 | case VPX_CODEC_CX_FRAME_PKT: { |
michael@0 | 181 | // Copy the encoded data from libvpx to frameData |
michael@0 | 182 | frameData.AppendElements((uint8_t*)pkt->data.frame.buf, |
michael@0 | 183 | pkt->data.frame.sz); |
michael@0 | 184 | break; |
michael@0 | 185 | } |
michael@0 | 186 | default: { |
michael@0 | 187 | break; |
michael@0 | 188 | } |
michael@0 | 189 | } |
michael@0 | 190 | // End of frame |
michael@0 | 191 | if ((pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) == 0) { |
michael@0 | 192 | if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) { |
michael@0 | 193 | frameType = EncodedFrame::VP8_I_FRAME; |
michael@0 | 194 | } |
michael@0 | 195 | break; |
michael@0 | 196 | } |
michael@0 | 197 | } |
michael@0 | 198 | |
michael@0 | 199 | if (!frameData.IsEmpty() && |
michael@0 | 200 | (pkt->data.frame.pts == mEncodedTimestamp)) { |
michael@0 | 201 | // Copy the encoded data to aData. |
michael@0 | 202 | EncodedFrame* videoData = new EncodedFrame(); |
michael@0 | 203 | videoData->SetFrameType(frameType); |
michael@0 | 204 | // Convert the timestamp and duration to Usecs. |
michael@0 | 205 | CheckedInt64 timestamp = FramesToUsecs(mEncodedTimestamp, mTrackRate); |
michael@0 | 206 | if (timestamp.isValid()) { |
michael@0 | 207 | videoData->SetTimeStamp( |
michael@0 | 208 | (uint64_t)FramesToUsecs(mEncodedTimestamp, mTrackRate).value()); |
michael@0 | 209 | } |
michael@0 | 210 | CheckedInt64 duration = FramesToUsecs(pkt->data.frame.duration, mTrackRate); |
michael@0 | 211 | if (duration.isValid()) { |
michael@0 | 212 | videoData->SetDuration( |
michael@0 | 213 | (uint64_t)FramesToUsecs(pkt->data.frame.duration, mTrackRate).value()); |
michael@0 | 214 | } |
michael@0 | 215 | rv = videoData->SwapInFrameData(frameData); |
michael@0 | 216 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 217 | VP8LOG("GetEncodedPartitions TimeStamp %lld Duration %lld\n", |
michael@0 | 218 | videoData->GetTimeStamp(), videoData->GetDuration()); |
michael@0 | 219 | VP8LOG("frameType %d\n", videoData->GetFrameType()); |
michael@0 | 220 | aData.AppendEncodedFrame(videoData); |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | return NS_OK; |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | void VP8TrackEncoder::PrepareMutedFrame() |
michael@0 | 227 | { |
michael@0 | 228 | if (mMuteFrame.IsEmpty()) { |
michael@0 | 229 | CreateMutedFrame(&mMuteFrame); |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | uint32_t yPlaneSize = mFrameWidth * mFrameHeight; |
michael@0 | 233 | uint32_t halfWidth = (mFrameWidth + 1) / 2; |
michael@0 | 234 | uint32_t halfHeight = (mFrameHeight + 1) / 2; |
michael@0 | 235 | uint32_t uvPlaneSize = halfWidth * halfHeight; |
michael@0 | 236 | |
michael@0 | 237 | MOZ_ASSERT(mMuteFrame.Length() >= (yPlaneSize + uvPlaneSize * 2)); |
michael@0 | 238 | uint8_t *y = mMuteFrame.Elements(); |
michael@0 | 239 | uint8_t *cb = mMuteFrame.Elements() + yPlaneSize; |
michael@0 | 240 | uint8_t *cr = mMuteFrame.Elements() + yPlaneSize + uvPlaneSize; |
michael@0 | 241 | |
michael@0 | 242 | mVPXImageWrapper->planes[PLANE_Y] = y; |
michael@0 | 243 | mVPXImageWrapper->planes[PLANE_U] = cb; |
michael@0 | 244 | mVPXImageWrapper->planes[PLANE_V] = cr; |
michael@0 | 245 | mVPXImageWrapper->stride[VPX_PLANE_Y] = mFrameWidth; |
michael@0 | 246 | mVPXImageWrapper->stride[VPX_PLANE_U] = halfWidth; |
michael@0 | 247 | mVPXImageWrapper->stride[VPX_PLANE_V] = halfWidth; |
michael@0 | 248 | } |
michael@0 | 249 | |
michael@0 | 250 | static bool isYUV420(const PlanarYCbCrImage::Data *aData) |
michael@0 | 251 | { |
michael@0 | 252 | if (aData->mYSize == aData->mCbCrSize * 2) { |
michael@0 | 253 | return true; |
michael@0 | 254 | } |
michael@0 | 255 | return false; |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | static bool isYUV422(const PlanarYCbCrImage::Data *aData) |
michael@0 | 259 | { |
michael@0 | 260 | if ((aData->mYSize.width == aData->mCbCrSize.width * 2) && |
michael@0 | 261 | (aData->mYSize.height == aData->mCbCrSize.height)) { |
michael@0 | 262 | return true; |
michael@0 | 263 | } |
michael@0 | 264 | return false; |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | static bool isYUV444(const PlanarYCbCrImage::Data *aData) |
michael@0 | 268 | { |
michael@0 | 269 | if (aData->mYSize == aData->mCbCrSize) { |
michael@0 | 270 | return true; |
michael@0 | 271 | } |
michael@0 | 272 | return false; |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | nsresult VP8TrackEncoder::PrepareRawFrame(VideoChunk &aChunk) |
michael@0 | 276 | { |
michael@0 | 277 | if (aChunk.mFrame.GetForceBlack() || aChunk.IsNull()) { |
michael@0 | 278 | PrepareMutedFrame(); |
michael@0 | 279 | } else { |
michael@0 | 280 | Image* img = aChunk.mFrame.GetImage(); |
michael@0 | 281 | ImageFormat format = img->GetFormat(); |
michael@0 | 282 | if (format != ImageFormat::PLANAR_YCBCR) { |
michael@0 | 283 | VP8LOG("Unsupported video format\n"); |
michael@0 | 284 | return NS_ERROR_FAILURE; |
michael@0 | 285 | } |
michael@0 | 286 | |
michael@0 | 287 | // Cast away constness b/c some of the accessors are non-const |
michael@0 | 288 | PlanarYCbCrImage* yuv = |
michael@0 | 289 | const_cast<PlanarYCbCrImage *>(static_cast<const PlanarYCbCrImage *>(img)); |
michael@0 | 290 | // Big-time assumption here that this is all contiguous data coming |
michael@0 | 291 | // from getUserMedia or other sources. |
michael@0 | 292 | MOZ_ASSERT(yuv); |
michael@0 | 293 | if (!yuv->IsValid()) { |
michael@0 | 294 | NS_WARNING("PlanarYCbCrImage is not valid"); |
michael@0 | 295 | return NS_ERROR_FAILURE; |
michael@0 | 296 | } |
michael@0 | 297 | const PlanarYCbCrImage::Data *data = yuv->GetData(); |
michael@0 | 298 | |
michael@0 | 299 | if (isYUV420(data) && !data->mCbSkip) { // 420 planar |
michael@0 | 300 | mVPXImageWrapper->planes[PLANE_Y] = data->mYChannel; |
michael@0 | 301 | mVPXImageWrapper->planes[PLANE_U] = data->mCbChannel; |
michael@0 | 302 | mVPXImageWrapper->planes[PLANE_V] = data->mCrChannel; |
michael@0 | 303 | mVPXImageWrapper->stride[VPX_PLANE_Y] = data->mYStride; |
michael@0 | 304 | mVPXImageWrapper->stride[VPX_PLANE_U] = data->mCbCrStride; |
michael@0 | 305 | mVPXImageWrapper->stride[VPX_PLANE_V] = data->mCbCrStride; |
michael@0 | 306 | } else { |
michael@0 | 307 | uint32_t yPlaneSize = mFrameWidth * mFrameHeight; |
michael@0 | 308 | uint32_t halfWidth = (mFrameWidth + 1) / 2; |
michael@0 | 309 | uint32_t halfHeight = (mFrameHeight + 1) / 2; |
michael@0 | 310 | uint32_t uvPlaneSize = halfWidth * halfHeight; |
michael@0 | 311 | if (mI420Frame.IsEmpty()) { |
michael@0 | 312 | mI420Frame.SetLength(yPlaneSize + uvPlaneSize * 2); |
michael@0 | 313 | } |
michael@0 | 314 | |
michael@0 | 315 | MOZ_ASSERT(mI420Frame.Length() >= (yPlaneSize + uvPlaneSize * 2)); |
michael@0 | 316 | uint8_t *y = mI420Frame.Elements(); |
michael@0 | 317 | uint8_t *cb = mI420Frame.Elements() + yPlaneSize; |
michael@0 | 318 | uint8_t *cr = mI420Frame.Elements() + yPlaneSize + uvPlaneSize; |
michael@0 | 319 | |
michael@0 | 320 | if (isYUV420(data) && data->mCbSkip) { |
michael@0 | 321 | // If mCbSkip is set, we assume it's nv12 or nv21. |
michael@0 | 322 | if (data->mCbChannel < data->mCrChannel) { // nv12 |
michael@0 | 323 | libyuv::NV12ToI420(data->mYChannel, data->mYStride, |
michael@0 | 324 | data->mCbChannel, data->mCbCrStride, |
michael@0 | 325 | y, mFrameWidth, |
michael@0 | 326 | cb, halfWidth, |
michael@0 | 327 | cr, halfWidth, |
michael@0 | 328 | mFrameWidth, mFrameHeight); |
michael@0 | 329 | } else { // nv21 |
michael@0 | 330 | libyuv::NV21ToI420(data->mYChannel, data->mYStride, |
michael@0 | 331 | data->mCrChannel, data->mCbCrStride, |
michael@0 | 332 | y, mFrameWidth, |
michael@0 | 333 | cb, halfWidth, |
michael@0 | 334 | cr, halfWidth, |
michael@0 | 335 | mFrameWidth, mFrameHeight); |
michael@0 | 336 | } |
michael@0 | 337 | } else if (isYUV444(data) && !data->mCbSkip) { |
michael@0 | 338 | libyuv::I444ToI420(data->mYChannel, data->mYStride, |
michael@0 | 339 | data->mCbChannel, data->mCbCrStride, |
michael@0 | 340 | data->mCrChannel, data->mCbCrStride, |
michael@0 | 341 | y, mFrameWidth, |
michael@0 | 342 | cb, halfWidth, |
michael@0 | 343 | cr, halfWidth, |
michael@0 | 344 | mFrameWidth, mFrameHeight); |
michael@0 | 345 | } else if (isYUV422(data) && !data->mCbSkip) { |
michael@0 | 346 | libyuv::I422ToI420(data->mYChannel, data->mYStride, |
michael@0 | 347 | data->mCbChannel, data->mCbCrStride, |
michael@0 | 348 | data->mCrChannel, data->mCbCrStride, |
michael@0 | 349 | y, mFrameWidth, |
michael@0 | 350 | cb, halfWidth, |
michael@0 | 351 | cr, halfWidth, |
michael@0 | 352 | mFrameWidth, mFrameHeight); |
michael@0 | 353 | } else { |
michael@0 | 354 | VP8LOG("Unsupported planar format\n"); |
michael@0 | 355 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 356 | } |
michael@0 | 357 | |
michael@0 | 358 | mVPXImageWrapper->planes[PLANE_Y] = y; |
michael@0 | 359 | mVPXImageWrapper->planes[PLANE_U] = cb; |
michael@0 | 360 | mVPXImageWrapper->planes[PLANE_V] = cr; |
michael@0 | 361 | mVPXImageWrapper->stride[VPX_PLANE_Y] = mFrameWidth; |
michael@0 | 362 | mVPXImageWrapper->stride[VPX_PLANE_U] = halfWidth; |
michael@0 | 363 | mVPXImageWrapper->stride[VPX_PLANE_V] = halfWidth; |
michael@0 | 364 | } |
michael@0 | 365 | } |
michael@0 | 366 | return NS_OK; |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | // These two define value used in GetNextEncodeOperation to determine the |
michael@0 | 370 | // EncodeOperation for next target frame. |
michael@0 | 371 | #define I_FRAME_RATIO (0.5) |
michael@0 | 372 | #define SKIP_FRAME_RATIO (0.75) |
michael@0 | 373 | |
michael@0 | 374 | /** |
michael@0 | 375 | * Compares the elapsed time from the beginning of GetEncodedTrack and |
michael@0 | 376 | * the processed frame duration in mSourceSegment |
michael@0 | 377 | * in order to set the nextEncodeOperation for next target frame. |
michael@0 | 378 | */ |
michael@0 | 379 | VP8TrackEncoder::EncodeOperation |
michael@0 | 380 | VP8TrackEncoder::GetNextEncodeOperation(TimeDuration aTimeElapsed, |
michael@0 | 381 | TrackTicks aProcessedDuration) |
michael@0 | 382 | { |
michael@0 | 383 | int64_t durationInUsec = |
michael@0 | 384 | FramesToUsecs(aProcessedDuration + mEncodedFrameDuration, |
michael@0 | 385 | mTrackRate).value(); |
michael@0 | 386 | if (aTimeElapsed.ToMicroseconds() > (durationInUsec * SKIP_FRAME_RATIO)) { |
michael@0 | 387 | // The encoder is too slow. |
michael@0 | 388 | // We should skip next frame to consume the mSourceSegment. |
michael@0 | 389 | return SKIP_FRAME; |
michael@0 | 390 | } else if (aTimeElapsed.ToMicroseconds() > (durationInUsec * I_FRAME_RATIO)) { |
michael@0 | 391 | // The encoder is a little slow. |
michael@0 | 392 | // We force the encoder to encode an I-frame to accelerate. |
michael@0 | 393 | return ENCODE_I_FRAME; |
michael@0 | 394 | } else { |
michael@0 | 395 | return ENCODE_NORMAL_FRAME; |
michael@0 | 396 | } |
michael@0 | 397 | } |
michael@0 | 398 | |
michael@0 | 399 | TrackTicks |
michael@0 | 400 | VP8TrackEncoder::CalculateRemainingTicks(TrackTicks aDurationCopied, |
michael@0 | 401 | TrackTicks aEncodedDuration) |
michael@0 | 402 | { |
michael@0 | 403 | return mRemainingTicks + aEncodedDuration - aDurationCopied; |
michael@0 | 404 | } |
michael@0 | 405 | |
michael@0 | 406 | // Try to extend the encodedDuration as long as possible if the target frame |
michael@0 | 407 | // has a long duration. |
michael@0 | 408 | TrackTicks |
michael@0 | 409 | VP8TrackEncoder::CalculateEncodedDuration(TrackTicks aDurationCopied) |
michael@0 | 410 | { |
michael@0 | 411 | TrackTicks temp64 = aDurationCopied; |
michael@0 | 412 | TrackTicks encodedDuration = mEncodedFrameDuration; |
michael@0 | 413 | temp64 -= mRemainingTicks; |
michael@0 | 414 | while (temp64 > mEncodedFrameDuration) { |
michael@0 | 415 | temp64 -= mEncodedFrameDuration; |
michael@0 | 416 | encodedDuration += mEncodedFrameDuration; |
michael@0 | 417 | } |
michael@0 | 418 | return encodedDuration; |
michael@0 | 419 | } |
michael@0 | 420 | |
michael@0 | 421 | /** |
michael@0 | 422 | * Encoding flow in GetEncodedTrack(): |
michael@0 | 423 | * 1: Check the mInitialized state and the packet duration. |
michael@0 | 424 | * 2: Move the data from mRawSegment to mSourceSegment. |
michael@0 | 425 | * 3: Encode the video chunks in mSourceSegment in a for-loop. |
michael@0 | 426 | * 3.1: Pick the video chunk by mRemainingTicks. |
michael@0 | 427 | * 3.2: Calculate the encoding duration for the parameter of vpx_codec_encode(). |
michael@0 | 428 | * The encoding duration is a multiple of mEncodedFrameDuration. |
michael@0 | 429 | * 3.3: Setup the video chunk to mVPXImageWrapper by PrepareRawFrame(). |
michael@0 | 430 | * 3.4: Send frame into vp8 encoder by vpx_codec_encode(). |
michael@0 | 431 | * 3.5: Get the output frame from encoder by calling GetEncodedPartitions(). |
michael@0 | 432 | * 3.6: Calculate the mRemainingTicks for next target frame. |
michael@0 | 433 | * 3.7: Set the nextEncodeOperation for the next target frame. |
michael@0 | 434 | * There is a heuristic: If the frame duration we have processed in |
michael@0 | 435 | * mSourceSegment is 100ms, means that we can't spend more than 100ms to |
michael@0 | 436 | * encode it. |
michael@0 | 437 | * 4. Remove the encoded chunks in mSourceSegment after for-loop. |
michael@0 | 438 | * |
michael@0 | 439 | * Ex1: Input frame rate is 100 => input frame duration is 10ms for each. |
michael@0 | 440 | * mEncodedFrameRate is 30 => output frame duration is 33ms. |
michael@0 | 441 | * In this case, the frame duration in mSourceSegment will be: |
michael@0 | 442 | * 1st : 0~10ms |
michael@0 | 443 | * 2nd : 10~20ms |
michael@0 | 444 | * 3rd : 20~30ms |
michael@0 | 445 | * 4th : 30~40ms |
michael@0 | 446 | * ... |
michael@0 | 447 | * The VP8 encoder will take the 1st and 4th frames to encode. At beginning |
michael@0 | 448 | * mRemainingTicks is 0 for 1st frame, then the mRemainingTicks is set |
michael@0 | 449 | * to 23 to pick the 4th frame. (mEncodedFrameDuration - 1st frame duration) |
michael@0 | 450 | * |
michael@0 | 451 | * Ex2: Input frame rate is 25 => frame duration is 40ms for each. |
michael@0 | 452 | * mEncodedFrameRate is 30 => output frame duration is 33ms. |
michael@0 | 453 | * In this case, the frame duration in mSourceSegment will be: |
michael@0 | 454 | * 1st : 0~40ms |
michael@0 | 455 | * 2nd : 40~80ms |
michael@0 | 456 | * 3rd : 80~120ms |
michael@0 | 457 | * 4th : 120~160ms |
michael@0 | 458 | * ... |
michael@0 | 459 | * Because the input frame duration is 40ms larger than 33ms, so the first |
michael@0 | 460 | * encoded frame duration will be 66ms by calling CalculateEncodedDuration. |
michael@0 | 461 | * And the mRemainingTicks will be set to 26 |
michael@0 | 462 | * (CalculateRemainingTicks 0+66-40) in order to pick the next frame(2nd) |
michael@0 | 463 | * in mSourceSegment. |
michael@0 | 464 | */ |
michael@0 | 465 | nsresult |
michael@0 | 466 | VP8TrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) |
michael@0 | 467 | { |
michael@0 | 468 | { |
michael@0 | 469 | // Move all the samples from mRawSegment to mSourceSegment. We only hold |
michael@0 | 470 | // the monitor in this block. |
michael@0 | 471 | ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
michael@0 | 472 | // Wait if mEncoder is not initialized, or when not enough raw data, but is |
michael@0 | 473 | // not the end of stream nor is being canceled. |
michael@0 | 474 | while (!mCanceled && (!mInitialized || |
michael@0 | 475 | (mRawSegment.GetDuration() + mSourceSegment.GetDuration() < |
michael@0 | 476 | mEncodedFrameDuration && !mEndOfStream))) { |
michael@0 | 477 | mon.Wait(); |
michael@0 | 478 | } |
michael@0 | 479 | if (mCanceled || mEncodingComplete) { |
michael@0 | 480 | return NS_ERROR_FAILURE; |
michael@0 | 481 | } |
michael@0 | 482 | mSourceSegment.AppendFrom(&mRawSegment); |
michael@0 | 483 | } |
michael@0 | 484 | |
michael@0 | 485 | VideoSegment::ChunkIterator iter(mSourceSegment); |
michael@0 | 486 | TrackTicks durationCopied = 0; |
michael@0 | 487 | TrackTicks totalProcessedDuration = 0; |
michael@0 | 488 | TimeStamp timebase = TimeStamp::Now(); |
michael@0 | 489 | EncodeOperation nextEncodeOperation = ENCODE_NORMAL_FRAME; |
michael@0 | 490 | |
michael@0 | 491 | for (; !iter.IsEnded(); iter.Next()) { |
michael@0 | 492 | VideoChunk &chunk = *iter; |
michael@0 | 493 | // Accumulate chunk's duration to durationCopied until it reaches |
michael@0 | 494 | // mRemainingTicks. |
michael@0 | 495 | durationCopied += chunk.GetDuration(); |
michael@0 | 496 | MOZ_ASSERT(mRemainingTicks <= mEncodedFrameDuration); |
michael@0 | 497 | VP8LOG("durationCopied %lld mRemainingTicks %lld\n", |
michael@0 | 498 | durationCopied, mRemainingTicks); |
michael@0 | 499 | if (durationCopied >= mRemainingTicks) { |
michael@0 | 500 | VP8LOG("nextEncodeOperation is %d\n",nextEncodeOperation); |
michael@0 | 501 | // Calculate encodedDuration for this target frame. |
michael@0 | 502 | TrackTicks encodedDuration = CalculateEncodedDuration(durationCopied); |
michael@0 | 503 | |
michael@0 | 504 | // Encode frame. |
michael@0 | 505 | if (nextEncodeOperation != SKIP_FRAME) { |
michael@0 | 506 | nsresult rv = PrepareRawFrame(chunk); |
michael@0 | 507 | NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); |
michael@0 | 508 | |
michael@0 | 509 | // Encode the data with VP8 encoder |
michael@0 | 510 | int flags = (nextEncodeOperation == ENCODE_NORMAL_FRAME) ? |
michael@0 | 511 | 0 : VPX_EFLAG_FORCE_KF; |
michael@0 | 512 | if (vpx_codec_encode(mVPXContext, mVPXImageWrapper, mEncodedTimestamp, |
michael@0 | 513 | (unsigned long)encodedDuration, flags, |
michael@0 | 514 | VPX_DL_REALTIME)) { |
michael@0 | 515 | return NS_ERROR_FAILURE; |
michael@0 | 516 | } |
michael@0 | 517 | // Get the encoded data from VP8 encoder. |
michael@0 | 518 | GetEncodedPartitions(aData); |
michael@0 | 519 | } else { |
michael@0 | 520 | // SKIP_FRAME |
michael@0 | 521 | // Extend the duration of the last encoded data in aData |
michael@0 | 522 | // because this frame will be skip. |
michael@0 | 523 | nsRefPtr<EncodedFrame> last = nullptr; |
michael@0 | 524 | last = aData.GetEncodedFrames().LastElement(); |
michael@0 | 525 | if (last) { |
michael@0 | 526 | last->SetDuration(last->GetDuration() + encodedDuration); |
michael@0 | 527 | } |
michael@0 | 528 | } |
michael@0 | 529 | // Move forward the mEncodedTimestamp. |
michael@0 | 530 | mEncodedTimestamp += encodedDuration; |
michael@0 | 531 | totalProcessedDuration += durationCopied; |
michael@0 | 532 | // Calculate mRemainingTicks for next target frame. |
michael@0 | 533 | mRemainingTicks = CalculateRemainingTicks(durationCopied, |
michael@0 | 534 | encodedDuration); |
michael@0 | 535 | |
michael@0 | 536 | // Check the remain data is enough for next target frame. |
michael@0 | 537 | if (mSourceSegment.GetDuration() - totalProcessedDuration |
michael@0 | 538 | >= mEncodedFrameDuration) { |
michael@0 | 539 | TimeDuration elapsedTime = TimeStamp::Now() - timebase; |
michael@0 | 540 | nextEncodeOperation = GetNextEncodeOperation(elapsedTime, |
michael@0 | 541 | totalProcessedDuration); |
michael@0 | 542 | // Reset durationCopied for next iteration. |
michael@0 | 543 | durationCopied = 0; |
michael@0 | 544 | } else { |
michael@0 | 545 | // Process done, there is no enough data left for next iteration, |
michael@0 | 546 | // break the for-loop. |
michael@0 | 547 | break; |
michael@0 | 548 | } |
michael@0 | 549 | } |
michael@0 | 550 | } |
michael@0 | 551 | // Remove the chunks we have processed. |
michael@0 | 552 | mSourceSegment.RemoveLeading(totalProcessedDuration); |
michael@0 | 553 | VP8LOG("RemoveLeading %lld\n",totalProcessedDuration); |
michael@0 | 554 | |
michael@0 | 555 | // End of stream, pull the rest frames in encoder. |
michael@0 | 556 | if (mEndOfStream) { |
michael@0 | 557 | VP8LOG("mEndOfStream is true\n"); |
michael@0 | 558 | mEncodingComplete = true; |
michael@0 | 559 | if (vpx_codec_encode(mVPXContext, nullptr, mEncodedTimestamp, |
michael@0 | 560 | mEncodedFrameDuration, 0, VPX_DL_REALTIME)) { |
michael@0 | 561 | return NS_ERROR_FAILURE; |
michael@0 | 562 | } |
michael@0 | 563 | GetEncodedPartitions(aData); |
michael@0 | 564 | } |
michael@0 | 565 | |
michael@0 | 566 | return NS_OK ; |
michael@0 | 567 | } |
michael@0 | 568 | |
michael@0 | 569 | } // namespace mozilla |