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, &timestamp, &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 }