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 mVideoRenderer->OnDataFrame(buffer, size, timestamp, false);
321 }
322
UpdateSurface(ANativeWindow * window)323 void IVideoRendererNode::UpdateSurface(ANativeWindow* window)
324 {
325 IMLOGD1("[UpdateSurface] surface[%p]", window);
326 mWindow = window;
327 }
328
UpdateRoundTripTimeDelay(int32_t delay)329 void IVideoRendererNode::UpdateRoundTripTimeDelay(int32_t delay)
330 {
331 IMLOGD1("[UpdateRoundTripTimeDelay] delay[%d]", delay);
332
333 if (mJitterBuffer != nullptr)
334 {
335 VideoJitterBuffer* jitter = reinterpret_cast<VideoJitterBuffer*>(mJitterBuffer);
336
337 // calculate Response wait time : RWT = RTTD (mm) + 2 * frame duration
338 jitter->SetResponseWaitTime((delay / DEMON_NTP2MSEC) + 2 * (1000 / mFramerate));
339 }
340 }
341
SetPacketLossParam(uint32_t time,uint32_t rate)342 void IVideoRendererNode::SetPacketLossParam(uint32_t time, uint32_t rate)
343 {
344 IMLOGD2("[SetPacketLossParam] time[%d], rate[%d]", time, rate);
345
346 mLossDuration = time;
347 mLossRateThreshold = rate;
348 }
349
hasStartingCode(uint8_t * buffer,uint32_t bufferSize)350 bool IVideoRendererNode::hasStartingCode(uint8_t* buffer, uint32_t bufferSize)
351 {
352 if (bufferSize <= 4)
353 {
354 return false;
355 }
356
357 // Check for NAL unit delimiter 0x00000001
358 if (buffer[0] == 0x00 && buffer[1] == 0x00 && buffer[2] == 0x00 && buffer[3] == 0x01)
359 {
360 return true;
361 }
362
363 return false;
364 }
365
GetFrameType(uint8_t * buffer,uint32_t bufferSize)366 FrameType IVideoRendererNode::GetFrameType(uint8_t* buffer, uint32_t bufferSize)
367 {
368 if (!hasStartingCode(buffer, bufferSize))
369 {
370 return UNKNOWN;
371 }
372
373 uint8_t nalType = buffer[4];
374
375 switch (mCodecType)
376 {
377 case kVideoCodecAvc:
378 {
379 if ((nalType & 0x1F) == 5)
380 {
381 return IDR;
382 }
383 else if ((nalType & 0x1F) == 7)
384 {
385 return SPS;
386 }
387 else if ((nalType & 0x1F) == 8)
388 {
389 return PPS;
390 }
391 else
392 {
393 return NonIDR;
394 }
395
396 break;
397 }
398 case kVideoCodecHevc:
399 {
400 if (((nalType >> 1) & 0x3F) == 19 || ((nalType >> 1) & 0x3F) == 20)
401 {
402 return IDR;
403 }
404 else if (((nalType >> 1) & 0x3F) == 32)
405 {
406 return VPS;
407 }
408 else if (((nalType >> 1) & 0x3F) == 33)
409 {
410 return SPS;
411 }
412 else if (((nalType >> 1) & 0x3F) == 34)
413 {
414 return PPS;
415 }
416 else
417 {
418 return NonIDR;
419 }
420
421 break;
422 }
423 default:
424 IMLOGE1("[GetFrameType] Invalid video codec type %d", mCodecType);
425 }
426
427 return UNKNOWN;
428 }
429
SaveConfigFrame(uint8_t * pbBuffer,uint32_t nBufferSize,uint32_t eMode)430 void IVideoRendererNode::SaveConfigFrame(uint8_t* pbBuffer, uint32_t nBufferSize, uint32_t eMode)
431 {
432 bool bSPSString = false;
433 bool bPPSString = false;
434
435 if (nBufferSize <= 4)
436 {
437 return;
438 }
439
440 IMLOGD_PACKET3(IM_PACKET_LOG_VIDEO, "[SaveConfigFrame] mode[%d], size[%d], data[%s]", eMode,
441 nBufferSize,
442 ImsMediaTrace::IMTrace_Bin2String(
443 reinterpret_cast<const char*>(pbBuffer), nBufferSize > 52 ? 52 : nBufferSize));
444
445 switch (mCodecType)
446 {
447 case kVideoCodecAvc:
448 {
449 uint32_t nCurrSize = 0;
450 uint32_t nOffset = 0;
451 uint32_t nConfigSize = 0;
452 uint8_t* nCurrBuff = pbBuffer;
453
454 while (nCurrSize <= nBufferSize)
455 {
456 if (nCurrBuff[0] == 0x00 && nCurrBuff[1] == 0x00 && nCurrBuff[2] == 0x00 &&
457 nCurrBuff[3] == 0x01)
458 {
459 if (eMode == kConfigSps && !bSPSString && ((nCurrBuff[4] & 0x1F) == 7))
460 {
461 nOffset = nCurrSize;
462 bSPSString = true;
463 }
464 else if (eMode == kConfigPps && !bPPSString && ((nCurrBuff[4] & 0x1F) == 8))
465 {
466 nOffset = nCurrSize;
467 bPPSString = true;
468 }
469 else if (bSPSString || bPPSString)
470 {
471 nConfigSize = nCurrSize - nOffset;
472 break;
473 }
474 }
475
476 nCurrBuff++;
477 nCurrSize++;
478 }
479
480 if ((bSPSString || bPPSString) && nConfigSize == 0)
481 {
482 nConfigSize = nBufferSize - nOffset;
483 }
484
485 IMLOGD_PACKET3(IM_PACKET_LOG_VIDEO,
486 "[SaveConfigFrame] AVC Codec - bSps[%d], bPps[%d], size[%d]", bSPSString,
487 bPPSString, nConfigSize);
488
489 // save
490 if (bSPSString || bPPSString)
491 {
492 uint8_t* pConfigData = nullptr;
493 uint32_t nConfigIndex = 0;
494
495 if (eMode == kConfigSps)
496 {
497 nConfigIndex = 0;
498 }
499 else if (eMode == kConfigPps)
500 {
501 nConfigIndex = 1;
502 }
503 else
504 {
505 return;
506 }
507
508 pConfigData = mConfigBuffer[nConfigIndex];
509
510 if (0 != memcmp(pConfigData, pbBuffer + nOffset, nConfigSize))
511 {
512 memcpy(pConfigData, pbBuffer + nOffset, nConfigSize);
513 mConfigLen[nConfigIndex] = nConfigSize;
514 }
515 }
516 break;
517 }
518
519 case kVideoCodecHevc:
520 {
521 uint32_t nCurrSize = 0;
522 uint32_t nOffset = 0;
523 uint32_t nConfigSize = 0;
524 uint8_t* nCurrBuff = pbBuffer;
525 bool bVPSString = false;
526
527 while (nCurrSize <= nBufferSize)
528 {
529 if (nCurrBuff[0] == 0x00 && nCurrBuff[1] == 0x00 && nCurrBuff[2] == 0x00 &&
530 nCurrBuff[3] == 0x01)
531 {
532 if (eMode == kConfigVps && !bVPSString && (((nCurrBuff[4] >> 1) & 0x3F) == 32))
533 {
534 nOffset = nCurrSize;
535 bVPSString = true;
536 break;
537 }
538 else if (eMode == kConfigSps && !bSPSString &&
539 (((nCurrBuff[4] >> 1) & 0x3F) == 33))
540 {
541 nOffset = nCurrSize;
542 bSPSString = true;
543 break;
544 }
545 else if (eMode == kConfigPps && !bPPSString &&
546 (((nCurrBuff[4] >> 1) & 0x3F) == 34))
547 {
548 nOffset = nCurrSize;
549 bPPSString = true;
550 break;
551 }
552 }
553
554 nCurrBuff++;
555 nCurrSize++;
556 }
557
558 if (bVPSString || bSPSString || bPPSString)
559 {
560 if ((nBufferSize - nOffset) > 0)
561 {
562 nConfigSize = nBufferSize - nOffset;
563 }
564 }
565
566 IMLOGD_PACKET4(IM_PACKET_LOG_VIDEO,
567 "[SaveConfigFrame - H265] bVPS[%d], bSPS[%d], bPPS[%d], nConfigSize[%d]",
568 bVPSString, bSPSString, bPPSString, nConfigSize);
569
570 // save
571 if (bVPSString || bSPSString || bPPSString)
572 {
573 uint8_t* pConfigData = nullptr;
574 uint32_t nConfigIndex = 0;
575
576 if (eMode == kConfigVps)
577 {
578 nConfigIndex = 0;
579 }
580 else if (eMode == kConfigSps)
581 {
582 nConfigIndex = 1;
583 }
584 else if (eMode == kConfigPps)
585 {
586 nConfigIndex = 2;
587 }
588 else
589 {
590 return;
591 }
592
593 pConfigData = mConfigBuffer[nConfigIndex];
594
595 if (0 != memcmp(pConfigData, pbBuffer + nOffset, nConfigSize))
596 {
597 memcpy(pConfigData, pbBuffer + nOffset, nConfigSize);
598 mConfigLen[nConfigIndex] = nConfigSize;
599 }
600 }
601 break;
602 }
603 default:
604 return;
605 }
606 }
607
RemoveAUDNalUnit(uint8_t * inBuffer,uint32_t inBufferSize,uint8_t ** outBuffer,uint32_t * outBufferSize)608 bool IVideoRendererNode::RemoveAUDNalUnit(
609 uint8_t* inBuffer, uint32_t inBufferSize, uint8_t** outBuffer, uint32_t* outBufferSize)
610 {
611 bool IsAudUnit = false;
612 *outBuffer = inBuffer;
613 *outBufferSize = inBufferSize;
614
615 if (inBufferSize <= 4)
616 {
617 return false;
618 }
619
620 switch (mCodecType)
621 {
622 case kVideoCodecAvc:
623 {
624 uint32_t currSize = inBufferSize;
625 uint8_t* currBuffer = inBuffer;
626 uint32_t count = 0;
627
628 while (currSize >= 5 && count <= 12)
629 {
630 if (IsAudUnit &&
631 (currBuffer[0] == 0x00 && currBuffer[1] == 0x00 && currBuffer[2] == 0x00 &&
632 currBuffer[3] == 0x01))
633 {
634 *outBuffer = currBuffer;
635 *outBufferSize = currSize;
636 break;
637 }
638 if (currBuffer[0] == 0x00 && currBuffer[1] == 0x00 && currBuffer[2] == 0x00 &&
639 currBuffer[3] == 0x01 && currBuffer[4] == 0x09)
640 {
641 IsAudUnit = true;
642 }
643
644 currBuffer++;
645 currSize--;
646 count++;
647 }
648 }
649 break;
650 case kVideoCodecHevc:
651 default:
652 return false;
653 }
654
655 return IsAudUnit;
656 }
657
CheckResolution(uint32_t nWidth,uint32_t nHeight)658 void IVideoRendererNode::CheckResolution(uint32_t nWidth, uint32_t nHeight)
659 {
660 if ((nWidth != 0 && nWidth != mWidth) || (nHeight != 0 && nHeight != mHeight))
661 {
662 IMLOGD4("[CheckResolution] resolution change[%dx%d] to [%dx%d]", mWidth, mHeight, nWidth,
663 nHeight);
664 mWidth = nWidth;
665 mHeight = nHeight;
666
667 NotifyPeerDimensionChanged();
668 }
669 }
670
QueueConfigFrame(uint32_t timestamp)671 void IVideoRendererNode::QueueConfigFrame(uint32_t timestamp)
672 {
673 uint32_t nNumOfConfigString = 0;
674 if (mCodecType == kVideoCodecAvc)
675 {
676 nNumOfConfigString = 2;
677 }
678 else if (mCodecType == kVideoCodecHevc)
679 {
680 nNumOfConfigString = 3;
681 }
682
683 for (int32_t i = 0; i < nNumOfConfigString; i++)
684 {
685 uint8_t* configFrame = nullptr;
686 uint32_t configLen = mConfigLen[i];
687 configFrame = mConfigBuffer[i];
688
689 if (configLen == 0 || mVideoRenderer == nullptr)
690 {
691 continue;
692 }
693
694 mVideoRenderer->OnDataFrame(configFrame, configLen, timestamp, true);
695 }
696 }
697
NotifyPeerDimensionChanged()698 void IVideoRendererNode::NotifyPeerDimensionChanged()
699 {
700 if (mCallback == nullptr)
701 {
702 return;
703 }
704
705 IMLOGD1("[NotifyPeerDimensionChanged] subtype[%d]", mSubtype);
706
707 // assume the device is portrait
708 if (mWidth > mHeight) // landscape
709 {
710 // local rotation
711 if (mDeviceOrientation == 0 || mDeviceOrientation == 180)
712 {
713 // peer rotation
714 if (mSubtype == MEDIASUBTYPE_ROT0 || mSubtype == MEDIASUBTYPE_ROT180)
715 {
716 mCallback->SendEvent(kImsMediaEventResolutionChanged, mWidth, mHeight);
717 }
718 else if (mSubtype == MEDIASUBTYPE_ROT90 || mSubtype == MEDIASUBTYPE_ROT270)
719 {
720 mCallback->SendEvent(kImsMediaEventResolutionChanged, mHeight, mWidth);
721 }
722 }
723 else
724 {
725 // peer rotation
726 if (mSubtype == MEDIASUBTYPE_ROT0 || mSubtype == MEDIASUBTYPE_ROT180)
727 {
728 mCallback->SendEvent(kImsMediaEventResolutionChanged, mHeight, mWidth);
729 }
730 else if (mSubtype == MEDIASUBTYPE_ROT90 || mSubtype == MEDIASUBTYPE_ROT270)
731 {
732 mCallback->SendEvent(kImsMediaEventResolutionChanged, mWidth, mHeight);
733 }
734 }
735 }
736 else // portrait
737 {
738 // peer rotation
739 if (mSubtype == MEDIASUBTYPE_ROT0 || mSubtype == MEDIASUBTYPE_ROT180)
740 {
741 mCallback->SendEvent(kImsMediaEventResolutionChanged, mWidth, mHeight);
742 }
743 else if (mSubtype == MEDIASUBTYPE_ROT90 || mSubtype == MEDIASUBTYPE_ROT270)
744 {
745 mCallback->SendEvent(kImsMediaEventResolutionChanged, mHeight, mWidth);
746 }
747 }
748 }