안드로이드 Framework에서 Camera 동작 원리 분석(2)

안드로이드/카메라 2015. 4. 26. 09:00

 지난 시간에 이어 Framework 단계에서 Camera가 CameraService와 어떻게 연결되는지 이어서 진행해 보도록 하겠습니다.

 Application 단계에서 Camera 클래스의 open() 매소드를 실행한 후 이를 지속적으로 추적하여 CameraBase 까지 접근하였고 이 곳에서 CameraService와의 접점을 찾는 과정까지 진행하였습니다.


/frameworks/av/camera/CameraBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <typename TCam, typename TCamTraits>
sp<TCam> CameraBase<TCam, TCamTraits>::connect(int cameraId,
                                               const String16& clientPackageName,
                                               int clientUid)
{
    ALOGV("%s: connect", __FUNCTION__);
    sp<TCam> c = new TCam(cameraId);
    sp<TCamCallbacks> cl = c;
    status_t status = NO_ERROR;
    const sp<ICameraService>& cs = getCameraService();
 
    if (cs != 0) {
        TCamConnectService fnConnectService = TCamTraits::fnConnectService;
        status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,
                                             /*out*/ c->mCamera);
    }
    if (status == OK && c->mCamera != 0) {
        c->mCamera->asBinder()->linkToDeath(c);
        c->mStatus = NO_ERROR;
    } else {
        ALOGW("An error occurred while connecting to camera: %d", cameraId);
        c.clear();
    }
    return c;
}
cs

 이제 여기서부터 한 줄씩 분석을 하며 내려가 보도록 하겠습니다.


sp<TCam> c = new TCam(cameraId);


먼저 저 Templete인 TCam의 정체를 확인해 보도록 합시다. TCam은 위 함수가 호출되기 전 미리 정의가 되어 있을 것입니다.


/frameworks/av/camera/Camera.cpp

1
2
3
4
5
6
7
#include <camera/Camera.h>
 
sp<Camera> Camera::connect(int cameraId, const String16& clientPackageName,
        int clientUid)
{
    return CameraBaseT::connect(cameraId, clientPackageName, clientUid);
}
cs


TCam을 알아보려 함수를 거슬러 올라왔더니 이번에는 CameraBaseT가 떡하니 있군요. 이 녀석은 무엇을 하는 녀석일까요?


/frameworks/av/include/camera/Camera.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <camera/CameraBase.h>
 
class Camera :
    public CameraBase<Camera>,
    public BnCameraClient
{
public:
    enum {
        USE_CALLING_UID = ICameraService::USE_CALLING_UID
    };
 
            // construct a camera client from an existing remote
    static  sp<Camera>  create(const sp<ICamera>& camera);
    static  sp<Camera>  connect(int cameraId,
                                const String16& clientPackageName,
                                int clientUid);
}
cs

/frameworks/av/include/camera/CameraBase.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
public:
    typedef typename TCamTraits::TCamListener       TCamListener;
    typedef typename TCamTraits::TCamUser           TCamUser;
    typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;
    typedef typename TCamTraits::TCamConnectService TCamConnectService;
 
    static sp<TCam>      connect(int cameraId,
                                 const String16& clientPackageName,
                                 int clientUid);
 
    typedef CameraBase<TCam>         CameraBaseT;
}
cs

 함수 하나를 설명하려다 보니 생각보다 많은 코드들이 추가되었군요. 일단 위 함수들을 근거로 하나씩 단서를 찾아보도록 합시다. 우선 CameraBaseT의 정체를 알아봅시다.


return CameraBaseT::connect(cameraId, clientPackageName, clientUid);


 바로 위의 CameraBase.h 파일 내에서 CameraBaseT가 typedef로 정의되어 있는 것을 확인하실 수 있습니다. 이를 변환해 보면 다음과 같군요.


return CameraBase<TCam>::connect(cameraId, clientPackageName, clientUid);

 그래도 아직 TCam의 정체는 풀리지 않았군요 그러나 이는 Camera.h 에 정의된 Camera의 상속 클래스를 확인하면 바로 풀리는 것을 보실 수 있습니다.


class Camera : public CameraBase<Camera>, public BnCameraClient


이로서 TCam의 정체는 Camera라는 것을 알게 되었습니다. 이를 다시 정리하면 다음과 같이 변환될 수 있음을 알 수 있습니다.


sp<Camera> c = new Camera(cameraId);


/frameworks/av/camera/Camera.cpp

1
2
3
4
Camera::Camera(int cameraId)
    : CameraBase(cameraId)
{
}
cs


/frameworks/av/include/camera/CameraBase.h

1
2
3
4
5
6
7
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
protected:
    CameraBase(int cameraId);
    const int                        mCameraId;
}
cs


/frameworks/av/camera/CameraBase.cpp

1
2
3
4
5
6
template <typename TCam, typename TCamTraits>
CameraBase<TCam, TCamTraits>::CameraBase(int cameraId) :
    mStatus(UNKNOWN_ERROR),
    mCameraId(cameraId)
{
}
cs

이러한 과정을 통해 Camera 객체가 생성되었음을 확인하였습니다. 이제 다음줄을 보도록 합니다.


sp<TCamCallbacks> cl = c;


자... 이번에는 TCamCallbacks가 뿅 하고 나타나 우리들을 괴롭히고 있군요! 이 녀석의 정체는 과연 누구일까요?

/frameworks/av/include/camera/Camera.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <camera/CameraBase.h>
 
template <>
struct CameraTraits<Camera>
{
    typedef CameraListener        TCamListener;
    typedef ICamera               TCamUser;
    typedef ICameraClient         TCamCallbacks;
    typedef status_t (ICameraService::*TCamConnectService)(const sp<ICameraClient>&,
                                                           intconst String16&, int,
                                                           /*out*/
                                                           sp<ICamera>&);
    static TCamConnectService     fnConnectService;
};
cs


/frameworks/av/include/camera/CameraBase.h

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename TCam>
struct CameraTraits {
};
 
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
public:
    typedef typename TCamTraits::TCamListener       TCamListener;
    typedef typename TCamTraits::TCamUser           TCamUser;
    typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;
    typedef typename TCamTraits::TCamConnectService TCamConnectService;
}
cs

위 코드를 처음 볼 땐 그냥 정신이 멍해질 겁니다. 하지만 다시 정신 바짝 차리시고 코드를 상세하게 살펴보도록 합니다!


/frameworks/av/include/camera/CameraBase.h
template <typename TCam>
struct CameraTraits {
};

template <typename TCam, typename TCamTraits = CameraTraits<TCam> >

우선 CameraBase.h 헤더에서 다음과 같이 CameraTraits<Tcam> 이 정의되어 있고 이는 struct 내의 CameraTraits에 적용이 됩니다.


/frameworks/av/include/camera/Camera.h

#include <camera/CameraBase.h>
 
template <>
struct CameraTraits<Camera>
{
....
    typedef ICameraClient         TCamCallbacks;
....
};

 위의 CameraTraits<Camera> 로 선언된 struct로 인해 TCam이 Camera임을 알 수 있습니다. 또한 ICameraClient 클래스가 TCamCallbacks로 선언되어 있는 모습을 보실 수 있습니다.


이제 문제는 CameraBase.h에 정의된 아래의 코드가 뜻을 의미하기 난해다다는 점이지요.

typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;

 저 위에 쓰인 typename은 template 내에서 쓰이는건 많이 보았는데 여기서는 클래스 내에까지 정의되어 있는 것을 보실 수 있습니다. 이는 위에 보이시는 TCamTraits라는 것이 class임을 컴파일에게 알려주기 위해 부득이하게 typename을 적어준 것이라고 이해해 주시면 되겠습니다. 이와 관련해서 좀 더 자세한 정보를 알고 싶으신 분께서는 아래 링크를 참조해 주시기 바랍니다.

http://ikpil.com/540

위의 코드를 우리들이 읽기 쉽게 변환하면 다음과 같다고 보실 수 있겠습니다.


typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;

typedef CameraTraits<Camera>::TCamCallbacks      TCamCallbacks;

typedef CameraTraits<Camera>::ICameraClient      TCamCallbacks;
CameraTraits<Camera>::ICameraClient


결국 TCamCallbacks는 ICameraClient와 같음을 확인하실 수 있습니다.

sp<ICameraClient> cl = c;


이제 다음 코드를 확인해 보도록 하겠습니다.


const sp<ICameraService>& cs = getCameraService();


ICameraService 클래스를 불러오라는 듯한 의미의 코드로 해석됩니다. 여기서 getCameraService() 함수를 살펴보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// establish binder interface to camera service
template <typename TCam, typename TCamTraits>
const sp<ICameraService>& CameraBase<TCam, TCamTraits>::getCameraService()
{
    Mutex::Autolock _l(gLock);
    if (gCameraService.get() == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
            binder = sm->getService(String16(kCameraServiceName));
            if (binder != 0) {
                break;
            }
            ALOGW("CameraService not published, waiting...");
            usleep(kCameraServicePollDelay);
        } while(true);
        if (gDeathNotifier == NULL) {
            gDeathNotifier = new DeathNotifier();
        }
        binder->linkToDeath(gDeathNotifier);
        gCameraService = interface_cast<ICameraService>(binder);
    }
    ALOGE_IF(gCameraService == 0"no CameraService!?");
    return gCameraService;
}

cs

 위 코드는 BpCameraService와 BpBinder를 불러들이는 코드입니다. 위 코드에 대해 자세한 내용은 아래 포스팅을 참고해주시기 바랍니다.

http://elecs.tistory.com/93


 위 과정까지 진행하셨다면 CameraBase와 CameraService가 Binder를 통해 연결되었음을 확인하실 수 있습니다. 이제 다음으로 이후의 코드들을 살펴보도록 하겠습니다.

/frameworks/av/camera/CameraBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <typename TCam, typename TCamTraits>
sp<TCam> CameraBase<TCam, TCamTraits>::connect(int cameraId,
                                               const String16& clientPackageName,
                                               int clientUid)
{
    ALOGV("%s: connect", __FUNCTION__);
    sp<TCam> c = new TCam(cameraId);
    sp<TCamCallbacks> cl = c;
    status_t status = NO_ERROR;
    const sp<ICameraService>& cs = getCameraService();
 
    if (cs != 0) {
        TCamConnectService fnConnectService = TCamTraits::fnConnectService;
        status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,
                                             /*out*/ c->mCamera);
    }
    if (status == OK && c->mCamera != 0) {
        c->mCamera->asBinder()->linkToDeath(c);
        c->mStatus = NO_ERROR;
    } else {
        ALOGW("An error occurred while connecting to camera: %d", cameraId);
        c.clear();
    }
    return c;
}
cs

이제 이후의 코드들을 한 줄씩 분석해 보도록 하겠습니다.

TCamConnectService fnConnectService = TCamTraits::fnConnectService;


 이 코드는 typedef로 정의된 함수 포인터 변수를 선언하고 이것에 실행하고자 하는 함수를 저장하고 있습니다. fnConnectService 내에는 다음과 같은 내용의 함수가 저장됩니다.


/frameworks/av/camera/Camera.cpp

1
2
CameraTraits<Camera>::TCamConnectService CameraTraits<Camera>::fnConnectService =
        &ICameraService::connect;
cs

 fnConnectService 함수 포인터는 ICamaerService의 connect 함수를 저장함을 확인할 수 있습니다. typedef 함수 포인터에 대해 좀 더 자세히 알고 싶으신 분은 아래 포스팅을 참조하시기 바랍니다.


http://elecs.tistory.com/97

 

status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,

                                             /*out*/ c->mCamera);


 위에서 설정하였던 함수 포인터를 실행하는 코드입니다. sp<T> 클래스에서 get() 함수를 호출하면 자신이 가지고 있는 객체값의 포인터 값을 불러옵니다. 이 때 리턴값의 자료형은 T입니다. 위의 코드에서 변수 cs는 ICameraService를 리턴합니다. 이 때 cs에는 BpCameraService를 저장하고 있으므로 BpCameraService::connect 함수를 실행하게 된다.


/frameworks/av/camera/ICameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class BpCameraService: public BpInterface<ICameraService>
{
public:
    BpCameraService(const sp<IBinder>& impl)
        : BpInterface<ICameraService>(impl)
    {
    }
....
    // connect to camera service (android.hardware.Camera)
    virtual status_t connect(const sp<ICameraClient>& cameraClient, int cameraId,
                             const String16 &clientPackageName, int clientUid,
                             /*out*/
                             sp<ICamera>& device)
    {
        Parcel data, reply;
        data.writeInterfaceToken(ICameraService::getInterfaceDescriptor());
        data.writeStrongBinder(cameraClient->asBinder());
        data.writeInt32(cameraId);
        data.writeString16(clientPackageName);
        data.writeInt32(clientUid);
        remote()->transact(BnCameraService::CONNECT, data, &reply);
 
        if (readExceptionCode(reply)) return -EPROTO;
        status_t status = reply.readInt32();
        if (reply.readInt32() != 0) {
            device = interface_cast<ICamera>(reply.readStrongBinder());
        }
        return status;
    }
....
};
cs


 Proxy 측인 BpCameraService 클래스에서 실행된 Parcel에 대한 transact() 함수는 이후 Native 측인 BnCameraService 클래스로 Parcel을 통해 값이 넘어오면서 실행되기 시작합니다.


/frameworks/av/camera/ICameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
status_t BnCameraService::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
    ....
        case CONNECT: {
            CHECK_INTERFACE(ICameraService, data, reply);
            sp<ICameraClient> cameraClient =
                    interface_cast<ICameraClient>(data.readStrongBinder());
            int32_t cameraId = data.readInt32();
            const String16 clientName = data.readString16();
            int32_t clientUid = data.readInt32();
            sp<ICamera> camera;
            status_t status = connect(cameraClient, cameraId,
                    clientName, clientUid, /*out*/ camera);
            reply->writeNoException();
            reply->writeInt32(status);
            if (camera != NULL) {
                reply->writeInt32(1);
                reply->writeStrongBinder(camera->asBinder());
            } else {
                reply->writeInt32(0);
            }
            return NO_ERROR;
        } break;
    ....
    }
 
}
cs


 위의 코드를 보았을 때 마치 connect() 함수가 이전에 실행했던 BpCameraService 클래스의 connect() 함수를 실행하는 듯한 모습을 보이고 있습니다. 하지만 사실은 Native 프로세스의 CameraService 내의 connect() 함수가 실행되고 있다는 것을 기억하시면 되겠습니다. 해당 코드를 분석해보도록 합시다.


/frameworks/av/services/camera/libcameraservice/CameraService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
class CameraService :
    public BinderService<CameraService>,
    public BnCameraService,
    public IBinder::DeathRecipient,
    public camera_module_callbacks_t
{
....
    virtual status_t connect(const sp<ICameraClient>& cameraClient, int cameraId,
            const String16& clientPackageName, int clientUid,
            /*out*/
            sp<ICamera>& device);
....
}
cs


 먼저 CameraService 클래스의 구조를 보겠습니다. 위에서 보시면 아시듯이 BnCameraService 클래스를 상속받고 있는 것을 보실 수 있으며 CameraService 클래스 내에 connect() 함수가 virtual로 선언되어 있는 모습을 확인하실 수 있습니다.


/frameworks/av/include/camera/ICameraService.h
1
2
3
4
5
6
7
8
9
10
class BnCameraService: public BnInterface<ICameraService>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);
};
 
}; // namespace android
cs


 BnCameraService 클래스는 보시듯이 BnInterface를 상속받고 있으며 BnInterface는 ICameraService 상속하는 상황임을 보실 수 있습니다.


/frameworks/native/include/binder/IInterface.h

1
2
3
4
5
6
7
8
9
10
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;
 
protected:
    virtual IBinder*            onAsBinder();
};
cs


여기까지 확인하셨다면 일단 CameraService 클래스의 상속상황이 어떻게 되어있는지 어느정도 감을 잡으셨으리라 생각합니다.


/frameworks/av/include/camera/ICameraService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ICameraService : public IInterface
{
....
 
public:
    /**
     * clientPackageName and clientUid are used for permissions checking.  if
     * clientUid == USE_CALLING_UID, then the calling UID is used instead. Only
     * trusted callers can set a clientUid other than USE_CALLING_UID.
     */
    virtual status_t connect(const sp<ICameraClient>& cameraClient,
            int cameraId,
            const String16& clientPackageName,
            int clientUid,
            /*out*/
            sp<ICamera>& device) = 0;
....
}
cs


 자, 이제 저 connect() 함수는 어느 부분에서 선언되어 있는 것인지 확인해 보도록 합시다. C++에서 virtual 함수는 Java에서 Dynamic 함수로 정의하는 것으로 감을 잡으시면 이해가 쉬울 것입니다.

 이에 대한 힌트는 이전에 작성하였던 포스팅 중에 있습니다. 바로 CameraService가 등록되는 과정입니다.


http://elecs.tistory.com/83


/frameworks/native/include/binder/BinderService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#ifndef ANDROID_BINDER_SERVICE_H
#define ANDROID_BINDER_SERVICE_H
 
#include <stdint.h>
 
#include <utils/Errors.h>
#include <utils/String16.h>
 
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
 
// ---------------------------------------------------------------------------
namespace android {
 
template<typename SERVICE>
class BinderService
{
public:
    static status_t publish(bool allowIsolated = false) {
        sp<IServiceManager> sm(defaultServiceManager());
        return sm->addService(
                String16(SERVICE::getServiceName()),
                new SERVICE(), allowIsolated);
    }
 
    static void publishAndJoinThreadPool(bool allowIsolated = false) {
        publish(allowIsolated);
        joinThreadPool();
    }
 
    static void instantiate() { publish(); }
 
    static status_t shutdown() { return NO_ERROR; }
 
private:
    static void joinThreadPool() {
        sp<ProcessState> ps(ProcessState::self());
        ps->startThreadPool();
        ps->giveThreadPoolName();
        IPCThreadState::self()->joinThreadPool();
    }
};
 
 
}; // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_BINDER_SERVICE_H
 
 
cs

 

 내용이 다소 생략되어 있습니다만 위의 addService() 과정에서 CameraService 클래스가 new를 통해 생성되고 있음을 보실 수 있습니다. 즉, CameraService가 SystemService에 등록될 때 순수 CameraService 클래스가 선언되어 있으므로 virtual 함수의 dynamic 성질을 생각한다면 CameraService 내의 함수가 호출되어야 함이 맞음을 알 수 있을 것입니다.


/frameworks/av/services/camera/libcameraservice/CameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
status_t CameraService::connect(
        const sp<ICameraClient>& cameraClient,
        int cameraId,
        const String16& clientPackageName,
        int clientUid,
        /*out*/
        sp<ICamera>& device) {
 
    String8 clientName8(clientPackageName);
    int callingPid = getCallingPid();
 
    LOG1("CameraService::connect E (pid %d \"%s\", id %d)", callingPid,
            clientName8.string(), cameraId);
 
    status_t status = validateConnect(cameraId, /*inout*/clientUid);
    if (status != OK) {
        return status;
    }
 
 
    sp<Client> client;
    {
        Mutex::Autolock lock(mServiceLock);
        sp<BasicClient> clientTmp;
        if (!canConnectUnsafe(cameraId, clientPackageName,
                              cameraClient->asBinder(),
                              /*out*/clientTmp)) {
            return -EBUSY;
        } else if (client.get() != NULL) {
            device = static_cast<Client*>(clientTmp.get());
            return OK;
        }
 
        int facing = -1;
        int deviceVersion = getDeviceVersion(cameraId, &facing);
 
        // If there are other non-exclusive users of the camera,
        //  this will tear them down before we can reuse the camera
        if (isValidCameraId(cameraId)) {
            // transition from PRESENT -> NOT_AVAILABLE
            updateStatus(ICameraServiceListener::STATUS_NOT_AVAILABLE,
                         cameraId);
        }
 
        switch(deviceVersion) {
          case CAMERA_DEVICE_API_VERSION_1_0:
            client = new CameraClient(this, cameraClient,
                    clientPackageName, cameraId,
                    facing, callingPid, clientUid, getpid());
            break;
          case CAMERA_DEVICE_API_VERSION_2_0:
          case CAMERA_DEVICE_API_VERSION_2_1:
          case CAMERA_DEVICE_API_VERSION_3_0:
            client = new Camera2Client(this, cameraClient,
                    clientPackageName, cameraId,
                    facing, callingPid, clientUid, getpid(),
                    deviceVersion);
            break;
          case -1:
            ALOGE("Invalid camera id %d", cameraId);
            return BAD_VALUE;
          default:
            ALOGE("Unknown camera device HAL version: %d", deviceVersion);
            return INVALID_OPERATION;
        }
 
        status_t status = connectFinishUnsafe(client, client->getRemote());
        if (status != OK) {
            // this is probably not recoverable.. maybe the client can try again
            // OK: we can only get here if we were originally in PRESENT state
            updateStatus(ICameraServiceListener::STATUS_PRESENT, cameraId);
            return status;
        }
 
        mClient[cameraId] = client;
        LOG1("CameraService::connect X (id %d, this pid is %d)", cameraId,
             getpid());
    }
    // important: release the mutex here so the client can call back
    //    into the service from its destructor (can be at the end of the call)
 
    device = client;
    return OK;
}
 
cs

 현재 과정에서 보시는 바와 같이 client 에는 CameraClient() 혹은 Camera2Client() 함수가 저장되었고 이는 Camera 클래스 내의 mCamera에 저장됩니다.


 위 과정까지 마치게 된다면 카메라가 CameraService와 연결이 완료가 되면서 Camera 클래스 객체를 android_hardware_Camera.cpp 에서 실행하고 있던 초기화 함수로 return하게 됩니다.


 다음 포스팅에서는 android_hardware_Camera.cpp 에서 진행하고 있던 초기화 함수 부분에서 이어서 진행하도록 하겠습니다.



300x250

Canon MG2900 시리즈 무선 프린터 설치방법

흔치않은일상 2015. 4. 18. 00:56

https://elecs.tistory.com/342 간만에 프린터를 구매하면서 이왕이면 무선 인터넷을 통한 프린팅 기능이 있는 프린터를 사고자 하여 Canon PIXMA MG2990을 구매하게 되었습니다.





오늘날이 스마트 시대인 만큼 프린터도 무선 기능을 지원하는 추세이기도 한데요

컴퓨터에 직접 연결해서 쓸 때는 설명서 내용에 충실하면 문제없이 사용이 가능합니다.

그런데 무선 프린터의 가장 큰 불편한 점은

공유기 설정 관련 지식이 해박하지 않으신 분들은

설치과정에서 어려운 점이 한두가지가 아니라는 겁니다.

특히 저의 경우 MG2990에서 지원하는 WPS 설치를 진행하다 피를 봤으니 말이지요.


그런고로 여기서는 Canon사의 MG2900 시리즈를 구매하신 분들을 대상으로 무선으로 프린터를 설치하는 과정에 대해 알아보고자 합니다.


2019년 10월 11일 추가 - 만약 이 글에서 소개된 무선 설치 방법이 정상적으로 진행되지 않으시는 분들께서는 아래의 링크와 같이 USB를 연결하여 프린터를 설치해보시길 바랍니다.

https://elecs.tistory.com/342


1. 먼저 동봉된 DVD를 삽입하신 후 설치 파일인 MSetup4.exe 파일을 실행합니다.


만약 자신의 컴퓨터에 광매체 드라이브(ODD)가 없어 동봉된 DVD를 사용하지 못하시는 분은 아래 사이트를 통해 설치 파일을 다운로드 받아 진행하시기 바랍니다.


http://canon.com/ij-setup



다음과 같은 화면이 나오면 우선 설치 프로그램이 실행된 것입니다. 다음 버튼을 눌러 설치를 진행합니다.



곧바로 설치 프로그램이 무선공유기를 통해 프린터를 찾는 모드에 진입합니다.

만약 자신의 공유기가 프린터와 연결되어 있다면 곧바로 드라이버 설치 모드에 진입될 것입니다.



'무선 LAN 연결'을 선택하신 후 '다음'버튼을 클릭합니다.



여기서는 무선 공유기를 통해 연결할 예정이므로 '액세스 포인트 연결(권장)'을 선택하신 후 '다음' 버튼을 클릭합니다.




당연히 처음에는 공유기가 프린터와 연결되어 있지 않으므로 다음과 같은 화면이 나오는 것을 보실 수 있습니다. '다음' 버튼을 클릭합니다.



다음과 같은 화면이 진행 된 후 설치가이드 모드에 들어가게 됩니다.



'다음' 버튼을 클릭합니다.



설치 환경이 총 3가지가 주어져 있습니다.

이 중 가장 쉬운 방법은 WPS를 사용하는 방법입니다만

이게 몇몇 공유기는 자칫하면 프린터가 공유기의 설정을 제멋대로 바꾸어 버리기 때문에

잘못하면 다른 기기들이 Wi-Fi를 사용하지 못하는 경우가 발생합니다.


여기서는 오른쪽 빨간색으로 표시한 부분 PIN 코드 (방법)을 통해 진행하겠습니다.




위에서 설명하고 있는 바와 같이

자신의 공유기 설정을 확인하셔서 자신의 공유기 환경이 어떻게 구성되어 있는지 살펴봅니다.

만약 자신의 공유기가 'IEEE802.11n 전용'으로 설정되어 있다면 이를 다른 것으로 설정해 주시면 되겠습니다.


이를 위해 자신의 공유기에 접속하여 설정을 변경해야 하는데

거의 대부분의 공유기는 다음 주소를 통해 설정모드에 진입하실 수 있습니다.


http://192.168.1.1/


종종 다른 공유기의 경우 설정 모드에 진입하는 방식이 다른 경우가 있습니다. 자세한 사항은 자신의 공유기 제작사의 홈페이지를 참조하시기 바랍니다.



자신의 프린터에 전원을 넣은 후 위의 그림과 같이 중지(Stop) 버튼을 꾸욱 누르고 기다립니다.

그러면 알람(Alarm) 림프가 깜빡거리기 시작하는데 이것이 15회 깜빡이는 것을 확인하시면 바로 버튼에서 손을 땝니다. 그러면 아래와 같이 프린트 물이 출력되는 것을 확인하실 수 있습니다.



이렇게 자신의 프린터에 설정된 정보들이 프린터에 출력됨을 확인하실 수 있습니다.



위에서 진행했던 것과 같이 다시 프린터의 중지(Stop) 버튼울 꾸욱 누르고 기다립니다.

이번에는 알람(Alarm) 버튼이 16번 깜빡거리기를 기다리다가 떼어주시면 전원과 무선 부분 두 램프가 함께 깜빡이는 것을 보실 수 있습니다.



위와 같은 화면을 보셨다면 곧바로 자신의 공유기 설정 모드에 진입하셔서 위의 과정에서 출력했던 프린트물 내에 적혀진 PIN코드를 자신의 공유기에 입력해줍니다.


아래는 netis사 공유기의 펌웨어 화면으로 'WPS 설정'을 누르신 후 '장비 추가' 버튼을 클리갛면 아래부분에 '새로운 장비 추가' 메뉴가 나타납니다. 여기서 '연결할 무선 장비의 PIN코드 입력' 을 선택한 후 출력된 인쇄지에 적힌 PIN코드를 입력하신 후 '연결' 버튼을 클릭하시면 됩니다.



이 과정이 성공적으로 끝난다면 프린터의 전원과 Wi-Fi 버튼의 깜빡임이 멈추게 될 것입니다.



위의 화면과 같이 네트워크 프린터 목록에서 자신의 프린터 기기가 공유기와 연결되었음을 확인하실 수 있습니다. 자신의 프린터를 마우스로 클릭한 후 '다음'버튼을 클릭하시면 드라이버가 본격적으로 설치되기 시작합니다.


만약 위의 화면과 같은 결과가 나오지 않으신 분들은 '다음'버튼을 누르신 후 위의 과정을 한번 더 반복합니다.



위와 같은 화면을 보셨다면 드디어 자신의 컴퓨터에 드라이버가 실치될 것입니다!


300x250

Wi-Fi 프린팅 지원 Canon PIXMA MG2990 개봉기

흔치않은일상 2015. 4. 17. 00:01

지금까지 컴퓨터는 몇 번이고 바꾸어 오면서도 프린터기는 약 10년전 즈음부터 사용했던 HP 복합기를 사용해 왔었습니다.

지금도 인쇄가 되고 있기는 합니다만

워낙 연세를 많이 잡수시다 보니 나중에는 인쇄 속도가 많이 떨어지는 모습을 보이고 있었습니다.


그래서 이참에 새로운 프린터기를 한 대 장만해볼까 해서 알아보았는데

요즘은 스마트 시대에 도래하면서 NFC 기능을 사용한 프린터기도 선보이는 추세인데요.

그 중 Wi-Fi를 활용하여 무선으로 프린팅이 가능한 Canon사의 PIXMA MG2900을 구매해 보았습니다.



인터넷으로 주문한 PIXMA MG2990 기기가 도착하였습니다.



자세히 살펴보니 제가 원하는 기능이기도 한 Wi-Fi 무선 프린팅을 지원하고 있는 것을 확인하실 수 있습니다.



드디어 오픈~1



열자마자 내용물로 거대한 그림을 활용한 설시 설명서를 보실 수 있습니다.

이 순서대로 사용하신다면 간편하게 바로 설치하여 사용하실 수 있습니다.



설치설명서를 꺼낸 후 내부의 모습입니다.



드디어 내용물을 꺼내봅니다!



바로 위에는 취급설명서와 드라이브 설치CD를 확인하실 수 있습니다.



프린터 박스 아랫쪽에 길다란 노란 포장지가 보이는데요 이것의 정체는 무엇일까요?



내용을 확인해 보니



다름아닌 전원코드와 USB 연결선이 들어있었습니다.

이와중에 왼쪽 오른쪽에는 정체불명의 플라스틱 포장물이 보이는데요.



아하!

컬러잉크와 흑색잉크가 각각 동봉이 되어 있었던 것이었네요!!



드디어 본론이로군요! MG2990를 살펴보도록 합시다!!



오호



하얀 빛의 디자인이 상당히 매력적입니다!



용지 삽입 부분을 개방해 보았습니다.



스캐너 부분도 살펴봅시다.

구조는 다른 복합기와는 딱히 차이는 없어보이는군요.



프린터기의 내부입니다.

잉크를 넣을 수 있는 슬롯 2개가 보이는데요

왼쪽 슬롯이 컬러잉크, 오른쪽 슬롯이 검은잉크를 삽입하는 부분입니다.



MG2990의 전원은 어댑터가 보시는 바와 같이 본체에 붙어있는 방식입니다.

그덕에 덩치큰 어댑터를 따로 공간 낭비없이 간편하게 해결한 발상이 참 대단합니다.



드디어 설치 완료!



전원을 키니 보시는 바와 같이 무선모드도 동시에 켜지는 것으로 보입니다.



그 와중에 경고 램프가 켜져있는데요

생각해보니 아직 잉크를 설치하지 않았더군요.



역시나 내부에는 잉크가 삽입되어 있지 않습니다.



앞에서 보았던 이 두 잉크를 꺼내봅니다.



꺼내자마자 샷 한장 더!



앞에서 설명드린 거와 같이

슬롯의 왼쪽에는 컬러, 오른쪽에는 검은 잉크를 넣어주세요~!



두 잉크가 정상적으로 설치되었습니다!



다시 경고 램프는 문제 없다는 듯 조용히 꺼지는군요.



인쇄를 위해 용지를 넣어주고



종이 밭침대도 늘려주면



드디어 설치 완료!



이렇게 용지가 인쇄되기를 학수고대 해준다면



보시는 바와 같이 프린터가 정상적으로 용지를 인쇄하는 거을 확인하실 수 있습니다!


 혹시 Canon PIXMA MG2900 시리즈의 무선 상태에서 드라이버를 설치하는 방법에 대해 알고 싶으신 분은 아래 링크를 참조해 주시길 바랍니다.


http://elecs.tistory.com/101

300x250