1 /**
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <IVideoRendererNode.h>
18 #include <ImsMediaVideoRenderer.h>
19 #include <ImsMediaTrace.h>
20 #include <ImsMediaTimer.h>
21 #include <ImsMediaBitReader.h>
22 #include <VideoConfig.h>
23 #include <ImsMediaVideoUtil.h>
24 #include <VideoJitterBuffer.h>
25 #include <string.h>
26
27 using namespace android::telephony::imsmedia;
28
29 #define DEFAULT_UNDEFINED (-1)
30
IVideoRendererNode(BaseSessionCallback * callback)31 IVideoRendererNode::IVideoRendererNode(BaseSessionCallback* callback) :
32 JitterBufferControlNode(callback, IMS_MEDIA_VIDEO)
33 {
34 std::unique_ptr<ImsMediaVideoRenderer> renderer(new ImsMediaVideoRenderer());
35 mVideoRenderer = std::move(renderer);
36 mVideoRenderer->SetSessionCallback(mCallback);
37
38 if (mJitterBuffer)
39 {
40 mJitterBuffer->SetSessionCallback(mCallback);
41 }
42
43 mWindow = nullptr;
44 mCondition.reset();
45 mCodecType = DEFAULT_UNDEFINED;
46 mWidth = 0;
47 mHeight = 0;
48 mSamplingRate = 0;
49 mCvoValue = 0;
50 memset(mConfigBuffer, 0, MAX_CONFIG_INDEX * MAX_CONFIG_LEN * sizeof(uint8_t));
51 memset(mConfigLen, 0, MAX_CONFIG_INDEX * sizeof(uint32_t));
52 mDeviceOrientation = 0;
53 mFirstFrame = false;
54 mSubtype = MEDIASUBTYPE_UNDEFINED;
55 mFramerate = 0;
56 mLossDuration = 0;
57 mLossRateThreshold = 0;
58 }
59
~IVideoRendererNode()60 IVideoRendererNode::~IVideoRendererNode() {}
61
GetNodeId()62 kBaseNodeId IVideoRendererNode::GetNodeId()
63 {
64 return kNodeIdVideoRenderer;
65 }
66
Start()67 ImsMediaResult IVideoRendererNode::Start()
68 {
69 IMLOGD1("[Start] codec[%d]", mCodecType);
70 if (mJitterBuffer)
71 {
72 VideoJitterBuffer* jitter = reinterpret_cast<VideoJitterBuffer*>(mJitterBuffer);
73 jitter->SetCodecType(mCodecType);
74 jitter->SetFramerate(mFramerate);
75 jitter->SetJitterBufferSize(15, 15, 25);
76 jitter->StartTimer(mLossDuration / 1000, mLossRateThreshold);
77 }
78
79 Reset();
80 std::lock_guard<std::mutex> guard(mMutex);
81
82 if (mVideoRenderer)
83 {
84 mVideoRenderer->SetCodec(mCodecType);
85 mVideoRenderer->SetResolution(mWidth, mHeight);
86 mVideoRenderer->SetDeviceOrientation(mDeviceOrientation);
87 mVideoRenderer->SetSurface(mWindow);
88
89 if (!mVideoRenderer->Start())
90 {
91 return RESULT_NOT_READY;
92 }
93 }
94
95 mFirstFrame = false;
96 mNodeState = kNodeStateRunning;
97 return RESULT_SUCCESS;
98 }
99
Stop()100 void IVideoRendererNode::Stop()
101 {
102 IMLOGD0("[Stop]");
103 std::lock_guard<std::mutex> guard(mMutex);
104
105 if (mVideoRenderer)
106 {
107 mVideoRenderer->Stop();
108 }
109
110 if (mJitterBuffer != nullptr)
111 {
112 VideoJitterBuffer* jitter = reinterpret_cast<VideoJitterBuffer*>(mJitterBuffer);
113 jitter->StopTimer();
114 }
115
116 mNodeState = kNodeStateStopped;
117 }
118
IsRunTime()119 bool IVideoRendererNode::IsRunTime()
120 {
121 return false;
122 }
123
IsSourceNode()124 bool IVideoRendererNode::IsSourceNode()
125 {
126 return false;
127 }
128
SetConfig(void * config)129 void IVideoRendererNode::SetConfig(void* config)
130 {
131 if (config == nullptr)
132 {
133 return;
134 }
135
136 VideoConfig* pConfig = reinterpret_cast<VideoConfig*>(config);
137 mCodecType = ImsMediaVideoUtil::ConvertCodecType(pConfig->getCodecType());
138 mSamplingRate = pConfig->getSamplingRateKHz();
139 mWidth = pConfig->getResolutionWidth();
140 mHeight = pConfig->getResolutionHeight();
141 mCvoValue = pConfig->getCvoValue();
142 mDeviceOrientation = pConfig->getDeviceOrientationDegree();
143 mFramerate = pConfig->getFramerate();
144 }
145
IsSameConfig(void * config)146 bool IVideoRendererNode::IsSameConfig(void* config)
147 {
148 if (config == nullptr)
149 {
150 return true;
151 }
152
153 VideoConfig* pConfig = reinterpret_cast<VideoConfig*>(config);
154 return (mCodecType == ImsMediaVideoUtil::ConvertCodecType(pConfig->getCodecType()) &&
155 mWidth == pConfig->getResolutionWidth() && mHeight == pConfig->getResolutionHeight() &&
156 mCvoValue == pConfig->getCvoValue() &&
157 mDeviceOrientation == pConfig->getDeviceOrientationDegree() &&
158 mSamplingRate == pConfig->getSamplingRateKHz());
159 }
160
ProcessData()161 void IVideoRendererNode::ProcessData()
162 {
163 std::lock_guard<std::mutex> guard(mMutex);
164 uint8_t* data = nullptr;
165 uint32_t dataSize = 0;
166 uint32_t prevTimestamp = 0;
167 bool mark = false;
168 uint32_t seq = 0;
169 uint32_t timestamp = 0;
170 uint32_t frameSize = 0;
171 ImsMediaSubType subtype = MEDIASUBTYPE_UNDEFINED;
172 uint32_t initialSeq = 0;
173 ImsMediaSubType dataType;
174
175 while (GetData(&subtype, &data, &dataSize, ×tamp, &mark, &seq, &dataType))
176 {
177 IMLOGD_PACKET4(IM_PACKET_LOG_VIDEO,
178 "[ProcessData] subtype[%d], Size[%d], TS[%d] frameSize[%d]", subtype, dataSize,
179 timestamp, frameSize);
180
181 if (prevTimestamp == 0)
182 {
183 prevTimestamp = timestamp;
184 }
185 else if (timestamp != prevTimestamp || (frameSize != 0 && hasStartingCode(data, dataSize)))
186 {
187 // break when the timestamp is changed or next data has another starting code
188 break;
189 }
190
191 if (dataSize >= MAX_RTP_PAYLOAD_BUFFER_SIZE)
192 {
193 IMLOGE1("[ProcessData] exceed buffer size[%d]", dataSize);
194 return;
195 }
196
197 memcpy(mBuffer + frameSize, data, dataSize);
198 frameSize += dataSize;
199
200 if (initialSeq == 0)
201 {
202 initialSeq = seq;
203 }
204
205 DeleteData();
206
207 if (mark)
208 {
209 break;
210 }
211 }
212
213 if (frameSize == 0)
214 {
215 return;
216 }
217
218 // remove AUD nal unit
219 uint32_t size = frameSize;
220 uint8_t* buffer = mBuffer;
221 RemoveAUDNalUnit(mBuffer, frameSize, &buffer, &size);
222
223 FrameType frameType = GetFrameType(buffer, size);
224
225 if (frameType == SPS)
226 {
227 SaveConfigFrame(buffer, size, kConfigSps);
228 tCodecConfig codecConfig;
229
230 if (mCodecType == kVideoCodecAvc)
231 {
232 if (ImsMediaVideoUtil::ParseAvcSps(buffer, size, &codecConfig))
233 {
234 CheckResolution(codecConfig.nWidth, codecConfig.nHeight);
235 }
236 }
237 else if (mCodecType == kVideoCodecHevc)
238 {
239 if (ImsMediaVideoUtil::ParseHevcSps(buffer, size, &codecConfig))
240 {
241 CheckResolution(codecConfig.nWidth, codecConfig.nHeight);
242 }
243 }
244
245 return;
246 }
247 else if (frameType == PPS)
248 {
249 SaveConfigFrame(buffer, size, kConfigPps);
250 return;
251 }
252 else if (frameType == VPS)
253 {
254 SaveConfigFrame(buffer, size, kConfigVps);
255 return;
256 }
257
258 IMLOGD_PACKET2(IM_PACKET_LOG_VIDEO, "[ProcessData] frame type[%d] size[%d]", frameType, size);
259
260 // TODO: Send PLI or FIR when I-frame wasn't received since beginning.
261
262 if (!mFirstFrame)
263 {
264 IMLOGD0("[ProcessData] notify first frame");
265 mFirstFrame = true;
266
267 if (mCallback != nullptr)
268 {
269 mCallback->SendEvent(kImsMediaEventFirstPacketReceived);
270
271 if (mCvoValue <= 0)
272 {
273 mCallback->SendEvent(kImsMediaEventResolutionChanged, mWidth, mHeight);
274 }
275 }
276 }
277
278 // cvo
279 if (mCvoValue > 0)
280 {
281 if (mSubtype == MEDIASUBTYPE_UNDEFINED && subtype == MEDIASUBTYPE_UNDEFINED)
282 {
283 subtype = MEDIASUBTYPE_ROT0;
284 }
285
286 // rotation changed
287 if (mSubtype != subtype && (subtype >= MEDIASUBTYPE_ROT0 && subtype <= MEDIASUBTYPE_ROT270))
288 {
289 mSubtype = subtype;
290 int degree = 0;
291
292 switch (mSubtype)
293 {
294 default:
295 case MEDIASUBTYPE_ROT0:
296 degree = 0;
297 break;
298 case MEDIASUBTYPE_ROT90:
299 degree = 90;
300 break;
301 case MEDIASUBTYPE_ROT180:
302 degree = 180;
303 break;
304 case MEDIASUBTYPE_ROT270:
305 degree = 270;
306 break;
307 }
308
309 mVideoRenderer->UpdatePeerOrientation(degree);
310 NotifyPeerDimensionChanged();
311 }
312 }
313
314 // send config frames before send I frame
315 if (frameType == IDR)
316 {
317 QueueConfigFrame(timestamp);
318 }
319
320 uint32_t currentTime = ImsMediaTimer::GetTimeInMilliSeconds();
321
322 if (mPrevTimestamp == 0)
323 {
324 mPrevTimestamp = currentTime;
325 }
326 else if (currentTime - mPrevTimestamp > 1000)
327 {
328 IMLOGD1("[ProcessData] current fps=%u", mCurrentFramerate);
329 mPrevTimestamp = currentTime;
330 mCurrentFramerate = 0;
331 }
332
333 mCurrentFramerate++;
334 mVideoRenderer->OnDataFrame(buffer, size, timestamp, false);
335 }
336
UpdateSurface(ANativeWindow * window)337 void IVideoRendererNode::UpdateSurface(ANativeWindow* window)
338 {
339 IMLOGD1("[UpdateSurface] surface[%p]", window);
340 mWindow = window;
341 }
342
UpdateRoundTripTimeDelay(int32_t delay)343 void IVideoRendererNode::UpdateRoundTripTimeDelay(int32_t delay)
344 {
345 IMLOGD1("[UpdateRoundTripTimeDelay] delay[%d]", delay);
346
347 if (mJitterBuffer != nullptr)
348 {
349 VideoJitterBuffer* jitter = reinterpret_cast<VideoJitterBuffer*>(mJitterBuffer);
350
351 // calculate Response wait time : RWT = RTTD (mm) + 2 * frame duration
352 jitter->SetResponseWaitTime((delay / DEMON_NTP2MSEC) + 2 * (1000 / mFramerate));
353 }
354 }
355
SetPacketLossParam(uint32_t time,uint32_t rate)356 void IVideoRendererNode::SetPacketLossParam(uint32_t time, uint32_t rate)
357 {
358 IMLOGD2("[SetPacketLossParam] time[%d], rate[%d]", time, rate);
359
360 mLossDuration = time;
361 mLossRateThreshold = rate;
362 }
363
hasStartingCode(uint8_t * buffer,uint32_t bufferSize)364 bool IVideoRendererNode::hasStartingCode(uint8_t* buffer, uint32_t bufferSize)
365 {
366 if (bufferSize <= 4)
367 {
368 return false;
369 }
370
371 // Check for NAL unit delimiter 0x00000001
372 if (buffer[0] == 0x00 && buffer[1] == 0x00 && buffer[2] == 0x00 && buffer[3] == 0x01)
373 {
374 return true;
375 }
376
377 return false;
378 }
379
GetFrameType(uint8_t * buffer,uint32_t bufferSize)380 FrameType IVideoRendererNode::GetFrameType(uint8_t* buffer, uint32_t bufferSize)
381 {
382 if (!hasStartingCode(buffer, bufferSize))
383 {
384 return UNKNOWN;
385 }
386
387 uint8_t nalType = buffer[4];
388
389 switch (mCodecType)
390 {
391 case kVideoCodecAvc:
392 {
393 if ((nalType & 0x1F) == 5)
394 {
395 return IDR;
396 }
397 else if ((nalType & 0x1F) == 7)
398 {
399 return SPS;
400 }
401 else if ((nalType & 0x1F) == 8)
402 {
403 return PPS;
404 }
405 else
406 {
407 return NonIDR;
408 }
409
410 break;
411 }
412 case kVideoCodecHevc:
413 {
414 if (((nalType >> 1) & 0x3F) == 19 || ((nalType >> 1) & 0x3F) == 20)
415 {
416 return IDR;
417 }
418 else if (((nalType >> 1) & 0x3F) == 32)
419 {
420 return VPS;
421 }
422 else if (((nalType >> 1) & 0x3F) == 33)
423 {
424 return SPS;
425 }
426 else if (((nalType >> 1) & 0x3F) == 34)
427 {
428 return PPS;
429 }
430 else
431 {
432 return NonIDR;
433 }
434
435 break;
436 }
437 default:
438 IMLOGE1("[GetFrameType] Invalid video codec type %d", mCodecType);
439 }
440
441 return UNKNOWN;
442 }
443
SaveConfigFrame(uint8_t * pbBuffer,uint32_t nBufferSize,uint32_t eMode)444 void IVideoRendererNode::SaveConfigFrame(uint8_t* pbBuffer, uint32_t nBufferSize, uint32_t eMode)
445 {
446 bool bSPSString = false;
447 bool bPPSString = false;
448
449 if (nBufferSize <= 4)
450 {
451 return;
452 }
453
454 IMLOGD_PACKET3(IM_PACKET_LOG_VIDEO, "[SaveConfigFrame] mode[%d], size[%d], data[%s]", eMode,
455 nBufferSize,
456 ImsMediaTrace::IMTrace_Bin2String(
457 reinterpret_cast<const char*>(pbBuffer), nBufferSize > 52 ? 52 : nBufferSize));
458
459 switch (mCodecType)
460 {
461 case kVideoCodecAvc:
462 {
463 uint32_t nCurrSize = 0;
464 uint32_t nOffset = 0;
465 uint32_t nConfigSize = 0;
466 uint8_t* nCurrBuff = pbBuffer;
467
468 while (nCurrSize <= nBufferSize)
469 {
470 if (nCurrBuff[0] == 0x00 && nCurrBuff[1] == 0x00 && nCurrBuff[2] == 0x00 &&
471 nCurrBuff[3] == 0x01)
472 {
473 if (eMode == kConfigSps && !bSPSString && ((nCurrBuff[4] & 0x1F) == 7))
474 {
475 nOffset = nCurrSize;
476 bSPSString = true;
477 }
478 else if (eMode == kConfigPps && !bPPSString && ((nCurrBuff[4] & 0x1F) == 8))
479 {
480 nOffset = nCurrSize;
481 bPPSString = true;
482 }
483 else if (bSPSString || bPPSString)
484 {
485 nConfigSize = nCurrSize - nOffset;
486 break;
487 }
488 }
489
490 nCurrBuff++;
491 nCurrSize++;
492 }
493
494 if ((bSPSString || bPPSString) && nConfigSize == 0)
495 {
496 nConfigSize = nBufferSize - nOffset;
497 }
498
499 IMLOGD_PACKET3(IM_PACKET_LOG_VIDEO,
500 "[SaveConfigFrame] AVC Codec - bSps[%d], bPps[%d], size[%d]", bSPSString,
501 bPPSString, nConfigSize);
502
503 // save
504 if (bSPSString || bPPSString)
505 {
506 uint8_t* pConfigData = nullptr;
507 uint32_t nConfigIndex = 0;
508
509 if (eMode == kConfigSps)
510 {
511 nConfigIndex = 0;
512 }
513 else if (eMode == kConfigPps)
514 {
515 nConfigIndex = 1;
516 }
517 else
518 {
519 return;
520 }
521
522 pConfigData = mConfigBuffer[nConfigIndex];
523
524 if (0 != memcmp(pConfigData, pbBuffer + nOffset, nConfigSize))
525 {
526 memcpy(pConfigData, pbBuffer + nOffset, nConfigSize);
527 mConfigLen[nConfigIndex] = nConfigSize;
528 }
529 }
530 break;
531 }
532
533 case kVideoCodecHevc:
534 {
535 uint32_t nCurrSize = 0;
536 uint32_t nOffset = 0;
537 uint32_t nConfigSize = 0;
538 uint8_t* nCurrBuff = pbBuffer;
539 bool bVPSString = false;
540
541 while (nCurrSize <= nBufferSize)
542 {
543 if (nCurrBuff[0] == 0x00 && nCurrBuff[1] == 0x00 && nCurrBuff[2] == 0x00 &&
544 nCurrBuff[3] == 0x01)
545 {
546 if (eMode == kConfigVps && !bVPSString && (((nCurrBuff[4] >> 1) & 0x3F) == 32))
547 {
548 nOffset = nCurrSize;
549 bVPSString = true;
550 break;
551 }
552 else if (eMode == kConfigSps && !bSPSString &&
553 (((nCurrBuff[4] >> 1) & 0x3F) == 33))
554 {
555 nOffset = nCurrSize;
556 bSPSString = true;
557 break;
558 }
559 else if (eMode == kConfigPps && !bPPSString &&
560 (((nCurrBuff[4] >> 1) & 0x3F) == 34))
561 {
562 nOffset = nCurrSize;
563 bPPSString = true;
564 break;
565 }
566 }
567
568 nCurrBuff++;
569 nCurrSize++;
570 }
571
572 if (bVPSString || bSPSString || bPPSString)
573 {
574 if ((nBufferSize - nOffset) > 0)
575 {
576 nConfigSize = nBufferSize - nOffset;
577 }
578 }
579
580 IMLOGD_PACKET4(IM_PACKET_LOG_VIDEO,
581 "[SaveConfigFrame - H265] bVPS[%d], bSPS[%d], bPPS[%d], nConfigSize[%d]",
582 bVPSString, bSPSString, bPPSString, nConfigSize);
583
584 // save
585 if (bVPSString || bSPSString || bPPSString)
586 {
587 uint8_t* pConfigData = nullptr;
588 uint32_t nConfigIndex = 0;
589
590 if (eMode == kConfigVps)
591 {
592 nConfigIndex = 0;
593 }
594 else if (eMode == kConfigSps)
595 {
596 nConfigIndex = 1;
597 }
598 else if (eMode == kConfigPps)
599 {
600 nConfigIndex = 2;
601 }
602 else
603 {
604 return;
605 }
606
607 pConfigData = mConfigBuffer[nConfigIndex];
608
609 if (0 != memcmp(pConfigData, pbBuffer + nOffset, nConfigSize))
610 {
611 memcpy(pConfigData, pbBuffer + nOffset, nConfigSize);
612 mConfigLen[nConfigIndex] = nConfigSize;
613 }
614 }
615 break;
616 }
617 default:
618 return;
619 }
620 }
621
RemoveAUDNalUnit(uint8_t * inBuffer,uint32_t inBufferSize,uint8_t ** outBuffer,uint32_t * outBufferSize)622 bool IVideoRendererNode::RemoveAUDNalUnit(
623 uint8_t* inBuffer, uint32_t inBufferSize, uint8_t** outBuffer, uint32_t* outBufferSize)
624 {
625 bool IsAudUnit = false;
626 *outBuffer = inBuffer;
627 *outBufferSize = inBufferSize;
628
629 if (inBufferSize <= 4)
630 {
631 return false;
632 }
633
634 switch (mCodecType)
635 {
636 case kVideoCodecAvc:
637 {
638 uint32_t currSize = inBufferSize;
639 uint8_t* currBuffer = inBuffer;
640 uint32_t count = 0;
641
642 while (currSize >= 5 && count <= 12)
643 {
644 if (IsAudUnit &&
645 (currBuffer[0] == 0x00 && currBuffer[1] == 0x00 && currBuffer[2] == 0x00 &&
646 currBuffer[3] == 0x01))
647 {
648 *outBuffer = currBuffer;
649 *outBufferSize = currSize;
650 break;
651 }
652 if (currBuffer[0] == 0x00 && currBuffer[1] == 0x00 && currBuffer[2] == 0x00 &&
653 currBuffer[3] == 0x01 && currBuffer[4] == 0x09)
654 {
655 IsAudUnit = true;
656 }
657
658 currBuffer++;
659 currSize--;
660 count++;
661 }
662 }
663 break;
664 case kVideoCodecHevc:
665 default:
666 return false;
667 }
668
669 return IsAudUnit;
670 }
671
CheckResolution(uint32_t nWidth,uint32_t nHeight)672 void IVideoRendererNode::CheckResolution(uint32_t nWidth, uint32_t nHeight)
673 {
674 if ((nWidth != 0 && nWidth != mWidth) || (nHeight != 0 && nHeight != mHeight))
675 {
676 IMLOGD4("[CheckResolution] resolution change[%dx%d] to [%dx%d]", mWidth, mHeight, nWidth,
677 nHeight);
678 mWidth = nWidth;
679 mHeight = nHeight;
680
681 NotifyPeerDimensionChanged();
682 }
683 }
684
QueueConfigFrame(uint32_t timestamp)685 void IVideoRendererNode::QueueConfigFrame(uint32_t timestamp)
686 {
687 uint32_t nNumOfConfigString = 0;
688 if (mCodecType == kVideoCodecAvc)
689 {
690 nNumOfConfigString = 2;
691 }
692 else if (mCodecType == kVideoCodecHevc)
693 {
694 nNumOfConfigString = 3;
695 }
696
697 for (int32_t i = 0; i < nNumOfConfigString; i++)
698 {
699 uint8_t* configFrame = nullptr;
700 uint32_t configLen = mConfigLen[i];
701 configFrame = mConfigBuffer[i];
702
703 if (configLen == 0 || mVideoRenderer == nullptr)
704 {
705 continue;
706 }
707
708 mVideoRenderer->OnDataFrame(configFrame, configLen, timestamp, true);
709 }
710 }
711
NotifyPeerDimensionChanged()712 void IVideoRendererNode::NotifyPeerDimensionChanged()
713 {
714 if (mCallback == nullptr)
715 {
716 return;
717 }
718
719 IMLOGD1("[NotifyPeerDimensionChanged] subtype[%d]", mSubtype);
720
721 // assume the device is portrait
722 if (mWidth > mHeight) // landscape
723 {
724 // local rotation
725 if (mDeviceOrientation == 0 || mDeviceOrientation == 180)
726 {
727 // peer rotation
728 if (mSubtype == MEDIASUBTYPE_ROT0 || mSubtype == MEDIASUBTYPE_ROT180)
729 {
730 mCallback->SendEvent(kImsMediaEventResolutionChanged, mWidth, mHeight);
731 }
732 else if (mSubtype == MEDIASUBTYPE_ROT90 || mSubtype == MEDIASUBTYPE_ROT270)
733 {
734 mCallback->SendEvent(kImsMediaEventResolutionChanged, mHeight, mWidth);
735 }
736 }
737 else
738 {
739 // peer rotation
740 if (mSubtype == MEDIASUBTYPE_ROT0 || mSubtype == MEDIASUBTYPE_ROT180)
741 {
742 mCallback->SendEvent(kImsMediaEventResolutionChanged, mHeight, mWidth);
743 }
744 else if (mSubtype == MEDIASUBTYPE_ROT90 || mSubtype == MEDIASUBTYPE_ROT270)
745 {
746 mCallback->SendEvent(kImsMediaEventResolutionChanged, mWidth, mHeight);
747 }
748 }
749 }
750 else // portrait
751 {
752 // peer rotation
753 if (mSubtype == MEDIASUBTYPE_ROT0 || mSubtype == MEDIASUBTYPE_ROT180)
754 {
755 mCallback->SendEvent(kImsMediaEventResolutionChanged, mWidth, mHeight);
756 }
757 else if (mSubtype == MEDIASUBTYPE_ROT90 || mSubtype == MEDIASUBTYPE_ROT270)
758 {
759 mCallback->SendEvent(kImsMediaEventResolutionChanged, mHeight, mWidth);
760 }
761 }
762 }