안드로이드 기반 단말기를 위한 앱을 개발하던 중, Firemonkey 카메라 컴포넌트의 해상도 설정 기능에서 작은(?) 버그를 발견했습니다. 기기의 최고 해상도보다 해상도를 낮춰 설정한 후 카메라를 재구동하면 해상도가 최고 해상도로 강제 설정되어버리는 것입니다. 더 골치아픈 것은, 이것을 막기 위해 카메라 재구동 전에 해상도를 강제로 다시 설정하더라도 어떤 경우엔 설정이 적용되지 않는 경우가 있습니다.
TCameraComponent에서는 두가지 방법으로 해상도를 설정할 수 있는데, 하나는 좀 간편한 방법으로서 Quality 속성을 설정하는 것이고(PhotoQuality/HighQuality/MediumQuality/LowQuality), 다른 한가지 방법은 CaptureSetting 속성에 설정하는 것입니다(보통은 AvailableCaptureSettings 배열로부터 하나를 선택하여 대입합니다). AvailableCaptureSettings에는 카메라가 지원하는 모든 해상도들이 배열로 들어있는데(더 정확하게는 FPS값까지 함께), 0번이 가장 높은 품질이고 마지막 번호가 가장 낮은 품질입니다.
이 중 Quality 속성은 약간 트릭성이어서, 실제로 내부적으로는 CaptureSetting 속성을 설정합니다. Quality 속성이 PhotoQuality와 HighQuality 값인 경우 CaptureSetting 속성을 AvailableCaptureSettings[0] 값으로 설정하고, Quality 속성이 LowQuality인 경우엔 AvailableCaptureSettings[최고인덱스]로 설정되며, MediumQuality의 경우에는 AvailableCaptureSettings의 중간값으로 설정합니다. 따라서 Quality 속성으로 카메라 해상도를 설정할 경우, 카메라 하드웨어의 지원 해상도들에 따라 실제 설정되는 해상도가 다릅니다. (예를 들어 MediumQuality의 실제 해상도 값은, 제 Nexus5에서는 800×600/24fps이며, 제 큰아들의 저가형 스마트폰인 LG K10에서는 640×480/30fps입니다)
Quality 속성으로 해상도를 설정하는 것은 이렇게 기기에 따라 실제 해상도가 어떻게 될지 미리 예상할 수 없는 단점이 있지만, “그럭저럭 괜찮은” 품질의 사진을 아주 빠르게 캡쳐할 수 있어서, 연속적으로 계속 캡쳐해야 하는 경우에는 MediumQuality가 더 높은 해상도 설정들보다 더 낫습니다. 실제로 저는 지금 카메라를 이용한 QR 코드 인식 기능을 작업중이라 MediumQuality가 제 목적에 잘 맞습니다.
그런데 CaptureSetting과 Quality 두 속성 모두, Active를 false로 했다가 다시 true로 할 경우 기기의 최고 해상도(즉 AvailableCaptureSettings[0])로 초기화되어버리는 문제가 있습니다. 아마도 엠바카데로의 개발팀에서는 아직 이 문제에 대해 알지 못하고 있는 것으로 보이는데, RAD Studio에 포함된 CameraComponent 샘플 코드에서 그에 대한 대비가 되어 있지 않기 때문입니다. 샘플 프로젝트의 소스에는 카메라의 해상도 설정을 Label에 보여주는 코드 부분이 있는데, 정상적으로 동작하려면 이 해상도 표시 코드는 카메라를 구동하기 전에(즉 Active =true 직전에) 매번 실행해줘야 현재 해상도를 정확하게 표시해주게 되겠지요. 하지만 그렇게 하지 않고 UI에서 해상도를 선택한 코드 직후에만 해상도를 표시하고 있어서, 카메라를 재구동하면 Label에는 실제 캡쳐 해상도와는 다른 엉뚱한 해상도가 표시되고 있습니다.
어쨌든 그래서, 매번 Active를 true로 하기 전에 다시 해상도 설정을 해줘야만 원하는 해상도로 캡쳐를 할 수 있는데요. 좀 더 골치아픈 문제는, 카메라 구동 직전에 매번 CaptureSetting 속성으로 설정하면 지정 해상도로 제대로 설정이 되지만, Quality 속성으로 설정하면 적용이 되지 않는다는 것입니다. (즉 Quality 속성값을 무시하고 최고 해상도 값으로 캡쳐가 됩니다) 엉뚱한 값이 들어있나 싶어 실행중에 값을 읽어봐도 설정된 값에는 문제가 없습니다. 즉 MediumQuality로 설정했고 다시 확인해봐도 그대로 값이 들어가 있는데 실제로는 최고 해상도(즉 PhotoQuality에 해당)로 설정된다는 것입니다. 증상으로 보면 좀 심각한 버그일 수도 있는데, 캡쳐 속도를 빠르게 하기 위해 MediumQuality로 설정했는데 가장 느린 PhotoQuality로 동작하게 되니 개발자나 사용자로서는 당황스러운 일입니다.
카메라를 멈췄다가 다시 구동시키지 않으면 되지 않나 하고 생각할 수 있겠지만, 사실은 그렇지 않습니다. 사용자가 홈 버튼을 누르거나 다른 앱을 실행시켜 개발중인 앱이 백그라운드로 빠질 경우에는, 원칙적으로 카메라를 정지시켜줘야 합니다. 안그러면 개발중인 앱이 카메라를 물고 있어 다른 앱에서 카메라를 사용할 수 없게 되기 때문입니다. 그래서 RAD Studio의 CameraComponent 예제 앱 소스에서도, 앱이 비활성화되거나 백그라운드로 빠질 경우 카메라 구동을 멈추도록 코드가 추가되어 있습니다(IFMXApplicationEventService.SetApplicationEventHandler를 이용합니다). 그러니 다시 현재 앱으로 돌아오면 다시 카메라를 시작시킬 수 있어야 하는데, 이렇게 카메라가 재구동되면 해상도가 최고 해상도로 변경되어버리는 겁니다. 특히 예제 앱 소스에서는 Active를 true로 할 때 해상도를 다시 표시하지 않기 때문에, 해상도가 자동으로 최고 수준으로 변경된 사실을 알아차리기도 어렵습니다. 그러니 사용자로서는 이상하게 카메라 캡쳐가 느려졌다고만 인식하고 뭔가 앱의 버그가 아닌지 의심하게 되겠지요. (사실 버그가 맞기도 합니다)
제 경우에도, 앱이 백그라운드로 빠졌다가 다시 돌아오면 원활한 속도로 동작하던 카메라 캡쳐가 갑자기 답답할 정도로 느려져서 꽤 오랫동안 문제의 원인을 찾느라 헤맸었습니다. 그래서 FMX의 소스 코드를 뒤져봤더니, 허무할 정도로 의외로 단순한 곳에 버그가 있었습니다.
Quality 속성값이 적용되지 않고 무시되는 이유는 Quality 속성의 setter 프로시저인 SetQuality 함수(TVideoCaptureDevice의 멤버)에서, 새로운 설정값이 현재 값과 같으면 아무것도 하지 않고 그대로 리턴하기 때문입니다. 아래 코드는 FMX.Media 유닛의 일부입니다.
1 2 3 4 5 6 7 |
procedure TVideoCaptureDevice.SetQuality(const Value: TVideoCaptureQuality); begin if FQuality <> Value then begin DoSetQuality(Value); end; end; |
(참고로, 위 코드에서 호출하는 프로시저인 DoSetQuality는 위 프로시저 바로 아래에 있습니다만, 위 코드에서 호출되는 DoSetQuality는 이것이 아니고 안드로이드용으로 상속된 비공개 TAndroidVideoCaptureDevice 클래스의 DoSetQuality 멤버입니다. FMX.Media.Android 유닛에 있습니다)
setter 프로시저에서 이런 식의 코드는 속성, property의 구현 코드로서는 아주 통상적인 코드입니다만, 이런 특수한 경우, 한 속성이 그 자체로서 동작하는 것이 아니라 다른 속성 혹은 함수를 위한 편의용이랄까, 프록시 목적의 속성일 경우에는 오동작의 가능성이 생깁니다. Quality 속성의 기능은 그 자체로서 유효한 것이 아니라 CaptureSetting에 의존하는 설정 기능이기 때문입니다. 이런 경우에는 setter 함수에서 현재값과 같은가를 검사하는 코드 없이 강제로 설정하도록 작성되어야만 안전합니다. 즉 속성의 코드 설계상의 버그라고도 볼 수 있습니다.
어쨌든… 이 문제는 이런 원인만 알고 나면 해결책은 무척 간단합니다. 외견상 같은 값으로 보여서 MediumQuality 값이 설정되지 않은 것이므로, 원하는 MediumQuality 값으로 설정하기 전에 다른 값으로 한번 설정했다가 MediumQuality 값으로 설정하면 되는 것입니다.
1 2 3 4 |
CameraComponent1.Quality := TVideoCaptureQuality.HighQuality; CameraComponent1.Quality := TVideoCaptureQuality.MediumQuality; CameraComponent1.Active := True; |
구질구질한 긴 설명에 비해 솔루션은 허무할 정도로 간단하죠? 단순하기 짝이 없는 코드의 모양만 언뜻 봐서는 꼼수처럼 보일 수도 있겠지만, Firemonkey 코드의 버그 때문에 어쩔 수 없는 코드입니다.
제가 이 버그를 발견한 버전은 10 Seattle이지만, 최근에 출시된 RAD Studio 10.1 Berlin 버전에도 동일합니다.
저는 아이폰/아이패드 앱은 개발하지 않아서 직접 확인해볼 수 없습니다만, 안드로이드 말고 iOS에서도 같은 해상도 설정 문제가 있을 수도 있습니다. 안드로이드처럼 iOS도 카메라를 구동할 때 자동으로 해상도 설정을 초기화하느냐에 달려있을 겁니다. 아이폰에서 테스트 가능하신 분은 확인 부탁드립니다.