content/media/encoder/VP8TrackEncoder.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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

mercurial